commit 013c6f3b4df09103f62298c1307416f088a32b11 Author: Diavolo Date: Thu Apr 13 17:30:38 2023 +0200 init diff --git a/codescripts/character.gsc b/codescripts/character.gsc new file mode 100644 index 0000000..a0c40b0 --- /dev/null +++ b/codescripts/character.gsc @@ -0,0 +1,199 @@ + + +function setModelFromArray(a) +{ + self setModel(a[randomint(a.size)]); +} + +function randomElement(a) +{ + return a[randomint(a.size)]; +} + +function attachFromArray(a) +{ + self attach(randomElement(a), "", true); +} + +function newcharacter() +{ + self detachAll(); + oldGunHand = self.anim_gunHand; + if (!isdefined(oldGunHand)) + return; + self.anim_gunHand = "none"; + self [[anim.PutGunInHand]](oldGunHand); +} + +function save() +{ + info["gunHand"] = self.anim_gunHand; + info["gunInHand"] = self.anim_gunInHand; + info["model"] = self.model; + info["hatModel"] = self.hatModel; + info["gearModel"] = self.gearModel; + if (isdefined (self.name)) + { + info["name"] = self.name; + /#println ("Save: Guy has name ", self.name);#/ + } + else + { + /#println ("save: Guy had no name!");#/ + } + + attachSize = self getAttachSize(); + for (i = 0; i < attachSize; i++) + { + info["attach"][i]["model"] = self getAttachModelName(i); + info["attach"][i]["tag"] = self getAttachTagName(i); + } + return info; +} + +function load(info) +{ + self detachAll(); + self.anim_gunHand = info["gunHand"]; + self.anim_gunInHand = info["gunInHand"]; + self setModel(info["model"]); + self.hatModel = info["hatModel"]; + self.gearModel = info["gearModel"]; + if (isdefined (info["name"])) + { + self.name = info["name"]; + /#println ("Load: Guy has name ", self.name);#/ + } + else + { + /#println ("Load: Guy had no name!");#/ + } + + attachInfo = info["attach"]; + attachSize = attachInfo.size; + for (i = 0; i < attachSize; i++) + self attach(attachInfo[i]["model"], attachInfo[i]["tag"]); +} + +/* +function sample save/precache/load usage (precache is only required if there are any waits in the level script before load): + +save: + info = foley character::save(); + game["foley"] = info; + changelevel("burnville", 0, true); + +precache: + character::precache(game["foley"]); + +load: + foley character::load(game["foley"]); + +*/ + +function get_random_character( amount ) +{ + self_info = strtok( self.classname, "_" ); + if ( self_info.size <= 2 ) + { + // some custom guy that doesn't use standard naming convention + return randomint( amount ); + } + + + group = "auto"; // by default the type is an auto-selected character + index = undefined; + prefix = self_info[ 2 ]; // merc, marine, etc + + // the designer can overwrite the character + if ( isdefined( self.script_char_index ) ) + { + index = self.script_char_index; + } + + // the designer can hint that this guy is a member of a group of like - spawned guys, so he should use a different index + if ( isdefined( self.script_char_group ) ) + { + type = "grouped"; + group = "group_" + self.script_char_group; + } + + if ( !isdefined( level.character_index_cache ) ) + { + // separately store script grouped guys and auto guys so that they dont influence each other + level.character_index_cache = []; + } + + if ( !isdefined( level.character_index_cache[ prefix ] ) ) + { + // separately store script grouped guys and auto guys so that they dont influence each other + level.character_index_cache[ prefix ] = []; + } + + if ( !isdefined( level.character_index_cache[ prefix ][ group ] ) ) + { + initialize_character_group( prefix, group, amount ); + } + + if ( !isdefined( index ) ) + { + index = get_least_used_index( prefix, group ); + + if ( !isdefined( index ) ) + { + // fail safe + index = randomint( 5000 ); + } + } + + + while ( index >= amount ) + { + index -= amount; + } + + level.character_index_cache[ prefix ][ group ][ index ]++; + + return index; +} + +function get_least_used_index( prefix, group ) +{ + lowest_indices = []; + lowest_use = level.character_index_cache[ prefix ][ group ][ 0 ]; + lowest_indices[ 0 ] = 0; + + for ( i = 1; i < level.character_index_cache[ prefix ][ group ].size; i++ ) + { + if ( level.character_index_cache[ prefix ][ group ][ i ] > lowest_use ) + { + continue; + } + + if ( level.character_index_cache[ prefix ][ group ][ i ] < lowest_use ) + { + // if its the new lowest, start over on the array + lowest_indices = []; + lowest_use = level.character_index_cache[ prefix ][ group ][ i ]; + } + + // the equal amounts end up in the array + lowest_indices[ lowest_indices.size ] = i; + } + assert( lowest_indices.size, "Tried to spawn a character but the lowest indices didn't exist" ); + return random( lowest_indices ); +} + +function initialize_character_group( prefix, group, amount ) +{ + for ( i = 0; i < amount; i++ ) + { + level.character_index_cache[ prefix ][ group ][ i ] = 0; + } +} + +function random( array ) +{ + keys = GetArrayKeys( array ); + return array[ keys[RandomInt( keys.size )] ]; +} diff --git a/codescripts/delete.csc b/codescripts/delete.csc new file mode 100644 index 0000000..fbec8b6 --- /dev/null +++ b/codescripts/delete.csc @@ -0,0 +1,26 @@ + + +function main() +{ + assert(isdefined(self)); + wait 0; + if (isdefined(self)) + { + /# + if(isdefined(self.classname)) + { + if( self.classname == "trigger_once" || self.classname == "trigger_radius" || self.classname == "trigger_multiple" ) + { + println( "" ); + println( "*** trigger debug: delete.gsc is deleting trigger with ent#: " + self getentitynumber() + " at origin: " + self.origin ); + + //players = GetPlayers( localClientNum ); + //println( "*** trigger debug: player origin is: " + players[0].origin ); + + println( "" ); + } + } + #/ + self delete(); + } +} diff --git a/codescripts/delete.gsc b/codescripts/delete.gsc new file mode 100644 index 0000000..3d46dff --- /dev/null +++ b/codescripts/delete.gsc @@ -0,0 +1,26 @@ + + +function main() +{ + assert(isdefined(self)); + wait 0; + if (isdefined(self)) + { + /# + if(isdefined(self.classname)) + { + if( self.classname == "trigger_once" || self.classname == "trigger_radius" || self.classname == "trigger_multiple" ) + { + println( "" ); + println( "*** trigger debug: delete.gsc is deleting trigger with ent#: " + self getentitynumber() + " at origin: " + self.origin ); + + //players = GetPlayers(); + //println( "*** trigger debug: player origin is: " + players[0].origin ); + + println( "" ); + } + } + #/ + self delete(); + } +} diff --git a/codescripts/struct.csc b/codescripts/struct.csc new file mode 100644 index 0000000..2d2addd --- /dev/null +++ b/codescripts/struct.csc @@ -0,0 +1,401 @@ +#using scripts\shared\scene_shared; + + + +function autoexec __init__() +{ + // set up arrays even if there are no structs in the level + + if ( !isdefined( level.struct ) ) + { + init_structs(); + } +} + +function init_structs() +{ + level.struct = []; + level.scriptbundles = []; + level.scriptbundlelists = []; + + level.struct_class_names = []; + level.struct_class_names[ "target" ] = []; + level.struct_class_names[ "targetname" ] = []; + level.struct_class_names[ "script_noteworthy" ] = []; + level.struct_class_names[ "script_linkname" ] = []; + level.struct_class_names[ "script_label" ] = []; + level.struct_class_names[ "classname" ] = []; + level.struct_class_names[ "script_unitrigger_type" ] = []; + level.struct_class_names[ "scriptbundlename" ] = []; +} + +function remove_unneeded_kvps( struct ) +{ +// struct.vmtype = undefined; +// struct.type = undefined; + struct.igdtseqnum = undefined; + struct.configstringfiletype = undefined; + /#devstate = struct.devstate;#/ + struct.devstate = undefined; + /#struct.devstate = devstate;#/ +} + +function CreateStruct( struct, type, name ) +{ + if ( !isdefined( level.struct ) ) + { + init_structs(); + } + + if ( isdefined( type ) ) + { + isFrontend = ( GetDvarString( "mapname" ) == "core_frontend" ); + if ( !isdefined( level.scriptbundles[ type ] ) ) + { + level.scriptbundles[ type ] = []; + } + + if ( isdefined( level.scriptbundles[ type ][ name ] ) ) + { + return level.scriptbundles[ type ][ name ]; + } + + if ( type == "scene" ) + { + level.scriptbundles[ type ][ name ] = scene::remove_invalid_scene_objects( struct ); + } + //////////////////////////////////////////////////////////////////////// + // clean out lots of shared data that is not specific to a game mode + /////////////////////////////////////////////////////////////////////// + else if( !( SessionModeIsMultiplayerGame() || isFrontend ) && type == "mpdialog_player" ) + { + // do nothing, save vars + } + else if( !( SessionModeIsMultiplayerGame() || isFrontend ) && type == "gibcharacterdef" && IsSubStr(name,"c_t7_mp_") ) + { + // do nothing, save vars + } + else if( !( SessionModeIsCampaignGame() || isFrontend ) && type == "collectibles" ) + { + // do nothing, save vars + } + ///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + else + { + level.scriptbundles[ type ][ name ] = struct; + } + + remove_unneeded_kvps( struct ); + } + else + { + struct init(); + } +} + +function CreateScriptBundleList( items, type, name ) +{ + if ( !isdefined( level.struct ) ) + { + init_structs(); + } + + level.scriptbundlelists[ type ][ name ] = items; +} + +function init() +{ + if ( !isdefined( level.struct ) ) level.struct = []; else if ( !IsArray( level.struct ) ) level.struct = array( level.struct ); level.struct[level.struct.size]=self;; + + if(!isdefined(self.angles))self.angles=( 0, 0, 0 ); + + if ( isdefined( self.targetname ) ) + { + if ( !isdefined( level.struct_class_names[ "targetname" ][ self.targetname ] ) ) level.struct_class_names[ "targetname" ][ self.targetname ] = []; else if ( !IsArray( level.struct_class_names[ "targetname" ][ self.targetname ] ) ) level.struct_class_names[ "targetname" ][ self.targetname ] = array( level.struct_class_names[ "targetname" ][ self.targetname ] ); level.struct_class_names[ "targetname" ][ self.targetname ][level.struct_class_names[ "targetname" ][ self.targetname ].size]=self;; + } + + if ( isdefined( self.target ) ) + { + if ( !isdefined( level.struct_class_names[ "target" ][ self.target ] ) ) level.struct_class_names[ "target" ][ self.target ] = []; else if ( !IsArray( level.struct_class_names[ "target" ][ self.target ] ) ) level.struct_class_names[ "target" ][ self.target ] = array( level.struct_class_names[ "target" ][ self.target ] ); level.struct_class_names[ "target" ][ self.target ][level.struct_class_names[ "target" ][ self.target ].size]=self;; + } + + if ( isdefined( self.script_noteworthy ) ) + { + if ( !isdefined( level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ] ) ) level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ] = []; else if ( !IsArray( level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ] ) ) level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ] = array( level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ] ); level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ][level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ].size]=self;; + } + + if ( isdefined( self.script_linkname ) ) + { + Assert( !isdefined( level.struct_class_names[ "script_linkname" ][ self.script_linkname ] ), "Two structs have the same linkname" ); + level.struct_class_names[ "script_linkname" ][ self.script_linkname ][ 0 ] = self; + } + + if ( isdefined( self.script_label ) ) + { + if ( !isdefined( level.struct_class_names[ "script_label" ][ self.script_label ] ) ) level.struct_class_names[ "script_label" ][ self.script_label ] = []; else if ( !IsArray( level.struct_class_names[ "script_label" ][ self.script_label ] ) ) level.struct_class_names[ "script_label" ][ self.script_label ] = array( level.struct_class_names[ "script_label" ][ self.script_label ] ); level.struct_class_names[ "script_label" ][ self.script_label ][level.struct_class_names[ "script_label" ][ self.script_label ].size]=self;; + } + + if ( isdefined( self.classname ) ) + { + if ( !isdefined( level.struct_class_names[ "classname" ][ self.classname ] ) ) level.struct_class_names[ "classname" ][ self.classname ] = []; else if ( !IsArray( level.struct_class_names[ "classname" ][ self.classname ] ) ) level.struct_class_names[ "classname" ][ self.classname ] = array( level.struct_class_names[ "classname" ][ self.classname ] ); level.struct_class_names[ "classname" ][ self.classname ][level.struct_class_names[ "classname" ][ self.classname ].size]=self;; + } + + if ( isdefined( self.script_unitrigger_type ) ) + { + if ( !isdefined( level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ] ) ) level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ] = []; else if ( !IsArray( level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ] ) ) level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ] = array( level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ] ); level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ][level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ].size]=self;; + } + + if ( isdefined( self.scriptbundlename ) ) + { + if ( !isdefined( level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ] ) ) level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ] = []; else if ( !IsArray( level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ] ) ) level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ] = array( level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ] ); level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ][level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ].size]=self;; + } +} + + +/@ +"Name: get( , [kvp_key] )" +"Summary: Returns a struct with the specified kvp." +"MandatoryArg: : kvp value" +"OptionalArg: [kvp_key] : defaults to targetname" +"Example: struct::get( "some_value", "targetname" );" +"SPMP: both" +@/ +function get( kvp_value, kvp_key = "targetname" ) +{ + if ( !isdefined( kvp_value ) ) + { + return undefined; + } + + if ( isdefined( level.struct_class_names[ kvp_key ][ kvp_value ] ) ) + { + /# + if ( level.struct_class_names[ kvp_key ][ kvp_value ].size > 1 ) + { + AssertMsg( "struct::get used for more than one struct with kvp '" + kvp_key + "' = '" + kvp_value + "'." ); + return undefined; + } + #/ + + return level.struct_class_names[ kvp_key ][ kvp_value ][ 0 ]; + } +} + +/@ +"Name: spawn( [v_origin], [v_angles] )" +"Summary: Returns a new struct." +"OptionalArg: [v_origin] : optional origin" +"OptionalArg: [v_angles] : optional angles" +"Example: s = struct::spawn( self GetTagOrigin( "tag_origin" ) );" +@/ +function spawn( v_origin = (0, 0, 0), v_angles = (0, 0, 0) ) +{ + s = SpawnStruct(); + s.origin = v_origin; + s.angles = v_angles; + return s; +} + +/@ +"Name: get_array( , [kvp_key] )" +"Summary: Returns an array of structs with the specified kvp." +"MandatoryArg: : kvp value" +"OptionalArg: [kvp_key] : defaults to targetname" +"Example: fxemitters = struct::get_array( "streetlights", "targetname" )" +"SPMP: both" +@/ +function get_array( kvp_value, kvp_key = "targetname" ) +{ + if ( isdefined( level.struct_class_names[ kvp_key ][ kvp_value ] ) ) + { + return ArrayCopy( level.struct_class_names[ kvp_key ][ kvp_value ] ); + } + + return []; +} + +function delete() +{ + if ( isdefined( self.target ) ) + { + ArrayRemoveValue( level.struct_class_names[ "target" ][ self.target ], self); + } + + if ( isdefined( self.targetname ) ) + { + ArrayRemoveValue( level.struct_class_names[ "targetname" ][ self.targetname ], self); + } + + if ( isdefined( self.script_noteworthy ) ) + { + ArrayRemoveValue( level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ], self); + } + + if ( isdefined( self.script_linkname ) ) + { + ArrayRemoveValue( level.struct_class_names[ "script_linkname" ][ self.script_linkname ], self); + } + + if ( isdefined( self.script_label) ) + { + ArrayRemoveValue( level.struct_class_names[ "script_label" ][ self.script_label ], self); + } + + if ( isdefined( self.classname) ) + { + ArrayRemoveValue( level.struct_class_names[ "classname" ][ self.classname ], self); + } + + if ( isdefined( self.script_unitrigger_type ) ) + { + ArrayRemoveValue( level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ], self); + } + + if ( isdefined( self.scriptbundlename ) ) + { + ArrayRemoveValue( level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ], self); + } +} + +/@ +"Name: get_script_bundle( , )" +"Summary: Returns a struct with the specified script bundle definition. This is the GDT data for the bundle." +"MandatoryArg: : The type of the script bundle" +"MandatoryArg: : The name of the script bundle" +"Example: struct::get_script_bundle( "scene", "my_scene" );" +"SPMP: both" +@/ +function get_script_bundle( str_type, str_name ) +{ + if ( isdefined( level.scriptbundles[ str_type ] ) && isdefined( level.scriptbundles[ str_type ][ str_name ] ) ) + { + return level.scriptbundles[ str_type ][ str_name ]; + } +} + +/@ +"Name: delete_script_bundle( , )" +"Summary: Deletes the specified script bundle definition. This is the GDT data for the bundle." +"MandatoryArg: : The type of the script bundle" +"MandatoryArg: : The name of the script bundle" +"Example: struct::delete_script_bundle( "scene", "my_scene" );" +"SPMP: both" +@/ +function delete_script_bundle( str_type, str_name ) +{ + if ( isdefined( level.scriptbundles[ str_type ] ) && isdefined( level.scriptbundles[ str_type ][ str_name ] ) ) + { + level.scriptbundles[ str_type ][ str_name ] = undefined; + } +} + +/@ +"Name: get_script_bundle( , )" +"Summary: Returns a struct with the specified script bundle definition. This is the GDT data for the bundle." +"MandatoryArg: : The type of the script bundle" +"MandatoryArg: : The name of the script bundle" +"Example: struct::get_script_bundle( "scene", "my_scene" );" +"SPMP: both" +@/ +function get_script_bundles_of_type( str_type ) +{ + if ( isdefined( level.scriptbundles[ str_type ] ) ) + { + return ArrayCopy( level.scriptbundles[ str_type ] ); + } +} + +/@ +"Name: get_script_bundles( )" +"Summary: Returns all of the script bundle definition structs for the specified type." +"MandatoryArg: : The type of the script bundle" +"Example: struct::get_script_bundles( "scene" );" +"SPMP: both" +@/ +function get_script_bundles( str_type ) +{ + if ( isdefined( level.scriptbundles ) && isdefined( level.scriptbundles[ str_type ] ) ) + { + return level.scriptbundles[ str_type ]; + } + + return []; +} + +/@ +"Name: get_script_bundle_list( , )" +"Summary: Returns a string array with the items specified by the script bundle list." +"MandatoryArg: : The type of the script bundle in the list" +"MandatoryArg: : The name of the script bundle list" +"Example: struct::get_script_bundle_list( "collectible", "completecollectibleslist" );" +"SPMP: both" +@/ +function get_script_bundle_list( str_type, str_name ) +{ + if ( isdefined( level.scriptbundlelists[ str_type ] ) && isdefined( level.scriptbundlelists[ str_type ][ str_name ] ) ) + { + return level.scriptbundlelists[ str_type ][ str_name ]; + } +} + +/@ +"Name: get_script_bundle_instances( , [str_name] )" +"Summary: Returns an array of all the script bundle instances with the specified script bundle definition and name." +"MandatoryArg: : The type of the script bundle" +"MandatoryArg: [str_name] : The name of the script bundle" +"Example: struct::get_script_bundle_instances( "scene", "my_scene" );" +"SPMP: both" +@/ +function get_script_bundle_instances( str_type, str_name = "" ) +{ + a_instances = struct::get_array( "scriptbundle_" + str_type, "classname" ); + + if ( str_name != "" ) + { + foreach ( i, s_instance in a_instances ) + { + if ( s_instance.name != str_name ) + { + ArrayRemoveIndex( a_instances, i, true ); + } + } + } + + return a_instances; +} + +//please don't ever call this from script - it's only for radiant live +// Search in each of the arrays for our desired struct +// It is done this way because we have deleted the level.struct array +// We are sacrificing performance in Radiant LiveUpdate in exchange +// for freeing up variables in the game +function FindStruct( position ) +{ + foreach ( key, _ in level.struct_class_names ) + { + foreach ( val, s_array in level.struct_class_names[ key ] ) + { + foreach ( struct in s_array ) + { + if( DistanceSquared( struct.origin, position ) < 1 ) + { + return struct; + } + } + } + } + + if( isdefined( level.struct ) ) + { + foreach( struct in level.struct ) + { + if( DistanceSquared( struct.origin, position ) < 1 ) + { + return struct; + } + } + } + + return undefined; +} \ No newline at end of file diff --git a/codescripts/struct.gsc b/codescripts/struct.gsc new file mode 100644 index 0000000..e8e664b --- /dev/null +++ b/codescripts/struct.gsc @@ -0,0 +1,420 @@ +#using scripts\shared\scene_shared; + + + +function autoexec __init__() +{ + // set up arrays even if there are no structs in the level + + if ( !isdefined( level.struct ) ) + { + init_structs(); + } +} + +function init_structs() +{ + level.struct = []; + level.scriptbundles = []; + level.scriptbundlelists = []; + + level.struct_class_names = []; + level.struct_class_names[ "target" ] = []; + level.struct_class_names[ "targetname" ] = []; + level.struct_class_names[ "script_noteworthy" ] = []; + level.struct_class_names[ "script_linkname" ] = []; + level.struct_class_names[ "script_label" ] = []; + level.struct_class_names[ "classname" ] = []; + level.struct_class_names[ "script_unitrigger_type" ] = []; + level.struct_class_names[ "scriptbundlename" ] = []; + level.struct_class_names[ "prefabname" ] = []; +} + +function remove_unneeded_kvps( struct ) +{ +// struct.vmtype = undefined; +// struct.type = undefined; + struct.igdtseqnum = undefined; + struct.configstringfiletype = undefined; + /#devstate = struct.devstate;#/ + struct.devstate = undefined; + /#struct.devstate = devstate;#/ +} + +function CreateStruct( struct, type, name ) +{ + if ( !isdefined( level.struct ) ) + { + init_structs(); + } + + if ( isdefined( type ) ) + { + isFrontend = ( GetDvarString( "mapname" ) == "core_frontend" ); + if ( !isdefined( level.scriptbundles[ type ] ) ) + { + level.scriptbundles[ type ] = []; + } + + if ( isdefined( level.scriptbundles[ type ][ name ] ) ) + { + return level.scriptbundles[ type ][ name ]; + } + + if ( type == "scene" ) + { + level.scriptbundles[ type ][ name ] = scene::remove_invalid_scene_objects( struct ); + } + //////////////////////////////////////////////////////////////////////// + // clean out lots of shared data that is not specific to a game mode + /////////////////////////////////////////////////////////////////////// + else if( !( SessionModeIsMultiplayerGame() || isFrontend ) && type == "mpdialog_player" ) + { + // do nothing, save vars + } + else if( !( SessionModeIsMultiplayerGame() || isFrontend ) && type == "gibcharacterdef" && IsSubStr(name,"c_t7_mp_") ) + { + // do nothing, save vars + } + else if( !( SessionModeIsCampaignGame() || isFrontend ) && type == "collectibles" ) + { + // do nothing, save vars + } + ///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + else + { + level.scriptbundles[ type ][ name ] = struct; + } + + remove_unneeded_kvps(struct); + } + else + { + struct init(); + } +} + +function CreateScriptBundleList( items, assetType, name ) +{ + if ( !isdefined( level.struct ) ) + { + init_structs(); + } + + level.scriptbundlelists[ assetType ][ name ] = items; +} + +function init() +{ + if ( !isdefined( level.struct ) ) level.struct = []; else if ( !IsArray( level.struct ) ) level.struct = array( level.struct ); level.struct[level.struct.size]=self;; + + if(!isdefined(self.angles))self.angles=( 0, 0, 0 ); + + if ( isdefined( self.targetname ) ) + { + if ( !isdefined( level.struct_class_names[ "targetname" ][ self.targetname ] ) ) level.struct_class_names[ "targetname" ][ self.targetname ] = []; else if ( !IsArray( level.struct_class_names[ "targetname" ][ self.targetname ] ) ) level.struct_class_names[ "targetname" ][ self.targetname ] = array( level.struct_class_names[ "targetname" ][ self.targetname ] ); level.struct_class_names[ "targetname" ][ self.targetname ][level.struct_class_names[ "targetname" ][ self.targetname ].size]=self;; + } + + if ( isdefined( self.target ) ) + { + if ( !isdefined( level.struct_class_names[ "target" ][ self.target ] ) ) level.struct_class_names[ "target" ][ self.target ] = []; else if ( !IsArray( level.struct_class_names[ "target" ][ self.target ] ) ) level.struct_class_names[ "target" ][ self.target ] = array( level.struct_class_names[ "target" ][ self.target ] ); level.struct_class_names[ "target" ][ self.target ][level.struct_class_names[ "target" ][ self.target ].size]=self;; + } + + if ( isdefined( self.script_noteworthy ) ) + { + if ( !isdefined( level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ] ) ) level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ] = []; else if ( !IsArray( level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ] ) ) level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ] = array( level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ] ); level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ][level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ].size]=self;; + } + + if ( isdefined( self.script_linkname ) ) + { + Assert( !isdefined( level.struct_class_names[ "script_linkname" ][ self.script_linkname ] ), "Two structs have the same linkname" ); + level.struct_class_names[ "script_linkname" ][ self.script_linkname ][ 0 ] = self; + } + + if ( isdefined( self.script_label ) ) + { + if ( !isdefined( level.struct_class_names[ "script_label" ][ self.script_label ] ) ) level.struct_class_names[ "script_label" ][ self.script_label ] = []; else if ( !IsArray( level.struct_class_names[ "script_label" ][ self.script_label ] ) ) level.struct_class_names[ "script_label" ][ self.script_label ] = array( level.struct_class_names[ "script_label" ][ self.script_label ] ); level.struct_class_names[ "script_label" ][ self.script_label ][level.struct_class_names[ "script_label" ][ self.script_label ].size]=self;; + } + + if ( isdefined( self.classname ) ) + { + if ( !isdefined( level.struct_class_names[ "classname" ][ self.classname ] ) ) level.struct_class_names[ "classname" ][ self.classname ] = []; else if ( !IsArray( level.struct_class_names[ "classname" ][ self.classname ] ) ) level.struct_class_names[ "classname" ][ self.classname ] = array( level.struct_class_names[ "classname" ][ self.classname ] ); level.struct_class_names[ "classname" ][ self.classname ][level.struct_class_names[ "classname" ][ self.classname ].size]=self;; + } + + if ( isdefined( self.script_unitrigger_type ) ) + { + if ( !isdefined( level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ] ) ) level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ] = []; else if ( !IsArray( level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ] ) ) level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ] = array( level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ] ); level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ][level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ].size]=self;; + } + + if ( isdefined( self.scriptbundlename ) ) + { + if ( !isdefined( level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ] ) ) level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ] = []; else if ( !IsArray( level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ] ) ) level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ] = array( level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ] ); level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ][level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ].size]=self;; + } + + if ( isdefined( self.prefabname ) ) + { + if ( !isdefined( level.struct_class_names[ "prefabname" ][ self.prefabname ] ) ) level.struct_class_names[ "prefabname" ][ self.prefabname ] = []; else if ( !IsArray( level.struct_class_names[ "prefabname" ][ self.prefabname ] ) ) level.struct_class_names[ "prefabname" ][ self.prefabname ] = array( level.struct_class_names[ "prefabname" ][ self.prefabname ] ); level.struct_class_names[ "prefabname" ][ self.prefabname ][level.struct_class_names[ "prefabname" ][ self.prefabname ].size]=self;; + } +} + +/@ +"Name: get( , [kvp_key] )" +"Summary: Returns a struct with the specified kvp." +"MandatoryArg: : kvp value" +"OptionalArg: [kvp_key] : defaults to targetname" +"Example: struct::get( "some_value", "targetname" );" +"SPMP: both" +@/ +function get( kvp_value, kvp_key = "targetname" ) +{ + if ( isdefined( level.struct_class_names[ kvp_key ] ) && isdefined( level.struct_class_names[ kvp_key ][ kvp_value ] ) ) + { + /# + if ( level.struct_class_names[ kvp_key ][ kvp_value ].size > 1 ) + { + AssertMsg( "struct::get used for more than one struct with kvp '" + kvp_key + "' = '" + kvp_value + "'." ); + return undefined; + } + #/ + + return level.struct_class_names[ kvp_key ][ kvp_value ][ 0 ]; + } +} + +/@ +"Name: spawn( [v_origin], [v_angles] )" +"Summary: Returns a new struct." +"OptionalArg: [v_origin] : optional origin" +"OptionalArg: [v_angles] : optional angles" +"Example: s = struct::spawn( self GetTagOrigin( "tag_origin" ) );" +@/ +function spawn( v_origin = (0, 0, 0), v_angles = (0, 0, 0) ) +{ + s = SpawnStruct(); + s.origin = v_origin; + s.angles = v_angles; + return s; +} + +/@ +"Name: get_array( , [kvp_key] )" +"Summary: Returns an array of structs with the specified kvp." +"MandatoryArg: : kvp value" +"OptionalArg: [kvp_key] : defaults to targetname" +"Example: fxemitters = struct::get_array( "streetlights", "targetname" )" +"SPMP: both" +@/ +function get_array( kvp_value, kvp_key = "targetname" ) +{ + if ( isdefined( level.struct_class_names[ kvp_key ][ kvp_value ] ) ) + { + return ArrayCopy( level.struct_class_names[ kvp_key ][ kvp_value ] ); + } + + return []; +} + +function delete() +{ + if ( isdefined( self.target ) ) + { + ArrayRemoveValue( level.struct_class_names[ "target" ][ self.target ], self); + } + + if ( isdefined( self.targetname ) ) + { + ArrayRemoveValue( level.struct_class_names[ "targetname" ][ self.targetname ], self); + } + + if ( isdefined( self.script_noteworthy ) ) + { + ArrayRemoveValue( level.struct_class_names[ "script_noteworthy" ][ self.script_noteworthy ], self); + } + + if ( isdefined( self.script_linkname ) ) + { + ArrayRemoveValue( level.struct_class_names[ "script_linkname" ][ self.script_linkname ], self); + } + + if ( isdefined( self.script_label) ) + { + ArrayRemoveValue( level.struct_class_names[ "script_label" ][ self.script_label ], self); + } + + if ( isdefined( self.classname) ) + { + ArrayRemoveValue( level.struct_class_names[ "classname" ][ self.classname ], self); + } + + if ( isdefined( self.script_unitrigger_type ) ) + { + ArrayRemoveValue( level.struct_class_names[ "script_unitrigger_type" ][ self.script_unitrigger_type ], self); + } + + if ( isdefined( self.scriptbundlename ) ) + { + ArrayRemoveValue( level.struct_class_names[ "scriptbundlename" ][ self.scriptbundlename ], self); + } + + if ( isdefined( self.prefabname ) ) + { + ArrayRemoveValue( level.struct_class_names[ "prefabname" ][ self.prefabname ], self); + } +} + +/@ +"Name: get_script_bundle( , )" +"Summary: Returns a struct with the specified script bundle definition. This is the GDT data for the bundle." +"MandatoryArg: : The type of the script bundle" +"MandatoryArg: : The name of the script bundle" +"Example: struct::get_script_bundle( "scene", "my_scene" );" +"SPMP: both" +@/ +function get_script_bundle( str_type, str_name ) +{ + if ( isdefined( level.scriptbundles[ str_type ] ) && isdefined( level.scriptbundles[ str_type ][ str_name ] ) ) + { + return level.scriptbundles[ str_type ][ str_name ]; + } +} + +/@ +"Name: delete_script_bundle( , )" +"Summary: Deletes the specified script bundle definition. This is the GDT data for the bundle." +"MandatoryArg: : The type of the script bundle" +"MandatoryArg: : The name of the script bundle" +"Example: struct::delete_script_bundle( "scene", "my_scene" );" +"SPMP: both" +@/ +function delete_script_bundle( str_type, str_name ) +{ + if ( isdefined( level.scriptbundles[ str_type ] ) && isdefined( level.scriptbundles[ str_type ][ str_name ] ) ) + { + level.scriptbundles[ str_type ][ str_name ] = undefined; + } +} + +/@ +"Name: get_script_bundles( )" +"Summary: Returns all of the script bundle definition structs for the specified type." +"MandatoryArg: : The type of the script bundle" +"Example: struct::get_script_bundles( "scene" );" +"SPMP: both" +@/ +function get_script_bundles( str_type ) +{ + if ( isdefined( level.scriptbundles ) && isdefined( level.scriptbundles[ str_type ] ) ) + { + return level.scriptbundles[ str_type ]; + } + + return []; +} + +/@ +"Name: get_script_bundle_list( , )" +"Summary: Returns a string array with the items specified by the script bundle list." +"MandatoryArg: : The type of the script bundle in the list" +"MandatoryArg: : The name of the script bundle list" +"Example: struct::get_script_bundle_list( "collectible", "completecollectibleslist" );" +"SPMP: both" +@/ +function get_script_bundle_list( str_type, str_name ) +{ + if ( isdefined( level.scriptbundlelists[ str_type ] ) && isdefined( level.scriptbundlelists[ str_type ][ str_name ] ) ) + { + return level.scriptbundlelists[ str_type ][ str_name ]; + } +} + +/@ +"Name: get_script_bundle_instances( , [str_name] )" +"Summary: Returns an array of all the script bundle instances with the specified script bundle definition and name." +"MandatoryArg: : The type of the script bundle" +"MandatoryArg: [str_name] : The name of the script bundle" +"Example: struct::get_script_bundle_instances( "scene", "my_scene" );" +"SPMP: both" +@/ +function get_script_bundle_instances( str_type, str_name = "" ) +{ + a_instances = struct::get_array( "scriptbundle_" + str_type, "classname" ); + + if ( str_name != "" ) + { + foreach ( i, s_instance in a_instances ) + { + if ( s_instance.name != str_name ) + { + ArrayRemoveIndex( a_instances, i, true ); + } + } + } + + return a_instances; +} + +//please don't ever call this from script - it's only for radiant live +// Search in each of the arrays for our desired struct +// It is done this way because we have deleted the level.struct array +// We are sacrificing performance in Radiant LiveUpdate in exchange +// for freeing up variables in the game +function FindStruct( param1, name, index ) +{ + if( IsVec( param1 ) ) + { + position = param1; + + foreach ( key, _ in level.struct_class_names ) + { + foreach ( val, s_array in level.struct_class_names[ key ] ) + { + foreach ( struct in s_array ) + { + if( DistanceSquared( struct.origin, position ) < 1 ) + { + return struct; + } + } + } + } + + if( isdefined( level.struct ) ) + { + foreach( struct in level.struct ) + { + if( DistanceSquared( struct.origin, position ) < 1 ) + { + return struct; + } + } + } + } + else + { + s = get(param1); + + if( isdefined(s) ) + { + return s; + } + + s = get_script_bundle( param1, name ); + if( isdefined( s ) ) + { + if( index < 0 ) + { + return s; + } + else + { + if( isdefined( s.objects ) ) + { + return s.objects[index]; + } + } + } + } + + return undefined; +} \ No newline at end of file diff --git a/core/_multi_extracam.csc b/core/_multi_extracam.csc new file mode 100644 index 0000000..3199338 --- /dev/null +++ b/core/_multi_extracam.csc @@ -0,0 +1,60 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; + + + +#using scripts\shared\util_shared; + +#namespace multi_extracam; + +function extracam_reset_index( localClientNum, index ) +{ + if( !isdefined(level.camera_ents) || !isdefined(level.camera_ents[localClientNum]) ) + { + return; + } + + if( isdefined( level.camera_ents[localClientNum][index] ) ) + { + level.camera_ents[localClientNum][index] ClearExtraCam(); + level.camera_ents[localClientNum][index] Delete(); + level.camera_ents[localClientNum][index] = undefined; + } +} + +function extracam_init_index( localClientNum, target, index ) +{ + cameraStruct = struct::get( target, "targetname" ); + return extracam_init_item( localClientNum, cameraStruct, index ); +} + +function extracam_init_item( localClientNum, copy_ent, index ) +{ + if(!isdefined(level.camera_ents))level.camera_ents=[]; + + if( !isdefined(level.camera_ents[localClientNum]) ) + { + level.camera_ents[localClientNum] = []; + } + + if( isdefined( level.camera_ents[localClientNum][index] ) ) + { + level.camera_ents[localClientNum][index] ClearExtraCam(); + level.camera_ents[localClientNum][index] Delete(); + level.camera_ents[localClientNum][index] = undefined; + } + + if ( isdefined( copy_ent ) ) + { + level.camera_ents[localClientNum][index] = Spawn( localClientNum, copy_ent.origin, "script_origin" ); + level.camera_ents[localClientNum][index].angles = copy_ent.angles; + + level.camera_ents[localClientNum][index] SetExtraCam( index ); + return level.camera_ents[localClientNum][index]; + } + + return undefined; +} \ No newline at end of file diff --git a/cp/_objectives.gsc b/cp/_objectives.gsc new file mode 100644 index 0000000..7a4f8b8 --- /dev/null +++ b/cp/_objectives.gsc @@ -0,0 +1,873 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\lui_shared; +#using scripts\shared\objpoints_shared; +#using scripts\shared\system_shared; +#using scripts\shared\trigger_shared; +#using scripts\shared\util_shared; +#using scripts\shared\callbacks_shared; + + + +#using scripts\cp\_util; + +#precache( "material", "compass_waypoint_target" ); +#precache( "material", "compass_waypoint_captureneutral" ); +#precache( "material", "compass_waypoint_capture" ); +#precache( "material", "compass_waypoint_defend" ); +#precache( "material", "compass_waypoint_captureneutral_a" ); +#precache( "material", "compass_waypoint_capture_a" ); +#precache( "material", "compass_waypoint_defend_a" ); +#precache( "material", "compass_waypoint_captureneutral_b" ); +#precache( "material", "compass_waypoint_capture_b" ); +#precache( "material", "compass_waypoint_defend_b" ); +#precache( "material", "compass_waypoint_captureneutral_c" ); +#precache( "material", "compass_waypoint_capture_c" ); +#precache( "material", "compass_waypoint_defend_c" ); + +#precache( "material", "waypoint_return" ); +#precache( "material", "objective_arrow" ); + +#precache( "eventstring", "comms_event_message" ); +#precache( "string", "CP_SH_CAIRO_PLAYER_READY" ); + + + + + + + +#precache( "lui_menu_data", "obj_x" ); +#precache( "lui_menu_data", "obj_y" ); + +#namespace objectives; + +class cObjective +{ + var m_str_type; + var m_a_game_obj; + var m_a_targets; + var m_str_lui_menu; + + constructor() + { + } + + function init( str_type, a_target_list, b_done = false ) + { + m_a_targets = []; + m_a_game_obj = []; + m_str_type = str_type; + + if ( b_done ) + { + gobj_id = gameobjects::get_next_obj_id(); + m_a_game_obj = array( gobj_id ); + Objective_Add( gobj_id, "done", (0,0,0), iString(str_type) ); + } + else + { + if ( isdefined( a_target_list ) && a_target_list.size > 0 ) + { + foreach( target in a_target_list ) + { + add_target( target ); + } + } + else + { + gobj_id = gameobjects::get_next_obj_id(); + m_a_game_obj = array( gobj_id ); + Objective_Add( gobj_id, "active", (0,0,0), iString(str_type) ); + } + } + } + + function update_value( str_menu_data_name, value ) + { + gobj_id = m_a_game_obj[0]; + Objective_SetUIModelValue( gobj_id, str_menu_data_name, value ); + } + + function update_counter( x_val, y_val ) + { + update_value( "obj_x", x_val ); + + if ( isdefined( y_val ) ) + { + update_value( "obj_y", y_val ); + } + } + + function add_target( target ) + { + // do not add a target twice. + if ( IsInArray( m_a_targets, target ) ) + { + return; + } + + gobj_id = undefined; + if ( m_a_targets.size < m_a_game_obj.size ) + { + gobj_id = m_a_game_obj[m_a_game_obj.size-1]; + } + else + { + gobj_id = gameobjects::get_next_obj_id(); + array::add( m_a_game_obj, gobj_id ); + } + + //Objective_Add can take in an entity or a vector for its 'target' + if ( IsVec( target ) || IsEntity( target ) ) + { + Objective_Add( gobj_id, "active", target, iString(m_str_type) ); + } + else + { + Objective_Add( gobj_id, "active", target.origin, iString(m_str_type) ); + } + + array::add( m_a_targets, target ); + + assert( m_a_targets.size == m_a_game_obj.size ); + } + + function complete( a_target_or_list ) + { + if ( a_target_or_list.size > 0 ) + { + // find specific targets to remove their objective + foreach ( target in a_target_or_list ) + { + for ( i = m_a_targets.size - 1; i >= 0; i-- ) + { + if ( m_a_targets[ i ] == target ) + { + Objective_State( m_a_game_obj[ i ], "done" ); + + ArrayRemoveIndex( m_a_game_obj, i ); + ArrayRemoveIndex( m_a_targets, i ); + + break; + } + } + } + } + else + { + foreach ( n_gobj_id in m_a_game_obj ) + { + Objective_State( n_gobj_id, "done" ); + } + + for ( i = m_a_targets.size - 1; i >= 0; i-- ) + { + ArrayRemoveIndex( m_a_game_obj, i ); + ArrayRemoveIndex( m_a_targets, i ); + } + } + + if ( m_a_game_obj.size == 0 ) + { + ArrayRemoveValue( level.a_objectives, self, true ); + } + } + + //hide waypoint + function hide( e_player ) + { + if(isdefined( e_player) ) + { + Assert( IsPlayer( e_player ), "Passed a non-player entity into cObjective::hide()" ); + + foreach( obj_id in m_a_game_obj ) + { + Objective_SetInvisibleToPlayer( obj_id, e_player ); + } + } + else + { + foreach( obj_id in m_a_game_obj ) + { + Objective_SetInvisibleToAll( obj_id ); + } + } + } + + //show waypoint + function show( e_player ) + { + if(isdefined( e_player) ) + { + Assert( IsPlayer( e_player ), "Passed a non-player entity into cObjective::show()" ); + + foreach( obj_id in m_a_game_obj ) + { + Objective_SetVisibleToPlayer( obj_id, e_player ); + } + } + else + { + foreach( obj_id in m_a_game_obj ) + { + Objective_SetVisibleToAll( obj_id ); + } + } + } + + function hide_for_target( e_target ) + { + foreach( i, obj_id in m_a_game_obj ) + { + ent = m_a_targets[i]; + if ( isdefined( ent ) && ent == e_target ) + { + Objective_State( obj_id, "invisible" ); + return; + } + } + } + + function show_for_target( e_target ) + { + foreach( i, obj_id in m_a_game_obj ) + { + ent = m_a_targets[i]; + if ( isdefined( ent ) && ent == e_target ) + { + Objective_State( obj_id, "active" ); + return; + } + } + } + + function get_id_for_target( e_target ) + { + foreach( i, obj_id in m_a_game_obj ) + { + ent = m_a_targets[i]; + if ( isdefined( ent ) && ent == e_target ) + { + return obj_id; + } + } + return -1; + } + + function is_breadcrumb() + { + return false; + } +} + + + +class cBreadcrumbObjective : cObjective +{ + var m_a_player_game_obj; // array of objective ids, one per player + var m_str_first_trig_targetname; // the targetname of the first trigger in the breadcrumb sequence + var m_done; // at least one of the players has reached the end of their breadcrumb trail + + constructor() + { + } + + function init( str_type, a_target_list, b_done = false ) + { + // call the parent version + cObjective::init( str_type, a_target_list, b_done ); + + // init some fields + m_str_first_trig_targetname = ""; + m_done = b_done; + + // create objectives for max players + m_a_player_game_obj = []; + for ( i = 0; i < 4; i++ ) + { + obj_id = gameobjects::get_next_obj_id(); + m_a_player_game_obj[i] = obj_id; + if ( m_done ) + { + Objective_Add( obj_id, "done", ( 0, 0, 0 ), iString( m_str_type ) ); + } + else + { + Objective_Add( obj_id, "empty", ( 0, 0, 0 ), iString( m_str_type ) ); + } + } + + // hide the actual objective, since each player now has their own + obj_id = m_a_game_obj[0]; + Objective_SetInvisibleToAll( obj_id ); + } + + function complete( a_target_or_list ) + { + // kill the threads + level notify( "breadcrumb_" + m_str_type + "_complete" ); + + // destroy the objectives + for ( i = 0; i < 4; i++ ) + { + obj_id = m_a_player_game_obj[i]; + Objective_State( obj_id, "done" ); + } + + foreach( player in level.players ) + { + player.v_current_active_breadcrumb = undefined; + } + + // call the parent version + cObjective::complete( a_target_or_list ); + } + + function hide( e_player ) + { + if ( isdefined( e_player ) ) + { + Assert( IsPlayer( e_player ), "Passed a non-player entity into cBreadcrumbObjective::hide()" ); + entnum = e_player GetEntityNumber(); + obj_id = m_a_player_game_obj[entnum]; + Objective_SetInvisibleToPlayer( obj_id, e_player ); + } + else + { + for ( i = 0; i < 4; i++ ) + { + obj_id = m_a_player_game_obj[i]; + Objective_SetInvisibleToPlayerByIndex( obj_id, i ); + } + } + } + + function show( e_player ) + { + if ( isdefined( e_player ) ) + { + Assert( IsPlayer( e_player ), "Passed a non-player entity into cBreadcrumbObjective::hide()" ); + entnum = e_player GetEntityNumber(); + obj_id = m_a_player_game_obj[entnum]; + Objective_SetVisibleToPlayer( obj_id, e_player ); + } + else + { + for ( i = 0; i < 4; i++ ) + { + obj_id = m_a_player_game_obj[i]; + Objective_SetVisibleToPlayerByIndex( obj_id, i ); + } + } + } + + function start( str_trig_targetname ) + { + // remember the name of the first trigger + m_str_first_trig_targetname = str_trig_targetname; + m_done = false; + + // create individual objectives for each player + foreach( player in level.players ) + { + add_player( player ); + } + } + + function add_player( player ) + { + // activate the objective + entnum = player GetEntityNumber(); + obj_id = m_a_player_game_obj[entnum]; + Objective_SetInvisibleToAll( obj_id ); + Objective_SetVisibleToPlayer( obj_id, player ); + Objective_State( obj_id, "active" ); + + // spawn a thread to track the player's movement along the breadcrumb trail + thread do_player_breadcrumb( player ); + } + + function private set_player_objective( player, target ) + { + entnum = player GetEntityNumber(); + obj_id = m_a_player_game_obj[entnum]; + + n_breadcrumb_height = 72; + + v_pos = target; + if ( !IsVec( target ) ) + { + v_pos = target.origin; + + if ( isdefined( target.script_height ) ) + { + n_breadcrumb_height = target.script_height; + } + } + + v_pos = util::ground_position( v_pos, 300, n_breadcrumb_height ); + + // cache off the breadcrumb for other systems look up + player.v_current_active_breadcrumb = v_pos; + + Objective_Position( obj_id, v_pos ); + Objective_State( obj_id, "active" ); + } + + function do_player_breadcrumb( player ) + { + level endon( "breadcrumb_" + m_str_type ); + level endon( "breadcrumb_" + m_str_type + "_complete" ); + player endon( "death" ); + + str_trig_targetname = m_str_first_trig_targetname; + entnum = player GetEntityNumber(); + obj_id = m_a_player_game_obj[entnum]; + Objective_SetVisibleToPlayer( obj_id, player ); + do + { + t_current = GetEnt( str_trig_targetname, "targetname" ); + + if ( isdefined( t_current ) ) + { + if ( isdefined( t_current.target ) ) + { + if ( isdefined( t_current.script_flag_true ) ) + { + Objective_SetInvisibleToPlayer( obj_id, player ); + level flag::wait_till( t_current.script_flag_true ); + Objective_SetVisibleToPlayer( obj_id, player ); + } + + s_current = struct::get( t_current.target, "targetname" ); + + if ( isdefined( s_current ) ) + { + set_player_objective( player, s_current ); + } + else + { + set_player_objective( player, t_current ); + } + } + else + { + set_player_objective( player, t_current ); + } + + str_trig_targetname = t_current.target; + //assert( !trigger::is_trigger_once( t_current ), "Trigger at " + t_current.origin + " in breadcrumb chain '" + m_str_first_trig_targetname + "' should not be set to TRIGGER_ONCE." ); + t_current trigger::wait_till( undefined, undefined, player ); + } + else + { + str_trig_targetname = undefined; + } + } + while ( isdefined( str_trig_targetname ) ); + Objective_SetInvisibleToPlayer( obj_id, player ); + + foreach( player in level.players ) + { + player.v_current_active_breadcrumb = undefined; + } + + // done + m_done = true; + } + + function is_breadcrumb() + { + return true; + } + + function is_done() + { + // we're done if at least one of the players has made it to the final breadcrumb + return m_done; + } +} + +function autoexec __init__sytem__() { system::register("objectives",&__init__,undefined,undefined); } + +function __init__() +{ + level.a_objectives = []; // Declares an array of objectives to hold the objective strings. + level.n_obj_index = 0; // This is the objective number associated with the objective strings. + + callback::on_spawned( &on_player_spawned ); +} + +/@ +"Name: set( , , )" +"Summary: Used for all objective-related tasks." +"CallOn: NA" +"Example: objectives::set( "cp_level_lotus_hakim", level.ai_hakim );" +"SPMP: singleplayer" +@/ +function set( str_obj_type, a_target_or_list, b_breadcrumb ) +{ + if(!isdefined(level.a_objectives))level.a_objectives=[]; + if(!isdefined(b_breadcrumb))b_breadcrumb=false; + + // Make an array out if it, if it's only one objective. + if ( !isdefined( a_target_or_list ) ) a_target_or_list = []; else if ( !IsArray( a_target_or_list ) ) a_target_or_list = array( a_target_or_list );; + + o_objective = undefined; + if ( isdefined( level.a_objectives[str_obj_type] ) ) + { + // TODO Slone: Clear old targets here? + o_objective = level.a_objectives[str_obj_type]; + if ( isdefined( a_target_or_list ) ) + { + foreach( target in a_target_or_list ) + { + [[o_objective]]->add_target( target ); + } + } + } + else + { + if ( b_breadcrumb ) + { + o_objective = new cBreadcrumbObjective(); + } + else + { + o_objective = new cObjective(); + } + [[o_objective]]->init( str_obj_type, a_target_or_list ); + level.a_objectives[ str_obj_type ] = o_objective; + } + + return o_objective; +} + +// Mark just one of many targets for this objective as complete. +// +// str_obj_type: the objective string ID we'd like to remove. +// a_target_or_list: the object from which we'd like to remove the marker. +// +/@ +"Name: complete( , )" +"Summary: Mark specific targets for this objective as complete." +"CallOn: NA" +"MandatoryArg: The objective string ID we'd like to remove." +"OptionalArg: The target or targets from which we'd like to remove the marker." +"Example: objectives::complete( "cp_level_lotus_minigun", self );" +"SPMP: singleplayer" +@/ +function complete( str_obj_type, a_target_or_list ) +{ + // Make an array out if it, if it's only one objective. + if ( !isdefined( a_target_or_list ) ) a_target_or_list = []; else if ( !IsArray( a_target_or_list ) ) a_target_or_list = array( a_target_or_list );; + + if ( isdefined( level.a_objectives[str_obj_type] ) ) + { + o_objective = level.a_objectives[str_obj_type]; + [[o_objective]]->complete( a_target_or_list ); + } + else + { + if ( str_obj_type == "cp_waypoint_breadcrumb" ) + { + o_objective = new cBreadcrumbObjective(); + } + else + { + o_objective = new cObjective(); + } + [[o_objective]]->init( str_obj_type, undefined, true ); + level.a_objectives[ str_obj_type ] = o_objective; + } +} + +/@ +"Name: set( , )" +"Summary: Set the objective and automatically set the counter to [0 of a_targets.size]" +"CallOn: NA" +"Example: objectives::set( "objective_kill_targets", a_targets );" +"SPMP: singleplayer" +@/ +function set_with_counter( str_obj_id, a_targets ) +{ + if ( !isdefined( a_targets ) ) a_targets = []; else if ( !IsArray( a_targets ) ) a_targets = array( a_targets );; + o_obj = set( str_obj_id, a_targets ); + [[o_obj]]->update_counter( 0, a_targets.size ); +} + +/@ +"Name: update_counter( , , )" +"Summary: Set the values on an objective with a counter." +"CallOn: NA" +"Example: objectives::update_counter( "objective_kill_targets", 2, a_targets.size );" +"SPMP: singleplayer" +@/ +function update_counter( str_obj_id, x_val, y_val ) +{ + o_obj = level.a_objectives[ str_obj_id ]; + if ( isdefined( o_obj ) ) + { + [[o_obj]]->update_counter( x_val, y_val ); + } +} + +/@ + "Name: set_value( , , )" + "Summary: Set the LUI model values on an objective." + "CallOn: NA" + "Example: objectives::set_value( "objective_kill_targets", "num_targets", a_targets.size );" + "SPMP: singleplayer" +@/ +function set_value( str_obj_id, str_menu_data_name, value ) +{ + o_obj = level.a_objectives[ str_obj_id ]; + if ( isdefined( o_obj ) ) + { + [[o_obj]]->update_value( str_menu_data_name, value ); + } +} + +/@ +"Name: breadcrumb( , [str_obj_id] )" +"Summary: Automatically advances a chain of triggered objective structs." +"Module: Objectives" +"CallOn: Level" +"MandatoryArg: The first trigger in the string of breadcrumbs." +"OptionalArg: [str_obj_id] The objective string ID we'd like to hide the marker and menu entry of. Default behavior hides for ALL players." +"Example: objectives::breadcrumb( "trig_breadcrumb" );" +"SPMP: singleplayer" +@/ +function breadcrumb( str_trig_targetname, str_obj_id = "cp_waypoint_breadcrumb", b_complete_on_first_player_finish = true ) +{ + level notify( "breadcrumb_" + str_obj_id ); + level endon( "breadcrumb_" + str_obj_id ); + + if ( isdefined( level.a_objectives[ str_obj_id ] ) ) + { + complete( str_obj_id ); + } + + o_objective = set( str_obj_id, undefined, true ); + [[o_objective]]->start( str_trig_targetname ); + + while ( ![[o_objective]]->is_done() ) + { + wait 0.05; + } + + if ( b_complete_on_first_player_finish ) + { + complete( str_obj_id ); + } +} + +/@ +"Name: hide( , )" +"Summary: Hide the objective marker and menu entry for an objective." +"CallOn: NA" +"MandatoryArg: The objective string ID we'd like to hide the marker and menu entry of. Default behavior hides for ALL players" +"OptionalArg: The specific player you would like to hide the objective waypoint from." +"Example: objectives::hide( "objective_to_do_things", e_player_2 );" +"SPMP: singleplayer" +@/ +function hide( str_obj_type, e_player ) +{ + if ( isdefined( level.a_objectives[str_obj_type] ) ) + { + o_objective = level.a_objectives[str_obj_type]; + [[o_objective]]->hide( e_player ); + } + else + { + Assert( false, "Attempting to hide a marker on an objective that does not exist." ); + } +} + +/@ +"Name: hide_for_target( , )" +"Summary: Hide the objective marker for a specific target." +"CallOn: NA" +"MandatoryArg: The objective string ID we'd like to hide the marker." +"MandatoryArg: The target for which you want to hide the objective marker." +"Example: objectives::hide_for_target( "tank_marker", vh_tank_3 );" +"SPMP: singleplayer" +@/ +function hide_for_target( str_obj_type, e_target ) +{ + if ( isdefined( level.a_objectives[str_obj_type] ) ) + { + o_objective = level.a_objectives[str_obj_type]; + [[o_objective]]->hide_for_target( e_target ); + } + else + { + Assert( false, "Attempting to hide a marker on an objective that does not exist." ); + } +} + +/@ +"Name: show( , )" +"Summary: Show the objective marker and menu entry for an objective." +"CallOn: NA" +"MandatoryArg: The objective string ID we'd like to show the marker and menu entry of. Default behavior shows for ALL players" +"OptionalArg: The specific player you would like to show the objective waypoint to." +"Example: objectives::show( "objective_to_do_things", e_player_2 );" +"SPMP: singleplayer" +@/ +function show( str_obj_type, e_player ) +{ + if ( isdefined( level.a_objectives[str_obj_type] ) ) + { + o_objective = level.a_objectives[str_obj_type]; + [[o_objective]]->show( e_player ); + } + else + { + Assert( false, "Attempting to show a marker on an objective that does not exist." ); + } +} + +/@ +"Name: show_for_target( , )" +"Summary: Show the objective marker for a specific target." +"CallOn: NA" +"MandatoryArg: The objective string ID we'd like to show the marker." +"MandatoryArg: The target for which you want to show the objective marker." +"Example: objectives::show_for_target( "tank_marker", vh_tank_3 );" +"SPMP: singleplayer" +@/ +function show_for_target( str_obj_type, e_target ) +{ + if ( isdefined( level.a_objectives[str_obj_type] ) ) + { + o_objective = level.a_objectives[str_obj_type]; + [[o_objective]]->show_for_target( e_target ); + } + else + { + Assert( false, "Attempting to hide a marker on an objective that does not exist." ); + } +} + +/@ +"Name: get_id_for_target( , )" +"Summary: Get objective ID for a specific target." +"CallOn: NA" +"MandatoryArg: The objective string ID." +"MandatoryArg: The target for which you want the ID." +"Example: id = objectives::get_id_for_target( "tank_marker", vh_tank_3 );" +"SPMP: singleplayer" +@/ +function get_id_for_target( str_obj_type, e_target ) +{ + id = -1; + if ( isdefined( level.a_objectives[str_obj_type] ) ) + { + o_objective = level.a_objectives[str_obj_type]; + id = [[o_objective]]->get_id_for_target( e_target ); + } + + if ( id < 0 ) + { + Assert( false, "get_id_for_target called for an objective that does not exist." ); + } + return id; +} + +/@ +"Name: event_message( )" +"Summary: Show an event message on the HUD." +"MandatoryArg: The localized string to show." +"Example: objectives::event_message( "Kill the quadtank" );" +@/ +function event_message( istr_message ) +{ + foreach ( player in level.players ) + { + util::show_event_message( player, istring( istr_message ) ); + } +} + +//str_obj_type - the icon type ( target, capture, defend, defend_b, etc ) +//str_obj_name - a unique identifier for this icon. Creating a new icon with a name already in use, will destroy/override the previous icon +//v_pos - the position the icon will be placed +function create_temp_icon( str_obj_type, str_obj_name, v_pos, v_offset=(0,0,0) ) +{ + switch( str_obj_type ) + { + case "target": + str_shader = "waypoint_targetneutral"; + break; + + case "capture": + str_shader = "waypoint_capture"; + break; + + case "capture_a": + str_shader = "waypoint_capture_a"; + break; + + case "capture_b": + str_shader = "waypoint_capture_b"; + break; + + case "capture_c": + str_shader = "waypoint_capture_c"; + break; + + case "defend": + str_shader = "waypoint_defend"; + break; + + case "defend_a": + str_shader = "waypoint_defend_a"; + break; + + case "defend_b": + str_shader = "waypoint_defend_b"; + break; + + case "defend_c": + str_shader = "waypoint_defend_c"; + break; + + case "return": + str_shader = "waypoint_return"; + break; + + default: + AssertMsg( "Type '" + str_obj_type + "' not supported. Please see create_temp_icon() in _objectives.gsc for supported types." ); + break; + } + + nextObjPoint = objpoints::create( str_obj_name, v_pos + v_offset, "all", str_shader ); + nextObjPoint setWayPoint( true, str_shader ); + + return nextObjPoint; +} + +function destroy_temp_icon() +{ + objpoints::delete( self ); +} + +function private on_player_spawned() // self = player +{ + // find any active breadcrumbs, and initialize them on this player + if ( isdefined( level.a_objectives ) ) + { + foreach( o_objective in level.a_objectives ) + { + if ( [[o_objective]]->is_breadcrumb() && ![[o_objective]]->is_done() ) + { + [[o_objective]]->add_player( self ); + } + } + } +} diff --git a/cp/_oed.gsc b/cp/_oed.gsc new file mode 100644 index 0000000..35b6ebe --- /dev/null +++ b/cp/_oed.gsc @@ -0,0 +1,450 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\scene_shared; +#using scripts\shared\spawner_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\visionset_mgr_shared; + +#using scripts\cp\gametypes\_save; + + + + + + +#namespace oed; + +function autoexec __init__sytem__() { system::register("oed",&__init__,&__main__,undefined); } + +//TODO - add in support for the animated portion of the hud/lui when it comes online + +function __init__() +{ + clientfield::register( "toplayer", "ev_toggle", 1, 1, "int" ); + clientfield::register( "toplayer", "sitrep_toggle", 1, 1, "int" ); + clientfield::register( "toplayer", "tmode_toggle", 1, 3, "int" ); + clientfield::register( "toplayer", "active_dni_fx", 1, 1, "counter" ); + clientfield::register( "toplayer", "hack_dni_fx", 1, 1, "counter" ); + + clientfield::register( "actor", "thermal_active", 1, 1, "int" ); + clientfield::register( "actor", "sitrep_material", 1, 1, "int" ); + clientfield::register( "actor", "force_tmode", 1, 1, "int" ); + clientfield::register( "actor", "tagged", 1, 1, "int" ); + + clientfield::register( "vehicle", "thermal_active", 1, 1, "int" ); + clientfield::register( "vehicle", "sitrep_material", 1, 1, "int" ); + + clientfield::register( "scriptmover", "thermal_active", 1, 1, "int" ); + clientfield::register( "scriptmover", "sitrep_material", 1, 1, "int" ); + + clientfield::register( "item", "sitrep_material", 1, 1, "int" ); + + if ( !IsDefined( level.vsmgr_prio_visionset_tmode ) ) + { + level.vsmgr_prio_visionset_tmode = 50; + } + visionset_mgr::register_info( "visionset", "tac_mode", 1, level.vsmgr_prio_visionset_tmode, 15, true, &visionset_mgr::ramp_in_out_thread_per_player, false ); + + callback::on_spawned( &on_player_spawned ); + + // Have all enemies show up on thermal + spawner::add_global_spawn_function( "axis", &enable_thermal_on_spawned ); + spawner::add_global_spawn_function( "allies", &enable_thermal_on_spawned ); + + level.b_enhanced_vision_enabled = true; + level.b_tactical_mode_enabled = true; + level.b_player_scene_active = false; + + level.enable_thermal = &enable_thermal; + level.disable_thermal = &disable_thermal; +} + +function __main__() +{ + keyline_weapons(); +} + +function keyline_weapons() +{ + waittillframeend; // clientfields can't be set before a wait + if ( level.b_tactical_mode_enabled ) + { + array::thread_all( + util::query_ents( + AssociativeArray( "classname", "weapon_" ), true, + [], + true, + true + ), &enable_keyline ); + } +} + +function on_player_spawned() +{ + // ev init + self.b_enhanced_vision_enabled = level.b_enhanced_vision_enabled; + self.ev_state = false; + self ev_activate_on_player( self.ev_state ); + + // tmode init + self.b_tactical_mode_enabled = level.b_tactical_mode_enabled; + self.tmode_state = false; + + b_playsound = false; + + if ( !( SessionModeIsCampaignZombiesGame() ) ) + if ( ( isdefined( GetLocalProfileInt( "tacticalModeAutoOn" ) ) && GetLocalProfileInt( "tacticalModeAutoOn" ) ) ) + { + self.tmode_state = true; + b_playsound = false; + } + + self tmode_activate_on_player( self.tmode_state, b_playsound ); + + // sitrep + self clientfield::set_to_player( "sitrep_toggle", 1 ); + + self thread check_keys_for_ev_and_tmode(); + self thread init_heroes(); +} + +////////////////////////////////////////////////////////////////////////////////// +// EV and TMODE +////////////////////////////////////////////////////////////////////////////////// +function check_keys_for_ev_and_tmode() +{ + self endon( "death" ); + self endon("killOEDMonitor"); + + while( 1 ) + { + /# level flagsys::wait_till_clear( "menu_open" ); #/ + + // EV on up dpad + if ( level.b_enhanced_vision_enabled && self.b_enhanced_vision_enabled && self ActionSlotOneButtonPressed() ) + { + if ( !scene::is_igc_active() ) + { + + self.ev_state = !( isdefined( self.ev_state ) && self.ev_state ); + self ev_activate_on_player( self.ev_state ); + + // wait until the key is released + while( self ActionSlotOneButtonPressed() ) + { + {wait(.05);}; + } + } + } + + // TMode on right dpad + if ( !( SessionModeIsCampaignZombiesGame() ) && level.b_tactical_mode_enabled && self.b_tactical_mode_enabled && self ActionSlotFourButtonPressed() ) + { + if ( !scene::is_igc_active() ) + { + self.tmode_state = !( isdefined( self.tmode_state ) && self.tmode_state ); + self tmode_activate_on_player( self.tmode_state ); + + visionset_mgr::activate( "visionset", "tac_mode", self, 0.05, 0.0, 0.8 ); + wait 0.05 + 0.0 + 0.8; + + while ( self ActionSlotFourButtonPressed() ) + { + {wait(.05);}; + } + } + } + + {wait(.05);}; + } +} + +////////////////////////////////////////////////////////////////////// +///ENHANCED VISION - THERMAL +////////////////////////////////////////////////////////////////////// + +// Spawn function to automatically enable thermal on enemies +function enable_thermal_on_spawned() +{ + if ( self.team == "axis" ) + { + self enable_thermal(); + } + else if( self.team == "allies" )//keep seperate for now in case we need extra logic here + { + self enable_thermal(); + } +} + +//TODO - will likely need to add in support for hud/lui changes +// Called externally to enable an entity to have a thermal shader when EV is activated +// self is the entity to have a thermal signature +// str_disable - a notify that can be used to deactivate the thermal signature +function enable_thermal( str_disable ) +{ + self endon( "death" ); + + self clientfield::set( "thermal_active", 1 ); + self thread disable_thermal_on_death(); + + if( isdefined( str_disable ) ) + { + level waittill( str_disable ); + + self disable_thermal(); + } +} + +// Remove the thermal signature on death +// self is the entity that has a thermal signature +function disable_thermal_on_death() +{ + self endon( "disable_thermal" ); + + self waittill( "death" ); + + if( isdefined( self ) ) + { + self disable_thermal(); + } +} + +// Remove the thermal signature from the entity +// self is the entity that has a thermal signature +function disable_thermal() +{ + self clientfield::set( "thermal_active", 0 ); + + self notify( "disable_thermal" ); +} + +//Toggles the vars used to enable/disable player access to thermal mode, also called EV +//This should be used when toggling during the course of the level +//If you just want to disable thermal access by default at level start, just use level.b_enhanced_vision_enabled = false; +function toggle_thermal_mode_for_players( b_enabled = true ) +{ + level.b_enhanced_vision_enabled = b_enabled; + foreach( e_player in level.players ) + { + e_player.b_enhanced_vision_enabled = b_enabled; + } +} + +// +// TMODE and EV are disabled & turned off or enabled for the player from a level script here. +// +function enable_ev( b_enabled = true ) +{ + self.b_enhanced_vision_enabled = b_enabled; + + if ( !b_enabled ) + self ev_activate_on_player( b_enabled ); +} + +function enable_tac_mode( b_enabled = true ) +{ + self.b_tactical_mode_enabled = b_enabled; + + if ( b_enabled ) + { + if ( !( SessionModeIsCampaignZombiesGame() ) && ( isdefined( GetLocalProfileInt( "tacticalModeAutoOn" ) ) && GetLocalProfileInt( "tacticalModeAutoOn" ) ) ) + { + self tmode_activate_on_player( true, false ); + } + } + else + { + self tmode_activate_on_player( false, false ); + } +} + + +// +// external call from other scripts to turn ev on and off on a player +// +function set_player_ev( b_enabled = true ) +{ + ev_activate_on_player( b_enabled ); +} + +function ev_activate_on_player( b_enabled = true ) +{ + self.ev_state = b_enabled; + + // turn on ev - so turn off tmode + if ( self.ev_state ) + { + if ( ( isdefined( self.tmode_state ) && self.tmode_state ) ) + self.tmode_state_before_ev = true; + else + self.tmode_state_before_ev = false; + + self tmode_activate_on_player( false, false, false ); + } + + //the tutorial moment in New World is listening for this + if ( self.ev_state ) + self notify( "enhanced_vision_activated" ); + else + self notify( "enhanced_vision_deactivated" ); + + self clientfield::set_to_player( "ev_toggle", self.ev_state ); + + // turn off ev - maybe turn tmode back on + if ( !self.ev_state ) + { + if ( ( isdefined( self.tmode_state_before_ev ) && self.tmode_state_before_ev ) ) + { + if ( !( SessionModeIsCampaignZombiesGame() ) && ( isdefined( GetLocalProfileInt( "tacticalModeAutoOn" ) ) && GetLocalProfileInt( "tacticalModeAutoOn" ) ) ) + { + self tmode_activate_on_player( true, false, false ); + } + } + } +} + +function tmode_activate_on_player( b_enabled = true, b_playsound = true, b_turnoffev = true ) +{ + self.tmode_state = b_enabled; + + + if ( b_turnoffev && self.tmode_state ) + self ev_activate_on_player( false ); + + if ( self.tmode_state ) + self notify( "tactical_mode_activated" ); + else + self notify( "tactical_mode_deactivated" ); + + self TmodeSetServerUser( self.tmode_state ); + + code = 0; + + if( !isdefined( self.tmode_count ) ) + self.tmode_count = 0; + + self.tmode_count++; + self.tmode_count &= 1; + + code = self.tmode_count; + + if ( b_playsound ) + code |= 0x2; + + if ( self.tmode_state ) + code |= 0x4; + + self clientfield::set_to_player( "tmode_toggle", code ); + + self savegame::set_player_data("tmode", self.tmode_state ); +} + +function init_heroes() +{ + a_e_heroes = GetEntArray(); + foreach( e_hero in a_e_heroes ) + { + if( ( isdefined( e_hero.is_hero ) && e_hero.is_hero ) ) + { + e_hero thread enable_thermal(); + } + } +} + +/////////////////////////////////////////////////////////////////////////// +/// TACTICAL MODE +/////////////////////////////////////////////////////////////////////////// + +//Toggles the vars used to enable/disable player access to tactical mode +//This should be used when toggling during the course of the level +//If you just want to disable tac mode access by default at level start, just use level.b_tactical_mode_enabled = false; + + +function toggle_tac_mode_for_players( b_enabled = true ) +{ + level.b_tactical_mode_enabled = b_enabled; + foreach( e_player in level.players ) + { + e_player.b_tactical_mode_enabled = b_enabled; + } +} + + +//self = AI enemy actor that will draw in tmode for all players no matter what +function set_force_tmode( b_enabled = true ) +{ + self.b_force_tmode = b_enabled; + self clientfield::set( "force_tmode", b_enabled ); +} + + +////////////////////////////////////////////////////////////////////////// +/// SITREP - KEYLINE +////////////////////////////////////////////////////////////////////////// + +//Set b_interact to true if this is an interactive entity//TODO we may need support now for non-interactable, for now we assume this is an interact object +function enable_keyline( b_interact = false, str_disable ) +{ + self endon( "death" ); + + self clientfield::set( "sitrep_material", 1 ); + + self thread disable_on_death(); + + if( isdefined( str_disable ) ) + { + level waittill( str_disable ); + self disable_keyline(); + } +} + +function disable_on_death() +{ + self waittill( "death" ); + if( isdefined( self ) ) + { + self disable_keyline(); + } +} + +function disable_keyline() +{ + self clientfield::set( "sitrep_material", 0 ); +} + +function toggle_sitrep_for_players( b_active = true ) +{ + foreach( player in level.players ) + { + player.sitrep_active = !( isdefined( player.sitrep_active ) && player.sitrep_active ); + player clientfield::set_to_player( "sitrep_toggle", player.sitrep_active ); + } +} + +function init_sitrep_model() +{ + if( !IsDefined(self.angles) ) + { + self.angles = (0, 0 ,0 ); + } + + s_sitrep_bundle = level.scriptbundles[ "sitrep" ][ self.scriptbundlename ]; + + e_sitrep = util::spawn_model( s_sitrep_bundle.model, self.origin, self.angles ); + + if( isdefined( s_sitrep_bundle.sitrep_interact ) ) + { + e_sitrep.script_sitrep_id = s_sitrep_bundle.sitrep_interact; + } + else + { + e_sitrep.script_sitrep_id = 0; + } + + return e_sitrep; +} diff --git a/cp/_oed.gsh b/cp/_oed.gsh new file mode 100644 index 0000000..3296f79 --- /dev/null +++ b/cp/_oed.gsh @@ -0,0 +1,24 @@ + + + + // Name of the visionset file + // alias in script + // priority vs other visionsets + // number of steps when ramping in/out + // activation ramp-in time + // How long to hold after ramp-in is done and before ramping-out + // deactivation ramp-out time + + + + + + + + + + + + + + diff --git a/cp/_util.gsc b/cp/_util.gsc new file mode 100644 index 0000000..d6be465 --- /dev/null +++ b/cp/_util.gsc @@ -0,0 +1,1967 @@ +#using scripts\shared\ai_shared; +#using scripts\shared\array_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\exploder_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\load_shared; +#using scripts\shared\lui_shared; +#using scripts\shared\math_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\spawner_shared; +#using scripts\shared\string_shared; +#using scripts\shared\util_shared; + + + + + +#using scripts\cp\_oed; +#using scripts\cp\_skipto; +#using scripts\cp\cybercom\_cybercom_util; +#using scripts\cp\gametypes\coop; + + + +#namespace util; + + + + + + +#precache( "lui_menu", "CPHintText" ); +#precache( "lui_menu_data", "hint_text_line" ); + +#precache( "string", "blinking"); +#precache( "string", "display_noblink"); +#precache( "string", "fadeout"); + +/@ +"Name: add_gametype()" +"Summary: dummy - Rex looks for these to populate the gametype pulldown" +"SPMP: COOP" +@/ +function add_gametype( gt ) +{ +} + + +/# +function error(msg) +{ + println("^c*ERROR* ", msg); + wait .05; // waitframe + + if (GetDvarString( "debug") != "1") + assertmsg("This is a forced error - attach the log file"); +} +#/ + +function warning( msg ) +{ +/# println( "^1WARNING: " + msg ); #/ +} + + + +/@ +"Name: within_fov( , , , )" +"Summary: Returns true if < end_origin > is within the players field of view, otherwise returns false." +"Module: Vector" +"CallOn: " +"MandatoryArg: : starting origin for FOV check( usually the players origin )" +"MandatoryArg: : angles to specify facing direction( usually the players angles )" +"MandatoryArg: : origin to check if it's in the FOV" +"MandatoryArg: : cosine of the FOV angle to use" +"Example: qBool = within_fov( level.player.origin, level.player.angles, target1.origin, cos( 45 ) );" +"SPMP: multiplayer" +@/ +function within_fov( start_origin, start_angles, end_origin, fov ) +{ + normal = VectorNormalize( end_origin - start_origin ); + forward = AnglesToForward( start_angles ); + dot = VectorDot( forward, normal ); + + return dot >= fov; +} + +function get_player_height() +{ + return 70.0; // inches, see bg_pmove.cpp::playerMins/playerMaxs +} + + +function IsBulletImpactMOD( sMeansOfDeath ) +{ + return IsSubStr( sMeansOfDeath, "BULLET" ) || sMeansOfDeath == "MOD_HEAD_SHOT"; +} + +function waitRespawnButton() +{ + self endon("disconnect"); + self endon("end_respawn"); + + while(self useButtonPressed() != true) + wait .05; +} + +function setLowerMessage( text, time, combineMessageAndTimer ) +{ + if ( !isdefined( self.lowerMessage ) ) + return; + + if ( isdefined( self.lowerMessageOverride ) && text != &"" ) + { + text = self.lowerMessageOverride; + time = undefined; + } + + self notify("lower_message_set"); + + self.lowerMessage setText( text ); + + if ( isdefined( time ) && time > 0 ) + { + if ( !isdefined( combineMessageAndTimer ) || !combineMessageAndTimer ) + self.lowerTimer.label = &""; + else + { + self.lowerMessage setText( "" ); + self.lowerTimer.label = text; + } + self.lowerTimer setTimer( time ); + } + else + { + self.lowerTimer setText( "" ); + self.lowerTimer.label = &""; + } + if( self IsSplitscreen() ) + self.lowerMessage.fontscale = 1.4; + + self.lowerMessage fadeOverTime( 0.05 ); + self.lowerMessage.alpha = 1; + self.lowerTimer fadeOverTime( 0.05 ); + self.lowerTimer.alpha = 1; +} + +function setLowerMessageValue( text, value, combineMessage ) +{ + if ( !isdefined( self.lowerMessage ) ) + return; + + if ( isdefined( self.lowerMessageOverride ) && text != &"" ) + { + text = self.lowerMessageOverride; + time = undefined; + } + + self notify("lower_message_set"); + if ( !isdefined( combineMessage ) || !combineMessage ) + self.lowerMessage setText( text ); + else + self.lowerMessage setText( "" ); + + if ( isdefined( value ) && value > 0 ) + { + if ( !isdefined( combineMessage ) || !combineMessage ) + self.lowerTimer.label = &""; + else + self.lowerTimer.label = text; + self.lowerTimer setValue( value ); + } + else + { + self.lowerTimer setText( "" ); + self.lowerTimer.label = &""; + } + + if( self IsSplitscreen() ) + self.lowerMessage.fontscale = 1.4; + + self.lowerMessage fadeOverTime( 0.05 ); + self.lowerMessage.alpha = 1; + self.lowerTimer fadeOverTime( 0.05 ); + self.lowerTimer.alpha = 1; +} + +function clearLowerMessage( fadetime ) +{ + if ( !isdefined( self.lowerMessage ) ) + return; + + self notify("lower_message_set"); + + if ( !isdefined( fadetime) || fadetime == 0 ) + { + setLowerMessage( &"" ); + } + else + { + self endon("disconnect"); + self endon("lower_message_set"); + + self.lowerMessage fadeOverTime( fadetime ); + self.lowerMessage.alpha = 0; + self.lowerTimer fadeOverTime( fadetime ); + self.lowerTimer.alpha = 0; + + wait fadetime; + + self setLowerMessage(""); + } +} + +function printOnTeam(text, team) +{ + assert( isdefined( level.players ) ); + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if ( ( isdefined(player.pers["team"]) ) && (player.pers["team"] == team) ) + player iprintln(text); + } +} + + +function printBoldOnTeam(text, team) +{ + assert( isdefined( level.players ) ); + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if ( ( isdefined(player.pers["team"]) ) && (player.pers["team"] == team) ) + player iprintlnbold(text); + } +} + + + +function printBoldOnTeamArg(text, team, arg) +{ + assert( isdefined( level.players ) ); + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if ( ( isdefined(player.pers["team"]) ) && (player.pers["team"] == team) ) + player iprintlnbold(text, arg); + } +} + + +function printOnTeamArg(text, team, arg) +{ + //assert( isdefined( level.players ) ); + //for ( i = 0; i < level.players.size; i++ ) + //{ + // player = level.players[i]; + // if ( ( isdefined(player.pers["team"]) ) && (player.pers["team"] == team) ) + // { + // player iprintln(text, arg); + // } + //} +} + + +function printOnPlayers( text, team ) +{ + players = level.players; + for(i = 0; i < players.size; i++) + { + if ( isdefined( team ) ) + { + if((isdefined(players[i].pers["team"])) && (players[i].pers["team"] == team)) + players[i] iprintln(text); + } + else + { + players[i] iprintln(text); + } + } +} + +function printAndSoundOnEveryone( team, enemyteam, printFriendly, printEnemy, soundFriendly, soundEnemy, printarg ) +{ + shouldDoSounds = isdefined( soundFriendly ); + + shouldDoEnemySounds = false; + if ( isdefined( soundEnemy ) ) + { + assert( shouldDoSounds ); // can't have an enemy sound without a friendly sound + shouldDoEnemySounds = true; + } + + if ( !isdefined( printarg ) ) + { + printarg = ""; + } + + if ( level.splitscreen || !shouldDoSounds ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + playerteam = player.pers["team"]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == team && isdefined( printFriendly ) && printFriendly != &"" ) + player iprintln( printFriendly, printarg ); + else if ( isdefined( printEnemy ) && printEnemy != &"" ) + { + if ( isdefined(enemyteam) && playerteam == enemyteam ) + player iprintln( printEnemy, printarg ); + else if ( !isdefined(enemyteam) && playerteam != team ) + player iprintln( printEnemy, printarg ); + } + } + } + if ( shouldDoSounds ) + { + assert( level.splitscreen ); + level.players[0] playLocalSound( soundFriendly ); + } + } + else + { + assert( shouldDoSounds ); + if ( shouldDoEnemySounds ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + playerteam = player.pers["team"]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == team ) + { + if( isdefined( printFriendly ) && printFriendly != &"" ) + player iprintln( printFriendly, printarg ); + player playLocalSound( soundFriendly ); + } + else if ( (isdefined(enemyteam) && playerteam == enemyteam) || ( !isdefined( enemyteam ) && playerteam != team ) ) + { + if( isdefined( printEnemy ) && printEnemy != &"" ) + player iprintln( printEnemy, printarg ); + player playLocalSound( soundEnemy ); + } + } + } + } + else + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + playerteam = player.pers["team"]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == team ) + { + if( isdefined( printFriendly ) && printFriendly != &"" ) + player iprintln( printFriendly, printarg ); + player playLocalSound( soundFriendly ); + } + else if ( isdefined( printEnemy ) && printEnemy != &"" ) + { + if ( isdefined(enemyteam) && playerteam == enemyteam ) + { + player iprintln( printEnemy, printarg ); + } + else if ( !isdefined(enemyteam) && playerteam != team ) + { + player iprintln( printEnemy, printarg ); + } + } + } + } + } + } +} + + +function _playLocalSound( soundAlias ) +{ + if ( level.splitscreen && !self IsHost() ) + return; + + self playLocalSound( soundAlias ); +} + +// this function is depricated +function getOtherTeam( team ) +{ + // TODO MTEAM - Need to fix this. + if ( team == "allies" ) + return "axis"; + else if ( team == "axis" ) + return "allies"; + else // all other teams + return "allies"; + + assertMsg( "getOtherTeam: invalid team " + team ); +} + +function getTeamMask( team ) +{ + // this can be undefined on connect + if ( !level.teambased || !isdefined(team) || !isdefined(level.spawnsystem.iSPAWN_TEAMMASK[team]) ) + return level.spawnsystem.iSPAWN_TEAMMASK_FREE; + + return level.spawnsystem.iSPAWN_TEAMMASK[team]; +} + +function getOtherTeamsMask( skip_team ) +{ + mask = 0; + foreach( team in level.teams ) + { + if ( team == skip_team ) + continue; + + mask = mask | getTeamMask( team ); + } + + return mask; +} + +function plot_points( plotpoints, r=1, g=1, b=1, server_frames=1 ) +{ + /# + lastpoint = plotpoints[ 0 ]; + server_frames = int( server_frames );//Make sure this is an int + + for( i = 1;i < plotpoints.size;i ++ ) + { + // AE 10-26-09: line function must have changed to Line( , , , , ) + line( lastpoint, plotpoints[ i ], ( r, g, b ), 1, server_frames ); + lastpoint = plotpoints[ i ]; + } + #/ +} + +function getfx( fx ) +{ + assert( isdefined( level._effect[ fx ] ), "Fx " + fx + " is not defined in level._effect." ); + return level._effect[ fx ]; +} + +function set_dvar_if_unset( + dvar, + value, + reset) +{ + if (!isdefined(reset)) + reset = false; + + if (reset || GetDvarString(dvar)=="") + { + SetDvar(dvar, value); + return value; + } + + return GetDvarString(dvar); +} + +function set_dvar_float_if_unset( + dvar, + value, + reset) +{ + if (!isdefined(reset)) + reset = false; + + if (reset || GetDvarString(dvar)=="") + { + SetDvar(dvar, value); + } + + return GetDvarFloat(dvar); +} + +function set_dvar_int_if_unset( + dvar, + value, + reset) +{ + if (!isdefined(reset)) + reset = false; + + if (reset || GetDvarString(dvar)=="") + { + SetDvar(dvar, value); + return int(value); + } + + return GetDvarInt(dvar); +} + +function add_trigger_to_ent(ent) // Self == The trigger volume +{ + if(!isdefined(ent._triggers)) + { + ent._triggers = []; + } + + ent._triggers[self GetEntityNumber()] = 1; +} + +function remove_trigger_from_ent(ent) // Self == The trigger volume. +{ + if(!isdefined(ent)) + return; + + if(!isdefined(ent._triggers)) + return; + + if(!isdefined(ent._triggers[self GetEntityNumber()])) + return; + + ent._triggers[self GetEntityNumber()] = 0; +} + +function ent_already_in_trigger(trig) // Self == The entity in the trigger volume. +{ + if(!isdefined(self._triggers)) + return false; + + if(!isdefined(self._triggers[trig GetEntityNumber()])) + return false; + + if(!self._triggers[trig GetEntityNumber()]) + return false; + + return true; // We're already in this trigger volume. +} + +function trigger_thread_death_monitor(ent, ender) +{ + ent waittill("death"); + self endon(ender); + self remove_trigger_from_ent(ent); +} + +function trigger_thread(ent, on_enter_payload, on_exit_payload) // Self == The trigger. +{ + ent endon("entityshutdown"); + ent endon("death"); + + if(ent ent_already_in_trigger(self)) + return; + + self add_trigger_to_ent(ent); + + ender = "end_trig_death_monitor" + self GetEntityNumber() + " " + ent GetEntityNumber(); + self thread trigger_thread_death_monitor(ent, ender); // If ent dies in trigger, clear trigger off of ent. + +// iprintlnbold("Trigger " + self.targetname + " hit by ent " + ent getentitynumber()); + + endon_condition = "leave_trigger_" + self GetEntityNumber(); + + if(isdefined(on_enter_payload)) + { + self thread [[on_enter_payload]](ent, endon_condition); + } + + while(isdefined(ent) && ent IsTouching(self)) + { + wait(0.01); + } + + ent notify(endon_condition); + +// iprintlnbold(ent getentitynumber() + " leaves trigger " + self.targetname + "."); + + if(isdefined(ent) && isdefined(on_exit_payload)) + { + self thread [[on_exit_payload]](ent); + } + + if(isdefined(ent)) + { + self remove_trigger_from_ent(ent); + } + + self notify(ender); // Get rid of the death monitor thread. +} + +function isStrStart( string1, subStr ) +{ + return ( getSubStr( string1, 0, subStr.size ) == subStr ); +} + +function isKillStreaksEnabled() +{ + return isdefined( level.killstreaksenabled ) && level.killstreaksenabled; +} + +function setUsingRemote( remoteName ) +{ + if ( isdefined( self.carryIcon) ) + self.carryIcon.alpha = 0; + + assert( !self isUsingRemote() ); + self.usingRemote = remoteName; + + self disableOffhandWeapons(); + self notify( "using_remote" ); +} + +function getRemoteName() +{ + assert( self isUsingRemote() ); + + return self.usingRemote; +} + +function setObjectiveText( team, text ) +{ + game["strings"]["objective_"+team] = text; +} + +function setObjectiveScoreText( team, text ) +{ + game["strings"]["objective_score_"+team] = text; +} + +function setObjectiveHintText( team, text ) +{ + game["strings"]["objective_hint_"+team] = text; +} + +function getObjectiveText( team ) +{ + return game["strings"]["objective_"+team]; +} + +function getObjectiveScoreText( team ) +{ + return game["strings"]["objective_score_"+team]; +} + +function getObjectiveHintText( team ) +{ + return game["strings"]["objective_hint_"+team]; +} + +function registerRoundSwitch( minValue, maxValue ) +{ + level.roundSwitch = math::clamp( GetGametypeSetting( "roundSwitch" ), minValue, maxValue ); + level.roundSwitchMin = minValue; + level.roundSwitchMax = maxValue; +} + +function registerRoundLimit( minValue, maxValue ) +{ + level.roundLimit = math::clamp( GetGametypeSetting( "roundLimit" ), minValue, maxValue ); + level.roundLimitMin = minValue; + level.roundLimitMax = maxValue; +} + + +function registerRoundWinLimit( minValue, maxValue ) +{ + level.roundWinLimit = math::clamp( GetGametypeSetting( "roundWinLimit" ), minValue, maxValue ); + level.roundWinLimitMin = minValue; + level.roundWinLimitMax = maxValue; +} + + +function registerScoreLimit( minValue, maxValue ) +{ + level.scoreLimit = math::clamp( GetGametypeSetting( "scoreLimit" ), minValue, maxValue ); + level.scoreLimitMin = minValue; + level.scoreLimitMax = maxValue; + SetDvar( "ui_scorelimit", level.scoreLimit ); +} + + +function registerTimeLimit( minValue, maxValue ) +{ + level.timeLimit = math::clamp( GetGametypeSetting( "timeLimit" ), minValue, maxValue ); + level.timeLimitMin = minValue; + level.timeLimitMax = maxValue; + SetDvar( "ui_timelimit", level.timeLimit ); +} + + +function registerNumLives( minValue, maxValue ) +{ + level.numLives = math::clamp( GetGametypeSetting( "playerNumLives" ), minValue, maxValue ); + level.numLivesMin = minValue; + level.numLivesMax = maxValue; +} + +function getPlayerFromClientNum( clientNum ) +{ + if ( clientNum < 0 ) + return undefined; + + for ( i = 0; i < level.players.size; i++ ) + { + if ( level.players[i] getEntityNumber() == clientNum ) + return level.players[i]; + } + return undefined; +} + +function isPressBuild() +{ + buildType = GetDvarString( "buildType" ); + + if ( isdefined( buildType ) && buildtype == "press" ) + { + return true; + } + + return false; +} + +function isFlashbanged() +{ + return isdefined( self.flashEndTime ) && gettime() < self.flashEndTime; +} + + +/@ +"Name: self isEntStunned()" +"Summary: Returns true if the entity is stunned" +"Module: Entity" +"CallOn: An entity" +"Example: if (self isEntStunned())" +"SPMP: singleplayer" +@/ +function isEntStunned() +{ + time = gettime(); + + if (isDefined(self.stunned) && self.stunned) + return true; + + if (self isFlashbanged()) + return true; + + if (isDefined(self.stun_fx)) + return true; + + if (isDefined(self.lastStunnedTime) && (self.lastStunnedTime + 5000) > time) + return true; + + if (isDefined( self.concussionEndTime ) && self.concussionEndTime > time) + return true; + + return false; +} + +function DoMaxDamage( origin, attacker, inflictor, headshot, mod ) // self == entity to damage +{ + if ( isdefined( self.damagedToDeath ) && self.damagedToDeath ) + { + return; + } + + if ( isdefined( self.maxHealth ) ) + { + damage = self.maxHealth + 1; + } + else + { + damage = self.health + 1; + } + + self.damagedToDeath = true; + + self DoDamage( damage, origin, attacker, inflictor, headshot, mod ); +} + + +/@ +"Name: self_delete()" +"Summary: Just calls the delete() script command on self. Reason for this is so that we can use array::thread_all to delete entities" +"Module: Entity" +"CallOn: An entity" +"Example: ai[ 0 ] thread self_delete();" +"SPMP: singleplayer" +@/ +function self_delete() +{ + if ( isdefined( self ) ) + { + self delete(); + } +} + + +/@ +"Name: screen_message_create()" +"Summary: Creates a HUD element at the correct position with the string or string reference passed in. Shows on all players screens in a co-op game." +"Module: Utility" +"CallOn: N/A" +"MandatoryArg: : A string or string reference to place on the screen." +"OptionalArg: : A second string to display below the first." +"OptionalArg: : A third string to display below the second." +"OptionalArg: : Optional offset in y direction that should only be used in very specific circumstances." +"OptionalArg: : Length of time to display the message." +"Example: screen_message_create( &"LEVEL_STRING" );" +"SPMP: singleplayer" +@/ +function screen_message_create( string_message_1, string_message_2, string_message_3, n_offset_y, n_time ) +{ + level notify( "screen_message_create" ); + level endon( "screen_message_create" ); + + // if the mission is failing then do no create this instruction + // because it can potentially overlap the death/hint string + if( isdefined( level.missionfailed ) && level.missionfailed ) + return; + + // if player is killed then this dvar will be set. + // SUMEET_TODO - make it efficient next game instead of checking dvar here + if( GetDvarInt( "hud_missionFailed" ) == 1 ) + return; + + if ( !isdefined( n_offset_y ) ) + { + n_offset_y = 0; + } + + //handle displaying the first string + if( !isdefined(level._screen_message_1) ) + { + //text element that displays the name of the event + level._screen_message_1 = NewHudElem(); + level._screen_message_1.elemType = "font"; + level._screen_message_1.font = "objective"; + level._screen_message_1.fontscale = 1.8; + level._screen_message_1.horzAlign = "center"; + level._screen_message_1.vertAlign = "middle"; + level._screen_message_1.alignX = "center"; + level._screen_message_1.alignY = "middle"; + level._screen_message_1.y = -60 + n_offset_y; + level._screen_message_1.sort = 2; + + level._screen_message_1.color = ( 1, 1, 1 ); + level._screen_message_1.alpha = 1; + + level._screen_message_1.hidewheninmenu = true; + } + + //set the text of the element to the string passed in + level._screen_message_1 SetText( string_message_1 ); + + if( isdefined(string_message_2) ) + { + //handle displaying the first string + if( !isdefined(level._screen_message_2) ) + { + //text element that displays the name of the event + level._screen_message_2 = NewHudElem(); + level._screen_message_2.elemType = "font"; + level._screen_message_2.font = "objective"; + level._screen_message_2.fontscale = 1.8; + level._screen_message_2.horzAlign = "center"; + level._screen_message_2.vertAlign = "middle"; + level._screen_message_2.alignX = "center"; + level._screen_message_2.alignY = "middle"; + level._screen_message_2.y = -33 + n_offset_y; + level._screen_message_2.sort = 2; + + level._screen_message_2.color = ( 1, 1, 1 ); + level._screen_message_2.alpha = 1; + + level._screen_message_2.hidewheninmenu = true; + } + + //set the text of the element to the string passed in + level._screen_message_2 SetText( string_message_2 ); + } + else if( isdefined(level._screen_message_2) ) + { + level._screen_message_2 Destroy(); + } + + if( isdefined(string_message_3) ) + { + //handle displaying the first string + if( !isdefined(level._screen_message_3) ) + { + //text element that displays the name of the event + level._screen_message_3 = NewHudElem(); + level._screen_message_3.elemType = "font"; + level._screen_message_3.font = "objective"; + level._screen_message_3.fontscale = 1.8; + level._screen_message_3.horzAlign = "center"; + level._screen_message_3.vertAlign = "middle"; + level._screen_message_3.alignX = "center"; + level._screen_message_3.alignY = "middle"; + level._screen_message_3.y = -6 + n_offset_y; + level._screen_message_3.sort = 2; + + level._screen_message_3.color = ( 1, 1, 1 ); + level._screen_message_3.alpha = 1; + + level._screen_message_3.hidewheninmenu = true; + } + + //set the text of the element to the string passed in + level._screen_message_3 SetText( string_message_3 ); + } + else if( isdefined(level._screen_message_3) ) + { + level._screen_message_3 Destroy(); + } + + if ( isdefined( n_time ) && n_time > 0 ) + { + wait( n_time ); + + screen_message_delete(); + } +} + +/@ +"Name: screen_message_delete()" +"Summary: Deletes the current message being displayed on the screen made using screen_message_create." +"Module: Utility" +"CallOn: N/A" +"Example: screen_message_delete();" +"SPMP: singleplayer" +@/ +function screen_message_delete( delay ) +{ + if( isdefined( delay ) ) + { + wait( delay ); + } + + if( isdefined(level._screen_message_1) ) + { + level._screen_message_1 Destroy(); + } + if( isdefined(level._screen_message_2) ) + { + level._screen_message_2 Destroy(); + } + if( isdefined(level._screen_message_3) ) + { + level._screen_message_3 Destroy(); + } +} + +/@ +"Name: screen_message_create_client()" +"Summary: Creates a HUD element at the correct position with the string or string reference passed in, for a specific client." +"Module: Utility" +"CallOn: Player, specific client to recieve the screen message" +"MandatoryArg: : A string or string reference to place on the screen." +"OptionalArg: : A second string to display below the first." +"OptionalArg: : A third string to display below the second." +"OptionalArg: : Optional offset in y direction that should only be used in very specific circumstances." +"OptionalArg: : Length of time to display the message." +"Example: level.players[0] screen_message_create( &"LEVEL_STRING" );" +"SPMP: co-op" +@/ +function screen_message_create_client( string_message_1, string_message_2, string_message_3, n_offset_y, n_time ) // self = player +{ + self notify( "screen_message_create" ); + self endon( "screen_message_create" ); + self endon( "death" ); + + // if the mission is failing then do no create this instruction + // because it can potentially overlap the death/hint string + if( isdefined( level.missionfailed ) && level.missionfailed ) + return; + + // if player is killed then this dvar will be set. + // SUMEET_TODO - make it efficient next game instead of checking dvar here + if( GetDvarInt( "hud_missionFailed" ) == 1 ) + return; + + if ( !isdefined( n_offset_y ) ) + { + n_offset_y = 0; + } + + //handle displaying the first string + if( !isdefined(self._screen_message_1) ) + { + //text element that displays the name of the event + self._screen_message_1 = NewClientHudElem( self ); + self._screen_message_1.elemType = "font"; + self._screen_message_1.font = "objective"; + self._screen_message_1.fontscale = 1.8; + self._screen_message_1.horzAlign = "center"; + self._screen_message_1.vertAlign = "middle"; + self._screen_message_1.alignX = "center"; + self._screen_message_1.alignY = "middle"; + self._screen_message_1.y = -60 + n_offset_y; + self._screen_message_1.sort = 2; + + self._screen_message_1.color = ( 1, 1, 1 ); + self._screen_message_1.alpha = 1; + + self._screen_message_1.hidewheninmenu = true; + } + + //set the text of the element to the string passed in + self._screen_message_1 SetText( string_message_1 ); + + if( isdefined(string_message_2) ) + { + //handle displaying the first string + if( !isdefined(self._screen_message_2) ) + { + //text element that displays the name of the event + self._screen_message_2 = NewClientHudElem( self ); + self._screen_message_2.elemType = "font"; + self._screen_message_2.font = "objective"; + self._screen_message_2.fontscale = 1.8; + self._screen_message_2.horzAlign = "center"; + self._screen_message_2.vertAlign = "middle"; + self._screen_message_2.alignX = "center"; + self._screen_message_2.alignY = "middle"; + self._screen_message_2.y = -33 + n_offset_y; + self._screen_message_2.sort = 2; + + self._screen_message_2.color = ( 1, 1, 1 ); + self._screen_message_2.alpha = 1; + + self._screen_message_2.hidewheninmenu = true; + } + + //set the text of the element to the string passed in + self._screen_message_2 SetText( string_message_2 ); + } + else if( isdefined(self._screen_message_2) ) + { + self._screen_message_2 Destroy(); + } + + if( isdefined(string_message_3) ) + { + //handle displaying the first string + if( !isdefined(self._screen_message_3) ) + { + //text element that displays the name of the event + self._screen_message_3 = NewClientHudElem( self ); + self._screen_message_3.elemType = "font"; + self._screen_message_3.font = "objective"; + self._screen_message_3.fontscale = 1.8; + self._screen_message_3.horzAlign = "center"; + self._screen_message_3.vertAlign = "middle"; + self._screen_message_3.alignX = "center"; + self._screen_message_3.alignY = "middle"; + self._screen_message_3.y = -6 + n_offset_y; + self._screen_message_3.sort = 2; + + self._screen_message_3.color = ( 1, 1, 1 ); + self._screen_message_3.alpha = 1; + + self._screen_message_3.hidewheninmenu = true; + } + + //set the text of the element to the string passed in + self._screen_message_3 SetText( string_message_3 ); + } + else if( isdefined(self._screen_message_3) ) + { + self._screen_message_3 Destroy(); + } + + if ( isdefined( n_time ) && n_time > 0 ) + { + wait( n_time ); + + self screen_message_delete_client(); + } +} + +/@ +"Name: screen_message_delete_client()" +"Summary: Deletes the current message being displayed on the client's screen made using screen_message_create_client." +"Module: Utility" +"CallOn: N/A" +"Example: level.players[0] screen_message_delete_client();" +"SPMP: co-op" +@/ +function screen_message_delete_client( delay ) +{ + self endon( "death" ); + + if( isdefined( delay ) ) + { + wait( delay ); + } + + if( isdefined(self._screen_message_1) ) + { + self._screen_message_1 Destroy(); + } + if( isdefined(self._screen_message_2) ) + { + self._screen_message_2 Destroy(); + } + if( isdefined(self._screen_message_3) ) + { + self._screen_message_3 Destroy(); + } +} + + +/@ +"Name: screen_fade_out( [n_time], [str_shader] )" +"Summary: Fades the screen out. Uses any shader. Defaults to black." +"Module: Utility" +"CallOn: NA" +"OptionalArg: n_time: The time to fade. Defaults to 2 seconds. Can be 0." +"OptionalArg: str_shader: The shader to use for the hud element. Defaults to black." +"OptionalArg: str_menu_id: Unique menu ID that will create a new menu seperate from the default one." +"Example: screen_fade_out( 3 );" +"SPMP: singleplayer" +@/ +function screen_fade_out( n_time, str_shader, str_menu_id ) +{ + level lui::screen_fade_out( n_time, str_shader, str_menu_id ); +} + +/@ +"Name: screen_fade_in( [n_time], [str_shader] )" +"Summary: Fades the screen in. Uses any shader. Defaults to black." +"Module: Utility" +"CallOn: NA" +"OptionalArg: n_time: The time to fade. Defaults to 2 seconds. Can be 0." +"OptionalArg: str_shader: The shader to use for the hud element. Defaults to black." +"OptionalArg: str_menu_id: Unique menu ID that will create a new menu seperate from the default one." +"Example: screen_fade_in( 3, "white", "special_white" );" +"SPMP: singleplayer" +@/ +function screen_fade_in( n_time, str_shader, str_menu_id ) +{ + level lui::screen_fade_in( n_time, str_shader, str_menu_id ); +} + +/@ +"Name: screen_fade_to_alpha_with_blur( n_alpha, [n_time], [n_blur], [str_shader] )" +"Summary: Fades the screen in to a specified alpha and blur value. Uses any shader. Defaults to black." +"Module: Utility" +"CallOn: NA" +"MandatoryArg: n_alpha: The alpha value to fade the hud to." +"MandatoryArg: n_fade_time: The time to fade." +"OptionalArg: n_blur: The blur value." +"OptionalArg: str_shader: The shader to use for the hud element. Defaults to black." +"Example: screen_fade_to_alpha_with_blur( .3, 4, 1 );" +"SPMP: singleplayer" +@/ +function screen_fade_to_alpha_with_blur( n_alpha, n_fade_time, n_blur, str_shader ) +{ + Assert( isdefined( n_alpha ), "Must specify an alpha value for screen_fade_to_alpha_with_blur." ); + Assert( IsPlayer( self ), "screen_fade_to_alpha_with_blur can only be called on players!" ); + + level notify( "_screen_fade" ); + level endon( "_screen_fade" ); + + hud_fade = get_fade_hud( str_shader ); + hud_fade FadeOverTime( n_fade_time ); + hud_fade.alpha = n_alpha; + + if ( isdefined( n_blur ) && ( n_blur >= 0 ) ) + { + self SetBlur( n_blur, n_fade_time ); + } + + wait n_fade_time; +} + +/@ +"Name: screen_fade_to_alpha( n_alpha, [n_time], [str_shader] )" +"Summary: Fades the screen in to a specified alpha value. Uses any shader. Defaults to black." +"Module: Utility" +"CallOn: NA" +"MandatoryArg: n_alpha: The alpha value to fade the hud to." +"MandatoryArg: n_fade_time: The time to fade." +"OptionalArg: str_shader: The shader to use for the hud element. Defaults to black." +"Example: screen_fade_to_alpha( .3 );" +"SPMP: singleplayer" +@/ +function screen_fade_to_alpha( n_alpha, n_fade_time, str_shader ) +{ + screen_fade_to_alpha_with_blur( n_alpha, n_fade_time, 0, str_shader ); +} + +function get_fade_hud( str_shader ) +{ + if ( !isdefined( str_shader ) ) + { + str_shader = "black"; + } + + if ( !isdefined( level.fade_hud ) ) + { + level.fade_hud = NewHudElem(); + level.fade_hud.x = 0; + level.fade_hud.y = 0; + level.fade_hud.horzAlign = "fullscreen"; + level.fade_hud.vertAlign = "fullscreen"; + //level.fade_hud.foreground = false; //Arcade Mode compatible + level.fade_hud.sort = 0; + level.fade_hud.alpha = 0; + } + + level.fade_hud SetShader( str_shader, 640, 480 ); + return level.fade_hud; +} + +/@ +"Name: missionFailedWrapper( fail_hint, shader, iWidth, iHeight, fDelay, x, y, b_count_as_death = true )" +"Summary: Call when you want the player to fail the mission." +"Module: Utility" +"CallOn: player or level entity" +"MandatoryArg:" +"OptionalArg: [fail_reason] : Localized string for the reason the player failed the mission." +"OptionalArg: [fail_hint] : Localized hint for how they can not fail the mission next time." +"OptionalArg: [shader] : Special fail icon Shader/Icon." +"OptionalArg: [iWidth] : Shader/Icon width." +"OptionalArg: [iHeight] : Shader/Icon height." +"OptionalArg: [fDelay] : Delay to show the Shader/Icon." +"OptionalArg: [b_count_as_death] : Count again player stats for deaths." +"Example: _utility::missionFailedWrapper();" +"SPMP: singleplayer" +@/ +function missionfailedwrapper( fail_reason, fail_hint, shader, iWidth, iHeight, fDelay, x, y, b_count_as_death = true ) +{ + if( level.missionfailed ) + { + return; + } + + if ( isdefined( level.nextmission ) ) + { + return; // don't fail the mission while the game is on it's way to the next mission. + } + + if ( GetDvarString( "failure_disabled" ) == "1" ) + { + return; + } + + // delete any existing in-game instructions created by screen_message_create() functionality + screen_message_delete(); + + if( isdefined( fail_hint ) ) + { + SetDvar( "ui_deadquote", fail_hint ); + } + + if( isdefined( shader ) ) + { + GetPlayers()[0] thread load::special_death_indicator_hudelement( shader, iWidth, iHeight, fDelay, x, y ); + } + + level.missionfailed = true; + // TODO: Why did this stop working? + // level flag::set( "missionfailed" ); + + if ( b_count_as_death ) + { + // GetPlayers()[0] inc_general_stat( "deaths" ); + } + + level thread coop::fadeOutAndLoadCheckpoint( fail_reason, fail_hint ); +} + +/@ +"Name: missionfailedwrapper_nodeath( fail_hint, shader, iWidth, iHeight, fDelay, x, y )" +"Summary: Call when you want the player to fail the mission but not count towards player death stats." +"Module: Utility" +"CallOn: player or level entity" +"MandatoryArg:" +"OptionalArg: [fail_reason] : Localized string for the reason the player failed the mission." +"OptionalArg: [fail_hint] : Localized hint for how they can not fail the mission next time." +"OptionalArg: [shader] : Special fail icon Shader/Icon." +"OptionalArg: [iWidth] : Shader/Icon width." +"OptionalArg: [iHeight] : Shader/Icon height." +"OptionalArg: [fDelay] : Delay to show the Shader/Icon." +"Example: _utility::missionfailedwrapper_nodeath();" +"SPMP: singleplayer" +@/ +function missionfailedwrapper_nodeath( fail_reason, fail_hint, shader, iWidth, iHeight, fDelay, x, y ) +{ + missionfailedwrapper( fail_reason, fail_hint, shader, iWidth, iHeight, fDelay, x, y, false ); +} + +function helper_message( message, delay, str_abort_flag ) +{ + level notify( "kill_helper_message" ); + level endon( "kill_helper_message" ); + + helper_message_delete(); + + level.helper_message = message; + + util::screen_message_create( message ); + + if( !isdefined(delay) ) + { + delay = 5; + } + + start_time = GetTime(); + while( 1 ) + { + time = GetTime(); + dt = ( time - start_time ) / 1000; + if( dt >= delay ) + { + break; + } + + if( isdefined(str_abort_flag) && (level flag::get(str_abort_flag) == true) ) + { + break; + } + + wait( 0.01 ); + } + + + if( isdefined(level.helper_message) ) + { + util::screen_message_delete(); + } + + level.helper_message = undefined; +} + +function helper_message_delete() +{ + if( isdefined(level.helper_message) ) + { + util::screen_message_delete(); + } + level.helper_message = undefined; +} + +/@ +"Name: show_hit_marker()" +"Summary: Displays hit marker on player HUD. Use this when custom scripting script models or brushes that need damage feedback." +"Module: HUD" +"CallOn: Player" +"Example: player show_hit_marker();" +@/ +function show_hit_marker() // self = player +{ + if ( IsDefined( self ) && IsDefined( self.hud_damagefeedback ) ) // hud_damagefeedback declared in _damagefeedback.gsc + { + self.hud_damagefeedback SetShader( "damage_feedback", 24, 48 ); + self.hud_damagefeedback.alpha = 1; + self.hud_damagefeedback FadeOverTime(1); + self.hud_damagefeedback.alpha = 0; + } +} + +/@ +"Name: init_hero(name, func_init, arg1, arg2, arg3, arg4, arg5 )" +"Summary: a function that can spawn or grab an entity and turn it into a hero character." +"Module: Level" +"CallOn: N/A" +"OptionalArg: func_init, arg1, arg2, arg3, arg4, arg5" +"Example: init_hero(woods, ::equip_wooods, primary_gun, secondary_gun); +"SPMP: singleplayer" +@/ + +function init_hero( name, func_init, arg1, arg2, arg3, arg4, arg5, b_show_in_ev = true ) +{ + if ( !isdefined( level.heroes ) ) + { + level.heroes = []; + } + + name = ToLower( name ); + + ai_hero = GetEnt( name + "_ai", "targetname", true ); + if ( !IsAlive( ai_hero ) ) + { + ai_hero = GetEnt( name, "targetname", true ); + + if ( !IsAlive( ai_hero ) ) + { + spawner = GetEnt( name, "targetname" ); + if ( !( isdefined( spawner.spawning ) && spawner.spawning ) ) + { + spawner.count++; + ai_hero = spawner::simple_spawn_single( spawner ); + + Assert( isdefined( ai_hero ), "Failed to spawn hero '" + name + "'." ); + + spawner notify( "hero_spawned", ai_hero ); + } + else + { + // Another thread is already spawning this hero, just wait for that one + spawner waittill( "hero_spawned", ai_hero ); + } + } + } + + level.heroes[ name ] = ai_hero; + ai_hero.animname = name; + ai_hero.is_hero = true; + ai_hero.enableTerrainIk = 1; //enable IK on Heros + + ai_hero SetTmodeProvider( true ); + ai_hero util::magic_bullet_shield(); + + ai_hero thread _hero_death( name ); + + if ( IsDefined( func_init ) ) + { + util::single_thread( ai_hero, func_init, arg1, arg2, arg3, arg4, arg5 ); + } + + if ( isdefined( level.customHeroSpawn ) ) + { + ai_hero [[level.customHeroSpawn]](); + } + + if( b_show_in_ev ) + { + ai_hero thread oed::enable_thermal(); + } + + return ai_hero; +} + +/@ +"Name: init_heroes(a_hero_names, func_init, arg1, arg2, arg3, arg4, arg5 )" +"Summary: a function that takes an array of targetname string and set them up as hero characters" +"Module: Level" +"CallOn: N/A" +"OptionalArg: func_init, arg1, arg2, arg3, arg4, arg5" +"Example: init_hero( a_pow_heroes, ::equip_pow_heroes, primary_gun, secondary_gun); +"SPMP: singleplayer" +@/ + +function init_heroes( a_hero_names, func, arg1, arg2, arg3, arg4, arg5 ) +{ + a_heroes = []; + foreach ( str_hero in a_hero_names ) + { + if ( !isdefined( a_heroes ) ) a_heroes = []; else if ( !IsArray( a_heroes ) ) a_heroes = array( a_heroes ); a_heroes[a_heroes.size]=init_hero( str_hero, func, arg1, arg2, arg3, arg4, arg5 );; + } + + return a_heroes; +} + + +function _hero_death( str_name ) +{ + self endon( "unmake_hero" ); + self waittill( "death" ); + + if ( isdefined( self ) ) + { + AssertMsg( "Hero '" + str_name + "' died." ); + } + + unmake_hero( str_name ); +} + +/@ +"Name: unmake_hero()" +"Summary: Removes the AI from the hero list and stops hero behaviors running on the AI such as magic_bullet_shield." +"Module: AI" +"CallOn: Friendly AI" +"Example: unmake_hero( "hendricks" );" +"SPMP: singleplayer" +@/ +function unmake_hero( str_name ) +{ + ai_hero = level.heroes[ str_name ]; + + level.heroes = array::remove_index( level.heroes, str_name, true ); + + // Do this last, as the _hero_death thread will be killed by it. + if ( IsAlive( ai_hero ) ) + { + ai_hero SetTmodeProvider( false ); + ai_hero util::stop_magic_bullet_shield(); + ai_hero notify( "unmake_hero" ); + } +} + +/@ +"Name: get_heroes()" +"Summary: Returns an array of all heroes currently in the level." +"Module: AI" +"Example: heroes = get_heroes();" +"SPMP: singleplayer" +@/ +function get_heroes() +{ + return level.heroes; +} + + +/@ +"Name: get_hero( str_name )" +"Summary: Returns a hero, or tries to spawn one if he doesn't exist" +"Module: Level" +"Example: level.ai_hendricks = get_hero( "hendricks" );" +"SPMP: singleplayer" +@/ +function get_hero( str_name ) +{ + if ( !isdefined( level.heroes ) ) + { + level.heroes = []; + } + + if ( isdefined( level.heroes[ str_name ] ) ) + { + return level.heroes[ str_name ]; + } + else + { + return init_hero( str_name ); + } +} + +/@ +"Name: is_hero()" +"Summary: Returns true if the AI is a hero, false if he is not." +"Module: AI" +"CallOn: Friendly AI" +"Example: ai_friendly is_hero();" +"SPMP: singleplayer" +@/ +function is_hero() +{ + return ( isdefined( self.is_hero ) && self.is_hero ); +} + +function init_streamer_hints( number_of_zones ) +{ + clientfield::register( "world", "force_streamer", 1, GetMinBitCountForNum( number_of_zones ), "int" ); +} + +/@ +"Name: clear_streamer_hint()" +"Summary: Clear all streamer hints." +"CallOn: NA" +"Example: util::clear_streamer_hint()" +@/ +function clear_streamer_hint() +{ + level flag::wait_till( "all_players_connected" ); + level clientfield::set( "force_streamer", 0 ); +} + +/@ +"Name: set_streamer_hint( , [b_clear_previous = true] )" +"Summary: Force the streamer to load a particular zone that you've set up in client script." +"CallOn: NA" +"MandatoryArg: : Integer of the zone you've defined" +"OptionalArg: [b_clear_previous] : Clears all previous streamer hints that script has asked for" +"Example: util::set_streamer_hint( STREAMER_LEVEL_START )" +@/ +function set_streamer_hint( n_zone, b_clear_previous = true ) +{ + level thread _set_streamer_hint( n_zone, b_clear_previous ); +} + +function _set_streamer_hint( n_zone, b_clear_previous = true ) +{ + level notify( "set_streamer_hint" ); + level endon( "set_streamer_hint" ); + + Assert( n_zone > 0, "Streamer hint zone values must be > 0." ); + + level flagsys::set( "streamer_loading" ); + + level flag::wait_till( "all_players_connected" ); + + if ( b_clear_previous ) + { + level clientfield::set( "force_streamer", 0 ); + util::wait_network_frame(); + } + + level clientfield::set( "force_streamer", n_zone ); + + if( !isdefined( level.b_wait_for_streamer_default ) ) + { + level.b_wait_for_streamer_default = 1; + // Don't wait in dev builds + /# + level.b_wait_for_streamer_default = 0; + #/ + } + + foreach ( player in level.players ) + { + player thread _streamer_hint_wait( n_zone ); + } + + /# + + n_timeout = GetTime() + ( 15 * 1000 ); + + #/ + + array::wait_till( level.players, "streamer" + n_zone, 15 ); + level flagsys::clear( "streamer_loading" ); + + level util::streamer_wait(); + + /# + + if ( GetTime() >= n_timeout ) + { + PrintTopRightln( "FORCE STREAMER TIMEOUT - " + string::rfill( GetTime(), 6, "0" ), ( 1, 0, 0 ) ); + } + else + { + PrintTopRightln( "FORCE STREAMER DONE - " + string::rfill( GetTime(), 6, "0" ), ( 1, 1, 1 ) ); + } + + #/ +} + +function _streamer_hint_wait( n_zone ) +{ + self endon( "disconnect" ); + level endon( "set_streamer_hint" ); + self waittillmatch( "streamer", n_zone ); + self notify( "streamer" + n_zone ); +} + +/@ +"Name: teleport_players_igc( , [coop_sort] )" +"Summary: Teleport players after a shared IGC. Functions similarly to skipto::teleport." +"CallOn: NA" +"MandatoryArg: : The name of the spawn point. Follows same ules as skipto system (using script_objective KVP)." +"OptionalArg: [coop_sort] : Specific which player gets which spot with the script_int KVP" +"Example: util::teleport_players_igc( "after_intro_igc" )" +@/ +function teleport_players_igc( str_spots, coop_sort ) +{ + // Don't teleport the players if it's a solo game + if( level.players.size <= 1 ) + { + return; + } + + // Grab the skipto points. if this skipto is the entrypoint into the level or needs each player in a particular spot, sort them for coop placement + a_spots = skipto::get_spots( str_spots, coop_sort ); + + // make sure there are enough points skipto spots for the players + assert( a_spots.size >= ( level.players.size - 1 ), "Need more teleport positions for players!" ); + + // set up each player + // ***SKIPS level.players[ 0 ]*** + // This allows scene animation to place Player 0 where he needs to be without a pop + for ( i = 0; i < level.players.size - 1; i++ ) + { + // Set the players' origin to each skipto point + level.players[i+1] SetOrigin( a_spots[i].origin ); + + if ( isdefined( a_spots[i].angles ) ) + { + level.players[i+1] util::delay_network_frames( 2, "disconnect", &SetPlayerAngles, a_spots[ i ].angles ); + } + } +} + +/@ +"Name: set_low_ready( b_lowready )" +"Summary: Sets the player to low ready, hides the hud and everything else that should happen when in low ready mode +"CallOn: Player" +"MandatoryArg: : True will set the player to low ready. False will turn off low ready" +"Example: e_player util::set_low_ready( true )" +@/ +function set_low_ready( b_lowready ) +{ + self SetLowReady( b_lowready ); + + self SetClientUIVisibilityFlag( "weapon_hud_visible", !b_lowready ); + + self AllowJump( !b_lowready ); + self AllowSprint( !b_lowready ); + self AllowDoubleJump( !b_lowready ); + + if ( b_lowready ) + { + self DisableOffhandWeapons(); + } + else + { + self EnableOffhandWeapons(); + } + + oed::enable_ev( !b_lowready ); + oed::enable_tac_mode( !b_lowready ); +} + +/@ +"Name: CleanupActorCorpses()" +"Summary: Delete corpses of actors in the level +"Example: CleanupActorCorpses()" +@/ +function CleanupActorCorpses() +{ + foreach (corpse in GetCorpseArray()) + { + if (isActorCorpse(corpse)) + { + corpse Delete(); + } + } +} + +/@ +"Name: set_level_start_flag( )" +"Summary: Tells the level to hold a black screen until this flag is set." +"CallOn: NA" +"MandatoryArg: : flag to wait for" +"Example: util::set_level_start_flag( "start_level" )" +@/ +function set_level_start_flag( str_flag ) +{ + level.str_level_start_flag = str_flag; + + if ( !flag::exists( str_flag ) ) + { + level flag::init( level.str_level_start_flag ); + } +} + +/@ +"Name: set_player_start_flag( )" +"Summary: Tells the player to hold a black screen when connecting until this flag is set." +"CallOn: NA" +"MandatoryArg: : flag to wait for" +"Example: util::set_level_start_flag( "start_player" )" +@/ +function set_player_start_flag( str_flag ) +{ + level.str_player_start_flag = str_flag; +} + +/@ +"Name: set_rogue_controlled( b_state )" +"Summary: Sets the enemy AI to be controlled by an enemy Cyber Soldier. Used in special cases, it disables the use of certain cyber core abilities." +"Module: AI" +"CallOn: Enemy AI" +"OptionalArg: : boolean value that toggles the rogue controlled state, defaults to true" +"Example: set_rogue_controlled( true );" +"SPMP: singleplayer" +@/ +function set_rogue_controlled( b_state = true ) +{ + if( b_state ) + { + self cybercom::cybercom_AIOptOut( "cybercom_hijack" ); + self cybercom::cybercom_AIOptOut( "cybercom_iffoverride" ); + self.rogue_controlled = true; + } + else + { + self cybercom::cybercom_AIClearOptOut( "cybercom_hijack" ); + self cybercom::cybercom_AIClearOptOut( "cybercom_iffoverride" ); + self.rogue_controlled = undefined; + } +} + +/@ +"Name: init_breath_fx()" +"Summary: will need to init fx in the main level init to use so clienfields get registered." +"CallOn: Level" +"Example: util::init_breath_fx();" +@/ +function init_breath_fx() +{ + clientfield::register( "toplayer", "player_cold_breath", 1, 1, "int" ); + clientfield::register( "actor", "ai_cold_breath", 1, 1, "counter" ); +} + +/@ +"Name: player_frost_breath( b_true )" +"Summary: Plays a breath effect on player camera" +"CallOn: Player" +"Example: array::thread_all( level.activeplayers, &util::player_frost_breath, true );" +@/ +function player_frost_breath( b_true ) //self = player +{ + self clientfield::set_to_player( "player_cold_breath", b_true ); +} + +/@ +"Name: ai_frost_breath( b_true )" +"Summary: Plays a breath effect on actor tag j_jaw" +"CallOn: Actors" +"Example: spawner::add_global_spawn_function( "allies", &util::ai_frost_breath );" +@/ +function ai_frost_breath() // self = ai spawned +{ + self endon( "death" ); + + if( ( self.archetype === "human" ) ) + { + wait( RandomFloatRange( 1, 3 ) ); //wait for ai to be spawned for client and dont breath right away. + + self clientfield::increment( "ai_cold_breath" ); + } +} + + +/@ +"Name: show_hint_text" +"Summary: Displays hint text for an amount of time. Can be turned off by sending a notify, or by calling hide_hint_text()." +"MandatoryArg: : The text to display." +"OptionalArg: : Should this menu flash on and off?" +"OptionalArg: : The use this notify to turn off the hint text." +"OptionalArg: : Override how many seconds the text is displayed for." +"Example: show_hint_text( "Your help text here!", "notify_hide_help_text" );" +@/ +function show_hint_text(str_text_to_show, b_should_blink=false, str_turn_off_notify="notify_turn_off_hint_text", n_display_time=4.0) +{ + self endon ("notify_turn_off_hint_text"); + self endon( "hint_text_removed" ); + + // Hide any help text which might already be up. + if ( isdefined(self.hint_menu_handle) ) + { + hide_hint_text(false); + } + + // Show the help text as a LUI menu. + self.hint_menu_handle = self OpenLUIMenu( "CPHintText" ); + self SetLUIMenuData( self.hint_menu_handle, "hint_text_line", str_text_to_show ); + + // Play the blink anim inside the LUImenu if it's meant to flash + if (b_should_blink) + { + lui::play_animation(self.hint_menu_handle, "blinking"); + } + else + { + lui::play_animation(self.hint_menu_handle, "display_noblink"); + } + + if( n_display_time != -1 ) + { + // Listen for hide or death notify. This interrupts the normal wait timer. + // This thread will also ensure the LUImenu is closed when the text has been displayed for the intended amount of time. + self thread hide_hint_text_listener(n_display_time); + + // Fade the hint text out after specified time if not interrupted. + // Note: the above thread will close the LUImenu when the desired time has elapsed. + self thread fade_hint_text_after_time(n_display_time, str_turn_off_notify); + } +} + +/@ +"Name: hide_hint_text" +"Summary: Hides any help text which may be on screen." +@/ +function hide_hint_text(b_fade_before_hiding=true) +{ + self endon( "hint_text_removed" ); + + if ( isdefined(self.hint_menu_handle) ) + { + if (b_fade_before_hiding) + { + lui::play_animation(self.hint_menu_handle, "fadeout"); + util::waittill_any_timeout(0.75, "kill_hint_text", "death"); + } + + self CloseLUIMenu(self.hint_menu_handle); + self.hint_menu_handle = undefined; + } + + // Terminate hint text threads listening for remove help text notify. + self notify("hint_text_removed"); +} + +// Fade out hint text before its luimenu is destroyed. +// If a notify to hide hint text is passed, this will fade out the hint text as well. +function fade_hint_text_after_time(n_display_time, str_turn_off_notify) +{ + self endon( "hint_text_removed" ); + self endon( "death" ); + self endon( "kill_hint_text" ); + + util::waittill_any_timeout(n_display_time - 0.75, str_turn_off_notify); + + hide_hint_text(true); +} + +// Listens for a notify to turn off the help text. +function hide_hint_text_listener(n_time) +{ + // Stop listening for turn off on death or when the hint text has been removed: + self endon( "hint_text_removed" ); + self endon( "disconnect" ); + + util::waittill_any_timeout(n_time, "kill_hint_text", "death"); + + hide_hint_text(false); +} + +// Displays an event message. +function show_event_message(player_handle, str_message) +{ + player_handle LUINotifyEvent( &"comms_event_message", 1, str_message ); + // Event messsage sound effect should go here +} + +/@ +"Name: init_interactive_gameobject( trigger, str_objective, str_hint_text , func_on_use , a_keyline_objects )" +"Summary: Returns a gameobject with all necessary parameters for CP." +"Module: Utility" +"MandatoryArg: : The trigger for the gameobject. Must be some type of use trigger" +"MandatoryArg: : The objective for the gameobject. This determines what icon and text appears on the object. 'Waypoint Type' of the objective should be '3D Prompt'. " +"MandatoryArg: : A string for the trigger's use prompt." +"OptionalArg: : Function to run when the gameobject is used. Passes in the player who triggered the gameobject as the first parameter. Gameobject becomes 'self' in the specified function" +"OptionalArg: : An entity or array of entities to be keylined" +@/ +function init_interactive_gameobject( trigger, str_objective, str_hint_text , func_on_use , a_keyline_objects ) +{ + trigger SetHintString( str_hint_text ); + trigger SetCursorHint( "HINT_INTERACTIVE_PROMPT" ); + + if( !isdefined( a_keyline_objects ) ) + { + a_keyline_objects = []; + } + else + { + if ( !isdefined( a_keyline_objects ) ) a_keyline_objects = []; else if ( !IsArray( a_keyline_objects ) ) a_keyline_objects = array( a_keyline_objects );; + + foreach( mdl in a_keyline_objects ) + { + mdl oed::enable_keyline( true ); + } + } + + game_object = gameobjects::create_use_object( "any", trigger, a_keyline_objects, ( 0, 0, 0 ), str_objective ); + game_object gameobjects::allow_use( "any" ); + game_object gameobjects::set_use_time( 0.35 ); + game_object gameobjects::set_owner_team( "allies" ); + game_object gameobjects::set_visible_team( "any" ); + game_object.single_use = false; + + // Set origin/angles so it can be used as an objective target. + game_object.origin = game_object.origin; + game_object.angles = game_object.angles; + + if ( isdefined( func_on_use ) ) + { + game_object.onUse = func_on_use; + } + + return game_object; +} + diff --git a/cp/bonuszm/_bonuszm.gsh b/cp/bonuszm/_bonuszm.gsh new file mode 100644 index 0000000..24aa303 --- /dev/null +++ b/cp/bonuszm/_bonuszm.gsh @@ -0,0 +1,308 @@ + +// ---------------------- +// AI RELATED +//----------------------- + + + + + + + + + + + + + +// 100 Deg + +// 180 Deg + + + + + + + + + + + + + + + + + + + + + +// ---------------------- +// SPAWNERS +//----------------------- + + + +// ---------------------- +// AITYPES +//----------------------- + + + + + + + + + + + + + + + + + + + + + + +// ---------------------- +// DROPS +//----------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// FFOTD ( 11/03/2015 ) Fix for DT#141451 +// Following list comes from the # texture/material index column of the weaponOptions.csv. Any indexes beyond these ones are post ship versions, +// which we do not want to use. The following indexes will not change at all in post ship environment so they are safe to use. + + +// ---------------------- +// POST FX +//----------------------- + + + + + + + +// ---------------------- +// GDT DATA +//----------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// ---------------------- +// WEAPONS +//----------------------- + +// Weapons Table path + + +// weapon + + +// min_attachments + + +// max_attachments + + +// attachments + + +// magicbox_only + + +// ---------------------- +// PATHABILITY - FOR AI +//----------------------- + + + +// ---------------------- +// REACHABILITY - FOR PLAYER +//----------------------- + + + + + +// ---------------------- +// MATURE +//----------------------- + + + +// ---------------------- +// Zombie Suicide Explosion +//----------------------- + + + + + + + + + + + + +// ---------------------- +// Zombie Sparky +//----------------------- + + + + + + + + + +// ---------------------- +// Deimos Zombie +//----------------------- + + + + +// ---------------------- +// ETHEREAL OVERLAY +//----------------------- + + + + + +// ---------------------- +// SPAWNING +//----------------------- + + + + + \ No newline at end of file diff --git a/cp/cybercom/_cybercom.gsh b/cp/cybercom/_cybercom.gsh new file mode 100644 index 0000000..c60ca47 --- /dev/null +++ b/cp/cybercom/_cybercom.gsh @@ -0,0 +1,115 @@ + + + + + + + + + + +//Control Set + + + + + + + +//Martial Set + + + + + + + +//Chaos Set + + + + + + + + + + + + +//rigs + + + + + + + + + + + + + + + + +// Enumerations for the different types of arm pulse patterns. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //1023 + + diff --git a/cp/gametypes/_globallogic_ui.gsc b/cp/gametypes/_globallogic_ui.gsc new file mode 100644 index 0000000..7daa0dc --- /dev/null +++ b/cp/gametypes/_globallogic_ui.gsc @@ -0,0 +1,960 @@ +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\lui_shared; +#using scripts\shared\scene_shared; +#using scripts\shared\string_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + +#using scripts\cp\gametypes\_loadout; +#using scripts\cp\gametypes\_globallogic; +#using scripts\cp\gametypes\_globallogic_player; +#using scripts\cp\gametypes\_save; +#using scripts\cp\gametypes\_spectating; + +#using scripts\cp\_util; +#using scripts\cp\teams\_teams; + +#precache( "string", "MP_HALFTIME" ); +#precache( "string", "MP_OVERTIME" ); +#precache( "string", "MP_ROUNDEND" ); +#precache( "string", "MP_INTERMISSION" ); +#precache( "string", "MP_SWITCHING_SIDES_CAPS" ); +#precache( "string", "MP_FRIENDLY_FIRE_WILL_NOT" ); +#precache( "string", "MP_RAMPAGE" ); +#precache( "string", "MP_ENDED_GAME" ); +#precache( "string", "MP_HOST_ENDED_GAME" ); + +#precache( "eventstring", "medal_received" ); +#precache( "eventstring", "killstreak_received" ); +#precache( "eventstring", "player_callout" ); +#precache( "eventstring", "score_event" ); +#precache( "eventstring", "rank_up" ); +#precache( "eventstring", "client_rank_up" ); +#precache( "eventstring", "weapon_unlocked" ); +#precache( "eventstring", "token_unlocked" ); +#precache( "eventstring", "gun_level_complete" ); +#precache( "eventstring", "challenge_complete" ); + +#precache( "menu", "InitialBlack" ); + + + +#namespace globallogic_ui; + +//REGISTER_SYSTEM( "globallogic_ui", &__init__, undefined ) + +function init() +{ + callback::add_callback( #"on_player_spawned", &on_player_spawn); + clientfield::register( "clientuimodel", "hudItems.cybercoreSelectMenuDisabled", 1, 1, "int" ); + clientfield::register( "clientuimodel", "hudItems.playerInCombat", 1, 1, "int" ); + clientfield::register( "clientuimodel", "playerAbilities.repulsorIndicatorDirection", 1, 2, "int" ); + clientfield::register( "clientuimodel", "playerAbilities.repulsorIndicatorIntensity", 1, 2, "int" ); + clientfield::register( "clientuimodel", "playerAbilities.proximityIndicatorDirection", 1, 2, "int" ); + clientfield::register( "clientuimodel", "playerAbilities.proximityIndicatorIntensity", 1, 2, "int" ); + clientfield::register( "clientuimodel", "serverDifficulty", 1, 3, "int" ); +} + +function on_player_spawn() +{ + self thread watch_player_in_combat(); + + assert( isdefined( level.gameSkill ) ); + + self clientfield::set_player_uimodel( "serverDifficulty", level.gameSkill ); +} + +function IsAnyAIAttackingThePlayer( playerEnt ) +{ + ais = GetAITeamArray( "axis" ); + ais = ArrayCombine( ais, GetAITeamArray( "team3" ), false, false ); // sometimes enemy AI is on team 3 as well + + foreach( ai in ais ) + { + if ( IsSentient( ai ) ) + { + if ( ai AttackedRecently( playerEnt, 10 ) ) + { + return true; + } + else if ( ai.enemy === playerEnt && + IsDefined( ai.weapon ) && + ai.weapon.name === "none" && + DistanceSquared( ai.origin, playerEnt.origin ) < ( (240) * (240) ) ) + { + // Melee AI is close to the player. + return true; + } + } + } + + return false; +} + +function IsAnyAIAwareOfPlayer( playerEnt ) +{ + ais = GetAITeamArray( "axis" ); + ais = ArrayCombine( ais, GetAITeamArray( "team3" ), false, false ); // sometimes enemy AI is on team 3 as well + + foreach( ai in ais ) + { + if ( IsSentient( ai ) ) + { + // Has known about player recently? + if ( ( ai LastKnownTime( playerEnt ) + 4000 ) >= GetTime() ) + return true; + } + } + + return false; +} + +function IsPlayerHurt( playerEnt ) +{ + return playerEnt.health < playerEnt.maxhealth; +} + +function watch_player_in_combat() +{ + self endon("kill_watch_player_in_combat"); + self endon( "disconnect" ); + + while( true ) + { + if ( IsPlayerHurt( self ) || IsAnyAIAttackingThePlayer( self ) ) + { + self clientfield::set_player_uimodel( "hudItems.playerInCombat", 1 ); + } + else + { + self clientfield::set_player_uimodel( "hudItems.playerInCombat", 0 ); + } + + wait( 0.5 ); + } +} + +function SetupCallbacks() +{ + level.autoassign =&menuAutoAssign; + level.spectator =&menuSpectator; + level.curClass =&menuClass; + level.teamMenu =&menuTeam; +} + +function freeGameplayHudElems() +{ + // free up some hud elems so we have enough for other things. + + // perk icons + if ( isdefined( self.perkicon ) ) + { + for ( numSpecialties = 0; numSpecialties < level.maxSpecialties; numSpecialties++ ) + { + if ( isdefined( self.perkicon[ numSpecialties ] ) ) + { + self.perkicon[ numSpecialties ] hud::destroyElem(); + self.perkname[ numSpecialties ] hud::destroyElem(); + } + } + } + + if ( isdefined( self.perkHudelem ) ) + { + self.perkHudelem hud::destroyElem(); + } + + // Killstreak icons + if ( isdefined( self.killstreakicon ) ) + { + if ( isdefined( self.killstreakicon[0] ) ) + { + self.killstreakicon[0] hud::destroyElem(); + } + if ( isdefined( self.killstreakicon[1] ) ) + { + self.killstreakicon[1] hud::destroyElem(); + } + if ( isdefined( self.killstreakicon[2] ) ) + { + self.killstreakicon[2] hud::destroyElem(); + } + if ( isdefined( self.killstreakicon[3] ) ) + { + self.killstreakicon[3] hud::destroyElem(); + } + if ( isdefined( self.killstreakicon[4] ) ) + { + self.killstreakicon[4] hud::destroyElem(); + } + } + + // lower message + if ( isdefined( self.lowerMessage ) ) + self.lowerMessage hud::destroyElem(); + if ( isdefined( self.lowerTimer ) ) + self.lowerTimer hud::destroyElem(); + + // progress bar + if ( isdefined( self.proxBar ) ) + self.proxBar hud::destroyElem(); + if ( isdefined( self.proxBarText ) ) + self.proxBarText hud::destroyElem(); + + // carry icon + if ( isdefined( self.carryIcon ) ) + self.carryIcon hud::destroyElem(); +} + +function teamPlayerCountsEqual( playerCounts ) +{ + count = undefined; + + foreach( team in level.teams ) + { + if ( !isdefined( count ) ) + { + count = playerCounts[team]; + continue; + } + if ( count != playerCounts[team] ) + return false; + } + + return true; +} + +function teamWithLowestPlayerCount( playerCounts, ignore_team ) +{ + count = 9999; + lowest_team = undefined; + + foreach( team in level.teams ) + { + if ( count > playerCounts[team] ) + { + count = playerCounts[team]; + lowest_team = team; + } + } + + return lowest_team; +} + +function menuAutoAssign( comingFromMenu ) +{ + teamKeys = GetArrayKeys( level.teams ); + assignment = teamKeys[randomInt(teamKeys.size)]; + + self closeMenus(); + + assignment = "allies"; + + if ( level.teamBased ) + { + if ( assignment == self.pers["team"] && (self.sessionstate == "playing" || self.sessionstate == "dead") ) + { + self beginClassChoice(); + return; + } + } + else + { + if ( GetDvarint( "party_autoteams" ) == 1 ) + { + // after they have spawned in once then we should let the random team assignment logic handle itself. + if( level.allow_teamchange != "1" || ( !self.hasSpawned && !comingFromMenu ) ) + { + team = getAssignedTeam( self ); + if( isdefined( level.teams[team] ) ) + { + assignment = team; + } + else if ( team == "spectator" && (!level.forceAutoAssign) ) + { + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + return; + } + } + } + } + + if ( assignment != self.pers["team"] && (self.sessionstate == "playing" || self.sessionstate == "dead") ) + { + self.switching_teams = true; + self.switchedTeamsResetGadgets = true; + self.joining_team = assignment; + self.leaving_team = self.pers["team"]; + self suicide(); + } + + self.pers["team"] = assignment; + self.team = assignment; + self.pers["class"] = undefined; + self.curClass = undefined; + self.pers["weapon"] = undefined; + self.pers["savedmodel"] = undefined; + + self updateObjectiveText(); + + self.sessionteam = assignment; + + if ( !isAlive( self ) ) + self.statusicon = "hud_status_dead"; + + self notify("joined_team"); + level notify( "joined_team" ); + callback::callback( #"on_joined_team" ); + self notify("end_respawn"); + + self beginClassChoice(); + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + +} + +function teamScoresEqual( ) +{ + score = undefined; + + foreach( team in level.teams ) + { + if ( !isdefined( score ) ) + { + score = getTeamScore(team); + continue; + } + if ( score != getTeamScore(team) ) + return false; + } + + return true; +} + +function teamWithLowestScore() +{ + score = 99999999; + lowest_team = undefined; + + foreach( team in level.teams ) + { + if ( score > getTeamScore(team ) ) + lowest_team = team; + } + + return lowest_team; +} + +function pickTeamFromScores(teams) +{ + assignment = "allies"; + + if ( teamScoresEqual() ) + assignment = teams[randomInt(teams.size)]; + else + assignment = teamWithLowestScore(); + + return assignment; +} + +function get_splitscreen_team() +{ + for ( index = 0; index < level.players.size; index++ ) + { + if ( !isdefined(level.players[index]) ) + continue; + + if ( level.players[index] == self ) + continue; + + if ( !(self IsPlayerOnSameMachine( level.players[index] )) ) + continue; + + team = level.players[index].sessionteam; + + // going to assume first non-spectator + if ( team != "spectator" ) + return team; + } + + return ""; +} + +function updateObjectiveText() +{ + if ( SessionModeIsZombiesGame() || (self.pers["team"] == "spectator") ) + { + self SetClientCGObjectiveText( "" ); + return; + } + + if( level.scorelimit > 0 ) + { + self SetClientCGObjectiveText( util::getObjectiveScoreText( self.pers["team"] ) ); + } + else + { + self SetClientCGObjectiveText( util::getObjectiveText( self.pers["team"] ) ); + } +} + + +function closeMenus() +{ + self closeInGameMenu(); +} + +function beginClassChoice() +{ + assert( isdefined( level.teams[self.pers["team"]] ) ); + + team = self.pers["team"]; + + self CloseMenu( game["menu_start_menu"] ); + + if ( !GetDvarInt( "art_review", 0 ) ) + { + self thread fullscreen_black(); + } + + b_disable_cac = GetDvarInt( "force_no_cac", 0 ); + + if ( !GetDvarInt( "force_cac", 0 ) || b_disable_cac ) + { + prevclass = self savegame::get_player_data( "playerClass", undefined ); + prevHeroWeapons = self savegame::get_player_data( savegame::get_mission_name() + "hero_weapon", undefined ); + + if ( isdefined( prevclass ) || b_disable_cac + || ( isdefined( level.disableClassSelection ) && level.disableClassSelection ) + || ( isdefined( self.disableClassSelection ) && self.disableClassSelection ) + || GetDvarint( "migration_soak" ) == 1 ) + { + self.curClass = (isdefined(prevclass)?prevclass:level.defaultClass); + self.pers[ "class" ] = self.curClass; + + {wait(.05);}; + + if ( self.sessionstate != "playing" && game[ "state" ] == "playing" ) + { + self thread [[ level.spawnClient ]](); + } + + globallogic::updateTeamStatus(); + self thread spectating::set_permissions_for_machine(); + + return; + } + } + + // menu_changeclass_team is the one where you choose one of the n classes to play as. + // menu_class_team is where you can choose to change your team, class, controls, or leave game. + self CloseMenu( game[ "menu_changeclass" ] ); + self openMenu( game[ "menu_changeclass_" + team ] ); + + //if ( level.rankedMatch ) + // self openMenu( game[ "menu_changeclass_" + team ] ); + //else + // self openMenu( game[ "menu_start_menu" ] ); +} + +function fullscreen_black() +{ + self endon( "disconnect" ); + + util::show_hud( 0 ); + + self CloseMenu( "InitialBlack" ); + self OpenMenu( "InitialBlack" ); + + b_hot_joining = false; + if ( level flag::get( "all_players_spawned" ) ) + { + b_hot_joining = true; + } + + self.fullscreen_black_active = true; + self thread fullscreen_black_checkpoint_restore(); //this thread makes sure the fullscreen black is held up even if you restore from checkpoint and it needs to be held up + + self Hide(); + + {wait(.05);}; + + if ( isdefined( level.str_level_start_flag ) || isdefined( level.str_player_start_flag ) ) + { + init_start_flags(); + + self thread fullscreen_black_freeze_controls(); + + if ( isdefined( level.str_level_start_flag ) ) + { + level flag::wait_till( level.str_level_start_flag ); + } + + if ( isdefined( level.str_player_start_flag ) ) + { + self flag::wait_till( level.str_player_start_flag ); + } + } + + if ( b_hot_joining && !( isdefined( level.is_safehouse ) && level.is_safehouse ) ) + { + while ( self.sessionstate !== "playing" ) + { + {wait(.05);}; + } + + self thread fullscreen_black_freeze_controls(); + + while ( self IsLoadingCinematicPlaying() ) + { + {wait(.05);}; + } + + self flag::wait_till( "loadout_given" ); + waittillframeend; // if we are hot-joining an IGC wait until we can detect that + + wait 2; + + // hold black and wait for streamer + self util::streamer_wait( undefined, 5, 5 ); + + self thread lui::screen_fade_in( .3, "black", "hot_join" ); + } + + if ( !flagsys::get( "shared_igc" ) ) + { + self Show(); + } + + self flagsys::set( "kill_fullscreen_black" ); + + self clientfield::set_to_player( "sndLevelStartSnapOff", 1 ); + self CloseMenu( "InitialBlack" ); + + self.fullscreen_black_active = undefined; + + util::show_hud( 1 ); + + /# + PrintTopRightln( "["+GetTime()+"] KILL INITIAL BLACKSCREEN: PLAYER " + self GetEntityNumber(), ( 1, 1, 1 ) ); + streamerSkiptoDebug( GetSkiptos() ); + #/ +} + +function fullscreen_black_checkpoint_restore() +{ + self endon( "disconnect" ); + self endon( "kill_fullscreen_black" ); //this seems to be set when the initial fullscreen is done + + b_fullscreen_black = self.fullscreen_black_active; + + level waittill( "save_restore" ); + + if( ( isdefined( b_fullscreen_black ) && b_fullscreen_black ) ) + { + self CloseMenu( "InitialBlack" ); + self OpenMenu( "InitialBlack" ); + } +} + +function init_start_flags() +{ + if ( isdefined( level.str_player_start_flag ) && !self flag::exists( level.str_player_start_flag ) ) + { + self flag::init( level.str_player_start_flag ); + } + + if ( isdefined( level.str_level_start_flag ) && !level flag::exists( level.str_level_start_flag ) ) + { + level flag::init( level.str_level_start_flag ); + } +} + +function fullscreen_black_freeze_controls() +{ + self endon( "disconnect" ); + + self.b_game_start_invulnerability = true; + + self flagsys::wait_till( "loadout_given" ); + + self DisableWeapons(); + self FreezeControls( true ); + + wait 0.1; // Several other scripts unfreeze controls when spawning, need to wait + waittillframeend; + + self FreezeControls( true ); + self DisableWeapons(); + + self flagsys::wait_till( "kill_fullscreen_black" ); + + self EnableWeapons(); + self FreezeControls( false ); + + self.b_game_start_invulnerability = undefined; +} + +function showMainMenuForTeam() +{ + assert( isdefined( level.teams[self.pers["team"]] ) ); + + team = self.pers["team"]; + + // menu_changeclass_team is the one where you choose one of the n classes to play as. + // menu_class_team is where you can choose to change your team, class, controls, or leave game. + + self openMenu( game[ "menu_changeclass_" + team ] ); +} + +function menuTeam( team ) +{ + self closeMenus(); + + if ( !level.console && level.allow_teamchange == "0" && (isdefined(self.hasDoneCombat) && self.hasDoneCombat) ) + { + return; + } + + if(self.pers["team"] != team) + { + // allow respawn when switching teams during grace period. + if ( level.inGracePeriod && (!isdefined(self.hasDoneCombat) || !self.hasDoneCombat) ) + self.hasSpawned = false; + + if(self.sessionstate == "playing") + { + self.switching_teams = true; + self.switchedTeamsResetGadgets = true; + self.joining_team = team; + self.leaving_team = self.pers["team"]; + self suicide(); + } + + self.pers["team"] = team; + self.team = team; + self.pers["class"] = undefined; + self.curClass = undefined; + self.pers["weapon"] = undefined; + self.pers["savedmodel"] = undefined; + + self updateObjectiveText(); + + if ( !level.rankedMatch && !level.leagueMatch ) + { + self.sessionstate = "spectator"; + } + + self.sessionteam = team; + + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + + self notify("joined_team"); + level notify( "joined_team" ); + callback::callback( #"on_joined_team" ); + self notify("end_respawn"); + } + + self beginClassChoice(); +} + +function menuSpectator() +{ + self closeMenus(); + + if(self.pers["team"] != "spectator") + { + if(isAlive(self)) + { + self.switching_teams = true; + self.switchedTeamsResetGadgets = true; + self.joining_team = "spectator"; + self.leaving_team = self.pers["team"]; + self suicide(); + } + + self.pers["team"] = "spectator"; + self.team = "spectator"; + self.pers["class"] = undefined; + self.curClass = undefined; + self.pers["weapon"] = undefined; + self.pers["savedmodel"] = undefined; + + self updateObjectiveText(); + + self.sessionteam = "spectator"; + + [[level.spawnSpectator]](); + + self thread globallogic_player::spectate_player_watcher(); + + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + + self notify("joined_spectators"); + callback::callback( #"on_joined_spectate" ); + } +} + +function menuClass( response ) +{ + self closeMenus(); + + // this should probably be an assert + if ( !isdefined( self.pers[ "team" ] ) || !( isdefined( level.teams[ self.pers[ "team" ]] ) ) ) + { + return; + } + + //-- mobile armory handles these changes within the system + if ( flagsys::get( "mobile_armory_in_use" ) ) + { + return; + } + + playerclass = ""; + + if ( response == "cancel" ) + { + prevclass = self savegame::get_player_data( "playerClass", undefined ); + + if ( isdefined( prevclass ) ) + { + playerclass = prevclass; + } + else + { + playerclass = level.defaultClass; + } + } + else + { + responseArray = strtok( response, "," ); + if( responseArray.size > 1 ) + { + str_class_chosen = responseArray[0]; + clientNum = Int( responseArray[1] ); + + altPlayer = util::getPlayerFromClientNum( clientNum ); + } + else + { + str_class_chosen = response; + } + + playerclass = self loadout::getClassChoice( str_class_chosen ); + + if( IsDefined( altPlayer ) ) + { + xuid = altPlayer GetXUID(); + self savegame::set_player_data( "altPlayerID", xuid ); + } + else + { + self savegame::set_player_data( "altPlayerID", undefined ); + } + + self savegame::set_player_data( "saved_weapon", undefined ); + self savegame::set_player_data( "saved_weapondata", undefined ); + self savegame::set_player_data( "lives", undefined ); + self savegame::set_player_data( "saved_rig1", undefined ); + self savegame::set_player_data( "saved_rig1_upgraded", undefined ); + self savegame::set_player_data( "saved_rig2", undefined ); + self savegame::set_player_data( "saved_rig2_upgraded", undefined ); + } + + if( (isdefined( self.pers["class"] ) && self.pers["class"] == playerclass) ) + return; + + self.pers["changed_class"] = true; + self notify ( "changed_class" ); + waittillframeend; + //The player selected the class they are currently using + if( isdefined(self.curClass) && self.curClass == playerclass ) + { + self.pers["changed_class"] = false; + } + + if ( self.sessionstate == "playing" ) + { + self savegame::set_player_data( "playerClass", playerClass ); + self.pers["class"] = playerClass; + self.curClass = playerclass; + self.pers["weapon"] = undefined; + + if ( game["state"] == "postgame" ) + return; + + supplyStationClassChange = isdefined( self.usingSupplyStation ) && self.usingSupplyStation; + self.usingSupplyStation = false; + + if ( ( level.inGracePeriod && !self.hasDoneCombat ) || supplyStationClassChange ) // used weapons check? + { + self loadout::setClass( self.pers["class"] ); + self.tag_stowed_back = undefined; + self.tag_stowed_hip = undefined; + self loadout::giveLoadout( self.pers["team"], self.pers["class"] ); + } + else if ( !( self IsSplitScreen() ) ) + { + self IPrintLnBold( game["strings"]["change_class"] ); + } + } + else + { + self savegame::set_player_data( "playerClass",playerClass); + self.pers["class"] = playerclass; + self.curClass = playerclass; + self.pers["weapon"] = undefined; + + if ( game["state"] == "postgame" ) + return; + + if ( self.sessionstate != "spectator" ) + { + if ( self IsInVehicle() ) + return; + + if ( self IsRemoteControlling() ) + return; + + if ( self IsWeaponViewOnlyLinked() ) + return false; + } + + if ( game["state"] == "playing" ) + { + timePassed = undefined; + + if ( isdefined( self.respawnTimerStartTime ) ) + { + timePassed = (gettime() - self.respawnTimerStartTime) / 1000; + } + + self thread [[level.spawnClient]](timePassed); + + self.respawnTimerStartTime = undefined; + + } + } + + globallogic::updateTeamStatus(); + + self thread spectating::set_permissions_for_machine(); + self notify("class_changed"); +} + + +/* +function showSafeSpawnMessage() +{ + if ( level.splitscreen ) + return; + + // don't show it if they've already asked for a safe spawn + if ( self.wantSafeSpawn ) + return; + + if ( !isdefined( self.safeSpawnMsg ) ) + { + self.safeSpawnMsg = hud::createFontString( "default", 1.4 ); + self.safeSpawnMsg hud::setPoint( "CENTER", level.lowerTextYAlign, 0, level.lowerTextY + 50 ); + self.safeSpawnMsg setText( &"PLATFORM_PRESS_TO_SAFESPAWN" ); + self.safeSpawnMsg.archived = false; + } + self.safeSpawnMsg.alpha = 1; +} +function hideSafeSpawnMessage() +{ + if ( !isdefined( self.safeSpawnMsg ) ) + return; + + self.safeSpawnMsg.alpha = 0; +} +*/ + + +function removeSpawnMessageShortly( delay ) +{ + self endon("disconnect"); + + waittillframeend; // so we don't endon the end_respawn from spawning as a spectator + + self endon("end_respawn"); + + wait delay; + + self util::clearLowerMessage( 2.0 ); +} + +// weakpoint helpers +#precache( "eventstring", "weakpoint_update" ); + +// self = entity +function weakpoint_anim_watch( precachedBoneName ) +{ + self endon( "death" ); + self endon( "weakpoint_destroyed" ); + + while(true) + { + self waittill( "weakpoint_update", boneName, event ); + + if ( boneName == precachedBoneName ) + { + if ( event == "damage" ) + { + LUINotifyEvent( &"weakpoint_update", 3, 2, self getEntityNumber(), precachedBoneName ); + } + else if ( event == "repulse" ) + { + LUINotifyEvent( &"weakpoint_update", 3, 3, self getEntityNumber(), precachedBoneName ); + } + + wait 0.5; + } + } +} + +// self = entity +function destroyWeakpointWidget( precachedBoneName ) +{ + LUINotifyEvent( &"weakpoint_update", 3, 0, self getEntityNumber(), precachedBoneName ); + self notify( "weakpoint_destroyed" ); +} + +// self = entity +function createWeakpointWidget( precachedBoneName, closeStateMaxDistance = undefined, mediumStateMaxDistance = undefined ) +{ + if ( !isdefined( closeStateMaxDistance ) ) + { + closeStateMaxDistance = GetDVarInt( "ui_weakpointIndicatorNear", 1050 ); + } + + if ( !isdefined( mediumStateMaxDistance ) ) + { + mediumStateMaxDistance = GetDVarInt( "ui_weakpointIndicatorMedium", 1900 ); + } + + LUINotifyEvent( &"weakpoint_update", 5, 1, self getEntityNumber(), precachedBoneName, closeStateMaxDistance, mediumStateMaxDistance ); + + self thread weakpoint_anim_watch( precachedBoneName ); +} + +// self = entity +function triggerWeakpointDamage( precachedBoneName ) +{ + self notify( "weakpoint_update", precachedBoneName, "damage" ); +} + +// self = entity +function triggerWeakpointRepulsed( precachedBoneName ) +{ + self notify( "weakpoint_update", precachedBoneName, "repulse" ); +} diff --git a/mp/_acousticsensor.csc b/mp/_acousticsensor.csc new file mode 100644 index 0000000..bfc4e2b --- /dev/null +++ b/mp/_acousticsensor.csc @@ -0,0 +1,19 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_acousticsensor; + + + +#using scripts\mp\_util; + +#namespace acousticsensor; + +function autoexec __init__sytem__() { system::register("acousticsensor",&__init__,undefined,undefined); } + +function __init__() +{ + acousticsensor::init_shared(); +} \ No newline at end of file diff --git a/mp/_acousticsensor.gsc b/mp/_acousticsensor.gsc new file mode 100644 index 0000000..c81b43d --- /dev/null +++ b/mp/_acousticsensor.gsc @@ -0,0 +1,19 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_acousticsensor; + + + +#using scripts\mp\_util; + +#namespace acousticsensor; + +function autoexec __init__sytem__() { system::register("acousticsensor",&__init__,undefined,undefined); } + +function __init__() +{ + acousticsensor::init_shared(); +} diff --git a/mp/_ambient.csc b/mp/_ambient.csc new file mode 100644 index 0000000..5d9ae97 --- /dev/null +++ b/mp/_ambient.csc @@ -0,0 +1,714 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\sound_shared; + + + +#namespace ambient; + +function autoexec __init__sytem__() { system::register("ambient",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_localclient_connect( &on_player_connect ); +} + +function on_player_connect( localclientnum ) +{ + thread ceiling_fans_init( localclientnum ); + thread clocks_init( localclientnum ); + thread spin_anemometers( localclientnum ); + if ( isdefined( level._levelSpecificAmbient ) ) + { + thread [[level._levelSpecificAmbient]]( localClientNum ); + } +} + +// setup a script_struct to play scripted Flak88 tracers FX +function setup_point_fx(point, fx_id) +{ + if(isdefined(point.script_fxid)) + { + fx_id = point.script_fxid; + } + + point.fx_id = fx_id; + + if(isdefined(point.angles)) + { + point.forward = anglesToForward(point.angles); + point.up = anglesToUp(point.angles); + } + else + { + point.angles = (0, 0, 0); + point.forward = (0, 0, 0); + point.up = (0, 0, 0); + } + + if(point.targetname == "flak_fire_fx") + { + level thread ambient_flak_think(point); + } + + if(point.targetname == "fake_fire_fx") + { + level thread ambient_fakefire_think(point); + } +} + +// rotate the Flak88 tracers FX +function ambient_flak_think(point) +{ + amount = undefined; + speed = undefined; + night = false; + + min_delay = 0.4; + max_delay = 4; + + min_burst_time = 1; + max_burst_time = 3; + + point.is_firing = false; + level thread ambient_flak_rotate(point); + level thread ambient_flak_flash(point, min_burst_time, max_burst_time); + + for(;;) + { + timer = randomFloatRange(min_burst_time, max_burst_time); + while(timer > 0) + { + point.is_firing = true; + playFX(0, level._effect[point.fx_id], point.origin, point.forward, point.up); + thread sound::play_in_space(0, "wpn_triple25_fire", point.origin); + wait(0.2); + + timer -= 0.2; + } + + point.is_firing = false; + wait(randomFloatRange(min_delay, max_delay)); + } +} + +// This mimics the rotation of the FX, but without using an ent (or client-ent) +// Updates the forward and up vectors which are used above +function ambient_flak_rotate(point) +{ + min_pitch = 30; + max_pitch = 80; + + if(isdefined(point.angles)) + { + pointangles = point.angles; + } + else + { + pointangles = (0, 0, 0); + } + + for(;;) + { + time = randomFloatRange(0.5, 2); + steps = time * 10; + + random_angle = (randomIntRange(min_pitch, max_pitch) * -1, randomInt(360), 0); + + forward = anglesToForward(random_angle); + up = anglesToUp(random_angle); + + diff_forward = (forward - point.forward) / steps; + diff_up = (up - point.up) / steps; + + for(i = 0; i < steps; i++) + { + point.forward += diff_forward; + point.up += diff_up; + + wait(0.1); + } + + point.forward = forward; + point.up = up; + } +} + +// This gives a random chance to play a cloud flash +// or flak burst FX for the ambient Flak +function ambient_flak_flash(point, min_burst_time, max_burst_time) +{ + min_dist = 5000; + max_dist = 6500; + + if(isdefined(point.script_mindist)) + { + min_dist = point.script_mindist; + } + + if(isdefined(point.script_maxdist)) + { + max_dist = point.script_maxdist; + } + + min_burst_time = 0.25; + max_burst_time = 1; + + fxpos = undefined; + + while(1) + { + if( !point.is_firing ) + { + wait( 0.25 ); + continue; + } + + fxpos = point.origin + VectorScale(point.forward, randomIntRange(min_dist, max_dist)); + + playFX(0, level._effect["flak_burst_single"], fxpos); + + if(isdefined(level.timeofday) && (level.timeofday == "evening" || level.timeofday == "night")) + { + playFX(0, level._effect["flak_cloudflash_night"], fxpos); + } + + wait randomFloatRange(min_burst_time, max_burst_time); + } +} + + + +// ******************************************************************************************************* +// Ambient Weapon Muzzleflashes +// +// point = a script_struct in the map +// This is used to play the ambient fake fire fx and sound +function ambient_fakefire_think(point) +{ + fireSound = undefined; + weapType = undefined; + + burstMin = undefined; + burstMax = undefined; + betweenShotsMin = undefined; + betweenShotsMax = undefined; + reloadTimeMin = undefined; + reloadTimeMax = undefined; + soundChance = undefined; + + if(!isdefined(point.weaponinfo)) + { + point.weaponinfo = "axis_turret"; + } + + // determine what type of weapon the script_struct is faking + switch(point.weaponinfo) + { + case "allies_assault": + + if(isdefined(level.allies_team) && (level.allies_team == "marines")) + { + fireSound = "weap_bar_fire"; + } + else + { + fireSound = "weap_dp28_fire_plr"; + } + + burstMin = 16; + burstMax = 24; + betweenShotsMin = 0.05; + betweenShotsMax = 0.08; + reloadTimeMin = 4; + reloadTimeMax = 7; + soundChance = 75; + weapType = "assault"; + break; + + case "axis_assault": + + if(isdefined(level.axis_team) && (level.axis_team == "german")) + { + fireSound = "weap_mp44_fire"; + } + else + { + fireSound = "weap_type99_fire"; + } + + burstMin = 16; + burstMax = 24; + betweenShotsMin = 0.05; + betweenShotsMax = 0.08; + reloadTimeMin = 4; + reloadTimeMax = 7; + soundChance = 75; + weapType = "assault"; + break; + + case "allies_rifle": + + if(isdefined(level.allies_team) && (level.allies_team == "marines")) + { + fireSound = "weap_m1garand_fire"; + } + else + { + fireSound = "weap_mosinnagant_fire"; + } + + burstMin = 1; + burstMax = 3; + betweenShotsMin = 0.8; + betweenShotsMax = 1.3; + reloadTimeMin = 3; + reloadTimeMax = 6; + soundChance = 95; + weapType = "rifle"; + break; + + case "axis_rifle": + + if(isdefined(level.axis_team) && (level.axis_team == "german")) + { + fireSound = "weap_kar98k_fire"; + } + else + { + fireSound = "weap_arisaka_fire"; + } + + burstMin = 1; + burstMax = 3; + betweenShotsMin = 0.8; + betweenShotsMax = 1.3; + reloadTimeMin = 3; + reloadTimeMax = 6; + soundChance = 95; + weapType = "rifle"; + break; + + case "allies_smg": + + if(isdefined(level.allies_team) && (level.allies_team == "marines")) + { + fireSound = "weap_thompson_fire"; + } + else + { + fireSound = "weap_ppsh_fire"; + } + + burstMin = 14; + burstMax = 28; + betweenShotsMin = 0.08; + betweenShotsMax = 0.12; + reloadTimeMin = 2; + reloadTimeMax = 5; + soundChance = 75; + weapType = "smg"; + break; + + case "axis_smg": + + if(isdefined(level.axis_team) && (level.axis_team == "german")) + { + fireSound = "weap_mp40_fire"; + } + else + { + fireSound = "weap_type100_fire"; + } + + burstMin = 14; + burstMax = 28; + betweenShotsMin = 0.08; + betweenShotsMax = 0.12; + reloadTimeMin = 2; + reloadTimeMax = 5; + soundChance = 75; + weapType = "smg"; + break; + + case "allies_turret": + + if(isdefined(level.allies_team) && (level.allies_team == "marines")) + { + fireSound = "weap_30cal_fire"; + } + else + { + fireSound = "weap_dp28_fire_plr"; + } + + burstMin = 60; + burstMax = 90; + betweenShotsMin = 0.05; + betweenShotsMax = 0.08; + reloadTimeMin = 3; + reloadTimeMax = 6; + soundChance = 95; + weapType = "turret"; + break; + + case "axis_turret": + + if(isdefined(level.axis_team) && (level.axis_team == "german")) + { + fireSound = "weap_bar_fire"; //update this if the sound changes + } + else + { + fireSound = "weap_type92_fire"; + } + + burstMin = 60; + burstMax = 90; + betweenShotsMin = 0.05; + betweenShotsMax = 0.08; + reloadTimeMin = 3; + reloadTimeMax = 6; + soundChance = 95; + weapType = "turret"; + break; + + default: + ASSERTMSG("Ambient Fakefire: Weapon Info '" + point.weaponinfo + "' is not recognized."); + } + + while(1) + { + // burst fire + burst = randomIntRange(burstMin, burstMax); + + for(i = 0; i < burst; i++) + { + traceDist = 10000; + + target = point.origin + VectorScale(anglesToForward(point.angles + (-3 + randomInt(6), -5 + randomInt(10), 0)), traceDist); + + // -- not using real weaponsettings + if ( randomInt(100) <= 20 ) + { + bulletTracer(point.origin, target); + } + + playFX(0, level._effect[point.fx_id], point.origin, point.forward); + + // snyder steez - reduce popcorn effect +// if(randomInt(100) <= soundChance) +// { +// thread sound::play_in_space(0, fireSound, point.origin); +// } + + wait (randomFloatRange(betweenShotsMin, betweenShotsMax)); + } + + wait (randomFloatRange(reloadTimeMin, reloadTimeMax)); + } +} + +function ceiling_fans_init(clientNum) +{ + // grab all of the ceiling fans and make them spin + fan_array = GetEntArray(clientNum, "ceiling_fan", "targetname"); + if( isdefined(fan_array) ) + { + /# println("**********fan array is defined, size: " + fan_array.size); #/ + array::thread_all( fan_array,&spin_fan ); + } +} + +function spin_fan() // self == fan from the array +{ + self endon("entityshutdown"); + + //println("**********fan running"); + // get the speed from the fan, if no speed then make it random + if ( !isdefined ( self.speed ) ) + { + self.speed = RandomIntRange(1, 100); + self.speed = (self.speed % 10) + 1; + } + if ( self.speed < 1 ) + { + self.speed = RandomIntRange(1, 100); + self.speed = (self.speed % 10) + 1; + } + + // see if they want it to wobble + do_wobble = false; + wobble = self.script_noteworthy; + if( isdefined(wobble) ) + { + if( wobble == "wobble" ) + { + do_wobble = true; + self.wobble_speed = self.speed * 0.5; + } + } + + //println("**********fan speed: " + self.speed); + //println("**********fan wobble: " + do_wobble); + + while(true) + { + if( !do_wobble ) + { + self RotateYaw(180, self.speed); + self waittill("rotatedone"); + } + else + { + self RotateYaw(340, self.speed); + self waittill("rotatedone"); + self RotateYaw(20, self.wobble_speed); + self waittill("rotatedone"); + } + } +} + + +function clocks_init(clientNum) +{ + // the format should be an array (hour, min, sec), military time + // if we pass in a 1 then we'll get GMT 0 London time, else we get the local time on the kit + curr_time = GetSystemTime(); + + // put the hands in the right place + hours = curr_time[0]; + if( hours > 12 ) + { + hours -= 12; + } + if( hours == 0 ) + { + hours = 12; + } + minutes = curr_time[1]; + seconds = curr_time[2]; + + // set the starting time + // hoping that all of the hands start pointing straight up at 12 + // each hour is 30 degrees of rotation ... + // it should also rotate a little bit each time the minute hand moves + // the math is 30 degrees of rotations in 3600 seconds (1 hour) + hour_hand = GetEntArray(clientNum, "hour_hand", "targetname"); + hour_values = []; + hour_values["hand_time"] = hours; + hour_values["rotate"] = 30; + hour_values["rotate_bit"] = 30 / 3600; + // we need to do the first rotation based on the beginning time, if we don't do this the time will look like it's off a little bit + hour_values["first_rotate"] = ((minutes * 60) + seconds) * hour_values["rotate_bit"]; + + // each minute is 6 degrees of rotation ... + // it should also rotate a little bit each time the second hand moves + // the math is 6 degrees of rotations in 60 seconds (1 minute) + minute_hand = GetEntArray(clientNum, "minute_hand", "targetname"); + minute_values = []; + minute_values["hand_time"] = minutes; + minute_values["rotate"] = 6; + minute_values["rotate_bit"] = 6 / 60; + // we need to do the first rotation based on the beginning time, if we don't do this the time will look like it's off a little bit + minute_values["first_rotate"] = seconds * minute_values["rotate_bit"]; + + // each second is 6 degrees of rotation + second_hand = GetEntArray(clientNum, "second_hand", "targetname"); + second_values = []; + second_values["hand_time"] = seconds; + second_values["rotate"] = 6; + second_values["rotate_bit"] = 6; + + hour_hand_array = GetEntArray(clientNum, "hour_hand", "targetname"); + if( isdefined(hour_hand_array) ) + { + /# println("**********hour_hand_array is defined, size: " + hour_hand_array.size); #/ + array::thread_all( hour_hand_array,&clock_run, hour_values ); + } + minute_hand_array = GetEntArray(clientNum, "minute_hand", "targetname"); + if( isdefined(minute_hand_array) ) + { + /# println("**********minute_hand_array is defined, size: " + minute_hand_array.size); #/ + array::thread_all( minute_hand_array,&clock_run, minute_values ); + } + second_hand_array = GetEntArray(clientNum, "second_hand", "targetname"); + if( isdefined(second_hand_array) ) + { + /# println("**********second_hand_array is defined, size: " + second_hand_array.size); #/ + array::thread_all( second_hand_array,&clock_run, second_values ); + } + +} + +function clock_run(time_values) // self == either hour hand, minute hand, or second hand +{ + self endon("entityshutdown"); + + // hour hands will have script_noteworthy = time zone if they want a different time zone + if( isdefined(self.script_noteworthy) ) + { + hour = time_values["hand_time"]; + curr_time = GetSystemTime(1); + + switch( ToLower(self.script_noteworthy) ) + { + case "honolulu": // GMT -10 + hour = curr_time[0] - 10; + break; + case "alaska": // GMT -9 + hour = curr_time[0] - 9; + break; + case "los angeles": // GMT -8 + hour = curr_time[0] - 8; + break; + case "denver": // GMT -7 + hour = curr_time[0] - 7; + break; + case "chicago": // GMT -6 + hour = curr_time[0] - 6; + break; + case "new york": // GMT -5 + hour = curr_time[0] - 5; + break; + case "halifax": // GMT -4 + hour = curr_time[0] - 4; + break; + case "greenland": // GMT -3 + hour = curr_time[0] - 3; + break; + case "london": // GMT 0 + hour = curr_time[0]; + break; + case "paris": // GMT +1 + hour = curr_time[0] + 1; + break; + case "helsinki": // GMT +2 + hour = curr_time[0] + 2; + break; + case "moscow": // GMT +3 + hour = curr_time[0] + 3; + break; + case "vietnam": // GMT +7 + hour = curr_time[0] + 7; + break; + case "china": // GMT +8 + hour = curr_time[0] + 8; + break; + } + + if( hour < 1 ) + { + hour += 12; + } + if( hour > 12 ) + { + hour -= 12; + } + time_values["hand_time"] = hour; + } + + self RotatePitch(time_values["hand_time"] * time_values["rotate"], 0.05); + self waittill("rotatedone"); + + if( isdefined(time_values["first_rotate"]) ) + { + self RotatePitch(time_values["first_rotate"], 0.05); + self waittill("rotatedone"); + } + + prev_time = GetSystemTime(); + + while(true) + { + curr_time = GetSystemTime(); + if( prev_time != curr_time ) + { + self RotatePitch(time_values["rotate_bit"], 0.05); + + prev_time = curr_time; + } + + wait(1.0); + } +} + +//Makes it so anemometers will spin whenever builders drop them into their map +function spin_anemometers( clientNum ) +{ + spoon_spinners = GetEntArray(clientNum, "spinner1","targetname"); + flat_spinners = GetEntArray( clientNum, "spinner2","targetname"); + + if( isdefined( spoon_spinners )) + { + /# println("**********spoon_spinners is defined, size: " + spoon_spinners.size); #/ + array::thread_all( spoon_spinners,&spoon_spin_func); + } + + if( isdefined( flat_spinners )) + { + /# println("**********flat_spinners is defined, size: " + flat_spinners.size); #/ + array::thread_all( flat_spinners,&arrow_spin_func); + } + +} + +//Self is the "spoon" like script model within the anemometer +function spoon_spin_func() +{ + self endon( "entityshutdown" ); + + if(isdefined( self.script_float)) + { + model_speed = self.script_float; + } + else + { + model_speed = 2; + } + + while(1) + { + speed = RandomFloatRange( model_speed * .6, model_speed); + self RotateYaw( 1200, speed); + self waittill ("rotatedone"); + } +} + +//Self is the "arrow" like script model within the anemometer +function arrow_spin_func() +{ + self endon( "entityshutdown" ); + + if(isdefined( self.script_int)) + { + model_direction_change = self.script_int; + } + else + { + model_direction_change = 25; + } + + if(isdefined( self.script_float)) + { + model_speed = self.script_float; + } + else + { + model_speed = .8; + } + + while(1) + { + direction_change = model_direction_change + RandomIntRange(-11, 11); + speed_change = RandomFloatRange(model_speed * .3, model_speed); + + self RotateYaw( direction_change, speed_change); + self waittill ("rotatedone"); + self RotateYaw( (direction_change * -1), speed_change); + self waittill ("rotatedone"); + } +} \ No newline at end of file diff --git a/mp/_arena.gsc b/mp/_arena.gsc new file mode 100644 index 0000000..cd285f0 --- /dev/null +++ b/mp/_arena.gsc @@ -0,0 +1,88 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\teams\_teams; + +#namespace arena; + +function autoexec __init__sytem__() { system::register("arena",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_connect( &on_connect ); +} + +function on_connect() +{ + if( isdefined( self.pers["arenaInit"] ) && self.pers["arenaInit"] == 1 ) + { + return; + } + + draftEnabled = ( GetGametypeSetting( "pregameDraftEnabled" ) == 1 ); + voteEnabled = ( GetGametypeSetting( "pregameItemVoteEnabled" ) == 1 ); + + if( !draftEnabled && !voteEnabled ) + { + self ArenaBeginMatch(); + } + + self.pers["arenaInit"] = 1; +} + +function update_arena_challenge_seasons() +{ + perSeasonWins = self GetDStat( "arenaPerSeasonStats", "wins" ); + if( perSeasonWins >= GetDvarInt( "arena_seasonVetChallengeWins" ) ) + { + arenaSlot = ArenaGetSlot(); + currentSeason = self GetDStat( "arenaStats", arenaSlot, "season" ); + seasonVetChallengeArrayCount = self GetDStatArrayCount( "arenaChallengeSeasons" ); + for( i = 0; i < seasonVetChallengeArrayCount; i++ ) + { + challengeSeason = self GetDStat( "arenaChallengeSeasons", i ); + if( challengeSeason == currentSeason ) // don't add a single season more than once + { + return; + } + + if( challengeSeason == 0 ) + { + self SetDStat( "arenaChallengeSeasons", i, currentSeason ); + break; + } + } + } +} + +function match_end( winner ) +{ + for( index = 0; index < level.players.size; index++ ) + { + player = level.players[index]; + + if( isdefined( player.pers["arenaInit"] ) && player.pers["arenaInit"] == 1 ) + { + if( winner == "tie" ) + { + player ArenaEndMatch( 0 ); + } + else if( winner == player.pers["team"] ) + { + player ArenaEndMatch( 1 ); + player arena::update_arena_challenge_seasons(); + } + else + { + player ArenaEndMatch( -1 ); + } + } + } +} + + diff --git a/mp/_armblade.gsc b/mp/_armblade.gsc new file mode 100644 index 0000000..7cde1ce --- /dev/null +++ b/mp/_armblade.gsc @@ -0,0 +1,55 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_proximity_grenade; +#using scripts\shared\util_shared; + + + + +#namespace armblade; + +function autoexec __init__sytem__() { system::register("armblade",&__init__,undefined,undefined); } + +function __init__() +{ + level.weaponArmblade = GetWeapon( "hero_armblade" ); + callback::on_spawned( &on_player_spawned ); +} + +function on_player_spawned() +{ + self thread armblade_sound_thread(); +} + +function armblade_sound_thread() +{ + self endon( "disconnect" ); + self endon( "death" ); + for( ;; ) + { + result = self util::waittill_any_return( "weapon_change", "disconnect" ); + if( IsDefined( result ) ) + { + if( ( result == "weapon_change" ) && ( self GetCurrentWeapon() == level.weaponArmblade ) ) + { + if( !IsDefined( self.armblade_loop_sound ) ) + { + self.armblade_loop_sound = spawn( "script_origin", self.origin ); + self.armblade_loop_sound linkto( self ); + } + + self.armblade_loop_sound PlayLoopSound( "wpn_armblade_idle", 0.25 ); + } + else + { + if ( IsDefined( self.armblade_loop_sound ) ) + { + self.armblade_loop_sound StopLoopSound( 0.25 ); + } + } + } + } +} \ No newline at end of file diff --git a/mp/_armor.gsc b/mp/_armor.gsc new file mode 100644 index 0000000..a1c87cc --- /dev/null +++ b/mp/_armor.gsc @@ -0,0 +1,91 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + +#precache( "lui_menu_data", "hudItems.armorPercent" ); + +#namespace armor; + +function setLightArmorHP( newValue ) +{ + if ( IsDefined( newValue ) ) + { + self.lightArmorHP = newValue; + if( IsPlayer( self ) && IsDefined( self.maxLightArmorHP ) && self.maxLightArmorHP > 0 ) + { + lightArmorPercent = math::clamp( self.lightArmorHP / self.maxLightArmorHP, 0, 1 ); + self SetControllerUIModelValue( "hudItems.armorPercent", lightArmorPercent ); + } + } + else + { + self.lightArmorHP = undefined; + self.maxLightArmorHP = undefined; + self SetControllerUIModelValue( "hudItems.armorPercent", 0 ); + } +} + +///////////////////////////////////////////////////////////////// +// ARMOR: give a health boost +function setLightArmor( optionalArmorValue ) +{ + self notify( "give_light_armor" ); + + if( IsDefined( self.lightArmorHP ) ) + unsetLightArmor(); + + self thread removeLightArmorOnDeath(); + self thread removeLightArmorOnMatchEnd(); + + if( IsDefined( optionalArmorValue ) ) + self.maxLightArmorHP = optionalArmorValue; + else + self.maxLightArmorHP = 150; + + self setLightArmorHP( self.maxLightArmorHP ); +} + +function removeLightArmorOnDeath() +{ + self endon ( "disconnect" ); + self endon( "give_light_armor" ); + self endon( "remove_light_armor" ); + + self waittill ( "death" ); + unsetLightArmor(); +} + +function unsetLightArmor() +{ + self setLightArmorHP( undefined ); + + self notify( "remove_light_armor" ); +} + +function removeLightArmorOnMatchEnd() +{ + self endon ( "disconnect" ); + self endon ( "remove_light_armor" ); + + level waittill( "game_ended" ); + + self thread unsetLightArmor(); +} + +function hasLightArmor() +{ + return ( IsDefined( self.lightArmorHP ) && self.lightArmorHP > 0 ); +} + +function getArmor() +{ + if( IsDefined( self.lightArmorHP ) ) + { + return self.lightArmorHP; + } + return 0; +} diff --git a/mp/_art.gsc b/mp/_art.gsc new file mode 100644 index 0000000..25c8e20 --- /dev/null +++ b/mp/_art.gsc @@ -0,0 +1,332 @@ +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#namespace art; + +function autoexec __init__sytem__() { system::register("art",&__init__,undefined,undefined); } + +// This function should take care of grain and glow settings for each map, plus anything else that artists +// need to be able to tweak without bothering level designers. + +function __init__() +{ +/# + if( GetDvarString( "scr_art_tweak" ) == "" || GetDvarString( "scr_art_tweak" ) == "0" ) + { + SetDvar( "scr_art_tweak", 0 ); + } + + if( GetDvarString( "scr_dof_enable" ) == "" ) + { + SetDvar( "scr_dof_enable", "1" ); + } + + if( GetDvarString( "scr_cinematic_autofocus" ) == "" ) + { + SetDvar( "scr_cinematic_autofocus", "1" ); + } + + if( GetDvarString( "scr_art_visionfile" ) == "" && isdefined(level.script) ) + { + SetDvar( "scr_art_visionfile", level.script ); + } +#/ + + if( !isdefined( level.dofDefault ) ) + { + level.dofDefault["nearStart"] = 0; + level.dofDefault["nearEnd"] = 1; + level.dofDefault["farStart"] = 8000; + level.dofDefault["farEnd"] = 10000; + level.dofDefault["nearBlur"] = 6; + level.dofDefault["farBlur"] = 0; + } + + level.curDoF = ( level.dofDefault["farStart"] - level.dofDefault["nearEnd"] ) / 2; + +/# + thread tweakart(); +#/ +} + +function artfxprintln( file, string ) +{ + /# + // printing to file is optional now + if( file == -1 ) + { + return; + } + fprintln( file, string ); + #/ +} + + +// Nate - hack Fixmed and replace with proper script command call once it's fixed. +// assumes " " as the deliiter. I'm not getting fancy. +// I would really like to go work on jeepride so here's a +// quick function that works for now untill engineering fixes strtok. + +function strtok_loc( string, par1 ) +{ + stringlist = []; + indexstring = ""; + for( i = 0 ; i < string.size ; i ++ ) + { + if( string[i] == " " ) + { + stringlist[stringlist.size] = indexstring; + indexstring = ""; + } + else + { + indexstring = indexstring+string[i]; + } + } + if( indexstring.size ) + { + stringlist[stringlist.size] = indexstring; + } + return stringlist; +} + + +function setfogsliders() +{ + //fixme. replace strtok_loc with strtok if it ever works properly. + fogall = strtok_loc( GetDvarString( "g_fogColorReadOnly" ), " " ) ; + red = fogall[ 0 ]; + green = fogall[ 1 ]; + blue = fogall[ 2 ]; + halfplane = GetDvarString( "g_fogHalfDistReadOnly" ); + nearplane = GetDvarString( "g_fogStartDistReadOnly" ); + + if ( !isdefined( red ) + || !isdefined( green ) + || !isdefined( blue ) + || !isdefined( halfplane ) + ) + { + red = 1; + green = 1; + blue = 1; + halfplane = 10000001; + nearplane = 10000000; + } + SetDvar("scr_fog_exp_halfplane",halfplane); + SetDvar("scr_fog_nearplane",nearplane); + SetDvar("scr_fog_color",red+" "+green+" "+blue); +} + +function tweakart() +{ + /# + if( !isdefined( level.tweakfile ) ) + { + level.tweakfile = false; + } + + + // Default values + + if(GetDvarString( "scr_fog_baseheight") == "") + { + SetDvar( "scr_fog_exp_halfplane", "500" ); + SetDvar( "scr_fog_exp_halfheight", "500" ); + SetDvar( "scr_fog_nearplane", "0" ); + SetDvar( "scr_fog_baseheight", "0" ); + } + + // not in DEVGUI + SetDvar( "scr_fog_fraction", "1.0" ); + SetDvar( "scr_art_dump", "0" ); + SetDvar("scr_art_sun_fog_dir_set", "0"); + + // update the devgui variables to current settings + SetDvar( "scr_dof_nearStart", level.dofDefault["nearStart"] ); + SetDvar( "scr_dof_nearEnd", level.dofDefault["nearEnd"] ); + SetDvar( "scr_dof_farStart", level.dofDefault["farStart"] ); + SetDvar( "scr_dof_farEnd", level.dofDefault["farEnd"] ); + SetDvar( "scr_dof_nearBlur", level.dofDefault["nearBlur"] ); + SetDvar( "scr_dof_farBlur", level.dofDefault["farBlur"] ); + + + file = undefined; + filename = undefined; + + // set dofvars from < levelname > _art.gsc + + + tweak_toggle = 1; + for( ;; ) + { + while(GetDvarint( "scr_art_tweak" ) == 0 ) + { + tweak_toggle = 1; + wait .05; + } + + if(tweak_toggle) + { + tweak_toggle = 0; + fogsettings = getfogsettings(); + + SetDvar( "scr_fog_nearplane", fogsettings[0] ); + SetDvar( "scr_fog_exp_halfplane", fogsettings[1] ); + SetDvar( "scr_fog_exp_halfheight", fogsettings[3] ); + SetDvar( "scr_fog_baseheight", fogsettings[2] ); + + SetDvar("scr_fog_color", fogsettings[4]+" "+fogsettings[5]+" "+fogsettings[6]); + SetDvar("scr_fog_color_scale", fogsettings[7]); + SetDvar("scr_sun_fog_color", fogsettings[8]+" "+fogsettings[9]+" "+fogsettings[10]); + + level.fogsundir = []; + level.fogsundir[0] = fogsettings[11]; + level.fogsundir[1] = fogsettings[12]; + level.fogsundir[2] = fogsettings[13]; + + SetDvar("scr_sun_fog_start_angle",fogsettings[14] ); + SetDvar("scr_sun_fog_end_angle",fogsettings[15] ); + + SetDvar("scr_fog_max_opacity", fogsettings[16]); + + + } + + //translate the slider values to script variables + + level.fogexphalfplane = GetDvarfloat( "scr_fog_exp_halfplane"); + level.fogexphalfheight = GetDvarfloat( "scr_fog_exp_halfheight"); + level.fognearplane = GetDvarfloat( "scr_fog_nearplane"); + level.fogbaseheight = GetDvarfloat( "scr_fog_baseheight"); + + colors = StrTok(GetDvarString("scr_fog_color")," "); + level.fogcolorred = int(colors[0]); + level.fogcolorgreen = int(colors[1]); + level.fogcolorblue = int(colors[2]); + level.fogcolorscale = GetDvarfloat( "scr_fog_color_scale"); + + colors = StrTok(GetDvarString("scr_sun_fog_color")," "); + level.sunfogcolorred = int(colors[0]); + level.sunfogcolorgreen = int(colors[1]); + level.sunfogcolorblue = int(colors[2]); + + level.sunstartangle = GetDvarfloat( "scr_sun_fog_start_angle"); + level.sunendangle = GetDvarfloat( "scr_sun_fog_end_angle"); + level.fogmaxopacity = GetDvarfloat( "scr_fog_max_opacity"); + + if( GetDvarint( "scr_art_sun_fog_dir_set") ) + { + SetDvar( "scr_art_sun_fog_dir_set", "0" ); + + println("Setting sun fog direction to facing of player"); + + players = GetPlayers(); + + dir = VectorNormalize( AnglesToForward( players[0] GetPlayerAngles() ) ); + + level.fogsundir = []; + level.fogsundir[0] = dir[0]; + level.fogsundir[1] = dir[1]; + level.fogsundir[2] = dir[2]; + } + + + // catch all those cases where a slider can be pushed to a place of conflict + fovslidercheck(); + + dumpsettings(); // dumps and returns true if the dump dvar is set + + // updates fog to the variables + + if ( ! GetDvarint( "scr_fog_disable" ) ) + { + if(!isdefined(level.fogsundir)) { + level.fogsundir = []; + level.fogsundir[0] = 1; + level.fogsundir[1] = 0; + level.fogsundir[2] = 0; + } + + setVolFog( level.fognearplane, level.fogexphalfplane, level.fogexphalfheight, level.fogbaseheight, level.fogcolorred, level.fogcolorgreen, level.fogcolorblue, + level.fogcolorscale, level.sunfogcolorred, level.sunfogcolorgreen, level.sunfogcolorblue, level.fogsundir[0], level.fogsundir[1], level.fogsundir[2], level.sunstartangle, level.sunendangle, 0, level.fogmaxopacity ); + } + else + { + setExpFog( 100000000, 100000001, 0, 0, 0, 0 ); // couldn't find discreet fog disabling other than to never set it in the first place + } + + wait .1; + } + #/ +} + +function fovslidercheck() +{ + // catch all those cases where a slider can be pushed to a place of conflict + if( level.dofDefault["nearStart"] >= level.dofDefault["nearEnd"] ) + { + level.dofDefault["nearStart"] = level.dofDefault["nearEnd"] - 1; + SetDvar( "scr_dof_nearStart", level.dofDefault["nearStart"] ); + } + if( level.dofDefault["nearEnd"] <= level.dofDefault["nearStart"] ) + { + level.dofDefault["nearEnd"] = level.dofDefault["nearStart"] + 1; + SetDvar( "scr_dof_nearEnd", level.dofDefault["nearEnd"] ); + } + if( level.dofDefault["farStart"] >= level.dofDefault["farEnd"] ) + { + level.dofDefault["farStart"] = level.dofDefault["farEnd"] - 1; + SetDvar( "scr_dof_farStart", level.dofDefault["farStart"] ); + } + if( level.dofDefault["farEnd"] <= level.dofDefault["farStart"] ) + { + level.dofDefault["farEnd"] = level.dofDefault["farStart"] + 1; + SetDvar( "scr_dof_farEnd", level.dofDefault["farEnd"] ); + } + if( level.dofDefault["farBlur"] >= level.dofDefault["nearBlur"] ) + { + level.dofDefault["farBlur"] = level.dofDefault["nearBlur"] - .1; + SetDvar( "scr_dof_farBlur", level.dofDefault["farBlur"] ); + } + if( level.dofDefault["farStart"] <= level.dofDefault["nearEnd"] ) + { + level.dofDefault["farStart"] = level.dofDefault["nearEnd"] + 1; + SetDvar( "scr_dof_farStart", level.dofDefault["farStart"] ); + } +} + +function dumpsettings() +{ + /# + if ( GetDvarString( "scr_art_dump" ) != "0" ) + { + PrintLn("\tstart_dist = " + level.fognearplane + ";"); + PrintLn("\thalf_dist = " + level.fogexphalfplane + ";"); + PrintLn("\thalf_height = " + level.fogexphalfheight + ";"); + PrintLn("\tbase_height = " + level.fogbaseheight + ";"); + PrintLn("\tfog_r = " + level.fogcolorred + ";"); + PrintLn("\tfog_g = " + level.fogcolorgreen + ";"); + PrintLn("\tfog_b = " + level.fogcolorblue + ";"); + PrintLn("\tfog_scale = " + level.fogcolorscale + ";"); + PrintLn("\tsun_col_r = " + level.sunfogcolorred + ";"); + PrintLn("\tsun_col_g = " + level.sunfogcolorgreen + ";"); + PrintLn("\tsun_col_b = " + level.sunfogcolorblue + ";"); + PrintLn("\tsun_dir_x = " + level.fogsundir[0] + ";"); + PrintLn("\tsun_dir_y = " + level.fogsundir[1] + ";"); + PrintLn("\tsun_dir_z = " + level.fogsundir[2] + ";"); + PrintLn("\tsun_start_ang = " + level.sunstartangle + ";"); + PrintLn("\tsun_stop_ang = " + level.sunendangle + ";"); + PrintLn("\ttime = 0;"); + PrintLn("\tmax_fog_opacity = " + level.fogmaxopacity +";"); + PrintLn(""); + PrintLn("\tsetVolFog(start_dist, half_dist, half_height, base_height, fog_r, fog_g, fog_b, fog_scale,"); + PrintLn("\t\tsun_col_r, sun_col_g, sun_col_b, sun_dir_x, sun_dir_y, sun_dir_z, sun_start_ang, "); + PrintLn("\t\tsun_stop_ang, time, max_fog_opacity);"); + + SetDvar( "scr_art_dump", "0" ); + } + #/ +} diff --git a/mp/_ballistic_knife.gsc b/mp/_ballistic_knife.gsc new file mode 100644 index 0000000..80cb455 --- /dev/null +++ b/mp/_ballistic_knife.gsc @@ -0,0 +1,17 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_ballistic_knife; + + + +#namespace ballistic_knife; + +function autoexec __init__sytem__() { system::register("ballistic_knife",&__init__,undefined,undefined); } + +function __init__() +{ + ballistic_knife::init_shared(); +} diff --git a/mp/_bb.gsc b/mp/_bb.gsc new file mode 100644 index 0000000..faa030c --- /dev/null +++ b/mp/_bb.gsc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\bb_shared; + + + +#namespace bb; + +function autoexec __init__sytem__() { system::register("bb",&__init__,undefined,undefined); } + +function __init__() +{ + bb::init_shared(); +} diff --git a/mp/_behavior_tracker.gsc b/mp/_behavior_tracker.gsc new file mode 100644 index 0000000..c5a287d --- /dev/null +++ b/mp/_behavior_tracker.gsc @@ -0,0 +1,279 @@ +#using scripts\shared\math_shared; + + + + + + +#namespace behaviorTracker; + +///////////////////////////////////////////////////// +///// Inititialize & Finaliize //// +///////////////////////////////////////////////////// +function SetupTraits() +{ + if ( isDefined( self.behaviorTracker.traits ) ) + return; + + self.behaviorTracker.traits = []; + // Define the traits with a default value. This default value will be used if we dont have stats. + // Add the information to the BBPrint() + self.behaviorTracker.traits["effectiveCombat"] = 0.5; + self.behaviorTracker.traits["effectiveWallRunCombat"] = 0.5; + self.behaviorTracker.traits["effectiveDoubleJumpCombat"] = 0.5; + self.behaviorTracker.traits["effectiveSlideCombat"] = 0.5; + + if ( self.behaviorTracker.version != 0 ) + { + traits = getArrayKeys( self.behaviorTracker.traits ); + + for ( i = 0; i < traits.size; i++ ) + { + trait = traits[i]; + self.behaviorTracker.traits[trait] = float( self GetTraitStatValue( trait ) ); + } + } +} + +function Initialize() +{ + if ( isdefined( self.pers["isBot"] ) ) + return; + + if ( isDefined( self.behaviorTracker ) ) + return; + + if ( isdefined( level.disableBehaviorTracker ) && level.disableBehaviorTracker == true ) + return; + + self.behaviorTracker = spawnstruct(); + self.behaviorTracker.version = int( self GetTraitStatValue( "version" ) ); + self.behaviorTracker.numRecords = int( self GetTraitStatValue( "numRecords" ) ) + 1; + self SetupTraits(); + + self.behaviorTracker.valid = true; +} + +function Finalize() +{ + if ( !( self IsAllowed() ) ) + return; + + self SetTraitStats(); + + self PrintTrackerToBlackBox(); +} + +///////////////////////////////////////////////////// +///// Utility Functions //// +///////////////////////////////////////////////////// +function IsAllowed() +{ + if ( !isDefined( self ) ) + return false; + + if ( !isDefined( self.behaviorTracker ) ) + return false; + + if ( !self.behaviorTracker.valid ) + return false; + + if ( isdefined( level.disableBehaviorTracker ) && level.disableBehaviorTracker == true ) + return false; + + return true; +} + +function PrintTrackerToBlackBox() +{ + // Prepare the list of traits & its values + // TODO: Construct the string with all the traits so that it can just be added as a single variable in the print. + /*traitsStr = ""; + traits = getArrayKeys( self.behaviorTracker.traits ); + + for ( i = 0; i < traits.size; i++ ) + { + trait = traits[i]; + value = self.behaviorTracker.traits[trait]; + + traitsStr = traitsStr + trait + " " + value + " "; + } + + bbPrint( "mpbehaviortracker", "username %s version %d numRecords %d %s", self.name, self.behaviorTracker.version, self.behaviorTracker.numRecords, traitsStr ); */ + + bbPrint( "mpbehaviortracker", "username %s version %d numRecords %d effectiveSlideCombat %f effectiveDoubleJumpCombat %f effectiveWallRunCombat %f effectiveCombat %f", + self.name, self.behaviorTracker.version, self.behaviorTracker.numRecords, self.behaviorTracker.traits["effectiveSlideCombat"], + self.behaviorTracker.traits["effectiveDoubleJumpCombat"], self.behaviorTracker.traits["effectiveWallRunCombat"], self.behaviorTracker.traits["effectiveCombat"] ); +} + +///////////////////////////////////////////////////// +///// Set, Get & Update Trait //// +///////////////////////////////////////////////////// +function GetTraitValue( trait ) +{ + return self.behaviorTracker.traits[ trait ]; +} + +function SetTraitValue( trait, value ) +{ + self.behaviorTracker.traits[trait] = value; +} + +function UpdateTrait( trait, percent ) +{ + if ( !( self IsAllowed() ) ) + return; + + math::clamp( percent, -1.0, 1.0 ); + + currentValue = self GetTraitValue( trait ); + + if ( percent >= 0 ) + { + delta = ( 1.0 - currentValue ) * percent; + } + else + { + delta = ( currentValue - 0.0 ) * percent; + } + + weightedDelta = 0.1 * delta; + + newValue = currentvalue + weightedDelta; + newValue = math::clamp( newValue, 0.0, 1.0 ); + self SetTraitValue( trait, newValue ); + + bbprint( "mpbehaviortraitupdate", "username %s trait %s percent %f", self.name, trait, percent ); +} + +///////////////////////////////////////////////////// +///// Game Side Hooks //// +///////////////////////////////////////////////////// +function UpdatePlayerDamage( attacker, victim, damage ) +{ + if ( isDefined( victim ) && victim IsAllowed() ) + { + damageRatio = float( damage ) / float( victim.maxhealth ); + math::clamp( damageRatio, 0.0, 1.0 ); + + damageRatio = damageRatio * -1.0; // Negative damage percent since this is the victim + + victim UpdateTrait( "effectiveCombat", damageRatio ); + + if ( victim IsWallRunning() ) + { + victim UpdateTrait( "effectiveWallRunCombat", damageRatio ); + } + + if ( victim IsSliding() ) + { + victim UpdateTrait( "effectiveSlideCombat", damageRatio ); + } + + if ( victim IsDoubleJumping() ) + { + victim UpdateTrait( "effectiveDoubleJumpCombat", damageRatio ); + } + } + + if ( isDefined( attacker ) && ( attacker IsAllowed() ) && attacker != victim ) + { + damageRatio = float( damage ) / float( attacker.maxhealth ); + math::clamp( damageRatio, 0.0, 1.0 ); + + attacker UpdateTrait( "effectiveCombat", damageRatio ); + + if ( attacker IsWallRunning() ) + { + attacker UpdateTrait( "effectiveWallRunCombat", damageRatio ); + } + + if ( attacker IsSliding() ) + { + attacker UpdateTrait( "effectiveSlideCombat", damageRatio ); + } + + if ( attacker IsDoubleJumping() ) + { + attacker UpdateTrait( "effectiveDoubleJumpCombat", damageRatio ); + } + } +} + +function UpdatePlayerKilled( attacker, victim ) +{ + if ( isDefined( victim ) && victim IsAllowed() ) + { + // Passing -1.0 since to denote negative 100%. + victim UpdateTrait( "effectiveCombat", -1.0 ); + + if ( victim IsWallRunning() ) + { + victim UpdateTrait( "effectiveWallRunCombat", -1.0 ); + } + + if ( victim IsSliding() ) + { + victim UpdateTrait( "effectiveSlideCombat", -1.0 ); + } + + if ( victim IsDoubleJumping() ) + { + victim UpdateTrait( "effectiveDoubleJumpCombat", -1.0 ); + } + } + + if ( isDefined( attacker ) && ( attacker IsAllowed() ) && attacker != victim ) + { + // Passing 1.0 since to denote positive 100%. + attacker UpdateTrait( "effectiveCombat", 1.0 ); + + if ( attacker IsWallRunning() ) + { + attacker UpdateTrait( "effectiveWallRunCombat", 1.0 ); + } + + if ( attacker IsSliding() ) + { + attacker UpdateTrait( "effectiveSlideCombat", 1.0 ); + } + + if ( attacker IsDoubleJumping() ) + { + attacker UpdateTrait( "effectiveDoubleJumpCombat", 1.0 ); + } + } +} + + +///////////////////////////////////////////////////// +///// Stats Related //// +///////////////////////////////////////////////////// + function SetTraitStats() + { + if ( self.behaviorTracker.version == 0 ) + return; + + self.behaviorTracker.numRecords = self.behaviorTracker.numRecords + 1; + self SetTraitStatValue( "numRecords", self.behaviorTracker.numRecords ); + + traits = getArrayKeys( self.behaviorTracker.traits ); + + for ( i = 0; i < traits.size; i++ ) + { + trait = traits[i]; + value = self.behaviorTracker.traits[trait]; + + self SetTraitStatValue( trait, value ); + } + } + +function GetTraitStatValue( trait ) +{ + return self getDStat( "behaviorTracker", trait ); +} + +function SetTraitStatValue( trait, value ) +{ + self setDStat( "behaviorTracker", trait, value ); +} \ No newline at end of file diff --git a/mp/_blackjack_challenges.gsc b/mp/_blackjack_challenges.gsc new file mode 100644 index 0000000..241dc0f --- /dev/null +++ b/mp/_blackjack_challenges.gsc @@ -0,0 +1,405 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\drown; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weapon_utils; + +#using scripts\mp\_challenges; +#using scripts\mp\gametypes\_loadout; + + + +#using scripts\mp\_util; + + + + + // for blackjack challenge: kills while using a specialist weapon or ability + // for blackjack challenge: count of unique specialist weapons or abilities used to kill + +#namespace blackjack_challenges; + +function autoexec __init__sytem__() { system::register("blackjack_challenges",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &start_gametype ); +} + +function start_gametype() +{ + if ( !isdefined( level.ChallengesCallbacks ) ) + { + level.ChallengesCallbacks = []; + } + + waittillframeend; + + if ( challenges::canProcessChallenges() ) + { + challenges::registerChallengesCallback( "playerKilled",&challenge_kills ); + challenges::registerChallengesCallback( "roundEnd",&challenge_round_ended ); + challenges::registerChallengesCallback( "gameEnd",&challenge_game_ended ); + scoreevents::register_hero_ability_kill_event( &on_hero_ability_kill ); + } + + callback::on_connect( &on_player_connect ); +} + +function on_player_connect() +{ + player = self; + if ( challenges::canProcessChallenges() ) + { + specialistIndex = player GetSpecialistIndex(); + isBlackjack = ( specialistIndex == 9 ); + if ( isBlackjack ) + { + player thread track_blackjack_consumable(); + + if ( !isdefined( self.pers[ "blackjack_challenge_active" ] ) ) + { + remaining_time = player ConsumableGet( "blackjack", "awarded" ) - player ConsumableGet( "blackjack", "consumed" ); + + if ( remaining_time > 0 ) + { + special_card_earned = player get_challenge_stat( "special_card_earned" ); + if ( !special_card_earned ) + { + player.pers[ "blackjack_challenge_active" ] = true; + player.pers[ "blackjack_unique_specialist_kills" ] = 0; + player.pers[ "blackjack_specialist_kills" ] = 0; + player.pers[ "blackjack_unique_weapon_mask" ] = 0; + player.pers[ "blackjack_unique_ability_mask" ] = 0; + } + } + } + } + } +} + +function is_challenge_active() +{ + return ( self.pers[ "blackjack_challenge_active" ] === true ); +} + +function on_hero_ability_kill( ability, victimAbility ) +{ + player = self; + + if ( !isdefined( player ) || !isplayer( player ) ) + return; + + if ( !isdefined( player.isRoulette ) || !player.isRoulette ) + return; + + if ( player is_challenge_active() ) + { + player.pers[ "blackjack_specialist_kills" ]++; + + currentHeroAbilityMask = player.pers[ "blackjack_unique_ability_mask" ]; + heroAbilityMask = get_hero_ability_mask( ability ); + newHeroAbilityMask = heroAbilityMask | currentHeroAbilityMask; + if ( newHeroAbilityMask != currentHeroAbilityMask ) + { + player.pers[ "blackjack_unique_specialist_kills" ]++; + player.pers[ "blackjack_unique_ability_mask" ] = newHeroAbilityMask; + } + + player check_blackjack_challenge(); + } +} + +/# +function debug_print_already_earned() +{ + if ( GetDvarInt( "scr_blackjack_sidebet_debug", 0 ) == 0 ) + return; + + IPrintLn( "Blackjack Side Bet already earned this game." ); +} +#/ + +/# +function debug_print_kill_info() +{ + if ( GetDvarInt( "scr_blackjack_sidebet_debug", 0 ) == 0 ) + return; + + player = self; + IPrintLn( "Blackjack Side Bet kills: " + player.pers[ "blackjack_specialist_kills" ] + " uniques: " + player.pers[ "blackjack_unique_specialist_kills" ] ); +} +#/ + +/# +function debug_print_earned() +{ + if ( GetDvarInt( "scr_blackjack_sidebet_debug", 0 ) == 0 ) + return; + + IPrintLn( "Blackjack Side Bet earned!!!" ); +} +#/ + +function check_blackjack_challenge() +{ + player = self; + + /# debug_print_kill_info(); #/ + + special_card_earned = player get_challenge_stat( "special_card_earned" ); + if ( special_card_earned ) + { + /# debug_print_already_earned(); #/ + return; + } + + if ( ( player.pers[ "blackjack_specialist_kills" ] >= 4 ) && + ( player.pers[ "blackjack_unique_specialist_kills" ] >= 2 ) ) + { + player set_challenge_stat( "special_card_earned", 1 ); + player AddPlayerStat( "blackjack_challenge", 1 ); + + /# debug_print_earned(); #/ + } +} + +function challenge_kills( data ) +{ + attackerisThief = data.attackerIsThief; + attackerIsRoulette = data.attackerIsRoulette; + attackerIsThiefOrRoulette = attackerisThief || attackerIsRoulette; + + if ( !attackerIsThiefOrRoulette ) + return; + + victim = data.victim; + attacker = data.attacker; + player = attacker; + weapon = data.weapon; + + if ( !isdefined( weapon ) || ( weapon == level.weaponNone ) ) + return; + + if ( !isdefined( player ) || !isplayer( player ) ) + return; + + if ( attackerIsThief ) + { + if ( weapon.isHeroWeapon === true ) + { + if ( player is_challenge_active() ) + { + player.pers[ "blackjack_specialist_kills" ]++; + + currentHeroWeaponMask = player.pers[ "blackjack_unique_weapon_mask" ]; + heroWeaponMask = get_hero_weapon_mask( attacker, weapon ); + newHeroWeaponMask = heroWeaponMask | currentHeroWeaponMask; + if ( newHeroWeaponMask != currentHeroWeaponMask ) + { + player.pers[ "blackjack_unique_specialist_kills" ] += 1; + player.pers[ "blackjack_unique_weapon_mask" ] = newHeroWeaponMask; + } + + player check_blackjack_challenge(); + } + } + } + + // ability kills are handled as events fired from score events + // if ( attackerIsRoulette ) + // { + // } +} + +function get_challenge_stat( stat_name ) +{ + return self GetDStat( "tenthspecialistcontract", stat_name ); +} + +function set_challenge_stat( stat_name, stat_value ) +{ + return self SetDStat( "tenthspecialistcontract", stat_name, stat_value ); +} + +function get_hero_weapon_mask( attacker, weapon ) +{ + if ( !isdefined( weapon ) ) + return 0; + + if ( isdefined( weapon.isHeroWeapon ) && !weapon.isHeroWeapon ) + return 0; + + switch( weapon.name ) + { + case "hero_minigun": + case "hero_minigun_body3": + return 1; // note: heroWeaponMask needs to stay unique and consistent for function across TUs and FFOTDs + break; + case "hero_flamethrower": + return 1 << 1; + break; + case "hero_lightninggun": + case "hero_lightninggun_arc": + return 1 << 2; + break; + case "hero_chemicalgelgun": + case "hero_firefly_swarm": + return 1 << 3; + break; + case "hero_pineapplegun": + case "hero_pineapple_grenade": + return 1 << 4; + break; + case "hero_armblade": + return 1 << 5; + break; + case "hero_bowlauncher": + case "hero_bowlauncher2": + case "hero_bowlauncher3": + case "hero_bowlauncher4": + return 1 << 6; + break; + case "hero_gravityspikes": + return 1 << 7; + break; + case "hero_annihilator": + return 1 << 8; + break; + default: + return 0; + } +} + +function get_hero_ability_mask( ability ) +{ + if ( !isdefined( ability ) ) + return 0; + + switch( ability.name ) + { + case "gadget_clone": + return 1; // note: heroAbilityMask needs to stay unique and consistent for functions across TUs and FFOTDs + break; + case "gadget_heat_wave": + return 1 << 1; + break; + case "gadget_flashback": + return 1 << 2; + break; + case "gadget_resurrect": + return 1 << 3; + break; + case "gadget_armor": + return 1 << 4; + break; + case "gadget_camo": + return 1 << 5; + break; + case "gadget_vision_pulse": + return 1 << 6; + break; + case "gadget_speed_burst": + return 1 << 7; + break; + case "gadget_combat_efficiency": + return 1 << 8; + break; + default: + return 0; + } +} + +function challenge_game_ended( data ) +{ + if ( !isdefined( data ) ) + return; + + player = data.player; + if ( !isdefined( player ) ) + return; + + if ( !isPlayer( player ) ) + return; + + if ( player util::is_bot() ) + return; + + if ( !player is_challenge_active() ) + return; + + player report_consumable(); +} + +function challenge_round_ended( data ) +{ + if ( !isdefined( data ) ) + return; + + player = data.player; + if ( !isdefined( player ) ) + return; + + if ( !isPlayer( player ) ) + return; + + if ( player util::is_bot() ) + return; + + if ( !player is_challenge_active() ) + return; + + player report_consumable(); +} + +function track_blackjack_consumable() +{ + level endon( "game_ended" ); + self notify( "track_blackjack_consumable_singleton" ); + self endon( "track_blackjack_consumable_singleton" ); + self endon( "disconnect" ); + + player = self; + + if ( !isdefined( player.last_blackjack_consumable_time ) ) + player.last_blackjack_consumable_time = 0; + + while ( isdefined( player ) ) + { + random_wait_time = GetDvarFloat( "mp_blackjack_consumable_wait", 20.0 ) + RandomFloatRange( -5.0, 5.0 ); + wait random_wait_time; + + player report_consumable(); + } +} + +function report_consumable() +{ + player = self; + + if ( !isdefined( player ) ) + return; + + if ( !isdefined( player.timePlayed ) || !isdefined( player.timePlayed["total"] ) ) + return; + + current_time_played = player.timePlayed["total"]; + + time_to_report = current_time_played - player.last_blackjack_consumable_time; + + if ( time_to_report > 0 ) + { + max_time_to_report = player ConsumableGet( "blackjack", "awarded" ) - player ConsumableGet( "blackjack", "consumed" ); + consumable_increment = int( min( time_to_report, max_time_to_report ) ); + if ( consumable_increment > 0 ) + { + player ConsumableIncrement( "blackjack", "consumed", consumable_increment ); // so we don't go over awarded time + } + } + + player.last_blackjack_consumable_time = current_time_played; +} diff --git a/mp/_bonuscard.gsh b/mp/_bonuscard.gsh new file mode 100644 index 0000000..99a939e --- /dev/null +++ b/mp/_bonuscard.gsh @@ -0,0 +1,13 @@ + +// bonusCards_t + + + + + + + + + + + diff --git a/mp/_bouncingbetty.csc b/mp/_bouncingbetty.csc new file mode 100644 index 0000000..25f9912 --- /dev/null +++ b/mp/_bouncingbetty.csc @@ -0,0 +1,19 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_bouncingbetty; + + + +#using scripts\mp\_util; + +#namespace bouncingbetty; + +function autoexec __init__sytem__() { system::register("bouncingbetty",&__init__,undefined,undefined); } + +function __init__( localClientNum ) +{ + bouncingbetty::init_shared(); +} \ No newline at end of file diff --git a/mp/_bouncingbetty.gsc b/mp/_bouncingbetty.gsc new file mode 100644 index 0000000..6a95919 --- /dev/null +++ b/mp/_bouncingbetty.gsc @@ -0,0 +1,23 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_bouncingbetty; + + + +#using scripts\mp\_util; + +#namespace bouncingbetty; + +function autoexec __init__sytem__() { system::register("bouncingbetty",&__init__,undefined,undefined); } + +function __init__() +{ + bouncingbetty::init_shared(); + + level.trackBouncingBettiesOnOwner = true; +} + + diff --git a/mp/_callbacks.csc b/mp/_callbacks.csc new file mode 100644 index 0000000..8e7fa74 --- /dev/null +++ b/mp/_callbacks.csc @@ -0,0 +1,301 @@ +#using scripts\shared\audio_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\exploder_shared; +#using scripts\shared\filter_shared; +#using scripts\shared\footsteps_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\vehicles\_driving_fx; +#using scripts\shared\abilities\gadgets\_gadget_vision_pulse; + + + +#using scripts\shared\weapons\_sticky_grenade; + +#using scripts\mp\killstreaks\_ai_tank; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_helicopter; +#using scripts\mp\killstreaks\_qrdrone; +#using scripts\mp\killstreaks\_rcbomb; +#using scripts\shared\_burnplayer; +#using scripts\mp\_callbacks; +#using scripts\mp\_util; +#using scripts\mp\_vehicle; + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_actor; + +#namespace callback; + +function autoexec __init__sytem__() { system::register("callback",&__init__,undefined,undefined); } + +// Callback set up, clientside. +function __init__() +{ + level thread set_default_callbacks(); +} + +function set_default_callbacks() +{ + level.callbackPlayerSpawned = &playerspawned; + level.callbackLocalClientConnect = &localclientconnect; + level.callbackCreatingCorpse = &creating_corpse; + level.callbackEntitySpawned = &entityspawned; + level.callbackAirSupport = &airsupport; + //level.callbackDogSoundNotify = &dogs::play_dog_sound; + level.callbackPlayAIFootstep = &footsteps::playaifootstep; + level.callbackPlayLightLoopExploder = &exploder::playlightloopexploder; + + level._custom_weapon_CB_Func = &callback::spawned_weapon_type; + + level.gadgetVisionPulse_reveal_func = &gadget_vision_pulse::gadget_visionpulse_reveal; +} + +function localclientconnect( localClientNum ) +{ + /# println( "*** Client script VM : Local client connect " + localClientNum ); #/ + + if ( isdefined( level.characterCustomizationSetup ) ) + { + [[ level.characterCustomizationSetup ]]( localclientnum ); + } + + callback::callback( #"on_localclient_connect", localClientNum ); +} + +function playerspawned(localClientNum) +{ + self endon( "entityshutdown" ); + self notify( "playerspawned_callback" ); + self endon( "playerspawned_callback" ); + + player = GetLocalPlayer(localClientNum); + + if ( isdefined( level.infraredVisionset ) ) + { + player SetInfraredVisionset( level.infraredVisionset ); + } + + if ( isdefined( level._playerspawned_override ) ) + { + self thread [[level._playerspawned_override]]( localClientNum ); + return; + } + +/# PrintLn( "Player spawned" ); #/ + if ( self isLocalPlayer() ) + callback::callback( #"on_localplayer_spawned", localClientNum ); + + callback::callback( #"on_player_spawned", localClientNum ); +} + +function entityspawned(localClientNum) +{ + self endon( "entityshutdown" ); + + if( self IsPlayer() ) + { + if( isdefined( level._clientFaceAnimOnPlayerSpawned ) ) + { + self thread [[level._clientFaceAnimOnPlayerSpawned]](localClientNum); + } + } + + if ( isdefined( level._entityspawned_override ) ) + { + self thread [[level._entityspawned_override]]( localClientNum ); + return; + } + + if ( !isdefined( self.type ) ) + { + /# println( "Entity type undefined!" ); #/ + return; + } + + if ( self.type == "missile" ) + { + if( isdefined( level._custom_weapon_CB_Func ) ) + { + self thread [[level._custom_weapon_CB_Func]]( localClientNum ); + } + } + else if ( self.type == "vehicle" || self.type == "helicopter" || self.type == "plane" ) + { + if( isdefined(level._customVehicleCBFunc) ) + { + self thread [[level._customVehicleCBFunc]](localClientNum); + } + + self thread vehicle::field_toggle_exhaustfx_handler( localClientNum, undefined, false, true ); + self thread vehicle::field_toggle_lights_handler( localClientNum, undefined, false, true ); + + if ( self.type == "plane" || self.type == "helicopter" ) + { + self thread vehicle::aircraft_dustkick(); + } + else + { + self thread driving_fx::play_driving_fx(localClientNum); + self thread vehicle::vehicle_rumble(localClientNum); + } + + if( self.type == "helicopter" ) + { + self thread helicopter::startfx_loop( localClientNum ); + } + } + + + if ( self.type == "actor" ) + { + if( isdefined(level._customActorCBFunc) ) + { + self thread [[level._customActorCBFunc]](localClientNum); + } + } +} + +function airsupport( localClientNum, x, y, z, type, yaw, team, teamfaction, owner, exittype, time, height ) +{ + pos = ( x, y, z ); + switch( teamFaction ) + { + case "v": + teamfaction = "vietcong"; + break; + case "n": + case "nva": + teamfaction = "nva"; + break; + case "j": + teamfaction = "japanese"; + break; + case "m": + teamfaction = "marines"; + break; + case "s": + teamfaction = "specops"; + break; + case "r": + teamfaction = "russian"; + break; + default: + /# println( "Warning: Invalid team char provided, defaulted to marines" ); #/ + /# println( "Teamfaction received: " + teamFaction + "\n" ); #/ + teamfaction = "marines"; + break; + } + + switch( team ) + { + case "x": + team = "axis"; + break; + case "l": + team = "allies"; + break; + case "r": + team = "free"; + break; + default: + /# println( "Invalid team used with playclientAirstike/napalm: " + team + "\n"); #/ + team = "allies"; + break; + } + + data = spawnstruct(); + + data.team = team; + data.owner = owner; + data.bombsite = pos; + data.yaw = yaw; + direction = ( 0, yaw, 0 ); + data.direction = direction; + data.flyHeight = height; + + if ( type == "a" ) + { + planeHalfDistance = 12000; + data.planeHalfDistance = planeHalfDistance; + data.startPoint = pos + VectorScale( anglestoforward( direction ), -1 * planeHalfDistance ); + data.endPoint = pos + VectorScale( anglestoforward( direction ), planeHalfDistance ); + data.planeModel = "t5_veh_air_b52"; + data.flyBySound = "null"; + data.washSound = "veh_b52_flyby_wash"; + data.apexTime = 6145; + data.exitType = -1; + data.flySpeed = 2000; + data.flyTime = ( ( planeHalfDistance * 2 ) / data.flySpeed ); + planeType = "airstrike"; + //_airstrike::addPlaneEvent( localClientNum, planeType, data, time ); + } + else if ( type == "n" ) + { + planeHalfDistance = 24000; + data.planeHalfDistance = planeHalfDistance; + data.startPoint = pos + VectorScale( anglestoforward( direction ), -1 * planeHalfDistance ); + data.endPoint = pos + VectorScale( anglestoforward( direction ), planeHalfDistance ); + data.planeModel = airsupport::getPlaneModel( teamFaction ); + data.flyBySound = "null"; + data.washSound = "evt_us_napalm_wash"; + data.apexTime = 2362; + data.exitType = exitType; + data.flySpeed = 7000; + data.flyTime = ( ( planeHalfDistance * 2 ) / data.flySpeed ); + planeType = "napalm"; + //_plane::addPlaneEvent( localClientNum, planeType, data, time ); + } + else + { + /# + println( "" ); + println( "Unhandled airsupport type, only A (airstrike) and N (napalm) supported" ); + println( type ); + println( "" ); + #/ + return; + } +} + +function creating_corpse(localClientNum, player ) +{ +} + +function callback_stunned( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self.stunned = newVal; + +/# PrintLn("stunned_callback"); #/ + + if ( newVal ) + { + self notify("stunned"); + } + else + { + self notify("not_stunned"); + } +} + +function callback_emp( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self.emp = newVal; + +/# PrintLn("emp_callback"); #/ + + if ( newVal ) + { + self notify("emp"); + } + else + { + self notify("not_emp"); + } +} + +function callback_proximity( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self.enemyInProximity = newVal; +} diff --git a/mp/_callbacks.gsc b/mp/_callbacks.gsc new file mode 100644 index 0000000..32e5077 --- /dev/null +++ b/mp/_callbacks.gsc @@ -0,0 +1,64 @@ +#using scripts\codescripts\struct; +#using scripts\shared\ai\systems\blackboard; +#using scripts\shared\audio_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_actor; +#using scripts\mp\gametypes\_globallogic_player; +#using scripts\mp\gametypes\_globallogic_vehicle; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\shared\bots\bot_traversals; + +#namespace callback; + +function autoexec __init__sytem__() { system::register("callback",&__init__,undefined,undefined); } + +function __init__() +{ + set_default_callbacks(); +} + +// Callback Setup +// This script provides the hooks from code into script for the gametype callback functions. + +//============================================================================= + +/*================ +Setup any misc callbacks stuff like defines and default callbacks +================*/ + +/*================ +Called from the gametype script to store off the default callback functions. +This allows the callbacks to be overridden by level script, but not lost. +================*/ +function set_default_callbacks() +{ + level.callbackStartGameType = &globallogic::Callback_StartGameType; + level.callbackPlayerConnect = &globallogic_player::Callback_PlayerConnect; + level.callbackPlayerDisconnect = &globallogic_player::Callback_PlayerDisconnect; + level.callbackPlayerDamage = &globallogic_player::Callback_PlayerDamage; + level.callbackPlayerKilled = &globallogic_player::Callback_PlayerKilled; + level.callbackPlayerMelee = &globallogic_player::Callback_PlayerMelee; + level.callbackPlayerLastStand = &globallogic_player::Callback_PlayerLastStand; + level.callbackActorSpawned = &globallogic_actor::Callback_ActorSpawned; + level.callbackActorDamage = &globallogic_actor::Callback_ActorDamage; + level.callbackActorKilled = &globallogic_actor::Callback_ActorKilled; + level.callbackActorCloned = &globallogic_actor::Callback_ActorCloned; + level.callbackVehicleSpawned = &globallogic_vehicle::Callback_VehicleSpawned; + level.callbackVehicleDamage = &globallogic_vehicle::Callback_VehicleDamage; + level.callbackVehicleKilled = &globallogic_vehicle::Callback_VehicleKilled; + level.callbackVehicleRadiusDamage = &globallogic_vehicle::Callback_VehicleRadiusDamage; + level.callbackPlayerMigrated = &globallogic_player::Callback_PlayerMigrated; + level.callbackHostMigration = &hostmigration::Callback_HostMigration; + level.callbackHostMigrationSave = &hostmigration::Callback_HostMigrationSave; + level.callbackPreHostMigrationSave = &hostmigration::Callback_PreHostMigrationSave; + level.callbackBotEnteredUserEdge = &bot::Callback_BotEnteredUserEdge; + + level._custom_weapon_damage_func = &callback::callback_weapon_damage; + + level._gametype_default = "tdm"; +} diff --git a/mp/_challenges.gsc b/mp/_challenges.gsc new file mode 100644 index 0000000..f99afe1 --- /dev/null +++ b/mp/_challenges.gsc @@ -0,0 +1,1952 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\drown; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weapon_utils; + +#using scripts\mp\gametypes\_loadout; + + + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_counteruav; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_satellite; +#using scripts\mp\killstreaks\_uav; + + + + + + + +#namespace challenges; + +function autoexec __init__sytem__() { system::register("challenges",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &start_gametype ); + callback::on_spawned( &on_player_spawn ); + level.heroAbilityActivateNearDeath = &heroAbilityActivateNearDeath; + level.callbackEndHeroSpecialistEMP = &callbackEndHeroSpecialistEMP; + level.capturedObjectiveFunction = &capturedObjectiveFunction; +} + +function start_gametype() +{ + if ( !isdefined( level.ChallengesCallbacks ) ) + { + level.ChallengesCallbacks = []; + } + + waittillframeend; + + if ( isdefined ( level.scoreEventGameEndCallback ) ) + { + challenges::registerChallengesCallback( "gameEnd",level.scoreEventGameEndCallback ); + } + + if ( canProcessChallenges() ) + { + challenges::registerChallengesCallback( "playerKilled",&challengeKills ); + challenges::registerChallengesCallback( "gameEnd",&challengeGameEndMP ); + } + + callback::on_connect( &on_player_connect ); +} + +function on_player_connect() +{ + initChallengeData(); + self addSpecialistUsedStatOnConnect(); + self thread spawnWatcher(); + self thread monitorReloads(); + self thread monitorGrenadeFire(); +} + +function initChallengeData() +{ + self.pers["bulletStreak"] = 0; + self.pers["lastBulletKillTime"] = 0; + self.pers["stickExplosiveKill"] = 0; + self.pers["carepackagesCalled"] = 0; + self.pers["challenge_destroyed_air"] = 0; + self.pers["challenge_destroyed_ground"] = 0; + self.pers["challenge_anteup_earn"] = 0; + self.pers["specialistStatAbilityUsage"] = 0; + self.pers["canSetSpecialistStat"] = self isSpecialistUnlocked(); + self.pers["activeKillstreaks"] = []; +} + +function addSpecialistUsedStatOnConnect() +{ + if ( !isdefined(self.pers["challenge_heroweaponkills"]) ) + { + // intentionally using "used" hero weapon on connect to determine frequency of specialist usage + // which is the sum of "specialist ability" usage and "specialist weapon" usage. + heroWeaponName = self GetLoadoutItemRef( 0, "heroWeapon" ); + heroWeapon = GetWeapon( heroWeaponName ); + if ( heroWeapon == level.weaponNone ) + { + heroAbilityName = self GetHeroAbilityName(); + heroWeapon = GetWeapon( heroAbilityName ); + } + if ( heroWeapon != level.weaponNone ) + { + self addweaponstat( heroWeapon, "used", 1 ); + } + + self.pers["challenge_heroweaponkills"] = 0; + } +} + +function spawnWatcher() +{ + self endon( "disconnect" ); + + self.pers["killNemesis"] = 0; + self.pers["killsFastMagExt"] = 0; + self.pers["longshotsPerLife"] = 0; + self.pers["specialistStatAbilityUsage"] = 0; + self.challenge_defenderkillcount = 0; + self.challenge_offenderkillcount = 0; + self.challenge_offenderProjectileMultiKillcount = 0; + self.challenge_offenderComlinkKillcount = 0; + self.challenge_offenderSentryTurretKillCount = 0; + self.challenge_objectiveDefensiveKillcount = 0; + self.challenge_objectiveOffensiveKillcount = 0; + self.challenge_scavengedCount = 0; + self.challenge_resuppliedNameKills = 0; + self.challenge_objectiveDefensive = undefined; + self.challenge_objectiveOffensive = undefined; + self.challenge_lastsurvivewithflakfrom = undefined; + self.explosiveInfo = []; + + for(;;) + { + self waittill("spawned_player"); + + self.weaponKillsThisSpawn = []; + self.attachmentKillsThisSpawn = []; + self.challenge_hatchetTossCount = 0; + self.challenge_hatchetkills = 0; + self.retreivedBlades = 0; + self.challenge_combatRobotAttackClientID = []; + + self thread watchDoubleJump(); + self thread watchJump(); + self thread watchSwimming(); + self thread watchWallrun(); + self thread watchSlide(); + self thread watchSprint(); + self thread watchScavengeLethal(); + self thread watchWallRunTwoOppositeWallsNoGround(); + self thread watchWeaponChangeComplete(); + } +} + +function watchScavengeLethal() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self.challenge_scavengedCount = 0; + + for(;;) + { + self waittill( "scavenged_primary_grenade" ); + self.challenge_scavengedCount++; + } +} + +function watchDoublejump() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self.challenge_doublejump_begin = 0; + self.challenge_doublejump_end = 0; + for(;;) + { + ret = util::waittill_any_return( "doublejump_begin", "doublejump_end", "disconnect" ); + switch( ret ) + { + case "doublejump_begin": + self.challenge_doublejump_begin = gettime(); + break; + case "doublejump_end": + self.challenge_doublejump_end = gettime(); + break; + } + } +} + +function watchJump() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self.challenge_jump_begin = 0; + self.challenge_jump_end = 0; + for(;;) + { + ret = util::waittill_any_return( "jump_begin", "jump_end", "disconnect" ); + switch( ret ) + { + case "jump_begin": + self.challenge_jump_begin = gettime(); + break; + case "jump_end": + self.challenge_jump_end = gettime(); + break; + } + } +} + +function watchSwimming() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self.challenge_swimming_begin = 0; + self.challenge_swimming_end = 0; + for(;;) + { + ret = util::waittill_any_return( "swimming_begin", "swimming_end", "disconnect" ); + switch( ret ) + { + case "swimming_begin": + self.challenge_swimming_begin = gettime(); + break; + case "swimming_end": + self.challenge_swimming_end = gettime(); + break; + } + } +} + +function watchWallrun() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self.challenge_wallrun_begin = 0; + self.challenge_wallrun_end = 0; + for(;;) + { + ret = util::waittill_any_return( "wallrun_begin", "wallrun_end", "disconnect" ); + switch( ret ) + { + case "wallrun_begin": + self.challenge_wallrun_begin = gettime(); + break; + case "wallrun_end": + self.challenge_wallrun_end = gettime(); + break; + } + } +} + +function watchSlide() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self.challenge_slide_begin = 0; + self.challenge_slide_end = 0; + for(;;) + { + ret = util::waittill_any_return( "slide_begin", "slide_end", "disconnect" ); + switch( ret ) + { + case "slide_begin": + self.challenge_slide_begin = gettime(); + break; + case "slide_end": + self.challenge_slide_end = gettime(); + break; + } + } +} + + +function watchSprint() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self.challenge_sprint_begin = 0; + self.challenge_sprint_end = 0; + for(;;) + { + ret = util::waittill_any_return( "sprint_begin", "sprint_end", "disconnect" ); + switch( ret ) + { + case "sprint_begin": + self.challenge_sprint_begin = gettime(); + break; + case "sprint_end": + self.challenge_sprint_end = gettime(); + break; + } + } +} + + + +function challengeKills( data ) +{ + victim = data.victim; + attacker = data.attacker; + time = data.time; + level.numKills++; + attacker.lastKilledPlayer = victim; + attackerDoubleJumping = data.attackerDoubleJumping; + attackerflashbackTime = data.attackerflashbackTime; + attackerHeroAbility = data.attackerHeroAbility; + attackerHeroAbilityActive = data.attackerHeroAbilityActive; + attackerSliding = data.attackerSliding; + attackerSpeedburst = data.attackerSpeedburst; + attackerTraversing = data.attackerTraversing; + attackerVisionPulseActivateTime = data.attackerVisionPulseActivateTime; + attackerVisionPulseArray = data.attackerVisionPulseArray; + attackerVisionPulseOrigin = data.attackerVisionPulseOrigin; + attackerVisionPulseOriginArray = data.attackerVisionPulseOriginArray; + attackerWallRunning = data.attackerWallRunning; + attackerWasConcussed = data.attackerWasConcussed; + attackerWasFlashed = data.attackerWasFlashed; + attackerWasHeatWaveStunned = data.attackerWasHeatWaveStunned; + attackerWasOnGround = data.attackerOnGround; + attackerWasUnderwater = data.attackerWasUnderwater; + attackerLastFastReloadTime = data.attackerLastFastReloadTime; + lastWeaponBeforeToss = data.lastWeaponBeforeToss; + meansOfDeath = data.sMeansOfDeath; + ownerWeaponAtLaunch = data.ownerWeaponAtLaunch; + victimBedOut = data.bledOut; + victimOrigin = data.victimOrigin; + victimCombatEfficiencyLastOnTime = data.victimCombatEfficiencyLastOnTime; + victimCombatEfficieny = data.victimCombatEfficieny; + victimElectrifiedBy = data.victimElectrifiedBy; + victimflashbackTime = data.victimflashbackTime; + victimHeroAbility = data.victimHeroAbility; + victimHeroAbilityActive = data.victimHeroAbilityActive; + victimSpeedburst = data.victimSpeedburst; + victimSpeedburstLastOnTime = data.victimSpeedburstLastOnTime; + victimVisionPulseActivateTime = data.victimVisionPulseActivateTime; + victimVisionPulseActivateTime = data.victimVisionPulseActivateTime; + victimVisionPulseArray = data.victimVisionPulseArray; + victimVisionPulseOrigin = data.victimVisionPulseOrigin; + victimVisionPulseOriginArray = data.victimVisionPulseOriginArray; + victimAttackersThisSpawn = data.victimAttackersThisSpawn; + victimWasDoubleJumping = data.victimWasDoubleJumping; + victimWasInSlamState = data.victimWasInSlamState; + victimWasLungingWithArmBlades = data.victimWasLungingWithArmBlades; + victimWasOnGround = data.victimOnGround; + victimWasUnderwater = data.wasUnderwater; + victimWasWallRunning = data.victimWasWallRunning; + victimLastStunnedBy = data.victimLastStunnedBy; + victimActiveProximityGrenades = data.victim.activeProximityGrenades; + victimActiveBouncingBetties = data.victim.activeBouncingBetties; + attackerLastFlashedBy = data.attackerLastFlashedBy; + attackerLastStunnedBy = data.attackerLastStunnedBy; + attackerLastStunnedTime = data.attackerLastStunnedTime; + attackerWasSliding = data.attackerWasSliding; + attackerWasSprinting = data.attackerWasSprinting; + wasDefusing = data.wasDefusing; + wasPlanting = data.wasPlanting; + inflictorOwnerWasSprinting = data.inflictorOwnerWasSprinting; + player = data.attacker; + playerOrigin = data.attackerOrigin; + weapon = data.weapon; + + victim_doublejump_begin = data.victim_doublejump_begin; + victim_doublejump_end = data.victim_doublejump_end; + victim_jump_begin = data.victim_jump_begin; + victim_jump_end = data.victim_jump_end; + victim_swimming_begin = data.victim_swimming_begin; + victim_swimming_end = data.victim_swimming_end; + victim_slide_begin = data.victim_slide_begin; + victim_slide_end = data.victim_slide_end; + victim_wallrun_begin = data.victim_wallrun_begin; + victim_wallrun_end = data.victim_wallrun_end; + victim_was_drowning = data.victim_was_drowning; + + attacker_doublejump_begin = data.attacker_doublejump_begin; + attacker_doublejump_end = data.attacker_doublejump_end; + attacker_jump_begin = data.attacker_jump_begin; + attacker_jump_end = data.attacker_jump_end; + attacker_swimming_begin = data.attacker_swimming_begin; + attacker_swimming_end = data.attacker_swimming_end; + attacker_slide_begin = data.attacker_slide_begin; + attacker_slide_end = data.attacker_slide_end; + attacker_wallrun_begin = data.attacker_wallrun_begin; + attacker_wallrun_end = data.attacker_wallrun_end; + attacker_was_drowning = data.attacker_was_drowning; + attacker_sprint_end = data.attacker_sprint_end; + attacker_sprint_begin = data.attacker_sprint_begin; + + attacker_wallRanTwoOppositeWallsNoGround = data.attacker_wallRanTwoOppositeWallsNoGround; + + inflictorIsCooked = data.inflictorIsCooked; + inflictorChallenge_hatchetTossCount = data.inflictorChallenge_hatchetTossCount; + inflictorOwnerWasSprinting = data.inflictorOwnerWasSprinting; + inflictorPlayerHasEngineerPerk = data.inflictorPlayerHasEngineerPerk; + + inflictor = data.eInflictor; // MAY ALREADY BE REMOVED + + + if ( !isdefined( data.weapon ) ) + { + return; + } + + if ( !isdefined( player ) || !isplayer( player ) || ( weapon == level.weaponNone ) ) + { + return; + } + + // getting the altweapon name is not ideal, but acceptable for BO3's current weapons in MP; + // TODO: add code-based accessor to parentWeaponName ("Stat Name" in APE) + weaponClass = util::getWeaponClass( weapon ); + baseWeapon = getBaseWeapon( weapon ); + baseWeaponItemIndex = GetBaseWeaponItemIndex( baseWeapon ); + weaponPurchased = player isItemPurchased( baseWeaponItemIndex ); + victimSupportIndex = victim.team; + playerSupportIndex = player.team; + if ( !level.teambased ) + { + playerSupportIndex = player.entnum; + victimSupportIndex = victim.entnum; + } + + if ( meansOfDeath == "MOD_HEAD_SHOT" || meansOfDeath == "MOD_PISTOL_BULLET" || meansOfDeath == "MOD_RIFLE_BULLET" ) + { + bulletKill = true; + } + else + { + bulletKill = false; + } + + if ( level.teambased ) + { + if ( player.team == victim.team ) + { + return; + } + } + else + { + if ( player == victim ) + { + return; + } + } + + killstreak = killstreaks::get_from_weapon( data.weapon ); + + if ( !isdefined( killstreak ) ) + { + player processSpecialistChallenge( "kills" ); + if ( weapon.isheroWeapon == true ) + { + player processSpecialistChallenge( "kills_weapon" ); + player.heroWeaponKillsThisActivation++; + player.pers["challenge_heroweaponkills"]++; + if ( player.pers["challenge_heroweaponkills"] >= 6 ) + { + player processSpecialistChallenge( "kill_one_game_weapon" ); + player.pers["challenge_heroweaponkills"] = 0; + } + } + } + + if ( bulletKill ) + { + if ( weaponPurchased ) + { + if ( weaponclass == "weapon_sniper" ) + { + if ( isdefined ( victim.firstTimeDamaged ) && victim.firstTimeDamaged == time ) + { + player AddPlayerStat( "kill_enemy_one_bullet_sniper", 1 ); + player AddWeaponStat( weapon, "kill_enemy_one_bullet_sniper", 1 ); + } + } + else if ( weaponclass == "weapon_cqb" ) + { + if ( isdefined ( victim.firstTimeDamaged ) && victim.firstTimeDamaged == time ) + { + player AddPlayerStat( "kill_enemy_one_bullet_shotgun", 1 ); + player AddWeaponStat( weapon, "kill_enemy_one_bullet_shotgun", 1 ); + } + } + } + + if ( GetDvarInt( "ui_enablePromoTracking", 0 ) && meansOfDeath == "MOD_HEAD_SHOT" ) + { + util::increment_zmhd_thermometer( 1 ); + } + + if ( ( time - data.attacker_swimming_end <= 2000 ) && ( time - data.attacker_doublejump_begin <= 2000 ) ) + { + player AddPlayerStat( "kill_after_doublejump_out_of_water", 1 ); + } + + if ( attackerWasSliding ) + { + if ( attacker_doublejump_end == attacker_slide_begin ) + { + player AddPlayerStat( "kill_while_sliding_from_doublejump", 1 ); + } + } + + if ( ( player IsBonusCardActive( 2, player.class_num ) ) && player IsItemPurchased( GetItemIndexFromRef( "bonuscard_primary_gunfighter_3" ) ) ) + { + if ( isdefined( weapon.attachments ) && weapon.attachments.size == 6 ) + { + player AddPlayerStat( "kill_with_gunfighter", 1 ); + } + } + + //Check for the killstreak 5 challenge completion + checkkillstreak5( baseWeapon, player ); + + if ( weapon.isDualWield && weaponPurchased ) + { + checkDualWield( baseWeapon, player, attacker, time, attackerwassprinting, attacker_sprint_end ); + } + + if ( isdefined( weapon.attachments ) && weapon.attachments.size > 0 ) + { + attachmentName = player GetWeaponOptic( weapon ); + + if ( isdefined( attachmentName ) && attachmentName != "" && player WeaponHasAttachmentAndUnlocked( weapon, attachmentName ) ) + { + if ( weapon.attachments.size > 5 && player AllWeaponAttachmentsUnlocked( weapon ) && !isdefined( attacker.tookWeaponFrom[ weapon ] ) ) + { + player AddPlayerStat( "kill_optic_5_attachments", 1 ); + } + if ( isdefined( player.attachmentKillsThisSpawn[ attachmentName ] ) ) + { + player.attachmentKillsThisSpawn[ attachmentName ]++; + if ( player.attachmentKillsThisSpawn[ attachmentName ] == 5 ) + { + player AddWeaponStat( weapon, "killstreak_5_attachment", 1 ); + } + } + else + { + player.attachmentKillsThisSpawn[ attachmentName ] = 1; + } + + if ( weapon_utils::isPistol( weapon.rootweapon ) ) + { + if ( player weaponHasAttachmentAndUnlocked( weapon, "suppressed", "extbarrel" ) ) + { + player AddPlayerStat( "kills_pistol_lasersight_suppressor_longbarrel", 1 ); + } + } + } + + if ( player weaponHasAttachmentAndUnlocked( weapon, "suppressed" ) ) + { + if ( attacker util::has_hard_wired_perk_purchased_and_equipped() // hardwired + && attacker util::has_ghost_perk_purchased_and_equipped() // ghost + && attacker util::has_jetquiet_perk_purchased_and_equipped() ) // blast suppressor + { + player AddPlayerStat( "kills_suppressor_ghost_hardwired_blastsuppressor", 1 ); + } + } + + if ( player PlayerAds() == 1 ) + { + if ( isdefined( player.smokeGrenadeTime ) && isdefined( player.smokeGrenadePosition ) ) + { + if ( player.smokeGrenadeTime + 14000 > time ) + { + if ( player util::is_looking_at( player.smokeGrenadePosition ) || ( distancesquared( player.origin, player.smokeGrenadePosition ) < 200 * 200 )) + { + if ( player weaponHasAttachmentAndUnlocked( weapon, "ir" ) ) + { + player AddPlayerStat( "kill_with_thermal_and_smoke_ads", 1 ); + player AddWeaponStat( weapon, "kill_thermal_through_smoke", 1 ); + } + } + } + } + } + + if ( weapon.attachments.size > 1 ) + { + if ( player PlayerAds() == 1 ) + { + if ( player weaponHasAttachmentAndUnlocked( weapon, "grip", "quickdraw" ) ) + { + player AddPlayerStat( "kills_ads_quickdraw_and_grip", 1 ); + } + if ( player weaponHasAttachmentAndUnlocked( weapon, "swayreduc", "stalker" ) ) + { + player AddPlayerStat( "kills_ads_stock_and_cpu", 1 ); + } + } + else + { + if ( player weaponHasAttachmentAndUnlocked( weapon, "rf", "steadyaim" ) ) + { + if ( attacker util::has_fast_hands_perk_purchased_and_equipped() ) + { + player AddPlayerStat( "kills_hipfire_rapidfire_lasersights_fasthands", 1 ); + } + } + } + if ( player weaponHasAttachmentAndUnlocked( weapon, "fastreload", "extclip" ) ) + { + player.pers["killsFastMagExt"]++; + if ( player.pers["killsFastMagExt"] > 4 ) + { + player AddPlayerStat( "kills_one_life_fastmags_and_extclip", 1 ); + player.pers["killsFastMagExt"] = 0; + } + } + } + + if ( weapon.attachments.size > 2 ) + { + if ( meansOfDeath == "MOD_HEAD_SHOT" ) + { + if ( player weaponHasAttachmentAndUnlocked( weapon, "fmj", "damage", "extbarrel" ) ) + { + player AddPlayerStat( "headshot_fmj_highcaliber_longbarrel", 1 ); + } + } + } + + if ( weapon.attachments.size > 4 ) + { + if ( player weaponHasAttachmentAndUnlocked( weapon, "extclip", "grip", "fastreload", "quickdraw", "stalker" ) ) + { + player AddPlayerStat( "kills_extclip_grip_fastmag_quickdraw_stock", 1 ); + } + } + } + + if ( victim_was_drowning && attacker_was_drowning ) + { + player AddPlayerStat( "dr_lung", 1 ); + } + + if ( isdefined( attackerLastFastReloadTime ) && ( time - attackerLastFastReloadTime <= 5000 ) && player WeaponHasAttachmentAndUnlocked( weapon, "fastreload" ) ) + { + player AddPlayerStat( "kills_after_reload_fastreload", 1 ); + } + + if ( victim.iDFlagsTime == time ) + { + if ( victim.iDFlags & 8 ) + { + player AddPlayerStat( "kill_enemy_through_wall", 1 ); + if ( player weaponHasAttachmentAndUnlocked( weapon, "fmj" ) ) + { + player AddPlayerStat( "kill_enemy_through_wall_with_fmj", 1 ); + } + } + } + + if ( attacker_wallRanTwoOppositeWallsNoGround === true ) + { + player AddPlayerStat( "kill_while_wallrunning_2_walls", 1 ); + } + + // end if ( bulletkill ) + } + else if ( weapon_utils::isMeleeMOD( meansOfDeath ) && !isdefined( killstreak ) ) + { + player AddPlayerStat( "melee", 1 ); + if ( weapon_utils::isPunch( weapon ) ) + { + player AddPlayerStat( "kill_enemy_with_fists", 1 ); + } + + //Check for the killstreak 5 challenge completion + checkkillstreak5( baseWeapon, player ); + } + else + { + if ( weaponPurchased ) + { + if ( weapon == player.grenadeTypePrimary ) + { + if ( player.challenge_scavengedCount > 0 ) + { + player.challenge_resuppliedNameKills++; + if ( player.challenge_resuppliedNameKills >= 3 ) + { + player AddPlayerStat( "kills_3_resupplied_nade_one_life", 1 ); + player.challenge_resuppliedNameKills = 0; + } + player.challenge_scavengedCount--; + } + } + if ( isdefined( inflictorIsCooked ) ) + { + if ( inflictorIsCooked == true && weapon.rootweapon.name != "hatchet" ) + { + player AddPlayerStat( "kill_with_cooked_grenade", 1 ); + } + } + + if ( victimLastStunnedBy === player ) + { + if ( weaponclass == "weapon_grenade" ) + { + player AddPlayerStat( "kill_stun_lethal", 1 ); + } + } + + if ( baseWeapon == level.weaponSpecialCrossbow ) + { + if ( weapon.isDualWield ) // determined purchased + { + checkDualWield( baseWeapon, player, attacker, time, attackerwassprinting, attacker_sprint_end ); + } + } + + // for "energy" shotgun + if ( baseWeapon == level.weaponShotgunEnergy ) + { + if ( isdefined ( victim.firstTimeDamaged ) && victim.firstTimeDamaged == time ) + { + player AddPlayerStat( "kill_enemy_one_bullet_shotgun", 1 ); + player AddWeaponStat( weapon, "kill_enemy_one_bullet_shotgun", 1 ); + } + } + } + + if ( baseWeapon.forceDamageHitLocation || baseWeapon == level.weaponSpecialCrossbow || baseWeapon == level.weaponShotgunEnergy || baseWeapon == level.weaponSpecialDiscGun || baseWeapon == level.weaponBallisticKnife || baseWeapon == level.weaponLauncherEx41 ) + { + //Check for the killstreak 5 challenge completion + checkkillstreak5( baseWeapon, player ); + } + } + + if ( isdefined( attacker.tookWeaponFrom[ weapon ] ) && isdefined( attacker.tookWeaponFrom[ weapon ].previousOwner ) ) + { + if ( !isdefined( attacker.tookWeaponFrom[ weapon ].previousOwner.team ) || attacker.tookWeaponFrom[ weapon ].previousOwner.team != player.team ) + { + player AddPlayerStat( "kill_with_pickup", 1 ); + } + } + + awarded_kill_enemy_that_blinded_you = false; + + playerHasTacticalMask = loadout::hasTacticalMask( player ); + + if ( attackerWasFlashed ) + { + if ( attackerLastFlashedBy === victim && !playerHasTacticalMask ) + { + player AddPlayerStat( "kill_enemy_that_blinded_you", 1 ); + awarded_kill_enemy_that_blinded_you = true; + } + } + + if ( !awarded_kill_enemy_that_blinded_you && isdefined( attackerLastStunnedTime ) && attackerLastStunnedTime + 5000 > time ) + { + if ( attackerLastStunnedBy === victim && !playerHasTacticalMask ) + { + player AddPlayerStat( "kill_enemy_that_blinded_you", 1 ); + awarded_kill_enemy_that_blinded_you = true; + } + } + + killedStunnedVictim = false; + if ( isdefined( victim.lastConcussedBy ) && victim.lastConcussedBy == attacker ) + { + if ( victim.concussionEndTime > time ) + { + if ( player util::is_item_purchased( "concussion_grenade" ) ) + { + player AddPlayerStat( "kill_concussed_enemy", 1 ); + } + killedStunnedVictim = true; + player AddWeaponStat( GetWeapon( "concussion_grenade" ), "CombatRecordStat", 1 ); + } + } + + if ( isdefined( victim.lastShockedBy ) && victim.lastShockedBy == attacker ) + { + if ( victim.shockEndTime > time ) + { + if ( player util::is_item_purchased( "proximity_grenade" ) ) + { + player AddPlayerStat( "kill_shocked_enemy", 1 ); + } + player AddWeaponStat( GetWeapon( "proximity_grenade" ), "CombatRecordStat", 1 ); + killedStunnedVictim = true; + if ( weapon.rootweapon.name == "bouncingbetty" ) + { + player AddPlayerStat( "kill_trip_mine_shocked", 1 ); + } + } + + } + + if ( victim util::isFlashbanged() ) + { + if ( isdefined( victim.lastFlashedBy ) && victim.lastFlashedBy == player ) + { + killedStunnedVictim = true; + if ( player util::is_item_purchased( "flash_grenade" ) ) + { + player AddPlayerStat( "kill_flashed_enemy", 1 ); + } + player AddWeaponStat( GetWeapon( "flash_grenade" ), "CombatRecordStat", 1 ); + } + } + + if ( level.teamBased ) + { + if ( ( !isDefined( player.pers["kill_every_enemy_with_specialist"] ) ) && ( level.playerCount[victim.pers["team"]] > 3 && player.pers["killed_players_with_specialist"].size >= level.playerCount[victim.pers["team"]] ) ) + { + player AddPlayerStat( "kill_every_enemy", 1 ); + player.pers["kill_every_enemy_with_specialist"] = true; + } + + + if ( isdefined( victimAttackersThisSpawn ) && IsArray( victimAttackersThisSpawn ) ) + { + if ( victimAttackersThisSpawn.size > 5 ) + { + attackerCount = 0; + foreach( attacking_player in victimAttackersThisSpawn ) + { + if ( !isdefined( attacking_player ) ) + continue; + + if ( attacking_player == attacker ) + continue; + + if ( attacking_player.team != attacker.team ) + continue; + + attackerCount++; + } + + if ( attackerCount > 4 ) + { + // note: "kill_enemy_5_teammates_assists" should be awarded withing on life, not just assist + player AddPlayerStat( "kill_enemy_5_teammates_assists", 1 ); + } + } + } + } + + if ( isdefined( killstreak ) ) + { + if ( killstreak == "rcbomb" || killstreak == "inventory_rcbomb" ) + { + if ( !victimWasOnGround || victimWasWallRunning ) + { + player AddPlayerStat( "kill_wallrunner_or_air_with_rcbomb", 1 ); + } + } + + if ( killstreak == "autoturret" || killstreak == "inventory_autoturret" ) + { + if ( isdefined( inflictor ) && player util::is_item_purchased( "killstreak_auto_turret" ) ) + { + if ( !isdefined( inflictor.challenge_killcount ) ) + { + inflictor.challenge_killcount = 0; + } + + inflictor.challenge_killcount++; + if ( inflictor.challenge_killcount == 5 ) + { + player AddPlayerStat( "kills_auto_turret_5", 1 ); + } + } + } + } + + if ( isdefined( victim.challenge_combatRobotAttackClientID[player.clientid] ) ) + { + if ( !isdefined( inflictor ) || !isdefined( inflictor.killstreakType ) || !IsString( inflictor.killstreakType ) || ( inflictor.killstreakType != "combat_robot" ) ) + { + player AddPlayerStat( "kill_enemy_who_damaged_robot", 1 ); + } + } + + if ( player IsBonusCardActive( 8, player.class_num ) && player util::is_item_purchased( "bonuscard_danger_close" ) ) + { + if ( weaponclass == "weapon_grenade" ) + { + player AddBonusCardStat( 8, "kills", 1, player.class_num ); + } + if ( weapon.rootweapon.name == "hatchet" && inflictorChallenge_hatchetTossCount <= 2 ) + { + player.challenge_hatchetkills++; + if ( player.challenge_hatchetkills == 2 ) + { + player AddPlayerStat( "kills_first_throw_both_hatchets", 1 ); + } + } + } + + player trackKillstreakSupportKills( victim ); + + // PERKS + if ( !isdefined( killstreak ) ) + { + if ( attackerWasUnderwater ) + { + player AddPlayerStat( "kill_while_underwater", 1 ); + } + + if ( player util::has_purchased_perk_equipped( "specialty_jetcharger" ) ) // afterburner + { + if ( ( attacker_doublejump_begin > attacker_doublejump_end || attacker_doublejump_end + 3000 > time ) + || ( attacker_slide_begin > attacker_slide_end || attacker_slide_end + 3000 > time ) ) + { + player AddPlayerStat( "kills_after_jumping_or_sliding", 1 ); + + if ( player util::has_purchased_perk_equipped( "specialty_overcharge" ) ) + { + { + player AddPlayerStat( "kill_overclock_afterburner_specialist_weapon_after_thrust", 1 ); + } + } + } + } + + trackedPlayer = false; + if ( player util::has_purchased_perk_equipped( "specialty_tracker" ) ) + { + if ( !victim hasPerk ( "specialty_trackerjammer" ) ) // hardwired + { + player AddPlayerStat( "kill_detect_tracker", 1 ); + trackedPlayer = true; + } + } + + if ( player util::has_purchased_perk_equipped( "specialty_detectnearbyenemies" ) ) // sixth sense + { + if ( !victim hasPerk ( "specialty_sixthsensejammer" ) ) + { + player AddPlayerStat( "kill_enemy_sixth_sense", 1 ); + + if ( player util::has_purchased_perk_equipped( "specialty_loudenemies" ) ) // awareness + { + if ( !victim hasPerk ( "specialty_quieter" ) ) // dead silence + { + player AddPlayerStat( "kill_sixthsense_awareness", 1 ); + } + } + } + + if ( trackedPlayer ) + { + player AddPlayerStat( "kill_tracker_sixthsense", 1 ); + } + } + + if ( weapon.isheroWeapon == true || attackerHeroAbilityActive ) + { + if ( player util::has_purchased_perk_equipped( "specialty_overcharge" ) ) // overclock + { + player AddPlayerStat( "kill_with_specialist_overclock", 1 ); + } + } + + if ( player util::has_purchased_perk_equipped( "specialty_gpsjammer" ) ) // ghost + { + if ( uav::HasUAV( victimSupportIndex ) ) + { + player AddPlayerStat( "kill_uav_enemy_with_ghost", 1 ); + } + + if ( player util::has_blind_eye_perk_purchased_and_equipped() ) // blindeye + { + activeKillstreaks = victim killstreaks::getActiveKillstreaks(); + + awarded_kill_blindeye_ghost_aircraft = false; + foreach( activeStreak in activeKillstreaks ) + { + if ( awarded_kill_blindeye_ghost_aircraft ) + break; + + switch( activeStreak.killstreakType ) + { + case "sentinel": + case "helicopter_comlink": + case "drone_striked": + case "uav": + player AddPlayerStat( "kill_blindeye_ghost_aircraft", 1 ); + awarded_kill_blindeye_ghost_aircraft = true; + break; + } + } + } + } + if ( player util::has_purchased_perk_equipped( "specialty_flakjacket" ) ) + { + if ( isdefined ( player.challenge_lastsurvivewithflakfrom ) && player.challenge_lastsurvivewithflakfrom == victim ) + { + player AddPlayerStat( "kill_enemy_survive_flak", 1 ); + } + + + if ( player util::has_tactical_mask_purchased_and_equipped() ) + { + recentlySurvivedFlak = false; + if ( isdefined ( player.challenge_lastsurvivewithflaktime ) ) + { + if ( ( player.challenge_lastsurvivewithflaktime + 3000 ) > time ) + { + recentlySurvivedFlak = true; + } + } + recentlyStunned = false; + if ( isdefined( player.lastStunnedTime ) ) + { + if ( player.lastStunnedTime + 2000 > time ) + { + recentlyStunned = true; + } + } + if ( recentlySurvivedFlak + || ( player util::isFlashbanged() ) + || recentlyStunned ) + { + player AddPlayerStat( "kill_flak_tac_while_stunned", 1 ); + } + } + } + + if ( player util::has_hard_wired_perk_purchased_and_equipped() ) // hard wired + { + if ( victim counteruav::HasIndexActiveCounterUAV( victimSupportIndex ) || victim emp::hasactiveemp() ) + { + player AddPlayerStat( "kills_counteruav_emp_hardline", 1 ); + } + } + + if ( player util::has_scavenger_perk_purchased_and_equipped() ) + { + if ( player.scavenged ) + { + player AddPlayerStat( "kill_after_resupply", 1 ); + if ( trackedPlayer ) + { + player AddPlayerStat( "kill_scavenger_tracker_resupply", 1 ); + } + } + } + + if ( player util::has_fast_hands_perk_purchased_and_equipped() ) // fasthands + { + if ( bulletKill ) + { + if ( attackerWasSprinting || attacker_sprint_end + 3000 > time ) + { + player AddPlayerStat( "kills_after_sprint_fasthands", 1 ); + if ( player util::has_gung_ho_perk_purchased_and_equipped() ) // gungho + { + player AddPlayerStat( "kill_fasthands_gungho_sprint", 1 ); + } + } + } + } + + if ( player util::has_hard_wired_perk_purchased_and_equipped() ) // hardwired + { + if ( player util::has_cold_blooded_perk_purchased_and_equipped() ) // cold blooded + { + player AddPlayerStat( "kill_hardwired_coldblooded", 1 ); + } + } + + + killedPlayerWithGungHo = false; + if ( player util::has_gung_ho_perk_purchased_and_equipped() ) // gung ho + { + if ( bulletKill ) + { + killedPlayerWithGungHo = true; + if ( attackerWasSprinting && player PlayerAds() != 1 ) + { + player AddPlayerStat( "kill_hip_gung_ho", 1 ); + } + } + if ( weaponclass == "weapon_grenade" ) + { + if ( isdefined( inflictorOwnerWasSprinting ) && inflictorOwnerWasSprinting == true ) + { + killedPlayerWithGungHo = true; + player AddPlayerStat( "kill_hip_gung_ho", 1 ); + } + } + } + + if ( player util::has_jetquiet_perk_purchased_and_equipped() ) // blast surpressor + { + if ( attackerDoubleJumping || ( attacker_doublejump_end + 3000 > time ) ) + { + player AddPlayerStat( "kill_blast_doublejump", 1 ); + if ( player util::has_ghost_perk_purchased_and_equipped() ) // ghost + { + if ( uav::HasUAV( victimSupportIndex ) ) + { + player AddPlayerStat( "kill_doublejump_uav_engineer_hardwired", 1 ); + } + } + } + } + if ( player util::has_awareness_perk_purchased_and_equipped() ) // awareness + { + player AddPlayerStat( "kill_awareness", 1 ); + } + + if ( killedStunnedVictim ) + { + if ( player util::has_tactical_mask_purchased_and_equipped() ) // tacmask + { + player AddPlayerStat( "kill_stunned_tacmask", 1 ); + if ( killedPlayerWithGungHo == true ) + { + player AddPlayerStat( "kill_sprint_stunned_gungho_tac", 1 ); + } + } + } + + if ( player util::has_ninja_perk_purchased_and_equipped() ) // dead silence + { + player AddPlayerStat( "kill_dead_silence", 1 ); + + if ( distanceSquared( playerOrigin, victimOrigin ) < 120 * 120 ) + { + if ( player util::has_awareness_perk_purchased_and_equipped() ) // awareness + { + player AddPlayerStat( "kill_close_deadsilence_awareness", 1 ); + } + + if ( player util::has_jetquiet_perk_purchased_and_equipped() ) // blast suppressr + { + player AddPlayerStat( "kill_close_blast_deadsilence", 1 ); + } + } + } + + greedCardsActive = 0; + if ( player IsBonusCardActive( 5, player.class_num ) && player util::is_item_purchased( "bonuscard_perk_1_greed" ) ) + { + greedCardsActive++; + } + if ( player IsBonusCardActive( 6, player.class_num ) && player util::is_item_purchased( "bonuscard_perk_2_greed" ) ) + { + greedCardsActive++; + } + if ( player IsBonusCardActive( 7, player.class_num ) && player util::is_item_purchased( "bonuscard_perk_3_greed" ) ) + { + greedCardsActive++; + } + + if ( greedCardsActive >= 2 ) + { + player AddPlayerStat( "kill_2_greed_2_perks_each", 1 ); + } + + if ( player BonusCardActiveCount( player.class_num ) >= 2 ) + { + player AddPlayerStat( "kill_2_wildcards", 1 ); + } + + + gunfighterOverkillActive = false; + if ( player IsBonusCardActive( 4, player.class_num ) && player util::is_item_purchased( "bonuscard_overkill" ) ) + { + primaryAttachmentsTotal = 0; + if ( isdefined( player.primaryLoadoutWeapon ) ) + primaryAttachmentsTotal = player.primaryLoadoutWeapon.attachments.size; + + secondaryAttachmentsTotal = 0; + if ( isdefined( player.secondaryLoadoutWeapon ) ) + secondaryAttachmentsTotal = player.secondaryLoadoutWeapon.attachments.size; + + if ( primaryAttachmentsTotal + secondaryAttachmentsTotal >= 5 ) + { + gunfighterOverkillActive = true; + } + } + + if ( ( isdefined (player.primaryLoadoutWeapon ) && weapon == player.primaryLoadoutWeapon ) + || ( isdefined (player.primaryLoadoutAltWeapon ) && weapon == player.primaryLoadoutAltWeapon ) ) + { + if ( player IsBonusCardActive( 0, player.class_num ) && player util::is_item_purchased( "bonuscard_primary_gunfighter" ) ) + { + player AddBonusCardStat( 0, "kills", 1, player.class_num ); + player AddPlayerStat( "kill_with_loadout_weapon_with_3_attachments", 1 ); + } + if ( isdefined( player.secondaryWeaponKill ) && player.secondaryWeaponKill == true ) + { + player.primaryWeaponKill = false; + player.secondaryWeaponKill = false; + if ( player IsBonusCardActive( 4, player.class_num ) && player util::is_item_purchased( "bonuscard_overkill" ) ) + { + player AddBonusCardStat( 4, "kills", 1, player.class_num ); + player AddPlayerStat( "kill_with_both_primary_weapons", 1 ); + if ( gunfighterOverkillActive ) + { + player AddPlayerStat( "kill_overkill_gunfighter_5_attachments", 1 ); + } + } + } + else + { + player.primaryWeaponKill = true; + } + } + else if ( ( isdefined( player.secondaryLoadoutWeapon ) && weapon == player.secondaryLoadoutWeapon ) + || ( isdefined( player.secondaryLoadoutAltWeapon ) && weapon == player.secondaryLoadoutAltWeapon ) ) + { + if ( player IsBonusCardActive( 3, player.class_num ) && player util::is_item_purchased( "bonuscard_secondary_gunfighter" ) ) + { + player AddBonusCardStat( 3, "kills", 1, player.class_num ); + } + if ( isdefined( player.primaryWeaponKill ) && player.primaryWeaponKill == true ) + { + player.primaryWeaponKill = false; + player.secondaryWeaponKill = false; + if ( player IsBonusCardActive( 4, player.class_num ) && player util::is_item_purchased( "bonuscard_overkill" ) ) + { + player AddBonusCardStat( 4, "kills", 1, player.class_num ); + player AddPlayerStat( "kill_with_both_primary_weapons", 1 ); + if ( gunfighterOverkillActive ) + { + player AddPlayerStat( "kill_overkill_gunfighter_5_attachments", 1 ); + } + } + } + else + { + player.secondaryWeaponKill = true; + } + } + + if ( player util::has_hacker_perk_purchased_and_equipped() && player util::has_hard_wired_perk_purchased_and_equipped() ) + { + should_award_kill_near_plant_engineer_hardwired = false; + + if ( isdefined( victimActiveBouncingBetties ) ) + { + foreach( bouncingBettyInfo in victimActiveBouncingBetties ) + { + if ( !isdefined( bouncingBettyInfo ) || !isdefined( bouncingBettyInfo.origin ) ) + continue; + + if ( DistanceSquared( bouncingBettyInfo.origin, victimOrigin ) < ( (400) * (400) ) ) + { + should_award_kill_near_plant_engineer_hardwired = true; + break; + } + } + } + + if ( isdefined( victimActiveProximityGrenades ) && should_award_kill_near_plant_engineer_hardwired == false ) + { + foreach( proximityGrenadeInfo in victimActiveProximityGrenades ) + { + if ( !isdefined( proximityGrenadeInfo ) || !isdefined( proximityGrenadeInfo.origin ) ) + continue; + + if ( DistanceSquared( proximityGrenadeInfo.origin, victimOrigin ) < ( (400) * (400) ) ) + { + should_award_kill_near_plant_engineer_hardwired = true; + break; + } + } + } + + if ( should_award_kill_near_plant_engineer_hardwired ) + { + player AddPlayerStat( "kill_near_plant_engineer_hardwired", 1 ); + } + } + } + else // it was a killstreak + { + if ( weapon.name == "supplydrop" ) + { + if (isdefined( inflictorPlayerHasEngineerPerk ) ) + { + player AddPlayerStat( "kill_booby_trap_engineer", 1 ); + } + } + } + + if ( weapon.isHeroWeapon == true || attackerHeroAbilityActive || isdefined( killstreak ) ) + { + if ( player util::has_purchased_perk_equipped( "specialty_overcharge" ) // overclock + && ( player util::has_purchased_perk_equipped( "specialty_anteup" ) ) ) // anteup + { + player AddPlayerStat( "kill_anteup_overclock_scorestreak_specialist", 1 ); + } + } +} + +function on_player_spawn() +{ + if ( canProcessChallenges() ) + { + self fix_challenge_stats_on_spawn(); + } +} + +function get_challenge_stat( stat_name ) +{ + return self GetDStat( "playerstatslist", stat_name, "challengevalue" ); +} + +function force_challenge_stat( stat_name, stat_value ) +{ + // make the stat value and challenge value identical + self SetDStat( "playerstatslist", stat_name, "statvalue", stat_value ); + self SetDStat( "playerstatslist", stat_name, "challengevalue", stat_value ); +} + +function get_challenge_group_stat( group_name, stat_name ) +{ + return self GetDStat( "groupstats", group_name, "stats", stat_name, "challengevalue" ); +} + +function fix_challenge_stats_on_spawn() +{ + // use this method to fix up any challenge inconsistencies at spawn + + player = self; + + if ( !isdefined( player ) ) + return; + + if ( player.fix_challenge_stats_performed === true ) + return; + + player fix_TU6_weapon_for_diamond( "special_crossbow_for_diamond" ); + player fix_TU6_weapon_for_diamond( "melee_crowbar_for_diamond" ); + player fix_TU6_weapon_for_diamond( "melee_sword_for_diamond" ); + player fix_TU6_ar_garand(); + player fix_TU6_pistol_shotgun(); + player TU7_fix_100_percenter(); + + player.fix_challenge_stats_performed = true; +} + +function fix_TU6_weapon_for_diamond( stat_name ) +{ + player = self; + + wepaon_for_diamond = player get_challenge_stat( stat_name ); + + if ( wepaon_for_diamond == 1 ) + { + // valid values for X_for_diamond are 0, 2, and 3; TU5 has an issue for some challenges + // where only 1 point was awarded if pistol diamond was awarded ( and some challenges where any secondary diamond camo was awwarded ) + // this fixes the issue by setting it to either zero or two + + secondary_mastery = player get_challenge_stat( "secondary_mastery" ); + + if ( secondary_mastery == 3 ) + { + player force_challenge_stat( stat_name, 2 ); + } + else + { + player force_challenge_stat( stat_name, 0 ); + } + } +} + +function fix_TU6_ar_garand() +{ + player = self; + + group_weapon_assault = player get_challenge_group_stat( "weapon_assault", "challenges" ); + weapons_mastery_assault = player get_challenge_stat( "weapons_mastery_assault" ); + + // earning weapons mastery on assault rifles prior to TU6 will exhibit an issue of not being able to obtain diamond camo for ar_garand + // this fixes the issue by awarding ar_garand_for_diamond if weapons_mastery_assault was not obtained + + // 49 obtained from challenge 648 in statsmilestones3.csv + if ( group_weapon_assault >= 49 && weapons_mastery_assault < 1 ) + { + player force_challenge_stat( "weapons_mastery_assault", 1 ); // intentionally forced stat here to avoid stat processing + + player AddPlayerStat( "ar_garand_for_diamond", 1 ); // now add stat with stat processing + } +} + +function fix_TU6_pistol_shotgun() +{ + player = self; + + group_weapon_pistol = player get_challenge_group_stat( "weapon_pistol", "challenges" ); + secondary_mastery_pistol = player get_challenge_stat( "secondary_mastery_pistol" ); + + // see comments in fix_TU6_ar_garand for more related details + + // 21 obtained from challenge 649 in statsmilestones3.csv + if ( group_weapon_pistol >= 21 && secondary_mastery_pistol < 1 ) + { + player force_challenge_stat( "secondary_mastery_pistol", 1 ); // intentionally forced stat here to avoid stat processing + + player AddPlayerStat( "pistol_shotgun_for_diamond", 1 ); // now add stat with stat processing + } +} + +function completed_specific_challenge( target_value, challenge_name ) +{ + challenge_count = self get_challenge_stat( challenge_name ); + + return ( challenge_count >= target_value ); +} + +function tally_completed_challenge( target_value, challenge_name ) +{ + return ( ( self completed_specific_challenge( target_value, challenge_name ) ) ? 1 : 0 ); +} + +function TU7_fix_100_percenter() +{ + self TU7_fix_mastery_perk_2(); +} + +function TU7_fix_mastery_perk_2() +{ + player = self; + + // if mastery_perk_2 is compeleted, then no need to try to fix up + mastery_perk_2 = player get_challenge_stat( "mastery_perk_2" ); + if ( mastery_perk_2 >= 12 ) + return; + + // if earn_scorestreak_anteup has not been completed, no need to try to fix up + if ( player completed_specific_challenge( 200, "earn_scorestreak_anteup") == false ) + return; + + perk_2_tally = 1; // init to 1 because earn_scorestreak_anteup is already completed + + perk_2_tally += player tally_completed_challenge( 100, "destroy_ai_scorestreak_coldblooded" ); //destroy_ai_scorestreak_coldblooded + perk_2_tally += player tally_completed_challenge( 100, "kills_counteruav_emp_hardline" ); //kills_counteruav_emp_hardline + perk_2_tally += player tally_completed_challenge( 200, "kill_after_resupply" ); //kill_after_resupply + perk_2_tally += player tally_completed_challenge( 100, "kills_after_sprint_fasthands" ); //kills_after_sprint_fasthands + perk_2_tally += player tally_completed_challenge( 200, "kill_detect_tracker" ); //kill_detect_tracker + //perk_2_tally += player tally_completed_challenge( 200, "earn_scorestreak_anteup" ); //earn_scorestreak_anteup + perk_2_tally += player tally_completed_challenge( 10, "earn_5_scorestreaks_anteup" ); //earn_5_scorestreaks_anteup + perk_2_tally += player tally_completed_challenge( 25, "kill_scavenger_tracker_resupply" ); //kill_scavenger_tracker_resupply + perk_2_tally += player tally_completed_challenge( 25, "kill_hardwired_coldblooded" ); //kill_hardwired_coldblooded + perk_2_tally += player tally_completed_challenge( 25, "kill_anteup_overclock_scorestreak_specialist" ); //kill_anteup_overclock_scorestreak_specialist + perk_2_tally += player tally_completed_challenge( 50, "kill_fasthands_gungho_sprint" ); //kill_fasthands_gungho_sprint + perk_2_tally += player tally_completed_challenge( 50, "kill_tracker_sixthsense" ); //kill_tracker_sixthsense + + if ( mastery_perk_2 < perk_2_tally ) + { + // award "mastery_perk_2" + player AddPlayerStat( "mastery_perk_2", 1 ); + } +} + +function getBaseWeapon( weapon ) +{ + // TODO: need to get weapon stat name available from script (and make sure all stat names are populated properly for weapons) + base_weapon_param = [[ level.get_base_weapon_param ]]( weapon ); // we have a "root" weapon after this executes + base_weapon_param_name = str_strip_lh_or_dw( base_weapon_param.name ); + base_weapon_param_name = str_strip_lh_from_crossbow( base_weapon_param_name ); // unique case because of poor naming convention + return GetWeapon( GetRefFromItemIndex( GetBaseWeaponItemIndex( GetWeapon( base_weapon_param_name ) ) ) ); +} + +function str_strip_lh_from_crossbow( str ) +{ + if ( StrEndsWith( str, "crossbowlh" ) ) + { + return GetSubStr( str, 0, str.size - 2 ); + } + + return str; +} + +function str_strip_lh_or_dw( str ) +{ + if ( StrEndsWith( str, "_lh" ) || StrEndsWith( str, "_dw" ) ) + { + return GetSubStr( str, 0, str.size - 3 ); + } + + return str; +} + +function checkKillStreak5( baseWeapon, player ) +{ + if ( isdefined( player.weaponKillsThisSpawn[ baseWeapon ] ) ) + { + player.weaponKillsThisSpawn[ baseWeapon ]++; + if ( ( player.weaponKillsThisSpawn[ baseWeapon ] ) % 5 == 0 ) + { + player AddWeaponStat( baseWeapon, "killstreak_5", 1 ); + } + } + else + { + player.weaponKillsThisSpawn[ baseWeapon ] = 1; + } +} + +function checkDualWield( baseWeapon, player, attacker, time, attackerWasSprinting, attacker_sprint_end ) +{ + // note: check weapon.isDualWield and weaponPurchased before calling checkDualWield for challenges. + if ( attackerWasSprinting || ( attacker_sprint_end + 1000 > time ) ) + { + if ( attacker util::has_gung_ho_perk_purchased_and_equipped() ) + { + player AddPlayerStat( "kills_sprinting_dual_wield_and_gung_ho", 1 ); + } + } +} + +function challengeGameEndMP( data ) +{ + player = data.player; + winner = data.winner; + + if ( !isdefined( player ) ) + return; + + if ( endedEarly( winner ) ) + return; + + if ( level.teambased ) + { + winnerScore = game["teamScores"][winner]; + loserScore = getLosersTeamScores( winner ); + } + + mostKillsLeastDeaths = true; + + for ( index = 0; index < level.placement["all"].size; index++ ) + { + if ( level.placement["all"][index].deaths < player.deaths ) + { + mostKillsLeastDeaths = false; + } + if ( level.placement["all"][index].kills > player.kills ) + { + mostKillsLeastDeaths = false; + } + } + + if ( mostKillsLeastDeaths && player.kills > 0 && level.placement["all"].size > 3 ) + { + if ( level.teambased ) + { + playerIsWinner = ( player.team === winner ); + } + else + { + // see if in top 3 + playerIsWinner = ( ( level.placement["all"][0] === winner ) || ( level.placement["all"][1] === winner ) || ( level.placement["all"][2] === winner ) ); + } + + if ( playerIsWinner ) + { + player AddPlayerStat( "most_kills_least_deaths", 1 ); + } + } +} + +function killedBaseOffender( objective, weapon ) +{ + self endon( "disconnect" ); + self AddPlayerStatWithGameType( "defends", 1 ); // awards the player for being a "defender" as they killed an offender + + self.challenge_offenderkillcount++; + + if ( !isdefined( self.challenge_objectiveOffensive ) || self.challenge_objectiveOffensive != objective ) + { + self.challenge_objectiveOffensiveKillcount = 0; + } + + self.challenge_objectiveOffensiveKillcount++; + self.challenge_ObjectiveOffensive = objective; + + killstreak = killstreaks::get_from_weapon( weapon ); + + if ( isdefined( killstreak ) ) + { + switch ( killstreak ) + { + case "planemortar": + case "inventory_planemortar": + case "remote_missile": + case "inventory_remote_missile": + case "drone_strike": + case "inventory_drone_strike": + self.challenge_offenderProjectileMultiKillcount++; + break; + case "helicopter_comlink": + case "inventory_helicopter_comlink": + self.challenge_offenderComlinkKillcount++; + break; + case "combat_robot": + case "inventory_combat_robot": + self AddPlayerStat( "kill_attacker_with_robot_or_tank", 1 ); + break; + case "inventory_autoturret": + case "autoturret": + self.challenge_offenderSentryTurretKillCount++; + self AddPlayerStat( "kill_attacker_with_robot_or_tank", 1 ); + break; + } + } + + if ( self.challenge_offenderComlinkKillcount == 2 ) + { + self AddPlayerStat( "kill_2_attackers_with_comlink", 1 ); + } + + if ( self.challenge_objectiveOffensiveKillcount > 4 ) + { + self AddPlayerStatWithGameType( "multikill_5_attackers", 1 ); + self.challenge_objectiveOffensiveKillcount = 0; + } + + if ( self.challenge_offenderSentryTurretKillCount > 2 ) + { + self AddPlayerStat( "multikill_3_attackers_ai_tank", 1 ); + self.challenge_offenderSentryTurretKillCount = 0; + } + + self util::player_contract_event( "offender_kill" ); + + self waitTillTimeoutOrDeath( 4.0 ); + + if ( self.challenge_offenderkillcount > 1 ) + { + self AddPlayerStat( "double_kill_attackers", 1 ); + } + + self.challenge_offenderkillcount = 0; + + if ( self.challenge_offenderProjectileMultiKillcount >= 2 ) + { + self AddPlayerStat( "multikill_2_objective_scorestreak_projectile", 1 ); + } + + self.challenge_offenderProjectileMultiKillcount = 0; +} + +function killedBaseDefender( objective ) +{ + self endon( "disconnect" ); + self AddPlayerStatWithGameType( "offends", 1 ); // awards the player for being an "offender" as they killed a defender + + if ( !isdefined( self.challenge_objectiveDefensive ) || self.challenge_objectiveDefensive != objective ) + { + self.challenge_objectiveDefensiveKillcount = 0; + } + + self.challenge_objectiveDefensiveKillcount++; + self.challenge_ObjectiveDefensive = objective; + + + self.challenge_defenderkillcount++; + + self util::player_contract_event( "defender_kill" ); + + self waitTillTimeoutOrDeath( 4.0 ); + + if ( self.challenge_defenderkillcount > 1 ) + { + self AddPlayerStat( "double_kill_defenders", 1 ); + } + + self.challenge_defenderkillcount = 0; +} + +function waitTillTimeoutOrDeath( timeout ) +{ + self endon( "death" ); + wait( timeout ); +} + +function killstreak_30_noscorestreaks() +{ + if ( level.gameType == "dm" ) + { + self AddPlayerStat( "killstreak_30_no_scorestreaks", 1 ); + } +} + +function heroAbilityActivateNearDeath() +{ + if ( isdefined( self.heroAbility ) && self.pers["canSetSpecialistStat"] ) + { + switch( self.heroAbility.name ) + { + case "gadget_camo": + case "gadget_armor": + case "gadget_clone": + case "gadget_speed_burst": + case "gadget_vision_pulse": + case "gadget_flashback": + case "gadget_heat_wave": + self thread checkForHeroSurvival(); + break; + } + } +} + + +function checkForHeroSurvival() +{ + self endon ("death"); + self endon ("disconnect"); + + self util::waittill_any_timeout( 8.0, "challenge_survived_from_death", "disconnect" ); + + self AddPlayerStat( "death_dodger", 1 ); +} + + +function callbackEndHeroSpecialistEMP() +{ + empOwner = self emp::EnemyEMPOwner(); + if ( isdefined( empOwner ) && IsPlayer( empOwner ) ) + { + empOwner AddPlayerStat( "end_enemy_specialist_ability_with_emp", 1 ); + return; + } + + if ( isdefined( self.empStartTime ) && self.empStartTime > ( getTime() - 100 ) ) + { + if ( isdefined(self.empedBy) && IsPlayer( self.empedBy ) ) + { + self.empedBy AddPlayerStat( "end_enemy_specialist_ability_with_emp", 1 ); + return; + } + } +} + + + +function calledInComlinkChopper() +{ + self.challenge_offenderComlinkKillcount = 0; +} + + +function combat_robot_damage( eAttacker, combatRobotOwner ) +{ + if ( !isdefined( eAttacker.challenge_combatRobotAttackClientID[combatRobotOwner.clientid] ) ) + { + eAttacker.challenge_combatRobotAttackClientID[combatRobotOwner.clientid] = spawnstruct(); + } +} + + +function trackKillstreakSupportKills( victim ) +{ + if ( level.activePlayerEMPs[ self.entNum ] > 0 ) + { + self AddWeaponStat( GetWeapon( "emp" ), "kills_while_active", 1 ); + } + + if ( ( level.activePlayerUAVs[ self.entNum ] > 0 ) && ( !isdefined( level.forceradar ) || level.forceRadar == false ) ) + { + self AddWeaponStat( GetWeapon( "uav" ), "kills_while_active", 1 ); + } + + if ( level.activePlayerSatellites[ self.entNum ] > 0 ) + { + self AddWeaponStat( GetWeapon( "satellite" ), "kills_while_active", 1 ); + } + + if ( level.activePlayerCounterUAVs[ self.entNum ] > 0 ) + { + self AddWeaponStat( GetWeapon( "counteruav" ), "kills_while_active", 1 ); + } + + if ( isdefined( victim.lastMicrowavedBy ) && victim.lastMicrowavedBy == self ) + { + self AddWeaponStat( GetWeapon( "microwave_turret" ), "kills_while_active", 1 ); + } +} + +function monitorReloads() +{ + self endon("disconnect"); + self endon("killMonitorReloads"); + + while(1) + { + self waittill("reload"); + currentWeapon = self getCurrentWeapon(); + if ( currentWeapon == level.weaponNone ) + { + continue; + } + + time = getTime(); + self.lastReloadTime = time; + + if ( WeaponHasAttachment( currentWeapon, "fastreload" ) ) // aka fastmags or Fast Mags + { + self.lastFastReloadTime = time; + } + } +} + +function monitorGrenadeFire() +{ + self notify( "grenadeTrackingStart" ); + + self endon( "grenadeTrackingStart" ); + self endon( "disconnect" ); + + for (;;) + { + self waittill ( "grenade_fire", grenade, weapon ); + + if ( !isdefined( grenade ) ) + { + continue; + } + + if ( weapon.rootweapon.name == "hatchet" ) + { + self.challenge_hatchetTossCount++; + grenade.challenge_hatchetTossCount = self.challenge_hatchetTossCount; + } + if ( self issprinting() ) + { + grenade.ownerWasSprinting = true; + } + } +} + +function watchWeaponChangeComplete() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "joined_team" ); + self endon( "joined_spectators" ); + + while( 1 ) + { + self.heroWeaponKillsThisActivation = 0; + + self waittill( "weapon_change_complete" ); + } +} + +function longDistanceKillMP( weapon ) +{ + self AddWeaponStat( weapon, "longshot_kill", 1 ); + if ( self weaponHasAttachmentAndUnlocked( weapon, "extbarrel", "suppressed" ) ) + { + if ( self GetWeaponOptic( weapon ) != "" ) + { + self addPlayerStat( "long_shot_longbarrel_suppressor_optic", 1 ); + } + } +} + + +function capturedObjectiveFunction() +{ + if ( self IsBonusCardActive( 9, self.class_num ) && self util::is_item_purchased( "bonuscard_two_tacticals" ) ) + { + self AddPlayerStat( "capture_objective_tactician", 1 ); + } +} + +function watchWallRunTwoOppositeWallsNoGround() +{ + player = self; + player endon( "death" ); + player endon( "disconnect" ); + player endon( "joined_team" ); + player endon( "joined_spectators" ); + + self.wallRanTwoOppositeWallsNoGround = false; + + while ( 1 ) + { + if ( !player IsWallRunning() ) + { + self.wallRanTwoOppositeWallsNoGround = false; + player waittill( "wallrun_begin" ); + } + + ret = player util::waittill_any_return( "jump_begin", "wallrun_end", "disconnect", "joined_team", "joined_spectators" ); + if ( ret == "wallrun_end" ) + continue; + + wall_normal = player GetWallRunWallNormal(); + + player waittill( "jump_end" ); + + if ( !player IsWallRunning() ) + continue; + + last_wall_normal = wall_normal; + wall_normal = player GetWallRunWallNormal(); + + opposite_walls = ( VectorDot( wall_normal, last_wall_normal ) < -0.5 ); + if ( !opposite_walls ) + continue; + + player.wallRanTwoOppositeWallsNoGround = true; + + while ( player IsWallRunning() ) + { + ret = player util::waittill_any_return( "jump_end", "wallrun_end", "disconnect", "joined_team", "joined_spectators" ); + + if ( ret == "wallrun_end" ) + break; + } + + {wait(.05);}; + + while ( !player IsOnGround() ) + { + {wait(.05);}; + } + } +} + +function processSpecialistChallenge( statName ) +{ + if ( self.pers["canSetSpecialistStat"] ) + { + self AddSpecialistStat( statName, 1 ); + } +} + + +function flakjacketProtectedMP( weapon, attacker ) +{ + if ( weapon.name == "claymore" ) + { + self.flakJacketClaymore[ attacker.clientid ] = true; + } + + self AddPlayerStat( "survive_with_flak", 1 ); + self.challenge_lastsurvivewithflakfrom = attacker; + self.challenge_lastsurvivewithflaktime = getTime(); +} + diff --git a/mp/_claymore.csc b/mp/_claymore.csc new file mode 100644 index 0000000..ffe92fd --- /dev/null +++ b/mp/_claymore.csc @@ -0,0 +1,45 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\_util; + +#precache( "client_fx", "_t6/weapon/claymore/fx_claymore_laser" ); + +#namespace claymore; + +function autoexec __init__sytem__() { system::register("claymore",&__init__,undefined,undefined); } + +function __init__( localClientNum ) +{ + level._effect["fx_claymore_laser"] = "_t6/weapon/claymore/fx_claymore_laser"; + + callback::add_weapon_type( "claymore", &spawned ); +} + +function spawned( localClientNum ) +{ + self endon( "entityshutdown" ); + + self util::waittill_dobj(localClientNum); + + while( true ) + { + if( isdefined( self.stunned ) && self.stunned ) + { + wait( 0.1 ); + continue; + } + + + self.claymoreLaserFXId = PlayFXOnTag( localClientNum, level._effect["fx_claymore_laser"], self, "tag_fx" ); + + self waittill( "stunned" ); + stopfx(localClientNum, self.claymoreLaserFXId); + + } +} diff --git a/mp/_contracts.gsc b/mp/_contracts.gsc new file mode 100644 index 0000000..5da642e --- /dev/null +++ b/mp/_contracts.gsc @@ -0,0 +1,991 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\drown; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weapon_utils; + +#using scripts\mp\_util; +#using scripts\mp\_challenges; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_loadout; + + + +#using scripts\mp\_util; + + + + + // look for dvar loot_cryptokeyCost + // look for dvar loot_cryptokeyCost + + + + + // should coincide with contracts array size on mp_stats.ddl + + + +// contract string table and columns + + + + + + + + // mp_stats.ddl, PlayerStatsList[ ] + // mp_stats.ddl, PlayerStatsList[ ] + // mp_stats.ddl + +// table index values from mp_contractTable.csv + +// weekly contracts + + + + + + + + + +// daily contracts + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// special contracts + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#namespace contracts; + +#precache( "eventstring", "mp_daily_challenge_complete" ); +#precache( "eventstring", "mp_weekly_challenge_complete" ); +#precache( "eventstring", "mp_special_contract_complete" ); + +function autoexec __init__sytem__() { system::register("contracts",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &start_gametype ); + + /# level thread watch_contract_debug(); #/ +} + +function start_gametype() +{ + if ( !isdefined( level.ChallengesCallbacks ) ) + { + level.ChallengesCallbacks = []; + } + + util::init_player_contract_events(); // must always initialize + + waittillframeend; + + if ( can_process_contracts() ) + { + /# execdevgui( "devgui/mp/mp_devgui_contracts" ); #/ + + challenges::registerChallengesCallback( "playerKilled",&contract_kills ); + challenges::registerChallengesCallback( "gameEnd",&contract_game_ended ); + globallogic_score::registerContractWinEvent( &contract_win ); + scoreevents::register_hero_ability_kill_event( &on_hero_ability_kill ); + scoreevents::register_hero_ability_multikill_event( &on_hero_ability_multikill ); + scoreevents::register_hero_weapon_multikill_event( &on_hero_weapon_multikill ); + + util::register_player_contract_event( "score", &on_player_score, 1 ); + util::register_player_contract_event( "killstreak_score", &on_killstreak_score, 2 ); + util::register_player_contract_event( "offender_kill", &on_offender_kill ); + util::register_player_contract_event( "defender_kill", &on_defender_kill ); + util::register_player_contract_event( "headshot", &on_headshot_kill ); + util::register_player_contract_event( "killed_hero_ability_enemy", &on_killed_hero_ability_enemy ); + util::register_player_contract_event( "killed_hero_weapon_enemy", &on_killed_hero_weapon_enemy ); + util::register_player_contract_event( "earned_specialist_ability_medal", &on_hero_ability_medal ); + + // globallogic::registerOtherLootXPAwards( &award_loot_xp ); // intentionally commented out, will use mp_loot_xp_due instead for now + } + + callback::on_connect( &on_player_connect ); +} + +function on_killed_hero_ability_enemy() +{ + self add_stat( 1014 ); +} + +function on_killed_hero_weapon_enemy() +{ + self add_stat( 1014 ); +} + +function on_player_connect() +{ + player = self; + + if ( can_process_contracts() ) + { + player setup_player_contracts(); + } +} + +function can_process_contracts() +{ + if ( GetDvarInt( "contracts_enabled_mp", 1 ) == 0 ) // mp contracts kill switch + return false; + + return challenges::canProcessChallenges(); +} + +function setup_player_contracts() +{ + player = self; + + player.pers["contracts"] = []; + + // no need to setup active contracts for bots + if ( player util::is_bot() ) + return; + + for( slot = 0; slot < 10; slot++ ) + { + if ( get_contract_stat( slot, "active" ) && !get_contract_stat( slot, "award_given" ) ) + { + contract_index = get_contract_stat( slot, "index" ); + player.pers["contracts"][contract_index] = SpawnStruct(); + player.pers["contracts"][contract_index].slot = slot; + table_row = TableLookupRowNum( "gamedata/tables/mp/mp_contractTable.csv", 0, contract_index ); + player.pers["contracts"][contract_index].table_row = table_row; + player.pers["contracts"][contract_index].target_value = int( TableLookupColumnForRow( "gamedata/tables/mp/mp_contractTable.csv", table_row, 2 ) ); + player.pers["contracts"][contract_index].calling_card_stat = TableLookupColumnForRow( "gamedata/tables/mp/mp_contractTable.csv", table_row, 7 ); + player.pers["contracts"][contract_index].weapon_camo_stat = TableLookupColumnForRow( "gamedata/tables/mp/mp_contractTable.csv", table_row, 8 ); + player.pers["contracts"][contract_index].absolute_stat_path = TableLookupColumnForRow( "gamedata/tables/mp/mp_contractTable.csv", table_row, 9 ); + } + } +} + +/# +function watch_contract_debug() +{ + level notify( "watch_contract_debug_singleton" ); + level endon( "watch_contract_debug_singleton" ); + + level endon( "game_ended" ); + + while( 1 ) + { + if ( GetDvarInt( "scr_contract_deactivate_all_slots" ) > 0 ) + { + if ( isdefined( level.players ) ) + { + new_index = GetDvarInt( "scr_contract_new_test_index", 0 ); + + foreach( player in level.players ) + { + if ( !isdefined( player ) ) + continue; + + if ( player util::is_bot() ) + continue; + + for ( slot = 0; slot < 10; slot++ ) + { + player set_contract_stat( slot, "active", 0 ); + } + + IPrintLn( "All contract slots deactivated for " + player.name ); + + player setup_player_contracts(); + } + } + + SetDvar( "scr_contract_deactivate_all_slots", 0 ); + } + + if ( GetDvarInt( "scr_contract_new_test_index", 0 ) > 0 ) + { + if ( isdefined( level.players ) ) + { + new_index = GetDvarInt( "scr_contract_new_test_index", 0 ); + + foreach( player in level.players ) + { + if ( !isdefined( player ) ) + continue; + + if ( player util::is_bot() ) + continue; + + test_slot = GetDvarInt( "scr_contract_debug_slot", 10 - 1 ); + + player set_contract_stat( test_slot, "active", 1 ); + player set_contract_stat( test_slot, "index", new_index ); + player set_contract_stat( test_slot, "progress", 0 ); + player set_contract_stat( test_slot, "award_given", 0 ); + player setup_player_contracts(); + + IPrintLn( "Contract Slot " + test_slot + " table index set to " + new_index + " for " + player.name + ". Progress reset." ); + } + } + + SetDvar( "scr_contract_new_test_index", 0 ); + } + + if ( GetDvarInt( "scr_contract_deactivate_debug_slot", 0 ) > 0 ) + { + if ( isdefined( level.players ) ) + { + test_slot = GetDvarInt( "scr_contract_debug_slot", 10 - 1 ); + IPrintLn( "Contract Debug Off" ); + + foreach( player in level.players ) + { + if ( !isdefined( player ) ) + continue; + + if ( player util::is_bot() ) + continue; + + if ( test_slot >= 3 ) + { + player set_contract_stat( test_slot, "active", 0 ); + player setup_player_contracts(); + + IPrintLn( "Contract Slot " + test_slot + " deactivated for " + player.name ); + } + else + { + IPrintLn( "Contract Slot " + test_slot + " left active for " + player.name ); + } + } + } + + SetDvar( "scr_contract_deactivate_debug_slot", 0 ); + } + + if ( GetDvarInt( "scr_contract_msg_front_end_only", 0 ) > 0 ) + { + IPrintLn( "Assign Slot menu(s) only available in FrontEnd." ); + SetDvar( "scr_contract_msg_front_end_only", 0 ); + } + + if ( GetDvarInt( "scr_contract_msg_debug_on", 0 ) > 0 ) + { + IPrintLn( "Contracts Debug On. No slots altered." ); + SetDvar( "scr_contract_msg_debug_on", 0 ); + } + + wait 0.5; + } +} +#/ + +function is_contract_active( challenge_index ) +{ + if ( !isPlayer( self ) ) + return false; + + if ( !isdefined( self.pers["contracts"] ) ) + return false; + + if ( !isdefined( self.pers["contracts"][challenge_index] ) ) + return false; + + // sanity check... better than causing an SRE + if ( self.pers["contracts"][challenge_index].table_row == -1 ) + return false; + + return true; +} + +function on_hero_ability_kill( ability, victimAbility ) +{ + player = self; + + if ( !isdefined( player ) || !isplayer( player ) ) + return; +} + +function on_hero_ability_medal() +{ + player = self; + + if ( !isdefined( player ) || !isplayer( player ) ) + return; + + player add_stat( 1013 ); + player add_stat( 3 ); +} + +function on_hero_ability_multikill( killcount, ability ) +{ + player = self; + + if ( !isdefined( player ) || !isplayer( player ) ) + return; +} + +function on_hero_weapon_multikill( killcount, weapon ) +{ + player = self; + + if ( !isdefined( player ) || !isplayer( player ) ) + return; +} + +function on_player_score( delta_score ) +{ + self add_stat( 1009, delta_score ); + self add_stat( 5, delta_score ); +} + +function on_killstreak_score( delta_score, killstreak_purchased ) +{ + if ( killstreak_purchased ) + { + self add_stat( 1011, delta_score ); + } +} + +function contract_kills( data ) +{ + victim = data.victim; + attacker = data.attacker; + player = attacker; + weapon = data.weapon; + time = data.time; + + if ( !isdefined( weapon ) || ( weapon == level.weaponNone ) ) + return; + + if ( !isdefined( player ) || !isplayer( player ) ) + return; + + player add_stat( 1015 ); + player add_stat( 4 ); + + if ( weapon.isHeroWeapon === true ) + { + player add_stat( 1012 ); + player add_stat( 7 ); + player add_stat( 3006 ); + } + + isKillstreak = isdefined( data.eInflictor ) && isdefined( data.eInflictor.killstreakid ); + if ( !isKillstreak && isdefined( level.isKillstreakWeapon ) ) + { + isKillstreakWeapon = [[level.isKillstreakWeapon]]( weapon ); + } + + if ( isKillstreak || ( isKillstreakWeapon === true ) ) + { + player add_stat( 1010 ); + player add_stat( 8 ); + } + + statItemIndex = weapon.statIndex; + + if ( player isItemPurchased( statItemIndex ) ) + { + weaponClass = util::getWeaponClass( weapon ); + + switch ( weaponClass ) // aka group in mp_statsTable + { + case "weapon_assault": + player add_stat( 1019 ); + player add_stat( 3001 ); + break; + + case "weapon_smg": + player add_stat( 1020 ); + player add_stat( 3000 ); + break; + + case "weapon_sniper": + player add_stat( 1021 ); + player add_stat( 3004 ); + break; + + case "weapon_lmg": + player add_stat( 1022 ); + player add_stat( 3003 ); + break; + + case "weapon_cqb": + player add_stat( 1023 ); + player add_stat( 3002 ); + break; + + case "weapon_pistol": + player add_stat( 1024 ); + break; + + case "weapon_knife": + player add_stat( 3005 ); + break; + + default: + break; + } + + total_unlocked = player GetTotalUnlockedWeaponAttachments( weapon ); + if ( total_unlocked >= 4 ) + { + player add_stat( 1025 ); + } + } +} + +function add_stat( contract_index, delta ) +{ + if ( self is_contract_active( contract_index ) ) + { + self add_active_stat( contract_index, delta ); + } +} + +function add_active_stat( contract_index, delta = 1 ) +{ + slot = self.pers["contracts"][contract_index].slot; + target_value = self.pers["contracts"][contract_index].target_value; + +/# + if ( GetDvarInt( "scr_contract_debug_multiplier", 0 ) > 0 ) + delta *= GetDvarInt( "scr_contract_debug_multiplier", 1 ); +#/ + + old_progress = get_contract_stat( slot, "progress" ); + new_progress = old_progress + delta; + + if ( new_progress > target_value ) + new_progress = target_value; + + if ( new_progress != old_progress ) + self set_contract_stat( slot, "progress", new_progress ); + + just_completed = false; + if ( old_progress < target_value && target_value <= new_progress ) + { + just_completed = true; + + // notify contract achieved + event = &"mp_weekly_challenge_complete"; + display_rewards = false; + + if ( slot == 2 ) + { + event = &"mp_daily_challenge_complete"; + display_rewards = true; + + self award_loot_xp_due( award_daily_contract() ); + self set_contract_stat( 2, "award_given", 1 ); + } + else if ( slot == 0 || slot == 1 ) + { + other_slot = 1; + if ( slot == 1 ) + { + other_slot = 0; + } + + foreach ( c_index,c_data in self.pers["contracts"] ) + { + if ( c_data.slot == other_slot ) + { + if ( c_data.target_value <= get_contract_stat( other_slot, "progress" ) ) + { + display_rewards = true; + + self award_loot_xp_due( award_weekly_contract() ); + self set_contract_stat( 0, "award_given", 1 ); + self set_contract_stat( 1, "award_given", 1 ); + } + + break; + } + } + } + else if ( slot == 3 ) + { + event = &"mp_special_contract_complete"; + display_rewards = true; + + absolute_stat_path = self.pers["contracts"][contract_index].absolute_stat_path; + if ( absolute_stat_path != "" ) + { + set_contract_award_stat_from_path( absolute_stat_path, true ); + } + + calling_card_stat = self.pers["contracts"][contract_index].calling_card_stat; + if ( calling_card_stat != "" ) + { + set_contract_award_stat( "calling_card", calling_card_stat ); + } + + weapon_camo_stat = self.pers["contracts"][contract_index].weapon_camo_stat; + if ( weapon_camo_stat != "" ) + { + set_contract_award_stat( "weapon_camo", weapon_camo_stat ); + } + + self set_contract_stat( 3, "award_given", 1 ); + } + +/# + test_slot = GetDvarInt( "scr_contract_debug_slot", 10 - 1 ); + if ( slot == test_slot ) + { + if ( contract_index >= 1000 && contract_index <= 2999 ) + { + event = &"mp_daily_challenge_complete"; + } + display_rewards = true; + } +#/ + + self LUINotifyEvent( event, 2, contract_index, display_rewards ); + } + +/# + if ( GetDvarInt( "scr_contract_debug", 0 ) > 0 ) + { + IPrintLn( "Contract Slot "+ slot + " table index " + contract_index + " progress: " + new_progress + "/" + target_value ); + } +#/ +} + +function get_contract_stat( slot, stat_name ) +{ + return self GetDStat( "contracts", slot, stat_name ); +} + +function set_contract_stat( slot, stat_name, stat_value ) +{ + return self SetDStat( "contracts", slot, stat_name, stat_value ); +} + +function set_contract_award_stat( award_type, stat_name, stat_value = 1 ) +{ + // award_type is unused for now as we use PlayerStatsList for now + + return self AddPlayerStat( stat_name, stat_value ); +} + +function set_contract_award_stat_from_path( stat_path, stat_value ) +{ + stat_path_array = StrTok( stat_path, " " ); + + string_path_1 = ""; + string_path_2 = ""; + string_path_3 = ""; + string_path_4 = ""; + string_path_5 = ""; + + switch( stat_path_array.size ) + { + case 5: + string_path_5 = stat_path_array[4]; + if( StrIsNumber( string_path_5 ) ) + { + string_path_5 = Int( string_path_5 ); + } + case 4: + string_path_4 = stat_path_array[3]; + if( StrIsNumber( string_path_4 ) ) + { + string_path_4 = Int( string_path_4 ); + } + case 3: + string_path_3 = stat_path_array[2]; + if( StrIsNumber( string_path_3 ) ) + { + string_path_3 = Int( string_path_3 ); + } + case 2: + string_path_2 = stat_path_array[1]; + if( StrIsNumber( string_path_2 ) ) + { + string_path_2 = Int( string_path_2 ); + } + case 1: + string_path_1 = stat_path_array[0]; + if( StrIsNumber( string_path_1 ) ) + { + string_path_1 = Int( string_path_1 ); + } + } + + switch( stat_path_array.size ) + { + case 1: + return self SetDStat( string_path_1, stat_value ); + break; + case 2: + return self SetDStat( string_path_1, string_path_2, stat_value ); + break; + case 3: + return self SetDStat( string_path_1, string_path_2, string_path_3, stat_value ); + break; + case 4: + return self SetDStat( string_path_1, string_path_2, string_path_3, string_path_4, stat_value ); + break; + case 5: + return self SetDStat( string_path_1, string_path_2, string_path_3, string_path_4, string_path_5, stat_value ); + break; + default: + AssertMsg( "Stat path depth of " + stat_path_array.size + " is too large. Limit to 5 deep" ); + break; + } +} + +function award_loot_xp_due( amount ) +{ + if ( !isdefined( self ) ) + return; + + if ( amount <= 0 ) + return; + + current_amount = (isdefined(self GetDStat( "mp_loot_xp_due" ))?self GetDStat( "mp_loot_xp_due" ):0); + new_amount = current_amount + amount; + self SetDStat( "mp_loot_xp_due", new_amount ); +} + +function get_hero_weapon_mask( attacker, weapon ) +{ + if ( !isdefined( weapon ) ) + return 0; + + if ( isdefined( weapon.isHeroWeapon ) && !weapon.isHeroWeapon ) + return 0; + + switch( weapon.name ) + { + case "hero_minigun": + case "hero_minigun_body3": + return 1; // note: heroWeaponMask needs to stay unique and consistent for function across TUs and FFOTDs + break; + case "hero_flamethrower": + return 1 << 1; + break; + case "hero_lightninggun": + case "hero_lightninggun_arc": + return 1 << 2; + break; + case "hero_chemicalgelgun": + case "hero_firefly_swarm": + return 1 << 3; + break; + case "hero_pineapplegun": + case "hero_pineapple_grenade": + return 1 << 4; + break; + case "hero_armblade": + return 1 << 5; + break; + case "hero_bowlauncher": + case "hero_bowlauncher2": + case "hero_bowlauncher3": + case "hero_bowlauncher4": + return 1 << 6; + break; + case "hero_gravityspikes": + return 1 << 7; + break; + case "hero_annihilator": + return 1 << 8; + break; + default: + return 0; + } +} + +function get_hero_ability_mask( ability ) +{ + if ( !isdefined( ability ) ) + return 0; + + switch( ability.name ) + { + case "gadget_clone": + return 1; // note: heroAbilityMask needs to stay unique and consistent for functions across TUs and FFOTDs + break; + case "gadget_heat_wave": + return 1 << 1; + break; + case "gadget_flashback": + return 1 << 2; + break; + case "gadget_resurrect": + return 1 << 3; + break; + case "gadget_armor": + return 1 << 4; + break; + case "gadget_camo": + return 1 << 5; + break; + case "gadget_vision_pulse": + return 1 << 6; + break; + case "gadget_speed_burst": + return 1 << 7; + break; + case "gadget_combat_efficiency": + return 1 << 8; + break; + default: + return 0; + } +} + +function contract_game_ended( data ) +{ + // TODO: award challenge award here: maybe +} + +function contract_win( winner ) +{ + winner add_stat( 1000 ); + winner add_stat( 1 ); + winner add_stat( 3007 ); + winner add_stat( 3008 ); + winner add_stat( 3009 ); + winner add_stat( 3010 ); + winner add_stat( 3011 ); + winner add_stat( 3012 ); + winner add_stat( 3013 ); + winner add_stat( 3014 ); + winner add_stat( 3015 ); + winner add_stat( 3016 ); + winner add_stat( 3017 ); + winner add_stat( 3018 ); + winner add_stat( 3019 ); + winner add_stat( 3020 ); + winner add_stat( 3021 ); + winner add_stat( 3022 ); + winner add_stat( 3023 ); + winner add_stat( 3024 ); + winner add_stat( 3025 ); + winner add_stat( 3026 ); + winner add_stat( 3027 ); + winner add_stat( 3028 ); + + if ( util::is_objective_game( level.gametype ) ) + { + winner add_stat( 2 ); + } + + if ( IsArenaMode() ) + { + winner add_stat( 1001 ); + } + + gametype_win( winner ); +} + +function gametype_win( winner ) +{ + switch ( level.gametype ) + { + case "tdm": + winner add_stat( 1002 ); + break; + + case "ball": + winner add_stat( 1003 ); + break; + + case "escort": + winner add_stat( 1004 ); + break; + + case "conf": + winner add_stat( 1005 ); + break; + + case "sd": + winner add_stat( 1006 ); + break; + + case "koth": + winner add_stat( 1007 ); + break; + + case "dom": + winner add_stat( 1008 ); + break; + + case "ctf": + winner add_stat( 1026 ); + break; + + case "dem": + winner add_stat( 1027 ); + break; + + case "dm": + winner add_stat( 1028 ); + break; + + case "clean": + winner add_stat( 1029 ); + break; + + default: + break; + } +} + +function on_offender_kill() +{ + self add_stat( 1018 ); + self add_stat( 6 ); +} + +function on_defender_kill() +{ + self add_stat( 1017 ); + self add_stat( 6 ); +} + +function on_headshot_kill() +{ + self add_stat( 1016, 1 ); +} + +function award_loot_xp() +{ + player = self; + + if ( !isdefined( player.pers["contracts"] ) ) + return 0; + + loot_xp = 0; + + // + // daily contract + // + daily_slot = 2; + if ( get_contract_stat( daily_slot, "active" ) && !get_contract_stat( daily_slot, "award_given" ) ) + { + if ( contract_slot_met( daily_slot ) ) + { + loot_xp += player award_daily_contract(); + player set_contract_stat( daily_slot, "award_given", 1 ); + } + } + + // + // weekly contract + // + weekly_slot_A = 0; + weekly_slot_B = 1; + if ( get_contract_stat( weekly_slot_A, "active" ) && !get_contract_stat( weekly_slot_A, "award_given" ) && + get_contract_stat( weekly_slot_B, "active" ) && !get_contract_stat( weekly_slot_B, "award_given" ) ) + { + if ( contract_slot_met( weekly_slot_A ) && contract_slot_met( weekly_slot_B ) ) + { + loot_xp += player award_weekly_contract(); + player set_contract_stat( weekly_slot_A, "award_given", 1 ); + player set_contract_stat( weekly_slot_B, "award_given", 1 ); + } + } + + return loot_xp; +} + +function contract_slot_met( slot ) +{ + player = self; + + contract_index = get_contract_stat( slot, "index" ); + + if ( !isdefined( player.pers["contracts"][contract_index] ) ) + return false; + + progress = player get_contract_stat( slot, "progress" ); + target_value = player.pers["contracts"][contract_index].target_value; + + return ( progress >= target_value ); +} + +function award_daily_contract() +{ + return GetDvarInt( "daily_contract_cryptokey_reward_count", 10 ) * GetDvarInt( "loot_cryptokeyCost", 100 ); +} + +function award_weekly_contract() +{ + self award_blackjack_contract(); + + return GetDvarInt( "weekly_contract_cryptokey_reward_count", 30 ) * GetDvarInt( "loot_cryptokeyCost", 100 ); +} + +function award_blackjack_contract() +{ + contract_count = self GetDStat( "blackjack_contract_count" ); + reward_count = GetDvarInt( "weekly_contract_blackjack_contract_reward_count", 1 ); + self SetDStat( "blackjack_contract_count", contract_count + reward_count ); +} diff --git a/mp/_contracts.gsh b/mp/_contracts.gsh new file mode 100644 index 0000000..bffb7f9 --- /dev/null +++ b/mp/_contracts.gsh @@ -0,0 +1,16 @@ +// +// mp contract header +// + + + + + + + + + + + + + diff --git a/mp/_ctf.csc b/mp/_ctf.csc new file mode 100644 index 0000000..5573fd7 --- /dev/null +++ b/mp/_ctf.csc @@ -0,0 +1,32 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; + + + + +#namespace client_flag; + +function autoexec __init__sytem__() { system::register("client_flag",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "scriptmover", "ctf_flag_away", 1, 1, "int", &setCTFAway, !true, !true ); +} + +function setCTFAway( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + team = self.team; + + SetFlagAsAway( localClientNum, team, newVal ); + + self thread clearCTFAway( localClientNum, team ); +} + +function clearCTFAway( localClientNum, team ) +{ + self waittill( "entityshutdown" ); + + SetFlagAsAway( localClientNum, team, 0 ); +} \ No newline at end of file diff --git a/mp/_decoy.csc b/mp/_decoy.csc new file mode 100644 index 0000000..cbfdd49 --- /dev/null +++ b/mp/_decoy.csc @@ -0,0 +1,16 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_decoy; + + + +#namespace decoy; + +function autoexec __init__sytem__() { system::register("decoy",&__init__,undefined,undefined); } + +function __init__() +{ + decoy::init_shared(); +} diff --git a/mp/_decoy.gsc b/mp/_decoy.gsc new file mode 100644 index 0000000..dda15a9 --- /dev/null +++ b/mp/_decoy.gsc @@ -0,0 +1,19 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_decoy; + + + +#using scripts\mp\_util; + +#namespace decoy; + +function autoexec __init__sytem__() { system::register("decoy",&__init__,undefined,undefined); } + +function __init__() +{ + decoy::init_shared(); +} \ No newline at end of file diff --git a/mp/_destructible.csc b/mp/_destructible.csc new file mode 100644 index 0000000..2e95e89 --- /dev/null +++ b/mp/_destructible.csc @@ -0,0 +1,48 @@ +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; + + + + + + +#namespace destructible; + +function autoexec __init__sytem__() { system::register("destructible",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "scriptmover", "start_destructible_explosion", 1, 10, "int", &doExplosion, !true, !true ); +} + +function playGrenadeRumble( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + PlayRumbleOnPosition( localClientNum, "grenade_rumble", self.origin ); + GetLocalPlayer( localClientNum ) Earthquake( 0.5, 0.5, self.origin, 800 ); +} + +function doExplosion( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if( newVal == 0 ) + { + return; + } + + physics_explosion = false; + + if( newVal & ( 1 << ( 10 - 1 ) ) ) + { + physics_explosion = true; + + newVal -= ( 1 << ( 10 - 1 ) ) ; + } + + physics_force = 0.3; + + if( physics_explosion ) + { + PhysicsExplosionSphere( localClientNum, self.origin, newVal, newVal - 1, physics_force, 25, 400 ); + } + + playGrenadeRumble( localClientNum, self.origin ); +} \ No newline at end of file diff --git a/mp/_destructible.gsc b/mp/_destructible.gsc new file mode 100644 index 0000000..76ac037 --- /dev/null +++ b/mp/_destructible.gsc @@ -0,0 +1,610 @@ +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic_player; + +#using scripts\mp\_challenges; + + + +#namespace destructible; + +function autoexec __init__sytem__() { system::register("destructible",&__init__,undefined,undefined); } + +#using_animtree ( "mp_vehicles" ); + +function __init__() +{ + clientfield::register( "scriptmover", "start_destructible_explosion", 1, 10, "int" ); + + level.destructible_callbacks = []; + destructibles = GetEntArray( "destructible", "targetname" ); + + // since this is globally run, only continue if we have a destructible in the level + if( destructibles.size <= 0 ) + { + return; + } + + // array::thread_all( destructibles,&destructible_think ); + + for ( i = 0; i < destructibles.size; i++ ) + { + if ( GetSubStr( destructibles[i].destructibledef, 0, 4 ) == "veh_" ) + { + destructibles[i] thread car_death_think(); + destructibles[i] thread car_grenade_stuck_think(); + } + else if ( destructibles[i].destructibledef == "fxdest_upl_metal_tank_01" ) + { + destructibles[i] thread tank_grenade_stuck_think(); + } + } + + init_explosions(); +} + +function init_explosions() +{ + level.explosion_manager = SpawnStruct(); + level.explosion_manager.count = 0; + level.explosion_manager.a_explosions = []; + + i = 0; + while( i < 32 ) + { + sExplosion = Spawn( "script_model", (0,0,0) ); + if ( !isdefined( level.explosion_manager.a_explosions ) ) level.explosion_manager.a_explosions = []; else if ( !IsArray( level.explosion_manager.a_explosions ) ) level.explosion_manager.a_explosions = array( level.explosion_manager.a_explosions ); level.explosion_manager.a_explosions[level.explosion_manager.a_explosions.size]=sExplosion;; + i++; + } +} + +function get_unused_explosion() +{ + foreach( explosion in level.explosion_manager.a_explosions ) + { + if( !( isdefined( explosion.in_use ) && explosion.in_use ) ) + { + return explosion; + } + } + return level.explosion_manager.a_explosions[0]; +} + +//assumes explosion radius will never be greater than 2 ^ ( DESTRUCTIBLE_CLIENTFIELD_NUM_BITS ) +function physics_explosion_and_rumble( origin, radius, physics_explosion ) +{ + sExplosion = get_unused_explosion(); + sExplosion.in_use = true; + sExplosion.origin = origin; + + assert( radius <= pow( 2, 10 ) - 1 ); + + if( ( isdefined( physics_explosion ) && physics_explosion ) ) + { + radius += ( 1 << ( 10 - 1 ) ); + } + + {wait(.05);}; + + sExplosion clientfield::set( "start_destructible_explosion", radius ); + + sExplosion.in_use = false; +} + +function event_callback( destructible_event, attacker, weapon ) // self == the destructible object (like the car or barrel) +{ + explosion_radius = 0; + if(IsSubStr(destructible_event, "explode") && destructible_event != "explode") + { + tokens = StrTok(destructible_event, "_"); + explosion_radius = tokens[1]; + + if(explosion_radius == "sm") + { + explosion_radius = 150; + } + else if(explosion_radius == "lg") + { + explosion_radius = 450; + } + else + { + explosion_radius = Int(explosion_radius); + } + + destructible_event = "explode_complex"; // use a different explosion function + } + + if( IsSubStr( destructible_event, "explosive" ) ) + { + tokens = StrTok(destructible_event, "_"); + explosion_radius_type = tokens[3]; + + if(explosion_radius_type == "small") + { + explosion_radius = 150; + } + else if(explosion_radius_type == "large") + { + explosion_radius = 450; + } + else + { + explosion_radius = 300; + } + } + + if ( IsSubStr( destructible_event, "simple_timed_explosion" ) ) + { + self thread simple_timed_explosion( destructible_event, attacker ); + return; + } + + switch ( destructible_event ) + { + case "destructible_car_explosion": + self car_explosion( attacker ); + if ( isdefined( weapon ) ) + { + self.destroyingWeapon = weapon; + } + break; + + case "destructible_car_fire": + level thread battlechatter::on_player_near_explodable( self, "car" ); + self thread car_fire_think(attacker); + if ( isdefined( weapon ) ) + { + self.destroyingWeapon = weapon; + } + break; + + case "explode": + self thread simple_explosion( attacker ); + break; + + case "explode_complex": + self thread complex_explosion( attacker, explosion_radius ); + break; + + case "destructible_explosive_incendiary_small": + case "destructible_explosive_incendiary_large": + self explosive_incendiary_explosion( attacker, explosion_radius, false ); + if ( isdefined( weapon ) ) + { + self.destroyingWeapon = weapon; + } + break; + + case "destructible_explosive_electrical_small": + case "destructible_explosive_electrical_large": + self explosive_electrical_explosion( attacker, explosion_radius, false ); + if ( isdefined( weapon ) ) + { + self.destroyingWeapon = weapon; + } + break; + + case "destructible_explosive_concussive_small": + case "destructible_explosive_concussive_large": + self explosive_concussive_explosion( attacker, explosion_radius, false ); + if ( isdefined( weapon ) ) + { + self.destroyingWeapon = weapon; + } + break; + + default: + //iprintln( "_destructible.gsc: unknown destructible event: '" + destructible_event + "'" ); + break; + } + + if ( isdefined( level.destructible_callbacks[ destructible_event ] ) ) + { + self thread [[level.destructible_callbacks[ destructible_event ]]]( destructible_event, attacker, weapon ); + } +} + +function simple_explosion( attacker ) +{ + if ( ( isdefined( self.exploded ) && self.exploded ) ) + { + return; + } + + self.exploded = true; + + offset = (0, 0, 5); + self RadiusDamage( self.origin + offset, 256, 300, 75, attacker, "MOD_EXPLOSIVE", GetWeapon( "explodable_barrel" ) ); + physics_explosion_and_rumble( self.origin, 255, true ); + + if ( isdefined( attacker ) ) + { + self DoDamage( self.health + 10000, self.origin + offset, attacker ); + } + else + { + self DoDamage( self.health + 10000, self.origin + offset ); + } +} + +function simple_timed_explosion( destructible_event, attacker ) +{ + self endon( "death" ); + + wait_times = []; + + str = GetSubStr( destructible_event, 23 /* strlen( simple_timed_explosion ) */ ); + tokens = StrTok( str, "_" ); + + for ( i = 0; i < tokens.size; i++ ) + { + wait_times[ wait_times.size ] = Int( tokens[i] ); + } + + if ( wait_times.size <= 0 ) + { + wait_times[ 0 ] = 5; + wait_times[ 1 ] = 10; + } + + wait( RandomIntRange( wait_times[0], wait_times[1] ) ); + simple_explosion( attacker ); +} + +function complex_explosion( attacker, max_radius ) +{ + offset = (0, 0, 5); + + if( isdefined( attacker ) ) + { + self RadiusDamage( self.origin + offset, max_radius, 300, 100, attacker ); + } + else + { + self RadiusDamage( self.origin + offset, max_radius, 300, 100 ); + } + + physics_explosion_and_rumble( self.origin, max_radius, true ); + if( isdefined( attacker ) ) + { + self DoDamage( 20000, self.origin + offset, attacker ); + } + else + { + self DoDamage( 20000, self.origin + offset ); + } +} + + +function car_explosion( attacker, physics_explosion ) +{ + if ( isdefined( self.car_dead ) && self.car_dead ) + { + // prevents recursive entry caused by DoDamage call below + return; + } + + if ( !isdefined( physics_explosion ) ) + { + physics_explosion = true; + } + + self notify( "car_dead" ); + self.car_dead = true; + + if ( isdefined( attacker )) + { + self RadiusDamage( self.origin, 256, 300, 75, attacker, "MOD_EXPLOSIVE", GetWeapon( "destructible_car" ) ); + } + else + { + self RadiusDamage( self.origin, 256, 300, 75 ); + } + + physics_explosion_and_rumble( self.origin, 255, physics_explosion ); + + if ( isdefined ( attacker ) ) + attacker thread challenges::destroyed_car(); + + level.globalCarsDestroyed++; + if ( isdefined ( attacker ) ) + { + self DoDamage( self.health + 10000, self.origin + (0, 0, 1), attacker ); + } + else + { + self DoDamage( self.health + 10000, self.origin + (0, 0, 1)); + } + + self MarkDestructibleDestroyed(); +} + +function tank_grenade_stuck_think() +{ + self endon( "destructible_base_piece_death" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "grenade_stuck", missile ); + + if ( !IsDefined( missile ) || !IsDefined( missile.model ) ) + { + continue; + } + + if ( missile.model == "t5_weapon_crossbow_bolt" || missile.model == "t6_wpn_grenade_semtex_projectile" || missile.model == "wpn_t7_c4_world" ) + { + self thread tank_grenade_stuck_explode( missile ); + } + } +} + +function tank_grenade_stuck_explode( missile ) +{ + self endon( "destructible_base_piece_death" ); + self endon( "death" ); + + owner = GetMissileOwner( missile ); + + if ( IsDefined( owner ) && missile.model == "wpn_t7_c4_world" ) + { + owner endon( "disconnect" ); + owner endon( "weapon_object_destroyed" ); + missile endon( "picked_up" ); + + missile thread tank_hacked_c4( self ); + } + + missile waittill( "explode" ); + + if ( IsDefined( owner ) ) + { + self DoDamage( self.health + 10000, self.origin + (0, 0, 1), owner ); + } + else + { + self DoDamage( self.health + 10000, self.origin + (0, 0, 1) ); + } +} + +function tank_hacked_c4( tank ) +{ + tank endon( "destructible_base_piece_death" ); + tank endon( "death" ); + self endon( "death" ); + + self waittill( "hacked" ); + + self notify( "picked_up" ); + tank thread tank_grenade_stuck_explode( self ); +} + +function car_death_think() +{ + self endon( "car_dead" ); + self.car_dead = false; + + self thread car_death_notify(); + + self waittill( "destructible_base_piece_death", attacker ); + + if ( isdefined( self ) ) + { + self thread car_explosion( attacker, false ); + } +} + +function car_grenade_stuck_think() +{ + self endon( "destructible_base_piece_death" ); + self endon( "car_dead" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "grenade_stuck", missile ); + + if ( !isdefined( missile ) || !isdefined( missile.model ) ) + { + continue; + } + + if ( missile.model == "t5_weapon_crossbow_bolt" || missile.model == "t6_wpn_grenade_semtex_projectile" || missile.model == "wpn_t7_c4_world" ) + { + self thread car_grenade_stuck_explode( missile ); + } + } +} + +function car_grenade_stuck_explode( missile ) +{ + self endon( "destructible_base_piece_death" ); + self endon( "car_dead" ); + self endon( "death" ); + + owner = GetMissileOwner( missile ); + + if ( isdefined( owner ) && missile.model == "wpn_t7_c4_world" ) + { + owner endon( "disconnect" ); + owner endon( "weapon_object_destroyed" ); + missile endon( "picked_up" ); + + missile thread car_hacked_c4( self ); + } + + missile waittill( "explode" ); + + if ( isdefined( owner ) ) + { + self DoDamage( self.health + 10000, self.origin + (0, 0, 1), owner ); + } + else + { + self DoDamage( self.health + 10000, self.origin + (0, 0, 1) ); + } +} + +function car_hacked_c4( car ) +{ + car endon( "destructible_base_piece_death" ); + car endon( "car_dead" ); + car endon( "death" ); + self endon( "death" ); + + self waittill( "hacked" ); + + self notify( "picked_up" ); + car thread car_grenade_stuck_explode( self ); +} + +function car_death_notify() +{ + self endon( "car_dead" ); + + self waittill( "death", attacker ); + self notify( "destructible_base_piece_death", attacker ); +} + +function car_fire_think(attacker) +{ + self endon( "death" ); + + wait( RandomIntRange( 7, 10 ) ); + + self thread car_explosion( attacker ); +} + +function CodeCallback_DestructibleEvent( event, param1, param2, param3, param4 ) +{ + if( event == "broken" ) + { + notify_type = param1; + attacker = param2; + piece = param3; + weapon = param4; + + event_callback( notify_type, attacker, weapon ); + + self notify( event, notify_type, attacker ); + } + else if( event == "breakafter" ) + { + piece = param1; + time = param2; + damage = param3; + self thread breakAfter( time, damage, piece ); + } +} + +function breakAfter( time, damage, piece ) +{ + self notify( "breakafter" ); + self endon( "breakafter" ); + + wait time; + + // this does not work in mp. DoDamage does not take a piece for mp. + self dodamage( damage, self.origin, undefined, /*piece*/undefined ); +} + +function explosive_incendiary_explosion( attacker, explosion_radius, physics_explosion ) +{ + if ( !IsVehicle( self ) ) + { + offset = (0, 0, 5); + if ( isdefined( attacker )) + { + self RadiusDamage( self.origin + offset, explosion_radius, 256, 75, attacker, "MOD_BURNED", GetWeapon( "incendiary_fire" ) ); + } + else + { + self RadiusDamage( self.origin + offset, explosion_radius, 256, 75 ); + } + physics_explosion_and_rumble( self.origin, 255, physics_explosion ); + + } + + + // delete the clip that is attached + if( isdefined( self.target ) ) + { + dest_clip = GetEnt( self.target, "targetname" ); + if( isDefined( dest_clip ) ) + { + dest_clip delete(); + } + } + + self MarkDestructibleDestroyed(); +} + +function explosive_electrical_explosion( attacker, explosion_radius, physics_explosion ) +{ + if ( !IsVehicle( self ) ) + { + offset = (0, 0, 5); + if ( isdefined( attacker )) + { + self RadiusDamage( self.origin + offset, explosion_radius, 256, 75, attacker, "MOD_ELECTROCUTED" ); + } + else + { + self RadiusDamage( self.origin + offset, explosion_radius, 256, 75 ); + } + physics_explosion_and_rumble( self.origin, 255, physics_explosion ); + } + + + // delete the clip that is attached + if( isdefined( self.target ) ) + { + dest_clip = GetEnt( self.target, "targetname" ); + if( isDefined( dest_clip ) ) + { + dest_clip delete(); + } + } + + self MarkDestructibleDestroyed(); +} + +function explosive_concussive_explosion( attacker, explosion_radius, physics_explosion ) +{ + if ( !IsVehicle( self ) ) + { + offset = (0, 0, 5); + if ( isdefined( attacker )) + { + self RadiusDamage( self.origin + offset, explosion_radius, 256, 75, attacker, "MOD_GRENADE" ); + } + else + { + self RadiusDamage( self.origin + offset, explosion_radius, 256, 75 ); + } + physics_explosion_and_rumble( self.origin, 255, physics_explosion ); + } + + + // delete the clip that is attached + if( isdefined( self.target ) ) + { + dest_clip = GetEnt( self.target, "targetname" ); + if( isDefined( dest_clip ) ) + { + dest_clip delete(); + } + } + + self MarkDestructibleDestroyed(); +} diff --git a/mp/_destructible.gsh b/mp/_destructible.gsh new file mode 100644 index 0000000..139597f --- /dev/null +++ b/mp/_destructible.gsh @@ -0,0 +1,2 @@ + + diff --git a/mp/_devgui.gsc b/mp/_devgui.gsc new file mode 100644 index 0000000..2222848 --- /dev/null +++ b/mp/_devgui.gsc @@ -0,0 +1,915 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\dev_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\load_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons_shared; +#using scripts\shared\weapons\_weapons; +#using scripts\shared\vehicle_ai_shared; +#using scripts\shared\vehicle_shared; +#using scripts\mp\_vehicle; + + + +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\shared\bots\_bot; + +/# + +#namespace devgui; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function autoexec __init__sytem__() { system::register("devgui",&__init__,undefined,undefined); } + +function __init__() +{ + SetDvar( "mp_weap_devgui", "" ); + SetDvar( "debug_center_screen", 0 ); + SetDvar( "mp_lockspawn_command_devgui", 0 ); + SetDvar( "mp_weap_use_give_console_command_devgui", 0 ); + SetDvar( "mp_weap_asset_name_display_devgui", 0 ); + SetDvar( "mp_weap_attachment_cosmetic_variant_index_devgui", 0 ); + SetDvar( "mp_weap_attachment_cosmetic_variant_attachment_1_devgui", "none" ); + SetDvar( "mp_weap_attachment_cosmetic_variant_attachment_2_devgui", "none" ); + + SetDvar( "mp_attachment_cycling_state_devgui", "none" ); + + SetDvar( "mp_attachment_cycling_1_devgui", "none" ); + SetDvar( "mp_attachment_cycling_2_devgui", "none" ); + SetDvar( "mp_attachment_cycling_3_devgui", "none" ); + SetDvar( "mp_attachment_cycling_4_devgui", "none" ); + SetDvar( "mp_attachment_cycling_5_devgui", "none" ); + SetDvar( "mp_attachment_cycling_6_devgui", "none" ); + + level.attachment_cycling_dvars = []; + level.attachment_cycling_dvars[level.attachment_cycling_dvars.size] = "mp_attachment_cycling_1_devgui"; + level.attachment_cycling_dvars[level.attachment_cycling_dvars.size] = "mp_attachment_cycling_2_devgui"; + level.attachment_cycling_dvars[level.attachment_cycling_dvars.size] = "mp_attachment_cycling_3_devgui"; + level.attachment_cycling_dvars[level.attachment_cycling_dvars.size] = "mp_attachment_cycling_4_devgui"; + level.attachment_cycling_dvars[level.attachment_cycling_dvars.size] = "mp_attachment_cycling_5_devgui"; + level.attachment_cycling_dvars[level.attachment_cycling_dvars.size] = "mp_attachment_cycling_6_devgui"; + + SetDvar( "mp_acv_cycling_1_devgui", 0 ); + SetDvar( "mp_acv_cycling_2_devgui", 0 ); + SetDvar( "mp_acv_cycling_3_devgui", 0 ); + SetDvar( "mp_acv_cycling_4_devgui", 0 ); + SetDvar( "mp_acv_cycling_5_devgui", 0 ); + SetDvar( "mp_acv_cycling_6_devgui", 0 ); + + level.acv_cycling_dvars = []; + level.acv_cycling_dvars[level.acv_cycling_dvars.size] = "mp_acv_cycling_1_devgui"; + level.acv_cycling_dvars[level.acv_cycling_dvars.size] = "mp_acv_cycling_2_devgui"; + level.acv_cycling_dvars[level.acv_cycling_dvars.size] = "mp_acv_cycling_3_devgui"; + level.acv_cycling_dvars[level.acv_cycling_dvars.size] = "mp_acv_cycling_4_devgui"; + level.acv_cycling_dvars[level.acv_cycling_dvars.size] = "mp_acv_cycling_5_devgui"; + level.acv_cycling_dvars[level.acv_cycling_dvars.size] = "mp_acv_cycling_6_devgui"; + + level thread devgui_weapon_think(); + level thread devgui_weapon_asset_name_display_think(); + level thread devgui_player_weapons(); + level thread devgui_test_chart_think(); + level thread devgui_player_spawn_think(); + level thread devgui_vehicle_spawn_think(); + + // center lines for making sure the gun aims properly + thread init_debug_center_screen(); + + level thread dev::body_customization_devgui( 1 ); + + callback::on_connect( &hero_art_on_player_connect ); + callback::on_connect( &on_player_connect ); +} + +function on_player_connect() +{ + /# + self.devguiLockSpawn = false; + self thread devgui_player_spawn(); + #/ +} + +function devgui_player_spawn() +{ + wait(1); + + player_devgui_base_mp = "devgui_cmd \"Player/Lock Spawn/"; + + {wait(.05);}; // so we don't overflow the Cbuf_InsertText() + + players = GetPlayers(); + foreach( player in players ) + { + if ( player != self ) + continue; + + temp = player_devgui_base_mp + player.playername + "\" \"set " + "mp_lockspawn_command_devgui" + " " + player.playername + "\"\n"; + + AddDebugCommand( player_devgui_base_mp + player.playername + "\" \"set " + "mp_lockspawn_command_devgui" + " " + player.playername + "\"\n"); + } +} + +function devgui_player_spawn_think() +{ + for ( ;; ) + { + playername = GetDvarString( "mp_lockspawn_command_devgui" ); + if (playername=="") + { + {wait(.05);}; + continue; + } + + players = GetPlayers(); + foreach( player in players ) + { + if ( player.playername != playername ) + continue; + + player.devguiLockSpawn = !player.devguiLockSpawn; + if ( player.devguiLockSpawn ) + { + player.resurrect_origin = player.origin; + player.resurrect_angles = player.angles; + } + } + + SetDvar( "mp_lockspawn_command_devgui", "" ); + wait( 0.5 ); + } +} + +function devgui_vehicle_spawn_think() +{ + {wait(.05);}; + + for( ;; ) + { + val = GetDvarInt( "scr_spawnvehicle" ); + if( val != 0 ) + { + if( val == 1 ) add_vehicle_at_eye_trace( "siegebot" ); + else if( val == 2 ) add_vehicle_at_eye_trace( "siegebot_boss" ); + else if( val == 3 ) add_vehicle_at_eye_trace( "quadtank" ); + else if( val == 4 ) add_vehicle_at_eye_trace( "mechtank" ); + SetDvar( "scr_spawnvehicle", "0" ); + } + {wait(.05);}; + } +} + +function devgui_player_weapons() +{ + if ( ( isdefined( game["devgui_weapons_added"] ) && game["devgui_weapons_added"] ) ) + { + return; + } + + level flag::wait_till("all_players_spawned" ); + + a_weapons = EnumerateWeapons( "weapon" ); + + a_weapons_mp = []; + a_grenades_mp = []; + a_misc_mp = []; + + // For misc weapons not in statstable + + for ( i = 0; i < a_weapons.size; i++ ) + { + if ( ( weapons::is_primary_weapon( a_weapons[i] ) || weapons::is_side_arm( a_weapons[i] ) ) && !killstreaks::is_killstreak_weapon( a_weapons[i] ) ) + { + ArrayInsert( a_weapons_mp, a_weapons[i], 0 ); + } + else if ( weapons::is_grenade( a_weapons[i] ) ) + { + ArrayInsert( a_grenades_mp, a_weapons[i], 0 ); + } + else + { + ArrayInsert( a_misc_mp, a_weapons[i], 0 ); + } + } + + player_devgui_base_mp = "devgui_cmd \"Player/Weapons/"; + + menu_index = 1; + // *** T8 INTEGRATION ***: do NOT integrate; T8 has better solution + level thread devgui_add_player_weapons( player_devgui_base_mp, "All", 0, a_weapons_mp, "Guns", menu_index ); + menu_index++; + // *** T8 INTEGRATION ***: do NOT integrate; T8 has better solution + level thread devgui_add_player_weapons( player_devgui_base_mp, "All", 0, a_grenades_mp, "Grenades", menu_index ); + menu_index++; + // *** T8 INTEGRATION ***: do NOT integrate; T8 has better solution + level thread devgui_add_player_weapons( player_devgui_base_mp, "All", 0, a_misc_mp, "Misc", menu_index ); + menu_index++; + + game["devgui_weapons_added"] = true; + {wait(.05);}; // so we don't overflow the Cbuf_InsertText() + + AddDebugCommand( player_devgui_base_mp + "Toggle Use Give Cmd" + "\" \"toggle " + "mp_weap_use_give_console_command_devgui" + " 1 0\" \n"); + menu_index++; + + AddDebugCommand( player_devgui_base_mp + "Toggle Debug Center Screen" + "\" \"toggle " + "debug_center_screen" + " 1 0\" \n"); + menu_index++; + + AddDebugCommand( player_devgui_base_mp + "Toggle Weapon Asset Name Display" + "\" \"toggle " + "mp_weap_asset_name_display_devgui" + " 1 0\" \n"); + menu_index++; + + acv_devgui_base_mp = player_devgui_base_mp + "Cosmetic Variants" + "/"; + menu_index++; + + acv_menu_index = 1; + acv_sub_menu_index = 1; + for ( i = 0; i <= 3; i++ ) + { + AddDebugCommand( acv_devgui_base_mp + "Variant Index" + "/" + i + "\" \"set " + "mp_weap_attachment_cosmetic_variant_index_devgui" + " " + i + "\" \n"); + acv_sub_menu_index++; + } + acv_menu_index++; + + attachmentNames = GetAttachmentNames(); + + acv_sub_menu_index = 1; + for ( i = 0; i < attachmentNames.size; i++ ) + { + if ( IsSubStr( attachmentNames[i], "gmod" ) ) + { + continue; + } + + // *** T8 INTEGRATION ***: do NOT integrate; T8 has better solution + devgui_wait(); + + AddDebugCommand( acv_devgui_base_mp + "Attachment 1" + "/" + attachmentNames[i] + "\" \"set " + "mp_weap_attachment_cosmetic_variant_attachment_1_devgui" + " " + attachmentNames[i] + "\" \n"); + acv_sub_menu_index++; + } + acv_menu_index++; + + acv_sub_menu_index = 1; + for ( i = 0; i < attachmentNames.size; i++ ) + { + if ( IsSubStr( attachmentNames[i], "gmod" ) ) + { + continue; + } + + // *** T8 INTEGRATION ***: do NOT integrate; T8 has better solution + devgui_wait(); + + AddDebugCommand( acv_devgui_base_mp + "Attachment 2" + "/" + attachmentNames[i] + "\" \"set " + "mp_weap_attachment_cosmetic_variant_attachment_2_devgui" + " " + attachmentNames[i] + "\" \n"); + acv_sub_menu_index++; + } + acv_menu_index++; + + {wait(.05);}; // so we don't overflow the Cbuf_InsertText() + + attachment_cycling_devgui_base_mp = player_devgui_base_mp + "Attachment Cycling" + "/"; + AddDebugCommand( attachment_cycling_devgui_base_mp + "Clear All\" \"set " + "mp_attachment_cycling_state_devgui" + " clear all\" \n"); + AddDebugCommand( attachment_cycling_devgui_base_mp + "Reapply\" \"set " + "mp_attachment_cycling_state_devgui" + " update\" \n"); + + for ( i = 0; i < 6; i++ ) + { + attachment_cycling_sub_menu_index = 1; + + AddDebugCommand( attachment_cycling_devgui_base_mp + "Attachment " + (i + 1) + "/Clear:1\" \"set " + "mp_attachment_cycling_state_devgui" + " clear " + i + "\" \n"); + + for ( attachmentName = 0; attachmentName < attachmentNames.size; attachmentName++ ) + { + if ( IsSubStr( attachmentNames[attachmentName], "gmod" ) ) + { + continue; + } + + // *** T8 INTEGRATION ***: do NOT integrate; T8 has better solution + devgui_wait(); + + AddDebugCommand( attachment_cycling_devgui_base_mp + "Attachment " + (i + 1) + "/" + attachmentNames[attachmentName] + "\" \"set " + "mp_attachment_cycling_state_devgui" + " update; set " + level.attachment_cycling_dvars[i] + " " + attachmentNames[attachmentName] + "; set " + level.acv_cycling_dvars[i] + " " + 0 + "\" \n"); + attachment_cycling_sub_menu_index++; + + AddDebugCommand( attachment_cycling_devgui_base_mp + "Attachment " + (i + 1) + "/" + attachmentNames[attachmentName] + " Variant 1\" \"set " + "mp_attachment_cycling_state_devgui" + " update; set " + level.attachment_cycling_dvars[i] + " " + attachmentNames[attachmentName] + "; set " + level.acv_cycling_dvars[i] + " " + 1 + "\" \n"); + attachment_cycling_sub_menu_index++; + } + } + + level thread devgui_attachment_cosmetic_variant_think(); + level thread devgui_attachment_cycling_think(); +} + +function devgui_add_player_weapons( root, pname, index, a_weapons, weapon_type, mindex ) +{ + if( isDedicated() ) + { + return; + } + + devgui_root = root + weapon_type + "/"; + + if ( IsDefined( a_weapons ) ) + { + for ( i = 0; i < a_weapons.size; i++ ) + { + attachments = a_weapons[i].supportedAttachments; + name = a_weapons[i].name; + + if ( attachments.size ) + { + devgui_add_player_weap_command( devgui_root + name + "/", index, name, i + 1 ); + + foreach( att in attachments ) + { + if ( att != "none" ) + { + devgui_add_player_weap_command( devgui_root + name + "/", index, name + "+" + att, i + 1 ); + } + } + } + else + { + devgui_add_player_weap_command( devgui_root, index, name, i + 1 ); + } + + {wait(.05);}; + } + } +} + +// *** T8 INTEGRATION ***: do NOT integrate; T8 has better solution +function devgui_wait() +{ + if(!isdefined(level.devgui_add_count))level.devgui_add_count=0; + + // prevent input buffer overflow + level.devgui_add_count++; + if ( level.devgui_add_count % 10 == 0 ) + wait RandomIntRange( 2, 5 ) * .05; +} + +function devgui_add_player_weap_command( root, pid, weap_name, cmdindex ) +{ + // *** T8 INTEGRATION ***: do NOT integrate; T8 has better solution + devgui_wait(); + + AddDebugCommand( root + weap_name+"\" \"set "+"mp_weap_devgui"+" "+weap_name+"\" \n"); +} + +function devgui_weapon_think() +{ + for ( ;; ) + { + weapon_name = GetDvarString( "mp_weap_devgui" ); + + if ( weapon_name != "" ) + { + devgui_handle_player_command( &devgui_give_weapon, weapon_name ); + } + + SetDvar( "mp_weap_devgui", "" ); + + wait( 0.5 ); + } +} + +function hero_art_on_player_connect() +{ + self._debugHeroModels = SpawnStruct(); +} + + + +function devgui_weapon_asset_name_display_think() +{ + update_time = 1; + + print_duration = Int( update_time / .05 ); + + printlnbold_update = Int( 1 / update_time ); + printlnbold_counter = 0; + + colors = []; + colors[colors.size] = (1, 1, 1); + colors[colors.size] = (1, 0, 0); + colors[colors.size] = (0, 1, 0); + colors[colors.size] = (1, 1, 0); + colors[colors.size] = (1, 0, 1); + colors[colors.size] = (0, 1, 1); + + for ( ;; ) + { + wait( update_time ); + + display = GetDvarInt( "mp_weap_asset_name_display_devgui" ); + + if ( !display ) + { + continue; + } + + if ( !printlnbold_counter ) + { + iPrintLnBold( level.players[0] GetCurrentWeapon().name ); + } + printlnbold_counter++; + if ( printlnbold_counter >= printlnbold_update ) + { + printlnbold_counter = 0; + } + + color_index = 0; + for ( i = 1; i < level.players.size; i++ ) + { + player = level.players[i]; + weapon = player GetCurrentWeapon(); + + if ( !IsDefined( weapon ) || level.weaponNone == weapon ) + { + continue; + } + + Print3D( player GetTagOrigin( "tag_flash" ), weapon.name, colors[color_index], 1, 0.15, print_duration ); + + color_index++; + if ( color_index >= colors.size ) + { + color_index = 0; + } + } + + color_index = 0; + ai_list = GetAIArray(); + for ( i = 0; i < ai_list.size; i++ ) + { + ai = ai_list[i]; + if ( IsVehicle( ai ) ) + { + weapon = ai.turretweapon; + } + else + { + weapon = ai.weapon; + } + + if ( !IsDefined( weapon ) || level.weaponNone == weapon ) + { + continue; + } + + Print3D( ai GetTagOrigin( "tag_flash" ), weapon.name, colors[color_index], 1, 0.15, print_duration ); + + color_index++; + if ( color_index >= colors.size ) + { + color_index = 0; + } + } + } +} + +function devgui_attachment_cosmetic_variant_think() +{ + old_index = 0; + old_attachment_1 = "none"; + old_attachment_2 = "none"; + for ( ;; ) + { + index = GetDvarInt( "mp_weap_attachment_cosmetic_variant_index_devgui" ); + attachment_1 = GetDvarString( "mp_weap_attachment_cosmetic_variant_attachment_1_devgui" ); + attachment_2 = GetDvarString( "mp_weap_attachment_cosmetic_variant_attachment_2_devgui" ); + + if ( old_attachment_1 != attachment_1 || old_attachment_2 != attachment_2 || old_index != index ) + { + devgui_handle_player_command( &devgui_update_attachment_cosmetic_variant, attachment_1, attachment_2 ); + } + + old_index = index; + old_attachment_1 = attachment_1; + old_attachment_2 = attachment_2; + + wait( 0.5 ); + } +} + +function devgui_attachment_cycling_clear( index ) +{ + SetDvar( level.attachment_cycling_dvars[index], "none" ); + SetDvar( level.acv_cycling_dvars[index], 0 ); +} + +function devgui_attachment_cycling_update() +{ + currentWeapon = self GetCurrentWeapon(); + + rootweapon = currentWeapon.rootweapon; + supportedAttachments = currentWeapon.supportedattachments; + + textColors = []; + attachments = []; + acvs = []; + originalAttachments = []; + originalAcvs = []; + for ( i = 0; i < 6; i++ ) + { + originalAttachments[i] = GetDvarString( level.attachment_cycling_dvars[i] ); + originalAcvs[i] = GetDvarInt( level.acv_cycling_dvars[i] ); + + textColor[i] = "^7"; + attachments[i] = "none"; + acvs[i] = 0; + + name = originalAttachments[i]; + if ( "none" == name ) + { + continue; + } + + textColor[i] = "^1"; + for ( supportedIndex = 0; supportedIndex < supportedAttachments.size; supportedIndex++ ) + { + if ( name == supportedAttachments[supportedIndex] ) + { + textColor[i] = "^7"; + attachments[i] = name; + acvs[i] = originalAcvs[i]; + break; + } + } + } + + for ( i = 0; i < 6; i++ ) + { + if ( "none" == originalAttachments[i] ) + { + continue; + } + + for ( j = i + 1; j < 6; j++ ) + { + if ( originalAttachments[i] == originalAttachments[j] ) + { + textColor[j] = "^6"; + attachments[j] = "none"; + acvs[j] = 0; + } + } + } + + msg = ""; + for ( i = 0; i < 6; i++ ) + { + if ( "none" == originalAttachments[i] ) + { + continue; + } + + msg += textColor[i]; + msg += i; + msg += ": "; + msg += originalAttachments[i]; + msg += ", "; + msg += originalAcvs[i]; + msg += ", "; + } + + IPrintLnBold( msg ); + + self TakeWeapon( currentWeapon ); + + currentWeapon = GetWeapon( rootweapon.name, attachments[0], attachments[1], attachments[2], attachments[3], attachments[4], attachments[5] ); + acvi = GetAttachmentCosmeticVariantIndexes( currentWeapon, attachments[0], acvs[0], attachments[1], acvs[1], attachments[2], acvs[2], attachments[3], acvs[3], attachments[4], acvs[4], attachments[5], acvs[5] ); + + wait( 0.25 ); // wait a little bit so that the weapon viewmodel will rebuild + + self GiveWeapon( currentWeapon, undefined, acvi ); + self SwitchToWeapon( currentWeapon ); +} + +function devgui_attachment_cycling_think() +{ + for ( ;; ) + { + state = GetDvarString( "mp_attachment_cycling_state_devgui" ); + SetDvar( "mp_attachment_cycling_state_devgui", "none" ); + + if ( IsSubStr( state, "clear " ) ) + { + if ( "clear all" == state ) + { + for ( i = 0; i < 6; i++ ) + { + devgui_attachment_cycling_clear( i ); + } + } + else + { + index = Int( GetSubStr( state, 6, 7 ) ); + devgui_attachment_cycling_clear( index ); + } + + state = "update"; + } + + if ( "update" == state ) + { + array::thread_all( GetPlayers(), &devgui_attachment_cycling_update ); + } + + wait( 0.5 ); + } +} + +function devgui_test_chart_think() +{ + {wait(.05);}; // wait to get 0 initially + + old_val = GetDvarInt( "scr_debug_test_chart" ); + + for ( ;; ) + { + val = GetDvarInt( "scr_debug_test_chart" ); + + if ( old_val != val ) + { + if ( IsDefined( level.test_chart_model ) ) + { + level.test_chart_model delete(); + level.test_chart_model = undefined; + } + + if ( val ) + { + player = GetPlayers()[0]; + + direction = player GetPlayerAngles(); + direction_vec = AnglesToForward( (0, direction[1], 0) ); // only want the player's yaw + + scale = 120; + direction_vec = (direction_vec[0] * scale, direction_vec[1] * scale, direction_vec[2] * scale); + + level.test_chart_model = Spawn( "script_model", player GetEye() + direction_vec ); + level.test_chart_model SetModel( "test_chart_model" ); + level.test_chart_model.angles = (0, direction[1], 0) + (0, 90, 0); // only want the yaw + } + } + + old_val = val; + {wait(.05);}; + } +} + +function devgui_give_weapon( weapon_name ) +{ + assert( IsDefined( self ) ); + assert( IsPlayer( self ) ); + assert( IsAlive( self ) ); + + self notify( "devgui_give_ammo" ); + self endon( "devgui_give_ammo" ); + + currentWeapon = self GetCurrentWeapon(); + + split = StrTok( weapon_name, "+" ); + switch ( split.size ) + { + default: + case 1: + weapon = GetWeapon( split[0] ); + break; + case 2: + weapon = GetWeapon( split[0], split[1] ); + break; + case 3: + weapon = GetWeapon( split[0], split[1], split[2] ); + break; + case 4: + weapon = GetWeapon( split[0], split[1], split[2], split[3] ); + break; + case 5: + weapon = GetWeapon( split[0], split[1], split[2], split[3], split[4] ); + break; + } + + if ( currentWeapon != weapon ) + { + // if the player already has 2 grenades, take one away + if ( weapon.isGrenadeWeapon ) + { + grenades = 0; + pweapons = self GetWeaponsList(); + foreach( pweapon in pweapons ) + { + if ( pweapon!=weapon && pweapon.isGrenadeWeapon ) + { + grenades++; + } + } + if ( grenades > 1 ) + { + foreach( pweapon in pweapons ) + { + if ( pweapon!=weapon && pweapon.isGrenadeWeapon ) + { + grenades--; + self TakeWeapon(pweapon); + if ( grenades < 2 ) + break; + } + } + } + } + + if ( GetDvarInt( "mp_weap_use_give_console_command_devgui" ) ) + { + AddDebugCommand( "give " + weapon_name ); + {wait(.05);}; // wait a frame to let the command take + } + else + { + self GiveWeapon( weapon ); + if ( !weapon.isGrenadeWeapon ) + self SwitchToWeapon( weapon ); + } + + max = weapon.maxAmmo; + + if ( max ) + { + self SetWeaponAmmoStock( weapon, max ); + } + } +} + +function devgui_update_attachment_cosmetic_variant( attachment_1, attachment_2 ) +{ + assert( IsDefined( self ) ); + assert( IsPlayer( self ) ); + assert( IsAlive( self ) ); + + currentWeapon = self GetCurrentWeapon(); + variant_index = GetDvarInt( "mp_weap_attachment_cosmetic_variant_index_devgui" ); + acvi = GetAttachmentCosmeticVariantIndexes( currentWeapon, attachment_1, variant_index, attachment_2, variant_index ); + self TakeWeapon( currentWeapon ); + wait( 0.25 ); // wait a little bit so that the weapon viewmodel will rebuild + self GiveWeapon( currentWeapon, undefined, acvi ); + self SwitchToWeapon( currentWeapon ); +} + +function devgui_handle_player_command( playercallback, pcb_param_1, pcb_param_2 ) +{ + pid = GetDvarInt( "mp_weap_devgui" ); + if (pid>0) + { + player = GetPlayers()[pid-1]; + if (IsDefined(player)) + { + if (IsDefined( pcb_param_2 )) + player thread [[playercallback]]( pcb_param_1, pcb_param_2 ); + else if (IsDefined( pcb_param_1 )) + player thread [[playercallback]]( pcb_param_1 ); + else + player thread [[playercallback]](); + } + } + else + { + array::thread_all( GetPlayers(), playercallback, pcb_param_1, pcb_param_2 ); + } + SetDvar( "mp_weap_devgui", "-1" ); +} + +// *** Debug the Center of the Screen *** +// Allows you to draw a large crosshair in the center of the screen by pressing in both sticks at once +function init_debug_center_screen() +{ + zero_idle_movement = "0"; + + for (;;) + { + if( GetDvarInt( "debug_center_screen" ) ) + { + if (!isdefined (level.center_screen_debug_hudelem_active) || level.center_screen_debug_hudelem_active == false) + { + thread debug_center_screen(); + + zero_idle_movement = GetDvarString( "zero_idle_movement" ); + if( isdefined( zero_idle_movement ) && zero_idle_movement == "0" ) + { + SetDvar( "zero_idle_movement", "1" ); + zero_idle_movement = "1"; + } + } + } + else + { + level notify ("stop center screen debug"); + + if( zero_idle_movement == "1" ) + { + SetDvar( "zero_idle_movement", "0" ); + zero_idle_movement = "0"; + } + } + wait (0.05); + } +} + +// draws the center screen debug marker +function debug_center_screen() +{ + level.center_screen_debug_hudelem_active = true; + wait 0.1; + + level.center_screen_debug_hudelem1 = newclienthudelem ( level.players[0] ); + level.center_screen_debug_hudelem1.alignX = "center"; + level.center_screen_debug_hudelem1.alignY = "middle"; + level.center_screen_debug_hudelem1.fontScale = 1; + level.center_screen_debug_hudelem1.alpha = 0.5; + level.center_screen_debug_hudelem1.x = (640/2) - 1; + level.center_screen_debug_hudelem1.y = (480/2); + level.center_screen_debug_hudelem1 setshader("white", 1000, 1); + + level.center_screen_debug_hudelem2 = newclienthudelem ( level.players[0] ); + level.center_screen_debug_hudelem2.alignX = "center"; + level.center_screen_debug_hudelem2.alignY = "middle"; + level.center_screen_debug_hudelem2.fontScale = 1; + level.center_screen_debug_hudelem2.alpha = 0.5; + level.center_screen_debug_hudelem2.x = (640/2) - 1; + level.center_screen_debug_hudelem2.y = (480/2); + level.center_screen_debug_hudelem2 setshader("white", 1, 480); + + level waittill ("stop center screen debug"); + + level.center_screen_debug_hudelem1 destroy(); + level.center_screen_debug_hudelem2 destroy(); + level.center_screen_debug_hudelem_active = false; +} + +function add_vehicle_at_eye_trace( vehicleName ) +{ + host = util::getHostPlayer(); + + trace = host bot::eye_trace(); + + //vehicle = SpawnVehicle( vehicleName, trace[ "position" ] + ( 0, 0, 20 ) , ( 0, 0, 0 ), "test_siege" ); + veh_spawner = GetEnt( vehicleName + "_spawner", "targetname" ); + vehicle = veh_spawner SpawnFromSpawner( vehicleName, true, true, true ); + if( isdefined( vehicle.archetype ) ) + vehicle ASMRequestSubstate( "locomotion@movement" ); + + {wait(.05);}; + + vehicle.origin = trace[ "position" ]; + + vehicle thread vehicle::VehicleTeamThread(); + + vehicle thread watch_player_death(); + + return vehicle; +} + +function watch_player_death() +{ + self endon( "death" ); + vehicle = self; + + while( 1 ) + { + driver = self GetSeatOccupant( 0 ); + if( isdefined( driver ) && !IsAlive( driver ) ) + driver unlink(); + {wait(.05);}; + } +} + +#/ diff --git a/mp/_end_game_flow.csc b/mp/_end_game_flow.csc new file mode 100644 index 0000000..3502149 --- /dev/null +++ b/mp/_end_game_flow.csc @@ -0,0 +1,319 @@ +#using scripts\codescripts\struct; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\end_game_taunts; +#using scripts\shared\scene_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + + + + + + +#using_animtree("all_player"); +#namespace end_game_flow; + +function autoexec __init__sytem__() { system::register("end_game_flow",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "world", "displayTop3Players", 1, 1, "int", &handleTopThreePlayers, !true, !true ); + clientfield::register( "world", "triggerScoreboardCamera", 1, 1, "int", &showScoreboard, !true, !true ); + clientfield::register( "world", "playTop0Gesture", 1000, 3, "int", &handlePlayTop0Gesture, !true, !true ); + clientfield::register( "world", "playTop1Gesture", 1000, 3, "int", &handlePlayTop1Gesture, !true, !true ); + clientfield::register( "world", "playTop2Gesture", 1000, 3, "int", &handlePlayTop2Gesture, !true, !true ); + + level thread streamerWatcher(); +} + +function setAnimationOnModel( localClientNum, characterModel, topPlayerIndex ) +{ + anim_name = end_game_taunts::getidleanimname( localClientNum, characterModel, topPlayerIndex ); + + if( isDefined(anim_name) ) + { + characterModel util::waittill_dobj( localClientNum ); + + if( !characterModel HasAnimTree() ) + { + characterModel UseAnimTree( #animtree ); + } + + characterModel SetAnim( anim_name ); + } +} + + +function loadCharacterOnModel( localClientNum, characterModel, topPlayerIndex ) +{ + assert( isdefined( characterModel ) ); + + // swap out our body + bodyModel = GetTopPlayersBodyModel( localClientNum, topPlayerIndex ); + displayTopPlayerModel = CreateUIModel( GetUIModelForController( localClientNum ), "displayTopPlayer" + (topPlayerIndex+1) ); + SetUIModelValue( displayTopPlayerModel, 1 ); + + // This happens when the client has never spawned in, we should not show his model and not show his playercard as well. + if ( !IsDefined( bodyModel ) || bodymodel == "" ) + { + SetUIModelValue( displayTopPlayerModel, 0 ); + return; + } + + characterModel SetModel( bodyModel ); + + // swap out our helmet + helmetModel = GetTopPlayersHelmetModel( localClientNum, topPlayerIndex ); + + if ( !( characterModel IsAttached( helmetModel, "" ) ) ) + { + characterModel.helmetModel = helmetModel; // Keep refcount on this string because we'll need it later + characterModel Attach( helmetModel, "" ); + } + + // set up our render options + modeRenderOptions = GetCharacterModeRenderOptions( CurrentSessionMode() ); + bodyRenderOptions = GetTopPlayersBodyRenderOptions( localClientNum, topPlayerIndex ); + helmetRenderOptions = GetTopPlayersHelmetRenderOptions( localClientNum, topPlayerIndex ); + weaponRenderOptions = GetTopPlayersWeaponRenderOptions( localClientNum, topPlayerIndex ); + + // Used by epic taunts to clone the model and match skins + characterModel.bodyModel = bodyModel; + // Headmodel is attached above + characterModel.modeRenderOptions = modeRenderOptions; + characterModel.bodyRenderOptions = bodyRenderOptions; + characterModel.helmetRenderOptions = helmetRenderOptions; + characterModel.headRenderOptions = helmetRenderOptions; + + weapon_right = GetTopPlayersWeaponInfo( localClientNum, topPlayerIndex ); + + if ( !isDefined( level.weaponNone ) ) + { + level.weaponNone = GetWeapon( "none" ); + } + + characterModel SetBodyRenderOptions( modeRenderOptions, bodyRenderOptions, helmetRenderOptions, helmetRenderOptions ); + + if ( weapon_right["weapon"] == level.weaponNone ) + { + weapon_right["weapon"] = GetWeapon("ar_standard"); + characterModel.showcaseWeapon = weapon_right["weapon"]; + characterModel AttachWeapon( weapon_right["weapon"] ); + } + else + { + characterModel.showcaseWeapon = weapon_right["weapon"]; + characterModel.showcaseWeaponRenderOptions = weaponRenderOptions; + characterModel.showcaseWeaponACVI = weapon_right["acvi"]; + + characterModel AttachWeapon( weapon_right["weapon"], weaponRenderOptions, weapon_right["acvi"] ); + characterModel UseWeaponHideTags( weapon_right["weapon"] ); + } +} + + + +function setupModelAndAnimation( localClientNum, characterModel, topPlayerIndex ) +{ + characterModel endon("entityshutdown"); + + loadCharacterOnModel( localClientNum, characterModel, topPlayerIndex ); + setAnimationOnModel( localClientNum, characterModel, topPlayerIndex ); +} + + + +function prepareTopThreePlayers( localClientNum ) +{ + numClients = GetTopScorerCount( localClientNum ); + position = struct::get( "endgame_top_players_struct", "targetname" ); + + if( !isdefined( position ) ) + { + return; + } + + for( index = 0; index < 3; index++ ) + { + if ( index < numClients ) + { + model = Spawn( localClientNum, position.origin, "script_model" ); + loadCharacterOnModel( localClientNum, model, index ); + model Hide(); + model SetHighDetail( true ); + } + } +} + +function showTopThreePlayers( localClientNum ) +{ + level.topPlayerCharacters = []; + topPlayerScriptStructs = []; + + topPlayerScriptStructs[0] = struct::get( "TopPlayer1", "targetname" ); + topPlayerScriptStructs[1] = struct::get( "TopPlayer2", "targetname" ); + topPlayerScriptStructs[2] = struct::get( "TopPlayer3", "targetname" ); + + foreach( index, scriptStruct in topPlayerScriptStructs ) + { + level.topPlayerCharacters[index] = Spawn( localClientNum, scriptStruct.origin, "script_model" ); + // level.topPlayerCharacters[index] SetDedicatedShadow( true ); + level.topPlayerCharacters[index].angles = scriptStruct.angles; + } + numClients = GetTopScorerCount( localClientNum ); + + foreach( index, characterModel in level.topPlayerCharacters ) + { + if ( index < numClients ) + { + thread setupModelAndAnimation( localClientNum, characterModel, index ); + + if ( index == 0 ) + { + thread end_game_taunts::playCurrentTaunt( localClientNum, characterModel, index ); + } + } + } + +/# + level thread end_game_taunts::check_force_taunt(); + level thread end_game_taunts::check_force_gesture(); + level thread end_game_taunts::draw_runner_up_bounds(); +#/ + + position = struct::get( "endgame_top_players_struct", "targetname" ); + PlayMainCamXCam( localClientNum, level.endGameXCamName, 0, "cam_topscorers", "topscorers", position.origin, position.angles ); + PlayRadiantExploder( localClientNum, "exploder_mp_endgame_lights" ); + + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "displayTop3Players" ), 1 ); + + thread spamUIModelValue( localClientNum ); + thread checkForGestures( localClientNum ); +} + +function spamUIModelValue( localClientNum ) +{ + while( 1 ) + { + wait (0.25); + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "displayTop3Players" ), 1 ); + } +} + +function checkForGestures( localClientNum ) +{ + localPlayers = GetLocalPlayers(); + + for ( i = 0; i < localPlayers.size; i++ ) + { + thread checkForPlayerGestures( localClientNum, localPlayers[i], i ); + } +} + +function checkForPlayerGestures( localClientNum, localPlayer, playerIndex ) +{ + localTopPlayerIndex = localPlayer GetTopPlayersIndex( localClientNum ); + + if ( !isdefined( localTopPlayerIndex ) || + !isdefined( level.topPlayerCharacters ) || + localTopPlayerIndex >= level.topPlayerCharacters.size ) + { + return; + } + + characterModel = level.topPlayerCharacters[localTopPlayerIndex]; + + if ( localTopPlayerIndex > 0 ) + { + wait( 3 ); + } + else if ( isdefined( characterModel.playingTaunt ) ) + { + characterModel waittill( "tauntFinished" ); + } + + showGestures( localClientNum, playerIndex ); +} + +function showGestures( localClientNum, playerIndex ) +{ + gesturesModel = GetUIModel( GetUIModelForController( localClientNum ), "topPlayerInfo.showGestures" ); + if ( isdefined( gesturesModel ) ) + { + SetUIModelValue( gesturesModel, true ); + AllowActionSlotInput( playerIndex ); + } +} + +function handlePlayTop0Gesture( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + handlePlayGesture( localClientNum, 0, newVal ); +} + +function handlePlayTop1Gesture( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + handlePlayGesture( localClientNum, 1, newVal ); +} + +function handlePlayTop2Gesture( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + handlePlayGesture( localClientNum, 2, newVal ); +} + +function handlePlayGesture( localClientNum, topPlayerIndex, gestureType ) +{ + if ( gestureType > 2 || + !isdefined( level.topPlayerCharacters ) || + topPlayerIndex >= level.topPlayerCharacters.size ) + { + return; + } + + characterModel = level.topPlayerCharacters[topPlayerIndex]; + + if ( isdefined( characterModel.playingTaunt ) || + ( isdefined( characterModel.playingGesture ) && characterModel.playingGesture ) ) + { + return; + } + + thread end_game_taunts::playGestureType( localClientNum, characterModel, topPlayerIndex, gestureType ); +} + +function streamerWatcher() +{ + while( true ) + { + level waittill( "streamFKsl", localClientNum ); + prepareTopThreePlayers( localClientNum ); + end_game_taunts::stream_epic_models(); + } +} + + +function handleTopThreePlayers( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( isdefined( newVal ) && newVal > 0 && isDefined( level.endGameXCamName ) ) + { + level.showedTopThreePlayers = true; + showTopThreePlayers( localClientNum ); + } +} + +function showScoreboard( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( isdefined( newVal ) && newVal > 0 && isDefined( level.endGameXCamName ) ) + { + end_game_taunts::stop_stream_epic_models(); + end_game_taunts::deleteCameraGlass( undefined ); + position = struct::get( "endgame_top_players_struct", "targetname" ); + PlayMainCamXCam( localClientNum, level.endGameXCamName, 0, "cam_topscorers", "", position.origin, position.angles ); + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "forceScoreboard" ), 1 ); + level.inEndGameFlow = true; + } +} + diff --git a/mp/_entityheadicons.gsc b/mp/_entityheadicons.gsc new file mode 100644 index 0000000..ea0250b --- /dev/null +++ b/mp/_entityheadicons.gsc @@ -0,0 +1,19 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\entityheadicons_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#namespace entityheadicons; + +function autoexec __init__sytem__() { system::register("entityheadicons",&__init__,undefined,undefined); } + +function __init__() +{ + entityheadicons::init_shared(); +} + + diff --git a/mp/_events.gsc b/mp/_events.gsc new file mode 100644 index 0000000..76114cc --- /dev/null +++ b/mp/_events.gsc @@ -0,0 +1,147 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic_utils; + +#using scripts\mp\_util; + +#namespace events; + +/*------------------------------------ +Adds an event based on the current value of the gametypes timer +------------------------------------*/ +function add_timed_event( seconds, notify_string, client_notify_string ) +{ + assert( seconds >= 0 ); + + if ( level.timelimit > 0 ) + { + level thread timed_event_monitor( seconds, notify_string, client_notify_string ); + } +} + + +/*------------------------------------ +checks the game/level timer for timed events +------------------------------------*/ +function timed_event_monitor( seconds, notify_string, client_notify_string ) +{ + for ( ;; ) + { + wait( 0.5 ); + + if( !isdefined( level.startTime ) ) + { + continue; + } + + //get the time remaining and see if events need to be fired off + millisecs_remaining = globallogic_utils::getTimeRemaining(); + seconds_remaining = millisecs_remaining / 1000; + + if( seconds_remaining <= seconds ) + { + event_notify( notify_string, client_notify_string ); + return; + } + } +} + + +function add_score_event( score, notify_string, client_notify_string ) +{ + assert( score >= 0 ); + + if ( level.scoreLimit > 0 ) + { + if ( level.teamBased ) + { + level thread score_team_event_monitor( score, notify_string, client_notify_string ); + } + else + { + level thread score_event_monitor( score, notify_string, client_notify_string ); + } + } +} + +function add_round_score_event( score, notify_string, client_notify_string ) +{ + assert( score >= 0 ); + + if ( level.roundScoreLimit > 0 ) + { + roundScoreToBeat = ( level.roundScoreLimit * game[ "roundsplayed" ] ) + score; + if ( level.teamBased ) + { + level thread score_team_event_monitor( roundScoreToBeat, notify_string, client_notify_string ); + } + else + { + level thread score_event_monitor( roundScoreToBeat, notify_string, client_notify_string ); + } + } +} + +function any_team_reach_score( score ) +{ + foreach( team in level.teams ) + { + if ( game["teamScores"][team] >= score ) + return true; + } + + return false; +} + +function score_team_event_monitor( score, notify_string, client_notify_string ) +{ + for ( ;; ) + { + wait( 0.5 ); + + if ( any_team_reach_score( score ) ) + { + event_notify( notify_string, client_notify_string ); + return; + } + } +} + + +function score_event_monitor( score, notify_string, client_notify_string ) +{ + for ( ;; ) + { + wait ( 0.5 ); + + players = GetPlayers(); + + for ( i = 0; i < players.size; i++ ) + { + if ( isdefined( players[i].score ) && players[i].score >= score ) + { + event_notify( notify_string, client_notify_string ); + return; + } + } + } +} + + +function event_notify( notify_string, client_notify_string ) +{ + if ( isdefined( notify_string ) ) + { + level notify( notify_string ); + } + + if ( isdefined( client_notify_string ) ) + { + util::clientNotify( client_notify_string ); + } +} + diff --git a/mp/_explosive_bolt.csc b/mp/_explosive_bolt.csc new file mode 100644 index 0000000..534c5c0 --- /dev/null +++ b/mp/_explosive_bolt.csc @@ -0,0 +1,120 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\_util; + +#precache( "client_fx", "weapon/fx_equip_light_os" ); +#namespace explosive_bolt; + +function autoexec __init__sytem__() { system::register("explosive_bolt",&__init__,undefined,undefined); } + +function __init__() +{ + level._effect["crossbow_light"] = "weapon/fx_equip_light_os"; + + callback::add_weapon_type( "explosive_bolt", &spawned ); +} + +function spawned( localClientNum ) +{ + if ( self isGrenadeDud() ) + return; + + self thread fx_think( localClientNum ); +} + +function fx_think( localClientNum ) +{ + self notify( "light_disable" ); + + self endon( "entityshutdown" ); + self endon( "light_disable" ); + + self util::waittill_dobj( localClientNum ); + + interval = 0.3; + + for( ;; ) + { + self stop_light_fx( localClientNum ); + self start_light_fx( localClientNum ); + self fullscreen_fx( localClientNum ); + self PlaySound( localClientNum, "wpn_semtex_alert" ); + + util::server_wait( localClientNum, interval, .016, "player_switch" ); + interval = math::clamp( ( interval / 1.2 ), 0.08, 0.3 ); + } +} + +function start_light_fx( localClientNum ) +{ + player = GetLocalPlayer( localClientNum ); + + self.fx = PlayFxOnTag( localClientNum, level._effect["crossbow_light"], self, "tag_origin" ); +} + +function stop_light_fx( localClientNum ) +{ + if ( isdefined( self.fx ) && self.fx != 0 ) + { + StopFx( localClientNum, self.fx ); + self.fx = undefined; + } +} + +function fullscreen_fx( localClientNum ) +{ + player = GetLocalPlayer( localClientNum ); + + if ( isdefined( player ) ) + { + if ( player GetInKillcam( localClientNum ) ) + { + return; + } + else if ( player util::is_player_view_linked_to_entity( localClientNum ) ) + { + return; + } + } + + if ( self util::friend_not_foe( localClientNum ) ) + { + return; + } + + parent = self GetParentEntity(); + + if ( isdefined( parent ) && parent == player ) + { + parent PlayRumbleOnEntity( localClientNum, "buzz_high" ); + + // support for this has been removed with the .menu system + /* + if ( IsSplitscreen() ) + { + AnimateUI( localClientNum, "sticky_grenade_overlay_ss"+localClientNum, "overlay", "pulse", 0 ); + + if ( GetDvarint( "ui_hud_hardcore" ) == 0 ) + { + AnimateUI( localClientNum, "stuck_ss"+localClientNum, "explosive_bolt", "pulse", 0 ); + } + } + else + { + AnimateUI( localClientNum, "sticky_grenade_overlay"+localClientNum, "overlay", "pulse", 0 ); + + if ( GetDvarint( "ui_hud_hardcore" ) == 0 ) + { + AnimateUI( localClientNum, "stuck"+localClientNum, "explosive_bolt", "pulse", 0 ); + } + } + */ + } +} \ No newline at end of file diff --git a/mp/_explosive_bolt.gsc b/mp/_explosive_bolt.gsc new file mode 100644 index 0000000..a1a1742 --- /dev/null +++ b/mp/_explosive_bolt.gsc @@ -0,0 +1,60 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weapons; + + + +#using scripts\mp\_util; + +#precache( "fx", "weapon/fx_equip_light_os" ); + +#namespace explosive_bolt; + +function autoexec __init__sytem__() { system::register("explosive_bolt",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_spawned( &on_player_spawned ); +} + +function on_player_spawned() +{ + self thread begin_other_grenade_tracking(); +} + +function begin_other_grenade_tracking() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self notify( "boltTrackingStart" ); + self endon( "boltTrackingStart" ); + + weapon_bolt = GetWeapon( "explosive_bolt" ); + + for (;;) + { + self waittill ( "grenade_fire", grenade, weapon, cookTime ); + + if ( grenade util::isHacked() ) + { + continue; + } + + if ( weapon == weapon_bolt ) + { + grenade.ownerWeaponAtLaunch = self.currentWeapon; + grenade.ownerAdsAtLaunch = ( self PlayerAds() == 1 ? true : false ); + grenade thread watch_bolt_detonation( self ); + grenade thread weapons::check_stuck_to_player( true, false, weapon ); + } + } +} + +function watch_bolt_detonation( owner ) // self == explosive_bolt entity +{ + //self SetTeam( owner.team ); +} diff --git a/mp/_flashgrenades.csc b/mp/_flashgrenades.csc new file mode 100644 index 0000000..22376f6 --- /dev/null +++ b/mp/_flashgrenades.csc @@ -0,0 +1,21 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\visionset_mgr_shared; +#using scripts\shared\weapons\_flashgrenades; + + + + +#using scripts\mp\_util; + +#namespace flashgrenades; + +function autoexec __init__sytem__() { system::register("flashgrenades",&__init__,undefined,undefined); } + +function __init__( localClientNum ) +{ + flashgrenades::init_shared(); +} diff --git a/mp/_flashgrenades.gsc b/mp/_flashgrenades.gsc new file mode 100644 index 0000000..f9ba5d3 --- /dev/null +++ b/mp/_flashgrenades.gsc @@ -0,0 +1,19 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_flashgrenades; + + + +#using scripts\mp\_util; + +#namespace flashgrenades; + +function autoexec __init__sytem__() { system::register("flashgrenades",&__init__,undefined,undefined); } + +function __init__() +{ + flashgrenades::init_shared(); +} + diff --git a/mp/_gameadvertisement.gsc b/mp/_gameadvertisement.gsc new file mode 100644 index 0000000..ec89c98 --- /dev/null +++ b/mp/_gameadvertisement.gsc @@ -0,0 +1,488 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_dev; +#using scripts\mp\gametypes\_globallogic_utils; + +#using scripts\mp\_util; + +#namespace gameadvertisement; + +function init() +{ +/# + level.sessionAdvertStatus = true; + thread sessionAdvertismentUpdateDebugHud(); +#/ + + level.gameAdvertisementRuleScorePercent = GetGametypeSetting( "gameAdvertisementRuleScorePercent" ); + level.gameAdvertisementRuleTimeLeft = ( 1000 * 60 ) * GetGametypeSetting( "gameAdvertisementRuleTimeLeft" ); + level.gameAdvertisementRuleRound = GetGametypeSetting( "gameAdvertisementRuleRound" ); + level.gameAdvertisementRuleRoundsWon = GetGametypeSetting( "gameAdvertisementRuleRoundsWon" ); + + thread sessionAdvertisementCheck(); +} + + +function setAdvertisedStatus( onOff ) +{ +/# + level.sessionAdvertStatus = onOff; +#/ + changeAdvertisedStatus( onOff ); +} + +function sessionAdvertisementCheck() +{ + if( SessionModeIsPrivate() ) + return; + + runRules = getGameTypeRules(); + + if( !isdefined( runRules ) ) + return; + + level endon( "game_end" ); + + level waittill( "prematch_over" ); + + currentAdvertisedStatus = undefined; + while( true ) + { + sessionAdvertCheckwait = GetDvarInt( "sessionAdvertCheckwait", 1 ); + + wait( sessionAdvertCheckwait ); + + advertise = [[runRules]](); + if ( !isdefined( currentAdvertisedStatus ) || ( isdefined( advertise ) && ( currentAdvertisedStatus != advertise ) ) ) + { + setAdvertisedStatus( advertise ); + } + currentAdvertisedStatus = advertise; + } +} + +function getGameTypeRules() +{ + gametype = level.gametype; + + switch( gametype ) + { + case "gun": + return &gun_rules; + default: + return &default_rules; + } + + return; +} + +function teamScoreLimitCheck( ruleScorePercent, debug_count ) +{ + scoreLimit = 0; + + //==================================================================== + // score check + + if ( level.roundScoreLimit ) + { + scoreLimit = util::get_current_round_score_limit(); + } + else if ( level.scoreLimit ) + { + scoreLimit = level.scoreLimit; + } + + if ( scoreLimit ) + { + minScorePercentageLeft = 100; + + foreach ( team in level.teams ) + { + scorePercentageLeft = 100 - ( ( game[ "teamScores" ][ team ] / scoreLimit ) * 100 ); + + if( minScorePercentageLeft > scorePercentageLeft ) + minScorePercentageLeft = scorePercentageLeft; + + if( ruleScorePercent >= scorePercentageLeft ) + { +/# + debug_count = updateDebugHud( debug_count, "Score Percentage Left: ", int( scorePercentageLeft ) ); +#/ + return false; + } + } + +/# + debug_count = updateDebugHud( debug_count, "Score Percentage Left: ", int( minScorePercentageLeft ) ); +#/ + } + + + return true; +} + +function timeLimitCheck( ruleTimeLeft ) +{ + maxTime = level.timeLimit; + + if( maxTime != 0 ) + { + timeLeft = globallogic_utils::getTimeRemaining(); + + if( ruleTimeLeft >= timeLeft ) + { + return false; + } + } + + return true; +} + +//======================================================================================================================================== +//======================================================================================================================================== +//======================================================================================================================================== + +function default_rules() +{ + + //==================================================================== + + currentRound = game[ "roundsplayed" ] + 1; + + debug_count = 1; + + //==================================================================== + + + //==================================================================== + // score check + + if( level.gameAdvertisementRuleScorePercent ) + { +/# + debug_count = updateDebugHud( debug_count, "Any player/team is within percent of score cap: ", level.gameAdvertisementRuleScorePercent ); +#/ + if ( level.teambased ) + { + if ( (currentRound >= (level.gameAdvertisementRuleRound - 1)) ) + { + if ( teamScoreLimitCheck( level.gameAdvertisementRuleScorePercent, debug_count ) == false ) + return false; + +/# + debug_count++; +#/ + } + } + else + { + if ( level.scoreLimit ) + { + highestScore = 0; + players = GetPlayers(); + + for( i = 0; i < players.size; i++) + { + if( players[i].pointstowin > highestScore ) + highestScore = players[i].pointstowin; + } + + scorePercentageLeft = 100 - ( ( highestScore / level.scoreLimit ) * 100 ); + +/# + debug_count = updateDebugHud( debug_count, "Score Percentage Left: ", int( scorePercentageLeft ) ); +#/ + + if( level.gameAdvertisementRuleScorePercent >= scorePercentageLeft ) + { + return false; + } + } + } + } + + //==================================================================== + // time left check + + if( level.gameAdvertisementRuleTimeLeft && (currentRound >= (level.gameAdvertisementRuleRound - 1)) ) + { +/# + debug_count = updateDebugHud( debug_count, "Time limit has less than minutes remaining: ", level.gameAdvertisementRuleTimeLeft/( 1000 * 60 ) ); +#/ + if ( timeLimitCheck( level.gameAdvertisementRuleTimeLeft ) == false ) + return false; + } + + //==================================================================== + // round check + + if( level.gameAdvertisementRuleRound ) + { +/# + debug_count = updateDebugHud( debug_count, "Is round: ", level.gameAdvertisementRuleRound ); + debug_count = updateDebugHud( debug_count, "Current Round: ", currentRound ); +#/ + if ( level.gameAdvertisementRuleRound <= currentRound ) + return false; + } + + //==================================================================== + // round won check + + if ( level.gameAdvertisementRuleRoundsWon ) + { +/# + debug_count = updateDebugHud( debug_count, "Any team has won rounds: ", level.gameAdvertisementRuleRoundsWon ); +#/ + maxRoundsWon = 0; + foreach ( team in level.teams ) + { + roundsWon = game[ "teamScores" ][ team ]; + + if( maxRoundsWon < roundsWon ) + maxRoundsWon = roundsWon; + + if( level.gameAdvertisementRuleRoundsWon <= roundsWon ) + { +/# + debug_count = updateDebugHud( debug_count, "Max Rounds Won: ", maxRoundsWon ); +#/ + return false; + } + } +/# + debug_count = updateDebugHud( debug_count, "Max Rounds Won: ", maxRoundsWon ); +#/ + } + + return true; +} + +function gun_rules() +{ + // Any player is within 3 weapons from winning + + //==================================================================== + + ruleWeaponsLeft = 3; // within 3 weapons of winning + + //==================================================================== + +/# + updateDebugHud( 1, "Any player is within X weapons from winning: ", ruleWeaponsLeft ); +#/ + + //==================================================================== + // weapons check + minWeaponsLeft = level.gunProgression.size; + + foreach ( player in level.activePlayers ) + { + if ( !isdefined( player ) ) + continue; + + if ( !isdefined( player.gunProgress ) ) + continue; + + weaponsLeft = level.gunProgression.size - player.gunProgress; + + if( minWeaponsLeft > weaponsLeft ) + minWeaponsLeft = weaponsLeft; + + if( ruleWeaponsLeft >= minWeaponsLeft ) + { +/# + updateDebugHud( 3, "Weapons Left: ", minWeaponsLeft ); +#/ + return false; + } + } +/# + updateDebugHud( 3, "Weapons Left: ", minWeaponsLeft ); +#/ + + return true; +} + +//======================================================================================================================================== +//======================================================================================================================================== +//======================================================================================================================================== + +/# +function sessionAdvertismentCreateDebugHud( lineNum, alignX ) +{ + debug_hud = dev::new_hud( "session_advert", "debug_hud", 0, 0, 1 ); + debug_hud.hidewheninmenu = true; + debug_hud.horzAlign = "right"; + debug_hud.vertAlign = "middle"; + debug_hud.alignX = "right"; + debug_hud.alignY = "middle"; + debug_hud.x = alignX; + debug_hud.y = -50 + (lineNum * 15); + debug_hud.foreground = true; + debug_hud.font = "default"; + debug_hud.fontScale = 1.5; + debug_hud.color = ( 1.0, 1.0, 1.0 ); + debug_hud.alpha = 1; + + debug_hud setText( "" ); + + return debug_hud; +} + +function updateDebugHud( hudIndex, text, value ) +{ + switch( hudIndex ) + { + case 1: + level.sessionAdvertHud_1A_text = text; + level.sessionAdvertHud_1B_text = value; + break; + + case 2: + level.sessionAdvertHud_2A_text = text; + level.sessionAdvertHud_2B_text = value; + break; + + case 3: + level.sessionAdvertHud_3A_text = text; + level.sessionAdvertHud_3B_text = value; + break; + + case 4: + level.sessionAdvertHud_4A_text = text; + level.sessionAdvertHud_4B_text = value; + break; + } + return hudIndex+1; +} + +function sessionAdvertismentUpdateDebugHud() +{ + level endon( "game_end" ); + + sessionAdvertHud_0 = undefined; + + sessionAdvertHud_1A = undefined; + sessionAdvertHud_1B = undefined; + sessionAdvertHud_2A = undefined; + sessionAdvertHud_2B = undefined; + sessionAdvertHud_3A = undefined; + sessionAdvertHud_3B = undefined; + sessionAdvertHud_4A = undefined; + sessionAdvertHud_4B = undefined; + + + level.sessionAdvertHud_0_text = ""; + level.sessionAdvertHud_1A_text = ""; + level.sessionAdvertHud_1B_text = ""; + level.sessionAdvertHud_2A_text = ""; + level.sessionAdvertHud_2B_text = ""; + level.sessionAdvertHud_3A_text = ""; + level.sessionAdvertHud_3B_text = ""; + level.sessionAdvertHud_4A_text = ""; + level.sessionAdvertHud_4B_text = ""; + + while( true ) + { + wait( 1 ); + + showDebugHud = GetDvarInt( "sessionAdvertShowDebugHud", 0 ); + + //==================================================================== + + level.sessionAdvertHud_0_text = "Session is advertised"; + if( level.sessionAdvertStatus == false ) + level.sessionAdvertHud_0_text = "Session is not advertised"; + + //==================================================================== + + if( !isdefined( sessionAdvertHud_0 ) && showDebugHud != 0 ) + { + host = util::getHostPlayer(); + + if( !isdefined( host ) ) + continue; + + sessionAdvertHud_0 = host sessionAdvertismentCreateDebugHud( 0, 0 ); + + sessionAdvertHud_1A = host sessionAdvertismentCreateDebugHud( 1, -20 ); + sessionAdvertHud_1B = host sessionAdvertismentCreateDebugHud( 1, 0 ); + sessionAdvertHud_2A = host sessionAdvertismentCreateDebugHud( 2, -20 ); + sessionAdvertHud_2B = host sessionAdvertismentCreateDebugHud( 2, 0 ); + sessionAdvertHud_3A = host sessionAdvertismentCreateDebugHud( 3, -20 ); + sessionAdvertHud_3B = host sessionAdvertismentCreateDebugHud( 3, 0 ); + sessionAdvertHud_4A = host sessionAdvertismentCreateDebugHud( 4, -20 ); + sessionAdvertHud_4B = host sessionAdvertismentCreateDebugHud( 4, 0 ); + + sessionAdvertHud_1A.color = ( 0.0, 0.5, 0.0 ); + sessionAdvertHud_1B.color = ( 0.0, 0.5, 0.0 ); + sessionAdvertHud_2A.color = ( 0.0, 0.5, 0.0 ); + sessionAdvertHud_2B.color = ( 0.0, 0.5, 0.0 ); + } + + if( isdefined( sessionAdvertHud_0 ) ) + { + if( showDebugHud == 0 ) + { + sessionAdvertHud_0 destroy(); + sessionAdvertHud_1A destroy(); + sessionAdvertHud_1B destroy(); + sessionAdvertHud_2A destroy(); + sessionAdvertHud_2B destroy(); + sessionAdvertHud_3A destroy(); + sessionAdvertHud_3B destroy(); + sessionAdvertHud_4A destroy(); + sessionAdvertHud_4B destroy(); + + sessionAdvertHud_0 = undefined; + sessionAdvertHud_1A = undefined; + sessionAdvertHud_1B = undefined; + sessionAdvertHud_2A = undefined; + sessionAdvertHud_2B = undefined; + sessionAdvertHud_3A = undefined; + sessionAdvertHud_3B = undefined; + sessionAdvertHud_4A = undefined; + sessionAdvertHud_4B = undefined; + } + else + { + if( level.sessionAdvertStatus == true ) + sessionAdvertHud_0.color = ( 1.0, 1.0, 1.0 ); + else + sessionAdvertHud_0.color = ( 0.9, 0.0, 0.0 ); + + sessionAdvertHud_0 setText( level.sessionAdvertHud_0_text ); + + if( level.sessionAdvertHud_1A_text != "" ) + { + sessionAdvertHud_1A setText( level.sessionAdvertHud_1A_text ); + sessionAdvertHud_1B setValue( level.sessionAdvertHud_1B_text ); + } + + if( level.sessionAdvertHud_2A_text != "" ) + { + sessionAdvertHud_2A setText( level.sessionAdvertHud_2A_text ); + sessionAdvertHud_2B setValue( level.sessionAdvertHud_2B_text ); + } + + if( level.sessionAdvertHud_3A_text != "" ) + { + sessionAdvertHud_3A setText( level.sessionAdvertHud_3A_text ); + sessionAdvertHud_3B setValue( level.sessionAdvertHud_3B_text ); + } + + if( level.sessionAdvertHud_4A_text != "" ) + { + sessionAdvertHud_4A setText( level.sessionAdvertHud_4A_text ); + sessionAdvertHud_4B setValue( level.sessionAdvertHud_4B_text ); + } + } + } + } +} +#/ \ No newline at end of file diff --git a/mp/_gamerep.gsc b/mp/_gamerep.gsc new file mode 100644 index 0000000..7ebd2cf --- /dev/null +++ b/mp/_gamerep.gsc @@ -0,0 +1,459 @@ + + +#using scripts\shared\rank_shared; + +#using scripts\shared\bots\_bot; + +#namespace gamerep; + +function init() +{ + if ( !isGameRepEnabled() ) + { + return; + } + + if ( isGameRepInitialized() ) + { + return; + } + + game["gameRepInitialized"] = true; + + game["gameRep"]["players"] = []; + game["gameRep"]["playerNames"] = []; + game["gameRep"]["max"] = []; + game["gameRep"]["playerCount"] = 0; + + gameRepInitializeParams(); +} + +function isGameRepInitialized() +{ + if ( !isdefined( game["gameRepInitialized"] ) || !game["gameRepInitialized"] ) + return false; + + return true; +} + +function isGameRepEnabled() +{ + if( bot::is_bot_ranked_match() ) + return false; + + if ( !level.rankedMatch ) + return false; + + return true; +} + +function gameRepInitializeParams() +{ + // this should match the thresholdExceeded_e in live_anticheat.cpp + THRESHOLD_EXCEEDED_SCORE = 0; + THRESHOLD_EXCEEDED_SCORE_PER_MIN = 1; + THRESHOLD_EXCEEDED_KILLS = 2; + THRESHOLD_EXCEEDED_DEATHS = 3; + THRESHOLD_EXCEEDED_KD_RATIO = 4; + THRESHOLD_EXCEEDED_KILLS_PER_MIN = 5; + THRESHOLD_EXCEEDED_PLANTS = 6; + THRESHOLD_EXCEEDED_DEFUSES = 7; + THRESHOLD_EXCEEDED_CAPTURES = 8; + THRESHOLD_EXCEEDED_DEFENDS = 9; + THRESHOLD_EXCEEDED_TOTAL_TIME_PLAYED = 10; + THRESHOLD_EXCEEDED_TACTICAL_INSERTION_USE = 11; + THRESHOLD_EXCEEDED_JOIN_ATTEMPTS = 12; + THRESHOLD_EXCEEDED_XP = 13; + THRESHOLD_EXCEEDED_SPLITSCREEN = 14; + + // Parameter Names (for individual players) + game["gameRep"]["params"] = []; + + game["gameRep"]["params"][0] = "score"; + game["gameRep"]["params"][1] = "scorePerMin"; + game["gameRep"]["params"][2] = "kills"; + game["gameRep"]["params"][3] = "deaths"; + game["gameRep"]["params"][4] = "killDeathRatio"; + game["gameRep"]["params"][5] = "killsPerMin"; + game["gameRep"]["params"][6] = "plants"; + game["gameRep"]["params"][7] = "defuses"; + game["gameRep"]["params"][8] = "captures"; + game["gameRep"]["params"][9] = "defends"; + game["gameRep"]["params"][10] = "totalTimePlayed"; + game["gameRep"]["params"][11] = "tacticalInsertions"; + game["gameRep"]["params"][12] = "joinAttempts"; + game["gameRep"]["params"][13] = "xp"; + + // New Param additions go here. + + // Parameters which we want to track, but not use for reporting. (for individual players) + game["gameRep"]["ignoreParams"] = []; + game["gameRep"]["ignoreParams"][0] = "totalTimePlayed"; + + // Game limit parameters + game["gameRep"]["gameLimit"] = []; + game["gameRep"]["gameLimit"]["default"] = []; + game["gameRep"]["gameLimit"]["tdm"] = []; + game["gameRep"]["gameLimit"]["dm"] = []; + game["gameRep"]["gameLimit"]["dom"] = []; + game["gameRep"]["gameLimit"]["hq"] = []; + game["gameRep"]["gameLimit"]["sd"] = []; + game["gameRep"]["gameLimit"]["dem"] = []; + game["gameRep"]["gameLimit"]["ctf"] = []; + game["gameRep"]["gameLimit"]["koth"] = []; + game["gameRep"]["gameLimit"]["conf"] = []; + + // Score + game["gameRep"]["gameLimit"]["id"]["score"] = THRESHOLD_EXCEEDED_SCORE; + game["gameRep"]["gameLimit"]["default"]["score"] = 20000; + + // Score per min + game["gameRep"]["gameLimit"]["id"]["scorePerMin"] = THRESHOLD_EXCEEDED_SCORE_PER_MIN; + game["gameRep"]["gameLimit"]["default"]["scorePerMin"] = 250; + game["gameRep"]["gameLimit"]["dem"]["scorePerMin"] = 1000; + game["gameRep"]["gameLimit"]["tdm"]["scorePerMin"] = 700; + game["gameRep"]["gameLimit"]["dm"]["scorePerMin"] = 950; + game["gameRep"]["gameLimit"]["dom"]["scorePerMin"] = 1000; + game["gameRep"]["gameLimit"]["sd"]["scorePerMin"] = 200; + game["gameRep"]["gameLimit"]["ctf"]["scorePerMin"] = 600; + game["gameRep"]["gameLimit"]["hq"]["scorePerMin"] = 1000; + game["gameRep"]["gameLimit"]["koth"]["scorePerMin"] = 1000; + game["gameRep"]["gameLimit"]["conf"]["scorePerMin"] = 1000; + + // Kills + game["gameRep"]["gameLimit"]["id"]["kills"] = THRESHOLD_EXCEEDED_KILLS; + game["gameRep"]["gameLimit"]["default"]["kills"] = 75; + game["gameRep"]["gameLimit"]["tdm"]["kills"] = 40; + game["gameRep"]["gameLimit"]["sd"]["kills"] = 15; + game["gameRep"]["gameLimit"]["dm"]["kills"] = 31; + + // Deaths + game["gameRep"]["gameLimit"]["id"]["deaths"] = THRESHOLD_EXCEEDED_DEATHS; + game["gameRep"]["gameLimit"]["default"]["deaths"] = 50; + game["gameRep"]["gameLimit"]["dm"]["deaths"] = 15; + game["gameRep"]["gameLimit"]["tdm"]["deaths"] = 40; + + // KD Ratio + game["gameRep"]["gameLimit"]["id"]["killDeathRatio"] = THRESHOLD_EXCEEDED_KD_RATIO; + game["gameRep"]["gameLimit"]["default"]["killDeathRatio"] = 30; + game["gameRep"]["gameLimit"]["tdm"]["killDeathRatio"] = 50; + game["gameRep"]["gameLimit"]["sd"]["killDeathRatio"] = 20; + + // Kills per min + game["gameRep"]["gameLimit"]["id"]["killsPerMin"] = THRESHOLD_EXCEEDED_KILLS_PER_MIN; + game["gameRep"]["gameLimit"]["default"]["killsPerMin"] = 15; + + // Plants + game["gameRep"]["gameLimit"]["id"]["plants"] = THRESHOLD_EXCEEDED_PLANTS; + game["gameRep"]["gameLimit"]["default"]["plants"] = 10; + + // Defuses + game["gameRep"]["gameLimit"]["id"]["defuses"] = THRESHOLD_EXCEEDED_DEFUSES; + game["gameRep"]["gameLimit"]["default"]["defuses"] = 10; + + // Captures + game["gameRep"]["gameLimit"]["id"]["captures"] = THRESHOLD_EXCEEDED_CAPTURES; + game["gameRep"]["gameLimit"]["default"]["captures"] = 30; + + // Defends + game["gameRep"]["gameLimit"]["id"]["defends"] = THRESHOLD_EXCEEDED_DEFENDS; + game["gameRep"]["gameLimit"]["default"]["defends"] = 50; + + // Total Time played (in sec) + game["gameRep"]["gameLimit"]["id"]["totalTimePlayed"] = THRESHOLD_EXCEEDED_TOTAL_TIME_PLAYED; + game["gameRep"]["gameLimit"]["default"]["totalTimePlayed"] = 10 * 60; + game["gameRep"]["gameLimit"]["dom"]["totalTimePlayed"] = 10 * 60; + game["gameRep"]["gameLimit"]["dem"]["totalTimePlayed"] = 19 * 60; + + // Tactical Insertion use + game["gameRep"]["gameLimit"]["id"]["tacticalInsertions"] = THRESHOLD_EXCEEDED_TACTICAL_INSERTION_USE; + game["gameRep"]["gameLimit"]["default"]["tacticalInsertions"] = 20; + + // Join attempts + game["gameRep"]["gameLimit"]["id"]["joinAttempts"] = THRESHOLD_EXCEEDED_JOIN_ATTEMPTS; + game["gameRep"]["gameLimit"]["default"]["joinAttempts"] = 3; + + // XP + game["gameRep"]["gameLimit"]["id"]["xp"] = THRESHOLD_EXCEEDED_XP; + game["gameRep"]["gameLimit"]["default"]["xp"] = 25000; + + // Splitscreen (total) + game["gameRep"]["gameLimit"]["id"]["splitscreen"] = THRESHOLD_EXCEEDED_SPLITSCREEN; + game["gameRep"]["gameLimit"]["default"]["splitscreen"] = 8; + +} + +function gameRepPlayerConnected() +{ + if ( !isGameRepEnabled() ) + return; + + name = self.name; + + /# + // println( "gameRepPlayerConnected() for player " + name ); + #/ + + if ( !isdefined( game["gameRep"]["players"][name] ) ) + { + game["gameRep"]["players"][name] = []; + + for( j = 0; j < game["gameRep"]["params"].size; j++ ) + { + paramName = game["gameRep"]["params"][j]; + game["gameRep"]["players"][name][ paramName ] = 0; + } + + game["gameRep"]["players"][name]["splitscreen"] = self IsSplitScreen(); + game["gameRep"]["players"][name]["joinAttempts"] = 1; + game["gameRep"]["players"][name]["connected"] = true; + game["gameRep"]["players"][name]["xpStart"] = self rank::getRankXpStat(); + + game["gameRep"]["playerNames"][ game["gameRep"]["playerCount"] ] = name; + game["gameRep"]["playerCount"]++; + } + else + { + if ( !game["gameRep"]["players"][name]["connected"] ) + { + game["gameRep"]["players"][name]["joinAttempts"]++; + game["gameRep"]["players"][name]["connected"] = true; + game["gameRep"]["players"][name]["xpStart"] = self rank::getRankXpStat(); + } + } +} + +function gameRepPlayerDisconnected() +{ + if ( !isGameRepEnabled() ) + return; + + name = self.name; + + if( !isdefined( game["gameRep"]["players"][name] ) + || !isdefined( self.pers["summary"] ) ) + { + // we are probably migrating + return; + } + + /# + // println( "gameRepPlayerDisconnected() for player " + name ); + #/ + + self gameRepUpdateNonPersistentPlayerInformation(); + self gameRepUpdatePersistentPlayerInformation(); + game["gameRep"]["players"][name]["connected"] = false; +} + +function gameRepUpdateNonPersistentPlayerInformation() +{ + name = self.name; + if ( !isdefined( game["gameRep"]["players"][name] ) ) + return; + + game["gameRep"]["players"][name]["totalTimePlayed"] += self.timePlayed["total"]; + + if ( isdefined( self.tacticalInsertionCount ) ) + game["gameRep"]["players"][name]["tacticalInsertions"] += self.tacticalInsertionCount; + + // New Non-Persistent Param Calculations go here. + + /*println( "gameRepUpdatePersistentPlayerInformation() for player " + name ); + paramName = "totalTimePlayed"; + println( "Param: " + paramName + "Value: " + getParamValueForPlayer( name, paramName ) );*/ +} + +function gameRepUpdatePersistentPlayerInformation() +{ + name = self.name; + if ( !isdefined( game["gameRep"]["players"][name] ) ) + return; + + if ( game["gameRep"]["players"][name]["totalTimePlayed"] != 0 ) + timePlayed = game["gameRep"]["players"][name]["totalTimePlayed"]; + else + timePlayed = 1; + + game["gameRep"]["players"][name]["score"] = self.score; + game["gameRep"]["players"][name]["scorePerMin"] = int( game["gameRep"]["players"][name]["score"] / ( timePlayed / 60 ) ); + + game["gameRep"]["players"][name]["kills"] = self.kills; + game["gameRep"]["players"][name]["deaths"] = self.deaths; + + if ( game["gameRep"]["players"][name]["deaths"] != 0 ) + game["gameRep"]["players"][name]["killDeathRatio"] = int( ( game["gameRep"]["players"][name]["kills"] / game["gameRep"]["players"][name]["deaths"] ) * 100 ); + else + game["gameRep"]["players"][name]["killDeathRatio"] = game["gameRep"]["players"][name]["kills"] * 100; + + + game["gameRep"]["players"][name]["killsPerMin"] = int( game["gameRep"]["players"][name]["kills"] / ( timePlayed / 60 ) ); + + game["gameRep"]["players"][name]["plants"] = self.plants; + game["gameRep"]["players"][name]["defuses"] = self.defuses; + + game["gameRep"]["players"][name]["captures"] = self.captures; + game["gameRep"]["players"][name]["defends"] = self.defends; + + game["gameRep"]["players"][name]["xp"] = self rank::getRankXpStat() - game["gameRep"]["players"][name]["xpStart"]; + game["gameRep"]["players"][name]["xpStart"] = self rank::getRankXpStat(); + + // New Persistent Param Calculations go here. + + /*println( "gameRepUpdatePersistentPlayerInformation() for player " + name ); + for ( j = 0; j < game["gameRep"]["params"].size; j++ ) + { + paramName = game["gameRep"]["params"][j]; + if ( isGameRepParamValid( paramName ) ) + println( "Param: " + paramName + "Value: " + getParamValueForPlayer( name, paramName ) ); + }*/ +} + +function getParamValueForPlayer( playerName, paramName ) +{ + if ( isdefined( game["gameRep"]["players"][playerName][paramName] ) ) + return game["gameRep"]["players"][playerName][paramName]; + + assertmsg( "Unknown parameter " + paramName + "for individual player" ); +} + +function isGameRepParamValid( paramName ) +{ + gametype = level.gametype; + + if ( !isdefined( game["gameRep"] ) ) + return false; + + if ( !isdefined( game["gameRep"]["gameLimit"] ) ) + return false; + + if ( !isdefined( game["gameRep"]["gameLimit"][gametype] ) ) + return false; + + if ( !isdefined( game["gameRep"]["gameLimit"][gametype][paramName] ) ) + return false; + + if ( !isdefined( game["gameRep"]["gameLimit"][gametype][paramName] ) && !isdefined( game["gameRep"]["gameLimit"]["default"][paramName] ) ) + return false; + + return true; +} + +function isGameRepParamIgnoredForReporting( paramName ) +{ + if ( isdefined( game["gameRep"]["ignoreParams"][paramName] ) ) + return true; + + return false; +} + +function getGameRepParamLimit( paramName ) +{ + gametype = level.gametype; + + if ( isdefined( game["gameRep"]["gameLimit"][gametype] ) ) + { + if ( isdefined( game["gameRep"]["gameLimit"][gametype][paramName] ) ) + return game["gameRep"]["gameLimit"][gametype][paramName]; + } + + if ( isdefined( game["gameRep"]["gameLimit"]["default"][paramName] ) ) + return game["gameRep"]["gameLimit"]["default"][paramName]; + + assertmsg( "Default values for parameter " + paramName + " is not defined." ); +} + +function setMaximumParamValueForCurrentGame( paramName, value ) +{ + if ( !isdefined( game["gameRep"]["max"][paramName] ) ) + { + game["gameRep"]["max"][ paramName ] = value; + return; + } + + if ( game["gameRep"]["max"][ paramName ] < value ) + { + game["gameRep"]["max"][ paramName ] = value; + } +} + +function gameRepUpdateInformationForRound() +{ + if ( !isGameRepEnabled() ) + return; + + // We will update only non persistent data between rounds. + players = GetPlayers(); + for( i = 0; i < players.size; i++ ) + { + player = players[i]; + player gameRepUpdateNonPersistentPlayerInformation(); + } +} + +function gameRepAnalyzeAndReport() +{ + if ( !isGameRepEnabled() ) + return; + + // Non persistent data is already updated at the end of the round. + // So, lets update only the persistent data for the current players. + players = GetPlayers(); + for( i = 0; i < players.size; i++ ) + { + player = players[i]; + player gameRepUpdatePersistentPlayerInformation(); + } + + splitscreenPlayerCount = 0; + // Set the maximums for this game (comparing for all players) + for ( i = 0; i < game["gameRep"]["playerNames"].size; i++ ) + { + playerName = game["gameRep"]["playerNames"][i]; + + // Individual Params + for ( j = 0; j < game["gameRep"]["params"].size; j++ ) + { + paramName = game["gameRep"]["params"][j]; + if ( isGameRepParamValid( paramName ) ) + setMaximumParamValueForCurrentGame( paramName, getParamValueForPlayer( playerName, paramName ) ); + } + + // Splitscreen Players count + paramName = "splitscreen"; + splitscreenPlayerCount += getParamValueForPlayer( playerName, paramName ); + } + + setMaximumParamValueForCurrentGame( paramName, splitscreenPlayerCount ); + + // Check if any of the params reached the maximum limit. + // If any of them reaches the maximum limit, then we will prepare and report this film. + for ( j = 0; j < game["gameRep"]["params"].size; j++ ) + { + paramName = game["gameRep"]["params"][j]; + + //println( "Param: " + paramName + " Max: " + game["gameRep"]["max"][ paramName ] + " Limit: " + getGameRepParamLimit( paramName ) ); + + if ( isGameRepParamValid( paramName ) && game["gameRep"]["max"][ paramName ] >= getGameRepParamLimit( paramName ) ) + { + gameRepPrepareAndReport( paramName ); + } + } + + paramName = "splitscreen"; + if ( game["gameRep"]["max"][ paramName ] >= getGameRepParamLimit( paramName ) ) + { + gameRepPrepareAndReport( paramName ); + } +} + +function gameRepPrepareAndReport( paramName ) +{ + if ( !isdefined( game["gameRep"]["gameLimit"]["id"][paramName] ) ) + return; + + if ( isGameRepParamIgnoredForReporting( paramName ) ) + return; + + gameRepThresholdExceeded( game["gameRep"]["gameLimit"]["id"][ paramName ] ); +} diff --git a/mp/_global_fx.csc b/mp/_global_fx.csc new file mode 100644 index 0000000..369ec69 --- /dev/null +++ b/mp/_global_fx.csc @@ -0,0 +1,39 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\fx_shared; +#using scripts\shared\system_shared; + + + +#namespace global_fx; + +function autoexec __init__sytem__() { system::register("global_fx",&__init__,&main,undefined); } + +function __init__() +{ + wind_initial_setting(); +} + +function main() +{ + check_for_wind_override(); +} + +function wind_initial_setting() +{ + SetSavedDvar( "enable_global_wind", 0 ); // enable wind for your level + SetSavedDvar( "wind_global_vector", "0 0 0" ); // change "0 0 0" to your wind vector + SetSavedDvar( "wind_global_low_altitude", 0 ); // change 0 to your wind's lower bound + SetSavedDvar( "wind_global_hi_altitude", 10000 ); // change 10000 to your wind's upper bound + SetSavedDvar( "wind_global_low_strength_percent", 0.5 ); // change 0.5 to your desired wind strength percentage +} + +function check_for_wind_override() +{ + //Allow for level overrides of global wind settings + if( isdefined( level.custom_wind_callback ) ) + { + level thread [[level.custom_wind_callback]](); + } +} + diff --git a/mp/_gravity_spikes.csc b/mp/_gravity_spikes.csc new file mode 100644 index 0000000..6adc96f --- /dev/null +++ b/mp/_gravity_spikes.csc @@ -0,0 +1,173 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\audio_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\_explode; + + + + + + + +#precache( "client_fx", "weapon/fx_hero_grvity_spk_grnd_hit_dust" ); + +#namespace gravity_spikes; + +function autoexec __init__sytem__() { system::register("gravity_spikes",&__init__,undefined,undefined); } + +function __init__() +{ + level._effect["gravity_spike_dust"] = "weapon/fx_hero_grvity_spk_grnd_hit_dust"; + level.gravity_spike_table = "surface_explosion_gravityspikes"; + + level thread watchForGravitySpikeExplosion(); + + level.dirt_enable_gravity_spikes = GetDvarInt( "scr_dirt_enable_gravity_spikes", 0 ); + /# + level thread updateDvars(); + #/ +} + +/# +function updateDvars() +{ + while(1) + { + level.dirt_enable_gravity_spikes = GetDvarInt( "scr_dirt_enable_gravity_spikes", level.dirt_enable_gravity_spikes ); + + wait(1.0); + } +} +#/ + +function watchForGravitySpikeExplosion() +{ + if ( GetActiveLocalClients() > 1 ) + return; + + weapon_proximity = GetWeapon( "hero_gravityspikes" ); + + while ( true ) + { + level waittill( "explode", localClientNum, position, mod, weapon, owner_cent ); + + if ( weapon.rootWeapon != weapon_proximity ) + { + continue; + } + + if( isdefined(owner_cent) && ( GetLocalPlayer( localClientNum ) == owner_cent ) && level.dirt_enable_gravity_spikes ) + { + owner_cent thread explode::dothedirty( localClientNum, 0, 1.0, 0, 1000, 500 ); + } + + thread do_gravity_spike_fx( localClientNum, owner_cent, weapon, position ); + thread audio::doRattle(position,200,700); + } +} + +function do_gravity_spike_fx( localClientNum, owner, weapon, position ) +{ + // this value will bring in the outermost circle so the effects don't spawn + // right on the outer edge of the explosion + radius_of_effect = 40; + + number_of_circles = 3; + base_number_of_effects = 3; + additional_number_of_effects_per_circle = 7; + + explosion_radius = weapon.explosionRadius; + radius_per_circle = ( explosion_radius - radius_of_effect ) / number_of_circles; + + for ( circle = 0; circle < number_of_circles; circle++ ) + { + wait( 0.1 ); + radius_for_this_circle = radius_per_circle * (circle + 1); + number_for_this_circle = base_number_of_effects + (additional_number_of_effects_per_circle * circle); + thread do_gravity_spike_fx_circle( localClientNum, owner, position, radius_for_this_circle, number_for_this_circle ); + } +} + +function getIdealLocationForFX( startPos, fxIndex, fxCount, defaultDistance, rotation ) +{ + currentAngle = ( ( 360 / fxCount ) * fxIndex ); + cosCurrent = cos( currentAngle + rotation ); + sinCurrent = sin( currentAngle + rotation ); + + return startPos + ( defaultDistance * cosCurrent, defaultDistance * sinCurrent, 0 ); +} + +function randomizeLocation( startPos, max_x_offset, max_y_offset ) +{ + half_x = int(max_x_offset / 2); + half_y = int(max_y_offset / 2); + rand_x = RandomIntRange( -half_x, half_x ); + rand_y = RandomIntRange( -half_y, half_y ); + + return startPos + ( rand_x, rand_y, 0 ); +} + +function ground_trace( startPos, owner ) +{ + trace_height = 50; + trace_depth = 100; + + return bullettrace( startPos + ( 0,0,trace_height ), startPos - ( 0,0,trace_depth ), false, owner ); +} + + +function do_gravity_spike_fx_circle( localClientNum, owner, center, radius, count ) +{ + segment = 360 / count; + up = ( 0,0,1 ); + + randomization = 40; + sphere_size = 5; + + for ( i = 0; i < count; i++ ) + { + fx_position = getIdealLocationForFX( center, i, count, radius, 0 ); + /# + // Sphere( fx_position, sphere_size, CYAN, 1, true, 8, 300 ); + #/ + fx_position = randomizeLocation( fx_position, randomization, randomization ); + + trace = ground_trace( fx_position, owner ); + + if ( trace["fraction"] < 1.0 ) + { + /# + // Sphere( fx_position, sphere_size, PURPLE, 1, true, 8, 300 ); + // Sphere( trace["position"], sphere_size, YELLOW, 1, true, 8, 300 ); + #/ + + fx = GetFXFromSurfaceTable( level.gravity_spike_table, trace["surfacetype"] ); + + if ( isdefined( fx ) ) + { + angles = ( 0, RandomIntRange(0,359), 0 ); + forward = AnglesToForward( angles ); + + normal = trace["normal"]; + if ( LengthSquared( normal ) == 0 ) + { + normal = ( 1, 0, 0); + } + PlayFx( localClientNum, fx, trace["position"], normal, forward ); + } + } + else + { + /# +// Line( fx_position + ( 0,0,50 ), fx_position - ( 0,0,50 ), RED, 1, false, 300 ); + #/ + } + + {wait(.016);}; + } +} + diff --git a/mp/_hacker_tool.csc b/mp/_hacker_tool.csc new file mode 100644 index 0000000..6059a3b --- /dev/null +++ b/mp/_hacker_tool.csc @@ -0,0 +1,18 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_hacker_tool; + + + + +#namespace hacker_tool; + +function autoexec __init__sytem__() { system::register("hacker_tool",&__init__,undefined,undefined); } + +function __init__() +{ + hacker_tool::init_shared(); +} diff --git a/mp/_hacker_tool.gsc b/mp/_hacker_tool.gsc new file mode 100644 index 0000000..bdce3cf --- /dev/null +++ b/mp/_hacker_tool.gsc @@ -0,0 +1,19 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_hacker_tool; + + + +#using scripts\mp\_util; + +#namespace hacker_tool; + +function autoexec __init__sytem__() { system::register("hacker_tool",&__init__,undefined,undefined); } + +function __init__() +{ + hacker_tool::init_shared(); +} diff --git a/mp/_hacker_tool.gsh b/mp/_hacker_tool.gsh new file mode 100644 index 0000000..139597f --- /dev/null +++ b/mp/_hacker_tool.gsh @@ -0,0 +1,2 @@ + + diff --git a/mp/_heatseekingmissile.gsc b/mp/_heatseekingmissile.gsc new file mode 100644 index 0000000..f10a2e2 --- /dev/null +++ b/mp/_heatseekingmissile.gsc @@ -0,0 +1,23 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_heatseekingmissile; + + + +#using scripts\mp\_util; + +#namespace heatseekingmissile; + +function autoexec __init__sytem__() { system::register("heatseekingmissile",&__init__,undefined,undefined); } + +function __init__() +{ + level.lockOnCloseRange = 220; + level.lockOnCloseRadiusScaler = 1; + + heatseekingmissile::init_shared(); +} diff --git a/mp/_helicopter_sounds.csc b/mp/_helicopter_sounds.csc new file mode 100644 index 0000000..5e7721d --- /dev/null +++ b/mp/_helicopter_sounds.csc @@ -0,0 +1,1213 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\audio_shared; +#using scripts\shared\music_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + +#using scripts\shared\clientfield_shared; + + + + +#using scripts\mp\_helicopter_sounds; +#using scripts\mp\_util; + +// ************************************************* +// INIT HELI SOUNDS +// +// List of availible sound specific bones +// "SND_Cockpit" +// "SND_INT_F_L" +// "SND_INT_F_R" +// "SND_INT_R_L" +// "SND_INT_R_R" +// "SND_INT_Rotor" +// "SND_Rotor" +// "SND_Tail_Rotor" +// "SND_UNDER_Body" +// "SND_WIND_Left" +// "SND_WIND_Right" +// +// ************************************************* + +#namespace helicopter_sounds; + +function autoexec __init__sytem__() { system::register("helicopter_sounds",&__init__,undefined,undefined); } + +function __init__() +{ + //clientfield::register("vehicle", "vehicle_is_firing", VERSION_SHIP, 1, "int",&vehicle_is_firing_function, CF_HOST_ONLY, !CF_CALLBACK_ZERO_ON_NEW_ENT); + //clientfield::register("helicopter", "vehicle_is_firing", VERSION_SHIP, 1, "int",&vehicle_is_firing_function, CF_HOST_ONLY, !CF_CALLBACK_ZERO_ON_NEW_ENT); + //clientfield::register("actor", "player_is_gunner", VERSION_SHIP, 1, "int",&chppoer_gunner_loop, CF_HOST_ONLY, !CF_CALLBACK_ZERO_ON_NEW_ENT); + + level._entityShutDownCBFunc = &helicopter_sounds::heli_linkto_sound_ents_delete; + level.heliSoundValues = []; + + // heli_type, part_type, max_speed_vol, min_vol, max_vol, max_speed_pitch, min_pitch, max_pitch + + init_heli_sound_values( "cobra", "turbine", 65, 0.6, 0.8, 65, 1, 1.1 ); + init_heli_sound_values( "cobra", "top_rotor", 45, 0.7, 1, 45, 0.95, 1.1); + init_heli_sound_values( "cobra", "tail_rotor", 45, 0.5, 1, 45, 0.95, 1.1); + + init_heli_sound_values( "hind", "turbine", 65, 0.6, 0.8, 65, 1, 1.1 ); + init_heli_sound_values( "hind", "top_rotor", 45, 0.7, 1, 45, 0.95, 1.1); + init_heli_sound_values( "hind", "tail_rotor", 45, 0.5, 1, 45, 0.95, 1.1); + + init_heli_sound_values( "supply", "turbine", 65, 0.7, 1, 65, 1, 1.1 ); + init_heli_sound_values( "supply", "top_rotor", 35, 0.95, 1, 100, 1, 1.1 ); + init_heli_sound_values( "supply", "tail_rotor", 35, 0.95, 1, 45, 1, 1.1 ); + + init_heli_sound_values( "huey", "turbine", 65, 0.7, 0.8, 65, 1, 1.1 ); + init_heli_sound_values( "huey", "top_rotor", 45, 0.8, 1, 45, 0.95, 1.1); + init_heli_sound_values( "huey", "tail_rotor", 45, 0.6, 1, 45, 0.95, 1.0); + init_heli_sound_values( "huey", "wind_rt", 45, 0.6, 1, 45, 0.95, 1.0); + init_heli_sound_values( "huey", "wind_lft", 45, 0.6, 1, 45, 0.95, 1.0); + + init_heli_sound_values( "qrdrone", "turbine_idle", 30, 0.8, 0.0, 16, .9, 1.1 ); + init_heli_sound_values( "qrdrone", "turbine_moving",30, 0.0, 0.9, 20, .9, 1.1 ); + init_heli_sound_values( "qrdrone", "turn", 5, 0, 1, 1, 1, 1 ); + + init_heli_sound_values( "heli_guard", "turbine", 10, 0.9, 1, 30, .9, 1.05 ); + init_heli_sound_values( "heli_guard", "rotor", 10, 0.9, 1, 30, .9, 1.1 ); + + +/# + if ( GetDvarString( "helisounds" ) == "" ) + { + SetDvar( "helisounds", "" ); + } + + level thread command_parser(); +#/ +} +/*chppoer_gunner_loop(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + if (newVal == 0) + { + audio::playloopat("mpl_cgunner_flir_lp", (0,0,0)); + } + else + { + audio::stoploopat ("mpl_cgunner_flir_lp", (0,0,0)); + } +} +*/ +function vehicle_is_firing_function(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + /# println("vehicle is firing : " + newVal); #/ + + if (newVal == 0) + { + self.isFiring = false; + } + else + { + self.isFiring = true; + } + +} + + +function init_heli_sound_values( heli_type, part_type, max_speed_vol, min_vol, max_vol, max_speed_pitch, min_pitch, max_pitch ) +{ + if ( !isdefined( level.heliSoundValues[heli_type] ) ) + { + level.heliSoundValues[heli_type] = []; + } + if ( !isdefined( level.heliSoundValues[heli_type][part_type] ) ) + { + level.heliSoundValues[heli_type][part_type] = spawnStruct(); + } + + level.heliSoundValues[heli_type][part_type].speedVolumeMax = max_speed_vol; + level.heliSoundValues[heli_type][part_type].speedPitchMax = max_speed_pitch; + level.heliSoundValues[heli_type][part_type].volumeMin = min_vol; + level.heliSoundValues[heli_type][part_type].volumeMax = max_vol; + level.heliSoundValues[heli_type][part_type].pitchMin = min_pitch; + level.heliSoundValues[heli_type][part_type].pitchMax = max_pitch; + /# + if( GetDvarint( "debug_heli" ) > 0 ) + { + println("Init Heli Sounds heli_type: " + heli_type ); + println("Init Heli Sounds part_type: " + part_type ); + println("Init Heli Sounds max_speed_vol: " + max_speed_vol ); + println("Init Heli Sounds min_vol: " + min_vol ); + println("Init Heli Sounds max_vol: " + max_vol ); + println("Init Heli Sounds max_speed_pitch: " + max_speed_pitch ); + println("Init Heli Sounds min_pitch: " + min_pitch ); + println("Init Heli Sounds max_pitch: " + max_pitch ); + } + #/ +} + +/# +function command_parser() +{ + while(1) + { + command = GetDvarString( "helisounds" ); + if ( command != "" ) + { + success = true; + tokens = strtok( command, " " ); + if ( !isdefined( tokens[0] ) || !isdefined(level.heliSoundValues[tokens[0]]) ) + { + if ( isdefined( tokens[0] ) ) + println("helisounds Did not recognize helicopter type: " + tokens[0] ); + else + println("helisounds Did not recognize helicopter type" ); + + + println("helisounds usage: helisounds " ); + + success = false; + } +// else if ( !isdefined( tokens[1] )|| !isdefined(level.heliSoundValues[heli_type][tokens[1]])) + else if ( !isdefined( tokens[1] ) ) + { + if ( isdefined( tokens[1] ) ) + println("helisounds Did not recognize helicopter part: " + tokens[0] + " for heli: " + tokens[1] ); + else + println("helisounds Did not recognize helicopter part for heli: " + tokens[0] ); + println("helisounds usage: helisounds " ); + success = false; + } + else if ( !isdefined( tokens[2] ) ) + { + println("helisounds Did not recognize helicopter value name for heli:" + tokens[0] + " part: " + tokens[1] ); + + println("helisounds usage: helisounds " ); + success = false; + } + else if ( !isdefined( tokens[3] ) ) + { + println("helisounds Did not recognize helicopter value for heli:" + tokens[0] + " part: " + tokens[1] ); + + println("helisounds usage: helisounds " ); + success = false; + } + + if ( success ) + { + heli_type = tokens[0]; + heli_part = tokens[1]; + value_name = tokens[2]; + value = float(tokens[3]); + //value = value / 10 + switch( value_name ) + { + case "volumemin": + level.heliSoundValues[heli_type][heli_part].volumeMin = value; + println("Setting volumeMin to " + value ); + break; + case "volumemax": + level.heliSoundValues[heli_type][heli_part].volumeMax = value; + println("Setting volumeMax to " + value ); + break; + case "pitchmin": + level.heliSoundValues[heli_type][heli_part].pitchMin = value; + println("Setting pitchMin to " + value ); + break; + case "pitchmax": + level.heliSoundValues[heli_type][heli_part].pitchMax = value; + println("Setting pitchMax to " + value ); + break; + case "speedvolumemax": + level.heliSoundValues[heli_type][heli_part].speedVolumeMax = value; + println("Setting speedVolumeMax to " + value ); + break; + case "speedpitchmax": + level.heliSoundValues[heli_type][heli_part].speedPitchMax = value; + println("Setting speedPitchMax to " + value ); + break; + default: + //iprintln("helisounds Did not recognize helicopter value name for heli:" + heli_name + " part: " + part_name + " value name:" + value_name); + println("no case match - helisounds usage: helisounds " ); + } + } + SetDvar( "helisounds", "" ); + } + wait(0.1); + } +} +#/ + +function init_heli_sounds_gunner() //tag, type, bone, run +{ + setup_heli_sounds( "lfe", "engine", "snd_cockpit", "veh_huey_rotor_lfe" ); // Distant LFE + setup_heli_sounds( "turbine", "engine", "snd_rotor", "veh_huey_turbine" ); // Turbine + setup_heli_sounds( "top_rotor", "engine", "snd_rotor", "veh_huey_rotor" ); // Top Rotor + setup_heli_sounds( "tail_rotor", "engine", "snd_tail_rotor", "veh_huey_tail" ); // Tail Rotor + setup_heli_sounds( "wind_rt", "engine", "snd_wind_right", "veh_huey_door_wind"); // Wind right + //setup_heli_sounds( "wind_lft", "engine", "snd_wind_left", "veh_huey_door_wind"); // Wind left + setup_heli_sounds( "radio", "engine", "snd_cockpit", "veh_huey_radio"); // radio cockpit + self.warning_tag = "snd_cockpit"; +} + +function init_heli_sounds_player_controlled() //tag, type, bone, run +{ + setup_heli_sounds( "lfe", "engine", "snd_cockpit", "veh_cobra_rotor_lfe" ); // Distant LFE + setup_heli_sounds( "turbine", "engine", "snd_rotor", "veh_cobra_turbine" ); // Turbine + setup_heli_sounds( "top_rotor", "engine", "snd_rotor", "veh_cobra_rotor" ); // Top Rotor + setup_heli_sounds( "tail_rotor", "engine", "snd_tail_rotor", "veh_cobra_tail" ); // Tail Rotor + self.warning_tag = "snd_cockpit"; +} + +function init_heli_sounds_supply() //tag, type, bone, run +{ + setup_heli_sounds( "lfe", "engine", undefined, "veh_supply_rotor_lfe" ); // Distant LFE + setup_heli_sounds( "turbine", "engine", undefined, "veh_supply_turbine" ); // Turbine + setup_heli_sounds( "top_rotor", "engine", undefined, "veh_supply_rotor" ); // Top Rotor + self.warning_tag = undefined; +} + +function init_heli_sounds_ai_attack() //tag, type, bone, run +{ + setup_heli_sounds( "lfe", "engine", undefined, "veh_hind_rotor_lfe" ); // Distant LFE + setup_heli_sounds( "turbine", "engine", undefined, "veh_hind_turbine" ); // Turbine + setup_heli_sounds( "top_rotor", "engine", undefined, "veh_hind_rotor" ); // Top Rotor + setup_heli_sounds( "tail_rotor", "engine", undefined, "veh_hind_tail" ); // Tail Rotor + + self.warning_tag = undefined; +} + +function init_heli_sounds_player_drone() //tag, type, bone, run +{ + setup_heli_sounds( "turbine_idle", "engine", "tag_body", "veh_qrdrone_turbine_idle" ); // Turbine + setup_heli_sounds( "turbine_moving", "engine", "tag_body", "veh_qrdrone_turbine_moving" ); // Turbine + setup_heli_sounds( "turn", "engine", "tag_body", "veh_qrdrone_idle_rotate" ); // Rotate angle + self.warning_tag = undefined; +} + +function init_heli_sounds_heli_guard() //tag, type, bone, run +{ + setup_heli_sounds( "lfe", "engine", undefined, "veh_overwatch_lfe" ); // Distant LFE + setup_heli_sounds( "turbine", "engine", undefined, "veh_overwatch_turbine" ); // Turbine + setup_heli_sounds( "rotor", "engine", undefined, "veh_overwatch_rotor" ); // Rotor + self.warning_tag = undefined; +} + + +function sound_linkto( parent, tag ) +{ + if( isdefined( tag ) ) + self linkto( parent, tag ); + else + self linkto( parent, "tag_body" ); +} + +function setup_heli_sounds( bone_location, type, tag, run, dmg1, dmg2, dmg3 ) +{ + self.heli[bone_location] = spawnStruct(); + self.heli[bone_location].sound_type = type; + + self.heli[bone_location].run = spawn( 0, self.origin, "script_origin" ); + self.heli[bone_location].run sound_linkto( self, tag ); + self.heli[bone_location].run.alias = run; + + self thread heli_loop_sound_delete( self.heli[bone_location].run ); + + if( isdefined( dmg1 ) ) + { + self.heli[bone_location].idle = spawn( 0, self.origin, "script_origin" ); + self.heli[bone_location].idle sound_linkto( self, tag ); + self.heli[bone_location].idle.alias = dmg1; + + self thread heli_loop_sound_delete( self.heli[bone_location].dmg1 ); + } + if( isdefined( dmg2 ) ) + { + self.heli[bone_location].idle = spawn( 0, self.origin, "script_origin" ); + self.heli[bone_location].idle sound_linkto( self, tag ); + self.heli[bone_location].idle.alias = dmg2; + + self thread heli_loop_sound_delete( self.heli[bone_location].dmg2 ); + } + if( isdefined( dmg3 ) ) + { + self.heli[bone_location].idle = spawn( 0, self.origin, "script_origin" ); + self.heli[bone_location].idle sound_linkto( self, tag ); + self.heli[bone_location].idle.alias = dmg3; + + self thread heli_loop_sound_delete( self.heli[bone_location].dmg3 ); + } +} + +function init_terrain_sounds() +{ + self.surface_type = []; + // dirt + self.surface_type["default"] = "dirt"; + self.surface_type["metal"] = "dirt"; + self.surface_type["concrete"] = "dirt"; + self.surface_type["wood"] = "dirt"; + self.surface_type["dirt"] = "dirt"; + self.surface_type["gravel"] = "dirt"; + self.surface_type["grass"] = "dirt"; + self.surface_type["mud"] = "dirt"; + self.surface_type["snow"] = "dirt"; + self.surface_type["asphalt"] = "dirt"; + self.surface_type["brick"] = "dirt"; + self.surface_type["glass"] = "dirt"; + self.surface_type["plaster"] = "dirt"; + self.surface_type["sand"] = "dirt"; + self.surface_type["rock"] = "dirt"; + // water + self.surface_type["water"] = "water"; + // foliage + self.surface_type["foliage"] = "dirt"; + //self.surface_type["foliage"] = "foliage"; + + // create ents + self setup_terrain_sounds( "dirt", "veh_chopper_prop_wash_dirt" ); + self setup_terrain_sounds( "water", "veh_chopper_prop_wash_water" ); + //self setup_terrain_sounds( "foliage", "veh_chopper_prop_wash_foliage" ); +} + +function setup_terrain_sounds( surface_type, alias ) //self == helicopter +{ + self.terrain_ent_array[surface_type] = spawn( 0, self.origin, "script_origin" ); + self.terrain_ent_array[surface_type].alias = alias; + + self thread heli_loop_sound_delete( self.terrain_ent_array[surface_type] ); +} +function setup_terrain_brass_sounds( surface_type, alias ) //self == helicopter +{ + self.terrain_brass_ent_array[surface_type] = spawn( 0, self.origin, "script_origin" ); + self.terrain_brass_ent_array[surface_type].alias = alias; + + self thread heli_loop_sound_delete( self.terrain_brass_ent_array[surface_type] ); +} +function start_helicopter_sounds( localClientNum ) +{ + //printLn( "^5 ********* in start_helicopter_sounds ***********" ); + + if( isdefined( self.sounddef ) ) + { + self.heli = []; + self.terrain = []; + self.sound_ents = []; + self.cur_speed = 0; + self.mph_to_inches_per_sec = 17.6; + self.speed_of_wind = 20;//40; // in MPH + self.idle_run_trans_speed = 5; // in MPH + + switch( self.sounddef ) + { + // cobra_mp - Default Attack heli - Cobra + case "veh_heli_ai_mp": + //self init_heli_sounds_ai_attack(); + //self play_attack_ai_sounds(); + break; + + // Overwatch - killstreak heli + case "veh_heli_guard_mp": + //self init_heli_sounds_heli_guard(); + //self play_heli_guard_sounds(); + break; + + // heli_supplydrop_mp - Supply drop killstreak heli - Chinoock + case "veh_heli_supplydrop_mp": + //self init_heli_sounds_supply(); + //self play_supply_sounds(); + break; + + // heli_gunner_mp - is the player gunner killstreak - Huey + case "veh_heli_gunner_mp": + //self init_heli_sounds_gunner(); + //self play_gunner_sounds(); + break; + + // heli_player_controlled_mp - is the player helicopter killstreak - hind + case "veh_heli_player_gunner_mp": + //self init_heli_sounds_player_controlled(); + //self play_player_controlled_sounds(); + break; + + case "veh_drn_qrdrone_mp": + //self init_heli_sounds_player_drone(); + //self play_player_drone_sounds(); + break; + + default: + /# printLn( "^5helicopter type: " + self.vehicletype + " vehicletype; playing no helicopter sounds" ); #/ + break; + } + self init_terrain_sounds(); + self thread terrain_trace(); + //print for debug + /# + if( GetDvarint( "debug_heli" ) > 0 ) + { + iprintLnbold( "helicopter type: " + self.vehicletype + " vehicletype" ); + } + #/ + } + else + { + /# printLn( "^6start_helicopter_sounds(): helicopter type not defined" ); #/ + } +} + +function heli_loop_sound_delete( real_ent ) +{ + self waittill( "entityshutdown" ); + real_ent Unlink(); + //wait (3); + real_ent StopAllLoopSounds( 4 ); + real_ent delete(); +} + +function heli_linkto_sound_ents_delete( localClientNum, entity ) +{ + entity notify( "entityshutdown" ); +} + +function heli_sound_play( heli_bone ) +{ + switch( heli_bone.sound_type ) + { + case "engine": + heli_bone.run playloopsound( heli_bone.run.alias, 2 ); + break; + case "wind": + break; + default: + /# printLn( "^6 unknown helicopter type: " + heli_bone.type + " expecting \"wind\" or \"engine\""); #/ + break; + } +} + +// ************************************************* +// PLAY HELICOPTER SOUNDS SECTION +// This section plays the Helicopter sounds +// heli_sound_play () - Starts a thread to play a loop unaffected by the entity speed +// heli_idle_run_transition () - Starts a thread that will pitch and volume the loop based on the arguments +// heli_bone - The tag to play the loop on, this is set to a bone in the init +// max_speed_vol - the speed at which volume will stop attenuation +// min_vol - This is the volume of the loop when not moving +// max_vol - This is the volume of the loop when at max_speed_vol +// max_speed_pitch - The speed at which pitch will no longer change +// min_pitch - This is the pitch of the loop when not moving +// max_pitch - This is the amount of pitch when at max_speed_vol +// ************************************************* +function play_player_controlled_sounds() // self == helicopter Cobra +{ + // ******* heli Cobra sounds, this is the player controled mp heli ************** + self heli_sound_play( self.heli["lfe"] ); // non-occulded, non-pitched 3d lfe + self thread heli_idle_run_transition( "cobra", "turbine" ); + self thread heli_idle_run_transition( "cobra", "top_rotor"); + self thread heli_idle_run_transition( "cobra", "tail_rotor"); +} + +function play_attack_ai_sounds() // self == helicopter +{ + // ******* heli Hind sounds, this is the AI controled mp heli ************** + self heli_sound_play( self.heli["lfe"] ); // non-occulded, non-pitched 3d lfe + self thread heli_idle_run_transition( "hind", "turbine" ); + self thread heli_idle_run_transition( "hind", "top_rotor"); + self thread heli_idle_run_transition( "hind", "tail_rotor"); +} + +function play_supply_sounds() // self == helicopter +{ + // ******* heli Chinoock sounds, this is the AI controled mp supply helicopter ************** + self thread heli_idle_run_transition( "supply", "turbine" ); + self thread heli_idle_run_transition( "supply", "top_rotor"); + self heli_sound_play( self.heli["lfe"] ); // non-occulded, non-pitched 3d lfe +} + +function play_gunner_sounds() // self == helicopter +{ + // ******* heli Huey sounds, this is the AI controled mp gunner helicopter, player hangs out side and fires ************** + self heli_sound_play( self.heli["lfe"] ); + self heli_sound_play( self.heli["radio"] ); // non-occulded, non-pitched 3d lfe + self thread heli_idle_run_transition( "huey", "turbine" ); + self thread heli_idle_run_transition( "huey", "top_rotor"); + self thread heli_idle_run_transition( "huey", "tail_rotor"); + self thread heli_idle_run_transition( "huey", "wind_rt" ); + //self thread heli_idle_run_transition( "huey", "wind_lft"); +} + +function play_player_drone_sounds() // self == helicopter Cobra +{ + // ******* qrdrone ************** + //self thread heli_idle_run_transition( "qrdrone", "turbine_idle", 0.1, 1 ); + //self thread heli_idle_run_transition( "qrdrone", "turbine_moving", 0.1, 1 ); + //self thread drone_up_down_transition(); + //self thread drone_rotate_angle( "qrdrone", "turn" ); +} + +function play_heli_guard_sounds() +{ + self heli_sound_play( self.heli["lfe"] ); + self thread heli_idle_run_transition( "heli_guard", "turbine" ); + self thread heli_idle_run_transition( "heli_guard", "rotor" ); + self thread terrain_trace_brass (); + //self thread tempBrassNotify (); +} +/* +function tempBrassNotify () +{ + while (1) + { + level waittill ("isfir"); + self.isfiring = true; + level waittill ("nofir"); + self.isfiring = false; + } +} +*/ +// ************************************************* +// END OF PLAY HELICOPTER SOUNDS SECTION +// ************************************************* + +// ************************************************* +// HELICOPTER VOLUME/PITCH CHANGES +// ************************************************* +function heli_idle_run_transition( heli_type, heli_part, wait_time, updown ) +{ + self endon( "entityshutdown" ); + + heli_bone = self.heli[heli_part]; + + run_id = heli_bone.run playloopsound( heli_bone.run.alias, 0.5 ); + + if (!isdefined( wait_time ) ) + wait_time = 0.5; + + while( isdefined( self ) ) + { + //audio::scale_speed( min_speed, max_speed, min_vol, max_vol, cur_speed ); + if( !isdefined( level.heliSoundValues[heli_type] ) || !isdefined( level.heliSoundValues[heli_type][heli_part] ) ) + { + /# printLn( "^5a speed vol/pitch parameter was not defined." ); #/ + return; + } + + max_speed_vol = level.heliSoundValues[heli_type][heli_part].speedVolumeMax; + min_vol = level.heliSoundValues[heli_type][heli_part].volumeMin; + max_vol = level.heliSoundValues[heli_type][heli_part].volumeMax; + max_speed_pitch = level.heliSoundValues[heli_type][heli_part].speedPitchMax; + min_pitch = level.heliSoundValues[heli_type][heli_part].pitchMin; + max_pitch = level.heliSoundValues[heli_type][heli_part].pitchMax; + + /* + self.cur_speed = abs (self getspeed()) / self.mph_to_inches_per_sec; + */ + + plr_vel = self getvelocity(); + self.cur_speed = abs (sqrt (VectorDot (plr_vel,plr_vel)) ) / self.mph_to_inches_per_sec; + + + run_volume = audio::scale_speed( self.idle_run_trans_speed, max_speed_vol, min_vol, max_vol, self.cur_speed ); + run_pitch = audio::scale_speed( self.idle_run_trans_speed, max_speed_pitch, min_pitch, max_pitch, self.cur_speed ); + + + //attenuate move loop volume when moving vertically + if (isdefined(updown)) + { + if (!isdefined (self.qrdrone_z_difference)) + { + self.qrdrone_z_difference = 0; + } + + run_volume_vertical = audio::scale_speed( 5, 50, 0, 1, abs (self.qrdrone_z_difference) ); + + run_volume = run_volume - run_volume_vertical; + } + + + + if( isdefined( run_volume ) && isdefined( run_pitch ) ) + { + //run_volume = 1; + //run_pitch = 1; + + //setSoundVolume( run_id, run_volume ); + //setSoundPitch( run_id, run_pitch ); + //setSoundPitchRate( run_id, 0.15 ); + heli_bone.run setloopstate(heli_bone.run.alias, run_volume, run_pitch, 1, .15); + + /# + if( GetDvarint( "debug_heli" ) > 0 ) + { + printLn( "^5a self.cur_speed = " + self.cur_speed ); + printLn( "^5a run_pitch . " + run_pitch ); + printLn( "^5a self.cur_speed = " + self.cur_speed ); + printLn( "^5a run_volume. " + run_volume ); + + } + #/ + } + + wait( wait_time ); + } +} +// ************************************************* +// HELICOPTER BRASS ON GROUND SOUNDS +// ************************************************* +function terrain_trace_brass() +{ + self endon( "entityshutdown" ); + + self setup_terrain_brass_sounds( "dirt", "prj_brass_loop_dirt" ); + self setup_terrain_brass_sounds( "water", "prj_brass_loop_water" ); + + self.isFiring = false; + + trace = undefined; + trace_ent = self; + + pre_terrain = undefined; + next_terrain = undefined; + + pre_trace_real_ent = undefined; + trace_real_ent = undefined; + pre_origin = ( 100000, 100000, 100000 ); + + while( isdefined( self ) ) + { + wait( 1 + RandomFloatRange( 0.0, 0.2) ); + + // do checks only if we moved more than a feet + if( DistanceSquared( pre_origin, trace_ent.origin ) < 144 ) + { + continue; + } + + pre_origin = trace_ent.origin; + + trace = tracepoint( trace_ent.origin, trace_ent.origin - ( 0, 0, 100000 ) ); + //println( "btrace " + trace_ent.origin ); + trace_surface_type = trace["surfacetype"]; + + if( !isdefined( trace ) ) + continue; + + pre_terrain = next_terrain; + next_terrain = trace_surface_type; + if( !isdefined( pre_terrain ) || !isdefined( next_terrain ) ) + { + //printLn( "heli_script: this is in non defined trerrain" ); + continue; + } + + if( !isdefined( self.surface_type[next_terrain] ) || !isdefined( self.surface_type[pre_terrain] ) ) + { + /# + //printLn( "^6 prop wash for terrain type: " + next_terrain + " or " + pre_terrain + " is not set up" ); + #/ + continue; + } + + surf_type = self.surface_type[next_terrain]; + trace_real_ent = self.terrain_brass_ent_array[surf_type]; + + pre_surf_type = self.surface_type[pre_terrain]; + pre_trace_real_ent = self.terrain_brass_ent_array[pre_surf_type]; + + if( !isdefined( trace["position"] ) ) + { + if( isdefined( pre_trace_real_ent ) ) + { + pre_trace_real_ent StopAllLoopSounds( 0.5 ); + //printLn( "^5heli_script: STOP playing terrain sound (position undefined): " + pre_surf_type ); + } + continue; + } + if (!self.isFiring) + { + pre_trace_real_ent StopAllLoopSounds( 0.5 ); + } + //update origin + trace_real_ent.origin = trace["position"]; + pre_trace_real_ent.origin = trace["position"]; + if( isdefined( surf_type ) && self.isFiring ) + { + if( ( surf_type == pre_surf_type ) && ( pre_trace_real_ent IsPlayingLoopSound() ) ) + continue; + + //printLn( "^5heli_script: STOP playing terrain sound: " + pre_surf_type + " surface '" + pre_terrain + "'" ); + pre_trace_real_ent StopAllLoopSounds( 0.5 ); + + //printLn( "^6heli_script: START playing terrain sound: " + surf_type + " surface '" + next_terrain + "'" ); + trace_real_ent PlayLoopSound( trace_real_ent.alias, 0.75 ); + } + } +} + +// ************************************************* +// HELICOPTER TERRAIN SOUNDS (PROP WASH) +// ************************************************* +function terrain_trace() +{ + self endon( "entityshutdown" ); + + trace = undefined; + trace_ent = self; + + pre_terrain = undefined; + next_terrain = undefined; + + pre_trace_real_ent = undefined; + trace_real_ent = undefined; + pre_origin = ( 100000, 100000, 100000 ); + + while( isdefined( self ) ) + { + wait( 1 + RandomFloatRange( 0.0, 0.2) ); + + // do checks only if we moved more than a feet + if( DistanceSquared( pre_origin, trace_ent.origin ) < 144 ) + { + continue; + } + + pre_origin = trace_ent.origin; + + trace = tracepoint( trace_ent.origin, trace_ent.origin - ( 0, 0, 100000 ) ); + //println( "btrace " + trace_ent.origin ); + trace_surface_type = trace["surfacetype"]; + + if( !isdefined( trace ) ) + continue; + + pre_terrain = next_terrain; + next_terrain = trace_surface_type; + if( !isdefined( pre_terrain ) || !isdefined( next_terrain ) ) + { + //printLn( "heli_script: this is in non defined trerrain" ); + continue; + } + + if( !isdefined( self.surface_type[next_terrain] ) || !isdefined( self.surface_type[pre_terrain] ) ) + { + /# + //printLn( "^6 prop wash for terrain type: " + next_terrain + " or " + pre_terrain + " is not set up" ); + #/ + continue; + } + + surf_type = self.surface_type[next_terrain]; + trace_real_ent = self.terrain_ent_array[surf_type]; + + pre_surf_type = self.surface_type[pre_terrain]; + pre_trace_real_ent = self.terrain_ent_array[pre_surf_type]; + + if( !isdefined( trace["position"] ) ) + { + if( isdefined( pre_trace_real_ent ) ) + { + pre_trace_real_ent StopAllLoopSounds( 0.5 ); + //printLn( "^5heli_script: STOP playing terrain sound (position undefined): " + pre_surf_type ); + } + continue; + } + + //update origin + trace_real_ent.origin = trace["position"]; + pre_trace_real_ent.origin = trace["position"]; + if( isdefined( surf_type ) ) + { + if( ( surf_type == pre_surf_type ) && ( pre_trace_real_ent IsPlayingLoopSound() ) ) + continue; + + //printLn( "^5heli_script: STOP playing terrain sound: " + pre_surf_type + " surface '" + pre_terrain + "'" ); + pre_trace_real_ent StopAllLoopSounds( 0.5 ); + + //printLn( "^6heli_script: START playing terrain sound: " + surf_type + " surface '" + next_terrain + "'" ); + trace_real_ent PlayLoopSound( trace_real_ent.alias, 0.5 ); + } + } +} + +function aircraft_dustkick(localClientNum) +{ + +/# printLn( "^stated aircraft_dustkick" ); #/ + self endon( "entityshutdown" ); + + maxHeight = 1200; + minHeight = 350; + + if ( self.vehicletype == "qrdrone_mp" ) + { + maxHeight = 120; + minHeight = 1; + } + + slowestRepeatWait = 0.15; + fastestRepeatWait = 0.05; + + numFramesPerTrace = 3; + doTraceThisFrame = numFramesPerTrace; + + defaultRepeatRate = 1.0; + repeatRate = defaultRepeatRate; + + trace = undefined; + d = undefined; + + trace_ent = self; + + while( isdefined( self ) ) + { + if( repeatRate <= 0 ) + { + repeatRate = defaultRepeatRate; + } + + if (!util::server_wait( localClientNum, repeatRate )) + { // wait failed, we may have rewinded + continue; + } + + if( !isdefined( self ) ) + { + return; + } + + doTraceThisFrame -- ; + + + if( doTraceThisFrame <= 0 ) + { + doTraceThisFrame = numFramesPerTrace; + + trace = bullettrace( trace_ent.origin, trace_ent.origin -( 0, 0, 100000 ), false, trace_ent ); + /* + trace["entity"] + trace["fraction"] + trace["normal"] + trace["position"] + trace["surfacetype"] + */ + + d = distance( trace_ent.origin, trace["position"] ); + + repeatRate = ( ( d - minHeight ) /( maxHeight - minHeight ) ) *( slowestRepeatWait - fastestRepeatWait ) + fastestRepeatWait; + } + + if( !isdefined( trace ) ) + { + continue; + } + + assert( isdefined( d ) ); + + if( d > maxHeight ) + { + repeatRate = defaultRepeatRate; + continue; + } + + if( isdefined( trace["entity"] ) ) + { + repeatRate = defaultRepeatRate; + continue; + } + + if( !isdefined( trace["position"] ) ) + { + repeatRate = defaultRepeatRate; + continue; + } + + if( !isdefined( trace["surfacetype"] ) ) + { + trace["surfacetype"] = "dirt"; + } + + //-- Glocke (12/16/2008) converted to prints from asserts while I come up with a better solution with Laufer + if ( !IsDefined( self.treadfxnamearray ) || !isdefined( self.treadfxnamearray[trace["surfacetype"]] ) ) + { + /# + if ( isdefined( self.vehicletype ) ) + { + println("SCRIPT PRINT: Unknown surface type " + trace["surfacetype"] + " for vehicle type " + self.vehicletype); + } + else + { + println("SCRIPT PRINT: Unknown surface type " + trace["surfacetype"] + " for vehicle of undefined vehicletype" ); + } + #/ + return; + } + //assert( isdefined( vehicle.treadfxnamearray[trace["surfacetype"]] ), "UNKNOWN SURFACE TYPE: " + trace["surfacetype"] ); + + + if( isdefined( self.treadfxnamearray[trace["surfacetype"]] ) ) + { + //printLn( "^6this is defaulting dirt" ); + //println("SCRIPT PRINT: Unknown surface type " + trace["surfacetype"] + " for vehicle type " + self.vehicletype); + playfx( localClientNum, self.treadfxnamearray[trace["surfacetype"]], trace["position"] ); + //print3d( trace["position"], "+" + self.treadfxnamearray[trace["surfacetype"]], ( 0, 1, 0 ), 1, 3, 30 ); + } + } +} + +function play_targeting_sound( play, sound, handle ) +{ + sound_ent = get_lock_sound_ent(); + if ( play ) + { + return sound_ent PlayLoopSound ( sound ); + } + else if ( isdefined(handle) ) + { + sound_ent StopAllLoopSounds( 0.1 ); + return undefined; + } +} + +function play_targeted_sound( play ) +{ + self.lockingSound = play_targeting_sound( play, "veh_hind_alarm_missile_locking_mp", self.lockingSound ); +} + +function play_locked_sound(play) +{ + self.lockedSound = play_targeting_sound( play, "veh_hind_alarm_missile_locked_mp", self.lockedSound ); +} + +function play_fired_sound(play) +{ + self.firedSound = play_targeting_sound( play, "veh_hind_alarm_missile_fired", self.firedSound ); +} + +function play_leaving_battlefield_alarm(play) +{ + sound_ent = get_leaving_sound_ent(); + if ( play ) + { + self.leavingBattlefieldSound = sound_ent PlayLoopSound ( "veh_helicopter_alarm" ); + } + else if ( isdefined(self.leavingBattlefieldSound) && self.leavingBattlefieldSound ) + { + sound_ent StopAllLoopSounds( 0.1 ); + } +} + +function get_heli_sound_ent( sound_ent ) +{ + if ( !isdefined(sound_ent)) + { + tag = "tag_origin"; + if ( isdefined(self.warning_tag) ) + { + tag = self.warning_tag; + } + sound_ent = spawn( 0, self GetTagOrigin( tag ), "script_origin" ); + sound_ent linkto( self, tag ); + + self thread heli_sound_ent_delete( sound_ent ); + } + + return sound_ent; +} + +function get_lock_sound_ent() +{ + self.lock_sound_ent = get_heli_sound_ent( self.lock_sound_ent ); + + return self.lock_sound_ent; +} + +function get_leaving_sound_ent() +{ + self.leaving_sound_ent = get_heli_sound_ent( self.leaving_sound_ent ); + + return self.leaving_sound_ent; +} + +function heli_sound_ent_delete( real_ent ) +{ + self waittill( "entityshutdown" ); + real_ent StopAllLoopSounds( 0.1 ); + real_ent delete(); +} + + + +function drone_up_down_transition() +{ + self endon( "entityshutdown" ); + + volumerate = 1; + + qr_ent_up = spawn( 0, self.origin, "script_origin" ); + qr_ent_down = spawn( 0, self.origin, "script_origin" ); + qr_ent_either = spawn( 0, self.origin, "script_origin" ); + + qr_ent_up thread qr_ent_cleanup(self); + qr_ent_down thread qr_ent_cleanup(self); + qr_ent_either thread qr_ent_cleanup(self); + + self.qrdrone_z_difference = 0; + + down = qr_ent_down playloopsound ("veh_qrdrone_move_down"); + //setSoundVolume( down, 0 ); + qr_ent_down setloopstate("veh_qrdrone_move_down", 0, 0); + + + up = qr_ent_up playloopsound ("veh_qrdrone_move_up"); + //setSoundVolume( up, 0 ); + qr_ent_up setloopstate("veh_qrdrone_move_up", 0, 0); + + + either = qr_ent_either playloopsound ("veh_qrdrone_vertical"); + //setSoundVolume( either, 0 ); + qr_ent_either setloopstate("veh_qrdrone_vertical", 0, 0); + + + tag = "tag_body"; + qr_ent_up linkto (self, tag); + qr_ent_down linkto (self, tag); + qr_ent_either linkto (self, tag); + + //self thread drone_button_watch("BUTTON_RSHLDR"); + //self thread drone_button_watch("BUTTON_LSHLDR"); + self thread drone_button_watch(); + + while(1) + { + last_pos = self.origin[2]; + wait (.1); + //curr_pos = self.origin[2]; + + self.qrdrone_z_difference = (last_pos - self.origin[2]); + + if ( self.qrdrone_z_difference < 0) + { + up_difference = self.qrdrone_z_difference * -1; + run_volume_up = audio::scale_speed( 5, 40, 0, 1, up_difference ); + run_pitch_up = audio::scale_speed( 5, 40, .9, 1.1, up_difference ); + + run_volume_either = audio::scale_speed( 5, 50, 0, 1, up_difference ); + run_pitch_either = audio::scale_speed( 5, 50, .9, 1.1, up_difference ); + } + else + { + run_volume_up = 0; + run_pitch_up = 1; + + run_volume_either = audio::scale_speed( 5, 50, 0, 1, self.qrdrone_z_difference ); + run_pitch_either = audio::scale_speed( 5, 50, .95, .8, self.qrdrone_z_difference ); + } + + run_volume_down = audio::scale_speed( 5, 50, 0, 1, self.qrdrone_z_difference ); + run_pitch_down = audio::scale_speed( 5, 50, 1, .8, self.qrdrone_z_difference ); + + /* + setSoundVolumeRate (up, volumerate); + setSoundVolumeRate (down, volumerate); + setSoundVolumeRate (either, volumerate); + + setSoundVolume( up, run_volume_up ); + setSoundPitch( up, run_pitch_up ); + setSoundVolume( down, run_volume_down ); + setSoundPitch( down, run_pitch_down ); + + setSoundVolume( either, run_volume_either ); + setSoundPitch( either, run_pitch_either ); + */ + + qr_ent_down setloopstate("veh_qrdrone_move_down", run_volume_down, run_pitch_down, volumerate); + + qr_ent_up setloopstate("veh_qrdrone_move_up", run_volume_up, run_pitch_up, volumerate); + + qr_ent_either setloopstate("veh_qrdrone_vertical", run_volume_either, run_pitch_either, volumerate); + + } +} + + +function qr_ent_cleanup(veh_ent) +{ + veh_ent waittill ("entityshutdown"); + self delete(); +} + + +function drone_rotate_angle( heli_type, heli_part ) //self == drone +{ + self endon( "entityshutdown" ); + level endon( "save_restore" ); + + volumerate = 2.5; + + qr_ent_angle = spawn( 0, self.origin, "script_origin" ); + qr_ent_angle thread qr_ent_cleanup (self); + + angle = qr_ent_angle playloopsound ("veh_qrdrone_idle_rotate"); + setSoundVolume( angle, 0 ); + + tag = "tag_body"; + qr_ent_angle linkto (self, tag); + + + while( 1 ) + { + + last_angle = abs( self.angles[1] ); + + wait (.1); + + turning_speed = last_angle - abs( self.angles[1] ); + + abs_turning_speed = abs(turning_speed); + + + + jet_stick_vol = audio::scale_speed ( 0, 5, 0, .4, abs_turning_speed); + jet_stick_pitch = audio::scale_speed ( 0, 4, .9, 1.05, abs_turning_speed); + + //setSoundVolumeRate (angle, volumerate); + //setSoundVolume ( angle, jet_stick_vol ); + //setSoundPitch ( angle, jet_stick_pitch ); + + qr_ent_angle setloopstate("veh_qrdrone_idle_rotate", jet_stick_vol, jet_stick_pitch, volumerate); + } +} + + + +function drone_button_watch() +{ + self endon( "entityshutdown" ); + + player = getlocalplayers()[0]; + return_to_zero= true; + + while (1) + { + if ( abs (self.qrdrone_z_difference) > 5 && return_to_zero) + { + self playsound (0, "veh_qrdrone_move_start"); + return_to_zero = false; + + /* + while (abs (self.qrdrone_z_difference) > 10) + { + wait (.05); + } + + check_speed = audio::scale_speed( 5, 50, 0, 1, self.qrdrone_z_difference ); + while (check_speed < .1) + { + wait (.05); + check_speed = audio::scale_speed( 5, 50, 0, 1, self.qrdrone_z_difference ); + } + */ + + } + + else if (abs (self.qrdrone_z_difference) < 5 && !return_to_zero ) + { + return_to_zero = true; + } + wait (.05); + } +} diff --git a/mp/_hive_gun.csc b/mp/_hive_gun.csc new file mode 100644 index 0000000..6fe7c42 --- /dev/null +++ b/mp/_hive_gun.csc @@ -0,0 +1,23 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_hive_gun; + + + + +#using scripts\mp\_util; + +#namespace hive_gun; + +function autoexec __init__sytem__() { system::register("hive_gun",&__init__,undefined,undefined); } + +function __init__() +{ + hive_gun::init_shared(); +} + + diff --git a/mp/_hive_gun.gsc b/mp/_hive_gun.gsc new file mode 100644 index 0000000..a8be731 --- /dev/null +++ b/mp/_hive_gun.gsc @@ -0,0 +1,19 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_hive_gun; + + + +#using scripts\mp\_util; + +#namespace hive_gun; + +function autoexec __init__sytem__() { system::register("hive_gun",&__init__,undefined,undefined); } + +function __init__() +{ + hive_gun::init_shared(); +} + diff --git a/mp/_incendiary.gsc b/mp/_incendiary.gsc new file mode 100644 index 0000000..6f2ecf2 --- /dev/null +++ b/mp/_incendiary.gsc @@ -0,0 +1,488 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\entityheadicons_shared; +#using scripts\shared\killcam_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_tacticalinsertion; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\_burnplayer; + + + + + +#namespace incendiary; + +function autoexec __init__sytem__() { system::register("incendiary_grenade",&init_shared,undefined,undefined); } + + +function init_shared() +{ + level.incendiaryfireDamage = GetDvarInt( "scr_incendiaryfireDamage", 35 ); // how much damage will the fire do each tick + level.incendiaryfireDamageHardcore = GetDvarInt( "scr_incendiaryfireDamageHardcore", 15 ); // how much damage will the fire do each tick in hardcore + level.incendiaryfireDuration = GetDvarInt ("scr_incendiaryfireDuration", 5 ); // time damage triggers will last. + level.incendiaryfxDuration = GetDvarFloat( "scr_incendiaryfxDuration", 0.4 ); // Incendiary fx duration + level.incendiaryDamageRadius = GetDvarInt( "scr_incendiaryDamageRadius", 125 ); // radius of individual damages + level.incendiaryfireDamageTickTime = GetDvarFloat( "scr_incendiaryfireDamageTickTime", 1 ); // time between damage hits + + + level.incendiaryDamageThisTick = []; + + callback::on_spawned( &create_incendiary_watcher ); +} + +/# +function updateIncendiaryFromDvars() +{ + level.incendiaryfireDamage = GetDvarInt( "scr_incendiaryfireDamage", level.incendiaryfireDamage ); + level.incendiaryfireDamageHardcore = GetDvarInt( "scr_incendiaryfireDamageHardcore", level.incendiaryfireDamageHardcore ); + level.incendiaryfireDuration = GetDvarInt( "scr_incendiaryfireDuration", level.incendiaryfireDuration ); + level.incendiaryDamageRadius = GetDvarInt( "scr_incendiaryDamageRadius", level.incendiaryDamageRadius ); + level.incendiaryfireDamageTickTime = GetDvarFloat( "scr_incendiaryfireDamageTickTime", level.incendiaryfireDamageTickTime ); + level.incendiaryfxDuration = GetDvarFloat( "scr_incendiaryfxDuration", level.incendiaryfxDuration ); +} +#/ + +function create_incendiary_watcher() // self == player +{ + watcher = self weaponobjects::createUseWeaponObjectWatcher( "incendiary_grenade", self.team ); + + watcher.onSpawn = &incendiary_system_spawn; +} + +function incendiary_system_spawn( watcher, player ) // self == incendiary grenade +{ + player endon( "death" ); + player endon( "disconnect" ); + level endon( "game_ended" ); + + player AddWeaponStat( self.weapon, "used", 1 ); + thread watchForExplode( player ); +} + + +function watchForExplode( owner ) +{ + self endon( "hacked" ); + self endon( "delete" ); + + killCamEnt = spawn( "script_model", self.origin ); + killCamEnt util::deleteAfterTime( 15.0 ); + killCamEnt.startTime = gettime(); + killCamEnt linkto( self ); + killCamEnt setWeapon( self.weapon ); + + killcamEnt killcam::store_killcam_entity_on_entity(self); + + self waittill( "projectile_impact_explode", origin, normal, surface ); + killCamEnt unlink(); + /# + updateIncendiaryFromDvars(); + #/ + PlaySoundAtPosition ("wpn_incendiary_core_start" ,self.origin); + + generateLocations( origin, owner, normal, killCamEnt ); +} + +function getstepoutdistance( normal ) +{ + if ( normal[2] < 0.5 ) + { + stepoutdistance = normal * GetDvarInt( "scr_incendiary_stepout_wall", 50 ); + } + else + { + stepoutdistance = normal * GetDvarInt( "scr_incendiary_stepout_ground", 12 ); + } + return stepoutdistance; +} + +function generateLocations( position, owner, normal, killCamEnt ) +{ + startPos = position + getstepoutdistance( normal ); + desiredEndPos = startPos + ( 0, 0, 60 ); + physTrace = PhysicsTrace( startPos, desiredEndPos, ( -4, -4, -4 ), ( 4, 4, 4 ), self, (1 << 0) ); + goalPos = ( ( physTrace[ "fraction" ] < 1 ) ? physTrace[ "position"] : desiredEndPos ); + + killCamEnt moveto( goalPos, 0.5 ); + rotation = RandomInt( 360 ); + + if ( normal[2] < 0.1 ) // vertical wall + { + black = ( 0.1, 0.1, 0.1 ); + + trace = hitPos( startPos, startpos + ( -normal * 70 ) + ( 0,0, -1 ) * 70, black ); + tracePosition = trace["position"]; + incendiaryGrenade = GetWeapon( "incendiary_fire" ); + + if ( trace["fraction"] < 0.9 ) + { + wallnormal = trace["normal"]; + SpawnTimedFX( incendiaryGrenade, trace["position"], wallnormal, level.incendiaryfireDuration, self.team ); + } + } + + fxCount = GetDvarInt( "scr_incendiary_fx_count", 6 ); + spawnAllLocs( owner, startPos, normal, 1, rotation, killcament, fxCount ); +} + +function getLocationForFX( startPos, fxIndex, fxCount, defaultDistance, rotation ) +{ + currentAngle = ( ( 360 / fxCount ) * fxIndex ); + cosCurrent = cos( currentAngle + rotation ); + sinCurrent = sin( currentAngle + rotation ); + + return startPos + ( defaultDistance * cosCurrent, defaultDistance * sinCurrent, 0 ); +} + +function spawnAllLocs( owner, startPos, normal, multiplier, rotation, killcament, fxCount ) +{ + defaultDistance = GetDvarInt( "scr_incendiary_trace_distance", 220 ) * multiplier; + defaultDropDistance = GetDvarInt( "scr_incendiary_trace_down_distance", 90 ); + + // DROCHE:TODO + // if we are going to keep this grenade this should be moved to code + + colorArray = []; + colorArray[colorArray.size] = ( 0.9, 0.2, 0.2 ); + colorArray[colorArray.size] = ( 0.2, 0.9, 0.2 ); + colorArray[colorArray.size] = ( 0.2, 0.2, 0.9 ); + colorArray[colorArray.size] = ( 0.9, 0.9, 0.9 ); + + + locations = []; + locations["color"] = []; + locations["loc"] = []; + locations["tracePos"] = []; + locations["distSqrd"] = []; + locations["fxtoplay"] = []; + locations["radius"] = []; + + + for( fxIndex = 0; fxIndex < fxCount; fxIndex++ ) + { + locations["point"][fxIndex] = getLocationForFX( startPos, fxIndex, fxCount, defaultDistance, rotation ); + locations["color"][fxIndex] = colorArray[fxIndex % colorArray.size]; + } + + for ( count = 0; count < fxCount; count++ ) + { + trace = hitPos( startPos, locations["point"][count], locations["color"][count] ); + tracePosition = trace["position"]; + locations["tracePos"][count] = tracePosition; + + if ( trace["fraction"] < 0.7 ) + { + locations["loc"][count] = tracePosition; + locations["normal"][count] = trace["normal"]; + continue; + } + + average = startPos/2 + tracePosition/2; + + trace = hitPos( average, average - ( 0, 0, defaultDropDistance ), locations["color"][count] ); + if ( trace["fraction"] != 1 ) + { + locations["loc"][count] = trace["position"]; + locations["normal"][count] = trace["normal"]; + } + } + + // startPos = startPos - getstepoutdistance( normal ); // start pos is already a good position for fx, we are using a different sized trigger now. + + incendiaryGrenade = GetWeapon( "incendiary_fire" ); + + SpawnTimedFX( incendiaryGrenade, startPos, normal, level.incendiaryfireDuration, self.team ); + + level.incendiaryDamageRadius = GetDvarInt( "scr_incendiaryDamageRadius", level.incendiaryDamageRadius ); + + thread damageEffectArea ( owner, startPos, level.incendiaryDamageRadius, level.incendiaryDamageRadius, killCamEnt ); + + for ( count = 0; count < locations["point"].size; count++ ) + { + if ( isdefined ( locations["loc"][count] ) ) + { + normal = locations["normal"][count]; + + SpawnTimedFX( incendiaryGrenade, locations["loc"][count], normal, level.incendiaryfireDuration, self.team ); + } + } +} + +/# +function incendiary_debug_line( from, to, color, depthTest, time ) +{ + debug_rcbomb = GetDvarInt( "scr_incendiary_debug", 0 ); + if ( debug_rcbomb == 1 ) + { + if ( !isdefined(time) ) + { + time = 100; + } + if ( !isdefined(depthTest) ) + { + depthTest = true; + } + Line( from, to, color, 1, depthTest, time); + } +} +#/ + + + +function damageEffectArea ( owner, position, radius, height, killCamEnt ) +{ + trigger_radius_position = position - ( 0 , 0, height ); + trigger_radius_height = height * 2; + + fireEffectArea = spawn( "trigger_radius", trigger_radius_position, 0, radius, trigger_radius_height ); + + // Create head icon +// objective = GetEquipmentHeadObjective( GetWeapon( "incendiary_grenade" ) ); +// killCamEnt entityheadicons::setEntityHeadIcon( owner.pers["team"], owner, (0,0,0), objective ); + +/# + if( GetDvarint( "scr_draw_triggers" ) ) + level thread util::drawcylinder( trigger_radius_position, radius, trigger_radius_height, undefined, "incendiary_draw_cylinder_stop" ); +#/ + + // raps stuff + if ( isdefined( level.rapsOnBurnRaps ) ) + { + owner thread [[level.rapsOnBurnRaps]]( fireEffectArea ); + } + + ownerOriginalTeam = owner.team; + + // loop variables + loopWaitTime = level.incendiaryFireDamageTickTime; + durationOfIncendiary = level.incendiaryFireDuration; + + // loop for the duration of the effect + while (durationOfIncendiary > 0) + { + if ( isdefined( owner ) && owner.team !== ownerOriginalTeam ) + { + break; + } + + durationOfIncendiary -= loopWaitTime; + damageApplied = false; + + potential_targets = self getPotentialTargets( owner ); + foreach( target in potential_targets ) + { + self tryToApplyFireDamage( target, owner, position, fireEffectArea, loopWaitTime, killcament ); + } + + wait (loopWaitTime); + } + + // Delete head icon + if ( isdefined( killCamEnt ) ) + killCamEnt entityheadicons::destroyEntityHeadIcons(); + // clean up + fireEffectArea delete(); + +/# + if( GetDvarint( "scr_draw_triggers" ) ) + level notify( "incendiary_draw_cylinder_stop" ); +#/ +} + +function getPotentialTargets( owner ) // self == incendiary grenade +{ + // try getting team based targets first + owner_team = ( isdefined( owner ) ? owner.team : undefined ); + if ( level.teambased && isdefined( owner_team ) && level.friendlyfire == 0 ) + { + enemy_team = ( owner_team == "axis" ? "allies" : "axis" ); + potential_targets = []; + potential_targets = ArrayCombine( potential_targets, GetPlayers( enemy_team ), false, false ); + potential_targets = ArrayCombine( potential_targets, GetAITeamArray( enemy_team ), false, false ); + potential_targets = ArrayCombine( potential_targets, GetVehicleTeamArray( enemy_team ), false, false ); + potential_targets[ potential_targets.size ] = owner; + return potential_targets; + } + + // now get all targets + all_targets = []; + all_targets = ArrayCombine( all_targets, level.players, false, false ); + all_targets = ArrayCombine( all_targets, GetAIArray(), false, false ); + all_targets = ArrayCombine( all_targets, GetVehicleArray(), false, false ); + + // if this is hardcore, then every entity is a potential target + if ( level.friendlyfire > 0 ) + return all_targets; + + // remove all targets not on the same team, except owner + potential_targets = []; + foreach( target in all_targets ) + { + if ( !isdefined( target ) ) + continue; + + if( !isdefined( target.team ) ) + continue; + + if( isdefined( owner ) ) + { + if( target != owner ) + { + if( !isdefined( owner_team ) ) + continue; + + if( target.team == owner_team ) + continue; + } + } + else + { + if ( !isdefined( self ) ) + continue; + + if( !isdefined( self.team ) ) + continue; + + if( target.team == self.team ) + continue; + } + + potential_targets[ potential_targets.size ] = target; + } + + return potential_targets; +} + +function tryToApplyFireDamage( target, owner, position, fireEffectArea, resetFireTime, killcament ) // self == incendiary grenade +{ + // see if we're not in the fire area + if ( ( !isdefined(target.infireArea) ) || (target.infireArea == false) ) + { + // since we're not in the poison area, now see if we're in the fire area + if ( target istouching(fireEffectArea) && ( !isdefined(target.sessionstate) || target.sessionstate == "playing" ) ) + { + trace = bullettrace( position, target GetShootAtPos(), false, target, true ); + + if ( trace["fraction"] == 1 ) + { + target.lastburnedBy = owner; + target thread damageInFireArea( fireEffectArea, killcament, trace, position, resetFireTime ); + } + } + } +} + +function damageInFireArea( fireEffectArea, killcament, trace, position, resetFireTime ) // self == player in fire area +{ + self endon( "disconnect" ); + self endon( "death" ); + + timer = 0; + + damage = level.incendiaryfireDamage; + if( level.hardcoreMode ) + { + damage = level.incendiaryfireDamageHardcore; + } + + if ( canDoFireDamage( killCamEnt, self, resetFireTime ) ) + { +/# + level.incendiary_debug = GetDvarInt( "scr_incendiary_debug", 0 ); + if ( level.incendiary_debug ) + { + if ( !isdefined( level.incendiaryDamageTime ) ) + { + level.incendiaryDamageTime = GetTime(); + } + + iprintlnbold( level.incendiaryDamageTime - getTime() ); + level.incendiaryDamageTime = getTime(); + } +#/ + self DoDamage( damage, fireEffectArea.origin, self.lastburnedBy, killCamEnt, "none", "MOD_BURNED", 0, GetWeapon( "incendiary_fire" ) ); + + entnum = self getentitynumber(); + + self thread sndFireDamage(); + } +} + + +function sndFireDamage() +{ + self notify( "sndFire" ); + self endon( "death" ); + self endon( "disconnect" ); + self endon( "sndFire" ); + + if( !isdefined( self.sndFireEnt ) ) + { + self.sndFireEnt = spawn( "script_origin", self.origin ); + self.sndFireEnt linkto( self, "tag_origin" ); + self.sndFireEnt playsound( "chr_burn_start" ); + self thread sndFireDamage_DeleteEnt(self.sndFireEnt); + } + + self.sndFireEnt playloopsound( "chr_burn_start_loop", .5 ); + wait(3); + self.sndFireEnt delete(); + self.sndFireEnt = undefined; +} +function sndFireDamage_DeleteEnt(ent) +{ + self endon( "disconnect" ); + self waittill( "death" ); + + if( isdefined( ent ) ) + ent delete(); //pfx_fire_incendiary +} + + +function hitPos( start, end, color ) +{ + trace = bullettrace( start, end, false, undefined ); + +/# + level.incendiary_debug = GetDvarInt( "scr_incendiary_debug", 0 ); + if ( level.incendiary_debug ) + { + debugstar(trace["position"], 2 * 1000, color); + } + + thread incendiary_debug_line( start, trace["position"], color, true, 80 ); +#/ + + return trace; +} + +function canDoFireDamage( killCamEnt, victim, resetFireTime ) +{ + entNum = victim getentitynumber(); + if ( !isdefined( level.incendiaryDamageThisTick[entNum] ) ) + { + level.incendiaryDamageThisTick[entNum] = 0; + level thread resetFireDamage( entnum, resetFireTime ); + return true; + } + + return false; +} + +function resetFireDamage( entnum, time ) +{ + if ( time > 0.05 ) + { + wait( time - 0.05 ); + } + level.incendiaryDamageThisTick[entnum] = undefined; +} + + \ No newline at end of file diff --git a/mp/_laststand.gsc b/mp/_laststand.gsc new file mode 100644 index 0000000..cab293f --- /dev/null +++ b/mp/_laststand.gsc @@ -0,0 +1,275 @@ +// tagTMR: this is a stripped down last stand implementation for the purposes of hooking into +// the resurrect gadget. Removed suicide and revive triggers, all hud elements, anything cp/zm specific + +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\killcam_shared; +#using scripts\shared\laststand_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\clientfield_shared; + +#using scripts\shared\abilities\gadgets\_gadget_resurrect; +#using scripts\shared\abilities\_ability_util; + + + + +#using scripts\mp\gametypes\_globallogic_player; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_killcam; + +#namespace laststand; + +function autoexec __init__sytem__() { system::register("laststand",&__init__,undefined,undefined); } + +function __init__() +{ + if (level.script=="frontend") + { + return ; + } +} + +function player_last_stand_stats( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + //stat tracking + if ( IsDefined( attacker ) && IsPlayer( attacker ) && attacker != self ) + { + attacker.kills++; + + if (isdefined(weapon)) + { + dmgweapon = weapon; + + weaponPickedUp = false; + if( isdefined( attacker.pickedUpWeapons ) && isdefined( attacker.pickedUpWeapons[weapon] ) ) + { + weaponPickedUp = true; + } + + attacker AddWeaponStat(dmgweapon, "kills", 1, attacker.class_num, weaponPickedUp ); + } + } + + self.downs++; +} + +function PlayerLastStand( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, delayOverride ) +{ + if( self player_is_in_laststand() ) + { + return; + } + + if ( isdefined( self.resurrect_not_allowed_by ) ) + { + return; + } + + self globallogic_player::Callback_PlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, 0, true ); + + self notify("entering_last_stand"); + + // check to see if we are in a game module that wants to do something with PvP damage + if( isDefined( level._game_module_player_laststand_callback ) ) + { + self [[ level._game_module_player_laststand_callback ]]( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, delayOverride ); + } + + self.lastStandParams = spawnstruct(); + self.lastStandParams.eInflictor = eInflictor; + self.lastStandParams.attacker = attacker; + self.lastStandParams.iDamage = iDamage; + self.lastStandParams.sMeansOfDeath = sMeansOfDeath; + self.lastStandParams.sWeapon = weapon; + self.lastStandParams.vDir = vDir; + self.lastStandParams.sHitLoc = sHitLoc; + self.lastStandParams.lastStandStartTime = gettime(); + self.lastStandParams.killcam_entity_info_cached = killcam::get_killcam_entity_info( attacker, eInflictor, weapon ); + + self thread player_last_stand_stats( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, delayOverride ); + + self.health = 1; + self.laststand = true; + self.ignoreme = true; + self EnableInvulnerability(); + self.meleeAttackers = undefined; + self.no_revive_trigger = true; + + callback::callback( #"on_player_laststand" ); + + assert( IsDefined( self.resurrect_weapon ) ); // defined in gadget_resurrect.gsc + assert( self.resurrect_weapon != level.weaponNone ); + + slot = self ability_util::gadget_slot_for_type( 40 ); + self GadgetStateChange( slot, self.resurrect_weapon, 2 ); + self laststand_disable_player_weapons(); + self thread MakeSureSwitchToWeapon(); + self thread resurrect::enter_rejack_standby(); // UI and input watchers + self thread watch_player_input(); + + demo::bookmark( "player_downed", gettime(), self ); +} + +function MakeSureSwitchToWeapon() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "bleed_out" ); + self endon( "player_input_revive" ); + self endon( "player_input_suicide" ); + level endon("game_ended"); + + while( 1 ) + { + if( self getcurrentweapon() != self.resurrect_weapon ) + { + self SwitchToWeapon( self.resurrect_weapon ); + } + {wait(.05);}; + } +} + +// self = a player +function laststand_disable_player_weapons() +{ + weaponInventory = self GetWeaponsList( true ); + self.lastActiveWeapon = self GetCurrentWeapon(); + if ( self IsThrowingGrenade() ) + { + primaryWeapons = self GetWeaponsListPrimaries(); + if( IsDefined( primaryWeapons ) && primaryWeapons.size > 0 ) + { + self.lastActiveWeapon = primaryWeapons[0]; + self SwitchToWeaponImmediate( self.lastActiveWeapon ); + } + } +} + +function laststand_enable_player_weapons( b_allow_grenades = true ) // self == player +{ + self EnableWeaponCycling(); + + if ( b_allow_grenades ) + { + self EnableOffhandWeapons(); + } + + if( IsDefined(self.lastActiveWeapon) && (self.lastActiveWeapon != level.weaponNone) && self HasWeapon( self.lastActiveWeapon ) ) + { + self SwitchToWeapon( self.lastActiveWeapon ); + } + else + { + primaryWeapons = self GetWeaponsListPrimaries(); + if( IsDefined( primaryWeapons ) && primaryWeapons.size > 0 ) + { + self SwitchToWeapon( primaryWeapons[0] ); + } + } +} + +function laststand_clean_up_on_interrupt( playerBeingRevived, reviverGun ) +{ + self endon( "do_revive_ended_normally" ); + + reviveTrigger = playerBeingRevived.revivetrigger; + + playerBeingRevived util::waittill_any( "disconnect", "game_ended", "death" ); + + if( isdefined( reviveTrigger ) ) + { + reviveTrigger delete(); + } + self cleanup_suicide_hud(); + + if( isdefined( self.reviveProgressBar ) ) + { + self.reviveProgressBar hud::destroyElem(); + } + + if( isdefined( self.reviveTextHud ) ) + { + self.reviveTextHud destroy(); + } +} + +function laststand_clean_up_reviving_any( playerBeingRevived ) +{ + self endon( "do_revive_ended_normally" ); + + playerBeingRevived util::waittill_any( "disconnect", "zombified", "stop_revive_trigger" ); + + self.is_reviving_any--; + if ( 0 > self.is_reviving_any ) + { + self.is_reviving_any = 0; + } +} + +function bleed_out() +{ + demo::bookmark( "player_bledout", gettime(), self, undefined, 1 ); + + level notify("bleed_out", self.characterindex); + + self UndoLastStand(); + self.ignoreme = false; + self.laststand = undefined; + + self.useLastStandParams = true; + + // attacker may have become undefined if the player that killed me has disconnected + if ( !isDefined( self.lastStandParams.attacker ) ) + { + self.lastStandParams.attacker = self; + } + + self suicide(); +} + +function watch_player_input() // self == player +{ + self thread watch_player_input_revive(); + self thread watch_player_input_suicide(); +} + +function watch_player_input_revive() // self == player +{ + level endon("game_ended"); + + self endon( "player_input_bleed_out" ); + self endon( "disconnect" ); + self endon( "death" ); + + self waittill("player_input_revive"); + + demo::bookmark( "player_revived", gettime(), self, self ); + + self Rejack(); // TODO: split this out into gadget-specific territory and keep this file last stand only + + self laststand_enable_player_weapons(); + + self.ignoreme = false; + self DisableInvulnerability(); + self.laststand = undefined; +} + +function watch_player_input_suicide() // self == player +{ + level endon("game_ended"); + + self endon( "player_input_revive" ); + self endon( "disconnect" ); + self endon( "death" ); + + self waittill("player_input_suicide"); + + self bleed_out(); +} \ No newline at end of file diff --git a/mp/_lightninggun.csc b/mp/_lightninggun.csc new file mode 100644 index 0000000..3cdc925 --- /dev/null +++ b/mp/_lightninggun.csc @@ -0,0 +1,22 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_lightninggun; + + + + +#using scripts\mp\_util; + +#namespace lightninggun; + +function autoexec __init__sytem__() { system::register("lightninggun",&__init__,undefined,undefined); } + +function __init__() +{ + lightninggun::init_shared(); +} + diff --git a/mp/_lightninggun.gsc b/mp/_lightninggun.gsc new file mode 100644 index 0000000..121d980 --- /dev/null +++ b/mp/_lightninggun.gsc @@ -0,0 +1,18 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_lightninggun; + + + + +#namespace lightninggun; + +function autoexec __init__sytem__() { system::register("lightninggun",&__init__,undefined,undefined); } + +function __init__() +{ + lightninggun::init_shared(); +} diff --git a/mp/_load.csc b/mp/_load.csc new file mode 100644 index 0000000..c71efb7 --- /dev/null +++ b/mp/_load.csc @@ -0,0 +1,160 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\archetype_shared\archetype_shared; +#using scripts\shared\audio_shared;//DO NOT REMOVE - needed for system registration +#using scripts\shared\clientfield_shared; +#using scripts\shared\clientfaceanim_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\footsteps_shared;//DO NOT REMOVE - needed for system registration +#using scripts\shared\load_shared; +#using scripts\shared\music_shared; +#using scripts\shared\player_shared;//DO NOT REMOVE - needed for system registration +#using scripts\shared\_oob; +#using scripts\shared\scene_shared; +#using scripts\shared\system_shared; +#using scripts\shared\turret_shared; +#using scripts\shared\postfx_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\weapons\_bouncingbetty; +#using scripts\shared\weapons\_hive_gun; +#using scripts\shared\weapons\spike_charge; +#using scripts\shared\weapons\_lightninggun; +#using scripts\shared\weapons\_pineapple_gun; +#using scripts\shared\weapons\_proximity_grenade; +#using scripts\shared\weapons\_riotshield; +#using scripts\shared\weapons\_satchel_charge; +#using scripts\shared\weapons\_tacticalinsertion; +#using scripts\shared\weapons\_trophy_system; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\weapons\multilockapguidance; + + + +#using scripts\mp\_util; + +//System registration +#using scripts\mp\_ambient; +#using scripts\shared\_burnplayer; +#using scripts\mp\_callbacks; +#using scripts\mp\_ctf; +#using scripts\mp\_destructible; +// #using scripts\mp\_devgui; // never add this to mp (it's for mp frontend only) +#using scripts\mp\_global_fx; +#using scripts\mp\_multi_extracam; +#using scripts\mp\_perks; +#using scripts\mp\_radiant_live_update; +#using scripts\mp\_rewindobjects; +#using scripts\mp\_rotating_object; +#using scripts\mp\_vehicle; +#using scripts\mp\_end_game_flow; +#using scripts\mp\mpdialog; + +//Gametype registration +#using scripts\mp\gametypes\_classicmode; + +//Weapon registration +#using scripts\shared\weapons\_sticky_grenade; +#using scripts\mp\_bouncingbetty; +#using scripts\mp\_hacker_tool; +#using scripts\mp\_hive_gun; +#using scripts\mp\_claymore; +#using scripts\mp\_decoy; +#using scripts\mp\_explosive_bolt; +#using scripts\mp\_flashgrenades; +#using scripts\mp\_gravity_spikes; +#using scripts\mp\_lightninggun; +#using scripts\mp\_proximity_grenade; +#using scripts\mp\_riotshield; +#using scripts\mp\_satchel_charge; +#using scripts\mp\_threat_detector; +#using scripts\mp\_tacticalinsertion; +#using scripts\mp\_trophy_system; +#using scripts\mp\gametypes\_weaponobjects; + +//Killstreak registration +#using scripts\mp\_helicopter_sounds; +#using scripts\mp\killstreaks\_counteruav; +#using scripts\mp\killstreaks\_dart; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\killstreaks\_helicopter; +#using scripts\mp\killstreaks\_helicopter_gunner; +#using scripts\mp\killstreaks\_flak_drone; +#using scripts\mp\killstreaks\_microwave_turret; +#using scripts\mp\killstreaks\_planemortar; +#using scripts\mp\killstreaks\_remotemissile; +#using scripts\mp\killstreaks\_raps; +#using scripts\mp\killstreaks\_turret; +#using scripts\mp\killstreaks\_killstreak_detect; + + +//Abilities registration +#using scripts\shared\abilities\_ability_player; + +#namespace load; + +function levelNotifyHandler(clientNum, state, oldState) +{ + if(state != "") + { + level notify(state,clientNum); + } +} + +function main() +{ + /# + + Assert( isdefined( level.first_frame ), "There should be no waits before load::main." ); + + #/ + + level thread util::serverTime(); + level thread util::init_utility(); + + util::registerSystem("levelNotify",&levelNotifyHandler); + + register_clientfields(); + + level.createFX_disable_fx = (GetDvarInt("disable_fx") == 1); + + //The functions are threaded just in case any of them create 'wait's. Note these functions should not have 'wait's in their 'init' and 'main' functions. + +// footsteps(); + + system::wait_till( "all" ); + + level flagsys::set( "load_main_complete" ); +} + +/*footsteps() +{ + util::setFootstepEffect( "asphalt", "_t6/bio/player/fx_footstep_dust" ); + util::setFootstepEffect( "brick", "_t6/bio/player/fx_footstep_dust" ); + util::setFootstepEffect( "carpet", "_t6/bio/player/fx_footstep_dust" ); + util::setFootstepEffect( "cloth", "_t6/bio/player/fx_footstep_dust" ); + util::setFootstepEffect( "concrete", "_t6/bio/player/fx_footstep_dust" ); + util::setFootstepEffect( "dirt", "_t6/bio/player/fx_footstep_sand" ); + util::setFootstepEffect( "foliage", "_t6/bio/player/fx_footstep_dust" ); + util::setFootstepEffect( "gravel", "_t6/bio/player/fx_footstep_sand" ); + util::setFootstepEffect( "grass", "_t6/bio/player/fx_footstep_sand" ); + util::setFootstepEffect( "metal", "_t6/bio/player/fx_footstep_dust" ); + util::setFootstepEffect( "mud", "_t6/bio/player/fx_footstep_mud" ); + util::setFootstepEffect( "paper", "_t6/bio/player/fx_footstep_dust" ); + util::setFootstepEffect( "plaster", "_t6/bio/player/fx_footstep_dust" ); + util::setFootstepEffect( "rock", "_t6/bio/player/fx_footstep_sand" ); + util::setFootstepEffect( "sand", "_t6/bio/player/fx_footstep_sand" ); + util::setFootstepEffect( "water", "_t6/bio/player/fx_footstep_water" ); + util::setFootstepEffect( "wood", "_t6/bio/player/fx_footstep_dust" ); +}*/ + +function register_clientfields() +{ + clientfield::register( "missile", "cf_m_proximity", 1, 1, "int", &callback::callback_proximity, !true, !true ); + clientfield::register( "missile", "cf_m_emp", 1, 1, "int", &callback::callback_emp, !true, !true ); + clientfield::register( "missile", "cf_m_stun", 1, 1, "int", &callback::callback_stunned, !true, !true ); + + //clientfield::register( "scriptmover", "cf_s_emp", VERSION_SHIP, 1, "int", &callback::callback_emp, !CF_HOST_ONLY, !CF_CALLBACK_ZERO_ON_NEW_ENT ); + //clientfield::register( "scriptmover", "cf_s_stun", VERSION_SHIP, 1, "int", &callback::callback_stunned, !CF_HOST_ONLY, !CF_CALLBACK_ZERO_ON_NEW_ENT ); + +} diff --git a/mp/_load.gsc b/mp/_load.gsc new file mode 100644 index 0000000..93992ab --- /dev/null +++ b/mp/_load.gsc @@ -0,0 +1,214 @@ +#using scripts\codescripts\struct; + +//TODO T7 - move what we can into load_shared once ZM gets a pass +#using scripts\shared\archetype_shared\archetype_shared; +#using scripts\shared\audio_shared;//DO NOT REMOVE - needed for system registration +#using scripts\shared\clientfield_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\entityheadicons_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\load_shared; +#using scripts\shared\math_shared; +#using scripts\shared\medals_shared; +#using scripts\shared\music_shared;//DO NOT REMOVE - needed for system registration +#using scripts\shared\player_shared;//DO NOT REMOVE - needed for system registration +#using scripts\shared\objpoints_shared; +#using scripts\shared\_oob; +#using scripts\shared\popups_shared; +#using scripts\shared\scene_shared; +#using scripts\shared\serverfaceanim_shared; +#using scripts\shared\system_shared; +#using scripts\shared\turret_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_ballistic_knife; +#using scripts\shared\weapons\_bouncingbetty; +#using scripts\shared\weapons\_hive_gun; +#using scripts\shared\weapons\_tacticalinsertion; +#using scripts\shared\weapons\_trophy_system; +#using scripts\shared\weapons\_flashgrenades; +#using scripts\shared\weapons\_lightninggun; +#using scripts\shared\weapons\_proximity_grenade; +#using scripts\shared\weapons\_riotshield; +#using scripts\shared\weapons\_satchel_charge; +#using scripts\shared\weapons\_sensor_grenade; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\weapons\multilockapguidance; + + + + +#using scripts\mp\_art; +#using scripts\mp\_destructible; +#using scripts\mp\_load; +#using scripts\mp\_util; + +//REGISTRATION - These scripts are initialized here +//Do not remove unless you are removing the script from the game + +//System registration +#using scripts\mp\_arena; +#using scripts\mp\_bb; +#using scripts\shared\_burnplayer; +#using scripts\mp\_callbacks; +#using scripts\mp\_devgui; +#using scripts\mp\_perks; +#using scripts\mp\_vehicle; +#using scripts\mp\_pickup_items; + +//Gametypes Registration - DO NOT REMOVE +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_classicmode; + +//Killstreak registration +#using scripts\mp\killstreaks\_ai_tank; +#using scripts\mp\killstreaks\_counteruav; +#using scripts\mp\killstreaks\_dogs; +#using scripts\mp\killstreaks\_drone_strike; +#using scripts\mp\killstreaks\_rcbomb; +#using scripts\mp\killstreaks\_placeables; +#using scripts\mp\killstreaks\_satellite; +#using scripts\mp\killstreaks\_uav; +#using scripts\mp\killstreaks\_killstreak_detect; + +//Weapon registration +#using scripts\shared\weapons\_pineapple_gun; +#using scripts\shared\weapons\_sticky_grenade; +#using scripts\mp\_ballistic_knife; +#using scripts\mp\_bouncingbetty; +#using scripts\mp\_hive_gun; +#using scripts\mp\_explosive_bolt; +#using scripts\mp\_flashgrenades; +#using scripts\mp\_hacker_tool; +#using scripts\mp\_heatseekingmissile; +#using scripts\mp\_incendiary; +#using scripts\mp\_armblade; +#using scripts\mp\_lightninggun; +#using scripts\mp\_proximity_grenade; +#using scripts\mp\_riotshield; +#using scripts\mp\_satchel_charge; +#using scripts\mp\_sensor_grenade; +#using scripts\mp\_threat_detector; +#using scripts\mp\_smokegrenade; +#using scripts\mp\_tacticalinsertion; +#using scripts\mp\_trophy_system; +#using scripts\mp\gametypes\_weaponobjects; + +//Abilities registration +#using scripts\shared\abilities\_ability_player; + +// Blackjack registration +#using scripts\mp\_blackjack_challenges; + +// Contract registration +#using scripts\mp\_contracts; + +#precache( "fx", "_t6/bio/player/fx_footstep_dust" ); +#precache( "fx", "_t6/bio/player/fx_footstep_sand" ); +#precache( "fx", "_t6/bio/player/fx_footstep_mud" ); +#precache( "fx", "_t6/bio/player/fx_footstep_water" ); + +#namespace load; + +function main() +{ + /# + + Assert( isdefined( level.first_frame ), "There should be no waits before load::main." ); + + #/ + + level._loadStarted = true; + + SetClearanceCeiling( 30 ); // slightly larger than the amws + + register_clientfields(); + + level.aiTriggerSpawnFlags = getaitriggerflags(); + level.vehicleTriggerSpawnFlags = getvehicletriggerflags(); + + setup_traversals(); + + //TODO T7 - remove once globallogic_audio is shared + level.globallogic_audio_dialog_on_player_override = &globallogic_audio::leader_dialog_on_player; + level.growing_hitmarker = true; + + system::wait_till( "all" ); + level flagsys::set( "load_main_complete" ); +} + +function setFootstepEffect(name, fx) +{ + assert(isdefined(name), "Need to define the footstep surface type."); + assert(isdefined(fx), "Need to define the mud footstep effect."); + if (!isdefined(anim.optionalStepEffects)) + anim.optionalStepEffects = []; + anim.optionalStepEffects[anim.optionalStepEffects.size] = name; + level._effect["step_" + name] = fx; +} + +function footsteps() +{ + setFootstepEffect( "asphalt", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "brick", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "carpet", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "cloth", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "concrete", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "dirt", "_t6/bio/player/fx_footstep_sand" ); + setFootstepEffect( "foliage", "_t6/bio/player/fx_footstep_sand" ); + setFootstepEffect( "gravel", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "grass", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "metal", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "mud", "_t6/bio/player/fx_footstep_mud" ); + setFootstepEffect( "paper", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "plaster", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "rock", "_t6/bio/player/fx_footstep_dust" ); + setFootstepEffect( "sand", "_t6/bio/player/fx_footstep_sand" ); + setFootstepEffect( "water", "_t6/bio/player/fx_footstep_water" ); + setFootstepEffect( "wood", "_t6/bio/player/fx_footstep_dust" ); +} + +// All "Begin" nodes get passed in here through _load.gsc +function init_traverse() +{ + point = GetEnt(self.target, "targetname"); + if (isdefined(point)) + { + self.traverse_height = point.origin[2]; + point Delete(); + } + else + { + point = struct::get(self.target, "targetname"); + if (isdefined(point)) + { + self.traverse_height = point.origin[2]; + } + } +} + +function setup_traversals() +{ + potential_traverse_nodes = GetAllNodes(); + for (i = 0; i < potential_traverse_nodes.size; i++) + { + node = potential_traverse_nodes[i]; + if (node.type == "Begin") + { + node init_traverse(); + } + } +} + +function register_clientfields() +{ + clientfield::register( "missile", "cf_m_proximity", 1, 1, "int" ); + clientfield::register( "missile", "cf_m_emp", 1, 1, "int" ); + clientfield::register( "missile", "cf_m_stun", 1, 1, "int" ); + + //clientfield::register( "scriptmover", "cf_s_emp", VERSION_SHIP, 1, "int" ); + //clientfield::register( "scriptmover", "cf_s_stun", VERSION_SHIP, 1, "int" ); +} diff --git a/mp/_mgturret.gsc b/mp/_mgturret.gsc new file mode 100644 index 0000000..711b8af --- /dev/null +++ b/mp/_mgturret.gsc @@ -0,0 +1,380 @@ +#using scripts\codescripts\struct; + + + +#namespace mgturret; + +//TODO T7 - nothing is calling main() anymore, do we need this? +function main() +{ + // TODO: what does this main do exactly? + // TODO: change this dvar name to turret or something; really? we have a dvar named mg42 + if( GetDvarString( "mg42" ) == "" ) + { + SetDvar( "mgTurret", "off" ); + } + + level.magic_distance = 24; + + turretInfos = getEntArray( "turretInfo", "targetname" ); + for( index = 0; index < turretInfos.size; index++ ) + { + turretInfos[index] Delete(); + } +} + +function set_difficulty( difficulty ) +{ + init_turret_difficulty_settings(); + + turrets = GetEntArray( "misc_turret", "classname" ); + + for( index = 0; index < turrets.size; index++ ) + { + if( isdefined( turrets[index].script_skilloverride ) ) + { + switch( turrets[index].script_skilloverride ) + { + case "easy": + difficulty = "easy"; + break; + case "medium": + difficulty = "medium"; + break; + case "hard": + difficulty = "hard"; + break; + case "fu": + difficulty = "fu"; + break; + default: + continue; + } + } + turret_set_difficulty( turrets[index], difficulty ); + } +} + +function init_turret_difficulty_settings() +{ + level.mgTurretSettings["easy"]["convergenceTime"] = 2.5; + level.mgTurretSettings["easy"]["suppressionTime"] = 3.0; + level.mgTurretSettings["easy"]["accuracy"] = 0.38; + level.mgTurretSettings["easy"]["aiSpread"] = 2; + level.mgTurretSettings["easy"]["playerSpread"] = 0.5; + + level.mgTurretSettings["medium"]["convergenceTime"] = 1.5; + level.mgTurretSettings["medium"]["suppressionTime"] = 3.0; + level.mgTurretSettings["medium"]["accuracy"] = 0.38; + level.mgTurretSettings["medium"]["aiSpread"] = 2; + level.mgTurretSettings["medium"]["playerSpread"] = 0.5; + + level.mgTurretSettings["hard"]["convergenceTime"] = .8; + level.mgTurretSettings["hard"]["suppressionTime"] = 3.0; + level.mgTurretSettings["hard"]["accuracy"] = 0.38; + level.mgTurretSettings["hard"]["aiSpread"] = 2; + level.mgTurretSettings["hard"]["playerSpread"] = 0.5; + + level.mgTurretSettings["fu"]["convergenceTime"] = .4; + level.mgTurretSettings["fu"]["suppressionTime"] = 3.0; + level.mgTurretSettings["fu"]["accuracy"] = 0.38; + level.mgTurretSettings["fu"]["aiSpread"] = 2; + level.mgTurretSettings["fu"]["playerSpread"] = 0.5; +} + +function turret_set_difficulty( turret, difficulty ) +{ + turret.convergenceTime = level.mgTurretSettings[difficulty]["convergenceTime"]; + turret.suppressionTime = level.mgTurretSettings[difficulty]["suppressionTime"]; + turret.accuracy = level.mgTurretSettings[difficulty]["accuracy"]; + turret.aiSpread = level.mgTurretSettings[difficulty]["aiSpread"]; + turret.playerSpread = level.mgTurretSettings[difficulty]["playerSpread"]; +} + +function turret_suppression_fire( targets ) // self == turret +{ + self endon( "death" ); + self endon( "stop_suppression_fire" ); + if( !isdefined( self.suppresionFire ) ) + { + self.suppresionFire = true; + } + + for( ;; ) + { + while( self.suppresionFire ) + { + self SetTargetEntity( targets[RandomInt( targets.size )] ); + wait( 2 + RandomFloat( 2 ) ); + } + + self ClearTargetEntity(); + while( !self.suppresionFire ) + { + wait( 1 ); + } + } +} + +// returns a time frame for the burst fire depending on the setting parameter +function burst_fire_settings( setting ) +{ + if( setting == "delay" ) + { + return 0.2; + } + else if( setting == "delay_range" ) + { + return 0.5; + } + else if( setting == "burst" ) + { + return 0.5; + } + else if( setting == "burst_range" ) + { + return 4; + } +} + +// makes the turret burst fire with delays in between +function burst_fire( turret, manual_target ) +{ + turret endon( "death" ); // MikeD: Incase we delete the turret. + turret endon( "stopfiring" ); + self endon( "stop_using_built_in_burst_fire" ); + + + if( isdefined( turret.script_delay_min ) ) + { + turret_delay = turret.script_delay_min; + } + else + { + turret_delay = burst_fire_settings( "delay" ); + } + + if( isdefined( turret.script_delay_max ) ) + { + turret_delay_range = turret.script_delay_max - turret_delay; + } + else + { + turret_delay_range = burst_fire_settings( "delay_range" ); + } + + if( isdefined( turret.script_burst_min ) ) + { + turret_burst = turret.script_burst_min; + } + else + { + turret_burst = burst_fire_settings( "burst" ); + } + + if( isdefined( turret.script_burst_max ) ) + { + turret_burst_range = turret.script_burst_max - turret_burst; + } + else + { + turret_burst_range = burst_fire_settings( "burst_range" ); + } + + while( 1 ) + { + turret StartFiring(); + + + + if( isdefined( manual_target ) ) + { + turret thread random_spread( manual_target ); + } + turret do_shoot(); + + wait( turret_burst + RandomFloat( turret_burst_range ) ); + + turret StopShootTurret(); + + turret StopFiring(); + + wait( turret_delay + RandomFloat( turret_delay_range ) ); + } +} + +// auto targeting and burst firing at the targets +function burst_fire_unmanned() // self == turret +{ + self notify( "stop_burst_fire_unmanned" ); + self endon( "stop_burst_fire_unmanned" ); + self endon( "death" ); + self endon( "remote_start" ); + level endon( "game_ended" ); + + if ( isdefined( self.controlled ) && self.controlled ) + { + return; + } + + if( isdefined( self.script_delay_min ) ) + { + turret_delay = self.script_delay_min; + } + else + { + turret_delay = burst_fire_settings( "delay" ); + } + + if( isdefined( self.script_delay_max ) ) + { + turret_delay_range = self.script_delay_max - turret_delay; + } + else + { + turret_delay_range = burst_fire_settings( "delay_range" ); + } + + if( isdefined( self.script_burst_min ) ) + { + turret_burst = self.script_burst_min; + } + else + { + turret_burst = burst_fire_settings( "burst" ); + } + + if( isdefined( self.script_burst_max ) ) + { + turret_burst_range = self.script_burst_max - turret_burst; + } + else + { + turret_burst_range = burst_fire_settings( "burst_range" ); + } + + pauseUntilTime = GetTime(); + turretState = "start"; + // SRS 05/02/07 - added this for link_turrets() so we can accurately tell when the function is + // actually firing or just waiting between bursts (IsFiringTurret() returns true the whole time) + self.script_shooting = false; + + for( ;; ) + { + if( isdefined( self.manual_targets ) ) + { + self ClearTargetEntity(); + self SetTargetEntity( self.manual_targets[RandomInt( self.manual_targets.size )] ); + } + + duration = ( pauseUntilTime - GetTime() ) * 0.001; + if( self IsFiringTurret() && (duration <= 0) ) + { + if( turretState != "fire" ) + { + turretState = "fire"; + self playsound ("mpl_turret_alert"); // Play a state change sound CDC + + self thread do_shoot(); + self.script_shooting = true; + } + + duration = turret_burst + RandomFloat( turret_burst_range ); + + //println( "fire duration: ", duration ); + self thread turret_timer( duration ); + + self waittill( "turretstatechange" ); // code or script + + self.script_shooting = false; + + duration = turret_delay + RandomFloat( turret_delay_range ); + //println( "stop fire duration: ", duration ); + + pauseUntilTime = GetTime() + Int( duration * 1000 ); + } + else + { + if( turretState != "aim" ) + { + turretState = "aim"; + } + + //println( "aim duration: ", duration ); + self thread turret_timer( duration ); + + // TODO: make the turret scan back and forth + + self waittill( "turretstatechange" ); // code or script + } + } +} + +function do_shoot() +{ + self endon( "death" ); + self endon( "turretstatechange" ); // code or script + + for( ;; ) + { + self ShootTurret(); + wait( 0.112 ); + } +} + +// waits for a duration and sends a turret state change notify +function turret_timer( duration ) +{ + if( duration <= 0 ) + { + return; + } + + self endon( "turretstatechange" ); // code + + //println( "start turret timer" ); + + wait( duration ); + if( isdefined( self ) ) + { + self notify( "turretstatechange" ); + } + + //println( "end turret timer" ); +} + +function random_spread( ent ) +{ + self endon( "death" ); + + self notify( "stop random_spread" ); + self endon( "stop random_spread" ); + + self endon( "stopfiring" ); + self SetTargetEntity( ent ); + + self.manual_target = ent; + + while( 1 ) + { + + // SCRIPTER_MOD + // MikeD( 3/21/2007 ): No more level.player + // if( ent == level.player ) + // ent.origin = self.manual_target GetOrigin(); + // else + // ent.origin = self.manual_target.origin; + + if( IsPlayer( ent ) ) + { + ent.origin = self.manual_target GetOrigin(); + } + else + { + ent.origin = self.manual_target.origin; + } + + ent.origin += ( 20 - RandomFloat( 40 ), 20 - RandomFloat( 40 ), 20 - RandomFloat( 60 ) ); + wait( 0.2 ); + } +} \ No newline at end of file diff --git a/mp/_multi_extracam.csc b/mp/_multi_extracam.csc new file mode 100644 index 0000000..4889e3b --- /dev/null +++ b/mp/_multi_extracam.csc @@ -0,0 +1,40 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; + + + +#using scripts\shared\util_shared; + + + +#namespace multi_extracam; + +function autoexec __init__sytem__() { system::register("multi_extracam",&__init__,undefined,undefined); } + +function __init__( localClientNum ) +{ + callback::on_localclient_connect( &multi_extracam_init ); +} + +function multi_extracam_init( localClientNum ) +{ + triggers = GetEntArray( localClientNum, "multicam_enable", "targetname" ); + + for( i=1 ; i<=4 ; i++ ) + { + cameraStruct = struct::get( "extracam" + i, "targetname" ); + if ( isdefined( cameraStruct ) ) + { + camera_ent = Spawn( localClientNum, cameraStruct.origin, "script_origin" ); + camera_ent.angles = cameraStruct.angles; + + width = ( isdefined( cameraStruct.extracam_width ) ? cameraStruct.extracam_width : -1 ); + height = ( isdefined( cameraStruct.extracam_height ) ? cameraStruct.extracam_height : -1 ); + + camera_ent SetExtraCam( i-1, width, height ); + } + } +} \ No newline at end of file diff --git a/mp/_nuketown_mannequin.gsc b/mp/_nuketown_mannequin.gsc new file mode 100644 index 0000000..a65210f --- /dev/null +++ b/mp/_nuketown_mannequin.gsc @@ -0,0 +1,185 @@ +#using scripts\shared\ai_shared; +#using scripts\shared\ai\archetype_mannequin; +#using scripts\shared\music_shared; +#using scripts\shared\util_shared; + + + + + +#precache( "fx", "dlc0/nuketown/fx_de_rez_man_spawn" ); + +#namespace NuketownMannequin; + +function SpawnMannequin( origin, angles, gender = "male", speed = undefined, weepingAngel ) +{ + if(!IsDefined ( level.mannequinspawn_music)) + { + level.mannequinspawn_music = 1; + music::setmusicstate( "mann" ); + } + + if ( gender == "male" ) + { + mannequin = SpawnActor( "spawner_bo3_mannequin_male", origin, angles, "", true, true ); + } + else + { + mannequin = SpawnActor( "spawner_bo3_mannequin_female", origin, angles, "", true, true ); + } + + // Select an initial speed. + rand = RandomInt( 100 ); + + if( rand <= 35 ) + { + mannequin.zombie_move_speed = "walk"; + } + else if( rand <= 70 ) + { + mannequin.zombie_move_speed = "run"; + } + else + { + mannequin.zombie_move_speed = "sprint"; + } + + if ( IsDefined( speed ) ) + { + mannequin.zombie_move_speed = speed; + } + + if( IsDefined( level.zm_variant_type_max ) ) + { + // Don't select variant 0 since those animations don't have proper footstep notetracks. + mannequin.variant_type = RandomIntRange( 1, level.zm_variant_type_max[ mannequin.zombie_move_speed ][ mannequin.zombie_arms_position ] ); + } + + mannequin ai::set_behavior_attribute( "can_juke", true ); + mannequin ASMSetAnimationRate( RandomFloatRange( 0.98, 1.02 ) ); // Slightly vary animation playback. + mannequin.holdFire = true; // No firing, performance gain + + // sjakatdar (10/24/2015) - Disabling the optimization for updating the sight. It will prevent ais from picking up + // new enemy. To be able to use this optimization, the script has to explicitly set favorite enemy, which is not being done + // for mannequins. Fixes DT#139243. + //mannequin.updateSight = false; // No sight update + + mannequin.canStumble = true; + mannequin.should_turn = true; + mannequin thread watch_game_ended(); + mannequin.team = "free"; + mannequin.overrideActorDamage = &mannequinDamage; // prevent mannequins from deal accidental melee damage to each other. + + mannequins = GetAIArchetypeArray( "mannequin" ); + + foreach ( otherMannequin in mannequins ) + { + if ( otherMannequin.archetype == "mannequin" ) + { + otherMannequin SetIgnoreEnt( mannequin, true ); + mannequin SetIgnoreEnt( otherMannequin, true ); + } + } + + if( weepingAngel ) + { + mannequin thread _mannequin_unfreeze_ragdoll(); + mannequin.is_looking_at_me = true; + mannequin.was_looking_at_me = false; + mannequin _mannequin_update_freeze( mannequin.is_looking_at_me ); + } + + PlayFx( "dlc0/nuketown/fx_de_rez_man_spawn", mannequin.origin, AnglesToForward( mannequin.angles ) ); + + return mannequin; +} + +function mannequinDamage( inflictor, attacker, damage, dFlags, mod, weapon, point, dir, hitLoc, offsetTime, boneIndex, modelIndex ) +{ + if ( IsDefined( inflictor ) && IsActor( inflictor ) && inflictor.archetype == "mannequin" ) + { + return 0; + } + + return damage; +} + +function private watch_game_ended() +{ + self endon ( "death" ); + + level waittill ( "game_ended" ); + + self SetEntityPaused( true ); + + level waittill ( "endgame_sequence" ); + + self Hide(); +} + +function private _mannequin_unfreeze_ragdoll() +{ + self waittill( "death" ); + + if ( IsDefined( self ) ) + { + self SetEntityPaused( false ); + + if ( !self IsRagdoll() ) + { + self StartRagdoll(); + } + } +} + +function private _mannequin_update_freeze( frozen ) +{ + self.is_looking_at_me = frozen; + + if( self.is_looking_at_me && !self.was_looking_at_me ) + { + self SetEntityPaused( true ); + } + else if( !self.is_looking_at_me && self.was_looking_at_me ) + { + self SetEntityPaused( false ); + } + + self.was_looking_at_me = self.is_looking_at_me; +} + +function watch_player_looking() +{ + level endon ( "game_ended" ); + level endon ( "mannequin_force_cleanup" ); + + while( 1 ) + { + mannequins = GetAIArchetypeArray( "mannequin" ); + foreach( mannequin in mannequins ) + { + mannequin.can_player_see_me = true; + } + + players = GetPlayers(); + + unseenMannequins = mannequins; + foreach( player in players ) + { + unseenMannequins = player CantSeeEntities( unseenMannequins, .67, false ); + } + + foreach( mannequin in unseenMannequins ) + { + mannequin.can_player_see_me = false; + } + + foreach( mannequin in mannequins ) + { + mannequin _mannequin_update_freeze( mannequin.can_player_see_me ); + } + + {wait(.05);}; + } +} + diff --git a/mp/_perks.csc b/mp/_perks.csc new file mode 100644 index 0000000..c7cb6c7 --- /dev/null +++ b/mp/_perks.csc @@ -0,0 +1,746 @@ +#using scripts\codescripts\struct; +#using scripts\shared\abilities\_ability_util; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\filter_shared; +#using scripts\shared\util_shared; + + + + + + +#using scripts\shared\system_shared; + +#namespace perks; + +function autoexec __init__sytem__() { system::register("perks",&__init__,undefined,undefined); } + + + + + + + + + + +#precache( "client_fx", "player/fx_plyr_footstep_tracker_l" ); +#precache( "client_fx", "player/fx_plyr_footstep_tracker_r" ); +#precache( "client_fx", "player/fx_plyr_flying_tracker_l" ); +#precache( "client_fx", "player/fx_plyr_flying_tracker_r" ); +#precache( "client_fx", "player/fx_plyr_footstep_tracker_lf" ); +#precache( "client_fx", "player/fx_plyr_footstep_tracker_rf" ); +#precache( "client_fx", "player/fx_plyr_flying_tracker_lf" ); +#precache( "client_fx", "player/fx_plyr_flying_tracker_rf" ); + + +function __init__() +{ + clientfield::register( "allplayers", "flying", 1, 1, "int", &flying_callback, !true, true ); + + callback::on_localclient_connect( &on_local_client_connect ); + callback::on_localplayer_spawned( &on_localplayer_spawned ); + callback::on_spawned( &on_player_spawned ); + + // kill tracker FX when tracked player dies + level.killTrackerFXEnable = true; + level._monitor_tracker = &monitor_tracker_perk; + + level.sitrepscan1_enable = GetDvarInt( "scr_sitrepscan1_enable", 2 ); + level.sitrepscan1_setoutline = GetDvarInt( "scr_sitrepscan1_setoutline", 1 ); + level.sitrepscan1_setsolid = GetDvarInt( "scr_sitrepscan1_setsolid", 1 ); + level.sitrepscan1_setlinewidth = GetDvarInt( "scr_sitrepscan1_setlinewidth", 1 ); + level.sitrepscan1_setradius = GetDvarInt( "scr_sitrepscan1_setradius", 50000 ); + level.sitrepscan1_setfalloff = GetDvarFloat( "scr_sitrepscan1_setfalloff", .01 ); + level.sitrepscan1_setdesat = GetDvarFloat( "scr_sitrepscan1_setdesat", .4 ); + + level.sitrepscan2_enable = GetDvarInt( "scr_sitrepscan2_enable", 2 ); + level.sitrepscan2_setoutline = GetDvarInt( "scr_sitrepscan2_setoutline", 10 ); + level.sitrepscan2_setsolid = GetDvarInt( "scr_sitrepscan2_setsolid", 0 ); + level.sitrepscan2_setlinewidth = GetDvarInt( "scr_sitrepscan2_setlinewidth", 1 ); + level.sitrepscan2_setradius = GetDvarInt( "scr_sitrepscan2_setradius", 50000 ); + level.sitrepscan2_setfalloff = GetDvarFloat( "scr_sitrepscan2_setfalloff", .01 ); + level.sitrepscan2_setdesat = GetDvarFloat( "scr_sitrepscan2_setdesat", .4 ); + + /# + level thread updateDvars(); + #/ +} + +function updateSitrepScan() +{ + self endon ( "entityshutdown" ); + while(1) + { + self oed_sitrepscan_enable( level.sitrepscan1_enable ); + self oed_sitrepscan_setoutline( level.sitrepscan1_setoutline ); + self oed_sitrepscan_setsolid( level.sitrepscan1_setsolid ); + self oed_sitrepscan_setlinewidth( level.sitrepscan1_setlinewidth ); + self oed_sitrepscan_setradius( level.sitrepscan1_setradius ); + self oed_sitrepscan_setfalloff( level.sitrepscan1_setfalloff ); + self oed_sitrepscan_setdesat( level.sitrepscan1_setdesat ); + + self oed_sitrepscan_enable( level.sitrepscan2_enable, 1 ); + self oed_sitrepscan_setoutline( level.sitrepscan2_setoutline, 1 ); + self oed_sitrepscan_setsolid( level.sitrepscan2_setsolid, 1 ); + self oed_sitrepscan_setlinewidth( level.sitrepscan2_setlinewidth, 1 ); + self oed_sitrepscan_setradius( level.sitrepscan2_setradius, 1 ); + self oed_sitrepscan_setfalloff( level.sitrepscan2_setfalloff, 1 ); + self oed_sitrepscan_setdesat( level.sitrepscan2_setdesat, 1 ); + wait(1.0); + } +} + +/# +function updateDvars() +{ + while(1) + { + level.sitrepscan1_enable = GetDvarInt( "scr_sitrepscan1_enable", level.sitrepscan1_enable ); + level.sitrepscan1_setoutline = GetDvarInt( "scr_sitrepscan1_setoutline", level.sitrepscan1_setoutline ); + level.sitrepscan1_setsolid = GetDvarInt( "scr_sitrepscan1_setsolid", level.sitrepscan1_setsolid ); + level.sitrepscan1_setlinewidth = GetDvarInt( "scr_sitrepscan1_setlinewidth", level.sitrepscan1_setlinewidth ); + level.sitrepscan1_setradius = GetDvarInt( "scr_sitrepscan1_setradius", level.sitrepscan1_setradius ); + level.sitrepscan1_setfalloff = GetDvarFloat( "scr_sitrepscan1_setfalloff", level.sitrepscan1_setfalloff ); + level.sitrepscan1_setdesat = GetDvarFloat( "scr_sitrepscan1_setdesat", level.sitrepscan1_setdesat ); + + level.sitrepscan2_enable = GetDvarInt( "scr_sitrepscan2_enable", level.sitrepscan2_enable ); + level.sitrepscan2_setoutline = GetDvarInt( "scr_sitrepscan2_setoutline", level.sitrepscan2_setoutline ); + level.sitrepscan2_setsolid = GetDvarInt( "scr_sitrepscan2_setsolid", level.sitrepscan2_setsolid ); + level.sitrepscan2_setlinewidth = GetDvarInt( "scr_sitrepscan2_setlinewidth", level.sitrepscan2_setlinewidth ); + level.sitrepscan2_setradius = GetDvarInt( "scr_sitrepscan2_setradius", level.sitrepscan2_setradius ); + level.sitrepscan2_setfalloff = GetDvarFloat( "scr_sitrepscan2_setfalloff", level.sitrepscan2_setfalloff ); + level.sitrepscan2_setdesat = GetDvarFloat( "scr_sitrepscan2_setdesat", level.sitrepscan2_setdesat ); + + level.friendlyContentOutlines = GetDvarInt( "friendlyContentOutlines", level.friendlyContentOutlines ); + + wait(1.0); + } +} +#/ + +function flying_callback( local_client_num, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self.flying = newVal; +} + +function on_local_client_connect( local_client_num ) +{ + RegisterRewindFX( local_client_num, "player/fx_plyr_footstep_tracker_l" ); + RegisterRewindFX( local_client_num, "player/fx_plyr_footstep_tracker_r" ); + RegisterRewindFX( local_client_num, "player/fx_plyr_flying_tracker_l" ); + RegisterRewindFX( local_client_num, "player/fx_plyr_flying_tracker_r" ); + RegisterRewindFX( local_client_num, "player/fx_plyr_footstep_tracker_lf" ); + RegisterRewindFX( local_client_num, "player/fx_plyr_footstep_tracker_rf" ); + RegisterRewindFX( local_client_num, "player/fx_plyr_flying_tracker_lf" ); + RegisterRewindFX( local_client_num, "player/fx_plyr_flying_tracker_rf" ); +} + +function on_localplayer_spawned( local_client_num ) +{ + if( self != GetLocalPlayer( local_client_num ) ) + return; + + self thread monitor_tracker_perk_killcam( local_client_num ); + self thread monitor_detectnearbyenemies( local_client_num ); + self thread monitor_tracker_existing_players( local_client_num ); +} + +function on_player_spawned( local_client_num ) +{ + /# + self thread watch_perks_change(local_client_num); + #/ + self notify("perks_changed"); + self thread updateSitrepScan(); + /# + self thread updateSitrepScan(); + #/ + self thread killTrackerFX_on_death( local_client_num ); + self thread monitor_tracker_perk( local_client_num ); +} + +/# + +function array_equal( &a, &b ) +{ + if ( IsDefined(a) && IsDefined(b) && IsArray(a) && IsArray(b) && a.size==b.size ) + { + for ( i=0; i serverTime - 5000 ) + { + positionAndRotationStruct = level.trackerSpecialtySelf[local_client_num][count]; + tracker_playFX(local_client_num, positionAndRotationStruct); + } + } + } + } + else + { + for(;;) + { + wait 0.05; + + positionAndRotationStruct = self getTrackerFXPosition( local_client_num ); + if ( isdefined ( positionAndRotationStruct ) ) + { + positionAndRotationStruct.time = getServerTime( local_client_num ); + + level.trackerSpecialtySelf[local_client_num][level.trackerSpecialtyCounter] = positionAndRotationStruct; + level.trackerSpecialtyCounter++; + if ( level.trackerSpecialtyCounter > 20 ) + { + level.trackerSpecialtyCounter = 0; + } + } + } + } +} + +function monitor_tracker_perk( local_client_num ) +{ + self notify( "monitor_tracker_perk" ); + self endon( "monitor_tracker_perk" ); + self endon( "death" ); + self endon( "disconnect" ); + self endon( "entityshutdown" ); + + self.flying = false; + self.tracker_flying = false; + self.tracker_last_pos = self.origin; + + offset = ( 0,0,GetDvarFloat( "perk_tracker_fx_foot_height", 0 ) ); + dist2 = ( 32 * 32 ); + + while(IsDefined(self)) + { + wait 0.05; + + watcher = GetLocalPlayer( local_client_num ); + + if ( !isdefined( watcher ) || self == watcher ) + return; // no need to monitor the watcher + + if ( IsDefined( watcher ) && watcher HasPerk( local_client_num, "specialty_tracker" ) ) + { + friend = self isFriendly( local_client_num, true ); + + camoOff = true; + if( !isDefined( self._isClone ) || !self._isClone ) + { + camo_val = self clientfield::get( "camo_shader" ); + if( camo_val != 0 ) + { + camoOff = false; + } + } + + if ( !friend && IsAlive(self) && camoOff ) + { + positionAndRotationStruct = self getTrackerFXPosition( local_client_num ); + if ( isdefined( positionAndRotationStruct ) ) + { + self tracker_playFX(local_client_num, positionAndRotationStruct); + } + } + else + { + self.tracker_flying = false; + } + } + } +} + +function tracker_playFX( local_client_num, positionAndRotationStruct ) +{ + handle = playFX( local_client_num, positionAndRotationStruct.fx, positionAndRotationStruct.pos, positionAndRotationStruct.fwd, positionAndRotationStruct.up ); + + self killTrackerFX_track( local_client_num, handle ); +} + +function killTrackerFX_track( local_client_num, handle ) +{ + if ( handle && isdefined( self.killTrackerFX ) ) + { + serverTime = getServerTime( local_client_num ); + + killFXStruct = SpawnStruct(); + killFXStruct.time = serverTime; + killFXStruct.handle = handle; + + index = self.killTrackerFX.index; + + if ( index >= 40 ) + { + index = 0; + } + + self.killTrackerFX.array[index] = killFXStruct; + self.killTrackerFX.index = index + 1; + } +} + +function killTrackerFX_on_death( local_client_num ) +{ + self endon( "disconnect" ); + + if ( !( isdefined( level.killTrackerFXEnable ) && level.killTrackerFXEnable ) ) + { + return; + } + + predictedLocalPlayer = getlocalplayer( local_client_num ); + + if ( predictedLocalPlayer == self ) + { + return; + } + + if ( isdefined( self.killTrackerFX ) ) + { + self.killTrackerFX.array = []; + self.killTrackerFX.index = 0; + self.killTrackerFX = undefined; + } + + killTrackerFX = SpawnStruct(); + killTrackerFX.array = []; + killTrackerFX.index = 0; + + self.killTrackerFX = killTrackerFX; + + self waittill( "entityshutdown" ); + + serverTime = getServerTime( local_client_num ); + + foreach( killFXStruct in killTrackerFX.array ) + { + if ( isdefined( killFXStruct ) && killFXStruct.time + 5000 > serverTime ) + { + KillFX( local_client_num, killFXStruct.handle ); + } + } + + killTrackerFX.array = []; + killTrackerFX.index = 0; + killTrackerFX = undefined; +} + +function getTrackerFXPosition( local_client_num ) +{ + positionAndRotation = undefined; + player = self; + if( ( isdefined( self._isClone ) && self._isClone ) ) + { + player = self.owner; + } + playFastFX = player hasperk( local_client_num, "specialty_trackerjammer" ); + if ( ( isdefined( self.flying ) && self.flying ) ) + { + offset = ( 0,0,GetDvarFloat( "perk_tracker_fx_fly_height", 0 ) ); + dist2 = ( 32 * 32 ); + if ( ( isdefined( self.trailRightFoot ) && self.trailRightFoot ) ) + { + if ( playFastFX ) + { + fx = "player/fx_plyr_flying_tracker_rf"; + } + else + { + fx = "player/fx_plyr_flying_tracker_r"; + } + } + else + { + if ( playFastFX ) + { + fx = "player/fx_plyr_flying_tracker_lf"; + } + else + { + fx = "player/fx_plyr_flying_tracker_l"; + } + } + } + else + { + offset = ( 0,0,GetDvarFloat( "perk_tracker_fx_foot_height", 0 ) ); + dist2 = ( 32 * 32 ); + if ( ( isdefined( self.trailRightFoot ) && self.trailRightFoot ) ) + { + if ( playFastFX ) + { + fx = "player/fx_plyr_footstep_tracker_rf"; + } + else + { + fx = "player/fx_plyr_footstep_tracker_r"; + } + } + else + { + if ( playFastFX ) + { + fx = "player/fx_plyr_footstep_tracker_lf"; + } + else + { + fx = "player/fx_plyr_footstep_tracker_l"; + } + } + } + + pos = self.origin + offset; + fwd = AnglesToForward( self.angles ); + right = AnglesToRight( self.angles ); + up = AnglesToUp( self.angles ); + + vel = self getvelocity(); + if (LengthSquared(vel) > ( 1 * 1 )) + { + up = VectorCross(vel,right); + if ( LengthSquared( up ) < 0.0001 ) + { + up = VectorCross(fwd, vel); + } + fwd = vel; + } + + if( self isplayer() && self isplayerwallrunning() ) + { + if( self isplayerwallrunningright() ) + { + up = VectorCross( up, fwd ); + } + else + { + up = VectorCross( fwd, up ); + } + } + + if ( !self.tracker_flying ) + { + self.tracker_flying = true; + self.tracker_last_pos = self.origin; + } + else + { + if ( DistanceSquared( self.tracker_last_pos, pos ) > dist2 ) + { + positionAndRotation = SpawnStruct(); + positionAndRotation.fx = fx; + positionAndRotation.pos = pos; + positionAndRotation.fwd = fwd; + positionAndRotation.up = up; + + self.tracker_last_pos = self.origin; + + if ( ( isdefined( self.trailRightFoot ) && self.trailRightFoot ) ) + { + self.trailRightFoot = false; + } + else + { + self.trailRightFoot = true; + } + } + } + + return positionAndRotation; +} + + + + + + + + + + + + + + + + + + + +function monitor_detectnearbyenemies( local_client_num ) +{ + self endon( "entityshutdown" ); + + controllerModel = GetUIModelForController( local_client_num ); + sixthsenseModel = CreateUIModel( controllerModel, "hudItems.sixthsense" ); + + enemyNearbyTime = 0.0; + enemyLostTime = 0.0; + previousEnemyDetectedBitField = 0; + + SetUIModelValue( sixthsenseModel, 0 ); + + while(1) + { + localPlayer = GetLocalPlayer( local_client_num ); + + if ( !( localPlayer IsPlayer() ) || + ( localPlayer HasPerk( local_client_num, "specialty_detectnearbyenemies" ) == false ) || + ( localPlayer GetInKillcam( local_client_num ) == true || IsAlive( localPlayer ) == false ) ) + { + SetUIModelValue( sixthsenseModel, 0 ); + previousEnemyDetectedBitField = 0; + self util::waittill_any( "death", "spawned", "perks_changed" ); + continue; + } + + enemyNearbyFront = false; + enemyNearbyBack = false; + enemyNearbyLeft = false; + enemyNearbyRight = false; + enemyDetectedBitField = 0; + + team = localPlayer.team; + innerDetect = getdvarint( "specialty_detectnearbyenemies_inner", 1 ); + outerDetect = getdvarint( "specialty_detectnearbyenemies_outer", 1 ); + zDetect = getdvarint( "specialty_detectnearbyenemies_zthreshold", 1 ); + + localPlayerAnglesToForward = anglesToForward( localPlayer.Angles ); + + players = getplayers( local_client_num ); + clones = getclones( local_client_num ); + sixthSenseEnts = arraycombine( players, clones, false, false ); + foreach( sixthSenseEnt in sixthSenseEnts ) + { + if ( sixthSenseEnt isfriendly( local_client_num, true ) || sixthSenseEnt == localPlayer ) // SJC: IsEntityFriendly check returns false on yourself in FFA + continue; + + if( !isAlive( sixthSenseEnt ) ) + continue; + + distanceScalarSq = 1; + zScalarSq = 1; + + player = sixthSenseEnt; + if( ( isdefined( sixthSenseEnt._isClone ) && sixthSenseEnt._isClone ) ) + { + player = sixthSenseEnt.owner; + } + + if ( player isplayer() && player HasPerk( local_client_num, "specialty_sixthsensejammer" ) ) + { + distanceScalarSq = GetDvarFloat( "specialty_sixthsensejammer_distance_scalar", 0.01 ); + zScalarSq = GetDvarFloat( "specialty_sixthsensejammer_z_scalar", 0.01 ); + } + + if ( previousEnemyDetectedBitField == 0 ) + { + distanceSq = 300 * 300 * distanceScalarSq; + } + else + { + distanceSq = 350 * 350 * distanceScalarSq; + } + + distCurrentSq = DistanceSquared( sixthSenseEnt.origin, localPlayer.origin ); + zdistCurrent = sixthSenseEnt.origin[2] - localPlayer.origin[2]; + zdistCurrentSq = zdistCurrent * zdistCurrent; + if ( distCurrentSq < distanceSq ) + { + distanceMask = 1; + + if ( previousEnemyDetectedBitField > 16 ) + { + zNearbyCheck = 350 * 350 * zScalarSq; + } + else + { + zNearbyCheck = 50 * 50 * zScalarSq; + } + + if ( zdistCurrentSq < zNearbyCheck && zDetect ) + { + distanceMask = 16; + } + + vector = sixthSenseEnt.origin - localPlayer.origin; + vector = ( vector[0], vector[1], 0 ); + vectorFlat = vectorNormalize( vector ); + cosAngle = VectorDot( vectorFlat, localPlayerAnglesToForward ); + + if ( cosAngle > 0.7071 ) + { + enemyDetectedBitField = enemyDetectedBitField | ( ( 1 << 0 ) * distanceMask ); + + } + else if ( cosAngle < -0.7071 ) + { + enemyDetectedBitField = enemyDetectedBitField | ( ( 1 << 1 ) * distanceMask ); + } + else + { + localPlayerAnglesToRight = anglesToRight( localPlayer.Angles ); + cosAngle = VectorDot( vectorFlat, localPlayerAnglesToRight ); + if ( cosAngle < 0 ) + { + enemyDetectedBitField = enemyDetectedBitField | ( ( 1 << 2 ) * distanceMask ); + } + else + { + enemyDetectedBitField = enemyDetectedBitField | ( ( 1 << 3 ) * distanceMask ); + } + } + } + } + + if ( enemyDetectedBitField ) + { + enemyLostTime = 0; + if ( previousEnemyDetectedBitField != enemyDetectedBitField && enemyNearbyTime >= 0.05 ) + { + SetUIModelValue( sixthsenseModel, enemyDetectedBitField ); + enemyNearbyTime = 0; + + diff = enemyDetectedBitField ^ previousEnemyDetectedBitField; + if ( diff & enemyDetectedBitField ) + { + // SOUND DEPT + // player has entered area + self playsound (0, "uin_sixth_sense_ping_on"); + } + if ( diff & previousEnemyDetectedBitField ) + { + // SOUND DEPT + // player has left area + //self playsound (0, "uin_sixth_sense_off"); + } + + previousEnemyDetectedBitField = enemyDetectedBitField; + } + enemyNearbyTime += 0.05; + } + else + { + enemyNearbyTime = 0; + if ( previousEnemyDetectedBitField != 0 && enemyLostTime >= 0.05 ) + { + SetUIModelValue( sixthsenseModel, 0 ); + previousEnemyDetectedBitField = 0; + } + enemyLostTime += 0.05; + } + + wait( 0.05 ); + } + SetUIModelValue( sixthsenseModel, 0 ); +} + diff --git a/mp/_perks.gsc b/mp/_perks.gsc new file mode 100644 index 0000000..8c700e9 --- /dev/null +++ b/mp/_perks.gsc @@ -0,0 +1,290 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_tacticalinsertion; + + + + + +#using scripts\mp\gametypes\_spawnlogic; + + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_airsupport; + +#namespace spawning; + +function autoexec __init__sytem__() { system::register("perks",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "allplayers", "flying", 1, 1, "int" ); + + callback::on_connect( &on_player_connect ); + callback::on_spawned( &on_player_spawned ); +} + +function on_player_connect( local_client_num ) +{ +} + +function on_player_spawned( local_client_num ) +{ + self thread monitorGPSJammer(); + self thread monitorSenGrenJammer(); + self thread monitorFlight(); +} + +function monitorFlight() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self.flying = false; + while( IsDefined(self) ) + { + flying = !( self isOnGround() ); + if ( self.flying != flying ) + { + self clientfield::set("flying",flying); + self.flying = flying; + } + + {wait(.05);}; + } +} + +function monitorGPSJammer() +{ + self endon( "death" ); + self endon( "disconnect" ); + + + require_perk = true; + /# + require_perk = false; + #/ + + if ( require_perk && self HasPerk( "specialty_gpsjammer" ) == false ) + return; + + self clientfield::set( "gps_jammer_active", ( self HasPerk( "specialty_gpsjammer" ) ? 1 : 0 ) ); + gracePeriods = GetDvarInt( "perk_gpsjammer_graceperiods", 4 ); + minspeed = GetDvarInt( "perk_gpsjammer_min_speed", 100 ); + mindistance = GetDvarInt( "perk_gpsjammer_min_distance", 10 ); + timePeriod = GetDvarInt( "perk_gpsjammer_time_period", 200 ); + timePeriodSec = timePeriod/1000; + minspeedSq = minspeed * minspeed; + mindistanceSq = mindistance * mindistance; + + if ( minspeedSq == 0 ) // will never fail min speed check below so early out. + return; + + assert ( timePeriodSec >= 0.05 ); + if ( timePeriodSec < 0.05 ) + return; + + hasPerk = true; + stateChange = false; + failedDistanceCheck = false; + currentFailCount = 0; + timePassed = 0; + timeSinceDistanceCheck = 0; + previousOrigin = self.origin; + GPSJammerProtection = false; + + while(1) + { +/# + gracePeriods = GetDvarInt( "perk_gpsjammer_graceperiods", gracePeriods ); + minspeed = GetDvarInt( "perk_gpsjammer_min_speed", minspeed ); + mindistance = GetDvarInt( "perk_gpsjammer_min_distance", mindistance ); + timePeriod = GetDvarInt( "perk_gpsjammer_time_period", timePeriod ); + timePeriodSec = timePeriod/1000; + minspeedSq = minspeed * minspeed; + mindistanceSq = mindistance * mindistance; +#/ + GPSJammerProtection = false; + if ( util::isUsingRemote() || ( isdefined( self.isPlanting ) && self.isPlanting ) || ( isdefined( self.isDefusing ) && self.isDefusing ) ) + { + GPSJammerProtection = true; + } + else + { + if ( timeSinceDistanceCheck > 1 ) + { + timeSinceDistanceCheck = 0; + if ( DistanceSquared( previousOrigin, self.origin ) < mindistanceSq ) + { + failedDistanceCheck = true; + } + else + { + failedDistanceCheck = false; + } + previousOrigin = self.origin; + } + velocity = self GetVelocity(); + + speedsq = lengthsquared( velocity ); + + if ( speedSq > minspeedSq && failedDistanceCheck == false ) + { + GPSJammerProtection = true; + } + } + + if ( GPSJammerProtection == true && self HasPerk( "specialty_gpsjammer" ) ) + { + /# + if (GetDvarint( "scr_debug_perk_gpsjammer")!=0) + { + sphere( self.origin + (0,0,70), 12, (0,0,1), 1, true, 16, 3 ); + } + #/ + currentFailCount = 0; + if ( hasPerk == false ) + { + stateChange = false; + hasPerk = true; + self clientfield::set( "gps_jammer_active", 1 ); + } + } + else + { + currentFailCount++; + + if ( hasPerk == true && currentFailCount >= gracePeriods ) + { + stateChange = true; + hasPerk = false; + self clientfield::set( "gps_jammer_active", 0 ); + } + } + if ( stateChange == true ) + { + level notify("radar_status_change"); + } + timeSinceDistanceCheck += timePeriodSec; + wait( timePeriodSec ); + } +} + +function monitorSenGrenJammer() +{ + self endon( "death" ); + self endon( "disconnect" ); + + require_perk = true; + /# + require_perk = false; + #/ + + if ( require_perk && self HasPerk( "specialty_sengrenjammer" ) == false ) + return; + + self clientfield::set( "sg_jammer_active", ( self HasPerk( "specialty_sengrenjammer" ) ? 1 : 0 ) ); + gracePeriods = GetDvarInt( "perk_sgjammer_graceperiods", 4 ); + minspeed = GetDvarInt( "perk_sgjammer_min_speed", 100 ); + mindistance = GetDvarInt( "perk_sgjammer_min_distance", 10 ); + timePeriod = GetDvarInt( "perk_sgjammer_time_period", 200 ); + timePeriodSec = timePeriod/1000; + minspeedSq = minspeed * minspeed; + mindistanceSq = mindistance * mindistance; + + if ( minspeedSq == 0 ) // will never fail min speed check below so early out. + return; + + assert ( timePeriodSec >= 0.05 ); + if ( timePeriodSec < 0.05 ) + return; + + hasPerk = true; + stateChange = false; + failedDistanceCheck = false; + currentFailCount = 0; + timePassed = 0; + timeSinceDistanceCheck = 0; + previousOrigin = self.origin; + SGJammerProtection = false; + + while(1) + { +/# + gracePeriods = GetDvarInt( "perk_sgjammer_graceperiods", gracePeriods ); + minspeed = GetDvarInt( "perk_sgjammer_min_speed", minspeed ); + mindistance = GetDvarInt( "perk_sgjammer_min_distance", mindistance ); + timePeriod = GetDvarInt( "perk_sgjammer_time_period", timePeriod ); + timePeriodSec = timePeriod/1000; + minspeedSq = minspeed * minspeed; + mindistanceSq = mindistance * mindistance; +#/ + SGJammerProtection = false; + if ( util::isUsingRemote() || ( isdefined( self.isPlanting ) && self.isPlanting ) || ( isdefined( self.isDefusing ) && self.isDefusing ) ) + { + SGJammerProtection = true; + } + else + { + if ( timeSinceDistanceCheck > 1 ) + { + timeSinceDistanceCheck = 0; + if ( DistanceSquared( previousOrigin, self.origin ) < mindistanceSq ) + { + failedDistanceCheck = true; + } + else + { + failedDistanceCheck = false; + } + previousOrigin = self.origin; + } + velocity = self GetVelocity(); + + speedsq = lengthsquared( velocity ); + + if ( speedSq > minspeedSq && failedDistanceCheck == false ) + { + SGJammerProtection = true; + } + } + + if ( SGJammerProtection == true && self HasPerk( "specialty_sengrenjammer" ) ) + { + /# + if (GetDvarint( "scr_debug_perk_sengrenjammer")!=0) + { + sphere( self.origin + (0,0,65), 12, (0,1,0), 1, true, 16, 3 ); + } + #/ + currentFailCount = 0; + if ( hasPerk == false ) + { + stateChange = false; + hasPerk = true; + self clientfield::set( "sg_jammer_active", 1 ); + } + } + else + { + currentFailCount++; + + if ( hasPerk == true && currentFailCount >= gracePeriods ) + { + stateChange = true; + hasPerk = false; + self clientfield::set( "sg_jammer_active", 0 ); + } + } + if ( stateChange == true ) + { + level notify("radar_status_change"); + } + timeSinceDistanceCheck += timePeriodSec; + wait( timePeriodSec ); + } +} + diff --git a/mp/_pickup_items.gsc b/mp/_pickup_items.gsc new file mode 100644 index 0000000..0b3bdec --- /dev/null +++ b/mp/_pickup_items.gsc @@ -0,0 +1,537 @@ + +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\weapons\_weapons; + +#using scripts\mp\_util; +#using scripts\mp\gametypes\_weaponobjects; + + + +#namespace pickup_items; + +function autoexec __init__sytem__() { system::register("pickup_items",&__init__,undefined,undefined); } + + + + + +// group 1 - blue +#precache( "xmodel", "p7_perk_t7_hud_perk_flakjacket" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_blind_eye" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_hardline" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_ghost" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_jetsilencer" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_lightweight" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_jetcharge" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_overcharge" ); + +// group 2 - green +#precache( "xmodel", "p7_perk_t7_hud_perk_hardwired" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_scavenger" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_cold_blooded" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_fasthands" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_toughness" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_tracker" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_ante_up" ); + +// group 3 - red +#precache( "xmodel", "p7_perk_t7_hud_perk_dexterity" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_engineer" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_deadsilence" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_tacticalmask" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_awareness" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_sixthsense" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_marathon" ); +#precache( "xmodel", "p7_perk_t7_hud_perk_gungho" ); + +#precache( "objective", "pickup_item" ); + +function __init__() +{ + callback::on_start_gametype( &start_gametype ); + + level.nullPrimaryOffhand = GetWeapon( "null_offhand_primary" ); + level.nullSecondaryOffhand = GetWeapon( "null_offhand_secondary" ); + + level.pickup_items = []; + + level.pickupItemRespawn = true; +} + +function on_player_spawned() // self == player +{ + self.pickup_damage_scale = undefined; + self.pickup_damage_scale_time = undefined; +} + +function start_gametype() +{ + callback::on_spawned( &on_player_spawned ); + + pickup_triggers = GetEntArray( "pickup_item", "targetname" ); + pickup_models = GetEntArray( "pickup_model", "targetname" ); + + visuals = []; + + foreach( trigger in pickup_triggers ) + { + visuals[0] = get_visual_for_trigger( trigger, pickup_models ); + assert( isDefined( visuals[0]) ); + + visuals[0] pickup_item_init(); + + pickup_item_object = gameobjects::create_use_object( "neutral", trigger, visuals, (0,0,32), istring("pickup_item") ); + pickup_item_object gameobjects::allow_use( "any" ); + pickup_item_object gameobjects::set_use_time( 0 ); + pickup_item_object.onUse = &on_touch; + + + level.pickup_items[level.pickup_items.size] = pickup_item_object; + } +} + +function get_visual_for_trigger( trigger, pickup_models ) +{ + foreach( model in pickup_models ) + { + if ( model istouchingswept( trigger ) ) + return model; + } + + return undefined; +} + +function set_pickup_bobbing() +{ + self Bobbing( (0,0,1), 4, 1 ); +} + +function set_pickup_rotation() +{ + self Rotate( (0,175,0) ); +} + +function get_item_for_pickup() +{ + if( self.items.size == 1 ) + { + return self.items[0]; + } + + if ( self.items_shuffle.size == 0 ) + { + self.items_shuffle = ArrayCopy( self.items ); + array::randomize( self.items_shuffle ); + } + + return array::pop_front( self.items_shuffle ); +} + +function cycle_item() +{ + self.current_item = self get_item_for_pickup(); + + if ( isdefined( self.current_item.model ) ) + { + self SetModel( self.current_item.model ); + } +} + +function get_item_from_string_ammo(perks_string) +{ + item_struct = SpawnStruct(); + + item_struct.name = "ammo"; + item_struct.weapon = GetWeapon( "scavenger_item" ); + item_struct.model = item_struct.weapon.worldmodel; + self.angles = ( 0, 0, 90 ); + + self thread weapons::scavenger_think(); + + return item_struct; +} + +function get_item_from_string_damage(perks_string) +{ + item_struct = SpawnStruct(); + + item_struct.name = "damage"; + item_struct.damage_scale = float(perks_string); + item_struct.model = "wpn_t7_igc_bullet_prop"; + self.angles = ( -45, 0, 0 ); + self SetScale ( 2 ); + + return item_struct; +} + +function get_item_from_string_health(perks_string) +{ + item_struct = SpawnStruct(); + + item_struct.name = "health"; + item_struct.extra_health = int(perks_string); + item_struct.model = "p7_medical_surgical_tools_syringe"; + self.angles = ( -45, 0, 45 ); + self SetScale ( 5 ); + + return item_struct; +} + +function get_item_from_string_perk(perks_string) +{ + item_struct = SpawnStruct(); + + if ( !IsDefined(level.perkSpecialties[ perks_string ]) ) + { + /# + util::error("Invalid perk name " + perks_string + " for pick up item at " + self.origin); + #/ + return; + } + + item_struct.name = perks_string; + item_struct.specialties = StrTok( level.perkSpecialties[ perks_string ], "|" ); + item_struct.model = "p7_perk_" + level.perkIcons[ perks_string ]; + self SetScale ( 2 ); + + return item_struct; +} + +function get_item_from_string_weapon(weapon_and_attachments_string) +{ + item_struct = SpawnStruct(); + + weapon_and_attachments = strtok(weapon_and_attachments_string, "+"); + weapon_name = GetSubStr(weapon_and_attachments[0],0,weapon_and_attachments[0].size); + + attachments = array::remove_index( weapon_and_attachments, 0 ); + + item_struct.name = weapon_name; + item_struct.weapon = GetWeapon( weapon_name, attachments ); + item_struct.model = item_struct.weapon.worldmodel; + self SetScale ( 1.5 ); + + return item_struct; +} + +function get_item_from_string( item_string ) +{ + switch( self.script_noteworthy ) + { + case "ammo": + return self get_item_from_string_ammo( item_string ); + case "damage": + return self get_item_from_string_damage( item_string ); + case "health": + return self get_item_from_string_health( item_string ); + case "perk": + return self get_item_from_string_perk( item_string ); + case "weapon": + return self get_item_from_string_weapon( item_string ); + } +} + +function init_items_for_pickup() +{ + items_string = self.script_parameters; + + items_array = strtok(items_string, " "); + + items = []; + + foreach( item_string in items_array ) + { + items[items.size] = self get_item_from_string( item_string ); + } + + return items; +} + +function pickup_item_respawn_time() +{ + switch( self.script_noteworthy ) + { + case "ammo": + return 10; + case "damage": + return 60; + case "health": + return 10; + case "perk": + return 10; + case "weapon": + return 30; + } +} + +function pickup_item_sound_pickup() +{ + switch( self.script_noteworthy ) + { + case "ammo": + return "wpn_ammo_pickup_oldschool"; + case "damage": + return "wpn_weap_pickup_oldschool"; + case "health": + return "wpn_weap_pickup_oldschool"; + case "perk": + return "wpn_weap_pickup_oldschool"; + case "weapon": + return "wpn_weap_pickup_oldschool"; + } +} + +function pickup_item_sound_respawn() +{ + switch( self.script_noteworthy ) + { + case "ammo": + return "wpn_ammo_pickup_oldschool"; + case "damage": + return "wpn_weap_pickup_oldschool"; + case "health": + return "wpn_weap_pickup_oldschool"; + case "perk": + return "wpn_weap_pickup_oldschool"; + case "weapon": + return "wpn_weap_pickup_oldschool"; + } +} + +function pickup_item_init() +{ + self.items_shuffle = []; + + // start the bobbing before calling the init functions so we can change the orientation of + // the models on items without affecting the bob + self set_pickup_bobbing(); + + self.items = self init_items_for_pickup(); + self.respawn_time = self pickup_item_respawn_time(); + self.sound_pickup = self pickup_item_sound_pickup(); + self.sound_respawn = self pickup_item_sound_respawn(); + + self set_pickup_rotation(); + self cycle_item(); +} + +function on_touch( player ) +{ + self endon("respawned"); + + pickup_item = self.visuals[0]; + switch( pickup_item.script_noteworthy ) + { + case "ammo": + pickup_item on_touch_ammo( player ); + break; + case "damage": + pickup_item on_touch_damage( player ); + break; + case "health": + pickup_item on_touch_health( player ); + break; + case "perk": + pickup_item on_touch_perk( player ); + break; + case "weapon": + if ( !pickup_item on_touch_weapon( player ) ) + return; + break; + } + + pickup_item PlaySound( pickup_item.sound_pickup ); + + self gameobjects::set_model_visibility( false ); + self gameobjects::allow_use( "none" ); + + if ( level.pickupItemRespawn ) + { + wait ( pickup_item.respawn_time ); + self thread respawn_pickup(); + } +} + +function respawn_pickup() +{ + self notify("respawned"); + pickup_item = self.visuals[0]; + pickup_item PlaySound( pickup_item.sound_respawn ); + + pickup_item cycle_item(); + + self gameobjects::set_model_visibility( true ); + self gameobjects::allow_use( "any" ); +} + +function respawn_all_pickups() +{ + foreach( item in level.pickup_items ) + { + item respawn_pickup(); + } +} + +function on_touch_ammo( player ) +{ + self notify( "scavenger", player ); + player PickupAmmoEvent(); +} + +function on_touch_damage( player ) +{ + damage_scale_length = 15 * 1000; + player.pickup_damage_scale = self.current_item.damage_scale; + player.pickup_damage_scale_time = GetTime() + damage_scale_length; +} + +function on_touch_health( player ) +{ + if ( self.current_item.extra_health <= 100 ) + { + health = player.health + self.current_item.extra_health; + + if ( health > 100 ) + health = 100; + } + else + { + health = self.current_item.extra_health; + } + + player.health = health; +} + +function on_touch_perk( player ) +{ + foreach( specialty in self.current_item.specialties ) + { + player setPerk( specialty ); + } +} + +function has_active_gadget() +{ + weapons = self GetWeaponsList( true ); + foreach ( weapon in weapons ) + { + if ( !weapon.isgadget ) + continue; + + if ( !weapon.isheroweapon && weapon.offhandslot !== "Gadget" ) + continue; + + slot = self GadgetGetSlot( weapon ); + if ( self GadgetIsActive( slot ) ) + return true; + } + + return false; +} + +function take_player_gadgets() +{ + weapons = self GetWeaponsList( true ); + foreach ( weapon in weapons ) + { + if ( weapon.isgadget ) + { + self TakeWeapon(weapon); + } + } +} + +function take_offhand_weapon( offhandSlot ) +{ + weapons = self GetWeaponsList( true ); + foreach ( weapon in weapons ) + { + if ( weapon.offhandSlot == offhandSlot ) + { + self TakeWeapon(weapon); + return; + } + } +} + +function should_switch_to_pickup_weapon( weapon ) +{ + if ( weapon.isgadget ) + return false; + + if ( weapon.isgrenadeweapon ) + return false; + + return true; +} + +function on_touch_weapon( player ) +{ + weapon = self.current_item.weapon; + had_weapon = player HasWeapon( weapon ); + +// ammo_in_clip = GetWeaponAmmoClip( weapon ); + ammo_in_reserve = player GetWeaponAmmoStock( weapon ); + + if ( weapon.isgadget ) + { + if ( player has_active_gadget() ) + return false; + + // need to take away what they currently have + player take_player_gadgets(); + } + + if ( weapon.inventoryType == "offhand" ) + { + player take_offhand_weapon( weapon.offhandslot ); + } + + player PickupWeaponEvent(weapon); + + player GiveWeapon( weapon ); + + // if for some reason we did not get the weapon drop out + if ( !player HasWeapon( weapon ) ) + return false; + + if ( isDefined(self.script_ammo_clip) && isDefined(self.script_ammo_extra) ) + { + // if they already have the weapon put everything into the reserve + if ( had_weapon ) + { + player SetWeaponAmmoStock( weapon, ammo_in_reserve + self.script_ammo_clip + self.script_ammo_extra ); + } + else + { + if ( self.script_ammo_clip >= 0 ) + { + player SetWeaponAmmoClip( weapon, self.script_ammo_clip ); + } + if ( self.script_ammo_extra >= 0 ) + { + player SetWeaponAmmoStock( weapon, self.script_ammo_extra ); + } + } + } + + if ( weapon.isgadget ) + { + slot = player GadgetGetSlot( weapon ); + player GadgetPowerSet( slot, 100.0 ); + } + + if ( !had_weapon && should_switch_to_pickup_weapon( weapon ) ) + { + player SwitchToWeapon(weapon); + } + + return true; +} diff --git a/mp/_pickup_items.gsh b/mp/_pickup_items.gsh new file mode 100644 index 0000000..8e5db81 --- /dev/null +++ b/mp/_pickup_items.gsh @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/mp/_proximity_grenade.csc b/mp/_proximity_grenade.csc new file mode 100644 index 0000000..a8de8ef --- /dev/null +++ b/mp/_proximity_grenade.csc @@ -0,0 +1,22 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_proximity_grenade; + + + + +#using scripts\mp\_util; + +#namespace proximity_grenade; + +function autoexec __init__sytem__() { system::register("proximity_grenade",&__init__,undefined,undefined); } + +function __init__() +{ + proximity_grenade::init_shared(); +} + diff --git a/mp/_proximity_grenade.gsc b/mp/_proximity_grenade.gsc new file mode 100644 index 0000000..6136d5f --- /dev/null +++ b/mp/_proximity_grenade.gsc @@ -0,0 +1,20 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_proximity_grenade; + + + + +#namespace proximity_grenade; + +function autoexec __init__sytem__() { system::register("proximity_grenade",&__init__,undefined,undefined); } + +function __init__() +{ + proximity_grenade::init_shared(); + + level.trackProximityGrenadesOnOwner = true; +} diff --git a/mp/_radiant_live_update.csc b/mp/_radiant_live_update.csc new file mode 100644 index 0000000..ece77c2 --- /dev/null +++ b/mp/_radiant_live_update.csc @@ -0,0 +1,50 @@ +#using scripts\shared\system_shared; + + + +#namespace radiant_live_update; + +/# +function autoexec __init__sytem__() { system::register("radiant_live_update",&__init__,undefined,undefined); } + +/* --------------------------------------------------------------------------------- +This script handles player radiant live update commands +-----------------------------------------------------------------------------------*/ + +function __init__() +{ + thread scriptstruct_debug_render(); +} + +function scriptstruct_debug_render() +{ + while( 1 ) + { + level waittill( "liveupdate", selected_struct ); + + if( isdefined(selected_struct) ) + { + level thread render_struct( selected_struct ); + } + else + { + level notify( "stop_struct_render" ); + } + } +} + +function render_struct( selected_struct ) +{ + self endon( "stop_struct_render" ); + + if( !isdefined( selected_struct.origin ) ) + return; + + while( isdefined( selected_struct ) ) + { + Box( selected_struct.origin, (-16, -16, -16), (16, 16, 16), 0, (1, 0.4, 0.4) ); + {wait(.016);}; + } +} + +#/ \ No newline at end of file diff --git a/mp/_rat.gsc b/mp/_rat.gsc new file mode 100644 index 0000000..a0370b0 --- /dev/null +++ b/mp/_rat.gsc @@ -0,0 +1,87 @@ +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\rat_shared; +#using scripts\shared\array_shared; + + + +#using scripts\mp\_util; +#using scripts\mp\bots\_bot; +#using scripts\mp\gametypes\_dev; + +/# +#namespace rat; + +function autoexec __init__sytem__() { system::register("rat",&__init__,undefined,undefined); } + +function __init__() +{ + rat_shared::init(); + + // Set up common function for the shared rat script commands to call + level.rat.common.gethostplayer = &util::getHostPlayer; + level.rat.deathCount = 0; + rat_shared::addRATScriptCmd( "addenemy", &rscAddEnemy ); + SetDvar( "rat_death_count", 0 ); + +} + +function rscAddEnemy( params ) +{ + player = [[level.rat.common.gethostplayer]](); + team = "axis"; + + if( isdefined( player.pers["team"] ) ) + { + team = util::getOtherTeam( player.pers["team"] ); + } + + bot = dev::getOrMakeBot( team ); + if ( !isdefined( bot ) ) { + println("Could not add test client"); + RatReportCommandResult( params._id, 0, "Could not add test client" ); + return; + } + + bot thread TestEnemy( team ); + bot thread DeathCounter(); + + // waiting for bot to respawn + wait 2; + + pos = ( Float(params.x), Float(params.y), Float(params.z) ); + bot SetOrigin(pos); + + if( isdefined( params.ax ) ) + { + angles = ( Float(params.ax), Float(params.ay), Float(params.az) ); + bot SetPlayerAngles(angles); + } + + RatReportCommandResult( params._id, 1 ); +} + +function TestEnemy(team) // self == test client +{ + self endon( "disconnect" ); + + while(!isdefined(self.pers["team"])) + wait .05; + + if ( level.teambased ) + { + self notify("menuresponse", game["menu_team"], team); + } +} + +function DeathCounter() +{ + self waittill("death"); + level.rat.deathCount++; + SetDvar( "rat_death_count", level.rat.deathCount ); +} + +#/ + + + diff --git a/mp/_rewindobjects.csc b/mp/_rewindobjects.csc new file mode 100644 index 0000000..788897f --- /dev/null +++ b/mp/_rewindobjects.csc @@ -0,0 +1,296 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\system_shared; + + + +#namespace rewindobjects; + +function autoexec __init__sytem__() { system::register("rewindobjects",&__init__,undefined,undefined); } + +function __init__() +{ + level.rewindWatcherArray = []; + +} + +function initRewindObjectWatchers( localClientNum ) +{ + level.rewindWatcherArray[localClientNum] = []; + createNapalmRewindWatcher( localClientNum ); + createAirstrikeRewindWatcher( localClientNum ); + level thread watchRewindableEvents( localClientNum ); +} + +function watchRewindableEvents( localClientNum ) +{ + for (;;) + { + if ( isdefined( level.rewindWatcherArray[localClientNum] ) ) + { + rewindWatcherKeys = getArrayKeys( level.rewindWatcherArray[localClientNum] ); + for ( i = 0; i < rewindWatcherKeys.size; i++ ) + { + rewindWatcher = level.rewindWatcherArray[localClientNum][rewindWatcherKeys[i]]; + + if ( !isdefined( rewindWatcher ) ) + continue; + + if ( !isdefined( rewindWatcher.event ) ) + continue; + + timeKeys = getArrayKeys( rewindWatcher.event ); + for ( j = 0; j < timeKeys.size; j++ ) + { + timeKey = timeKeys[j]; + if ( rewindWatcher.event[timeKey].inProgress == true ) + continue; + + if ( level.serverTime >= timeKey ) + { + rewindWatcher thread startRewindableEvent( localClientNum, timeKey ); + } + } + } + } + wait( 0.1 ); + } +} + +function startRewindableEvent( localClientNum, timeKey ) +{ + player = getlocalplayer( localClientNum ); + level endon( "demo_jump" + localClientNum ); + self.event[timeKey].inProgress = true; + allFunctionsStarted = false; + while( allFunctionsStarted == false ) + { + allFunctionsStarted = true; + assert(isdefined( self.timedFunctions ) ); + timedFunctionKeys = getArrayKeys( self.timedFunctions ); + + for ( i = 0; i < timedFunctionKeys.size; i++ ) + { + timedFunction = self.timedFunctions[timedFunctionKeys[i]]; + timedFunctionKey = timedFunctionKeys[i]; + if ( self.event[timeKey].timedFunction[timedFunctionKey] == true ) + continue; + startTime = timeKey + ( timedFunction.startTimeSec * 1000 ); + if ( startTime > level.serverTime ) + { + allFunctionsStarted = false; + continue; + } + self.event[timeKey].timedFunction[timedFunctionKey] = true; + + level thread [[timedFunction.func]]( localClientNum, startTime, timedFunction.startTimeSec, self.event[timeKey].data ); + } + wait( 0.1 ); + } +} + +function createNapalmRewindWatcher( localClientNum ) +{ + napalmRewindWatcher = createRewindWatcher( localClientNum, "napalm" ); + + timeIncreaseBetweenPlanes = 0; + + //napalmRewindWatcher addTimedFunction( "napalmPlane", _plane::flyPlane, timeIncreaseBetweenPlanes ); +} + +function createAirstrikeRewindWatcher( localClientNum ) +{ + airstrikeRewindWatcher = createRewindWatcher( localClientNum, "airstrike" ); +// airstrikeRewindWatcher addTimedFunction( "aistrikePlane", _airstrike::flyAirstrikePlane, 0 ); +} + + +function createRewindWatcher( localClientNum, name ) +{ + player = getlocalplayer( localClientNum ); + if ( !isdefined(level.rewindWatcherArray[localClientNum]) ) + { + level.rewindWatcherArray[localClientNum] = []; + } + + rewindWatcher = getRewindWatcher( localClientNum, name ); + + if ( !isdefined( rewindWatcher ) ) + { + rewindWatcher = SpawnStruct(); + level.rewindWatcherArray[localClientNum][level.rewindWatcherArray[localClientNum].size] = rewindWatcher; + } + + rewindWatcher.name = name; + rewindWatcher.event = []; + + rewindWatcher thread resetOnDemoJump( localClientNum ); + + return rewindWatcher; +} + +function resetOnDemoJump( localClientNum ) +{ + for (;;) + { + level waittill( "demo_jump" + localClientNum ); + + self.inProgress = false; + + timedFunctionKeys = getArrayKeys( self.timedFunctions ); + + for ( i = 0; i < timedFunctionKeys.size; i++ ) + { + self.timedFunctions[timedFunctionKeys[i]].inProgress = false; + } + + eventKeys = getArrayKeys( self.event ); + for ( i = 0; i < eventKeys.size; i++ ) + { + self.event[eventKeys[i]].inProgress = false; + timedFunctionKeys = getArrayKeys( self.event[eventKeys[i]].timedFunction ); + for ( index = 0; index < timedFunctionKeys.size; index++ ) + { + self.event[eventKeys[i]].timedFunction[timedFunctionKeys[index]] = false; + } + } + } +} + +function addTimedFunction( name, func, relativeStartTimeInSecs ) +{ + if ( !isdefined(self.timedFunctions) ) + { + self.timedFunctions = []; + } + + assert( !isdefined(self.timedFunctions[name] ) ); + + self.timedFunctions[name] = spawnStruct(); + self.timedFunctions[name].inProgress = false; + self.timedFunctions[name].func = func; + self.timedFunctions[name].startTimeSec = relativeStartTimeInSecs; +} + +function getRewindWatcher( localClientNum, name ) +{ + if ( !isdefined( level.rewindWatcherArray[localClientNum] ) ) + { + return undefined; + } + + for ( watcher = 0; watcher < level.rewindWatcherArray[localClientNum].size; watcher++ ) + { + if (level.rewindWatcherArray[localClientNum][watcher].name == name ) + { + return level.rewindWatcherArray[localClientNum][watcher]; + } + } + + return undefined; +} + +function addRewindableEventToWatcher( startTime, data ) +{ + // duplicate message + if ( isdefined( self.event[startTime] ) ) + { + return; + } + + self.event[startTime] = spawnStruct(); + self.event[startTime].data = data; + self.event[startTime].inprogress = false; + + if ( isdefined( self.timedFunctions ) ) + { + timedFunctionKeys = getArrayKeys( self.timedFunctions ); + self.event[startTime].timedFunction = []; + for ( i = 0; i < timedFunctionKeys.size; i++ ) + { + timedFunctionKey = timedFunctionKeys[i]; + self.event[startTime].timedFunction[timedFunctionKey] = false; + } + } +} + +function serverTimedMoveTo( localClientNum, startPoint, endPoint, startTime, duration ) +{ + level endon( "demo_jump" + localClientNum ); + timeElapsed = ( level.serverTime - startTime ) * 0.001; + assert( duration > 0 ); + doJump = true; + if ( timeElapsed < 0.02 ) + doJump = false; + if ( timeElapsed < duration ) + { + moveTime = duration - timeElapsed; + if ( doJump ) + { + jumpPoint = getPointOnLine( startPoint, endPoint, ( timeElapsed / duration ) ); + self.origin = jumpPoint; + } + self moveTo( endPoint, moveTime, 0, 0 ); + return true; + } + else + { + self.origin = endPoint; + return false; + } +} + +function serverTimedRotateTo( localClientNum, angles, startTime, duration, timeIn, timeout ) +{ + level endon( "demo_jump" + localClientNum ); + timeElapsed = ( level.serverTime - startTime ) * 0.001; + if ( !isdefined ( timeIn ) ) + { + timeIn = 0; + } + if ( !isdefined( timeout ) ) + { + timeOut = 0; + } + + assert( duration > 0 ); + if ( timeElapsed < duration ) + { + rotateTime = duration - timeElapsed; + self RotateTo(angles, rotateTime, timeIn, timeOut ); + return true; + } + else + { + self.angles = angles; + return false; + } +} + +function waitForServerTime( localClientNum, timeFromStart ) +{ + while( timeFromStart > level.serverTime ) + { + {wait(.016);}; + } +} + +function removeClientEntOnJump( clientEnt, localClientNum ) +{ + clientEnt endon( "complete" ); + player = GetLocalPlayer( localClientNum ); + level waittill( "demo_jump" + localClientNum ); + + clientEnt notify( "delete" ); + clientEnt forcedelete(); +} + +function getPointOnLine( startPoint, endPoint, ratio ) +{ + nextPoint = ( startPoint[0] + ( ( endPoint[0] - startPoint[0] ) * ratio ) , + startPoint[1] + ( ( endPoint[1] - startPoint[1] ) * ratio ) , + startPoint[2] + ( ( endPoint[2] - startPoint[2] ) * ratio ) ); + + return nextPoint; +} + diff --git a/mp/_riotshield.csc b/mp/_riotshield.csc new file mode 100644 index 0000000..d2e20d0 --- /dev/null +++ b/mp/_riotshield.csc @@ -0,0 +1,20 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_riotshield; + + + + +#using scripts\mp\_util; + +#namespace riotshield; + +function autoexec __init__sytem__() { system::register("riotshield",&__init__,undefined,undefined); } + +function __init__() +{ + riotshield::init_shared(); +} diff --git a/mp/_riotshield.gsc b/mp/_riotshield.gsc new file mode 100644 index 0000000..5c540a9 --- /dev/null +++ b/mp/_riotshield.gsc @@ -0,0 +1,23 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_riotshield; + + + + +#using scripts\mp\_util; + +#namespace riotshield; + +function autoexec __init__sytem__() { system::register("riotshield",&__init__,undefined,undefined); } + +function __init__() +{ + riotshield::init_shared(); +} \ No newline at end of file diff --git a/mp/_rotating_object.csc b/mp/_rotating_object.csc new file mode 100644 index 0000000..3226f00 --- /dev/null +++ b/mp/_rotating_object.csc @@ -0,0 +1,92 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#namespace rotating_object; + +function autoexec __init__sytem__() { system::register("rotating_object",&__init__,undefined,undefined); } + +/* +This script sets up functionality in multiplayer where an object can be rotated. It must be given the "rotating_object" targetname. +It must also have a script_float, which determines how many seconds it take for the object to complete a full 360 degree rotation. +This script is called by the _load.csc +*/ + +function __init__() +{ + callback::on_localclient_connect( &init ); +} + +//Start a rotation thread on each object with the appropriate targetname and set a rotation speed dvar. +function init( localClientNum ) +{ + rotating_objects = GetEntArray( localClientNum, "rotating_object", "targetname" ); + array::thread_all( rotating_objects,&rotating_object_think ); +} + +//Set up rotation behvahior. 'Self' is the rotating object. +function rotating_object_think() +{ + self endon ("entityshutdown"); + + util::waitforallclients(); + + //I create this variable to manage what kind of rotate I want to use + axis = "yaw"; + direction = 360; + revolutions = 100; + rotate_time = 12; + + if( isdefined( self.script_noteworthy ) ) + { + axis = self.script_noteworthy; + } + + if( isdefined( self.script_float ) ) + { + //The script_float on the object determines how fast it spins, if it is defined + rotate_time = self.script_float; + } + + //Prevent SRE if zero were to be passed as a time value in the rotate function + if ( rotate_time == 0 ) + { + //Default spin speed + rotate_time = 12; + } + + if ( rotate_time < 0 ) + { + direction *= -1; + rotate_time *= -1; + } + + angles = self.angles; + + while( 1 ) + { + switch( axis ) + { + case "roll": + self RotateRoll( direction * revolutions, rotate_time * revolutions ); + break; + + case "pitch": + self RotatePitch( direction * revolutions, rotate_time * revolutions ); + break; + + case "yaw": + default: + self RotateYaw( direction * revolutions, rotate_time * revolutions ); + break; + } + + self waittill( "rotatedone" ); + self.angles = angles; + } +} diff --git a/mp/_satchel_charge.csc b/mp/_satchel_charge.csc new file mode 100644 index 0000000..05873a3 --- /dev/null +++ b/mp/_satchel_charge.csc @@ -0,0 +1,19 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_satchel_charge; + + + +#using scripts\mp\_util; + +#namespace satchel_charge; + +function autoexec __init__sytem__() { system::register("satchel_charge",&__init__,undefined,undefined); } + +function __init__( localClientNum ) +{ + satchel_charge::init_shared(); +} diff --git a/mp/_satchel_charge.gsc b/mp/_satchel_charge.gsc new file mode 100644 index 0000000..32eae14 --- /dev/null +++ b/mp/_satchel_charge.gsc @@ -0,0 +1,18 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_satchel_charge; + + + +#using scripts\mp\_util; + +#namespace satchel_charge; + +function autoexec __init__sytem__() { system::register("satchel_charge",&__init__,undefined,undefined); } + +function __init__() +{ + satchel_charge::init_shared(); +} diff --git a/mp/_scoreevents.gsc b/mp/_scoreevents.gsc new file mode 100644 index 0000000..da2ef06 --- /dev/null +++ b/mp/_scoreevents.gsc @@ -0,0 +1,1694 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\abilities\_ability_player; +#using scripts\shared\weapons\_weapon_utils; + + + + +#using scripts\mp\gametypes\_globallogic_score; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\_teamops; + + + +#namespace scoreevents; + +function autoexec __init__sytem__() { system::register("scoreevents",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); +} + +function init() +{ + level.scoreEventCallbacks = []; + level.scoreEventGameEndCallback =&onGameEnd; + + registerScoreEventCallback( "playerKilled", &scoreevents::scoreEventPlayerKill ); +} +function scoreEventTableLookupInt( index, scoreEventColumn ) +{ + return int( tableLookup( getScoreEventTableName(), 0, index, scoreEventColumn ) ); +} + +function scoreEventTableLookup( index, scoreEventColumn ) +{ + return tableLookup( getScoreEventTableName(), 0, index, scoreEventColumn ); +} + +function registerScoreEventCallback( callback, func ) +{ + if ( !isdefined( level.scoreEventCallbacks[callback] ) ) + { + level.scoreEventCallbacks[callback] = []; + } + level.scoreEventCallbacks[callback][level.scoreEventCallbacks[callback].size] = func; +} + +function scoreEventPlayerKill( data, time ) +{ + victim = data.victim; + attacker = data.attacker; + time = data.time; + level.numKills++; + attacker.lastKilledPlayer = victim; + wasDefusing = data.wasDefusing; + wasPlanting = data.wasPlanting; + victimWasOnGround = data.victimOnGround; + attackerWasOnGround = data.attackerOnGround; + meansOfDeath = data.sMeansOfDeath; + attackerTraversing = data.attackerTraversing; + attackerWallRunning = data.attackerWallRunning; + attackerDoubleJumping = data.attackerDoubleJumping; + attackerSliding = data.attackerSliding; + victimWasWallRunning = data.victimWasWallRunning; + victimWasDoubleJumping = data.victimWasDoubleJumping; + attackerSpeedburst = data.attackerSpeedburst; + victimSpeedburst = data.victimSpeedburst; + victimCombatEfficieny = data.victimCombatEfficieny; + attackerflashbackTime = data.attackerflashbackTime; + victimflashbackTime = data.victimflashbackTime; + victimSpeedburstLastOnTime = data.victimSpeedburstLastOnTime; + victimCombatEfficiencyLastOnTime = data.victimCombatEfficiencyLastOnTime; + victimVisionPulseActivateTime = data.victimVisionPulseActivateTime; + attackerVisionPulseActivateTime = data.attackerVisionPulseActivateTime; + victimVisionPulseActivateTime = data.victimVisionPulseActivateTime; + attackerVisionPulseArray = data.attackerVisionPulseArray; + victimVisionPulseArray = data.victimVisionPulseArray; + attackerVisionPulseOriginArray = data.attackerVisionPulseOriginArray; + victimVisionPulseOriginArray = data.victimVisionPulseOriginArray; + attackerVisionPulseOrigin = data.attackerVisionPulseOrigin; + victimVisionPulseOrigin = data.victimVisionPulseOrigin; + attackerWasFlashed = data.attackerWasFlashed; + attackerWasConcussed = data.attackerWasConcussed; + victimWasUnderwater = data.wasUnderwater; + victimHeroAbilityActive = data.victimHeroAbilityActive; + victimHeroAbility = data.victimHeroAbility; + attackerHeroAbilityActive = data.attackerHeroAbilityActive; + attackerHeroAbility = data.attackerHeroAbility; + attackerWasHeatWaveStunned = data.attackerWasHeatWaveStunned; + victimWasHeatWaveStunned = data.victimWasHeatWaveStunned; + victimElectrifiedBy = data.victimElectrifiedBy; + victimWasInSlamState = data.victimWasInSlamState; + victimBledOut = data.bledOut; + victimWasLungingWithArmBlades = data.victimWasLungingWithArmBlades; + victimPowerArmorLastTookDamageTime = data.victimPowerArmorLastTookDamageTime; + victimHeroWeaponKillsThisActivation = data.victimHeroWeaponKillsThisActivation; + victimGadgetPower = data.victimGadgetPower; + victimGadgetWasActiveLastDamage = ( data.victimGadgetWasActiveLastDamage === true ); + victimIsThiefOrRoulette = data.victimIsThiefOrRoulette; + attackerIsRoulette = data.attackerIsRoulette; + victimHeroAbilityName = data.victimHeroAbilityName; // note that victim hero ability name cannot be reliably taken from victimHeroAbility, so this is done instead + attackerInVehicleArchetype = data.attackerInVehicleArchetype; + if ( isdefined( victimHeroAbilityName ) ) + victimHeroAbilityEquipped = GetWeapon( victimHeroAbilityName ); + + if ( victimBledOut == true ) // do not process player killed score events if the player bled out + return; + + exlosiveDamage = false; + attackerShotVictim = ( meansOfDeath == "MOD_PISTOL_BULLET" || meansOfDeath == "MOD_RIFLE_BULLET" || meansOfDeath == "MOD_HEAD_SHOT" ); + + weapon = level.weaponNone; + inflictor = data.eInflictor; + isGrenade = false; + if ( isdefined( data.weapon ) ) + { + weapon = data.weapon; + weaponClass = util::getWeaponClass( data.weapon ); + isGrenade = weapon.isGrenadeWeapon; + killstreak = killstreaks::get_from_weapon( data.weapon ); + } + + victim.anglesOnDeath = victim getPlayerAngles(); + + if ( meansOfDeath == "MOD_GRENADE" || meansOfDeath == "MOD_GRENADE_SPLASH" || meansOfDeath == "MOD_EXPLOSIVE" || meansOfDeath == "MOD_EXPLOSIVE_SPLASH" || meansOfDeath == "MOD_PROJECTILE" || meansOfDeath == "MOD_PROJECTILE_SPLASH" ) + { + if ( weapon == level.weaponNone && isdefined( data.victim.explosiveInfo["weapon"] ) ) + weapon = data.victim.explosiveInfo["weapon"]; + + exlosiveDamage = true; + } + + + if ( !isdefined( killstreak ) ) + { + if ( level.teamBased ) + { + if ( isdefined( victim.lastKillTime ) && ( victim.lastKillTime > time - 3000 ) ) + { + if ( isdefined( victim.lastkilledplayer ) && victim.lastkilledplayer util::IsEnemyPlayer( attacker ) == false && attacker != victim.lastkilledplayer ) + { + processScoreEvent( "kill_enemy_who_killed_teammate", attacker, victim, weapon ); + victim RecordKillModifier("avenger"); + } + } + + if ( isdefined( victim.damagedPlayers ) ) + { + keys = getarraykeys(victim.damagedPlayers); + + for ( i = 0; i < keys.size; i++ ) + { + key = keys[i]; + if ( key == attacker.clientid ) + continue; + + if ( !isdefined( victim.damagedPlayers[key].entity ) ) + continue; + + if ( attacker util::IsEnemyPlayer( victim.damagedPlayers[key].entity ) ) + continue; + + if ( ( time - victim.damagedPlayers[key].time ) < 1000 ) + { + processScoreEvent( "kill_enemy_injuring_teammate", attacker, victim, weapon ); + if ( isdefined( victim.damagedPlayers[key].entity ) ) + { + victim.damagedPlayers[key].entity.lastRescuedBy = attacker; + victim.damagedPlayers[key].entity.lastRescuedTime = time; + } + victim RecordKillModifier("defender"); + } + } + } + } + + if ( isGrenade == false || ( weapon.name == "hero_gravityspikes" ) ) + { + if ( victimWasDoubleJumping == true ) + { + if ( attackerDoubleJumping == true ) + { + processScoreEvent( "kill_enemy_while_both_in_air", attacker, victim, weapon ); + } + + processScoreEvent( "kill_enemy_that_is_in_air", attacker, victim, weapon ); + attacker addPlayerStat( "kill_enemy_that_in_air", 1 ); + } + + if ( attackerDoubleJumping == true ) + { + processScoreEvent( "kill_enemy_while_in_air", attacker, victim, weapon ); + attacker addPlayerStat( "kill_while_in_air", 1 ); + } + + if ( victimWasWallRunning == true ) + { + processScoreEvent( "kill_enemy_that_is_wallrunning", attacker, victim, weapon ); + attacker addPlayerStat( "kill_enemy_thats_wallrunning", 1 ); + } + + if ( attackerWallRunning == true ) + { + processScoreEvent( "kill_enemy_while_wallrunning", attacker, victim, weapon ); + attacker addPlayerStat( "kill_while_wallrunning", 1 ); + } + + if ( attackerSliding == true ) + { + processScoreEvent( "kill_enemy_while_sliding", attacker, victim, weapon ); + attacker addPlayerStat( "kill_while_sliding", 1 ); + } + + if ( attackerTraversing == true ) + { + processScoreEvent( "traversal_kill", attacker, victim, weapon ); + attacker addPlayerStat( "kill_while_mantling", 1 ); + } + + if ( attackerWasFlashed ) + { + processScoreEvent( "kill_enemy_while_flashbanged", attacker, victim, weapon ); + } + + if ( attackerWasConcussed ) + { + processScoreEvent( "kill_enemy_while_stunned", attacker, victim, weapon ); + } + + if ( attackerWasHeatWaveStunned ) + { + processScoreEvent( "kill_enemy_that_heatwaved_you", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_ability_enemy" ); + } + + if ( victimWasHeatWaveStunned ) + { + if ( isdefined( victim._heat_wave_stunned_by ) && + isdefined( victim._heat_wave_stunned_by[attacker.clientid] ) && + victim._heat_wave_stunned_by[attacker.clientid] >= time ) + { + processScoreEvent( "heatwave_kill", attacker, victim, weapon ); + attacker hero_ability_kill_event( getWeapon( "gadget_heat_wave" ), get_equipped_hero_ability( victimHeroAbilityName ) ); + attacker specialistMedalAchievement(); + attacker thread specialistStatAbilityUsage( 4, false ); + } + + if ( attackerIsRoulette && !victimIsThiefOrRoulette && victimHeroAbilityName === "gadget_heat_wave" ) + { + processScoreEvent( "kill_enemy_with_their_hero_ability", attacker, victim, weapon ); + } + } + } + + if ( attackerSpeedburst == true ) + { + processScoreEvent( "speed_burst_kill", attacker, victim, weapon ); + attacker hero_ability_kill_event( GetWeapon( "gadget_speed_burst" ), get_equipped_hero_ability( victimHeroAbilityName ) ); + attacker specialistMedalAchievement(); + attacker thread specialistStatAbilityUsage( 4, false ); + + if ( attackerIsRoulette && !victimIsThiefOrRoulette && victimHeroAbilityName === "gadget_speed_burst" ) + { + processScoreEvent( "kill_enemy_with_their_hero_ability", attacker, victim, weapon ); + } + } + + if ( victimSpeedburstLastOnTime > time - 50 ) + { + processScoreEvent( "kill_enemy_who_is_speedbursting", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_ability_enemy" ); + } + + if ( victimCombatEfficiencyLastOnTime > time - 50 ) + { + processScoreEvent( "kill_enemy_who_is_using_focus", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_ability_enemy" ); + } + + if ( attackerflashbackTime != 0 && attackerflashbackTime > time - 4000 ) + { + processScoreEvent( "flashback_kill", attacker, victim, weapon ); + attacker hero_ability_kill_event( GetWeapon( "gadget_flashback" ), get_equipped_hero_ability( victimHeroAbilityName ) ); + attacker specialistMedalAchievement(); + attacker thread specialistStatAbilityUsage( 4, false ); + + if ( attackerIsRoulette && !victimIsThiefOrRoulette && victimHeroAbilityName === "gadget_flashback" ) + { + processScoreEvent( "kill_enemy_with_their_hero_ability", attacker, victim, weapon ); + } + } + + if ( victimflashbackTime != 0 && victimflashbackTime > time - 4000 ) + { + processScoreEvent( "kill_enemy_who_has_flashbacked", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_ability_enemy" ); + } + + if ( victimWasInSlamState ) + { + processScoreEvent( "end_enemy_gravity_spike_attack", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_weapon_enemy" ); + } + + if ( challenges::isHighestScoringPlayer( victim ) ) + { + processScoreEvent( "kill_enemy_who_has_high_score", attacker, victim, weapon ); + } + + if ( victimWasUnderwater && exlosiveDamage ) + { + processScoreEvent( "kill_underwater_enemy_explosive", attacker, victim, weapon ); + } + + if ( isdefined ( victimElectrifiedBy ) && victimElectrifiedBy != attacker ) + { + processScoreEvent( "electrified", victimElectrifiedBy, victim, weapon ); + } + + if ( victimVisionPulseActivateTime != 0 && victimVisionPulseActivateTime > time - 4000 ) + { + gadgetWeapon = getWeapon("gadget_vision_pulse"); + for ( i = 0; i < victimVisionPulseArray.size; i++ ) + { + player = victimVisionPulseArray[i]; + if ( player == attacker ) + { + gadget = GetWeapon( "gadget_vision_pulse" ); + + if ( victimVisionPulseActivateTime + 300 > time - gadgetWeapon.gadget_pulse_duration ) + { + distanceToPulse = distance( victimVisionPulseOriginArray[i], victimVisionPulseOrigin ); + + ratio = distanceToPulse / gadgetWeapon.gadget_pulse_max_range; + timing = ratio * gadgetWeapon.gadget_pulse_duration; + if ( victimVisionPulseActivateTime + 300 > time - timing ) + { + break; + } + } + + processScoreEvent( "kill_enemy_that_pulsed_you", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_ability_enemy" ); + break; + } + } + + if ( isdefined( victimHeroAbility ) ) + { + attacker notify( "hero_shutdown", victimHeroAbility ); + attacker notify( "hero_shutdown_gadget", victimHeroAbility, victim ); + } + } + + if ( attackerVisionPulseActivateTime != 0 && attackerVisionPulseActivateTime > time - ( 2000 + 4000 + 500 ) ) + { + gadgetWeapon = getWeapon("gadget_vision_pulse"); + for ( i = 0; i < attackerVisionPulseArray.size; i++ ) + { + player = attackerVisionPulseArray[i]; + if ( player == victim ) + { + gadget = GetWeapon( "gadget_vision_pulse" ); + + if ( attackerVisionPulseActivateTime > time - gadgetWeapon.gadget_pulse_duration ) + { + distanceToPulse = distance( attackerVisionPulseOriginArray[i], attackerVisionPulseOrigin ); + + ratio = distanceToPulse / gadgetWeapon.gadget_pulse_max_range; + timing = ratio * gadgetWeapon.gadget_pulse_duration; + if ( attackerVisionPulseActivateTime > time - timing ) + { + break; + } + } + + processScoreEvent( "vision_pulse_kill", attacker, victim, weapon ); + attacker hero_ability_kill_event( gadgetWeapon, get_equipped_hero_ability( victimHeroAbilityName ) ); + attacker specialistMedalAchievement(); + attacker thread specialistStatAbilityUsage( 4, false ); + + if ( attackerIsRoulette && !victimIsThiefOrRoulette && victimHeroAbilityName === "gadget_vision_pulse" ) + { + processScoreEvent( "kill_enemy_with_their_hero_ability", attacker, victim, weapon ); + } + break; + } + } + } + + if ( victimHeroAbilityActive && isdefined ( victimHeroAbility ) ) + { + attacker notify( "hero_shutdown", victimHeroAbility ); + attacker notify( "hero_shutdown_gadget", victimHeroAbility, victim ); + + switch( victimHeroAbility.name ) + { + case "gadget_armor": + processScoreEvent( "kill_enemy_who_has_powerarmor", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_ability_enemy" ); + break; + case "gadget_resurrect": + processScoreEvent( "kill_enemy_that_used_resurrect", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_ability_enemy" ); + break; + case "gadget_camo": + processScoreEvent( "kill_enemy_that_is_using_optic_camo", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_ability_enemy" ); + break; + case "gadget_clone": + processScoreEvent( "end_enemy_psychosis", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_ability_enemy" ); + break; + } + } + else + { + if ( isdefined( victimPowerArmorLastTookDamageTime ) && ( time - victimPowerArmorLastTookDamageTime <= 3000 ) ) + { + attacker notify( "hero_shutdown", victimHeroAbility ); + attacker notify( "hero_shutdown_gadget", victimHeroAbility, victim ); + processScoreEvent( "kill_enemy_who_has_powerarmor", attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_ability_enemy" ); + } + } + + + if ( isdefined( data.victimWeapon ) && isdefined( data.victimWeapon.isHeroWeapon ) && data.victimWeapon.isHeroWeapon == true ) + { + attacker notify( "hero_shutdown", data.victimWeapon ); + attacker notify( "hero_shutdown_gadget", data.victimWeapon, victim ); + } + else if ( isdefined( victim.heroWeapon ) && victimGadgetWasActiveLastDamage && victimGadgetPower < 100 ) + { + // need to do this for some hero weapons like armblades + attacker notify( "hero_shutdown", victim.heroWeapon ); + attacker notify( "hero_shutdown_gadget", victim.heroWeapon, victim ); + } + + if ( attackerHeroAbilityActive && isdefined ( attackerHeroAbility ) ) + { + abilityToCheck = undefined; + + switch( attackerHeroAbility.name ) + { + case "gadget_armor": + { + processScoreEvent( "power_armor_kill", attacker, victim, weapon ); + attacker hero_ability_kill_event( attackerHeroAbility, get_equipped_hero_ability( victimHeroAbilityName ) ); + attacker specialistMedalAchievement(); + attacker thread specialistStatAbilityUsage( 4, false ); + abilityToCheck = attackerHeroAbility.name; + } + break; + case "gadget_resurrect": + { + processScoreEvent( "resurrect_kill", attacker, victim, weapon ); + attacker hero_ability_kill_event( attackerHeroAbility, get_equipped_hero_ability( victimHeroAbilityName ) ); + attacker specialistMedalAchievement(); + attacker thread specialistStatAbilityUsage( 4, false ); + abilityToCheck = attackerHeroAbility.name; + } + break; + case "gadget_camo": + { + processScoreEvent( "optic_camo_kill", attacker, victim, weapon ); + attacker hero_ability_kill_event( attackerHeroAbility, get_equipped_hero_ability( victimHeroAbilityName ) ); + attacker specialistMedalAchievement(); + attacker thread specialistStatAbilityUsage( 4, false ); + abilityToCheck = attackerHeroAbility.name; + } + break; + case "gadget_clone": + { + processScoreEvent( "kill_enemy_while_using_psychosis", attacker, victim, weapon ); + attacker hero_ability_kill_event( attackerHeroAbility, get_equipped_hero_ability( victimHeroAbilityName ) ); + attacker specialistMedalAchievement(); + attacker thread specialistStatAbilityUsage( 4, false ); + abilityToCheck = attackerHeroAbility.name; + } + break; + } + + if ( attackerIsRoulette && !victimIsThiefOrRoulette && isdefined( abilityToCheck ) && victimHeroAbilityName === abilityToCheck ) + { + processScoreEvent( "kill_enemy_with_their_hero_ability", attacker, victim, weapon ); + } + } + + if ( victimWasLungingWithArmBlades ) + { + processScoreEvent( "end_enemy_armblades_attack", attacker, victim, weapon ); + } + + if ( isdefined( data.victimWeapon ) ) + { + killedHeroWeaponEnemy( attacker, victim, weapon, data.victimWeapon, victimGadgetPower, victimGadgetWasActiveLastDamage ); + + if ( data.victimWeapon.name == "minigun" ) + { + processScoreEvent( "killed_death_machine_enemy", attacker, victim, weapon ); + } + else if ( data.victimWeapon.name == "m32" ) + { + processScoreEvent( "killed_multiple_grenade_launcher_enemy", attacker, victim, weapon ); + } + + // armblade is special case since the victimWeapon can be a primary or secondary weapon + is_hero_armblade_and_active = ( isdefined( victim.heroweapon ) && victim.heroweapon.name == "hero_armblade" && victimGadgetWasActiveLastDamage ); + + if ( ( data.victimWeapon.inventorytype == "hero" || is_hero_armblade_and_active ) && victimGadgetPower < 100 ) + { + if ( victimHeroWeaponKillsThisActivation == 0 ) + { + attacker AddPlayerStat( "kill_before_specialist_weapon_use", 1 ); + } + + if ( weapon.inventorytype == "hero" ) + { + attacker AddPlayerStat( "kill_specialist_with_specialist", 1 ); + } + + // add here + attacker_is_thief = ( isdefined( attacker.heroweapon ) && attacker.heroweapon.name == "gadget_thief" ); + if ( !attacker_is_thief ) + { + processScoreEvent( "end_enemy_specialist_weapon", attacker, victim, weapon ); + } + } + } + + if ( weapon.rootweapon.name == "frag_grenade" ) + { + attacker thread updateSingleFragMultiKill( victim, weapon, weaponClass, killstreak ); + } + + attacker thread updateMultiKills( weapon, weaponClass, killstreak, victim ); + + if ( level.numKills == 1 ) + { + victim RecordKillModifier("firstblood"); + processScoreEvent( "first_kill", attacker, victim, weapon ); + } + else + { + if ( isdefined( attacker.lastKilledBy ) ) + { + if ( attacker.lastKilledBy == victim ) + { + level.globalPaybacks++; + processScoreEvent( "revenge_kill", attacker, victim, weapon ); + attacker AddWeaponStat( weapon, "revenge_kill", 1 ); + victim RecordKillModifier("revenge"); + attacker.lastKilledBy = undefined; + } + } + if ( victim killstreaks::is_an_a_killstreak() ) + { + level.globalBuzzKills++; + processScoreEvent( "stop_enemy_killstreak", attacker, victim, weapon ); + victim RecordKillModifier("buzzkill"); + } + if ( isdefined( victim.lastManSD ) && victim.lastManSD == true ) + { + processScoreEvent( "final_kill_elimination", attacker, victim, weapon ); + if ( isdefined( attacker.lastManSD ) && attacker.lastManSD == true ) + { + processScoreEvent( "elimination_and_last_player_alive", attacker, victim, weapon ); + } + } + } + + if ( is_weapon_valid( meansOfDeath, weapon, weaponClass, killstreak ) ) + { + if ( isdefined( victim.vAttackerOrigin ) ) + attackerOrigin = victim.vAttackerOrigin; + else + attackerOrigin = attacker.origin; + distToVictim = distanceSquared( victim.origin, attackerOrigin ); + weap_min_dmg_range = get_distance_for_weapon( weapon, weaponClass ); + if ( distToVictim > weap_min_dmg_range ) + { + attacker challenges::longDistanceKillMP( weapon ); + if ( weapon.rootweapon.name == "hatchet" ) + { + attacker challenges::longDistanceHatchetKill(); + } + processScoreEvent( "longshot_kill", attacker, victim, weapon ); + attacker.pers["longshots"]++; + attacker.longshots = attacker.pers["longshots"]; + victim RecordKillModifier("longshot"); + } + + // Record kill distances and num of entries + killdistance = distance( victim.origin, attackerOrigin ); + attacker.pers["kill_distances"] += killdistance; + attacker.pers["num_kill_distance_entries"]++; + } + + if ( isAlive( attacker ) ) + { + if ( attacker.health < ( attacker.maxHealth * 0.35 ) ) + { + attacker.lastKillWhenInjured = time; + processScoreEvent( "kill_enemy_when_injured", attacker, victim, weapon ); + + attacker AddWeaponStat( weapon, "kill_enemy_when_injured", 1 ); + if ( attacker util::has_toughness_perk_purchased_and_equipped() ) + { + attacker AddPlayerStat( "perk_bulletflinch_kills", 1 ); + } + } + } + else + { + if ( isdefined( attacker.deathtime ) && ( ( attacker.deathtime + 800 ) < time ) && !attacker IsInVehicle() ) + { + level.globalAfterlifes++; + processScoreEvent( "kill_enemy_after_death", attacker, victim, weapon ); + victim RecordKillModifier("posthumous"); + } + } + + if( attacker.cur_death_streak >= 3 ) + { + level.globalComebacks++; + processScoreEvent( "comeback_from_deathstreak", attacker, victim, weapon ); + victim RecordKillModifier("comeback"); + } + + if ( isdefined( victim.lastMicrowavedBy ) ) + { + foreach( beingMicrowavedBy in victim.beingMicrowavedBy ) + { + if ( isdefined( beingMicrowavedBy ) && ( attacker util::IsEnemyPlayer( beingMicrowavedBy ) == false ) ) + { + if ( beingMicrowavedBy != attacker ) + { + scoreGiven = processScoreEvent( "microwave_turret_assist", beingMicrowavedBy, victim, weapon ); + if ( isdefined ( scoreGiven ) ) + { + beingMicrowavedBy challenges::earnedMicrowaveAssistScore( scoreGiven ); + } + } + else + { + attackerMicrowavedVictim = true; + } + } + } + + if ( attackerMicrowavedVictim === true && weapon.name != "microwave_turret" ) + { + attacker challenges::killWhileDamagingWithHPM(); + } + } + + if ( weapon_utils::isMeleeMOD( meansOfDeath ) && !weapon.isRiotShield ) + { + // "stabs" are now "melees" + attacker.pers["stabs"]++; + attacker.stabs = attacker.pers["stabs"]; + + if ( meansOfDeath == "MOD_MELEE_WEAPON_BUTT" && weapon.name != "ball" ) + { + processScoreEvent( "kill_enemy_with_gunbutt", attacker, victim, weapon ); + } + else if ( weapon_utils::isPunch( weapon ) ) + { + processScoreEvent( "kill_enemy_with_fists", attacker, victim, weapon ); + } + else if ( weapon_utils::isNonBareHandsMelee( weapon ) ) + { + vAngles = victim.anglesOnDeath[1]; + pAngles = attacker.anglesOnKill[1]; + angleDiff = AngleClamp180( vAngles - pAngles ); + + if ( angleDiff > -30 && angleDiff < 70 ) + { + level.globalBackstabs++; + processScoreEvent( "backstabber_kill", attacker, victim, weapon ); + + weaponPickedUp = false; + if( isdefined( attacker.pickedUpWeapons ) && isdefined( attacker.pickedUpWeapons[weapon] ) ) + { + weaponPickedUp = true; + } + + attacker AddWeaponStat( weapon, "backstabber_kill", 1, attacker.class_num, weaponPickedUp, undefined, attacker.primaryLoadoutGunSmithVariantIndex, attacker.secondaryLoadoutGunSmithVariantIndex ); + attacker.pers["backstabs"]++; + attacker.backstabs = attacker.pers["backstabs"]; + } + } + } + else + { + if ( isdefined ( victim.firstTimeDamaged ) && victim.firstTimeDamaged == time && !( isdefined( weapon.isHeroWeapon ) && weapon.isHeroWeapon ) ) + { + if ( attackerShotVictim ) + { + attacker thread updateOneShotMultiKills( victim, weapon, victim.firstTimeDamaged ); + attacker AddWeaponStat( weapon, "kill_enemy_one_bullet", 1 ); + } + } + } + + if ( isdefined( attacker.tookWeaponFrom ) && isdefined( attacker.tookWeaponFrom[ weapon ] ) && isdefined( attacker.tookWeaponFrom[ weapon ].previousOwner ) ) + { + pickedUpWeapon = attacker.tookWeaponFrom[ weapon ]; + if ( pickedUpWeapon.previousOwner == victim ) + { + processScoreEvent( "kill_enemy_with_their_weapon", attacker, victim, weapon ); + attacker AddWeaponStat( weapon, "kill_enemy_with_their_weapon", 1 ); + if ( isdefined( pickedUpWeapon.sWeapon ) && isdefined( pickedUpWeapon.sMeansOfDeath ) && weapon_utils::isMeleeMOD( pickedUpWeapon.sMeansOfDeath ) ) + { + foreach( meleeWeapon in level.meleeWeapons ) + { + if ( weapon != meleeWeapon && pickedUpWeapon.sWeapon.rootweapon == meleeWeapon ) + { + attacker AddWeaponStat( meleeWeapon, "kill_enemy_with_their_weapon", 1 ); + break; // once found and handled, no need to continue + } + } + } + } + } + + if ( wasDefusing ) + { + processScoreEvent( "killed_bomb_defuser", attacker, victim, weapon ); + } + else if ( wasPlanting ) + { + processScoreEvent( "killed_bomb_planter", attacker, victim, weapon ); + } + + heroWeaponKill( attacker, victim, weapon ); + + } // end if ( !isdefined( killstreak ) ) + + specificWeaponKill( attacker, victim, weapon, killstreak, inflictor ); + + if( isdefined( level.vtol ) && isdefined( level.vtol.owner ) && ( weapon.name == "helicopter_gunner_turret_secondary" || weapon.name == "helicopter_gunner_turret_tertiary" ) ) + { + attacker addplayerstat( "kill_as_support_gunner", 1 ); + + processScoreEvent( "mothership_assist_kill", level.vtol.owner, victim, weapon ); + } + + if ( isdefined( attackerInVehicleArchetype ) ) + { + if ( attackerInVehicleArchetype == "siegebot" ) + { + if ( meansOfDeath == "MOD_CRUSH" ) + { + processScoreEvent( "kill_enemy_with_siegebot_crush", attacker, victim, weapon ); + } + + if(!isdefined(attacker.siegebot_kills))attacker.siegebot_kills=0; + + attacker.siegebot_kills++; + + if ( attacker.siegebot_kills % 5 == 0 ) + { + processScoreEvent( "siegebot_killstreak_5", attacker, victim, weapon ); + } + } + } + + switch ( weapon.rootweapon.name ) + { + case "hatchet": + { + attacker.pers["tomahawks"]++; + attacker.tomahawks = attacker.pers["tomahawks"]; + + processScoreEvent( "hatchet_kill", attacker, victim, weapon ); + + if ( isdefined( data.victim.explosiveInfo["projectile_bounced"] ) && data.victim.explosiveInfo["projectile_bounced"] == true ) + { + level.globalBankshots++; + + processScoreEvent( "bounce_hatchet_kill", attacker, victim, weapon ); + } + } + break; + case "supplydrop": + case "inventory_supplydrop": + case "supplydrop_marker": + case "inventory_supplydrop_marker": + { + if ( meansOfDeath == "MOD_HIT_BY_OBJECT" || meansOfDeath == "MOD_CRUSH" ) + { + processScoreEvent( "kill_enemy_with_care_package_crush", attacker, victim, weapon ); + } + else + { + processScoreEvent( "kill_enemy_with_hacked_care_package", attacker, victim, weapon ); + } + } + break; + + } + + if( isdefined( killstreak ) ) + { + attacker thread updateMultiKills( weapon, weaponClass, killstreak, victim ); + + victim RecordKillModifier("killstreak"); + } + + attacker.cur_death_streak = 0; + attacker disabledeathstreak(); +} + +function get_equipped_hero_ability( ability_name ) +{ + if ( !isdefined( ability_name ) ) + return undefined; + + return GetWeapon( ability_name ); +} + +function heroWeaponKill( attacker, victim, weapon ) +{ + if ( !isdefined( weapon ) ) + { + return; + } + + if ( isdefined( weapon.isHeroWeapon ) && !weapon.isHeroWeapon ) + { + return; + } + + switch( weapon.name ) + { + case "hero_minigun": + case "hero_minigun_body3": + event = "minigun_kill"; + break; + case "hero_flamethrower": + event = "flamethrower_kill"; + break; + case "hero_lightninggun": + case "hero_lightninggun_arc": + event = "lightninggun_kill"; + break; + case "hero_chemicalgelgun": + case "hero_firefly_swarm": + event = "gelgun_kill"; + break; + case "hero_pineapplegun": + case "hero_pineapple_grenade": + event = "pineapple_kill"; + break; + case "hero_armblade": + event = "armblades_kill"; + break; + case "hero_bowlauncher": + case "hero_bowlauncher2": + case "hero_bowlauncher3": + case "hero_bowlauncher4": + event = "bowlauncher_kill"; + break; + case "hero_gravityspikes": + event = "gravityspikes_kill"; + break; + case "hero_annihilator": + event = "annihilator_kill"; + break; + default: + return; + } + + processScoreEvent( event, attacker, victim, weapon ); +} + +function killedHeroWeaponEnemy( attacker, victim, weapon, victim_weapon, victim_gadget_power, victimGadgetWasActiveLastDamage ) +{ + if ( !isdefined( victim_weapon ) ) + { + return; + } + + if ( victim_gadget_power >= 100 ) + return; + + switch( victim_weapon.name ) + { + case "hero_minigun": + case "hero_minigun_body3": + event = "killed_minigun_enemy"; + break; + case "hero_flamethrower": + event = "killed_flamethrower_enemy"; + break; + case "hero_lightninggun": + case "hero_lightninggun_arc": + event = "killed_lightninggun_enemy"; + break; + case "hero_chemicalgelgun": + event = "killed_gelgun_enemy"; + break; + case "hero_pineapplegun": + event = "killed_pineapple_enemy"; + break; + case "hero_bowlauncher": + case "hero_bowlauncher2": + case "hero_bowlauncher3": + case "hero_bowlauncher4": + event = "killed_bowlauncher_enemy"; + break; + case "hero_gravityspikes": + event = "killed_gravityspikes_enemy"; + break; + case "hero_annihilator": + event = "killed_annihilator_enemy"; + break; + + default: + if ( isdefined( victim.heroWeapon ) && victim.heroWeapon.name == "hero_armblade" && victimGadgetWasActiveLastDamage ) + { + event = "killed_armblades_enemy"; + } + else + { + return; + } + } + + processScoreEvent( event, attacker, victim, weapon ); + attacker util::player_contract_event( "killed_hero_weapon_enemy" ); +} + +function specificWeaponKill( attacker, victim, weapon, killstreak, inflictor ) +{ + switchWeapon = weapon.name; + + if( isdefined( killstreak ) ) + { + switchWeapon = killstreak; + } + switch( switchWeapon ) + { + case "remote_missile": + case "inventory_remote_missile": + event = "remote_missile_kill"; + break; + case "autoturret": + case "inventory_autoturret": + event = "sentry_gun_kill"; + break; + case "planemortar": + case "inventory_planemortar": + event = "plane_mortar_kill"; + break; + case "ai_tank_drop": + case "inventory_ai_tank_drop": + event = "aitank_kill"; + if ( isdefined( inflictor ) && isdefined( inflictor.controlled ) ) + { + if ( inflictor.controlled == true ) + { + attacker addPlayerStat( "kill_with_controlled_ai_tank", 1 ); + } + } + break; + case "microwave_turret": + case "inventory_microwave_turret": + case "microwaveturret": + case "inventory_microwaveturret": + event = "microwave_turret_kill"; + break; + case "raps": + case "inventory_raps": + event = "raps_kill"; + break; + case "sentinel": + case "inventory_sentinel": + { + event = "sentinel_kill"; + if ( isdefined( inflictor ) && isdefined( inflictor.controlled ) ) + { + if ( inflictor.controlled == true ) + { + attacker addPlayerStat( "kill_with_controlled_sentinel", 1 ); + } + } + } + break; + case "combat_robot": + case "inventory_combat_robot": + event = "combat_robot_kill"; + break; + case "rcbomb": + case "inventory_rcbomb": + event = "hover_rcxd_kill"; + break; + case "helicopter_gunner": + case "helicopter_gunner_assistant": + case "inventory_helicopter_gunner": + case "inventory_helicopter_gunner_assistant": + event = "vtol_mothership_kill"; + break; + case "helicopter_comlink": + case "inventory_helicopter_comlink": + event = "helicopter_comlink_kill"; + break; + case "drone_strike": + case "inventory_drone_strike": + event = "drone_strike_kill"; + break; + case "dart": + case "inventory_dart": + case "dart_turret": + event = "dart_kill"; + break; + default: + return; + } + + if ( isdefined( inflictor ) ) + { + if ( isdefined( inflictor.killstreak_id ) && isdefined( level.matchRecorderKillstreakKills[inflictor.killstreak_id] ) ) + { + level.matchRecorderKillstreakKills[inflictor.killstreak_id]++; + } + else if ( isdefined( inflictor.killcament ) && isdefined( inflictor.killcament.killstreak_id ) && isdefined( level.matchRecorderKillstreakKills[inflictor.killcament.killstreak_id] ) ) + { + level.matchRecorderKillstreakKills[inflictor.killcament.killstreak_id]++; + } + } + + processScoreEvent( event, attacker, victim, weapon ); +} + +function multiKill( killCount, weapon ) +{ + assert( killCount > 1 ); + + self challenges::multiKill( killCount, weapon ); + + if ( killCount > 8 ) + { + processScoreEvent( "multikill_more_than_8", self, undefined, weapon ); + } + else + { + processScoreEvent( "multikill_" + killCount, self, undefined, weapon ); + } + + if ( killCount > 2 ) + { + if ( isdefined( self.challenge_objectiveDefensiveKillcount ) && self.challenge_objectiveDefensiveKillcount > 0 ) + { + self.challenge_objectiveDefensiveTripleKillMedalOrBetterEarned = true; + } + } + + self RecordMultiKill( killCount ); +} + +function multiHeroAbilityKill( killCount, weapon ) +{ + if ( killcount > 1 ) + { + self addPlayerStat( "multikill_2_with_heroability", int( killCount / 2 ) ); + self AddWeaponStat( weapon, "heroability_doublekill", int( killCount / 2 ) ); + self addPlayerStat( "multikill_3_with_heroability", int( killCount / 3 ) ); + self AddWeaponStat( weapon, "heroability_triplekill", int( killCount / 3 ) ); + } +} + +function is_weapon_valid( meansOfDeath, weapon, weaponClass, killstreak ) +{ + valid_weapon = false; + + if ( isdefined( killstreak ) ) + valid_weapon = false; + else if ( get_distance_for_weapon( weapon, weaponClass ) == 0 ) + valid_weapon = false; + else if ( meansOfDeath == "MOD_PISTOL_BULLET" || meansOfDeath == "MOD_RIFLE_BULLET" ) + valid_weapon = true; + else if ( meansOfDeath == "MOD_HEAD_SHOT" ) + valid_weapon = true; + else if ( weapon.rootweapon.name == "hatchet" && meansOfDeath == "MOD_IMPACT" ) + valid_weapon = true; + else + { + baseWeapon = challenges::getBaseWeapon( weapon ); + if ( baseWeapon == level.weaponSpecialCrossbow && meansOfDeath == "MOD_IMPACT" ) + valid_weapon = true; + else if ( baseWeapon == level.weaponBallisticKnife && meansOfDeath == "MOD_IMPACT" ) + valid_weapon = true; + else if ( ( baseWeapon.forceDamageHitLocation || baseWeapon == level.weaponShotgunEnergy || baseWeapon == level.weaponSpecialDiscGun ) && meansofDeath == "MOD_PROJECTILE" ) + valid_weapon = true; + } + + return valid_weapon; +} + + +function updateSingleFragMultiKill( victim, weapon, weaponClass, killstreak ) +{ + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + self notify ( "updateSingleFragMultiKill" ); + self endon ( "updateSingleFragMultiKill" ); + + if ( !isdefined (self.recent_SingleFragMultiKill) || self.recent_SingleFragMultiKillID != victim.explosiveinfo["damageid"] ) + self.recent_SingleFragMultiKill = 0; + + self.recent_SingleFragMultiKillID = victim.explosiveinfo["damageid"]; + self.recent_SingleFragMultiKill++; + + self waitTillTimeoutOrDeath( 0.05 ); + + if ( self.recent_SingleFragMultiKill >= 2 ) + { + processScoreEvent( "frag_multikill", self, victim, weapon ); + } + + self.recent_SingleFragMultiKill = 0; +} + +function updatemultikills( weapon, weaponClass, killstreak, victim ) +{ + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + self notify ( "updateRecentKills" ); + self endon ( "updateRecentKills" ); + + baseWeaponParam = [[ level.get_base_weapon_param ]]( weapon ); + baseWeapon = GetWeapon( GetRefFromItemIndex( GetBaseWeaponItemIndex( baseWeaponParam ) ) ); + + if ( !isdefined( self.recentKillVariables ) ) + { + self resetRecentKillVariables(); + } + + if ( !isdefined( self.recentKillCountWeapon ) || self.recentKillCountWeapon != baseWeapon ) + { + self.recentKillCountSameWeapon = 0; + self.recentKillCountWeapon = baseWeapon; + } + + if (!isdefined(killstreak)) + { + self.recentKillCountSameWeapon++; + self.recentKillCount++; + } + + if ( isdefined( weaponClass ) ) + { + if ( weaponClass == "weapon_lmg" || weaponClass == "weapon_smg" ) + { + if ( self PlayerAds() < 1.0 ) + { + self.recent_LMG_SMG_KillCount++; + } + } + if ( weaponClass == "weapon_grenade" ) + { + self.recentLethalCount++; + } + } + + if ( weapon.name == "satchel_charge" ) + { + self.recentC4KillCount++; + } + + if ( isdefined( level.killstreakWeapons ) && isdefined( level.killstreakWeapons[weapon] ) ) + { + switch( level.killstreakWeapons[weapon] ) + { + case "remote_missile": + case "inventory_remote_missile": + { + self.recentRemoteMissileCount++; + } + break; + case "rcbomb": + case "inventory_rcbomb": + { + self.recentRCBombCount++; + } + break; + } + } + + + if ( isdefined( weapon.isHeroWeapon ) && weapon.isHeroWeapon == true ) + { + self.recentHeroKill = getTime(); + self.recentHeroWeaponKillCount++; + if ( isdefined( victim ) ) + { + self.recentHeroWeaponVictims[ victim GetEntityNumber() ] = victim; + } + switch( weapon.name ) + { + case "hero_annihilator": + self.recentAnihilatorCount++; + break; + case "hero_minigun": + case "hero_minigun_body3": + self.recentMiniGunCount++; + break; + case "hero_bowlauncher": + case "hero_bowlauncher2": + case "hero_bowlauncher3": + case "hero_bowlauncher4": + self.recentBowLauncherCount++; + break; + case "hero_flamethrower": + self.recentFlameThrowerCount++; + break; + case "hero_gravityspikes": + self.recentGravitySpikesCount++; + break; + case "hero_lightninggun": + case "hero_lightninggun_arc": + self.recentLightningGunCount++; + break; + case "hero_pineapplegun": + case "hero_pineapple_grenade": + self.recentPineappleGunCount++; + break; + case "hero_chemicalgun": + case "hero_firefly_swarm": + self.recentGelGunCount++; + break; + case "hero_armblade": + self.recentArmBladeCount++; + break; + } + } + + if ( isdefined( self.heroAbility ) && isdefined( victim ) ) + { + if ( victim ability_player::gadget_CheckHeroAbilityKill( self ) ) + { + if ( isdefined( self.recentHeroAbilityKillWeapon ) && self.recentHeroAbilityKillWeapon != self.heroAbility ) + { + self.recentHeroAbilityKillCount = 0; + } + self.recentHeroAbilityKillWeapon = self.heroAbility; + self.recentHeroAbilityKillCount++; + } + } + + if ( isdefined ( killstreak ) ) + { + switch( killstreak ) + { + case "remote_missile": + self.recentRemoteMissileKillCount++; + break; + case "rcbomb": + self.recentRCBombKillCount++; + break; + case "inventory_m32": + case "m32": + self.recentMGLKillCount++; + break; + } + } + + if ( self.recentKillCountSameWeapon == 2 ) + { + self AddWeaponStat( weapon, "multikill_2", 1 ); + } + else if ( self.recentKillCountSameWeapon == 3 ) + { + self AddWeaponStat( weapon, "multikill_3", 1 ); + } + + self waitTillTimeoutOrDeath( 4.0 ); + + if ( self.recent_LMG_SMG_KillCount >= 3 ) + self challenges::multi_LMG_SMG_Kill(); + + if ( self.recentRCBombKillCount >= 2 ) + self challenges::multi_RCBomb_Kill(); + + if ( self.recentMGLKillCount >= 3 ) + self challenges::multi_MGL_Kill(); + + if ( self.recentRemoteMissileKillCount >= 3 ) + self challenges::multi_RemoteMissile_Kill(); + + + if ( self.recentHeroWeaponKillCount > 1 ) + { + self scoreevents::hero_weapon_multikill_event( self.recentHeroWeaponKillCount, weapon ); + } + + if ( self.recentHeroWeaponKillCount > 5 ) + { + ArrayRemoveValue( self.recentHeroWeaponVictims, undefined ); + if ( self.recentHeroWeaponVictims.size > 5 ) + { + self addPlayerStat( "kill_entire_team_with_specialist_weapon", 1 ); + } + } + + if ( self.recentAnihilatorCount >= 3 ) + { + processScoreEvent( "annihilator_multikill", self, undefined, weapon ); + self multikillMedalAchievement(); + } + else if ( self.recentAnihilatorCount == 2 ) + { + processScoreEvent( "annihilator_multikill_2", self, undefined, weapon ); + self multikillMedalAchievement(); + } + if ( self.recentMiniGunCount >= 3 ) + { + processScoreEvent( "minigun_multikill", self, undefined, weapon ); + self multikillMedalAchievement(); + } + else if ( self.recentMiniGunCount == 2 ) + { + processScoreEvent( "minigun_multikill_2", self, undefined, weapon ); + self multikillMedalAchievement(); + } + if ( self.recentBowLauncherCount >= 3 ) + { + processScoreEvent( "bowlauncher_multikill", self, undefined, weapon ); + self multikillMedalAchievement(); + } + else if ( self.recentBowLauncherCount == 2 ) + { + processScoreEvent( "bowlauncher_multikill_2", self, undefined, weapon ); + self multikillMedalAchievement(); + } + if ( self.recentFlameThrowerCount >= 3 ) + { + processScoreEvent( "flamethrower_multikill", self, undefined, weapon ); + self multikillMedalAchievement(); + } + else if ( self.recentFlameThrowerCount == 2 ) + { + processScoreEvent( "flamethrower_multikill_2", self, undefined, weapon ); + self multikillMedalAchievement(); + } + if ( self.recentGravitySpikesCount >= 3 ) + { + processScoreEvent( "gravityspikes_multikill", self, undefined, weapon ); + self multikillMedalAchievement(); + } + else if ( self.recentGravitySpikesCount == 2 ) + { + processScoreEvent( "gravityspikes_multikill_2", self, undefined, weapon ); + self multikillMedalAchievement(); + } + if ( self.recentLightningGunCount >= 3 ) + { + processScoreEvent( "lightninggun_multikill", self, undefined, weapon ); + self multikillMedalAchievement(); + } + else if ( self.recentLightningGunCount == 2 ) + { + processScoreEvent( "lightninggun_multikill_2", self, undefined, weapon ); + self multikillMedalAchievement(); + } + if ( self.recentPineappleGunCount >= 3 ) + { + processScoreEvent( "pineapple_multikill", self, undefined, weapon ); + self multikillMedalAchievement(); + } + else if ( self.recentPineappleGunCount == 2 ) + { + processScoreEvent( "pineapple_multikill_2", self, undefined, weapon ); + self multikillMedalAchievement(); + } + if ( self.recentGelGunCount >= 3 ) + { + processScoreEvent( "gelgun_multikill", self, undefined, weapon ); + self multikillMedalAchievement(); + } + else if ( self.recentGelGunCount == 2 ) + { + processScoreEvent( "gelgun_multikill_2", self, undefined, weapon ); + self multikillMedalAchievement(); + } + if ( self.recentArmBladeCount >= 3 ) + { + processScoreEvent( "armblades_multikill", self, undefined, weapon ); + self multikillMedalAchievement(); + } + else if ( self.recentArmBladeCount == 2 ) + { + processScoreEvent( "armblades_multikill_2", self, undefined, weapon ); + self multikillMedalAchievement(); + } + + if ( self.recentC4KillCount >= 2 ) + { + processScoreEvent( "c4_multikill", self, undefined, weapon ); + } + if ( self.recentRemoteMissileCount >= 3 ) + { + self addPlayerStat( "multikill_3_remote_missile", 1 ); + } + if ( self.recentRCBombCount >= 2 ) + { + self addPlayerStat( "multikill_2_rcbomb", 1 ); + } + if ( self.recentLethalCount >= 2 ) + { + if ( !isdefined( self.pers["challenge_kills_double_kill_lethal"] ) ) + { + self.pers["challenge_kills_double_kill_lethal"] = 0; + } + + self.pers["challenge_kills_double_kill_lethal"]++; + if ( self.pers["challenge_kills_double_kill_lethal"] >= 3 ) + { + self addPlayerStat( "kills_double_kill_3_lethal", 1 ); + } + } + + if ( self.recentKillCount > 1 ) + self multiKill( self.recentKillCount, weapon ); + + if ( self.recentHeroAbilityKillCount > 1 ) + { + self multiHeroAbilityKill( self.recentHeroAbilityKillCount, self.recentHeroAbilityKillWeapon ); + self scoreevents::hero_ability_multikill_event( self.recentHeroAbilityKillCount, self.recentHeroAbilityKillWeapon ); + } + + self resetRecentKillVariables(); +} + +function resetRecentKillVariables() +{ + self.recent_LMG_SMG_KillCount = 0; + self.recentAnihilatorCount = 0; + self.recentArmBladeCount = 0; + self.recentBowLauncherCount = 0; + self.recentC4KillCount = 0; + self.recentFlameThrowerCount = 0; + self.recentGelGunCount = 0; + self.recentGravitySpikesCount = 0; + self.recentHeroAbilityKillCount = 0; + self.recentHeroWeaponKillCount = 0; + self.recentHeroWeaponVictims = []; + self.recentKillCount = 0; + self.recentKillCountSameWeapon = 0; + self.recentKillCountWeapon = undefined; + self.recentLethalCount = 0; + self.recentLightningGunCount = 0; + self.recentMGLKillCount = 0; + self.recentMiniGunCount = 0; + self.recentPineappleGunCount = 0; + self.recentRCBombCount = 0; + self.recentRCBombKillCount = 0; + self.recentRemoteMissileCount = 0; + self.recentRemoteMissileKillCount = 0; + + self.recentKillVariables = true; +} + +function waitTillTimeoutOrDeath( timeout ) +{ + self endon( "death" ); + wait( timeout ); +} + +function updateoneshotmultikills( victim, weapon, firstTimeDamaged ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "updateoneshotmultikills" + firstTimeDamaged ); + self endon( "updateoneshotmultikills" + firstTimeDamaged ); + if ( !isdefined( self.oneshotmultikills ) || firstTimeDamaged > (isdefined(self.oneshotmultikillsdamagetime)?self.oneshotmultikillsdamagetime:0) ) + { + self.oneshotmultikills = 0; + } + + self.oneshotmultikills++; + self.oneshotmultikillsdamagetime = firstTimeDamaged; + + wait( 1.0 ); + if ( self.oneshotmultikills > 1 ) + { + processScoreEvent( "kill_enemies_one_bullet", self, victim, weapon ); + } + else + { + processScoreEvent( "kill_enemy_one_bullet", self, victim, weapon ); + } + self.oneshotmultikills = 0; +} + +function get_distance_for_weapon( weapon, weaponClass ) +{ + // this is special for the long shot medal + // need to do this on a per weapon category basis, to better control it + + distance = 0; + + if( !isdefined( weaponClass ) ) + { + return 0; + } + + // special case weapons + if ( weapon.rootweapon.name == "pistol_shotgun" ) + { + weaponClass = "weapon_cqb"; // per design + } + + + switch ( weaponClass ) + { + case "weapon_smg": + distance = 1500 * 1500; + break; + + case "weapon_assault": + distance = 1750 * 1750; + break; + + case "weapon_lmg": + distance = 1750 * 1750; + break; + + case "weapon_sniper": + distance = 2000 * 2000; + break; + + case "weapon_pistol": + distance = 1000 * 1000; + break; + + case "weapon_cqb": + distance = 550 * 550; + break; + + + case "weapon_special": + { + baseWeapon = challenges::getBaseWeapon( weapon ); + if ( baseWeapon == level.weaponBallisticKnife || baseWeapon == level.weaponSpecialCrossbow || baseWeapon == level.weaponSpecialDiscGun ) + { + distance = 1500 * 1500; + } + } + break; + case "weapon_grenade": + if ( weapon.rootweapon.name == "hatchet" ) + { + distance = 1500 * 1500; + } + break; + default: + distance = 0; + break; + } + + return distance; +} + + +function onGameEnd( data ) +{ + player = data.player; + winner = data.winner; + + if ( isdefined( winner ) ) + { + if ( level.teambased ) + { + if ( winner != "tie" && player.team == winner ) + { + processScoreEvent( "won_match", player ); + return; + } + } + else + { + placement = level.placement["all"]; + topThreePlayers = min( 3, placement.size ); + + for ( index = 0; index < topThreePlayers; index++ ) + { + if ( level.placement["all"][index] == player ) + { + processScoreEvent( "won_match", player ); + return; + } + } + } + } + processScoreEvent( "completed_match", player ); +} + + +function specialistMedalAchievement() +{ + if( level.rankedMatch ) + { + if ( !isdefined( self.pers["specialistMedalAchievement"] ) ) + { + self.pers["specialistMedalAchievement"] = 0; + } + self.pers["specialistMedalAchievement"]++; + if ( self.pers["specialistMedalAchievement"] == 5 ) + { + self GiveAchievement( "MP_SPECIALIST_MEDALS" ); // Get 5 Medals based on a Specialist Ability in a single game. + } + + self util::player_contract_event( "earned_specialist_ability_medal" ); + } +} + +function specialistStatAbilityUsage( usageSingleGame, multiTrackPerLife ) +{ + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + self notify ( "updatethread specialistStatAbilityUsage" ); + self endon ( "updatethread specialistStatAbilityUsage" ); + + isRoulette = ( self.isRoulette === true ); + if ( isdefined( self.heroAbility ) && !isRoulette ) + { + self addWeaponStat( self.heroAbility, "combatRecordStat", 1 ); + } + + self challenges::processSpecialistChallenge( "kills_ability" ); + if ( !isdefined( self.pers["specialistUsagePerGame"] ) ) + { + self.pers["specialistUsagePerGame"] = 0; + } + self.pers["specialistUsagePerGame"]++; + if ( self.pers["specialistUsagePerGame"] >= usageSingleGame ) + { + self challenges::processSpecialistChallenge( "kill_one_game_ability" ); + self.pers["specialistUsagePerGame"] = 0; + } + + if ( multiTrackPerLife ) + { + self.pers["specialistStatAbilityUsage"]++; + if ( self.pers["specialistStatAbilityUsage"] >= 2 ) + { + self challenges::processSpecialistChallenge( "multikill_ability" ); + } + } + else + { + if ( !isdefined( self.specialistStatAbilityUsage ) ) + { + self.specialistStatAbilityUsage = 0; + } + + self.specialistStatAbilityUsage++; + self waitTillTimeoutOrDeath( 4.0 ); + if ( self.specialistStatAbilityUsage >= 2 ) + { + self challenges::processSpecialistChallenge( "multikill_ability" ); + } + self.specialistStatAbilityUsage = 0; + } +} + +function multikillMedalAchievement() +{ + if ( level.rankedMatch ) + { + self GiveAchievement( "MP_MULTI_KILL_MEDALS" ); // Get a Specialist-based medal that requires 3 or more rapid kills while using any Specialist Weapon. + self challenges::processSpecialistChallenge( "multikill_weapon" ); + } +} + diff --git a/mp/_scrambler.csc b/mp/_scrambler.csc new file mode 100644 index 0000000..b8edf81 --- /dev/null +++ b/mp/_scrambler.csc @@ -0,0 +1,22 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\fx_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_scrambler; + + + +#using scripts\mp\_util; + + + +#namespace scrambler; + +function autoexec __init__sytem__() { system::register("scrambler",&__init__,undefined,undefined); } + +function __init__() +{ + scrambler::init_shared(); +} \ No newline at end of file diff --git a/mp/_scrambler.gsc b/mp/_scrambler.gsc new file mode 100644 index 0000000..6ba9af8 --- /dev/null +++ b/mp/_scrambler.gsc @@ -0,0 +1,20 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_scrambler; + + + + +#using scripts\mp\_util; + +#namespace scrambler; + +function autoexec __init__sytem__() { system::register("scrambler",&__init__,undefined,undefined); } + +function __init__() +{ + scrambler::init_shared(); +} \ No newline at end of file diff --git a/mp/_sensor_grenade.gsc b/mp/_sensor_grenade.gsc new file mode 100644 index 0000000..680c6fb --- /dev/null +++ b/mp/_sensor_grenade.gsc @@ -0,0 +1,19 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_sensor_grenade; + + + + +#namespace sensor_grenade; + +function autoexec __init__sytem__() { system::register("sensor_grenade",&__init__,undefined,undefined); } + +function __init__() +{ + sensor_grenade::init_shared(); +} + diff --git a/mp/_shoutcaster.csc b/mp/_shoutcaster.csc new file mode 100644 index 0000000..e58bb95 --- /dev/null +++ b/mp/_shoutcaster.csc @@ -0,0 +1,58 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\system_shared; + +#namespace shoutcaster; + + + + + + +function is_shoutcaster(localClientNum) +{ + return IsShoutcaster(localClientNum); +} + +function is_shoutcaster_using_team_identity(localClientNum) +{ + return (is_shoutcaster(localClientNum) && GetShoutcasterSetting(localClientNum, "shoutcaster_team_identity" )); +} + +function get_team_color_id( localClientNum, team ) +{ + if ( team == "allies" ) + { + return GetShoutcasterSetting(localClientNum, "shoutcaster_fe_team1_color" ); + } + + return GetShoutcasterSetting(localClientNum, "shoutcaster_fe_team2_color" ); +} + +function get_team_color_fx( localClientNum, team, script_bundle ) +{ + color = get_team_color_id( localClientNum, team ); + return script_bundle.objects[color].fx_colorid; +} + +function get_color_fx( localClientNum, script_bundle ) +{ + effects = []; + effects["allies"] = get_team_color_fx( localClientNum, "allies", script_bundle ); + effects["axis"] = get_team_color_fx( localClientNum, "axis", script_bundle ); + return effects; +} + +function is_friendly( localClientNum ) +{ + localplayer = getlocalplayer( localClientNum ); + + scorepanel_flipped = GetShoutcasterSetting(localClientNum, "shoutcaster_flip_scorepanel" ); + + if ( !scorepanel_flipped ) + friendly = ( self.team == "allies" ); + else + friendly = ( self.team == "axis" ); + + return friendly; +} \ No newline at end of file diff --git a/mp/_smokegrenade.gsc b/mp/_smokegrenade.gsc new file mode 100644 index 0000000..7e7f081 --- /dev/null +++ b/mp/_smokegrenade.gsc @@ -0,0 +1,21 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_smokegrenade; + + + +#using scripts\mp\_util; + +#namespace smokegrenade; + +function autoexec __init__sytem__() { system::register("smokegrenade",&__init__,undefined,undefined); } + +function __init__() +{ + smokegrenade::init_shared(); +} + diff --git a/mp/_tabun.gsc b/mp/_tabun.gsc new file mode 100644 index 0000000..72612e2 --- /dev/null +++ b/mp/_tabun.gsc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; +#using scripts\shared\sound_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_tabun; + + + +#using scripts\mp\_util; + +#namespace tabun; + +function init() +{ + tabun::init_shared(); +} diff --git a/mp/_tacticalinsertion.csc b/mp/_tacticalinsertion.csc new file mode 100644 index 0000000..2f637d3 --- /dev/null +++ b/mp/_tacticalinsertion.csc @@ -0,0 +1,20 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_tacticalinsertion; + +#using scripts\mp\_util; + + + + +#namespace tacticalinsertion; + +function autoexec __init__sytem__() { system::register("tacticalinsertion",&__init__,undefined,undefined); } + +function __init__() +{ + tacticalinsertion::init_shared(); +} \ No newline at end of file diff --git a/mp/_tacticalinsertion.gsc b/mp/_tacticalinsertion.gsc new file mode 100644 index 0000000..c29c9da --- /dev/null +++ b/mp/_tacticalinsertion.gsc @@ -0,0 +1,21 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_tacticalinsertion; + +#using scripts\mp\_util; + + + + +#namespace tacticalinsertion; + +function autoexec __init__sytem__() { system::register("tacticalinsertion",&__init__,undefined,undefined); } + +function __init__() +{ + tacticalinsertion::init_shared(); +} \ No newline at end of file diff --git a/mp/_teamops.gsc b/mp/_teamops.gsc new file mode 100644 index 0000000..2b3eb98 --- /dev/null +++ b/mp/_teamops.gsc @@ -0,0 +1,355 @@ +#using scripts\codescripts\struct; +#using scripts\shared\system_shared; +#using scripts\shared\callbacks_shared; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\shared\util_shared; +#using scripts\shared\sound_shared; + + + +#using scripts\mp\_util; + + +#namespace teamops; + +function getTeamopsTableID() +{ + teamopsInfoTableLoaded = false; + teamopsInfoTableID = TableLookupFindCoreAsset( "gamedata/tables/mp/teamops.csv" ); + + if ( isdefined( teamopsInfoTableID ) ) + { + teamopsInfoTableLoaded = true; + } + assert( teamopsInfoTableLoaded, "Team Ops Event Table is not loaded: " + "gamedata/tables/mp/teamops.csv" ); + return teamopsInfoTableID; +} + +function init() +{ + game["teamops"] = SpawnStruct(); + + game["teamops"].data = []; + game["teamops"].teamProgress = []; + game["teamops"].teamopsName = undefined; + + foreach( team in level.teams ) + { + game["teamops"].teamProgress[team] = 0; + } + + level.teamopsOnProcessPlayerEvent = &teamops::processPlayerEvent; + + tableID = getTeamopsTableID(); + + assert( isdefined( tableID ) ); + if( !isdefined( tableID ) ) + { + game["teamops"].teamopsInitialed = false; + return; + } + + for( row = 1; row < 256; row++ ) + { + name = tableLookupColumnForRow( tableID, row, 0 ); + if( name != "" ) + { + game["teamops"].data[name] = SpawnStruct(); + game["teamops"].data[name].description = tableLookupColumnForRow( tableID, row, 1 ); + game["teamops"].data[name].pushevent = tableLookupColumnForRow( tableID, row, 2 ); + game["teamops"].data[name].popevent = tableLookupColumnForRow( tableID, row, 3 ); + game["teamops"].data[name].resetevent = tableLookupColumnForRow( tableID, row, 4 ); + game["teamops"].data[name].count = int( tableLookupColumnForRow( tableID, row, 5 ) ); + game["teamops"].data[name].time = int( tableLookupColumnForRow( tableID, row, 6 ) ); + game["teamops"].data[name].modes = StrTok( tableLookupColumnForRow( tableID, row, 7 ), "," ); + game["teamops"].data[name].rewards = StrTok( tableLookupColumnForRow( tableID, row, 8 ), "," ); + } + } + + game["teamops"].teamopsInitialized = true; +} + +function getID( name ) +{ + tableID = getTeamopsTableID(); + for( row = 1; row < 256; row++ ) + { + _name = tableLookupColumnForRow( tableID, row, 0 ); + if( name == _name ) + return row; + } + return 0; +} + +function teamOpsAllowed( name ) +{ + teamops = game["teamops"].data[name]; + + if( teamops.modes.size == 0 ) + return true; + + for( mi = 0; mi < teamops.modes.size; mi++ ) + { + if( teamops.modes[mi] == level.gameType ) + return true; + } + return false; +} + +function startTeamops( name ) +{ + level notify( "teamops_starting" ); + level.teamopsOnPlayerKilled = undefined; + if( !teamOpsAllowed( name ) ) + return; + + TeamOpsShowHUD( 0 ); + + preAnounceTime = GetDvarInt( "teamOpsPreanounceTime", 5 ); + + foreach( team in level.teams ) + { + globallogic_audio::leader_dialog( "teamops_preannounce", team ); + } + + wait ( preAnounceTime ); + + for( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if( isdefined( player ) ) + player playlocalsound( "uin_objective_updated" ); + } + + teamops = game["teamops"].data[name]; + + game["teamops"].teamopsName = name; + game["teamops"].teamOpsID = getID( name ); + game["teamops"].teamopsRewardIndex = RandomIntRange( 0, teamops.rewards.size ); + game["teamops"].teamopsReward = teamops.rewards[game["teamops"].teamopsRewardIndex]; + game["teamops"].teamopsStartTime = GetTime(); + + foreach( team in level.teams ) + { + game["teamops"].teamProgress[team] = 0; + } + + wait 0.1; + + TeamOpsStart( game["teamops"].teamOpsID, game["teamops"].teamopsRewardIndex, game["teamops"].teamopsStartTime, teamops.time ); + + wait 0.1; + + TeamOpsShowHUD( 1 ); + TeamOpsUpdateProgress( "axis", 0 ); + TeamOpsUpdateProgress( "allies", 0 ); + + level thread teamOpsWatcher(); +} + +function teamOpsWatcher() +{ + while( isdefined( game["teamops"].teamopsName ) ) + { + time = game["teamops"].data[game["teamops"].teamopsName].time; + if( isdefined( time ) && ( time > 0 ) ) + { + elapsed = GetTime() - game["teamops"].teamopsStartTime; + if( elapsed > time * 1000 ) + { + stopTeamops(); + foreach( team in level.teams ) + { + globallogic_audio::leader_dialog( "teamops_timeout", team ); + } + } + } + wait 0.5; + } +} + +function stopTeamops() +{ + TeamOpsShowHUD( 0 ); + game["teamops"].teamopsName = undefined; + game["teamops"].teamopsReward = undefined; + game["teamops"].teamopsStartTime = undefined; + + foreach( team in level.teams ) + { + game["teamops"].teamProgress[team] = 0; + } +} + +function processPlayerEvent( event, player ) +{ + teamopsName = game["teamops"].teamopsName; + + if( isplayer( player ) && isdefined( teamopsName ) ) + { + level processTeamEvent( event, player, player.team ); + } +} + +function processTeamEvent( event, player, team ) +{ + teamopsName = game["teamops"].teamopsName; + teamops = game["teamops"].data[teamopsName]; + + if( isdefined( teamops.pushevent ) && ( event == teamops.pushevent ) ) + { + game["teamops"].teamProgress[team] += 1; + level updateTeamOps( event, player, team ); + } + + if( isdefined( teamops.popevent ) && ( event == teamops.popevent ) ) + { + game["teamops"].teamProgress[team] -= 1; + if( game["teamops"].teamProgress[team] < 0 ) + game["teamops"].teamProgress[team]= 0; + level updateTeamOps( event, player, team ); + } + + if( isdefined( teamops.resetevent ) && ( event == teamops.resetevent ) ) + { + game["teamops"].teamProgress[team] = 0; + level updateTeamOps( event, player, team ); + } + +} + +function updateTeamOps( event, player, team ) +{ + teamopsName = game["teamops"].teamopsName; + teamops = game["teamops"].data[teamopsName]; + + count_target = teamops.count; + progress = int( ( 100 * game["teamops"].teamProgress[team] ) / count_target ); + + TeamOpsUpdateProgress( team, progress ); + + if( game["teamops"].teamProgress[team] >= teamops.count ) + { + if( isdefined( player ) ) + { + level thread teamOpsAcheived( player, team ); + } + } +} + +function teamOpsAcheived( player, team ) +{ + game["teamops"].teamopsName = undefined; + wait 0.5; + TeamOpsShowHUD( 0 ); + + wait 2; + globallogic_audio::leader_dialog( "teamops_win", team ); + globallogic_audio::leader_dialog_for_other_teams( "teamops_lose", team ); + + player killstreaks::give( game["teamops"].teamopsReward, 1 ); + wait 2; + player killstreaks::useKillstreak( game["teamops"].teamopsReward, 1 ); +} + +function main() +{ + thread watchTeamOpsTime(); + level.teamopsTargetKills = GetDvarInt( "teamOpsKillsCountTrigger_" + level.gameType, 37 ); + if ( level.teamopsTargetKills > 0 ) + { + level.teamopsOnPlayerKilled = &teamops::onPlayerKilled; + } +} + +function GetCompatibleOperation() +{ + operations = StrTok( GetDvarString( "teamOpsName" ), "," ); + + // try to pickup a random one + for( i = 0; i < 20; i++ ) + { + operation = operations[RandomIntRange( 0, operations.size )]; + if( teamOpsAllowed( operation ) ) + return operation; + } + + // check if we have any compatible operation + for( i = 0; i < operations.size; i++ ) + { + operation = operations[i]; + if( teamOpsAllowed( operation ) ) + return operation; + } + + return undefined; +} + +function watchTeamOpsTime() +{ + level endon( "teamops_starting" ); + + if ( ( isdefined( level.inPrematchPeriod ) && level.inPrematchPeriod ) ) + level waittill("prematch_over"); + + activeTeamOps = GetCompatibleOperation(); + if( !isdefined( activeTeamOps ) ) + return; + + startDelay = GetDvarInt( "teamOpsStartDelay_" + level.gameType, 300 ); + + while( 1 ) + { + if( isdefined( game["teamops"].teamopsName ) ) + { + if ( GetDvarInt( "scr_stop_teamops" ) == 1 ) + { + teamops::stopTeamops(); + SetDvar( "scr_stop_teamops", 0 ); + } + } + + timePassed = globallogic_utils::getTimePassed() / 1000; + + startTeamOps = 0; + if( timePassed > startDelay ) + { + level thread startTeamops( activeTeamOps ); + break; + } + + wait 1; + } +} + + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + level endon( "teamops_starting" ); + + if ( isPlayer( attacker ) == false || attacker.team == self.team ) + return; + + if ( !isdefined( level.teamopsKillTracker ) ) + { + level.teamopsKillTracker = []; + } + + if ( !isdefined( level.teamopsKillTracker[attacker.team] ) ) + { + level.teamopsKillTracker[attacker.team] = 0; + } + + level.teamopsKillTracker[attacker.team]++; + + if ( level.teamopsKillTracker[attacker.team] >= level.teamopsTargetKills ) + { + activeTeamOps = GetCompatibleOperation(); + if( !isdefined( activeTeamOps ) ) + return; + + level thread startTeamops( activeTeamOps ); + } +} \ No newline at end of file diff --git a/mp/_teamops.gsh b/mp/_teamops.gsh new file mode 100644 index 0000000..384ee9a --- /dev/null +++ b/mp/_teamops.gsh @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/mp/_teamset.csc b/mp/_teamset.csc new file mode 100644 index 0000000..8bf8e22 --- /dev/null +++ b/mp/_teamset.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\system_shared; + + + +#namespace teamset; + +function autoexec __init__sytem__() { system::register("teamset_seals",&__init__,undefined,undefined); } + +function __init__() +{ + level.allies_team = "allies"; + level.axis_team = "axis"; +} \ No newline at end of file diff --git a/mp/_teargrenades.gsc b/mp/_teargrenades.gsc new file mode 100644 index 0000000..6917fb0 --- /dev/null +++ b/mp/_teargrenades.gsc @@ -0,0 +1,192 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_perplayer; + +#using scripts\mp\_util; + +function main() +{ + level.tearradius = 170; + level.tearheight = 128; + level.teargasfillduration = 7; // time until gas fills the area specified + level.teargasduration = 23; // duration gas remains in area + level.tearsufferingduration = 3; // (duration after leaving area of effect) + + level.teargrenadetimer = 4; // should match time to appearance of effect + + fgmonitor = perplayer::init("tear_grenade_monitor",&startMonitoringTearUsage,&stopMonitoringTearUsage); + perplayer::enable(fgmonitor); +} + +function startMonitoringTearUsage() +{ + self thread monitorTearUsage(); +} +function stopMonitoringTearUsage(disconnected) +{ + self notify("stop_monitoring_tear_usage"); +} + +function monitorTearUsage() +{ + self endon("stop_monitoring_tear_usage"); + + wait .05; + + weapon = GetWeapon( "tear_grenade" ); + if (!self hasWeapon( weapon )) + return; + + // when this player's tear grenade ammo goes down, assume the nearest "grenade" entity is a tear grenade. + + prevammo = self getammocount( weapon ); + while(1) + { + ammo = self getammocount( weapon ); + if (ammo < prevammo) + { + num = prevammo - ammo; +/# +// iprintln(num); +#/ + for (i = 0; i < num; i++) + { + grenades = getEntArray("grenade", "classname"); + bestdist = undefined; + bestg = undefined; + for (g = 0; g < grenades.size; g++) + { + if (!isdefined(grenades[g].teargrenade)) + { + dist = distance(grenades[g].origin, self.origin + (0,0,48)); + if (!isdefined(bestdist) || dist < bestdist) + { + bestdist = dist; + bestg = g; + } + } + } + if (isdefined(bestdist)) + { + grenades[bestg].teargrenade = true; + grenades[bestg] thread tearGrenade_think(self.team); + } + } + } + prevammo = ammo; + wait .05; + } +} + +function tearGrenade_think(team) +{ + // wait for death + + // (grenade doesn't actually die until finished smoking) + wait level.teargrenadetimer; + + ent = spawnstruct(); + ent thread tear(self.origin); +} + +function tear(pos) +{ + trig = spawn("trigger_radius", pos, 0, level.tearradius, level.tearheight); + + //thread drawcylinder(pos, level.tearradius, level.tearheight); + + starttime = gettime(); + + self thread teartimer(); + self endon("tear_timeout"); + + while(1) + { + trig waittill("trigger", player); + + if (player.sessionstate != "playing") + continue; + + time = (gettime() - starttime) / 1000; + + currad = level.tearradius; + curheight = level.tearheight; + if (time < level.teargasfillduration) { + currad = currad * (time / level.teargasfillduration); + curheight = curheight * (time / level.teargasfillduration); + } + + offset = (player.origin + (0,0,32)) - pos; + offset2d = (offset[0], offset[1], 0); + if (lengthsquared(offset2d) > currad*currad) + continue; + if (player.origin[2] - pos[2] > curheight) + continue; + + player.teargasstarttime = gettime(); // purposely overriding old value + if (!isdefined(player.teargassuffering)) + player thread teargassuffering(); + } +} +function teartimer() +{ + wait level.teargasduration; + self notify("tear_timeout"); +} + +function teargassuffering() +{ + self endon("death"); + self endon("disconnect"); + + self.teargassuffering = true; + + if ( self util::mayApplyScreenEffect() ) + self shellshock("teargas", 60); + + while(1) + { + if (gettime() - self.teargasstarttime > level.tearsufferingduration * 1000) + break; + + wait 1; + } + + self shellshock("teargas", 1); + + if ( self util::mayApplyScreenEffect() ) + self.teargassuffering = undefined; +} + +function drawcylinder(pos, rad, height) +{ +/# + time = 0; + while(1) + { + currad = rad; + curheight = height; + if (time < level.teargasfillduration) { + currad = currad * (time / level.teargasfillduration); + curheight = curheight * (time / level.teargasfillduration); + } + + for (r = 0; r < 20; r++) + { + theta = r / 20 * 360; + theta2 = (r + 1) / 20 * 360; + line(pos + (cos(theta) * currad, sin(theta) * currad, 0), pos + (cos(theta2) * currad, sin(theta2) * currad, 0)); + line(pos + (cos(theta) * currad, sin(theta) * currad, curheight), pos + (cos(theta2) * currad, sin(theta2) * currad, curheight)); + line(pos + (cos(theta) * currad, sin(theta) * currad, 0), pos + (cos(theta) * currad, sin(theta) * currad, curheight)); + } + time += .05; + if (time > level.teargasduration) + break; + wait .05; + } +#/ +} \ No newline at end of file diff --git a/mp/_threat_detector.csc b/mp/_threat_detector.csc new file mode 100644 index 0000000..3be2ae0 --- /dev/null +++ b/mp/_threat_detector.csc @@ -0,0 +1,116 @@ +#using scripts\codescripts\struct; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_decoy; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\duplicaterender_mgr; + + + + +#namespace threat_detector; + +function autoexec __init__sytem__() { system::register("threat_detector",&__init__,undefined,undefined); } + +function __init__() +{ + level.sensorHandle = 1; + level.sensors = []; + + clientfield::register( "missile", "threat_detector", 1, 1, "int", &spawnedThreatDetector,!true, !true ); +} + +function spawnedThreatDetector( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + if( newVal != 1 ) + { + return; + } + + if( GetLocalPlayer( localClientNum ) != self.owner ) + { + return; + } + + sensorIndex = level.sensors.size; + level.sensorHandle++; + + level.sensors[ sensorIndex ] = spawnstruct(); + level.sensors[ sensorIndex ].handle = level.sensorHandle; + level.sensors[ sensorIndex ].cent = self; + level.sensors[ sensorIndex ].team = self.team; + level.sensors[ sensorIndex ].owner = self GetOwner( localClientNum ); + + level.sensors[ sensorIndex ].owner AddSensorGrenadeArea( self.origin, level.sensorHandle ); + + self.owner thread sensorGrenadeThink( self, level.sensorHandle, localClientNum ); + self.owner thread clearThreatDetectorOnDelete( self, level.sensorHandle, localClientNum ); +} + +function sensorGrenadeThink( sensorEnt, sensorHandle, localClientNum ) +{ + sensorEnt endon( "entityshutdown" ); + + if( isdefined( sensorEnt.owner ) == false ) + { + return; + } + + while( true ) + { + players = GetPlayers( localClientNum ); + foreach( player in players ) + { + if( self util::IsEnemyPlayer( player ) ) + { + if( player hasPerk( localClientNum, "specialty_nomotionsensor" ) || player hasPerk( localClientNum, "specialty_sengrenjammer" ) ) + { + player duplicate_render::set_player_threat_detected( localClientNum, false ); + continue; + } + + threatDetectorRadius = GetDvarFloat( "cg_threatDetectorRadius", 0 ); + threatDetectorRadiusSqrd = threatDetectorRadius * threatDetectorRadius; + + if( DistanceSquared( player.origin, sensorEnt.origin ) < threatDetectorRadiusSqrd ) + { + player duplicate_render::set_player_threat_detected( localClientNum, true ); + } + else + { + player duplicate_render::set_player_threat_detected( localClientNum, false ); + } + } + } + + wait( 1 ); + } +} + +function clearThreatDetectorOnDelete( sensorEnt, sensorHandle, localClientNum ) +{ + sensorEnt waittill( "entityshutdown" ); + + entIndex = 0; + for( i = 0; i < level.sensors.size; i++ ) + { + size = level.sensors.size; + if( sensorHandle == level.sensors[ i ].handle ) + { + level.sensors[ i ].owner RemoveSensorGrenadeArea( sensorHandle ); + entIndex = 0; + break; + } + } + + players = GetPlayers( localClientNum ); + foreach( player in players ) + { + if( self util::IsEnemyPlayer( player ) ) + { + player duplicate_render::set_player_threat_detected( localClientNum, false ); + } + } +} diff --git a/mp/_threat_detector.gsc b/mp/_threat_detector.gsc new file mode 100644 index 0000000..4f93907 --- /dev/null +++ b/mp/_threat_detector.gsc @@ -0,0 +1,187 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_hacker_tool; +#using scripts\shared\weapons\_weaponobjects; + + + + + + +#namespace threat_detector; + + +function autoexec __init__sytem__() { system::register("threat_detector",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "missile", "threat_detector", 1, 1, "int" ); + callback::add_weapon_watcher( &createThreatDetectorWatcher ); +} + +function createThreatDetectorWatcher() +{ + watcher = self weaponobjects::createUseWeaponObjectWatcher( "threat_detector", self.team ); + watcher.headicon = false; + watcher.onSpawn =&onSpawnThreatDetector; + watcher.onDetonateCallback =&threatDetectorDestroyed; + watcher.stun = &weaponobjects::weaponStun; + watcher.stunTime = 0; + watcher.onDamage =&watchThreatDetectorDamage; + + watcher.enemyDestroy = true; +} + +function onSpawnThreatDetector( watcher, player ) +{ + self endon( "death" ); + + self thread weaponobjects::onSpawnUseWeaponObject( watcher, player ); + + self SetOwner( player ); + self SetTeam( player.team ); + self.owner = player; + + self PlayLoopSound( "wpn_sensor_nade_lp" ); + self hacker_tool::registerWithHackerTool( level.equipmentHackerToolRadius, level.equipmentHackerToolTimeMs ); + + player AddWeaponStat( self.weapon, "used", 1 ); + + self thread watchForStationary( player ); +} + +function watchForStationary( owner ) +{ + self endon( "death" ); + self endon( "hacked" ); + self endon( "explode" ); + owner endon( "death" ); + owner endon( "disconnect" ); + + self waittill("stationary" ); + self clientfield::set( "threat_detector", 1 ); +} + +function trackSensorGrenadeVictim( victim ) +{ + if( !isdefined( self.sensorGrenadeData ) ) + { + self.sensorGrenadeData = []; + } + + if ( !isdefined( self.sensorGrenadeData[victim.clientid] ) ) + { + self.sensorGrenadeData[victim.clientid] = getTime(); + } + + self clientfield::set_to_player( "threat_detected", 1 ); +} + +//****************************************************************** +// * +// * +//****************************************************************** +function threatDetectorDestroyed( attacker, weapon, target ) +{ + if ( !isdefined( weapon ) || !weapon.isEmp ) + { + PlayFX( level._equipment_explode_fx, self.origin ); + } + + if ( isdefined( attacker ) ) + { + if ( self.owner util::IsEnemyPlayer( attacker ) ) + { + attacker challenges::destroyedEquipment( weapon ); + scoreevents::processScoreEvent( "destroyed_motion_sensor", attacker, self.owner, weapon ); + } + } + + PlaySoundAtPosition ( "wpn_sensor_nade_explo", self.origin ); + self delete(); +} + +//****************************************************************** +// * +// * +//****************************************************************** +function watchThreatDetectorDamage( watcher ) // self == sensor grenade +{ + self endon( "death" ); + self endon( "hacked" ); + + self SetCanDamage( true ); + damageMax = 1; + + if ( !self util::isHacked() ) + { + self.damageTaken = 0; + } + + while( true ) + { + self.maxhealth = 100000; + self.health = self.maxhealth; + + self waittill( "damage", damage, attacker, direction, point, type, tagName, modelName, partname, weapon, iDFlags ); + + if( !isdefined( attacker ) || !isplayer( attacker ) ) + continue; + + if ( level.teambased && IsPlayer( attacker ) ) + { + // if we're not hardcore and the team is the same, do not destroy + if( !level.hardcoreMode && self.owner.team == attacker.pers["team"] && self.owner != attacker ) + { + continue; + } + } + + // most equipment should be flash/concussion-able, so it'll disable for a short period of time + // check to see if the equipment has been flashed/concussed and disable it (checking damage < 5 is a bad idea, so check the weapon name) + // we're currently allowing the owner/teammate to flash their own + // do damage feedback + if ( watcher.stunTime > 0 && weapon.doStun ) + { + self thread weaponobjects::stunStart( watcher, watcher.stunTime ); + } + + if ( weapon.doDamageFeedback ) + { + // if we're not on the same team then show damage feedback + if ( level.teambased && self.owner.team != attacker.team ) + { + if ( damagefeedback::doDamageFeedback( weapon, attacker ) ) + attacker damagefeedback::update(); + } + // for ffa just make sure the owner isn't the same + else if ( !level.teambased && self.owner != attacker ) + { + if ( damagefeedback::doDamageFeedback( weapon, attacker ) ) + attacker damagefeedback::update(); + } + } + + if ( type == "MOD_MELEE" || weapon.isEmp ) + { + self.damageTaken = damageMax; + } + else + { + self.damageTaken += damage; + } + + if( self.damageTaken >= damageMax ) + { + watcher thread weaponobjects::waitAndDetonate( self, 0.0, attacker, weapon ); + return; + } + } +} diff --git a/mp/_trophy_system.csc b/mp/_trophy_system.csc new file mode 100644 index 0000000..f729601 --- /dev/null +++ b/mp/_trophy_system.csc @@ -0,0 +1,21 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_trophy_system; + + + + +#using scripts\mp\_util; + +#namespace trophy_system; + +function autoexec __init__sytem__() { system::register("trophy_system",&__init__,undefined,undefined); } + +function __init__( localClientNum ) +{ + trophy_system::init_shared(); +} \ No newline at end of file diff --git a/mp/_trophy_system.gsc b/mp/_trophy_system.gsc new file mode 100644 index 0000000..cb9d228 --- /dev/null +++ b/mp/_trophy_system.gsc @@ -0,0 +1,20 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_trophy_system; + + + + +#using scripts\mp\_util; + +#namespace trophy_system; + +function autoexec __init__sytem__() { system::register("trophy_system",&__init__,undefined,undefined); } + +function __init__() +{ + trophy_system::init_shared(); +} \ No newline at end of file diff --git a/mp/_util.csc b/mp/_util.csc new file mode 100644 index 0000000..c4eb00a --- /dev/null +++ b/mp/_util.csc @@ -0,0 +1,10 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\util_shared; + + + +#namespace util; + + + diff --git a/mp/_util.gsc b/mp/_util.gsc new file mode 100644 index 0000000..236cf7f --- /dev/null +++ b/mp/_util.gsc @@ -0,0 +1,1270 @@ +#using scripts\shared\array_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\exploder_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\lui_shared; +#using scripts\shared\math_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\util_shared; + + + + +#namespace util; + + + + + + +#precache( "lui_menu", "MPHintText" ); +#precache( "lui_menu_data", "hint_text_line" ); + +#precache( "string", "blinking"); +#precache( "string", "display_noblink"); +#precache( "string", "fadeout"); + +/# +function error(msg) +{ + println("^c*ERROR* ", msg); + wait .05; // waitframe + + if (GetDvarString( "debug") != "1") + assertmsg("This is a forced error - attach the log file"); +} +#/ + +function warning( msg ) +{ +/# println( "^1WARNING: " + msg ); #/ +} + +/@ +"Name: within_fov( , , , )" +"Summary: Returns true if < end_origin > is within the players field of view, otherwise returns false." +"Module: Vector" +"CallOn: " +"MandatoryArg: : starting origin for FOV check( usually the players origin )" +"MandatoryArg: : angles to specify facing direction( usually the players angles )" +"MandatoryArg: : origin to check if it's in the FOV" +"MandatoryArg: : cosine of the FOV angle to use" +"Example: qBool = within_fov( level.player.origin, level.player.angles, target1.origin, cos( 45 ) );" +"SPMP: multiplayer" +@/ +function within_fov( start_origin, start_angles, end_origin, fov ) +{ + normal = VectorNormalize( end_origin - start_origin ); + forward = AnglesToForward( start_angles ); + dot = VectorDot( forward, normal ); + + return dot >= fov; +} + +function get_player_height() +{ + return 70.0; // inches, see bg_pmove.cpp::playerMins/playerMaxs +} + + +function IsBulletImpactMOD( sMeansOfDeath ) +{ + return IsSubStr( sMeansOfDeath, "BULLET" ) || sMeansOfDeath == "MOD_HEAD_SHOT"; +} + +function waitRespawnButton() +{ + self endon("disconnect"); + self endon("end_respawn"); + + while(self useButtonPressed() != true) + wait .05; +} + + +function setLowerMessage( text, time, combineMessageAndTimer ) +{ + if ( !isdefined( self.lowerMessage ) ) + return; + + if ( isdefined( self.lowerMessageOverride ) && text != &"" ) + { + text = self.lowerMessageOverride; + time = undefined; + } + + self notify("lower_message_set"); + + self.lowerMessage setText( text ); + + if ( isdefined( time ) && time > 0 ) + { + if ( !isdefined( combineMessageAndTimer ) || !combineMessageAndTimer ) + self.lowerTimer.label = &""; + else + { + self.lowerMessage setText( "" ); + self.lowerTimer.label = text; + } + self.lowerTimer setTimer( time ); + } + else + { + self.lowerTimer setText( "" ); + self.lowerTimer.label = &""; + } + if( self IsSplitscreen() ) + self.lowerMessage.fontscale = 1.4; + + self.lowerMessage fadeOverTime( 0.05 ); + self.lowerMessage.alpha = 1; + self.lowerTimer fadeOverTime( 0.05 ); + self.lowerTimer.alpha = 1; +} + +function setLowerMessageValue( text, value, combineMessage ) +{ + if ( !isdefined( self.lowerMessage ) ) + return; + + if ( isdefined( self.lowerMessageOverride ) && text != &"" ) + { + text = self.lowerMessageOverride; + time = undefined; + } + + self notify("lower_message_set"); + if ( !isdefined( combineMessage ) || !combineMessage ) + self.lowerMessage setText( text ); + else + self.lowerMessage setText( "" ); + + if ( isdefined( value ) && value > 0 ) + { + if ( !isdefined( combineMessage ) || !combineMessage ) + self.lowerTimer.label = &""; + else + self.lowerTimer.label = text; + self.lowerTimer setValue( value ); + } + else + { + self.lowerTimer setText( "" ); + self.lowerTimer.label = &""; + } + + if( self IsSplitscreen() ) + self.lowerMessage.fontscale = 1.4; + + self.lowerMessage fadeOverTime( 0.05 ); + self.lowerMessage.alpha = 1; + self.lowerTimer fadeOverTime( 0.05 ); + self.lowerTimer.alpha = 1; +} + +function clearLowerMessage( fadetime ) +{ + if ( !isdefined( self.lowerMessage ) ) + return; + + self notify("lower_message_set"); + + if ( !isdefined( fadetime) || fadetime == 0 ) + { + setLowerMessage( &"" ); + } + else + { + self endon("disconnect"); + self endon("lower_message_set"); + + self.lowerMessage fadeOverTime( fadetime ); + self.lowerMessage.alpha = 0; + self.lowerTimer fadeOverTime( fadetime ); + self.lowerTimer.alpha = 0; + + wait fadetime; + + self setLowerMessage(""); + } +} + +function printOnTeam(text, team) +{ + assert( isdefined( level.players ) ); + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if ( ( isdefined(player.pers["team"]) ) && (player.pers["team"] == team) ) + player iprintln(text); + } +} + + +function printBoldOnTeam(text, team) +{ + assert( isdefined( level.players ) ); + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if ( ( isdefined(player.pers["team"]) ) && (player.pers["team"] == team) ) + player iprintlnbold(text); + } +} + + + +function printBoldOnTeamArg(text, team, arg) +{ + assert( isdefined( level.players ) ); + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if ( ( isdefined(player.pers["team"]) ) && (player.pers["team"] == team) ) + player iprintlnbold(text, arg); + } +} + + +function printOnTeamArg(text, team, arg) +{ + //assert( isdefined( level.players ) ); + //for ( i = 0; i < level.players.size; i++ ) + //{ + // player = level.players[i]; + // if ( ( isdefined(player.pers["team"]) ) && (player.pers["team"] == team) ) + // { + // player iprintln(text, arg); + // } + //} +} + + +function printOnPlayers( text, team ) +{ + players = level.players; + for(i = 0; i < players.size; i++) + { + if ( isdefined( team ) ) + { + if((isdefined(players[i].pers["team"])) && (players[i].pers["team"] == team)) + players[i] iprintln(text); + } + else + { + players[i] iprintln(text); + } + } +} + +function printAndSoundOnEveryone( team, enemyteam, printFriendly, printEnemy, soundFriendly, soundEnemy, printarg ) +{ + shouldDoSounds = isdefined( soundFriendly ); + + shouldDoEnemySounds = false; + if ( isdefined( soundEnemy ) ) + { + assert( shouldDoSounds ); // can't have an enemy sound without a friendly sound + shouldDoEnemySounds = true; + } + + if ( !isdefined( printarg ) ) + { + printarg = ""; + } + + if ( level.splitscreen || !shouldDoSounds ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + playerteam = player.pers["team"]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == team && isdefined( printFriendly ) && printFriendly != &"" ) + player iprintln( printFriendly, printarg ); + else if ( isdefined( printEnemy ) && printEnemy != &"" ) + { + if ( isdefined(enemyteam) && playerteam == enemyteam ) + player iprintln( printEnemy, printarg ); + else if ( !isdefined(enemyteam) && playerteam != team ) + player iprintln( printEnemy, printarg ); + } + } + } + if ( shouldDoSounds ) + { + assert( level.splitscreen ); + level.players[0] playLocalSound( soundFriendly ); + } + } + else + { + assert( shouldDoSounds ); + if ( shouldDoEnemySounds ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + playerteam = player.pers["team"]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == team ) + { + if( isdefined( printFriendly ) && printFriendly != &"" ) + player iprintln( printFriendly, printarg ); + player playLocalSound( soundFriendly ); + } + else if ( (isdefined(enemyteam) && playerteam == enemyteam) || ( !isdefined( enemyteam ) && playerteam != team ) ) + { + if( isdefined( printEnemy ) && printEnemy != &"" ) + player iprintln( printEnemy, printarg ); + player playLocalSound( soundEnemy ); + } + } + } + } + else + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + playerteam = player.pers["team"]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == team ) + { + if( isdefined( printFriendly ) && printFriendly != &"" ) + player iprintln( printFriendly, printarg ); + player playLocalSound( soundFriendly ); + } + else if ( isdefined( printEnemy ) && printEnemy != &"" ) + { + if ( isdefined(enemyteam) && playerteam == enemyteam ) + { + player iprintln( printEnemy, printarg ); + } + else if ( !isdefined(enemyteam) && playerteam != team ) + { + player iprintln( printEnemy, printarg ); + } + } + } + } + } + } +} + + +function _playLocalSound( soundAlias ) +{ + if ( level.splitscreen && !self IsHost() ) + return; + + self playLocalSound( soundAlias ); +} + +// this function is depricated +function getOtherTeam( team ) +{ + // TODO MTEAM - Need to fix this. + if ( team == "allies" ) + return "axis"; + else if ( team == "axis" ) + return "allies"; + else // all other teams + return "allies"; + + assertMsg( "getOtherTeam: invalid team " + team ); +} + +function getTeamMask( team ) +{ + // this can be undefined on connect + if ( !level.teambased || !isdefined(team) || !isdefined(level.spawnsystem.iSPAWN_TEAMMASK[team]) ) + return level.spawnsystem.iSPAWN_TEAMMASK_FREE; + + return level.spawnsystem.iSPAWN_TEAMMASK[team]; +} + +function getOtherTeamsMask( skip_team ) +{ + mask = 0; + foreach( team in level.teams ) + { + if ( team == skip_team ) + continue; + + mask = mask | getTeamMask( team ); + } + + return mask; +} + +function wait_endon( waitTime, endOnString, endonString2, endonString3, endonString4 ) +{ + self endon ( endOnString ); + if ( isdefined( endonString2 ) ) + self endon ( endonString2 ); + if ( isdefined( endonString3 ) ) + self endon ( endonString3 ); + if ( isdefined( endonString4 ) ) + self endon ( endonString4 ); + + wait ( waitTime ); + return true; +} + +function plot_points( plotpoints, r, g, b, timer ) +{ + /# + lastpoint = plotpoints[ 0 ]; + if( !isdefined( r ) ) + r = 1; + if( !isdefined( g ) ) + g = 1; + if( !isdefined( b ) ) + b = 1; + if( !isdefined( timer ) ) + timer = 0.05; + for( i = 1;i < plotpoints.size;i ++ ) + { + // AE 10-26-09: line function must have changed to Line( , , , , ) + line( lastpoint, plotpoints[ i ], ( r, g, b ), 1, timer ); + //line( lastpoint, plotpoints[ i ], ( r, g, b ), 1, 1, int(timer) ); + lastpoint = plotpoints[ i ]; + } + #/ +} + +function getfx( fx ) +{ + assert( isdefined( level._effect[ fx ] ), "Fx " + fx + " is not defined in level._effect." ); + return level._effect[ fx ]; +} + +function set_dvar_if_unset( + dvar, + value, + reset) +{ + if (!isdefined(reset)) + reset = false; + + if (reset || GetDvarString(dvar)=="") + { + SetDvar(dvar, value); + return value; + } + + return GetDvarString(dvar); +} + +function set_dvar_float_if_unset( + dvar, + value, + reset) +{ + if (!isdefined(reset)) + reset = false; + + if (reset || GetDvarString(dvar)=="") + { + SetDvar(dvar, value); + } + + return GetDvarFloat(dvar); +} + +function set_dvar_int_if_unset( + dvar, + value, + reset) +{ + if (!isdefined(reset)) + reset = false; + + if (reset || GetDvarString(dvar)=="") + { + SetDvar(dvar, value); + return int(value); + } + + return GetDvarInt(dvar); +} + +function add_trigger_to_ent(ent) // Self == The trigger volume +{ + if(!isdefined(ent._triggers)) + { + ent._triggers = []; + } + + ent._triggers[self GetEntityNumber()] = 1; +} + +function remove_trigger_from_ent(ent) // Self == The trigger volume. +{ + if(!isdefined(ent)) + return; + + if(!isdefined(ent._triggers)) + return; + + if(!isdefined(ent._triggers[self GetEntityNumber()])) + return; + + ent._triggers[self GetEntityNumber()] = 0; +} + +function ent_already_in_trigger(trig) // Self == The entity in the trigger volume. +{ + if(!isdefined(self._triggers)) + return false; + + if(!isdefined(self._triggers[trig GetEntityNumber()])) + return false; + + if(!self._triggers[trig GetEntityNumber()]) + return false; + + return true; // We're already in this trigger volume. +} + +function trigger_thread_death_monitor(ent, ender) +{ + ent waittill("death"); + self endon(ender); + self remove_trigger_from_ent(ent); +} + +function trigger_thread(ent, on_enter_payload, on_exit_payload) // Self == The trigger. +{ + ent endon("entityshutdown"); + ent endon("death"); + + if(ent ent_already_in_trigger(self)) + return; + + self add_trigger_to_ent(ent); + + ender = "end_trig_death_monitor" + self GetEntityNumber() + " " + ent GetEntityNumber(); + self thread trigger_thread_death_monitor(ent, ender); // If ent dies in trigger, clear trigger off of ent. + +// iprintlnbold("Trigger " + self.targetname + " hit by ent " + ent getentitynumber()); + + endon_condition = "leave_trigger_" + self GetEntityNumber(); + + if(isdefined(on_enter_payload)) + { + self thread [[on_enter_payload]](ent, endon_condition); + } + + while(isdefined(ent) && ent IsTouching(self)) + { + wait(0.01); + } + + ent notify(endon_condition); + +// iprintlnbold(ent getentitynumber() + " leaves trigger " + self.targetname + "."); + + if(isdefined(ent) && isdefined(on_exit_payload)) + { + self thread [[on_exit_payload]](ent); + } + + if(isdefined(ent)) + { + self remove_trigger_from_ent(ent); + } + + self notify(ender); // Get rid of the death monitor thread. +} + +function isStrStart( string1, subStr ) +{ + return ( getSubStr( string1, 0, subStr.size ) == subStr ); +} + +function isKillStreaksEnabled() +{ + return isdefined( level.killstreaksenabled ) && level.killstreaksenabled; +} + +function setUsingRemote( remoteName, set_killstreak_delay_killcam = true ) +{ + if ( isdefined( self.carryIcon) ) + self.carryIcon.alpha = 0; + + assert( !self isUsingRemote() ); + self.usingRemote = remoteName; + + if ( set_killstreak_delay_killcam ) + self.killstreak_delay_killcam = remoteName; // this is the only place we set directly, do not use killstreaks::set_killstreak_delay_killcam() here + + self disableOffhandWeapons(); + self clientfield::set_player_uimodel( "hudItems.remoteKillstreakActivated", 1 ); + self notify( "using_remote" ); +} + +function setObjectiveText( team, text ) +{ + game["strings"]["objective_"+team] = text; +} + +function setObjectiveScoreText( team, text ) +{ + game["strings"]["objective_score_"+team] = text; +} + +function setObjectiveHintText( team, text ) +{ + game["strings"]["objective_hint_"+team] = text; +} + +function getObjectiveText( team ) +{ + return game["strings"]["objective_"+team]; +} + +function getObjectiveScoreText( team ) +{ + return game["strings"]["objective_score_"+team]; +} + +function getObjectiveHintText( team ) +{ + return game["strings"]["objective_hint_"+team]; +} + +function registerRoundSwitch( minValue, maxValue ) +{ + level.roundSwitch = math::clamp( GetGametypeSetting( "roundSwitch" ), minValue, maxValue ); + level.roundSwitchMin = minValue; + level.roundSwitchMax = maxValue; +} + +function registerRoundLimit( minValue, maxValue ) +{ + level.roundLimit = math::clamp( GetGametypeSetting( "roundLimit" ), minValue, maxValue ); + level.roundLimitMin = minValue; + level.roundLimitMax = maxValue; +} + + +function registerRoundWinLimit( minValue, maxValue ) +{ + level.roundWinLimit = math::clamp( GetGametypeSetting( "roundWinLimit" ), minValue, maxValue ); + level.roundWinLimitMin = minValue; + level.roundWinLimitMax = maxValue; +} + + +function registerScoreLimit( minValue, maxValue ) +{ + level.scoreLimit = math::clamp( GetGametypeSetting( "scoreLimit" ), minValue, maxValue ); + level.scoreLimitMin = minValue; + level.scoreLimitMax = maxValue; + SetDvar( "ui_scorelimit", level.scoreLimit ); +} + + +function registerRoundScoreLimit( minValue, maxValue ) +{ + level.roundScoreLimit = math::clamp( GetGametypeSetting( "roundScoreLimit" ), minValue, maxValue ); + level.roundScoreLimitMin = minValue; + level.roundScoreLimitMax = maxValue; +} + + +function registerTimeLimit( minValue, maxValue ) +{ + level.timeLimit = math::clamp( GetGametypeSetting( "timeLimit" ), minValue, maxValue ); + level.timeLimitMin = minValue; + level.timeLimitMax = maxValue; + SetDvar( "ui_timelimit", level.timeLimit ); +} + + +function registerNumLives( minValue, maxValue, teamLivesMinValue, teamLivesMaxValue ) +{ + if(!isdefined(teamLivesMinValue))teamLivesMinValue=minValue; + if(!isdefined(teamLivesMaxValue))teamLivesMaxValue=maxValue; + + + level.numLives = math::clamp( GetGametypeSetting( "playerNumLives" ), minValue, maxValue ); + level.numLivesMin = minValue; + level.numLivesMax = maxValue; + level.numTeamLives = math::clamp( GetGametypeSetting( "teamNumLives" ), teamLivesMinValue, teamLivesMaxValue ); + level.numTeamLivesMin = teamLivesMinValue; + level.numTeamLivesMax = teamLivesMaxValue; +} + +function getPlayerFromClientNum( clientNum ) +{ + if ( clientNum < 0 ) + return undefined; + + for ( i = 0; i < level.players.size; i++ ) + { + if ( level.players[i] getEntityNumber() == clientNum ) + return level.players[i]; + } + return undefined; +} + +function isPressBuild() +{ + buildType = GetDvarString( "buildType" ); + + if ( isdefined( buildType ) && buildtype == "press" ) + { + return true; + } + + return false; +} + +function isFlashbanged() +{ + return isdefined( self.flashEndTime ) && gettime() < self.flashEndTime; +} + +function DoMaxDamage( origin, attacker, inflictor, headshot, mod ) // self == entity to damage +{ + if ( isdefined( self.damagedToDeath ) && self.damagedToDeath ) + { + return; + } + + if ( isdefined( self.maxHealth ) ) + { + damage = self.maxHealth + 1; + } + else + { + damage = self.health + 1; + } + + self.damagedToDeath = true; + + self DoDamage( damage, origin, attacker, inflictor, headshot, mod ); +} + + +/@ +"Name: self_delete()" +"Summary: Just calls the delete() script command on self. Reason for this is so that we can use array::thread_all to delete entities" +"Module: Entity" +"CallOn: An entity" +"Example: ai[ 0 ] thread self_delete();" +"SPMP: singleplayer" +@/ +function self_delete() +{ + if ( isdefined( self ) ) + { + self delete(); + } +} + + +/@ +"Name: screen_message_create()" +"Summary: Creates a HUD element at the correct position with the string or string reference passed in." +"Module: Utility" +"CallOn: N/A" +"MandatoryArg: : A string or string reference to place on the screen." +"OptionalArg: : A second string to display below the first." +"OptionalArg: : A third string to display below the second." +"OptionalArg: : Optional offset in y direction that should only be used in very specific circumstances." +"OptionalArg: : Length of time to display the message." +"Example: screen_message_create( &"LEVEL_STRING" );" +"SPMP: singleplayer" +@/ +function screen_message_create( string_message_1, string_message_2, string_message_3, n_offset_y, n_time ) +{ + level notify( "screen_message_create" ); + level endon( "screen_message_create" ); + + // if the mission is failing then do no create this instruction + // because it can potentially overlap the death/hint string + if( isdefined( level.missionfailed ) && level.missionfailed ) + return; + + // if player is killed then this dvar will be set. + // SUMEET_TODO - make it efficient next game instead of checking dvar here + if( GetDvarInt( "hud_missionFailed" ) == 1 ) + return; + + if ( !isdefined( n_offset_y ) ) + { + n_offset_y = 0; + } + + //handle displaying the first string + if( !isdefined(level._screen_message_1) ) + { + //text element that displays the name of the event + level._screen_message_1 = NewHudElem(); + level._screen_message_1.elemType = "font"; + level._screen_message_1.font = "objective"; + level._screen_message_1.fontscale = 1.8; + level._screen_message_1.horzAlign = "center"; + level._screen_message_1.vertAlign = "middle"; + level._screen_message_1.alignX = "center"; + level._screen_message_1.alignY = "middle"; + level._screen_message_1.y = -60 + n_offset_y; + level._screen_message_1.sort = 2; + + level._screen_message_1.color = ( 1, 1, 1 ); + level._screen_message_1.alpha = 1; + + level._screen_message_1.hidewheninmenu = true; + } + + //set the text of the element to the string passed in + level._screen_message_1 SetText( string_message_1 ); + + if( isdefined(string_message_2) ) + { + //handle displaying the first string + if( !isdefined(level._screen_message_2) ) + { + //text element that displays the name of the event + level._screen_message_2 = NewHudElem(); + level._screen_message_2.elemType = "font"; + level._screen_message_2.font = "objective"; + level._screen_message_2.fontscale = 1.8; + level._screen_message_2.horzAlign = "center"; + level._screen_message_2.vertAlign = "middle"; + level._screen_message_2.alignX = "center"; + level._screen_message_2.alignY = "middle"; + level._screen_message_2.y = -33 + n_offset_y; + level._screen_message_2.sort = 2; + + level._screen_message_2.color = ( 1, 1, 1 ); + level._screen_message_2.alpha = 1; + + level._screen_message_2.hidewheninmenu = true; + } + + //set the text of the element to the string passed in + level._screen_message_2 SetText( string_message_2 ); + } + else if( isdefined(level._screen_message_2) ) + { + level._screen_message_2 Destroy(); + } + + if( isdefined(string_message_3) ) + { + //handle displaying the first string + if( !isdefined(level._screen_message_3) ) + { + //text element that displays the name of the event + level._screen_message_3 = NewHudElem(); + level._screen_message_3.elemType = "font"; + level._screen_message_3.font = "objective"; + level._screen_message_3.fontscale = 1.8; + level._screen_message_3.horzAlign = "center"; + level._screen_message_3.vertAlign = "middle"; + level._screen_message_3.alignX = "center"; + level._screen_message_3.alignY = "middle"; + level._screen_message_3.y = -6 + n_offset_y; + level._screen_message_3.sort = 2; + + level._screen_message_3.color = ( 1, 1, 1 ); + level._screen_message_3.alpha = 1; + + level._screen_message_3.hidewheninmenu = true; + } + + //set the text of the element to the string passed in + level._screen_message_3 SetText( string_message_3 ); + } + else if( isdefined(level._screen_message_3) ) + { + level._screen_message_3 Destroy(); + } + + if ( isdefined( n_time ) && n_time > 0 ) + { + wait( n_time ); + + screen_message_delete(); + } +} + +/@ +"Name: screen_message_delete()" +"Summary: Deletes the current message being displayed on the screen made using screen_message_create." +"Module: Utility" +"CallOn: N/A" +"Example: screen_message_delete();" +"SPMP: singleplayer" +@/ +function screen_message_delete( delay ) +{ + if( isdefined( delay ) ) + { + wait( delay ); + } + + if( isdefined(level._screen_message_1) ) + { + level._screen_message_1 Destroy(); + } + if( isdefined(level._screen_message_2) ) + { + level._screen_message_2 Destroy(); + } + if( isdefined(level._screen_message_3) ) + { + level._screen_message_3 Destroy(); + } +} + +/@ +"Name: ghost_wait_show()" +"Summary: ghosts an entity, waits, then shows the entity; mainly used to hide pops when setting up models poses via setanim" +"Module: Utility" +"CallOn: An entity" +"OptionalArg: : how long to wait before showing" +"Example: turret thread ghost_wait_show();" +"SPMP: multiplayer" +@/ +function ghost_wait_show( wait_time = 0.1 ) +{ + self endon( "death" ); + + self Ghost(); + wait wait_time; + self Show(); +} + +/@ +"Name: ghost_wait_show_to_player()" +"Summary: ghosts an entity, waits, then shows the entity to a player; mainly used to hide pops when setting up models poses via setanim" +"Module: Utility" +"CallOn: An entity" +"MandatoryArg: : the player to whom to show this entity" +"OptionalArg: : how long to wait before showing to the player" +"OptionalArg: : sets up a self endon with this string" +"Example: turret thread ghost_wait_show_to_player( player );" +"SPMP: multiplayer" +@/ +function ghost_wait_show_to_player( player, wait_time = 0.1, self_endon_string1 ) +{ + if ( !isdefined( self ) ) + return; + + self endon( "death" ); + self.abort_ghost_wait_show_to_player = undefined; + + if ( isdefined( player ) ) + { + player endon( "death" ); + player endon( "disconnect" ); + player endon( "joined_team" ); + player endon( "joined_spectators" ); + } + + if ( isdefined( self_endon_string1 ) ) + self endon( self_endon_string1 ); + + self Ghost(); + self SetInvisibleToAll(); + self SetVisibleToPlayer( player ); + wait wait_time; + + if ( !isdefined( self.abort_ghost_wait_show_to_player ) ) + self ShowToPlayer( player ); +} + +/@ +"Name: ghost_wait_show_to_others()" +"Summary: ghosts an entity, waits, then shows the entity to other players; mainly used to hide pops when setting up models poses via setanim" +"Module: Utility" +"CallOn: An entity" +"MandatoryArg: : the player from whom to hide this entity" +"OptionalArg: : how long to wait before showing to others" +"OptionalArg: : sets up a self endon with this string" +"Example: turret thread ghost_wait_show_to_others( player );" +"SPMP: multiplayer" +@/ +function ghost_wait_show_to_others( player, wait_time = 0.1, self_endon_string1 ) +{ + if ( !isdefined( self ) ) + return; + + self endon( "death" ); + self.abort_ghost_wait_show_to_others = undefined; + + if ( isdefined( player ) ) + { + player endon( "death" ); + player endon( "disconnect" ); + player endon( "joined_team" ); + player endon( "joined_spectators" ); + } + + if ( isdefined( self_endon_string1 ) ) + self endon( self_endon_string1 ); + + self Ghost(); + self SetInvisibleToPlayer( player ); + wait wait_time; + + if ( !isdefined( self.abort_ghost_wait_show_to_others ) ) + { + self Show(); + self SetInvisibleToPlayer( player ); + } +} + +// button pressed wrappers +function use_button_pressed() +{ + Assert( IsPlayer( self ), "Must call use_button_pressed() on a player." ); + return ( self UseButtonPressed() ); +} + + +/@ +"Name: waittill_use_button_pressed()" +"Summary: Waits until the player is pressing their use button." +"Module: Player" +"Example: level.player waittill_use_button_pressed()" +"SPMP: SP" +@/ + +function waittill_use_button_pressed() +{ + while ( !self use_button_pressed() ) + { + wait .05; + } +} + +/@ +"Name: show_hint_text" +"Summary: Displays hint text for an amount of time. Can be turned off by sending a notify, or by calling hide_hint_text()." +"MandatoryArg: : The text to display." +"OptionalArg: : Should this menu flash on and off?" +"OptionalArg: : The use this notify to turn off the hint text." +"OptionalArg: : Override how many seconds the text is displayed for." +"Example: show_hint_text( "Your help text here!", "notify_hide_help_text" );" +@/ +function show_hint_text(str_text_to_show, b_should_blink=false, str_turn_off_notify="notify_turn_off_hint_text", n_display_time=4.0) +{ + self endon ("notify_turn_off_hint_text"); + self endon( "hint_text_removed" ); + + // Hide any help text which might already be up. + if ( isdefined(self.hint_menu_handle) ) + { + hide_hint_text(false); + } + + // Show the help text as a LUI menu. + self.hint_menu_handle = self OpenLUIMenu( "MPHintText" ); + self SetLUIMenuData( self.hint_menu_handle, "hint_text_line", str_text_to_show ); + + // Play the blink anim inside the LUImenu if it's meant to flash + if (b_should_blink) + { + lui::play_animation(self.hint_menu_handle, "blinking"); + } + else + { + lui::play_animation(self.hint_menu_handle, "display_noblink"); + } + + if( n_display_time != -1 ) + { + // Listen for hide or death notify. This interrupts the normal wait timer. + // This thread will also ensure the LUImenu is closed when the text has been displayed for the intended amount of time. + self thread hide_hint_text_listener(n_display_time); + + // Fade the hint text out after specified time if not interrupted. + // Note: the above thread will close the LUImenu when the desired time has elapsed. + self thread fade_hint_text_after_time(n_display_time, str_turn_off_notify); + } +} + +/@ +"Name: hide_hint_text" +"Summary: Hides any help text which may be on screen." +@/ +function hide_hint_text(b_fade_before_hiding=true) +{ + self endon( "hint_text_removed" ); + + if ( isdefined(self.hint_menu_handle) ) + { + if (b_fade_before_hiding) + { + lui::play_animation(self.hint_menu_handle, "fadeout"); + util::waittill_any_timeout(0.75, "kill_hint_text", "death", "hint_text_removed" ); + } + + self CloseLUIMenu(self.hint_menu_handle); + self.hint_menu_handle = undefined; + } + + // Terminate hint text threads listening for remove help text notify. + self notify("hint_text_removed"); +} + +// Fade out hint text before its luimenu is destroyed. +// If a notify to hide hint text is passed, this will fade out the hint text as well. +function fade_hint_text_after_time(n_display_time, str_turn_off_notify) +{ + self endon( "hint_text_removed" ); + self endon( "death" ); + self endon( "kill_hint_text" ); + + util::waittill_any_timeout(n_display_time - 0.75, str_turn_off_notify, "hint_text_removed", "kill_hint_text" ); + + hide_hint_text(true); +} + +// Listens for a notify to turn off the help text. +function hide_hint_text_listener(n_time) +{ + // Stop listening for turn off on death or when the hint text has been removed: + self endon( "hint_text_removed" ); + self endon( "disconnect" ); + + util::waittill_any_timeout(n_time, "kill_hint_text", "death", "hint_text_removed", "disconnect" ); + + hide_hint_text(false); +} + +function set_team_radar( team, value ) +{ + if( team == "allies" ) + { + SetMatchFlag( "radar_allies", value ); + } + else if ( team == "axis" ) + { + SetMatchFlag( "radar_axis", value ); + } +} + +function init_player_contract_events() +{ + if ( !isdefined( level.player_contract_events ) ) + level.player_contract_events = []; +} + +function register_player_contract_event( event_name, event_func, max_param_count = 0 ) +{ + if ( !isdefined( level.player_contract_events[event_name] ) ) + { + level.player_contract_events[event_name] = SpawnStruct(); + level.player_contract_events[event_name].param_count = max_param_count; + level.player_contract_events[event_name].events = []; + } + + assert( max_param_count == level.player_contract_events[event_name].param_count ); + + level.player_contract_events[event_name].events[ level.player_contract_events[event_name].events.size ] = event_func; +} + +function player_contract_event( event_name, param1 = undefined, param2 = undefined, param3 = undefined ) +{ + if ( !isdefined( level.player_contract_events[event_name] ) ) + return; + + param_count = (isdefined(level.player_contract_events[event_name].param_count)?level.player_contract_events[event_name].param_count:0); + + switch ( param_count ) + { + default: + case 0: + foreach( event_func in level.player_contract_events[event_name].events ) + { + if ( isdefined( event_func ) ) + self [[ event_func ]](); + } + break; + + case 1: + foreach( event_func in level.player_contract_events[event_name].events ) + { + if ( isdefined( event_func ) ) + self [[ event_func ]]( param1 ); + } + break; + + case 2: + foreach( event_func in level.player_contract_events[event_name].events ) + { + if ( isdefined( event_func ) ) + self [[ event_func ]]( param1, param2 ); + } + break; + + case 3: + foreach( event_func in level.player_contract_events[event_name].events ) + { + if ( isdefined( event_func ) ) + self [[ event_func ]]( param1, param2, param3 ); + } + break; + } +} + +/# +function debug_slow_heli_speed() +{ + // function intentionally not parameterized so that the speed can be globally controlled + if ( GetDvarInt( "scr_slow_heli", 0 ) > 0 ) + { + self SetSpeed( GetDvarInt( "scr_slow_heli" ) ); + } +} +#/ + +function is_objective_game( game_type ) +{ + switch ( game_type ) + { + case "tdm": + case "dm": // ffa + case "gun": + case "conf": + return false; + break; + + default: + return true; + } +} + +function isPropHuntGametype() +{ + return ( isdefined( level.isPropHunt ) && level.isPropHunt ); +} + +function isProp() +{ + return ( isdefined( self.pers["team"] ) && self.pers["team"] == game["defenders"] ); +} + +function increment_cwl_thermometer( amount ) +{ + if ( GetDvarInt( "ui_enablePromoTracking", 0 ) ) + { + util::increment_live_counter( "cwl_thermometer", amount ); + } +} + +function isInfectedGametype() +{ + return ( isdefined( level.isInfectMode ) && level.isInfectMode ); +} diff --git a/mp/_vehicle.csc b/mp/_vehicle.csc new file mode 100644 index 0000000..4b695a4 --- /dev/null +++ b/mp/_vehicle.csc @@ -0,0 +1,202 @@ +#using scripts\shared\clientfield_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; + + + + + +//Vehicle registration +#using scripts\mp\killstreaks\_ai_tank; +#using scripts\mp\killstreaks\_qrdrone; +#using scripts\mp\killstreaks\_rcbomb; + +#namespace vehicle; + +function autoexec __init__sytem__() { system::register("vehicle",&__init__,undefined,undefined); } + +function __init__() +{ + if( !isdefined( level._effect ) ) + { + level._effect = []; + } + level.vehicles_inited = true; + + clientfield::register( "vehicle", "timeout_beep", 1, 2, "int",&timeout_beep, !true, !true ); +} + +function vehicle_rumble(localClientNum) +{ + self endon( "entityshutdown" ); + + if(!isdefined(level.vehicle_rumble)) + return; + + type = self.vehicletype; + + if( !isdefined( level.vehicle_rumble[ type ] ) ) + return; + + rumblestruct = level.vehicle_rumble[ type ]; + height = rumblestruct.radius * 2; + zoffset = -1 * rumblestruct.radius; + + if( !isdefined( self.rumbleon ) ) + self.rumbleon = true; + + if( isdefined( rumblestruct.scale ) ) + self.rumble_scale = rumblestruct.scale; + else + self.rumble_scale = 0.15; + + if( isdefined( rumblestruct.duration ) ) + self.rumble_duration = rumblestruct.duration; + else + self.rumble_duration = 4.5; + + if( isdefined( rumblestruct.radius ) ) + self.rumble_radius = rumblestruct.radius; + else + self.rumble_radius = 600; + if( isdefined( rumblestruct.basetime ) ) + self.rumble_basetime = rumblestruct.basetime; + else + self.rumble_basetime = 1; + if( isdefined( rumblestruct.randomaditionaltime ) ) + self.rumble_randomaditionaltime = rumblestruct.randomaditionaltime; + else + self.rumble_randomaditionaltime = 1; + + self.player_touching = 0; + + // This is threaded on each vehicle, per local client - so we only need to be concerned with checking on + // client that we've been threaded on. + + radius_squared = rumblestruct.radius * rumblestruct.radius; + + while( 1 ) + { + if( (distancesquared(self.origin, level.localPlayers[localClientNum].origin) > radius_squared) || self getspeed() < 35 ) + { + wait(0.2); + continue; + } + + if(isdefined(self.rumbleon) && !self.rumbleon) + { + wait(0.2); + continue; + } + + self PlayRumbleLoopOnEntity( localClientNum, level.vehicle_rumble[ type ].rumble ); + + while((distancesquared(self.origin, level.localPlayers[localClientNum].origin) < radius_squared) && (self getspeed() > 5)) + { + //self earthquake( self.rumble_scale, self.rumble_duration, self.origin, self.rumble_radius ); // scale duration source radius + wait( self.rumble_basetime + randomfloat( self.rumble_randomaditionaltime ) ); + } + + self StopRumble( localClientNum, level.vehicle_rumble[ type ].rumble ); + } + +} + +function set_static_amount( staticAmount ) +{ + driverLocalClient = self GetLocalClientDriver(); + if ( isdefined( driverLocalClient ) ) + { + driver = GetLocalPlayer( driverLocalClient ); + if ( isdefined( driver ) ) + { + setfilterpassconstant( driver.localClientNum, 4, 0, 1, staticAmount ); + } + } +} + +function vehicle_variants( localClientNum ) +{ + // NOT YET SUPPORTED IN MP + /*if(isdefined(level.vehicleGearModels)) + { + if(isdefined(level.vehicleGearModels[self.vehicletype])) + { + numGear = level.vehicleGearModels[self.vehicletype].size; + maxGear = 2; + if(numGear level.vehicles_damage_states["sherman_mp"] + The default MP vehicle is "defaultvehicle_mp". This is used for vehicles who do not have a custom entry in the vehicles_damage_states[] array. + + Here we setup the vehicle damage effects response behaviors for all vehicles, using a data structure layout as follows: + struct s_vehicle_damage_state + { + float health_percentage; // apply effect(s) from effect_array[] when vehicle health is <= this amount + struct s_effect_array + { + id damage_effect; // this is the damage effect to apply + string sound_effect; // this is the sound effect to play + string vehicle_tag; // this is the tag on the vehicle where the effect should be applied + } effect_array[]; + }; + So, when a vehicle's health percentage becomes <= s_vehicle_damage_effects.health_percentage, + all the effects listed in s_vehicle_damage_effects.effect_array[] are applied as defined + */ + + // damage indices + k_mild_damage_index= 0; + k_moderate_damage_index= 1; + k_severe_damage_index= 2; + k_total_damage_index= 3; + + // health_percentage constants + k_mild_damage_health_percentage= 0.85; + k_moderate_damage_health_percentage= 0.55; + k_severe_damage_health_percentage= 0.35; + k_total_damage_health_percentage= 0; + level.k_mild_damage_health_percentage = k_mild_damage_health_percentage; + level.k_moderate_damage_health_percentage = k_moderate_damage_health_percentage; + level.k_severe_damage_health_percentage = k_severe_damage_health_percentage; + level.k_total_damage_health_percentage = k_total_damage_health_percentage; + + level.vehicles_damage_states= []; + level.vehicles_husk_effects = []; + level.vehicles_damage_treadfx = []; + + // setup the default vehicle + vehicle_name= get_default_vehicle_name(); + { + level.vehicles_damage_states[vehicle_name]= []; + level.vehicles_damage_treadfx[vehicle_name] = []; + + // mild damage + { + level.vehicles_damage_states[vehicle_name][k_mild_damage_index]= SpawnStruct(); + level.vehicles_damage_states[vehicle_name][k_mild_damage_index].health_percentage= k_mild_damage_health_percentage; + level.vehicles_damage_states[vehicle_name][k_mild_damage_index].effect_array= []; + // effect '0' - placed @ "tag_origin" + level.vehicles_damage_states[vehicle_name][k_mild_damage_index].effect_array[0]= SpawnStruct(); + level.vehicles_damage_states[vehicle_name][k_mild_damage_index].effect_array[0].damage_effect= "_t6/vehicle/vfire/fx_tank_sherman_smldr"; // smoldering (smoke puffs)//TODO T7 - contact FX team to get proper replacement + level.vehicles_damage_states[vehicle_name][k_mild_damage_index].effect_array[0].sound_effect= undefined; + level.vehicles_damage_states[vehicle_name][k_mild_damage_index].effect_array[0].vehicle_tag= "tag_origin"; + } + // moderate damage + { + level.vehicles_damage_states[vehicle_name][k_moderate_damage_index]= SpawnStruct(); + level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].health_percentage= k_moderate_damage_health_percentage; + level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].effect_array= []; + // effect '0' - placed @ "tag_origin" + level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].effect_array[0]= SpawnStruct(); + level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].effect_array[0].damage_effect= "_t6/vehicle/vfire/fx_vfire_med_12"; // flames & more smoke//TODO T7 - contact FX team to get proper replacement + level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].effect_array[0].sound_effect= undefined; + level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].effect_array[0].vehicle_tag= "tag_origin"; + } + // severe damage + { + level.vehicles_damage_states[vehicle_name][k_severe_damage_index]= SpawnStruct(); + level.vehicles_damage_states[vehicle_name][k_severe_damage_index].health_percentage= k_severe_damage_health_percentage; + level.vehicles_damage_states[vehicle_name][k_severe_damage_index].effect_array= []; + // effect '0' - placed @ "tag_origin" + level.vehicles_damage_states[vehicle_name][k_severe_damage_index].effect_array[0]= SpawnStruct(); + level.vehicles_damage_states[vehicle_name][k_severe_damage_index].effect_array[0].damage_effect= "_t6/vehicle/vfire/fx_vfire_sherman"; // pillar of smoke//TODO T7 - contact FX team to get proper replacement + level.vehicles_damage_states[vehicle_name][k_severe_damage_index].effect_array[0].sound_effect= undefined; + level.vehicles_damage_states[vehicle_name][k_severe_damage_index].effect_array[0].vehicle_tag= "tag_origin"; + } + // total damage + { + level.vehicles_damage_states[vehicle_name][k_total_damage_index]= SpawnStruct(); + level.vehicles_damage_states[vehicle_name][k_total_damage_index].health_percentage= k_total_damage_health_percentage; + level.vehicles_damage_states[vehicle_name][k_total_damage_index].effect_array= []; + // effect '0' - placed @ "tag_origin" + level.vehicles_damage_states[vehicle_name][k_total_damage_index].effect_array[0]= SpawnStruct(); + level.vehicles_damage_states[vehicle_name][k_total_damage_index].effect_array[0].damage_effect= "_t6/vehicle/vexplosion/fx_vexplode_helicopter_exp_mp"; + level.vehicles_damage_states[vehicle_name][k_total_damage_index].effect_array[0].sound_effect= "vehicle_explo"; // kaboom! + level.vehicles_damage_states[vehicle_name][k_total_damage_index].effect_array[0].vehicle_tag= "tag_origin"; + } + + + { + default_husk_effects = SpawnStruct(); + default_husk_effects.damage_effect = undefined; + default_husk_effects.sound_effect = undefined; + default_husk_effects.vehicle_tag = "tag_origin"; + + level.vehicles_husk_effects[ vehicle_name ] = default_husk_effects; + } + } + +// _t34::build_damage_states(); +// _panzeriv::build_damage_states(); + + return; +} + +//string +function get_vehicle_name( + vehicle) +{ + name= ""; + + if (isdefined(vehicle)) + { + if (isdefined(vehicle.vehicletype)) + { + name= vehicle.vehicletype; + } + } + + return name; +} + +//string +function get_default_vehicle_name() +{ + return "defaultvehicle_mp"; +} + +//string +function get_vehicle_name_key_for_damage_states( + vehicle) +{ + vehicle_name= get_vehicle_name(vehicle); + + if ( !isdefined(level.vehicles_damage_states) || !isdefined(level.vehicles_damage_states[vehicle_name])) + { + vehicle_name= get_default_vehicle_name(); + } + + return vehicle_name; +} + +//int +function get_vehicle_damage_state_index_from_health_percentage( + vehicle) +{ + if ( !isdefined( level.vehicles_damage_states ) ) + return -1; + + damage_state_index= -1; + vehicle_name= get_vehicle_name_key_for_damage_states(); + + for (test_index= 0; test_index0) + { + previous_damage_state_index= get_vehicle_damage_state_index_from_health_percentage(vehicle); + vehicle.current_health_percentage= vehicle.health/vehicle.initial_state.health; + current_damage_state_index= get_vehicle_damage_state_index_from_health_percentage(vehicle); + // if we have reached a new damage state, play associated effects + if (previous_damage_state_index!=current_damage_state_index) + { + vehicle notify ( "damage_state_changed" ); + if (previous_damage_state_index<0) + { + start_damage_state_index= 0; + } + else + { + start_damage_state_index= previous_damage_state_index+1; + } + play_damage_state_effects(vehicle, start_damage_state_index, current_damage_state_index); + if ( vehicle.health <= 0 ) + { + vehicle kill_vehicle(attacker); + } + } + } +} + + +function play_damage_state_effects( + vehicle, + start_damage_state_index, + end_damage_state_index) +{ + vehicle_name= get_vehicle_name_key_for_damage_states( vehicle ); + + // play effects for all damage states from start_damage_state_index --> end_damage_state_index + for (damage_state_index= start_damage_state_index; damage_state_index<=end_damage_state_index; damage_state_index++) + { + for (effect_index= 0; + effect_index 0 ) + { + // if the specified effect was loaded, play it on the associated vehicle tag + if ( isdefined( effects.damage_effect ) ) + { + PlayFxOnTag( effects.damage_effect, self, effects.vehicle_tag ); + } + wait( waitTime ); + } +} + + +function init_vehicle_entities() +{ + vehicles = getentarray( "script_vehicle", "classname" ); + array::thread_all( vehicles,&init_original_vehicle ); + + if ( isdefined( vehicles ) ) + { + return vehicles.size; + } + + return 0; +} + + +function precache_vehicles() +{ + // Last time I tested, this is actually not called from anywhere +} + + +function register_vehicle() +{ + // Register vehicle husk + if ( !isdefined( level.vehicles_list ) ) + { + level.vehicles_list = []; + } + + level.vehicles_list[ level.vehicles_list.size ] = self; +} + + +// Before spawning a new vehicle, we need to bookkeep our list +// of instantiated vehicles. If spawning the new vehicle would cause us to go +// over the max vehicle limit, then we need to force-delete +// the oldest (dead) ones to make room. + +function manage_vehicles() +{ + if ( !isdefined( level.vehicles_list ) ) + { + return true; + } + else + { + MAX_VEHICLES = GetMaxVehicles(); + + { + // Consolidate array - Husks might have been cleaned up in the interim + + newArray = []; + + for ( i = 0; i < level.vehicles_list.size; i++ ) + { + if ( isdefined( level.vehicles_list[ i ] ) ) + { + newArray[ newArray.size ] = level.vehicles_list[ i ]; + } + } + + level.vehicles_list = newArray; + } + + + // make sure there's room for one more + vehiclesToDelete = ( level.vehicles_list.size + 1 ) - MAX_VEHICLES; + + + if ( vehiclesToDelete > 0 ) + { + newArray = []; + + for ( i = 0; i < level.vehicles_list.size; i++ ) + { + vehicle = level.vehicles_list[ i ]; + + if ( vehiclesToDelete > 0 ) + { + // ".permanentlyRemoved" vehicles will never be really deleted + if ( isdefined( vehicle.is_husk ) && !isdefined( vehicle.permanentlyRemoved ) ) + { + deleted = vehicle husk_do_cleanup(); + + if ( deleted ) + { + vehiclesToDelete--; + continue; + } + } + } + + newArray[ newArray.size ] = vehicle; + } + + level.vehicles_list = newArray; + } + + return level.vehicles_list.size < MAX_VEHICLES; + } +} + + +/@ +"Name: init_vehicle( )" +"Summary: Initializes a vehicle entity when the game starts" +"Module: Vehicle" +"Example: vehicle init_vehicle();" +"SPMP: multiplayer" +@/ +function init_vehicle() +{ + self register_vehicle(); + + + // setting the tank health here so it is universal + // we should do the same for the other vehicles + if ( isdefined( level.vehicleHealths ) && isdefined( level.vehicleHealths[ self.vehicletype ] ) ) + { + self.maxhealth = level.vehicleHealths[ self.vehicletype ]; + } + else + { + self.maxhealth = GetDvarint( "scr_veh_health_tank"); +/# + println( "No health specified for vehicle type "+self.vehicletype+"! Using default..." ); +#/ + } + self.health = self.maxhealth; + + self vehicle_record_initial_values(); + + self init_vehicle_threads(); + + system::wait_till( "spawning" ); + + // add the influencer to block all teams + // do not spawn all team influencer -- per design + // self spawning::create_entity_masked_enemy_influencer( "vehicle", 0 ); +} + + +function initialize_vehicle_damage_state_data() +{ + if (self.initial_state.health>0) + { + self.current_health_percentage= self.health/self.initial_state.health; + self.previous_health_percentage= self.health/self.initial_state.health; + } + else + { + self.current_health_percentage= 1; + self.previous_health_percentage= 1; + } + + return; +} + +function init_original_vehicle() +{ + // this is a temporary hack trying to resolve the "!cent->pose.fx.effect" + // crash bug. Basically I think the bug is caused by deleteing the original + // tanks that were in the bsp + self.original_vehicle = true; + + self init_vehicle(); +} + +// debug code below is for a simple vehicle health bar +function vehicle_wait_player_enter_t() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + while( 1 ) + { + self waittill( "enter_vehicle", player ); + + player thread player_wait_exit_vehicle_t(); + + player player_update_vehicle_hud( true, self ); + } +} + + +function player_wait_exit_vehicle_t() +{ + // Don't endon "death". Player will receive + // "exit_vehicle" message when killed in a vehicle + self endon( "disconnect" ); + + self waittill( "exit_vehicle", vehicle ); + self player_update_vehicle_hud( false, vehicle ); +} + + +function vehicle_wait_damage_t() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + while( 1 ) + { + self waittill ( "damage" ); + + occupants = self GetVehOccupants(); + if( isdefined( occupants ) ) + { + for( i = 0; i < occupants.size; i++ ) + { + occupants[i] player_update_vehicle_hud( true, self ); + } + } + } +} + + +function player_update_vehicle_hud( show, vehicle ) +{ + if( show ) + { + if ( !isdefined( self.vehicleHud ) ) + { + self.vehicleHud = hud::createBar( (1, 1, 1), 64, 16 ); + self.vehicleHud hud::setPoint( "CENTER", "BOTTOM", 0, -40 ); + self.vehicleHud.alpha = 0.75; + } + + self.vehicleHud hud::updateBar( vehicle.health / vehicle.initial_state.health ); + } + else + { + if ( isdefined( self.vehicleHud ) ) + { + self.vehicleHud hud::destroyElem(); + } + } + + if ( GetDvarString( "scr_vehicle_healthnumbers" )!= "" ) + { + if ( GetDvarint( "scr_vehicle_healthnumbers" )!= 0 ) + { + if( show ) + { + if ( !isdefined( self.vehicleHudHealthNumbers ) ) + { + self.vehicleHudHealthNumbers = hud::createFontString( "default", 2.0 ); + self.vehicleHudHealthNumbers hud::setParent( self.vehicleHud ); + self.vehicleHudHealthNumbers hud::setPoint( "LEFT", "RIGHT", 8, 0 ); + self.vehicleHudHealthNumbers.alpha = 0.75; + self.vehicleHudHealthNumbers.hideWhenInMenu = false; + self.vehicleHudHealthNumbers.archived = false; + } + + self.vehicleHudHealthNumbers setValue( vehicle.health ); + } + else + { + if ( isdefined( self.vehicleHudHealthNumbers ) ) + { + self.vehicleHudHealthNumbers hud::destroyElem(); + } + } + } + } +} + + +function init_vehicle_threads() +{ + self thread vehicle_abandoned_by_drift_t(); + self thread vehicle_abandoned_by_occupants_t(); + self thread vehicle_damage_t(); + self thread vehicle_ghost_entering_occupants_t(); + + self thread vehicle_recycle_spawner_t(); + self thread vehicle_disconnect_paths(); + + // enable debug vehicle health bar + if( isdefined( level.enableVehicleHealthbar ) && level.enableVehicleHealthbar ) + { + self thread vehicle_wait_player_enter_t(); + self thread vehicle_wait_damage_t(); + } + + self thread vehicle_wait_tread_damage(); + + self thread vehicle_overturn_eject_occupants(); + + if (GetDvarint( "scr_veh_disableoverturndamage") == 0) + { + self thread vehicle_overturn_suicide(); + } + + /# + self thread cleanup_debug_print_t(); + self thread cleanup_debug_print_clearmsg_t(); + #/ +} + +/@ +"Name: build_template( , , )" +"Summary: called in individual vehicle file - mandatory to call this in all vehicle files at the top!" +"Module: vehicle_build( vehicle.gsc )" +"CallOn: " +"MandatoryArg: : vehicle type to set" +"MandatoryArg: : model to set( this is usually generated by the level script )" +"OptionalArg: : this overrides the type, used for copying a vehicle script" +"Example: build_template( "bmp", model, type );" +"SPMP: singleplayer" +@/ + +function build_template( type, model, typeoverride ) +{ + if( isdefined( typeoverride ) ) + type = typeoverride; + + if( !isdefined( level.vehicle_death_fx ) ) + level.vehicle_death_fx = []; + if( !isdefined( level.vehicle_death_fx[ type ] ) ) + level.vehicle_death_fx[ type ] = []; // can have overrides + + level.vehicle_compassicon[ type ] = false; + level.vehicle_team[ type ] = "axis"; + level.vehicle_life[ type ] = 999; + level.vehicle_hasMainTurret[ model ] = false; + level.vehicle_mainTurrets[ model ] = []; + level.vtmodel = model; + level.vttype = type; +} + +/@ +"Name: build_rumble( , , , , , )" +"Summary: called in individual vehicle file - define amount of radius damage to be set on each vehicle" +"Module: vehicle_build( vehicle.gsc )" +"CallOn: " +"MandatoryArg: : rumble asset" +"MandatoryArg: : scale" +"MandatoryArg: : duration" +"MandatoryArg: : radius" +"MandatoryArg: : time to wait between rumbles" +"MandatoryArg: : random amount of time to add to basetime" +"Example: build_rumble( "tank_rumble", 0.15, 4.5, 600, 1, 1 );" +"SPMP: singleplayer" +@/ + +function build_rumble( rumble, scale, duration, radius, basetime, randomaditionaltime ) +{ + if( !isdefined( level.vehicle_rumble ) ) + level.vehicle_rumble = []; + struct = build_quake( scale, duration, radius, basetime, randomaditionaltime ); + assert( isdefined( rumble ) ); + struct.rumble = rumble; + level.vehicle_rumble[ level.vttype ] = struct; +} + +function build_quake( scale, duration, radius, basetime, randomaditionaltime ) +{ + struct = spawnstruct(); + struct.scale = scale; + struct.duration = duration; + struct.radius = radius; + if( isdefined( basetime ) ) + struct.basetime = basetime; + if( isdefined( randomaditionaltime ) ) + struct.randomaditionaltime = randomaditionaltime; + return struct; +} + +/@ +"Name: build_exhaust( )" +"Summary: called in individual vehicle file - assign an exhaust effect to this vehicle!" +"Module: vehicle_build( vehicle.gsc )" +"CallOn: " +"MandatoryArg: : exhaust effect in string format" +"Example: build_exhaust( "_t6/vehicle/exhaust/fx_exhaust_tank" );" +"SPMP: singleplayer" +@/ + +function build_exhaust( effect ) +{ + level.vehicle_exhaust[ level.vtmodel ] = effect; +} + +function cleanup_debug_print_t() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + /# + while ( 1 ) + { + if ( isdefined( self.debug_message ) + && GetDvarint( "scr_veh_cleanupdebugprint" ) != 0 ) + { + Print3d( self.origin + ( 0, 0, 150 ), self.debug_message, ( 0, 1, 0 ), 1, 1, 1 ); + } + + wait 0.01; + } + #/ +} + + +function cleanup_debug_print_clearmsg_t() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + /# + while ( 1 ) + { + self waittill( "enter_vehicle" ); + self.debug_message = undefined; + } + #/ +} + + +function cleanup_debug_print( message ) +{ + /# + self.debug_message = message; + #/ +} + + +// ===================================================================================== +// Abandonment Code + +function vehicle_abandoned_by_drift_t() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + self wait_then_cleanup_vehicle( "Drift Test", "scr_veh_cleanupdrifted" ); +} + + +function vehicle_abandoned_by_occupants_timeout_t() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + self wait_then_cleanup_vehicle( "Abandon Test", "scr_veh_cleanupabandoned" ); +} + + +function wait_then_cleanup_vehicle( test_name, cleanup_dvar_name ) +{ + self endon( "enter_vehicle" ); + + self wait_until_severely_damaged(); + self do_alive_cleanup_wait( test_name ); + self wait_for_vehicle_to_stop_outside_min_radius(); // unoccupied vehicle can be being pushed! + self cleanup( test_name, cleanup_dvar_name,&vehicle_recycle ); +} + + +function wait_until_severely_damaged() +{ + while ( 1 ) + { + health_percentage = self.health / self.initial_state.health; + + if ( isdefined( level.k_severe_damage_health_percentage ) ) // initially called before this is set up. + { + self cleanup_debug_print( "Damage Test: Still healthy - (" + health_percentage + " >= " + level.k_severe_damage_health_percentage + ") and working treads"); + } + else + { + self cleanup_debug_print( "Damage Test: Still healthy and working treads"); + } + + + self waittill( "damage" ); + + health_percentage = self.health / self.initial_state.health; + + if ( isdefined( level.k_severe_damage_health_percentage ) && health_percentage < level.k_severe_damage_health_percentage ) + break; + } +} + + +function get_random_cleanup_wait_time( state ) +{ + varnamePrefix = "scr_veh_" + state + "_cleanuptime"; + minTime = getdvarfloat( varnamePrefix + "min" ); + maxTime = getdvarfloat( varnamePrefix + "max" ); + + if ( maxTime > minTime ) + { + return RandomFloatRange( minTime, maxTime ); + } + else + { + return maxTime; + } +} + + +function do_alive_cleanup_wait( test_name ) +{ + initialRandomWaitSeconds = get_random_cleanup_wait_time( "alive" ); + + secondsWaited = 0.0; + seconds_per_iteration = 1.0; + + while ( true ) + { + curve_begin = GetDvarfloat( "scr_veh_cleanuptime_dmgfraction_curve_begin" ); + curve_end = GetDvarfloat( "scr_veh_cleanuptime_dmgfraction_curve_end" ); + + factor_min = GetDvarfloat( "scr_veh_cleanuptime_dmgfactor_min" ); + factor_max = GetDvarfloat( "scr_veh_cleanuptime_dmgfactor_max" ); + + treadDeadDamageFactor = GetDvarfloat( "scr_veh_cleanuptime_dmgfactor_deadtread" ); + + + damageFraction = 0.0; + + if ( self is_vehicle() ) + { + damageFraction = ( self.initial_state.health - self.health ) / self.initial_state.health; + } + else // is husk + { + damageFraction = 1.0; + } + + damageFactor = 0.0; + + if ( damageFraction <= curve_begin ) + { + damageFactor = factor_max; + } + else if ( damageFraction >= curve_end ) + { + damageFactor = factor_min; + } + else + { + dydx = ( factor_min - factor_max ) / ( curve_end - curve_begin ); + damageFactor = factor_max + ( damageFraction - curve_begin ) * dydx; + } + + totalSecsToWait = initialRandomWaitSeconds * damageFactor; + + if ( secondsWaited >= totalSecsToWait ) + { + break; + } + + self cleanup_debug_print( test_name + ": Waiting " + ( totalSecsToWait - secondsWaited ) + "s" ); + + wait seconds_per_iteration; + secondsWaited = secondsWaited + seconds_per_iteration; + } +} + + +function do_dead_cleanup_wait( test_name ) +{ + total_secs_to_wait = get_random_cleanup_wait_time( "dead" ); + + seconds_waited = 0.0; + seconds_per_iteration = 1.0; + + while ( seconds_waited < total_secs_to_wait ) + { + self cleanup_debug_print( test_name + ": Waiting " + ( total_secs_to_wait - seconds_waited ) + "s" ); + wait seconds_per_iteration; + seconds_waited = seconds_waited + seconds_per_iteration; + } +} + + +function cleanup( test_name, cleanup_dvar_name, cleanup_func ) +{ + keep_waiting = true; + + while ( keep_waiting ) + { + cleanupEnabled = !isdefined( cleanup_dvar_name ) + || getdvarint( cleanup_dvar_name ) != 0; + + if ( cleanupEnabled != 0 ) + { + self [[cleanup_func]](); + break; + } + + keep_waiting = false; + + /# + // Only in debug mode, we will keep looping in case someone turns the + // cleanup dvar back on. + self cleanup_debug_print( "Cleanup disabled for " + test_name + " ( dvar = " + cleanup_dvar_name + " )" ); + wait 5.0; + keep_waiting = true; + #/ + } +} + +function vehicle_wait_tread_damage() +{ + self endon( "death" ); + self endon( "delete" ); + + vehicle_name= get_vehicle_name(self); + + while ( 1 ) + { + self waittill ( "broken", brokenNotify ); + if ( brokenNotify == "left_tread_destroyed" ) + { + if ( isdefined( level.vehicles_damage_treadfx[vehicle_name] ) && isdefined( level.vehicles_damage_treadfx[vehicle_name][0] ) ) + { + self thread play_vehicle_effects( level.vehicles_damage_treadfx[vehicle_name][0], true ); + } + } + else if ( brokenNotify == "right_tread_destroyed" ) + { + if ( isdefined( level.vehicles_damage_treadfx[vehicle_name] ) && isdefined( level.vehicles_damage_treadfx[vehicle_name][1] ) ) + { + self thread play_vehicle_effects( level.vehicles_damage_treadfx[vehicle_name][1], true ); + } + } + } +} + +function wait_for_vehicle_to_stop_outside_min_radius() +{ + maxWaitTime = GetDvarfloat( "scr_veh_waittillstoppedandmindist_maxtime" ); + iterationWaitSeconds = 1.0; + + maxWaitTimeEnableDistInches = 12 * GetDvarfloat( "scr_veh_waittillstoppedandmindist_maxtimeenabledistfeet" ); + + initialOrigin = self.initial_state.origin; + + for ( totalSecondsWaited = 0.0; totalSecondsWaited < maxWaitTime; totalSecondsWaited += iterationWaitSeconds ) + { + // We don't want to disappear it if someone is + // currently pushing it with another vehicle + speedMPH = self GetSpeedMPH(); + cutoffMPH = GetDvarfloat( "scr_veh_cleanupmaxspeedmph" ); + + if ( speedMPH > cutoffMPH ) + { + cleanup_debug_print( "(" + ( maxWaitTime - totalSecondsWaited ) + "s) Speed: " + speedMPH + ">" + cutoffMPH ); + } + else + { + break; + + /* + initialOriginOnTerrain = ( initialOrigin[0], initialOrigin[1], 0 ); + originOnTerrain = ( self.origin[0], self.origin[1], 0 ); + moveDistInches = Distance( initialOriginOnTerrain, originOnTerrain ); + cutoffInches = 12 * GetDvarint( "scr_veh_cleanupmindistancefeet" ); + + if ( moveDistInches < cutoffInches ) + { + cleanup_debug_print( "(" + ( maxWaitTime - totalSecondsWaited ) + "s) Dist: " + moveDistInches + "<" + cutoffInches ); + + if ( moveDistInches < maxWaitTimeEnableDistInches ) + { + totalSecondsWaited = 0.0; + } + } + else + { + break; + } + */ + } + + wait iterationWaitSeconds; + } +} + + +function vehicle_abandoned_by_occupants_t() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + while ( 1 ) + { + self waittill( "exit_vehicle" ); + + occupants = self GetVehOccupants(); + + if ( occupants.size == 0 ) + { + self play_start_stop_sound( "tank_shutdown_sfx" ); + self thread vehicle_abandoned_by_occupants_timeout_t(); + } + } +} + + +function play_start_stop_sound( sound_alias, modulation ) +{ + if ( isdefined( self.start_stop_sfxid ) ) + { + //stopSound( self.start_stop_sfxid ); + } + + self.start_stop_sfxid = self playSound( sound_alias ); +} + + +// this function should be replaced by code in cg_player.cpp cg_player() +// we should just not be rendering the player when in a non-visible seat +function vehicle_ghost_entering_occupants_t() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + if ( IsDefined( self.vehicleClass ) && "artillery" == self.vehicleClass ) + { + return; + } + + //if ( self vehicle_is_tank() ) + { + while ( 1 ) + { + self waittill( "enter_vehicle", player, seat ); + + isDriver = seat == 0; + + if ( GetDvarint( "scr_veh_driversarehidden" ) != 0 + && isDriver ) + { + player Ghost(); + } + + + { + occupants = self GetVehOccupants(); + + if ( occupants.size == 1 ) + { + self play_start_stop_sound( "tank_startup_sfx" ); + } + } + + + player thread player_change_seat_handler_t( self ); + player thread player_leave_vehicle_cleanup_t( self ); + } + } +} + + +function player_is_occupant_invulnerable( sMeansOfDeath ) +{ + if ( self IsRemoteControlling() ) + return false; + + if (!isdefined(level.vehicle_drivers_are_invulnerable)) + level.vehicle_drivers_are_invulnerable = false; + + invulnerable = ( level.vehicle_drivers_are_invulnerable && ( self player_is_driver() ) ); + + return invulnerable; +} + + +function player_is_driver() +{ + if ( !isalive(self) ) + return false; + + vehicle = self GetVehicleOccupied(); + + if ( isdefined( vehicle ) ) + { + if ( IsDefined( vehicle.vehicleClass ) && "artillery" == vehicle.vehicleClass ) + { + return false; + } + + seat = vehicle GetOccupantSeat( self ); + + if ( isdefined(seat) && seat == 0 ) + return true; + } + + return false; +} + + +// this function should be replaced by code in cg_player.cpp cg_player() +// we should just not be rendering the player when in a non-visible seat +function player_change_seat_handler_t( vehicle ) +{ + self endon( "disconnect" ); + self endon( "exit_vehicle" ); + + while ( 1 ) + { + self waittill( "change_seat", vehicle, oldSeat, newSeat ); + + isDriver = newSeat == 0; + + if ( isDriver ) + { + if ( GetDvarint( "scr_veh_driversarehidden" ) != 0 ) + { + self Ghost(); + } + } + else + { + self Show(); + } + } +} + + +// this function should be replaced by code in cg_player.cpp cg_player() +// we should just not be rendering the player when in a non-visible seat +function player_leave_vehicle_cleanup_t( vehicle ) +{ + self endon( "disconnect" ); + self waittill( "exit_vehicle" ); + currentWeapon = self getCurrentWeapon(); + + if ( IsDefined( self.lastWeapon ) && self.lastWeapon != currentWeapon && self.lastWeapon != level.weaponNone ) + { + self switchToWeapon( self.lastWeapon ); + } + + self Show(); +} + + +function vehicle_is_tank() +{ + return self.vehicletype == "sherman_mp" + || self.vehicletype == "panzer4_mp" + || self.vehicletype == "type97_mp" + || self.vehicletype == "t34_mp"; +} + +// ===================================================================================== + + +function vehicle_record_initial_values() +{ + if ( !isdefined( self.initial_state ) ) + { + self.initial_state= SpawnStruct(); + } + + if ( isdefined( self.origin ) ) + { + self.initial_state.origin= self.origin; + } + + if ( isdefined( self.angles ) ) + { + self.initial_state.angles= self.angles; + } + + if ( isdefined( self.health ) ) + { + self.initial_state.health= self.health; + } + + self initialize_vehicle_damage_state_data(); + + return; +} + + +function vehicle_should_explode_on_cleanup() +{ + return GetDvarint( "scr_veh_explode_on_cleanup" ) != 0; +} + + +function vehicle_recycle() +{ + self wait_for_unnoticeable_cleanup_opportunity(); + self.recycling = true; + self suicide(); +} + +function wait_for_vehicle_overturn() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + worldup = anglestoup((0,90,0)); + + overturned = 0; + + while (!overturned) + { + if ( isdefined( self.angles ) ) + { + up = AnglesToUp( self.angles ); + dot = vectordot(up, worldup); + if (dot <= 0.0) + overturned = 1; + } + + if (!overturned) + wait (1.0); + } +} + +function vehicle_overturn_eject_occupants() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + for(;;) + { + self waittill( "veh_ejectoccupants" ); + + if ( isdefined( level.onEjectOccupants ) ) + { + [[level.onEjectOccupants]](); + } + + wait .25; + } +} + +function vehicle_eject_all_occupants() +{ + occupants = self GetVehOccupants(); + if ( isdefined( occupants ) ) + { + for ( i = 0; i < occupants.size; i++ ) + { + if ( isdefined( occupants[i] ) ) + { + occupants[i] Unlink(); + } + } + } +} + +function vehicle_overturn_suicide() +{ + self endon( "transmute" ); + self endon( "death" ); + self endon( "delete" ); + + self wait_for_vehicle_overturn(); + + seconds = RandomFloatRange( 5, 7 ); + wait seconds; + + damageOrigin = self.origin + (0,0,25); + self finishVehicleRadiusDamage(self, self, 32000, 32000, 32000, 0, "MOD_EXPLOSIVE", level.weaponNone, damageOrigin, 400, -1, (0,0,1), 0); +} + +function suicide() +{ + self kill_vehicle( self ); +} + +function kill_vehicle( attacker ) +{ + damageOrigin = self.origin + (0,0,1); + self finishVehicleRadiusDamage(attacker, attacker, 32000, 32000, 10, 0, "MOD_EXPLOSIVE", level.weaponNone, damageOrigin, 400, -1, (0,0,1), 0); +} + +function value_with_default( preferred_value, default_value ) +{ + if ( isdefined( preferred_value ) ) + { + return preferred_value; + } + + return default_value; +} + + +function vehicle_transmute( attacker ) +{ + deathOrigin = self.origin; + deathAngles = self.angles; + + + vehicle_name = get_vehicle_name_key_for_damage_states( self ); + + + respawn_parameters = SpawnStruct(); + respawn_parameters.origin = self.initial_state.origin; + respawn_parameters.angles = self.initial_state.angles; + respawn_parameters.health = self.initial_state.health; + respawn_parameters.targetname = value_with_default( self.targetname, "" ); + respawn_parameters.vehicletype = value_with_default( self.vehicletype, "" ); + respawn_parameters.destructibledef = self.destructibledef; // Vehicle may or may not be a destructible vehicle + + + vehicleWasDestroyed = !isdefined( self.recycling ); + + if ( vehicleWasDestroyed + || vehicle_should_explode_on_cleanup() ) + { + _spawn_explosion( deathOrigin ); + + if ( vehicleWasDestroyed + && GetDvarint( "scr_veh_explosion_doradiusdamage" ) != 0 ) + { + // Vehicle is exploding, so damage nearby entities + // Damage first, so doesn't affect other entities spawned + // by this function + + explosionRadius = GetDvarint( "scr_veh_explosion_radius" ); + explosionMinDamage = GetDvarint( "scr_veh_explosion_mindamage" ); + explosionMaxDamage = GetDvarint( "scr_veh_explosion_maxdamage" ); + self kill_vehicle(attacker); + self RadiusDamage( deathOrigin, explosionRadius, explosionMaxDamage, explosionMinDamage, attacker, "MOD_EXPLOSIVE", GetWeapon( self.vehicletype + "_explosion" ) );////////////////XXXXXXXXXXXXXXXXX COLLATERAL DAMAGE + } + } + + + self notify( "transmute" ); + + + respawn_vehicle_now = true; + + if ( vehicleWasDestroyed + && GetDvarint( "scr_veh_ondeath_createhusk" ) != 0 ) + { + // Spawn burned out husk for players to use as cover + + if ( GetDvarint( "scr_veh_ondeath_usevehicleashusk" ) != 0 ) + { + husk = self; + self.is_husk = true; + } + else + { + husk = _spawn_husk( deathOrigin, deathAngles, self.vehmodel ); + } + + husk _init_husk( vehicle_name, respawn_parameters ); + + if ( GetDvarint( "scr_veh_respawnafterhuskcleanup" ) != 0 ) + { + respawn_vehicle_now = false; + } + } + + + if ( !isdefined( self.is_husk ) ) + { + self remove_vehicle_from_world(); + } + + if ( GetDvarint( "scr_veh_disablerespawn" ) != 0 ) //The Vehicle Mayhem gamemode handles spawning the vehicles, so it does not need to respawn here + { + respawn_vehicle_now = false; + } + + if ( respawn_vehicle_now ) + { + respawn_vehicle( respawn_parameters ); + } +} + + +function respawn_vehicle( respawn_parameters ) +{ + { + minTime = GetDvarint( "scr_veh_respawntimemin" ); + maxTime = GetDvarint( "scr_veh_respawntimemax" ); + seconds = RandomFloatRange( minTime, maxTime ); + wait seconds; + } + + + wait_until_vehicle_position_wont_telefrag( respawn_parameters.origin ); + + + if ( !manage_vehicles() ) // make sure we don't hit max vehicle limit + { + /# + iprintln("Vehicle can't respawn because MAX_VEHICLES has been reached and none of the vehicles could be cleaned up."); + #/ + } + else + { + if ( isdefined( respawn_parameters.destructibledef ) ) // passing undefined argument doesn't make the server happy + { + vehicle = SpawnVehicle( + respawn_parameters.vehicletype, + respawn_parameters.origin, + respawn_parameters.angles, + respawn_parameters.targetname, + respawn_parameters.destructibledef ); + } + else + { + vehicle = SpawnVehicle( + respawn_parameters.vehicletype, + respawn_parameters.origin, + respawn_parameters.angles, + respawn_parameters.targetname ); + } + + vehicle.vehicletype = respawn_parameters.vehicletype; + vehicle.destructibledef = respawn_parameters.destructibledef; + vehicle.health = respawn_parameters.health; + + vehicle init_vehicle(); + + vehicle vehicle_telefrag_griefers_at_position( respawn_parameters.origin ); + } +} + + +function remove_vehicle_from_world() +{ + // this is a temporary hack trying to resolve the "!cent->pose.fx.effect" + // crash bug. Basically I think the bug is caused by deleteing the original + // tanks that were in the bsp + + self notify ( "removed" ); + + if ( isdefined( self.original_vehicle ) ) + { + if ( !isdefined( self.permanentlyRemoved ) ) + { + self.permanentlyRemoved = true; // Mark that it has been permanently removed from the world + self thread hide_vehicle(); // threaded because it calls a wait() + } + + return false; + } + else + { + self _delete_entity(); + return true; + } +} + + +function _delete_entity() +{ + /# + //iprintln("$e" + ( self GetEntNum() ) + " is deleting"); + #/ + + self Delete(); +} + + +function hide_vehicle() +{ + under_the_world = ( self.origin[0], self.origin[1], self.origin[2] - 10000 ); + self.origin = under_the_world; + + wait 0.1; + self Hide(); + + self notify( "hidden_permanently" ); +} + + +function wait_for_unnoticeable_cleanup_opportunity() +{ + maxPreventDistanceFeet = GetDvarint( "scr_veh_disappear_maxpreventdistancefeet" ); + maxPreventVisibilityFeet = GetDvarint( "scr_veh_disappear_maxpreventvisibilityfeet" ); + + maxPreventDistanceInchesSq = 144 * maxPreventDistanceFeet * maxPreventDistanceFeet; + maxPreventVisibilityInchesSq = 144 * maxPreventVisibilityFeet * maxPreventVisibilityFeet; + + + maxSecondsToWait = GetDvarfloat( "scr_veh_disappear_maxwaittime" ); + iterationWaitSeconds = 1.0; + + for ( secondsWaited = 0.0; secondsWaited < maxSecondsToWait; secondsWaited += iterationWaitSeconds ) + { + players_s = util::get_all_alive_players_s(); + + okToCleanup = true; + + for ( j = 0; j < players_s.a.size && okToCleanup; j++ ) + { + player = players_s.a[ j ]; + distInchesSq = DistanceSquared( self.origin, player.origin ); + + if ( distInchesSq < maxPreventDistanceInchesSq ) + { + self cleanup_debug_print( "(" + ( maxSecondsToWait - secondsWaited ) + "s) Player too close: " + distInchesSq + "<" + maxPreventDistanceInchesSq ); + okToCleanup = false; + } + else if ( distInchesSq < maxPreventVisibilityInchesSq ) + { + vehicleVisibilityFromPlayer = self SightConeTrace( player.origin, player, AnglesToForward( player.angles ) ); + + if ( vehicleVisibilityFromPlayer > 0 ) + { + self cleanup_debug_print( "(" + ( maxSecondsToWait - secondsWaited ) + "s) Player can see" ); + okToCleanup = false; + } + } + } + + if ( okToCleanup ) + { + return; + } + + wait iterationWaitSeconds; + } +} + + +function wait_until_vehicle_position_wont_telefrag( position ) +{ + maxIterations = GetDvarint( "scr_veh_respawnwait_maxiterations" ); + iterationWaitSeconds = GetDvarint( "scr_veh_respawnwait_iterationwaitseconds" ); + + for ( i = 0; i < maxIterations; i++ ) + { + if ( !vehicle_position_will_telefrag( position ) ) + { + return; + } + + wait iterationWaitSeconds; + } +} + + +function vehicle_position_will_telefrag( position ) +{ + players_s = util::get_all_alive_players_s(); + + for ( i = 0; i < players_s.a.size; i++ ) + { + if ( players_s.a[ i ] player_vehicle_position_will_telefrag( position ) ) + { + return true; + } + } + + return false; +} + + +function vehicle_telefrag_griefers_at_position( position ) +{ + attacker = self; + inflictor = self; + + players_s = util::get_all_alive_players_s(); + + for ( i = 0; i < players_s.a.size; i++ ) + { + player = players_s.a[ i ]; + + if ( player player_vehicle_position_will_telefrag( position ) ) + { + player DoDamage( 20000, player.origin + ( 0, 0, 1 ), attacker, inflictor, "none" ); + } + } +} + + +function player_vehicle_position_will_telefrag( position ) +{ + distanceInches = 20 * 12; ///< 20 ft., in inches + minDistInchesSq = distanceInches * distanceInches; + + distInchesSq = DistanceSquared( self.origin, position ); + + return distInchesSq < minDistInchesSq; +} + + +function vehicle_recycle_spawner_t() +{ + self endon( "delete" ); + + self waittill( "death", attacker ); // "vehicle Delete()" sends death message too!!! + + if ( isdefined( self ) ) + { + self vehicle_transmute( attacker ); + } +} + + +function vehicle_play_explosion_sound() +{ + self playSound( "car_explo_large" ); +} + + +function vehicle_damage_t() +{ + self endon( "delete" ); + self endon( "removed" ); + + for( ;; ) + { + self waittill ( "damage", damage, attacker ); + + players = GetPlayers(); + for ( i = 0 ; i < players.size ; i++ ) + { + if ( !isalive(players[i]) ) + continue; + + vehicle = players[i] GetVehicleOccupied(); + if ( isdefined( vehicle) && self == vehicle && players[i] player_is_driver() ) + { + if (damage>0) + { + // ^^^ earthquake() will generate an SRE if scale <= 0 + earthquake( damage/400, 1.0, players[i].origin, 512, players[i] ); + } + + if ( damage > 100.0 ) + { +/# + println( "Playing heavy rumble." ); +#/ + players[i] PlayRumbleOnEntity( "tank_damage_heavy_mp" ); + } + else if ( damage > 10.0 ) + { +/# + println( "Playing light rumble." ); +#/ + players[i] PlayRumbleOnEntity( "tank_damage_light_mp" ); + } + } + } + + update_damage_effects(self, attacker); + } +} + + +// ===================================================================================== +// Burnt-Out Husk Code + +function _spawn_husk( origin, angles, modelname ) +{ + husk = spawn( "script_model", origin ); + husk.angles = angles; + husk SetModel( modelname ); + + husk.health = 1; + husk SetCanDamage( false ); ///< Does this really work? It doesn't for players, but might for other entities + + return husk; +} + + +function is_vehicle() +{ + // Could check classname=="script_vehicle", but this is a little more general purpose, I think + return isdefined( self.vehicletype ); +} + + +function swap_to_husk_model() +{ + if ( isdefined( self.vehicletype ) ) + { + husk_model = level.veh_husk_models[ self.vehicletype ]; + + if ( isdefined( husk_model ) ) + { + self SetModel( husk_model ); + } + } +} + + +function _init_husk( vehicle_name, respawn_parameters ) +{ + self swap_to_husk_model(); + + if ( isdefined( level.vehicles_husk_effects ) ) + { + effects = level.vehicles_husk_effects[ vehicle_name ]; + self play_vehicle_effects( effects ); + } + + self.respawn_parameters = respawn_parameters; + + + forcePointVariance = GetDvarint( "scr_veh_explosion_husk_forcepointvariance" ); + horzVelocityVariance = GetDvarint( "scr_veh_explosion_husk_horzvelocityvariance" ); + vertVelocityMin = GetDvarint( "scr_veh_explosion_husk_vertvelocitymin" ); + vertVelocityMax = GetDvarint( "scr_veh_explosion_husk_vertvelocitymax" ); + + + forcePointX = RandomFloatRange( 0-forcePointVariance, forcePointVariance ); + forcePointY = RandomFloatRange( 0-forcePointVariance, forcePointVariance ); + forcePoint = ( forcePointX, forcePointY, 0 ); + + forcePoint += self.origin; + + initialVelocityX = RandomFloatRange( 0-horzVelocityVariance, horzVelocityVariance ); + initialVelocityY = RandomFloatRange( 0-horzVelocityVariance, horzVelocityVariance ); + initialVelocityZ = RandomFloatRange( vertVelocityMin, vertVelocityMax ); + initialVelocity = ( initialVelocityX, initialVelocityY, initialVelocityZ ); + + + if ( self is_vehicle() ) + { + self LaunchVehicle( initialVelocity, forcePoint ); + } + else + { + self PhysicsLaunch( forcePoint, initialVelocity ); + } + + + self thread husk_cleanup_t(); + + /# + self thread cleanup_debug_print_t(); + #/ +} + + +function husk_cleanup_t() +{ + self endon( "death" ); // ent Delete() actually sends the "death" message!!! + self endon( "delete" ); + self endon( "hidden_permanently" ); + + + respawn_parameters = self.respawn_parameters; + + + self do_dead_cleanup_wait( "Husk Cleanup Test" ); + + self wait_for_unnoticeable_cleanup_opportunity(); + + + self thread final_husk_cleanup_t( respawn_parameters ); // break off new thread to avoid end-ons +} + + +function final_husk_cleanup_t( respawn_parameters ) +{ + self husk_do_cleanup(); // causes endons, which is why we broke this off into a new thread + + if ( GetDvarint( "scr_veh_respawnafterhuskcleanup" ) != 0 ) + { + if ( GetDvarint( "scr_veh_disablerespawn" ) == 0 ) //The Vehicle Mayhem gamemode handles spawning the vehicles, so it does not need to respawn here + { + respawn_vehicle( respawn_parameters ); + } + } +} + + +// Returns true only if the entity is actually deleted, rather than just hidden. +function husk_do_cleanup() +{ + // Don't ever let vehicles just blink out. Spawn a VFX + // explosion that doesn't injur any surrounding entities, + // just to mask the blink out, in case players are + // looking in the direction of this vehicle husk. + self _spawn_explosion( self.origin ); + + + if ( self is_vehicle() ) + { + return self remove_vehicle_from_world(); + } + else + { + self _delete_entity(); + return true; + } +} + +// ===================================================================================== + + +// ===================================================================================== +// Explosion Code + +function _spawn_explosion( origin ) +{ + if ( GetDvarint( "scr_veh_explosion_spawnfx" ) == 0 ) + { + return; + } + + if ( isdefined( level.vehicle_explosion_effect ) ) + { + forward = ( 0, 0, 1 ); + + rot = randomfloat( 360 ); + up = ( cos( rot ), sin( rot ), 0 ); + + PlayFX( level.vehicle_explosion_effect, origin, forward, up ); + } + + thread _play_sound_in_space( "vehicle_explo", origin ); +} + + +// NOTE: This function was copied from sab.gsc. Should be centralized somewhere... +function _play_sound_in_space( soundEffectName, origin ) +{ + org = Spawn( "script_origin", origin ); + org.origin = origin; + org PlaySoundWithNotify( soundEffectName, "sounddone" ); + org waittill( "sounddone" ); + org delete(); +} + +// ===================================================================================== + +function vehicle_kill_disconnect_paths_forever() +{ + self notify( "kill_disconnect_paths_forever" ); +} + +function vehicle_disconnect_paths() +{ +/* MPAI_PETER_TODO + self endon( "death" ); + self endon( "kill_disconnect_paths_forever" ); + if ( isdefined( self.script_disconnectpaths ) && !self.script_disconnectpaths ) + { + self.dontDisconnectPaths = true;// lets other parts of the script know not to disconnect script + return; + } + wait( randomfloat( 1 ) ); + while( isdefined( self ) ) + { + if( self getspeed() < 1 ) + { + if ( !isdefined( self.dontDisconnectPaths ) ) + self disconnectpaths(); + self notify( "speed_zero_path_disconnect" ); + while( self getspeed() < 1 ) + wait .05; + } + self connectpaths(); + wait 1; + } + */ +} + +function follow_path( node ) +{ + self endon("death"); + + assert( isdefined( node ), "vehicle_path() called without a path" ); + self notify( "newpath" ); + + // dynamicpaths unique. node isn't defined by info vehicle node calls to this function + if( isdefined( node ) ) + { + self.attachedpath = node; + } + + pathstart = self.attachedpath; + self.currentNode = self.attachedpath; + + if( !isdefined( pathstart ) ) + { + return; + } + + self AttachPath( pathstart ); + self StartPath(); + + self endon( "newpath" ); + + nextpoint = pathstart; + + while ( isdefined( nextpoint ) ) + { + self waittill( "reached_node", nextpoint ); + + self.currentNode = nextpoint; + + // the sweet stuff! Pathpoints handled in script as triggers! + nextpoint notify( "trigger", self ); + + if ( isdefined( nextpoint.script_noteworthy ) ) + { + self notify( nextpoint.script_noteworthy ); + self notify( "noteworthy", nextpoint.script_noteworthy, nextpoint ); + } + + waittillframeend; + } +} + +function InitVehicleMap() +{ + root = "devgui_cmd \"MP/Vehicles/"; + AddDebugCommand( root + "Spawn siegebot\" \"set scr_spawnvehicle 1\"\n"); + AddDebugCommand( root + "Spawn siegebot boss\" \"set scr_spawnvehicle 2\"\n"); + AddDebugCommand( root + "Spawn quadtank\" \"set scr_spawnvehicle 3\"\n"); + AddDebugCommand( root + "Spawn mechtank\" \"set scr_spawnvehicle 4\"\n"); + + thread VehicleMainThread(); +} + +function VehicleMainThread() +{ + if ( level.disableVehicleSpawners === true ) + return; + + //"siegebot"; + //"siegebot_boss"; + //"quadtank"; + //"mechtank"; + + //spawn_nodes = getentarray( "veh_spawn_point", "targetname" ); + spawn_nodes = struct::get_array( "veh_spawn_point", "targetname" ); + + veh_spawner_id = 0; // a unique identifier for gameplay driven fx + for( i = 0; i < spawn_nodes.size; i++ ) + { + spawn_node = spawn_nodes[i]; + + veh_name = spawn_node.script_noteworthy; + time_interval = int(spawn_node.script_parameters); + + if( !isdefined( veh_name ) ) + continue; + + veh_spawner_id++; + thread VehicleSpawnThread( veh_spawner_id, veh_name, spawn_node.origin, spawn_node.angles, time_interval ); + + if ( isdefined( level.vehicle_spawner_init ) ) + level [[ level.vehicle_spawner_init ]] ( veh_spawner_id, veh_name, spawn_node.origin, spawn_node.angles ); + + {wait(.05);}; + } + + if ( isdefined( level.vehicle_spawners_init_finished ) ) + level thread [[ level.vehicle_spawners_init_finished ]](); +} + +function VehicleSpawnThread( veh_spawner_id, veh_name, origin, angles, time_interval ) +{ + level endon( "game_ended" ); + + veh_spawner = GetEnt( veh_name + "_spawner", "targetname" ); + kill_trigger = spawn( "trigger_radius", origin, 0, 60, 180 ); + + /# + level thread test_vehicle_spawn_fx( veh_name, origin, angles ); + original_time_interval = time_interval; + #/ + + while( 1 ) + { + vehicle = veh_spawner SpawnFromSpawner( veh_name, true, true, true ); + if( !isdefined( vehicle ) ) + { + wait RandomFloatRange( 1.0, 2.0 ); + continue; + } + + if( isdefined( vehicle.archetype ) ) + vehicle ASMRequestSubstate( "locomotion@movement" ); + + {wait(.05);}; + + vehicle.origin = origin; + vehicle.angles = angles; + vehicle.veh_spawner_id = veh_spawner_id; + + + vehicle thread VehicleTeamThread(); + + /# + level thread debug_vehicle_destroy_think( vehicle ); + #/ + + vehicle waittill( "death" ); + vehicle vehicle_death::DeleteWhenSafe( 0.25 ); + + if ( isdefined( level.vehicle_destroyed ) ) + level thread [[ level.vehicle_destroyed ]]( veh_spawner_id ); + + /# + time_interval = original_time_interval; + if ( GetDvarFloat( "scr_vehicle_spawn_time", 0.0 ) != 0.0 ) + { + time_interval = GetDvarFloat( "scr_vehicle_spawn_time", 0.0 ); + if ( time_interval < 5.1 ) + time_interval = 5.1; + } + #/ + + if( isdefined( time_interval ) ) + { + level thread PerformVehiclePreSpawn( veh_spawner_id, veh_name, origin, angles, time_interval, kill_trigger ); + wait time_interval; + } + } +} + +function PerformVehiclePreSpawn( veh_spawner_id, veh_name, origin, angles, spawn_delay, kill_trigger ) +{ + fx_prespawn_time = 5.0; + fx_spawn_delay = spawn_delay - fx_prespawn_time; + + wait fx_spawn_delay; + + if ( isdefined( level.vehicle_about_to_spawn ) ) + { + level thread [[ level.vehicle_about_to_spawn ]]( veh_spawner_id, veh_name, origin, angles, fx_prespawn_time ); + } + + kill_overlap_time = 0.1; + + wait_before_kill = fx_prespawn_time - kill_overlap_time; + wait wait_before_kill; + + kill_duration_ms = kill_overlap_time * 2 * 1000; + level thread kill_any_touching( kill_trigger, kill_duration_ms ); + + wait kill_overlap_time; +} + +function kill_any_touching( kill_trigger, kill_duration_ms ) +{ + kill_expire_time_ms = GetTime() + kill_duration_ms; + + kill_weapon = GetWeapon( "hero_minigun" ); // we like this for the gib settings + + while ( GetTime() <= kill_expire_time_ms ) + { + foreach( player in level.players ) + { + if ( !isdefined( player ) ) + continue; + + if ( player IsTouching( kill_trigger ) ) + { + if ( player IsInVehicle() ) + { + vehicle = player GetVehicleOccupied(); + if ( isdefined( vehicle ) && ( vehicle.is_oob_kill_target === true ) ) // intentionally piggy-backed on is_oob_kill_target + { + destroy_vehicle( vehicle ); + continue; + } + } + player DoDamage( player.health + 1, player.origin, kill_trigger, kill_trigger, "none", "MOD_SUICIDE", 0, kill_weapon ); + } + } + + potential_victims = GetAIArray(); + + if ( isdefined( potential_victims ) ) + { + foreach( entity in potential_victims ) + { + if ( !isdefined( entity ) ) + continue; + + if ( !entity IsTouching( kill_trigger ) ) + continue; + + if ( isdefined( entity.health ) && entity.health <= 0 ) + continue; + + if ( IsVehicle( entity ) ) + destroy_vehicle( entity ); + } + } + + {wait(.05);}; + } +} + +function destroy_vehicle( vehicle ) +{ + // need to do this to destroy vehicles + + vehicle DoDamage( vehicle.health + 10000, vehicle.origin, undefined, undefined, "none", "MOD_TRIGGER_HURT" ); // must be MOD_TRIGGER_HURT +} + +/# +function test_vehicle_spawn_fx( veh_name, origin, angles ) +{ + fx_prespawn_time = 5.0; + + while( 1 ) + { + if ( GetDvarInt( "scr_vehicle_spawn_fx_debug", 0 ) == 0 ) + { + wait 1.0; + } + else + { + + if ( isdefined( level.vehicle_about_to_spawn ) ) + { + level thread [[ level.vehicle_about_to_spawn ]]( veh_name, origin, angles, fx_prespawn_time ); + } + wait 6.0; + } + } +} +#/ + +/# +function debug_vehicle_destroy_think( vehicle ) +{ + vehicle endon( "death" ); + + SetDvar( "scr_kill_vehicle", 0 ); + + while( 1 ) + { + if ( GetDvarInt( "scr_kill_vehicle" ) != 0 ) + destroy_vehicle( vehicle ); + + wait 1.0; + } +} +#/ + +function VehicleTeamThread() +{ + vehicle = self; + vehicle endon( "death" ); + + vehicle MakeVehicleUsable(); + if( Target_isTarget( vehicle ) ) + Target_Remove( vehicle ); + + vehicle.noJumping = true; + vehicle.forceDamageFeedback = true; + vehicle.vehkilloccupantsondeath = true; + vehicle DisableAimAssist(); + + while( 1 ) + { + vehicle setteam( "neutral" ); + vehicle.ignoreme = true; + //vehicle SetHighDetail( false ); + vehicle clientfield::set( "toggle_lights", 1 ); + if( Target_isTarget( vehicle ) ) + Target_Remove( vehicle ); + + + vehicle waittill( "enter_vehicle", player ); + player ClearAndCachePerks(); + vehicle setteam( player.team ); + vehicle.ignoreme = false; + //vehicle SetHighDetail( true ); + vehicle clientfield::set( "toggle_lights", 0 ); + vehicle spawning::create_entity_enemy_influencer( "small_vehicle", player.team ); + player spawning::enable_influencers( false ); + if( !Target_isTarget( vehicle ) ) + { + if( isdefined( vehicle.targetOffset ) ) + Target_Set( vehicle, vehicle.targetOffset ); + else + Target_Set( vehicle, ( 0, 0, 0 ) ); + } + + vehicle thread WatchPlayerExitRequestThread( player ); + + vehicle waittill( "exit_vehicle", player ); + + if( isdefined( player ) ) + { + player SetCachedPerks(); + player spawning::enable_influencers( true ); + } + + vehicle spawning::remove_influencers(); + } +} + +function WatchPlayerExitRequestThread( player ) +{ + level endon( "game_ended" ); + player endon ( "death" ); + player endon( "disconnect" ); + + vehicle = self; + + vehicle endon( "death" ); + + wait 1.5; + + while( true ) + { + timeUsed = 0; + while( player UseButtonPressed() ) + { + timeUsed += 0.05; + if( timeUsed > 0.25 ) + { + player unlink(); + return; + } + {wait(.05);}; + } + {wait(.05);}; + } +} + +// We will cache of the perks on the player. If the player dies, cache is lost but respawned +function ClearAndCachePerks() +{ + self.perks_before_vehicle = self GetPerks(); + self ClearPerks(); +} + +function SetCachedPerks() +{ + assert( IsDefined( self.perks_before_vehicle ) ); + foreach( perk in self.perks_before_vehicle ) + { + self SetPerk( perk ); + } +} + + + diff --git a/mp/_waterfall.csc b/mp/_waterfall.csc new file mode 100644 index 0000000..5b19536 --- /dev/null +++ b/mp/_waterfall.csc @@ -0,0 +1,191 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\filter_shared; +#using scripts\shared\postfx_shared; +#using scripts\shared\trigger_shared; +#using scripts\shared\util_shared; +#using scripts\shared\water_surface; + +#using scripts\mp\_load; +#using scripts\mp\_util; + + + +#namespace waterfall; + +function waterfallOverlay( localClientNum ) +{ + triggers = GetEntArray( localClientNum, "waterfall", "targetname" ); + foreach( trigger in triggers ) + { + trigger thread setupWaterfall( localClientNum ); + } +} + +function waterfallMistOverlay( localClientNum ) +{ + triggers = GetEntArray( localClientNum, "waterfall_mist", "targetname" ); + foreach( trigger in triggers ) + { + trigger thread setupWaterfallMist( localClientNum ); + } +} + +function waterfallMistOverlayReset( localClientNum ) +{ + localPlayer = GetLocalPlayer( localClientNum ); + localPlayer.rainOpacity = 0.0; +} + +function setupWaterfallMist( localClientNum ) +{ + level notify( "setupWaterfallmist_waterfall_csc" + localclientnum ); + level endon ( "setupWaterfallmist_waterfall_csc" + localclientnum ); + + trigger = self; + for(;;) + { + trigger waittill( "trigger", trigPlayer ); + + if ( !trigPlayer islocalplayer() ) + { + continue; + } + + localclientnum = trigPlayer getlocalclientnumber(); + if ( isdefined( localclientnum ) ) + { + localplayer = getlocalplayer( localclientnum ); + } + else + { + localplayer = trigPlayer; + } + + filter::init_filter_sprite_rain( localplayer ); + trigger thread trigger::function_thread( localplayer, &trig_enter_waterfall_mist, &trig_leave_waterfall_mist ); + } +} + +function setupWaterfall( localClientNum, localowner ) +{ + level notify( "setupWaterfall_waterfall_csc" + localclientnum ); + level endon ( "setupWaterfall_waterfall_csc" + localclientnum ); + + trigger = self; + for(;;) + { + trigger waittill( "trigger", trigPlayer ); + + if ( !trigPlayer islocalplayer() ) + { + continue; + } + + localclientnum = trigPlayer getlocalclientnumber(); + if ( isdefined( localclientnum ) ) + { + localplayer = getlocalplayer( localclientnum ); + } + else + { + localplayer = trigPlayer; + } + + trigger thread trigger::function_thread( localplayer, &trig_enter_waterfall, &trig_leave_waterfall ); + } +} + +function trig_enter_waterfall( localplayer ) +{ + trigger = self; + localclientnum = localplayer.localclientnum; + localplayer thread postfx::playPostfxBundle( "pstfx_waterfall" ); + + playsound(0, "amb_waterfall_hit", (0,0,0)); + + while ( trigger istouching( localplayer ) ) + { + localplayer PlayRumbleOnEntity( localClientNum, "waterfall_rumble" ); + wait( 0.1 ); + } +} + +function trig_leave_waterfall( localplayer ) +{ + trigger = self; + localClientNum = localplayer.localClientNum; + localplayer postfx::StopPostfxBundle(); + if ( IsUnderwater( localClientNum ) == false ) + { + localplayer thread water_surface::startWaterSheeting(); + } +} + +function trig_enter_waterfall_mist( localPlayer ) +{ + localPlayer endon( "entityshutdown" ); + trigger = self; + + if ( !isdefined( localPlayer.rainOpacity ) ) + localPlayer.rainOpacity = 0; + + if ( localPlayer.rainOpacity == 0 ) + { + filter::set_filter_sprite_rain_seed_offset( localPlayer, 0, RandomFloat( 1 ) ); + } + + filter::enable_filter_sprite_rain( localPlayer, 0 ); + while ( trigger istouching( localPlayer ) ) + { + localClientNum = trigger.localClientNum; + if ( !isdefined( localClientNum ) ) + { + localClientNum = localPlayer getlocalclientnumber(); + } + if ( IsUnderwater( localClientNum ) ) + { + filter::disable_filter_sprite_rain( localPlayer, 0 ); + break; + } + + localPlayer.rainOpacity += 0.003; + if ( localPlayer.rainOpacity > 1 ) + { + localPlayer.rainOpacity = 1; + } + filter::set_filter_sprite_rain_opacity( localPlayer, 0, localPlayer.rainOpacity ); + filter::set_filter_sprite_rain_elapsed( localPlayer, 0, localPlayer getClientTime() ); + + {wait(.016);}; + } + +} + +function trig_leave_waterfall_mist( localPlayer ) +{ + localPlayer endon( "entityshutdown" ); + trigger = self; + + if ( isdefined( localPlayer.rainOpacity ) ) + { + while ( !( trigger istouching( localPlayer ) ) && localPlayer.rainOpacity > 0.0 ) + { + localClientNum = trigger.localClientNum; + if ( IsUnderwater( localClientNum ) ) + { + filter::disable_filter_sprite_rain( localPlayer, 0 ); + break; + } + + localPlayer.rainOpacity -= 0.005; + filter::set_filter_sprite_rain_opacity( localPlayer, 0, localPlayer.rainOpacity ); + filter::set_filter_sprite_rain_elapsed( localPlayer, 0, localPlayer getClientTime() ); + {wait(.016);}; + } + } + + localPlayer.rainOpacity = 0; + filter::disable_filter_sprite_rain( localPlayer, 0 ); +} diff --git a/mp/bots/_bot.gsc b/mp/bots/_bot.gsc new file mode 100644 index 0000000..7093200 --- /dev/null +++ b/mp/bots/_bot.gsc @@ -0,0 +1,954 @@ +#using scripts\codescripts\struct; +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons_shared; +#using scripts\shared\weapons\_weapons; + + + + + +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\shared\bots\bot_traversals; +#using scripts\shared\bots\bot_buttons; +#using scripts\mp\bots\_bot_ball; +#using scripts\mp\bots\_bot_clean; +#using scripts\mp\bots\_bot_combat; +#using scripts\mp\bots\_bot_conf; +#using scripts\mp\bots\_bot_ctf; +#using scripts\mp\bots\_bot_dem; +#using scripts\mp\bots\_bot_dom; +#using scripts\mp\bots\_bot_escort; +#using scripts\mp\bots\_bot_hq; +#using scripts\mp\bots\_bot_koth; +#using scripts\mp\bots\_bot_loadout; +#using scripts\mp\bots\_bot_sd; + +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_uav; +#using scripts\mp\killstreaks\_satellite; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\teams\_teams; +#using scripts\mp\_util; + + + + + + + + +#namespace bot; + +function autoexec __init__sytem__() { system::register("bot_mp",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); + + level.getBotSettings = &get_bot_settings; + + level.onBotConnect = &on_bot_connect; + level.onBotSpawned = &on_bot_spawned; + level.onBotKilled = &on_bot_killed; + + level.botIdle = &bot_idle; + + level.botThreatLost = &bot_combat::chase_threat; + + level.botPreCombat = &bot_combat::mp_pre_combat; + level.botCombat = &bot_combat::combat_think; + level.botPostCombat = &bot_combat::mp_post_combat; + + level.botIgnoreThreat = &bot_combat::bot_ignore_threat; + + level.enemyEmpActive = &emp::EnemyEmpActive; + +/# + level.botDevguiCmd = &bot_devgui_cmd; + level thread system_devgui_gadget_think(); +#/ +} + +function init() +{ + level endon( "game_ended" ); + + level.botSoak = is_bot_soak(); + + if ( ( level.rankedMatch && !level.botSoak ) || !init_bot_gametype() ) + { + return; + } + + wait_for_host(); + + level thread populate_bots(); +} + +// Init Utils +//======================================== + +function is_bot_soak() +{ +/# + return GetDvarInt( "sv_botsoak", 0 ); +#/ + return IsDedicated() && GetDvarInt( "sv_botsoak", 0 ); +} + +function wait_for_host() +{ + level endon( "game_ended" ); + + if ( level.botSoak ) + { + return; + } + + host = util::getHostPlayerForBots(); + + while ( !isdefined( host ) ) + { + wait( 0.25 ); + host = util::getHostPlayerForBots(); + } +} + +function get_host_team() +{ + host = util::getHostPlayerForBots(); + + if ( !isdefined( host ) || host.team == "spectator" ) + { + return "allies"; + } + + return host.team; +} + +function is_bot_comp_stomp() +{ + return false; +} + + +// Bot Events +//======================================== + +function on_bot_connect() +{ + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( ( isdefined( level.disableClassSelection ) && level.disableClassSelection ) ) + { + self set_rank(); + + // Doesn't work if we don't do it in this order + self bot_loadout::pick_hero_gadget(); + self bot_loadout::pick_killstreaks(); + + return; + } + + if ( !( isdefined( self.pers["bot_loadout"] ) && self.pers["bot_loadout"] ) ) + { + self set_rank(); + + // Doesn't work if we don't do it in this order + self bot_loadout::build_classes(); + self bot_loadout::pick_hero_gadget(); + self bot_loadout::pick_killstreaks(); + + self.pers["bot_loadout"] = true; + } + + self bot_loadout::pick_classes(); + self choose_class(); +} + +function on_bot_spawned() +{ + self.bot.goalTag = undefined; +/# + weapon = undefined; + + if ( GetDvarInt( "scr_botsHasPlayerWeapon" ) != 0 ) + { + player = util::getHostPlayer(); + weapon = player GetCurrentWeapon(); + } + + if ( GetDvarString( "devgui_bot_weapon", "" ) != "" ) + { + weapon = GetWeapon( GetDvarString( "devgui_bot_weapon" ) ); + } + + if ( isdefined( weapon ) && level.weaponNone != weapon ) + { + self weapons::detach_all_weapons(); + self TakeAllWeapons(); + self GiveWeapon( weapon ); + self SwitchToWeapon( weapon ); + self SetSpawnWeapon( weapon ); + + self teams::set_player_model( self.team, weapon ); + } +#/ +} + +function on_bot_killed() +{ + self endon("disconnect"); + level endon( "game_ended" ); + self endon( "spawned" ); + self waittill ( "death_delay_finished" ); + + wait 0.1; + + if ( self choose_class() && level.playerForceRespawn ) + { + return; + } + + self thread respawn(); +} + +function respawn() +{ + self endon( "spawned" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + while( 1 ) + { + self bot::tap_use_button(); + + wait 0.1; + } +} + +function bot_idle() +{ + if ( self do_supplydrop() ) + { + return; + } + + // TODO: Look for an enemy radar blip + // TODO: Get points on navmesh and feed into the spawn system to see if an enemy is likely to spawn there + self bot::navmesh_wander(); + self bot::sprint_to_goal(); +} + +// Crate maxs: 23.1482 + + // Wild guess on usable radius + +function do_supplydrop( maxRange = 1400 ) // A little under minimap width +{ + crates = GetEntArray( "care_package", "script_noteworthy" ); + + maxRangeSq = maxRange * maxRange; + + useRadiusSq = 62 * 62; + + closestCrate = undefined; + closestCrateDistSq = undefined; + + foreach( crate in crates ) + { + if ( !crate IsOnGround() ) + { + continue; + } + + crateDistSq = Distance2DSquared( self.origin, crate.origin ); + + if ( crateDistSq > maxRangeSq ) + { + continue; + } + + inUse = isdefined( crate.useEnt ) && ( isdefined( crate.useEnt.inUse ) && crate.useEnt.inUse ); + + if ( crateDistSq <= useRadiusSq ) + { + if ( inUse && !self useButtonPressed() ) + { + continue; + } + + self bot::press_use_button(); + return true; + } + + if ( !self has_minimap() && !self BotSightTracePassed( crate ) ) + { + continue; + } + + if ( !isdefined( closestCrate ) || crateDistSq < closestCrateDistSq ) + { + closestCrate = crate; + closestCrateDistSq = crateDistSq; + } + } + + if ( isdefined( closestCrate ) ) + { + randomAngle = ( 0, RandomInt( 360 ), 0 ); + randomVec = AnglesToForward( randomAngle ); + + point = closestCrate.origin + randomVec * 39; + + if ( self BotSetGoal( point ) ) + { + self thread watch_crate( closestCrate ); + return true; + } + } + + return false; +} + +function watch_crate( crate ) +{ + self endon( "death" ); + self endon( "bot_goal_reached" ); + level endon( "game_ended" ); + + while ( isdefined( crate ) && !self bot_combat::has_threat() ) + { + wait level.botSettings.thinkInterval; + } + + self BotSetGoal( self.origin ); +} + +// Bot Team Population +//======================================== + +function populate_bots() +{ + level endon( "game_ended" ); + + if ( level.teambased ) + { + maxAllies = GetDvarInt( "bot_maxAllies", 0 ); + maxAxis = GetDvarInt( "bot_maxAxis", 0 ); + + level thread monitor_bot_team_population( maxAllies, maxAxis ); + } + else + { + maxFree = GetDvarInt( "bot_maxFree", 0 ); + + level thread monitor_bot_population( maxFree ); + } +} + +function monitor_bot_team_population( maxAllies, maxAxis ) +{ + level endon( "game_ended" ); + + if ( !maxAllies && !maxAxis ) + { + return; + } + + fill_balanced_teams( maxAllies, maxAxis ); + + while ( 1 ) + { + wait 3; + + // TODO: Get a player count that includes 'CON_CONNECTING' players + allies = GetPlayers( "allies" ); + axis = GetPlayers( "axis" ); + + if ( allies.size > maxAllies && + remove_best_bot( allies ) ) + { + continue; + } + + if ( axis.size > maxAxis && + remove_best_bot( axis ) ) + { + continue; + } + + if ( allies.size < maxAllies || axis.size < maxAxis ) + { + add_balanced_bot( allies, maxAllies, axis, maxAxis ); + } + } +} + +function fill_balanced_teams( maxAllies, maxAxis ) +{ + allies = GetPlayers( "allies" ); + axis = GetPlayers( "axis" ); + + while ( ( allies.size < maxAllies || axis.size < maxAxis ) && + add_balanced_bot( allies, maxAllies, axis, maxAxis ) ) + { + {wait(.05);}; + + allies = GetPlayers( "allies" ); + axis = GetPlayers( "axis" ); + } +} + +function add_balanced_bot( allies, maxAllies, axis, maxAxis ) +{ + bot = undefined; + + if ( allies.size < maxAllies && + ( allies.size <= axis.size || axis.size >= maxAxis ) ) + { + bot = add_bot( "allies" ); + } + else if ( axis.size < maxAxis ) + { + bot = add_bot( "axis" ); + } + + return isdefined( bot ); +} + +function monitor_bot_population( maxFree ) +{ + level endon( "game_ended" ); + + if ( !maxFree ) + { + return; + } + + // Initial Fill + players = GetPlayers( ); + while ( players.size < maxFree ) + { + add_bot(); + {wait(.05);}; + players = GetPlayers( ); + } + + while ( 1 ) + { + wait 3; + + // TODO: Get a player count that includes 'CON_CONNECTING' players + players = GetPlayers( ); + + if ( players.size < maxFree ) + { + add_bot(); + } + else if ( players.size > maxFree ) + { + remove_best_bot( players ); + } + } +} + +function remove_best_bot( players ) +{ + bots = filter_bots( players ); + + if ( !bots.size ) + { + return false; + } + + // Prefer non-combat bots + bestBots = []; + + foreach( bot in bots ) + { + // Don't kick bots in the process of connecting + if ( bot.sessionstate == "spectator" ) + { + continue; + } + + if ( bot.sessionstate == "dead" || !bot bot_combat::has_threat() ) + { + bestBots[bestBots.size] = bot; + } + } + + if ( bestBots.size ) + { + remove_bot( bestBots[RandomInt( bestBots.size )] ); + } + else + { + remove_bot( bots[RandomInt( bots.size )] ); + } + + return true; +} + +// Bot Loadouts +//======================================== + +function choose_class() +{ + if ( ( isdefined( level.disableClassSelection ) && level.disableClassSelection ) ) + { + return false; + } + + currClass = self bot_loadout::get_current_class(); + + if ( !isdefined( currClass ) || RandomInt( 100 ) < (isdefined(level.botSettings.changeClassWeight)?level.botSettings.changeClassWeight:0) ) + { + classIndex = RandomInt( self.loadoutClasses.size ); + className = self.loadoutClasses[classIndex].name; + } + + if ( !isdefined(className) || className === currClass ) + { + return false; + } + + self notify( "menuresponse", "ChooseClass_InGame", className ); + + return true; +} + +// Killstreaks +//======================================== + +function use_killstreak() +{ + if ( !level.loadoutKillstreaksEnabled || + self emp::EnemyEMPActive() ) + { + return; + } + + weapons = self GetWeaponsList(); + inventoryWeapon = self GetInventoryWeapon(); + + foreach( weapon in weapons ) + { + killstreak = killstreaks::get_killstreak_for_weapon( weapon ); + + if ( !isdefined( killstreak ) ) + { + continue; + } + + if ( weapon != inventoryWeapon && !self GetWeaponAmmoClip( weapon ) ) + { + continue; + } + + if ( self killstreakrules::isKillstreakAllowed( killstreak, self.team ) ) + { + useWeapon = weapon; + break; + } + } + + if ( !isdefined( useWeapon ) ) + { + return; + } + + killstreak_ref = killstreaks::get_menu_name( killstreak ); + + switch( killstreak_ref ) + { + case "killstreak_uav": + case "killstreak_counteruav": + case "killstreak_satellite": + case "killstreak_helicopter_player_gunner": + case "killstreak_raps": + case "killstreak_sentinel": + self SwitchToWeapon( useWeapon ); + break; + } +} + + +function has_radar() +{ + if ( level.teambased ) + { + return ( uav::HasUAV( self.team ) || satellite::HasSatellite( self.team ) ); + } + + return ( uav::HasUAV( self.entnum ) || satellite::HasSatellite( self.entnum ) ); +} + +function has_minimap() +{ + if ( self IsEmpJammed() ) + { + return false; + } + + if ( ( isdefined( level.hardcoreMode ) && level.hardcoreMode ) ) + { + return self has_radar(); + } + + return true; +} + +function get_enemies( on_radar ) +{ + if ( !isdefined( on_radar ) ) + { + on_radar = false; + } + + enemies = self GetEnemies(); + +/# + for ( i = 0; i < enemies.size; i++ ) + { + if ( isplayer( enemies[i] ) && enemies[i] IsInMoveMode( "ufo", "noclip" ) ) + { + ArrayRemoveIndex( enemies, i ); + i--; + } + } +#/ + + if ( on_radar && !self has_radar() ) + { + for ( i = 0; i < enemies.size; i++ ) + { + if ( !isdefined( enemies[i].lastFireTime ) ) + { + ArrayRemoveIndex( enemies, i ); + i--; + } + else if ( GetTime() - enemies[i].lastFireTime > 2000 ) + { + ArrayRemoveIndex( enemies, i ); + i--; + } + } + } + + return enemies; +} + +function set_rank() +{ + players = GetPlayers(); + + ranks = []; + bot_ranks = []; + human_ranks = []; + + for ( i = 0; i < players.size; i++ ) + { + if ( players[i] == self ) + continue; + + if ( isdefined( players[i].pers[ "rank" ] ) ) + { + if ( players[i] util::is_bot() ) + { + bot_ranks[ bot_ranks.size ] = players[i].pers[ "rank" ]; + } + else + { + human_ranks[ human_ranks.size ] = players[i].pers[ "rank" ]; + } + } + } + + if( !human_ranks.size ) + human_ranks[ human_ranks.size ] = 10; + + human_avg = math::array_average( human_ranks ); + + while ( bot_ranks.size + human_ranks.size < 5 ) + { + // add some random ranks for better random number distribution + r = human_avg + RandomIntRange( -5, 5 ); + rank = math::clamp( r, 0, level.maxRank ); + human_ranks[ human_ranks.size ] = rank; + } + + ranks = ArrayCombine( human_ranks, bot_ranks, true, false ); + + avg = math::array_average( ranks ); + s = math::array_std_deviation( ranks, avg ); + + rank = Int( math::random_normal_distribution( avg, s, 0, level.maxRank ) ); + + while ( !isdefined( self.pers["codpoints"] ) ) + { + wait 0.1; + } + + self.pers[ "rank" ] = rank; + self.pers[ "rankxp" ] = rank::getRankInfoMinXP( rank ); + + self setRank( rank ); + self rank::syncXPStat(); +} + +function init_bot_gametype() +{ + switch( level.gameType ) + { + case "ball": + bot_ball::init(); + return true; + case "conf": + bot_conf::init(); + return true; + case "ctf": + bot_ctf::init(); + return true; + case "dem": + bot_dem::init(); + return true; + case "dm": + return true; + case "dom": + bot_dom::init(); + return true; + case "escort": + bot_escort::init(); + return true; + case "infect": + return true; + case "gun": + return true; + case "koth": + bot_koth::init(); + return true; + case "sd": + bot_sd::init(); + return true; + case "clean": + bot_clean::init(); + return true; + case "tdm": + return true; + } + + return false; +} + +function get_bot_settings() +{ + switch ( GetDvarInt( "bot_difficulty", 1 ) ) + { + case 0: + bundleName = "bot_mp_easy"; + break; + + case 1: + bundleName = "bot_mp_normal"; + break; + case 2: + bundleName = "bot_mp_hard"; + break; + case 3: + default: + bundleName = "bot_mp_veteran"; + break; + } + + return struct::get_script_bundle( "botsettings", bundleName ); +} + +function friend_goal_in_radius( goal_name, origin, radius ) +{ + return 0; +} + +function friend_in_radius( goal_name, origin, radius ) +{ + return false; +} + +function get_friends() +{ + return []; +} + +function get_closest_enemy( origin, someFlag ) +{ + return undefined; +} + +function bot_vehicle_weapon_ammo( weaponName ) +{ + return false; +} + +function navmesh_points_visible( origin, point ) +{ + return false; +} + +function dive_to_prone( exit_stance ) +{ + +} + +/# + +// Devgui +//======================================== + +function bot_devgui_cmd( cmd ) +{ + cmdTokens = strtok( cmd," "); + + if ( cmdTokens.size == 0 ) + { + return false; + } + + host = util::getHostPlayerForBots(); + team = get_host_team(); + + switch( cmdTokens[0] ) + { + case "spawn_enemy": + team = util::getotherteam(team); + case "spawn_friendly": + count = 1; + if ( cmdTokens.size > 1 ) + { + count = int( cmdTokens[1] ); + } + for( i = 0; i < count; i++ ) + { + add_bot( team ); + } + return true; + case "remove_enemy": + team = util::getotherteam(team); + case "remove_friendly": + remove_bots( undefined, team ); + return true; + case "fixed_spawn_enemy": + team = util::getotherteam(team); + case "fixed_spawn_friendly": + bot = add_bot_at_eye_trace( team ); + if ( isdefined( bot ) ) + { + bot thread fixed_spawn_override(); + } + return true; + + case "player_weapon": + players = GetPlayers(); + foreach( player in players ) + { + if ( !player util::is_bot() ) + { + continue; + } + + weapon = host GetCurrentWeapon(); + + player weapons::detach_all_weapons(); + player TakeAllWeapons(); + player GiveWeapon( weapon ); + player SwitchToWeapon( weapon ); + player SetSpawnWeapon( weapon ); + + player teams::set_player_model( player.team, weapon ); + } + return true; + } + + return false; +} + +function system_devgui_gadget_think() +{ + SetDvar( "devgui_bot_gadget", "" ); + + for ( ;; ) + { + wait( 1 ); + + gadget = GetDvarString( "devgui_bot_gadget" ); + + if ( gadget != "" ) + { + bot_turn_on_gadget( GetWeapon(gadget) ); + SetDvar( "devgui_bot_gadget", "" ); + } + } +} + +function bot_turn_on_gadget( gadget ) +{ + players = GetPlayers(); + + foreach( player in players ) + { + if ( !player util::is_bot() ) + { + continue; + } + + host = util::getHostPlayer(); + weapon = host GetCurrentWeapon(); + + if ( !isdefined( weapon ) || weapon == level.weaponNone || weapon == level.weaponNull ) + { + weapon = GetWeapon( "smg_standard" ); + } + + player weapons::detach_all_weapons(); + player TakeAllWeapons(); + player GiveWeapon( weapon ); + player SwitchToWeapon( weapon ); + player SetSpawnWeapon( weapon ); + + player teams::set_player_model( player.team, weapon ); + + player GiveWeapon( gadget ); + slot = player GadgetGetSlot( gadget ); + player GadgetPowerSet( slot, 100.0 ); + player BotPressButtonForGadget( gadget ); + } +} + + + +function fixed_spawn_override() +{ + self endon( "disconnect" ); + + spawnOrigin = self.origin; + spawnAngles = self.angles; + + while( 1 ) + { + self waittill( "spawned_player" ); + + self SetOrigin( spawnOrigin ); + self SetPlayerAngles( spawnAngles ); + } +} + +#/ diff --git a/mp/bots/_bot.gsh b/mp/bots/_bot.gsh new file mode 100644 index 0000000..946bcfc --- /dev/null +++ b/mp/bots/_bot.gsh @@ -0,0 +1,57 @@ +// matches the enum in bot.h + + + + + + + + + + + + + + // 65 degrees + // 85 degrees + // 100 degrees + // 160 degrees + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mp/bots/_bot_ball.gsc b/mp/bots/_bot_ball.gsc new file mode 100644 index 0000000..6c13b97 --- /dev/null +++ b/mp/bots/_bot_ball.gsc @@ -0,0 +1,165 @@ +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + + +#using scripts\mp\bots\_bot; +#using scripts\mp\bots\_bot_combat; +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\shared\bots\bot_traversals; +#using scripts\shared\bots\bot_buttons; + + +#using scripts\mp\teams\_teams; +#using scripts\mp\_util; + +#namespace bot_ball; + +function init() +{ + level.botIdle = &bot_idle; + level.botCombat = &bot_combat; + level.botPreCombat = &bot_pre_combat; +} + +function release_control_on_landing() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while ( self IsOnGround() ) + { + {wait(.05);}; + } + + while ( !self IsOnGround() ) + { + {wait(.05);}; + } + + self BotReleaseManualControl(); +} + +function bot_pre_combat() +{ + if ( isdefined( self.carryObject ) ) + { + if ( self IsOnGround() && self BotGoalSet() ) + { + goal = level.ball_goals[util::getotherteam( self.team )]; + + radius = 300; + radiusSq = radius * radius; + + if ( Distance2DSquared( self.origin, goal.trigger.origin ) <= radiusSq ) + { + if ( self BotSightTrace( goal.trigger ) ) + { + self BotTakeManualControl(); + self thread bot::jump_to( goal.trigger.origin ); + self thread release_control_on_landing(); + + // TODO: Try throwing the ball + + return; + } + } + } + + if ( !self IsMeleeing() ) + { + self bot::use_killstreak(); + } + + return; + } + + self bot_combat::mp_pre_combat(); +} + +function bot_combat() +{ + if ( isdefined( self.carryObject ) ) + { + if ( self bot_combat::has_threat() ) + { + self bot_combat::clear_threat(); + } + + meleeThreat = bot_combat::get_greatest_threat( level.botSettings.meleeRange ); + + if ( isdefined( meleeThreat ) ) + { + angles = self GetPlayerAngles(); + fwd = AnglesToForward( angles ); + + threatDir = meleeThreat.origin - self.origin ; + threatDir = VectorNormalize( threatDir ); + + dot = VectorDot( fwd, threatDir ); + + if ( dot > level.botSettings.meleeDot ) + { + self bot::tap_melee_button(); + } + } + + return; + } + + self bot_combat::combat_think(); +} + +function bot_idle() +{ + if ( isdefined( self.carryObject ) ) + { + if ( !self BotGoalSet() ) + { + // The goal trigger is too far off the navmesh + goal = level.ball_goals[util::getotherteam( self.team )]; + goalPoint = goal.origin - ( 0, 0, 125 ); + self bot::approach_point( goalPoint ); + self bot::sprint_to_goal(); + } + + return; + } + + triggers = []; + balls = array::randomize( level.balls ); + + foreach( ball in balls ) + { + if ( !isdefined( ball.carrier ) && !ball.in_goal ) + { + triggers[triggers.size] = ball.trigger; + } + else if ( isdefined( ball.carrier ) && ball.carrier.team != self.team ) + { + // Don't go straight for the carrier since the objective doesn't update perfectly + self bot::approach_point( ball.carrier.origin, 250, 1000, 128 ); + self bot::sprint_to_goal(); + return; + } + } + + // Go pick up the closest ball + if ( triggers.size > 0 ) + { + triggers = ArraySort( triggers, self.origin ); + + self BotSetGoal( triggers[0].origin ); + self bot::sprint_to_goal(); + + return; + } + + self bot::bot_idle(); +} \ No newline at end of file diff --git a/mp/bots/_bot_clean.gsc b/mp/bots/_bot_clean.gsc new file mode 100644 index 0000000..aef79d6 --- /dev/null +++ b/mp/bots/_bot_clean.gsc @@ -0,0 +1,187 @@ +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + + +#using scripts\mp\bots\_bot; +#using scripts\mp\bots\_bot_combat; +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\shared\bots\bot_traversals; +#using scripts\shared\bots\bot_buttons; + + +#using scripts\mp\teams\_teams; +#using scripts\mp\_util; + +#namespace bot_clean; + + + + + +function init() +{ + level.botPostCombat = &bot_post_combat; + level.botIdle = &bot_idle; + + level.botUpdateThreatGoal = &update_threat_goal; +} + +function bot_post_combat() +{ + // Forget about the current target or hub when entering combat + //if ( self bot_combat::has_threat() ) + //{ + // self.targetTaco = undefined; + // self.targetHub = undefined; + //} + + // Ditch the hub if it's inactive or out of tacos + if ( isdefined( self.targetHub ) ) + { + if ( self.carriedTacos == 0 || self.targetHub.interactTeam == "none" ) + { + self.targetHub = undefined; + self BotSetGoal( self.origin ); + } + } + + // Ditch the taco if it's inactive or recycled + if ( isdefined( self.targetTaco ) ) + { + if ( self.targetTaco.interactTeam == "none" || self.targetTaco.DropTime != self.targetTacoDropTime ) + { + self.targetTaco = undefined; + self BotSetGoal( self.origin ); + } + } + + // Check for nearby tacos + if ( !self bot_combat::has_threat() ) + { + look_for_taco( 1024 ); + } + + self bot_combat::mp_post_combat(); +} + +function bot_idle() +{ + // Go to/stay in the deposit point + if ( isdefined( self.targetHub ) ) + { + self bot::path_to_point_in_trigger( self.targetHub.trigger ); + self bot::sprint_to_goal(); + return; + } + + // Go look for a deposit point + if ( RandomInt( 10 ) < self.carriedTacos ) + { + foreach( hub in level.cleanDepositHubs ) + { + if ( hub.interactTeam == "any" ) + { + self.targetHub = hub; + self.targetTaco = undefined; + self bot::path_to_point_in_trigger( self.targetHub.trigger ); + self bot::sprint_to_goal(); + return; + } + } + } + + if ( look_for_taco( 1024 ) ) + { + return; + } + + self bot::bot_idle(); +} + +function look_for_taco( radius ) +{ + bestTaco = get_best_taco( radius ); + + if ( !isdefined( bestTaco ) ) + { + return false; + } + + self.targetTaco = bestTaco; + self.targetTacoDropTime = bestTaco.dropTime; + + self bot::path_to_point_in_trigger( bestTaco.trigger ); + self bot::sprint_to_goal(); + + return true; +} + +function get_best_taco( radius ) +{ + radiusSq = radius * radius; + + // Look for the closest taco in the nearby radius, or the closest one on the map + bestTaco = undefined; + bestTacoDistSq = undefined; + + foreach ( taco in level.tacos ) + { + if ( taco.interactTeam == "none" || !IsPointOnNavMesh( taco.origin , self ) ) + { + continue; + } + + tacoDistSq = Distance2DSquared( self.origin, taco.origin ); + + if ( taco.attacker != self && tacoDistSq > radiusSq ) + { + continue; + } + + if ( !isdefined( bestTaco ) || tacoDistSq < bestTacoDistSq ) + { + bestTaco = taco; + bestTacoDistSq = tacoDistSq; + } + } + + return bestTaco; +} + +function update_threat_goal() +{ + if ( isdefined( self.targetHub ) ) + { + if ( !self BotGoalSet() ) + { + self bot::path_to_point_in_trigger( self.targetHub.trigger ); + self bot::sprint_to_goal(); + } + return; + } + + radiusSq = 256 * 256; + + if ( isdefined( self.targetTaco ) ) + { + tacoDistSq = Distance2DSquared( self.origin, self.targetTaco.origin ); + if ( tacoDistSq > radiusSq ) + { + self.targetTaco = undefined; + } + } + + if ( isdefined( self.targetTaco ) || self look_for_taco( 1024 ) ) + { + return; + } + + self bot_combat::update_threat_goal(); +} \ No newline at end of file diff --git a/mp/bots/_bot_combat.gsc b/mp/bots/_bot_combat.gsc new file mode 100644 index 0000000..3934be4 --- /dev/null +++ b/mp/bots/_bot_combat.gsc @@ -0,0 +1,242 @@ +#using scripts\shared\array_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons_shared; +#using scripts\shared\weapons\_weapon_utils; +#using scripts\shared\weapons\_weapons; + + + +#using scripts\mp\_util; +#using scripts\mp\bots\_bot; +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\shared\bots\bot_traversals; +#using scripts\shared\killstreaks_shared; + + + + + + + // 128 * 128 + +#namespace bot_combat; + +function bot_ignore_threat( entity ) +{ + if ( threat_requires_launcher( entity ) && !self bot::has_launcher() ) + { + return true; +/* TODO: + eyePos = self GetEye(); + + pitchMax = GetDvarFloat( "player_view_pitch_up" ); + pitchMin = -GetDvarFloat( "player_view_pitch_down" ); + + // Entity is in FOV but outside pitch range + threatAngles = VectorToAngles( entity.origin - eyePos ); + + // TODO: Is threatAngles[0] in 0 to 360 or -180 to 180? + if ( threatAngles[0] > pitchMax || threatAngles[0] < pitchMin ) + { + continue; + } +*/ + } + + return false; +} + +function mp_pre_combat() +{ + self bot_combat::bot_pre_combat(); + + if ( self IsReloading() || + self IsSwitchingWeapons() || + self IsThrowingGrenade() || + self IsMeleeing() || + self IsRemoteControlling() || + self IsInVehicle() || + self IsWeaponViewOnlyLinked() ) + { + return; + } + + if ( self has_threat() ) + { + self threat_switch_weapon(); + return; + } + + if ( self switch_weapon() ) + { + return; + } + + if ( self reload_weapon() ) + { + return; + } + + self bot::use_killstreak(); +} + + +function mp_post_combat() +{ + // Dogtag handling + if( !IsDefined( level.dogtags ) ) + { + return; + } + + if ( isdefined( self.bot.goalTag ) ) + { + if ( !self.bot.goalTag gameobjects::can_interact_with( self ) ) + { + // Cancel the tag + self.bot.goalTag = undefined; + + if ( !self bot_combat::has_threat() && self BotGoalSet() ) + { + self BotSetGoal( self.origin ); + } + } + else if ( !self.bot.goalTagOnGround && + !self bot_combat::has_threat() && + self IsOnGround() && + Distance2DSquared( self.origin , self.bot.goalTag.origin ) < 16384 && + self BotSightTrace( self.bot.goalTag ) ) + { + self thread bot::jump_to( self.bot.goalTag.origin ); + } + } + else if ( !self BotGoalSet() ) + { + closestTag = self get_closest_tag(); + + if ( isdefined( closestTag ) ) + { + // Trigger radius from _dogtags.gsc + self set_goal_tag( closestTag ); + } + } + +} + +function threat_requires_launcher( enemy ) +{ + if ( !isdefined( enemy ) || IsPlayer( enemy ) ) + { + return false; + } + + killstreakType = undefined; + + if ( isdefined( enemy.killstreakType ) ) + { + killstreakType = enemy.killstreakType; + } + else if ( isdefined( enemy.parentStruct ) && isdefined( enemy.parentStruct.killstreakType ) ) + { + killstreakType = enemy.parentStruct.killstreakType; + } + + if ( !isdefined( killstreakType ) ) + { + return false; + } + + switch( killstreakType ) + { + case "uav": + case "counteruav": + case "satellite": + case "helicopter_gunner": + return true; + } + + return false; +} + +function combat_throw_proximity( origin ) +{ +} + +function combat_throw_smoke( origin ) +{ +} + +function combat_throw_lethal( origin ) +{ +} + +function combat_throw_tactical( origin ) +{ +} + +function combat_toss_frag( origin ) +{ +} + +function combat_toss_flash( origin ) +{ +} + +function combat_tactical_insertion( origin ) +{ + return false; +} + +function nearest_node( origin ) +{ + return undefined; +} + +function dot_product( origin ) +{ + return bot::fwd_dot( origin ); +} + +// Dogtags +//======================================== + +function get_closest_tag() +{ + closestTag = undefined; + closestTagDistSq = undefined; + + foreach( tag in level.dogtags ) + { + if ( !tag gameobjects::can_interact_with( self ) ) + { + continue; + } + + distSq = DistanceSquared( self.origin, tag.origin ); + + if ( !isdefined( closestTag ) || distSq < closestTagDistSq ) + { + closestTag = tag; + closestTagDistSq = distSq; + } + } + + return closestTag; +} + +function set_goal_tag( tag ) +{ + self.bot.goalTag = tag; + + traceStart = tag.origin; + traceEnd = tag.origin + (0,0,-64); + trace = BulletTrace( traceStart, traceEnd, false, undefined ); + + self.bot.goalTagOnGround = ( trace["fraction"] < 1 ); + + self bot::path_to_trigger( tag.trigger ); + self bot::sprint_to_goal(); +} \ No newline at end of file diff --git a/mp/bots/_bot_conf.gsc b/mp/bots/_bot_conf.gsc new file mode 100644 index 0000000..a0b1d93 --- /dev/null +++ b/mp/bots/_bot_conf.gsc @@ -0,0 +1,23 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\gameobjects_shared; + + + +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\shared\bots\bot_traversals; +#using scripts\mp\bots\_bot; +#using scripts\mp\bots\_bot_combat; + + + + +#namespace bot_conf; + +function init() +{ + // Handled by the base tag seeking behavior now +} + + diff --git a/mp/bots/_bot_ctf.gsc b/mp/bots/_bot_ctf.gsc new file mode 100644 index 0000000..f111864 --- /dev/null +++ b/mp/bots/_bot_ctf.gsc @@ -0,0 +1,105 @@ +#using scripts\shared\array_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\ctf; + +#using scripts\mp\_util; +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\mp\bots\_bot; +#using scripts\mp\bots\_bot_combat; + + + + + + + + +#namespace bot_ctf; + +function init() +{ + level.onBotConnect = &on_bot_connect; + level.botIdle = &bot_idle; +} + +function on_bot_connect() +{ + foreach( flag in level.flags ) + { + if ( flag gameobjects::get_owner_team() == self.team ) + { + self.bot.flag = flag; + } + else + { + self.bot.enemyFlag = flag; + } + } + + self bot::on_bot_connect(); +} + +function bot_idle() +{ + carrier = self.bot.enemyFlag gameobjects::get_carrier(); + + // Carrying the enemy flag + if ( isdefined( carrier ) && carrier == self ) + { + // Wander around the home flag area until it comes back + if ( self.bot.flag gameobjects::is_object_away_from_home() ) + { + self bot::approach_point( self.bot.flag.flagBase.trigger.origin, 0, 1024 ); + } + // Capture the enemy flag + else + { + self bot::approach_goal_trigger( self.bot.flag.flagBase.trigger ); + } + + self bot::sprint_to_goal(); + return; + } + // Defend home base + else if ( Distance2DSquared( self.origin, self.bot.flag.flagBase.trigger.origin ) < ( 1024 * 1024 ) && + RandomInt( 100 ) < 80 ) + { + self bot::approach_point( self.bot.flag.flagBase.trigger.origin, 0, 1024 ); + self bot::sprint_to_goal(); + return; + } + // Return the friendly flag + else if ( self.bot.flag gameobjects::is_object_away_from_home() ) + { + enemyCarrier = self.bot.flag gameobjects::get_carrier(); + + if ( isdefined( enemyCarrier ) ) + { + // Don't go straight for the carrier since the objective doesn't update perfectly + self bot::approach_point( enemyCarrier.origin, 250, 1000, 128 ); + self bot::sprint_to_goal(); + return; + } + else + { + self BotSetGoal( self.bot.flag.trigger.origin ); + self bot::sprint_to_goal(); + return; + } + } + // Take the enemy flag + else if ( !isdefined( carrier ) ) + { + self bot::approach_goal_trigger( self.bot.enemyFlag.trigger ); + self bot::sprint_to_goal(); + return; + } + + self bot::bot_idle(); +} \ No newline at end of file diff --git a/mp/bots/_bot_dem.gsc b/mp/bots/_bot_dem.gsc new file mode 100644 index 0000000..04cc8c0 --- /dev/null +++ b/mp/bots/_bot_dem.gsc @@ -0,0 +1,148 @@ +#using scripts\shared\array_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + + + +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\shared\bots\bot_buttons; +#using scripts\mp\bots\_bot; +#using scripts\mp\bots\_bot_combat; + + + +#namespace bot_dem; + + + + + +function init() +{ + level.botIdle = &bot_idle; + level.botCombat = &combat_think; + level.botThreatLost = &bot_threat_lost; +} + +function bot_idle() +{ + approachRadiusSq = 750 * 750; + + // Check for Plant / Defuse + foreach( zone in level.bombZones ) + { + if ( ( isdefined( zone.bombExploded ) && zone.bombExploded ) ) + { + continue; + } + + if ( self IsTouching( zone.trigger ) ) + { + if ( self can_plant( zone ) || self can_defuse( zone ) ) + { + self bot::press_use_button(); + return; + } + } + + if ( DistanceSquared( self.origin, zone.trigger.origin ) < approachRadiusSq ) + { + // Defuse / Plant nearby zone + if ( self can_plant( zone ) || self can_defuse( zone ) ) + { + self bot::path_to_trigger( zone.trigger ); + self bot::sprint_to_goal(); + return; + } + } + } + + // Shuffle zones + zones = array::randomize( level.bombZones ); + + // Check for zones to defuse + foreach( zone in zones ) + { + if ( ( isdefined( zone.bombExploded ) && zone.bombExploded ) ) + { + continue; + } + + if ( self can_defuse( zone ) && RandomInt( 100 ) < 70 ) + { + self bot::approach_goal_trigger( zone.trigger, 750 ); + self bot::sprint_to_goal(); + return; + } + } + + // Go to a random zone to plant / guard + foreach( zone in zones ) + { + if ( ( isdefined( zone.bombExploded ) && zone.bombExploded ) ) + { + continue; + } + + // Defend the nearby point + if ( DistanceSquared( self.origin, zone.trigger.origin ) < approachRadiusSq && + RandomInt( 100 ) < 70 ) + { + triggerRadius = self bot::get_trigger_radius( zone.trigger ); + self bot::approach_point( zone.trigger.origin, triggerRadius, 750 ); + self bot::sprint_to_goal(); + return; + } + } + + self bot::bot_idle(); +} + +function can_plant( zone ) +{ + return !( isdefined( zone.bombPlanted ) && zone.bombPlanted ) && self.team != zone gameobjects::get_owner_team(); +} + +function can_defuse( zone ) +{ + return ( isdefined( zone.bombPlanted ) && zone.bombPlanted ) && self.team == zone gameobjects::get_owner_team(); +} + + +function combat_think() +{ + if ( ( isdefined( self.isPlanting ) && self.isPlanting ) || + ( isdefined( self.isDefusing ) && self.isDefusing ) ) + { + return; + } + + self bot_combat::combat_think(); +} + +function bot_threat_lost() +{ + approachRadiusSq = 750 * 750; + + // Don't chase enemies if near a plantable/defusable zone + foreach( zone in level.bombZones ) + { + if ( ( isdefined( zone.bombExploded ) && zone.bombExploded ) ) + { + continue; + } + + if ( DistanceSquared( self.origin, zone.trigger.origin ) < approachRadiusSq ) + { + // Defuse / Plant nearby zone + if ( self can_plant( zone ) || self can_defuse( zone ) ) + { + return; + } + } + } + + self bot_combat::chase_threat(); +} \ No newline at end of file diff --git a/mp/bots/_bot_dom.gsc b/mp/bots/_bot_dom.gsc new file mode 100644 index 0000000..257729e --- /dev/null +++ b/mp/bots/_bot_dom.gsc @@ -0,0 +1,149 @@ +#using scripts\shared\array_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + + +#using scripts\mp\gametypes\dom; + +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\mp\bots\_bot; +#using scripts\mp\bots\_bot_combat; + + + + + +#namespace bot_dom; + + // size of radius trigger in BO2 + + + // Default Radius * 2.5 + + +function init() +{ + level.botUpdate = &bot_update; + level.botPreCombat = &bot_pre_combat; + level.botUpdateThreatGoal = &bot_update_threat_goal; + level.botIdle = &bot_idle; +} + +function bot_update() +{ + self.bot.capturingFlag = self get_capturing_flag(); + + self.bot.goalFlag = undefined; + + if ( !self BotGoalReached() ) + { + foreach( flag in level.domFlags ) + { + if ( self bot::goal_in_trigger( flag.trigger ) ) + { + self.bot.goalFlag = flag; + break; + } + } + } + + self bot::bot_update(); +} + +function bot_pre_combat() +{ + // Stop heading to goals that have been captured + if ( !self bot_combat::has_threat() && + isdefined( self.bot.goalFlag ) && + self.bot.goalflag gameobjects::get_owner_team() == self.team ) + { + self BotSetGoal( self.origin ); + } + + self bot_combat::mp_pre_combat(); +} + + +function bot_idle() +{ + if ( isdefined( self.bot.capturingFlag ) ) + { + self bot::path_to_point_in_trigger( self.bot.capturingFlag.trigger ); + return; + } + + bestFlag = get_best_flag(); + + if ( isdefined( bestFlag ) ) + { + self bot::approach_goal_trigger( bestFlag.trigger ); + self bot::sprint_to_goal(); + return; + } + + self bot::bot_idle(); +} + +// Combat +//======================================== + +// Stay inside the goal when attacking +function bot_update_threat_goal() +{ + if ( isdefined( self.bot.capturingFlag ) ) + { + if ( self BotGoalReached() ) + { + self bot::path_to_point_in_trigger( self.bot.capturingFlag.trigger ); + } + return; + } + + self bot_combat::update_threat_goal(); +} + +// Dom Flags +//======================================== + +function get_capturing_flag() +{ + foreach( flag in level.domFlags ) + { + if ( self.team != flag gameobjects::get_owner_team() && self IsTouching( flag.trigger ) ) + { + return flag; + } + } + + return undefined; +} + +function get_best_flag() +{ + bestFlag = undefined; + bestFlagDistSq = undefined; + + // Closest flag under attack by the enemy, or not owned by their team + foreach( flag in level.domFlags ) + { + ownerTeam = flag gameobjects::get_owner_team(); + contested = flag gameobjects::get_num_touching_except_team( ownerTeam ); + distSq = Distance2DSquared( self.origin, flag.origin ); + + if ( ownerTeam == self.team && !contested ) + { + continue; + } + + if ( !isdefined( bestFlag ) || + distSq < bestFlagDistSq ) + { + bestFlag = flag; + bestFlagDistSq = distSq; + } + } + + return bestFlag; +} \ No newline at end of file diff --git a/mp/bots/_bot_escort.gsc b/mp/bots/_bot_escort.gsc new file mode 100644 index 0000000..a551351 --- /dev/null +++ b/mp/bots/_bot_escort.gsc @@ -0,0 +1,76 @@ +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + + +#using scripts\mp\bots\_bot; +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\shared\bots\bot_traversals; +#using scripts\shared\bots\bot_buttons; + + +#using scripts\mp\teams\_teams; +#using scripts\mp\_util; + + + + + + + +#namespace bot_escort; + +function init() +{ + level.botIdle = &bot_idle; +} + +function bot_idle() +{ + if ( self.team == game["attackers"] ) + { + self attackers_idle(); + } + else + { + self defenders_idle(); + } +} + +function attackers_idle() +{ + if ( isdefined( level.moveObject ) && ( level.robot.active || level.rebootPlayers > 0 ) ) + { + if ( !level.robot.moving || math::cointoss() ) + { + self bot::path_to_point_in_trigger( level.moveObject.trigger ); + } + else + { + self bot::approach_point( level.moveObject.trigger.origin, 160, 400 ); + } + + self bot::sprint_to_goal(); + return; + } + + self bot::bot_idle(); +} + +function defenders_idle() +{ + if ( isdefined( level.moveObject ) && level.robot.active ) + { + self bot::approach_point( level.moveObject.trigger.origin, 160, 400 ); + self bot::sprint_to_goal(); + return; + } + + self bot::bot_idle(); +} \ No newline at end of file diff --git a/mp/bots/_bot_hack.gsc b/mp/bots/_bot_hack.gsc new file mode 100644 index 0000000..0df67ef --- /dev/null +++ b/mp/bots/_bot_hack.gsc @@ -0,0 +1,203 @@ + + + + +#namespace bot_hack; + +function hack_tank_get_goal_origin( tank ) +{ + nodes = GetNodesInRadiusSorted( tank.origin, 256, 0, 64, "Path" ); + + foreach( node in nodes ) + { + dir = VectorNormalize( node.origin - tank.origin ); + dir = VectorScale( dir, 32 ); + + goal = tank.origin + dir; + + if ( self FindPath( self.origin, goal, false ) ) + { + return goal; + } + } + + return undefined; +} + +function hack_has_goal( tank ) +{ + goal = self GetGoal( "hack" ); + + if ( isdefined( goal ) ) + { + if ( DistanceSquared( goal, tank.origin ) < 128 * 128 ) + { + return true; + } + } + + return false; +} + +function hack_at_goal() +{ + if ( self AtGoal( "hack" ) ) + { + return true; + } + + goal = self GetGoal( "hack" ); + + if ( isdefined( goal ) ) + { + tanks = GetEntArray( "talon", "targetname" ); + tanks = ArraySort( tanks, self.origin ); + + foreach( tank in tanks ) + { + if ( DistanceSquared( goal, tank.origin ) < 128 * 128 ) + { + if ( isdefined( tank.trigger ) && self IsTouching( tank.trigger ) ) + { + return true; + } + } + } + } + + return false; +} + +function hack_goal_pregame( tanks ) +{ + foreach( tank in tanks ) + { + if ( isdefined( tank.owner ) ) + { + continue; + } + + if ( isdefined( tank.team ) && tank.team == self.team ) + { + continue; + } + + goal = self hack_tank_get_goal_origin( tank ); + + if ( isdefined( goal ) ) + { + if ( self AddGoal( goal, 24, 2, "hack" ) ) + { + self.goal_flag = tank; + return; + } + } + } +} + +function hack_think() +{ + if ( hack_at_goal() ) + { + self SetStance( "crouch" ); + wait( 0.25 ); + + self AddGoal( self.origin, 24, 4, "hack" ); + self PressUseButton( level.drone_hack_time + 1 ); + wait( level.drone_hack_time + 1 ); + + self SetStance( "stand" ); + self CancelGoal( "hack" ); + } + + tanks = GetEntArray( "talon", "targetname" ); + tanks = ArraySort( tanks, self.origin ); + + if ( !( isdefined( level.drones_spawned ) && level.drones_spawned ) ) + { + self hack_goal_pregame( tanks ); + } + else + { + foreach( tank in tanks ) + { + if ( isdefined( tank.owner ) && tank.owner == self ) + { + continue; + } + + if ( !isdefined( tank.owner ) ) + { + if ( self hack_has_goal( tank ) ) + { + return; + } + + goal = self hack_tank_get_goal_origin( tank ); + + if ( isdefined( goal ) ) + { + self AddGoal( goal, 24, 2, "hack" ); + return; + } + } + + if ( tank.isStunned && DistanceSquared( self.origin, tank.origin ) < 512 * 512 ) + { + goal = self hack_tank_get_goal_origin( tank ); + + if ( isdefined( goal ) ) + { + self AddGoal( goal, 24, 3, "hack" ); + return; + } + } + } + +/* + if ( !bot::bot_vehicle_weapon_ammo( "emp_grenade" ) ) + { + ammo = GetEntArray( "weapon_scavenger_item_hack", "classname" ); + ammo = ArraySort( ammo, self.origin ); + + foreach( bag in ammo ) + { + if ( FindPath( self.origin, bag.origin, false ) ) + { + self AddGoal( bag.origin, BOT_DEFAULT_GOAL_RADIUS, PRIORITY_NORMAL, "hack" ); + return; + } + } + + return; + } +*/ + + foreach( tank in tanks ) + { + if ( isdefined( tank.owner ) && tank.owner == self ) + { + continue; + } + + if ( tank.isStunned ) + { + continue; + } + + if ( self ThrowGrenade( GetWeapon( "emp_grenade" ), tank.origin ) ) + { + self waittill( "grenade_fire" ); + + goal = self hack_tank_get_goal_origin( tank ); + + if ( isdefined( goal ) ) + { + self AddGoal( goal, 24, 3, "hack" ); + wait( 0.5 ); + return; + } + } + } + } +} diff --git a/mp/bots/_bot_hq.gsc b/mp/bots/_bot_hq.gsc new file mode 100644 index 0000000..4410122 --- /dev/null +++ b/mp/bots/_bot_hq.gsc @@ -0,0 +1,435 @@ +#using scripts\shared\array_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\bots\_bot; +#using scripts\mp\bots\_bot_combat; + + + + + +#namespace bot_hq; + +function hq_think() +{ + time = GetTime(); + + if ( time < self.bot.update_objective ) + { + return; + } + + self.bot.update_objective = time + RandomIntRange( 500, 1500 ); + + if ( should_patrol_hq() ) + { + self patrol_hq(); + } + else if ( !has_hq_goal() ) + { + self move_to_hq(); + } + + if ( self is_capturing_hq() ) + { + self capture_hq(); + } + + hq_tactical_insertion(); + hq_grenade(); + + if ( !is_capturing_hq() && !self AtGoal( "hq_patrol" ) ) + { + mine = GetNearestNode( self.origin ); + point = hq_nearest_point(); + + if ( IsDefined( mine ) && bot::navmesh_points_visible( mine.origin, point ) ) + { + self LookAt( level.radio.baseorigin + ( 0, 0, 30 ) ); + } + } +} + +function has_hq_goal() +{ + origin = self GetGoal( "hq_radio" ); + + if ( isdefined( origin ) ) + { + foreach( point in level.radio.points ) + { + if ( DistanceSquared( origin, point ) < 64 * 64 ) + { + return true; + } + } + } + + return false; +} + +function is_capturing_hq() +{ + return ( self AtGoal( "hq_radio" ) ); +} + +function should_patrol_hq() +{ + if ( level.radio.gameobject.ownerTeam == "neutral" ) + { + return false; + } + + if ( level.radio.gameobject.ownerTeam != self.team ) + { + return false; + } + + if ( hq_is_contested() ) + { + return false; + } + + return true; +} + +function patrol_hq() +{ + self CancelGoal( "hq_radio" ); + + if ( self AtGoal( "hq_patrol" ) ) + { + node = GetNearestNode( self.origin ); + + if ( isdefined( node ) && node.type == "Path" ) + { + self SetStance( "crouch" ); + } + else + { + self SetStance( "stand" ); + } + + if ( GetTime() > self.bot.update_lookat ) + { + origin = self get_look_at(); + + z = 20; + + if ( DistanceSquared( origin, self.origin ) > 512 * 512 ) + { + z = RandomIntRange( 16, 60 ); + } + + self LookAt( origin + ( 0, 0, z ) ); + + if ( DistanceSquared( origin, self.origin ) > 256 * 256 ) + { + dir = VectorNormalize( self.origin - origin ); + dir = VectorScale( dir, 256 ); + + origin = origin + dir; + } + + self bot_combat::combat_throw_proximity( origin ); + + self.bot.update_lookat = GetTime() + RandomIntRange( 1500, 3000 ); + } + + goal = self GetGoal( "hq_patrol" ); + nearest = hq_nearest_point(); + + mine = GetNearestNode( goal ); + + if ( isdefined( mine ) && !bot::navmesh_points_visible( mine.origin, nearest ) ) + { + self ClearLookAt(); + self CancelGoal( "hq_patrol" ); + } + + if ( GetTime() > self.bot.update_objective_patrol ) + { + self ClearLookAt(); + self CancelGoal( "hq_patrol" ); + } + + return; + } + + nearest = hq_nearest_point(); + + if ( self HasGoal( "hq_patrol" ) ) + { + goal = self GetGoal( "hq_patrol" ); + + if ( DistanceSquared( self.origin, goal ) < 256 * 256 ) + { + origin = self get_look_at(); + self LookAt( origin ); + } + + if ( DistanceSquared( self.origin, goal ) < 128 * 128 ) + { + self.bot.update_objective_patrol = GetTime() + RandomIntRange( 3000, 6000 ); + } + + mine = GetNearestNode( goal ); + + if ( isdefined( mine ) && !bot::navmesh_points_visible( mine.origin, nearest ) ) + { + self ClearLookAt(); + self CancelGoal( "hq_patrol" ); + } + + return; + } + + points = util::PositionQuery_PointArray( nearest, 0, 512, 70, 64 ); + points = NavPointSightFilter( points, nearest ); + assert( points.size ); + + i = RandomInt( points.size ); + + for ( ; i < points.size; i++ ) + { + if ( self bot::friend_goal_in_radius( "hq_radio", points[i], 128 ) == 0 ) + { + if ( self bot::friend_goal_in_radius( "hq_patrol", points[i], 256 ) == 0 ) + { + self AddGoal( points[i], 24, 3, "hq_patrol" ); + return; + } + } + } +} + +function move_to_hq() +{ + self ClearLookAt(); + self CancelGoal( "hq_radio" ); + self CancelGoal( "hq_patrol" ); + + if ( self GetStance() == "prone" ) + { + self SetStance( "crouch" ); + wait( 0.25 ); + } + + if ( self GetStance() == "crouch" ) + { + self SetStance( "stand" ); + wait( 0.25 ); + } + + points = array::randomize( level.radio.points ); + + foreach( point in points ) + { + if ( self bot::friend_goal_in_radius( "hq_radio", point, 64 ) == 0 ) + { + self AddGoal( point, 24, 3, "hq_radio" ); + return; + } + } + + self AddGoal( array::random( points ), 24, 3, "hq_radio" ); +} + +function get_look_at() +{ + enemy = self bot::get_closest_enemy( self.origin, true ); + + if ( isdefined( enemy ) ) + { + node = GetVisibleNode( self.origin, enemy.origin ); + + if ( isdefined( node ) && DistanceSquared( self.origin, node.origin ) > 128 * 128 ) + { + return node.origin; + } + } + + enemies = self bot::get_enemies( false ); + + if ( enemies.size ) + { + enemy = array::random( enemies ); + } + + if ( isdefined( enemy ) ) + { + node = GetVisibleNode( self.origin, enemy.origin ); + + if ( isdefined( node ) && DistanceSquared( self.origin, node.origin ) > 128 * 128 ) + { + return node.origin; + } + } + + spawn = array::random( level.spawnpoints ); + node = GetVisibleNode( self.origin, spawn.origin ); + + if ( isdefined( node ) && DistanceSquared( self.origin, node.origin ) > 128 * 128 ) + { + return node.origin; + } + + return level.radio.baseorigin; +} + +function capture_hq() +{ + self AddGoal( self.origin, 24, 3, "hq_radio" ); + self SetStance( "crouch" ); + + if ( GetTime() > self.bot.update_lookat ) + { + origin = self get_look_at(); + + z = 20; + + if ( DistanceSquared( origin, self.origin ) > 512 * 512 ) + { + z = RandomIntRange( 16, 60 ); + } + + self LookAt( origin + ( 0, 0, z ) ); + + if ( DistanceSquared( origin, self.origin ) > 256 * 256 ) + { + dir = VectorNormalize( self.origin - origin ); + dir = VectorScale( dir, 256 ); + + origin = origin + dir; + } + + self bot_combat::combat_throw_proximity( origin ); + + self.bot.update_lookat = GetTime() + RandomIntRange( 1500, 3000 ); + } +} + +function any_other_team_touching( skip_team ) +{ + foreach( team in level.teams ) + { + if ( team == skip_team ) + continue; + + if ( level.radio.gameobject.numtouching[ team ] ) + return true; + } + + return false; +} + +function is_hq_contested( skip_team ) +{ + if ( any_other_team_touching( skip_team ) ) + { + return true; + } + + enemy = self bot::get_closest_enemy( level.radio.baseorigin, true ); + + if ( isdefined( enemy ) && DistanceSquared( enemy.origin, level.radio.baseorigin ) < 512 * 512 ) + { + return true; + } + + return false; +} + +function hq_grenade() +{ + enemies = bot::get_enemies(); + + if ( !enemies.size ) + { + return; + } + + if ( self AtGoal( "hq_patrol" ) || self AtGoal( "hq_radio" ) ) + { + if ( self GetWeaponAmmoStock( GetWeapon( "proximity_grenade" ) ) > 0 ) + { + origin = get_look_at(); + + if ( self bot_combat::combat_throw_proximity( origin ) ) + { + return; + } + } + } + + if ( !is_hq_contested( self.team ) ) + { + self bot_combat::combat_throw_smoke( level.radio.baseorigin ); + return; + } + + enemy = self bot::get_closest_enemy( level.radio.baseorigin, false ); + + if ( isdefined( enemy ) ) + { + origin = enemy.origin; + } + else + { + origin = level.radio.baseorigin; + } + + dir = VectorNormalize( self.origin - origin ); + dir = ( 0, dir[1], 0 ); + + origin = origin + VectorScale( dir, 128 ); + + if ( !self bot_combat::combat_throw_lethal( origin ) ) + { + self bot_combat::combat_throw_tactical( origin ); + } +} + +function hq_tactical_insertion() +{ + if ( !self HasWeapon( GetWeapon( "tactical_insertion" ) ) ) + { + return; + } + + dist = self GetLookAheadDist(); + dir = self GetLookaheadDir(); + + if ( !isdefined( dist ) || !isdefined( dir ) ) + { + return; + } + + point = hq_nearest_point(); + mine = GetNearestNode( self.origin ); + + if ( isdefined( mine ) && !bot::navmesh_points_visible( mine.origin, point ) ) + { + origin = self.origin + VectorScale( dir, dist ); + + next = GetNearestNode( origin ); + + if ( isdefined( next ) && bot::navmesh_points_visible( next.origin, point ) ) + { + bot_combat::combat_tactical_insertion( self.origin ); + } + } +} + +function hq_nearest_point() +{ + return array::random( level.radio.points ); +} + +function hq_is_contested() +{ + enemy = self bot::get_closest_enemy( level.radio.baseorigin, false ); + return ( IsDefined( enemy ) && DistanceSquared( enemy.origin, level.radio.baseorigin ) < level.radio.node_radius * level.radio.node_radius ); +} diff --git a/mp/bots/_bot_koth.gsc b/mp/bots/_bot_koth.gsc new file mode 100644 index 0000000..d23ad65 --- /dev/null +++ b/mp/bots/_bot_koth.gsc @@ -0,0 +1,77 @@ +#using scripts\shared\array_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + + + +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\mp\bots\_bot; +#using scripts\mp\bots\_bot_combat; + + + +#namespace bot_koth; + +function init() +{ + level.onBotSpawned = &on_bot_spawned; + level.botUpdateThreatGoal = &bot_update_threat_goal; + level.botIdle = &bot_idle; +} + +function on_bot_spawned() +{ + self thread wait_zone_moved(); + + self bot::on_bot_spawned(); +} + +function wait_zone_moved() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while(1) + { + level waittill( "zone_moved" ); + + if ( !self bot_combat::has_threat() && self BotGoalSet() ) + { + self BotSetGoal( self.origin ); + } + } +} + +function bot_update_threat_goal() +{ + if ( isdefined( level.zone ) && self IsTouching( level.zone.gameobject.trigger ) ) + { + if ( self BotGoalReached() ) + { + self bot::path_to_point_in_trigger( level.zone.gameobject.trigger ); + } + return; + } + + self bot_combat::update_threat_goal(); +} + +function bot_idle() +{ + if ( isdefined( level.zone ) ) + { + if ( self IsTouching( level.zone.gameobject.trigger ) ) + { + self bot::path_to_point_in_trigger( level.zone.gameobject.trigger ); + } + else + { + self bot::approach_goal_trigger( level.zone.gameobject.trigger ); + self bot::sprint_to_goal(); + } + return; + } + + self bot::bot_idle(); +} diff --git a/mp/bots/_bot_loadout.gsc b/mp/bots/_bot_loadout.gsc new file mode 100644 index 0000000..33f3324 --- /dev/null +++ b/mp/bots/_bot_loadout.gsc @@ -0,0 +1,876 @@ +#using scripts\shared\array_shared; +#using scripts\shared\rank_shared; + + + + +#using scripts\mp\killstreaks\_killstreaks; + +#using scripts\mp\bots\_bot; + + + + + + +#namespace bot_loadout; + +// Item Whitelist +//======================================== + +function in_whitelist( itemName ) +{ + if ( !isdefined( itemName ) ) + return false; + + switch( itemName ) + { + // Secondaries + case "WEAPON_KNIFE_LOADOUT": + + case "WEAPON_PISTOL_STANDARD": + case "WEAPON_PISTOL_BURST": + case "WEAPON_PISTOL_FULLAUTO": + + case "WEAPON_LAUNCHER_STANDARD": + case "WEAPON_LAUNCHER_LOCKONLY": + + case "WEAPON_SMG_STANDARD": + case "WEAPON_SMG_BURST": + case "WEAPON_SMG_FASTFIRE": + case "WEAPON_SMG_LONGRANGE": + case "WEAPON_SMG_VERSATILE": + case "WEAPON_SMG_CAPACITY": + + // Primaries + case "WEAPON_AR_STANDARD": + case "WEAPON_AR_ACCURATE": + case "WEAPON_AR_CQB": + case "WEAPON_AR_DAMAGE": + case "WEAPON_AR_FASTBURST": + case "WEAPON_AR_LONGBURST": + case "WEAPON_AR_MARKSMAN": + + case "WEAPON_LMG_CQB": + case "WEAPON_LMG_HEAVY": + case "WEAPON_LMG_LIGHT": + case "WEAPON_LMG_SLOWFIRE": + + case "WEAPON_SNIPER_FASTBOLT": + case "WEAPON_SNIPER_FASTSEMI": + case "WEAPON_SNIPER_POWERBOLT": + case "WEAPON_SNIPER_CHARGESHOT": + + case "WEAPON_SHOTGUN_FULLAUTO": + case "WEAPON_SHOTGUN_PRECISION": + case "WEAPON_SHOTGUN_PUMP": + case "WEAPON_SHOTGUN_SEMIAUTO": + + // Lethals + case "WEAPON_FRAGGRENADE": + case "WEAPON_HATCHET": + case "WEAPON_STICKY_GRENADE": + //case "WEAPON_SATCHEL_CHARGE": + case "WEAPON_BOUNCINGBETTY": + case "WEAPON_INCENDIARY_GRENADE": + + // Tacticals + case "WEAPON_WILLY_PETE": + case "WEAPON_STUN_GRENADE": + case "WEAPON_EMPGRENADE": + case "WEAPON_FLASHBANG": + case "WEAPON_PROXIMITY_GRENADE": + //case "WEAPON_PDA_HACK": + //case "WEAPON_TROPHY_SYSTEM": + + // Killstreaks + //case "KILLSTREAK_RCBOMB": + case "KILLSTREAK_RECON": + case "KILLSTREAK_COUNTER_UAV": + //case "KILLSTREAK_SUPPLY_DROP": + //case "KILLSTREAK_MICROWAVE_TURRET": + //case "KILLSTREAK_REMOTE_MISSILE": + //case "KILLSTREAK_PLANEMORTAR": + //case "KILLSTREAK_AUTO_TURRET": + //case "KILLSTREAK_AI_TANK_DROP": + //case "KILLSTREAK_HELICOPTER_COMLINK": + case "KILLSTREAK_SATELLITE": + //case "KILLSTREAK_EMP": + //case "KILLSTREAK_HELICOPTER_GUNNER": + case "KILLSTREAK_RAPS": + //case "KILLSTREAK_DRONE_STRIKE": + //case "KILLSTREAK_DART": + case "KILLSTREAK_SENTINEL": + + // TU Something Weapons + //case "WEAPON_MELEE_KNUCKLES": + //case "WEAPON_MELEE_BUTTERFLY": + //case "WEAPON_MELEE_WRENCH": + + // TU 6 Weapons + //case "WEAPON_PISTOL_SHOTGUN": + //case "WEAPON_AR_GARAND": + //case "WEAPON_SPECIAL_CROSSBOW": + //case "WEAPON_MELEE_CROWBAR": + //case "WEAPON_MELEE_SWORD": + return true; + } + + return false; +} + +// Classes +//======================================== + +function build_classes() +{ + primaryWeapons = self get_available_items( undefined, "primary" ); + secondaryWeapons = self get_available_items( undefined, "secondary" ); + lethals = self get_available_items( undefined, "primarygadget" ); + tacticals = self get_available_items( undefined, "secondarygadget" ); + if ( ( isdefined( level.perksEnabled ) && level.perksEnabled ) ) + { + specialties1 = self get_available_items( undefined, "specialty1" ); + specialties2 = self get_available_items( undefined, "specialty2" ); + specialties3 = self get_available_items( undefined, "specialty3" ); + } + + foreach( className, classValue in level.classMap ) + { + if ( !isSubstr( className, "custom" ) ) + { + continue; + } + + classIndex = int( className[className.size-1] ); + + pickedItems = []; + + pick_item( pickedItems, primaryWeapons ); + + if ( RandomInt( 100 ) < 95 ) // 5% chance to be a boxer for Scronce + { + pick_item( pickedItems, secondaryWeapons ); + } + + // Shuffle these selections around a bit so the classes don't all look the same when the allocation is low + otherItems = Array ( lethals, tacticals, specialties1, specialties2, specialties3 ); + otherItems = array::randomize( otherItems ); + + for ( i = 0; i < otherItems.size; i ++ ) + { + pick_item( pickedItems, otherItems[i] ); + } + + // Add items up to the max allocation + for ( i = 0; i < pickedItems.size && i < level.maxAllocation; i++ ) + { + self BotClassAddItem( classIndex, pickedItems[i] ); + } + + // TODO: Pick primary/secondary attachments, extra perks, extra lethal, extra tactical, overkill +/* + primaryWeapon = self GetLoadoutWeapon( classIndex, "primary" ); + + if ( primaryWeapon != level.weaponNone && primaryWeapon.supportedAttachments.size ) + { + attachment = array::random( primaryWeapon.supportedAttachments ); + self BotClassAddAttachment( classIndex, primaryWeapon, attachment, "primary" ); + } + + secondaryWeapon = self GetLoadoutWeapon( classIndex, "secondary" ); + + if ( secondaryWeapon != level.weaponNone && secondaryWeapon.supportedAttachments.size ) + { + attachment = array::random( secondaryWeapon.supportedAttachments ); + self BotClassAddAttachment( classIndex, secondaryWeapon, attachment, "secondary" ); + } +*/ + } +} + +function pick_item( &pickedItems, items ) +{ + if ( !isdefined( items ) || items.size <= 0 ) + { + return; + } + + pickedItems[pickedItems.size] = array::random( items ); +} + +function pick_classes() +{ + self.loadoutClasses = []; + self.launcherClassCount = 0; + + foreach( className, classValue in level.classMap ) + { + if ( isSubstr( className, "custom" ) ) + { + if ( level.disableCAC ) + { + continue; + } + + classIndex = int( className[className.size-1] ); + } + else + { + // Things bots could use better in the default classes: + // C4, Trophy System, Lock on only launcher + classIndex = level.classToClassNum[ classValue ]; + } + + primary = self GetLoadoutWeapon( classIndex, "primary" ); + secondary = self GetLoadoutWeapon( classIndex, "secondary" ); + + botClass = SpawnStruct(); + botClass.name = className; + botClass.index = classIndex; + botClass.value = classValue; + botClass.primary = primary; + botClass.secondary = secondary; + + if ( botClass.secondary.isRocketLauncher ) + { + self.launcherClassCount++; + } + + self.loadoutClasses[ self.loadoutClasses.size ] = botClass; + } +} + +function get_current_class() +{ + currValue = self.pers["class"]; + if ( !isdefined( currValue ) ) + { + return undefined; + } + + foreach( botClass in self.loadoutClasses ) + { + if ( botClass.value == currValue ) + { + return botClass; + } + } + + return undefined; +} + +// Specialists +//======================================== + +function pick_hero_gadget() +{ + if ( RandomInt( 2 ) < 1 || !self pick_hero_ability() ) + { + self pick_hero_weapon(); + } +} + +function pick_hero_weapon() +{ + heroWeaponRef = self GetHeroWeaponName(); + + if ( IsItemRestricted( heroWeaponRef ) ) + { + return false; + } + + heroWeaponName = self get_item_name( heroWeaponRef ); + self BotClassAddItem( 0, heroWeaponName ); + + return true; +} + +function pick_hero_ability() +{ + heroAbilityRef = self GetHeroAbilityName(); + + if ( IsItemRestricted( heroAbilityRef ) ) + { + return false; + } + + heroAbilityName = self get_item_name( heroAbilityRef ); + self BotClassAddItem( 0, heroAbilityName ); + + return true; +} + +// Killstreaks +//======================================== + +function pick_killstreaks() +{ + killstreaks = array::randomize( self get_available_items( "killstreak" ) ); + + for( i = 0; i < 3 && i < killstreaks.size; i++ ) + { + self BotClassAddItem( 0, killstreaks[i] ); + } +} + + +// Get Items +//======================================== + +function get_available_items( filterGroup, filterSlot ) +{ + // Get unlocked and unrestricted items + items = []; + + for( i = 0; i < 256; i++ ) + { + row = tableLookupRowNum( level.statsTableID, 0, i ); + + if ( row < 0 ) + { + continue; + } + + name = tableLookupColumnForRow( level.statsTableID, row, 3 ); + + if ( name == "" || !in_whitelist( name ) ) + { + continue; + } + + allocation = Int( tableLookupColumnForRow( level.statsTableID, row, 12 ) ); + + if ( allocation < 0 ) + { + continue; + } + + ref = tableLookupColumnForRow( level.statsTableId, row, 4 ); + + if ( IsItemRestricted( ref ) ) + { + continue; + } + + number = Int( tableLookupColumnForRow( level.statsTableID, row, 0 ) ); + + if ( !SessionModeIsPrivate() && self IsItemLocked( number ) ) + { + continue; + } + + if ( isdefined( filterGroup ) ) + { + group = tableLookupColumnForRow( level.statsTableID, row, 2 ); + + if ( group != filterGroup ) + { + continue; + } + } + + if ( isdefined( filterSlot ) ) + { + slot = tableLookupColumnForRow( level.statsTableID, row, 13 ); + + if ( slot != filterSlot ) + { + continue; + } + } + + items[items.size] = name; + } + + return items; +} + +function get_item_name( itemReference ) +{ + for( i = 0; i < 256; i++ ) + { + row = tableLookupRowNum( level.statsTableID, 0, i ); + + if ( row < 0 ) + { + continue; + } + + reference = tableLookupColumnForRow( level.statsTableID, row, 4 ); + + if ( reference != itemReference ) + { + continue; + } + + name = tableLookupColumnForRow( level.statsTableID, row, 3 ); + + return name; + } + + return undefined; +} + +// Not in use + +function init() +{ + level endon( "game_ended" ); + + level.bot_banned_killstreaks = Array ( "KILLSTREAK_RCBOMB", + "KILLSTREAK_QRDRONE", + "KILLSTREAK_REMOTE_MISSILE", + "KILLSTREAK_REMOTE_MORTAR", + "KILLSTREAK_HELICOPTER_GUNNER" ); + for ( ;; ) + { + level waittill( "connected", player ); + + if ( !player IsTestClient() ) + { + continue; + } + + player thread on_bot_connect(); + } +} + +function on_bot_connect() +{ + self endon( "disconnect" ); + + if ( isdefined( self.pers[ "bot_loadout" ] ) ) + { + return; + } + + wait( 0.10 ); + + if ( self GetEntityNumber() % 2 == 0 ) + { + {wait(.05);}; + } + + self bot::set_rank(); + + self BotSetRandomCharacterCustomization(); + + if ( level.onlineGame && !SessionModeIsPrivate() ) + { + self BotSetDefaultClass( 5, "class_assault" ); + self BotSetDefaultClass( 6, "class_smg" ); + self BotSetDefaultClass( 7, "class_lmg" ); + self BotSetDefaultClass( 8, "class_cqb" ); + self BotSetDefaultClass( 9, "class_sniper" ); + } + else + { + self BotSetDefaultClass( 5, "class_assault" ); + self BotSetDefaultClass( 6, "class_smg" ); + self BotSetDefaultClass( 7, "class_lmg" ); + self BotSetDefaultClass( 8, "class_cqb" ); + self BotSetDefaultClass( 9, "class_sniper" ); + } + + max_allocation = 10; + + if ( !SessionModeIsPrivate() ) + { + for ( i = 1; i <= 3; i++ ) + { + if ( self IsItemLocked( rank::GetItemIndex( "feature_allocation_slot_" + i ) ) ) + { + max_allocation--; + } + } + } + + self construct_loadout( max_allocation ); + self.pers[ "bot_loadout" ] = true; +} + +function construct_loadout( allocation_max ) +{ + if ( !SessionModeIsPrivate() && self IsItemLocked( rank::GetItemIndex( "feature_cac" ) ) ) + { + // cac still locked + return; + } + + pixbeginevent( "bot_construct_loadout" ); + + item_list = build_item_list(); + +// item_list["primary"] = []; +// item_list["primary"][0] = "WEAPON_RIOTSHIELD"; + + construct_class( 0, item_list, allocation_max ); + construct_class( 1, item_list, allocation_max ); + construct_class( 2, item_list, allocation_max ); + construct_class( 3, item_list, allocation_max ); + construct_class( 4, item_list, allocation_max ); + + killstreaks = item_list["killstreak1"]; + + if ( isdefined( item_list["killstreak2"] ) ) + { + killstreaks = ArrayCombine( killstreaks, item_list["killstreak2"], true, false ); + } + + if ( isdefined( item_list["killstreak3"] ) ) + { + killstreaks = ArrayCombine( killstreaks, item_list["killstreak3"], true, false ); + } + + if ( isdefined( killstreaks ) && killstreaks.size ) + { + choose_weapon( 0, killstreaks ); + choose_weapon( 0, killstreaks ); + choose_weapon( 0, killstreaks ); + } + + self.claimed_items = undefined; + pixendevent(); +} + +function construct_class( constructclass, items, allocation_max ) +{ + allocation = 0; + + claimed_count = build_claimed_list( items ); + self.claimed_items = []; + + // primary + weapon = choose_weapon( constructclass, items["primary"] ); + claimed_count["primary"]++; + allocation++; + + // secondary + weapon = choose_weapon( constructclass, items["secondary"] ); + choose_weapon_option( constructclass, "camo", 1 ); +} + +function make_choice( chance, claimed, max_claim ) +{ + return ( claimed < max_claim && RandomInt( 100 ) < chance ); +} + +function chose_action( action1, chance1, action2, chance2, action3, chance3, action4, chance4 ) +{ + chance1 = Int( chance1 / 10 ); + chance2 = Int( chance2 / 10 ); + chance3 = Int( chance3 / 10 ); + chance4 = Int( chance4 / 10 ); + + actions = []; + + for( i = 0; i < chance1; i++ ) + { + actions[ actions.size ] = action1; + } + + for( i = 0; i < chance2; i++ ) + { + actions[ actions.size ] = action2; + } + + for( i = 0; i < chance3; i++ ) + { + actions[ actions.size ] = action3; + } + + for( i = 0; i < chance4; i++ ) + { + actions[ actions.size ] = action4; + } + + return array::random( actions ); +} + +function item_is_claimed( item ) +{ + foreach( claim in self.claimed_items ) + { + if ( claim == item ) + { + return true; + } + } + + return false; +} + +function choose_weapon( weaponclass, items ) +{ + if ( !isdefined( items ) || !items.size ) + { + return undefined; + } + + start = RandomInt( items.size ); + + for( i = 0; i < items.size; i++ ) + { + weapon = items[ start ]; + + if ( !item_is_claimed( weapon ) ) + { + break; + } + + start = ( start + 1 ) % items.size; + } + + self.claimed_items[ self.claimed_items.size ] = weapon; + + self BotClassAddItem( weaponclass, weapon ); + return weapon; +} + +function build_weapon_options_list( optionType ) +{ + level.botWeaponOptionsId[optionType] = []; + level.botWeaponOptionsProb[optionType] = []; + + csv_filename = "gamedata/weapons/common/attachmentTable.csv"; + prob = 0; + for ( row = 0 ; row < 255 ; row++ ) + { + if ( tableLookupColumnForRow( csv_filename, row, 1 ) == optionType ) + { + index = level.botWeaponOptionsId[optionType].size; + level.botWeaponOptionsId[optionType][index] = Int( tableLookupColumnForRow( csv_filename, row, 0 ) ); + prob += Int( tableLookupColumnForRow( csv_filename, row, 15 ) ); + level.botWeaponOptionsProb[optionType][index] = prob; + } + } +} + +function choose_weapon_option( weaponclass, optionType, primary ) +{ + if ( !isdefined( level.botWeaponOptionsId ) ) + { + level.botWeaponOptionsId = []; + level.botWeaponOptionsProb = []; + + build_weapon_options_list( "camo" ); + build_weapon_options_list( "reticle" ); + } + + // weapon options cannot be set in local matches + if ( !level.onlineGame && !level.systemLink ) + return; + + // Increase the range of the probability to reduce the chances of picking the option when the bot's level is less than BOT_RANK_ALL_OPTIONS_AVAILABLE + // (in system link all options are available) + numOptions = level.botWeaponOptionsProb[optionType].size; + maxProb = level.botWeaponOptionsProb[optionType][numOptions-1]; + if ( !level.systemLink && self.pers[ "rank" ] < 20 ) + maxProb += 4 * maxProb * ( ( 20 - self.pers[ "rank" ] ) / 20 ); + + rnd = RandomInt( Int( maxProb ) ); + for (i=0 ; i rnd ) + { + self BotClassSetWeaponOption( weaponclass, primary, optionType, level.botWeaponOptionsId[optionType][i] ); + break; + } + } +} + +function choose_primary_attachments( weaponclass, weapon, allocation, allocation_max ) +{ + attachments = weapon.supportedAttachments; + remaining = allocation_max - allocation; + + if ( !attachments.size || !remaining ) + { + return 0; + } + + attachment_action = chose_action( "3_attachments", 25, "2_attachments", 35, "1_attachments", 35, "none", 5 ); + + if ( remaining >= 4 && attachment_action == "3_attachments" ) + { + a1 = array::random( attachments ); + self BotClassAddAttachment( weaponclass, weapon, a1, "primaryattachment1" ); + count = 1; + + attachments = GetWeaponAttachments( weapon, a1 ); + + if ( attachments.size ) + { + a2 = array::random( attachments ); + self BotClassAddAttachment( weaponclass, weapon, a2, "primaryattachment2" ); + count++; + + attachments = GetWeaponAttachments( weapon, a1, a2 ); + + if ( attachments.size ) + { + a3 = array::random( attachments ); + self BotClassAddItem( weaponclass, "BONUSCARD_PRIMARY_GUNFIGHTER" ); + self BotClassAddAttachment( weaponclass, weapon, a3, "primaryattachment3" ); + return 4; + } + } + + return count; + } + else if ( remaining >= 2 && attachment_action == "2_attachments" ) + { + a1 = array::random( attachments ); + self BotClassAddAttachment( weaponclass, weapon, a1, "primaryattachment1" ); + + attachments = GetWeaponAttachments( weapon, a1 ); + + if ( attachments.size ) + { + a2 = array::random( attachments ); + self BotClassAddAttachment( weaponclass, weapon, a2, "primaryattachment2" ); + return 2; + } + + return 1; + } + else if ( remaining >= 1 && attachment_action == "1_attachments" ) + { + a = array::random( attachments ); + self BotClassAddAttachment( weaponclass, weapon, a, "primaryattachment1" ); + return 1; + } + + return 0; +} + +function choose_secondary_attachments( weaponclass, weapon, allocation, allocation_max ) +{ + attachments = weapon.supportedAttachments ; + remaining = allocation_max - allocation; + + if ( !attachments.size || !remaining ) + { + return 0; + } + + attachment_action = chose_action( "2_attachments", 10, "1_attachments", 40, "none", 50, "none", 0 ); + + if ( remaining >= 3 && attachment_action == "2_attachments" ) + { + a1 = array::random( attachments ); + self BotClassAddAttachment( weaponclass, weapon, a1, "secondaryattachment1" ); + + attachments = GetWeaponAttachments( weapon, a1 ); + + if ( attachments.size ) + { + a2 = array::random( attachments ); + self BotClassAddItem( weaponclass, "BONUSCARD_SECONDARY_GUNFIGHTER" ); + self BotClassAddAttachment( weaponclass, weapon, a2, "secondaryattachment2" ); + return 3; + } + + return 1; + } + else if ( remaining >= 1 && attachment_action == "1_attachments" ) + { + a = array::random( attachments ); + self BotClassAddAttachment( weaponclass, weapon, a, "secondaryattachment1" ); + return 1; + } + + return 0; +} + +function build_item_list() +{ + items = []; + + for( i = 0; i < 256; i++ ) + { + row = tableLookupRowNum( level.statsTableID, 0, i ); + + if ( row > -1 ) + { + slot = tableLookupColumnForRow( level.statsTableID, row, 13 ); + + if ( slot == "" ) + { + continue; + } + + number = Int( tableLookupColumnForRow( level.statsTableID, row, 0 ) ); + + if ( !SessionModeIsPrivate() && self IsItemLocked( number ) ) + { + continue; + } + + allocation = Int( tableLookupColumnForRow( level.statsTableID, row, 12 ) ); + + if ( allocation < 0 ) + { + continue; + } + + name = tableLookupColumnForRow( level.statsTableID, row, 3 ); +/* + if ( item_is_banned( slot, name ) ) + { + continue; + } +*/ + if ( !isdefined( items[slot] ) ) + { + items[slot] = []; + } + + items[ slot ][ items[slot].size ] = name; + } + } + + return items; +} + +function item_is_banned( slot, item ) +{ + if ( item == "WEAPON_KNIFE_BALLISTIC" ) + { + return true; + } + + if ( GetDvarInt("tu6_enableDLCWeapons") == 0 && item == "WEAPON_PEACEKEEPER" ) + { + return true; + } + + if ( slot != "killstreak1" && slot != "killstreak2" && slot != "killstreak3" ) + { + return false; + } + + foreach( banned in level.bot_banned_killstreaks ) + { + if ( item == banned ) + { + return true; + } + } + + return false; +} + +function build_claimed_list( items ) +{ + claimed = []; + keys = GetArrayKeys( items ); + + foreach( key in keys ) + { + claimed[ key ] = 0; + } + + return claimed; +} \ No newline at end of file diff --git a/mp/bots/_bot_sd.gsc b/mp/bots/_bot_sd.gsc new file mode 100644 index 0000000..1e126a2 --- /dev/null +++ b/mp/bots/_bot_sd.gsc @@ -0,0 +1,141 @@ +#using scripts\shared\array_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic_utils; + +#using scripts\shared\bots\_bot; +#using scripts\shared\bots\_bot_combat; +#using scripts\shared\bots\bot_buttons; +#using scripts\mp\bots\_bot; +#using scripts\mp\bots\_bot_combat; + + + +#namespace bot_sd; + + + + +function init() +{ + level.botIdle = &bot_idle; +} + +function bot_idle() +{ + if( !level.bombPlanted && !level.multibomb && self.team == game["attackers"] ) + { + carrier = level.sdBomb gameobjects::get_carrier(); + + if( !isdefined( carrier ) ) + { + self BotSetGoal( level.sdBomb.trigger.origin ); + self bot::sprint_to_goal(); + return; + } + } + + approachRadiusSq = 750 * 750; + + // Check for Plant / Defuse + foreach( zone in level.bombZones ) + { + if ( ( isdefined( level.bombPlanted ) && level.bombPlanted ) && !( isdefined( zone.isPlanted ) && zone.isPlanted ) ) + { + continue; + } + + zoneTrigger = self get_zone_trigger( zone ); + + if ( self IsTouching( zoneTrigger ) ) + { + if ( self can_plant( zone ) || self can_defuse( zone ) ) + { + self bot::press_use_button(); + return; + } + } + + if ( DistanceSquared( self.origin, zone.trigger.origin ) < approachRadiusSq ) + { + // Defuse / Plant nearby zone + if ( self can_plant( zone ) || self can_defuse( zone ) ) + { + self bot::path_to_trigger( zoneTrigger ); + self bot::sprint_to_goal(); + return; + } + } + } + + // Shuffle zones + zones = array::randomize( level.bombZones ); + + // Check for zones to defuse + foreach( zone in zones ) + { + if ( ( isdefined( level.bombPlanted ) && level.bombPlanted ) && !( isdefined( zone.isPlanted ) && zone.isPlanted ) ) + { + continue; + } + + if ( self can_defuse( zone ) ) + { + self bot::approach_goal_trigger( zoneTrigger, 750 ); + self bot::sprint_to_goal(); + return; + } + } + + // Go to a random zone to plant / guard + foreach( zone in zones ) + { + if ( ( isdefined( level.bombPlanted ) && level.bombPlanted ) && !( isdefined( zone.isPlanted ) && zone.isPlanted ) ) + { + continue; + } + + // Defend the nearby point + if ( DistanceSquared( self.origin, zone.trigger.origin ) < approachRadiusSq && + RandomInt( 100 ) < 70 ) + { + triggerRadius = self bot::get_trigger_radius( zone.trigger ); + self bot::approach_point( zone.trigger.origin, triggerRadius, 750 ); + self bot::sprint_to_goal(); + return; + } + } + + self bot::bot_idle(); +} + +function get_zone_trigger( zone ) +{ + if ( self.team == zone gameobjects::get_owner_team() ) + { + return zone.bombDefuseTrig; + } + + return zone.trigger; +} + +function can_plant( zone ) +{ + if ( level.multibomb ) + { + return !( isdefined( zone.isPlanted ) && zone.isPlanted ) && self.team != zone gameobjects::get_owner_team(); + } + + carrier = level.sdBomb gameobjects::get_carrier(); + + return isdefined( carrier ) && self == carrier; +} + +function can_defuse( zone ) +{ + return ( isdefined( zone.isPlanted ) && zone.isPlanted ) && self.team == zone gameobjects::get_owner_team(); +} \ No newline at end of file diff --git a/mp/gametypes/_ball_utils.gsc b/mp/gametypes/_ball_utils.gsc new file mode 100644 index 0000000..a88677b --- /dev/null +++ b/mp/gametypes/_ball_utils.gsc @@ -0,0 +1,13 @@ + +#namespace ball; + +function add_ball_return_trigger( trigger ) +{ + if ( !IsDefined( level.ball_return_trigger ) ) + { + level.ball_return_trigger = []; + } + + level.ball_return_trigger[level.ball_return_trigger.size] = trigger; +} + diff --git a/mp/gametypes/_battlechatter.gsc b/mp/gametypes/_battlechatter.gsc new file mode 100644 index 0000000..b071405 --- /dev/null +++ b/mp/gametypes/_battlechatter.gsc @@ -0,0 +1,1908 @@ + + + +#using scripts\codescripts\struct; +#using scripts\shared\abilities\gadgets\_gadget_camo; +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + +#using scripts\mp\gametypes\_dev; + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; + +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\_util; + +#namespace battlechatter; + +function autoexec __init__sytem__() { system::register("battlechatter",&__init__,undefined,undefined); } + + + + + + + + + + // Plays when VO is turned off + + + + // ALL + INTERRUPT + // ALL + INTERRUPT + UNDERWATER + EXERT + + + + + + + + + + +function __init__() +{ +/# + level thread devgui_think(); +#/ + callback::on_joined_team( &on_joined_team ); + callback::on_connect( &on_player_connect ); + callback::on_spawned( &on_player_spawned ); + + level.heroPlayDialog = &play_dialog; + level.playGadgetReady = &play_gadget_ready; + level.playGadgetActivate = &play_gadget_activate; + level.playGadgetSuccess = &play_gadget_success; + level.playPromotionReaction = &play_promotion_reaction; + level.playThrowHatchet = &play_throw_hatchet; + + level.bcSounds = []; + level.bcSounds[ "incoming_alert" ] = []; + level.bcSounds[ "incoming_alert" ][ "frag_grenade" ] = "incomingFrag"; + level.bcSounds[ "incoming_alert" ][ "incendiary_grenade" ] = "incomingIncendiary"; + level.bcSounds[ "incoming_alert" ][ "sticky_grenade" ] = "incomingSemtex"; + level.bcSounds[ "incoming_alert" ][ "launcher_standard" ] = "threatRpg"; + + + level.bcSounds[ "incoming_delay" ] = []; + level.bcSounds[ "incoming_delay" ][ "frag_grenade" ] = "fragGrenadeDelay"; + level.bcSounds[ "incoming_delay" ][ "incendiary_grenade" ] = "incendiaryGrenadeDelay"; + level.bcSounds[ "incoming_alert" ][ "sticky_grenade" ] = "semtexDelay"; + level.bcSounds[ "incoming_delay" ][ "launcher_standard" ] = "missileDelay"; + + level.bcSounds[ "kill_dialog" ] = []; + level.bcSounds[ "kill_dialog" ][ "assassin" ] = "killSpectre"; + level.bcSounds[ "kill_dialog" ][ "grenadier" ] = "killGrenadier"; + level.bcSounds[ "kill_dialog" ][ "outrider" ] = "killOutrider"; + level.bcSounds[ "kill_dialog" ][ "prophet" ] = "killTechnomancer"; + level.bcSounds[ "kill_dialog" ][ "pyro" ] = "killFirebreak"; + level.bcSounds[ "kill_dialog" ][ "reaper" ] = "killReaper"; + level.bcSounds[ "kill_dialog" ][ "ruin" ] = "killMercenary"; + level.bcSounds[ "kill_dialog" ][ "seraph" ] = "killEnforcer"; + level.bcSounds[ "kill_dialog" ][ "trapper" ] = "killTrapper"; + level.bcSounds[ "kill_dialog" ][ "blackjack" ] = "killBlackjack"; + + if ( level.teambased && !isdefined( game["boostPlayersPicked"] ) ) + { + game["boostPlayersPicked"] = []; + foreach ( team in level.teams ) + { + game["boostPlayersPicked"][ team ] = false; + } + } + + level.allowbattlechatter = GetGametypeSetting( "allowBattleChatter" ); + + clientfield::register( "world", "boost_number", 1, 2, "int" ); + clientfield::register( "allplayers", "play_boost", 1, 2, "int" ); + + level thread pick_boost_number(); + + playerDialogBundles = struct::get_script_bundles( "mpdialog_player" ); + foreach( bundle in playerDialogBundles ) + { + count_keys( bundle, "killGeneric" ); + count_keys( bundle, "killSniper" ); + + count_keys( bundle, "killSpectre" ); + count_keys( bundle, "killGrenadier"); + count_keys( bundle, "killOutrider" ); + count_keys( bundle, "killTechnomancer" ); + count_keys( bundle, "killFirebreak" ); + count_keys( bundle, "killReaper" ); + count_keys( bundle, "killMercenary" ); + count_keys( bundle, "killEnforcer" ); + count_keys( bundle, "killTrapper" ); + count_keys( bundle, "killBlackjack" ); + +/* No separate aliases yet + count_keys( bundle, "gravspikesWeaponSuccess"; + count_keys( bundle, "overdriveAbilitySuccess"; + count_keys( bundle, "sparrowWeaponSuccess"; + count_keys( bundle, "visionpulseAbilitySuccess"; + count_keys( bundle, "tempestWeaponSuccess"; + count_keys( bundle, "glitchAbilitySuccess"; + count_keys( bundle, "warmachineWeaponSuccess"; + count_keys( bundle, "kineticArmorAbilitySuccess"; + count_keys( bundle, "annihilatorWeaponSuccess"; + count_keys( bundle, "combatfocusAbilitySuccess"; + count_keys( bundle, "hiveWeaponSuccess"; + count_keys( bundle, "rejackAbilitySuccess"; + count_keys( bundle, "scytheWeaponSuccess"; + count_keys( bundle, "psychosisAbilitySuccess"; + count_keys( bundle, "ripperWeaponSuccess"; + count_keys( bundle, "activeCamoAbilitySuccess"; + count_keys( bundle, "purifierWeaponSuccess"; + count_keys( bundle, "heatwaveAbilitySuccess"; +*/ + } + + level.allowSpecialistDialog = mpdialog_value( "enableHeroDialog", false ) && level.allowBattlechatter; + level.playStartConversation = mpdialog_value( "enableConversation", false ) && level.allowBattlechatter; +} + +function pick_boost_number( ) +{ + // Don't set client fields on the first frame + wait( 5 ); + + level clientfield::set( "boost_number", RandomInt( 4 ) ); +} + +function on_joined_team() +{ + self endon( "disconnect" ); + + if ( level.teambased ) + { + if ( self.team == "allies" ) + { + self set_blops_dialog(); + } + else + { + self set_cdp_dialog(); + } + } + else + { + if ( randomIntRange( 0, 2 ) ) + { + self set_blops_dialog(); + } + else + { + self set_cdp_dialog(); + } + } + + self globallogic_audio::flush_dialog(); + + if ( level.disablePrematchMessages === true ) + { + return; + } + + if ( ( isdefined( level.inPrematchPeriod ) && level.inPrematchPeriod ) && !( isdefined( self.pers["playedGameMode"] ) && self.pers["playedGameMode"] ) && isdefined( level.leaderDialog ) ) + { + if( level.hardcoreMode ) + self globallogic_audio::leader_dialog_on_player( level.leaderDialog.startHcGameDialog, undefined, undefined, undefined, true ); + else + self globallogic_audio::leader_dialog_on_player( level.leaderDialog.startGameDialog, undefined, undefined, undefined, true ); + + self.pers["playedGameMode"] = true; + } +} + +function set_blops_dialog() +{ + self.pers["mptaacom"] = "blops_taacom"; + self.pers["mpcommander"] = "blops_commander"; +} + +function set_cdp_dialog() +{ + self.pers["mptaacom"] = "cdp_taacom"; + self.pers["mpcommander"] = "cdp_commander"; +} + +function on_player_connect() +{ + self reset_dialog_fields(); +} + +function on_player_spawned() +{ + self reset_dialog_fields(); + + // help players be stealthy in splitscreen by not announcing their intentions + if ( level.splitscreen ) + { + return; + } + + self thread water_vox(); + + self thread grenade_tracking(); + self thread missile_tracking(); + self thread sticky_grenade_tracking(); + + // Don't bother with these in non-team games + if ( level.teambased ) + { + self thread enemy_threat(); + self thread check_boost_start_conversation(); + } +} + +function reset_dialog_fields() +{ + self.enemyThreatTime = 0; + self.heartbeatsnd = false; + + self.soundMod = "player"; + + self.voxUnderwaterTime = 0; + self.voxEmergeBreath = false; + self.voxDrowning = false; + + self.pilotisSpeaking = false; + self.playingDialog = false; + self.playingGadgetReadyDialog = false; + + self.playedGadgetSuccess = true; +} + +function dialog_chance( chanceKey ) +{ + dialogChance = mpdialog_value( chanceKey ); + + if ( !isdefined( dialogChance ) || dialogChance <= 0 ) + { + return false; + } + else if ( dialogChance >= 100 ) + { + return true; + } + + return ( RandomInt( 100 ) < dialogChance ); +} + +function mpdialog_value( mpdialogKey, defaultValue ) +{ + if ( !isdefined( mpdialogKey ) ) + { + return defaultValue; + } + + mpdialog = struct::get_script_bundle( "mpdialog", "mpdialog_default" ); + + if ( !isdefined( mpdialog ) ) + { + return defaultValue; + } + + structValue = GetStructField( mpdialog, mpdialogKey ); + + if ( !isdefined( structValue ) ) + { + return defaultValue; + } + + return structValue; +} + +function water_vox() +{ + self endon ( "death" ); + level endon ( "game_ended" ); + + while(1) + { + interval = mpdialog_value( "underwaterInterval", .05 ); + + if ( interval <= 0 ) + { + assert( interval > 0, "underWaterInterval mpdialog scriptbundle value must be greater than 0" ); + return; + } + + wait ( interval ); + + if ( util::isPropHuntGametype() && self util::isProp() ) + continue; + + if ( self IsPlayerUnderwater() ) + { + if ( !self.voxUnderwaterTime && !self.voxEmergeBreath ) + { + self StopSounds(); + self.voxUnderwaterTime = GetTime(); + } + else if ( self.voxUnderwaterTime ) + { + if ( GetTime() > self.voxUnderwaterTime + mpdialog_value( "underwaterBreathTime", 0 ) * 1000 ) + { + self.voxUnderwaterTime = 0; + self.voxEmergeBreath = true; + } + } + } + else + { + if ( self.voxDrowning ) + { + self thread play_dialog( "exertEmergeGasp", 4 | 16, mpdialog_value( "playerExertBuffer", 0 ) ); + + self.voxDrowning = false; + self.voxEmergeBreath = false; + } + else if ( self.voxEmergeBreath ) + { + self thread play_dialog( "exertEmergeBreath", 4 | 16, mpdialog_value( "playerExertBuffer", 0 ) ); + self.voxEmergeBreath = false; + } + } + } +} + + +function pain_vox(meansofDeath) +{ + if( dialog_chance( "smallPainChance" ) ) + { + if( meansOfDeath == "MOD_DROWN" ) + { + dialogKey = "exertPainDrowning"; + self.voxDrowning = true; + } + else if ( meansofDeath == "MOD_FALLING" ) + { + dialogKey = "exertPainFalling"; + } + else if ( self IsPlayerUnderwater() ) + { + dialogKey = "exertPainUnderwater"; + } + else + { + dialogKey = "exertPain"; + } + + exertBuffer = mpdialog_value( "playerExertBuffer", 0 ); + self thread play_dialog( dialogKey, 30, exertBuffer ); + } +} + +function on_player_suicide_or_team_kill( player, type ) +{ + self endon( "death" ); + level endon( "game_ended" ); + + // make sure that this does not execute in the player killed callback time + waittillframeend; + + if( !level.teamBased ) + { + return; + } +} + +function on_player_near_explodable( object, type ) +{ + self endon( "death" ); + level endon( "game_ended" ); +} + +function enemy_threat() +{ + self endon( "death" ); + level endon( "game_ended" ); + + // Disable callouts in prop hunt + if ( util::isPropHuntGametype() ) + return; + + while(1) + { + self waittill ( "weapon_ads" ); + + if ( self HasPerk( "specialty_quieter" ) ) + { + continue; + } + + if( self.enemyThreatTime + ( mpdialog_value( "enemyContactInterval", 0 ) * 1000 ) >= getTime() ) + { + continue; + } + + closest_ally = self get_closest_player_ally( true ); + + if ( !isdefined ( closest_ally ) ) + { + continue; + } + + allyRadius = mpdialog_value( "enemyContactAllyRadius", 0 ); + + if ( DistanceSquared( self.origin, closest_ally.origin ) < allyRadius * allyRadius ) + { + eyePoint = self getEye(); + dir = AnglesToForward( self GetPlayerAngles() ); + + dir = dir * mpdialog_value( "enemyContactDistance", 0 ); + + endPoint = eyePoint + dir; + + traceResult = BulletTrace( eyePoint, endPoint, true, self ); + + if ( isdefined( traceResult["entity"] ) && traceResult["entity"].className == "player" && traceResult["entity"].team != self.team ) + { + if( dialog_chance( "enemyContactChance" ) ) + { + self thread play_dialog( "threatInfantry", 1 ); + + level notify ( "level_enemy_spotted", self.team); + + self.enemyThreatTime = GetTime(); + } + } + } + } +} + + +// self is killed +function killed_by_sniper( sniper ) +{ + self endon("disconnect"); + sniper endon("disconnect"); + level endon( "game_ended" ); + + if ( !level.teamBased ) + { + return false; + } + + if ( util::isPropHuntGametype() ) + return false; + + // make sure that this does not execute in the player killed callback time + waittillframeend; + + if( dialog_chance( "sniperKillChance" ) ) + { + closest_ally = self get_closest_player_ally(); + + allyRadius = mpdialog_value( "sniperKillAllyRadius", 0 ); + + if( isdefined( closest_ally ) && DistanceSquared( self.origin, closest_ally.origin ) < allyRadius * allyRadius ) + { + closest_ally thread play_dialog( "threatSniper", 1 ); + + sniper.spottedTime = GetTime(); + sniper.spottedBy = []; + + players = self get_friendly_players(); + players = ArraySort( players, self.origin ); + + voiceRadius = mpdialog_value( "playerVoiceRadius", 0 ); + voiceRadiusSq = voiceRadius * voiceRadius; + + foreach( player in players ) + { + if ( DistanceSquared( closest_ally.origin, player.origin) <= voiceRadiusSq ) + { + sniper.spottedBy[sniper.spottedBy.size] = player; + } + } + } + } +} + +// self is killed +function player_killed( attacker, killstreakType ) +{ + if ( !level.teamBased ) + { + return; + } + + if ( self === attacker ) + { + // Play hilarious 'Stop killing yourself' dialog + return; + } + + // make sure that this does not execute in the player killed callback time + waittillframeend; + + if( isdefined( killstreakType ) ) + { + if ( !isdefined( level.killstreaks[killstreakType] ) || + !isdefined( level.killstreaks[killstreakType].threatOnKill ) || + !level.killstreaks[killstreakType].threatOnKill || + !dialog_chance( "killstreakKillChance" ) ) + { + return; + } + + ally = battlechatter::get_closest_player_ally( true ); + allyRadius = mpdialog_value( "killstreakKillAllyRadius", 0 ); + + if ( isdefined( ally ) && DistanceSquared( self.origin, ally.origin ) < allyRadius * allyRadius ) + { + ally play_killstreak_threat( killstreakType ); + } + } +} + +function say_kill_battle_chatter( attacker, weapon, victim, inflictor ) +{ + if ( weapon.skipBattlechatterKill || + !isdefined( attacker ) || + !IsPlayer( attacker ) || + !IsAlive( attacker ) || + attacker IsRemoteControlling() || + attacker IsInVehicle() || + attacker IsWeaponViewOnlyLinked() || + !isdefined( victim ) || + !IsPlayer( victim ) || + util::isPropHuntGametype() ) + { + return; + } + + // Don't play kill chatter if the player died since initiating the attack + if ( isdefined( inflictor ) && !IsPlayer( inflictor ) && inflictor.birthtime < attacker.spawntime ) + { + return; + } + + if ( weapon.inventorytype == "hero" ) + { + if(!isdefined(attacker.heroweaponKillCount))attacker.heroweaponKillCount=0; + + attacker.heroweaponKillCount++; + + if ( !( isdefined( attacker.playedGadgetSuccess ) && attacker.playedGadgetSuccess ) && attacker.heroweaponKillCount === mpdialog_value( "heroWeaponKillCount", 0 ) ) + { + // TODO: Keep trying to play on each kill until successful + // TODO: Play to multiple enemies killed together + attacker thread play_gadget_success( weapon, "enemyKillDelay", victim ); + attacker thread hero_weapon_success_reaction(); + } + } + else if ( ( isdefined( attacker.speedburstOn ) && attacker.speedburstOn ) ) + { + if ( !( isdefined( attacker.speedburstKill ) && attacker.speedburstKill ) ) + { + speedBurstKillDist = mpdialog_value( "speedBurstKillDistance", 0 ); + if ( DistanceSquared( attacker.origin, victim.origin ) < speedBurstKillDist * speedBurstKillDist ) + { + attacker.speedburstKill = true; + } + } + } + else if ( attacker _gadget_camo::camo_is_inuse( ) || + ( isdefined( attacker.gadget_camo_off_time ) && attacker.gadget_camo_off_time + ( mpdialog_value( "camoKillTime", 0 ) * 1000 ) >= GetTime() ) ) + { + if ( !( isdefined( attacker.playedGadgetSuccess ) && attacker.playedGadgetSuccess ) ) + { + attacker thread play_gadget_success( GetWeapon( "gadget_camo" ), "enemyKillDelay", victim ); + } + } + else if ( dialog_chance( "enemyKillChance" ) ) + { + if ( isdefined( victim.spottedTime ) && + victim.spottedTime + mpdialog_value( "enemySniperKillTime", 0 ) >= GetTime() && + array::contains( victim.spottedBy, attacker ) && + dialog_chance( "enemySniperKillChance" ) ) + { + killDialog = attacker get_random_key( "killSniper" ); + } + else if ( dialog_chance( "enemyHeroKillChance" ) ) + { + victimDialogName = victim GetMpDialogName(); + killDialog = attacker get_random_key( level.bcSounds[ "kill_dialog" ][ victimDialogName ] ); + } + else + { + killDialog = attacker get_random_key( "killGeneric" ); + } + } + + // Clear sniper spotted fields + victim.spottedTime = undefined; + victim.spottedBy = undefined; + + if ( !isdefined( killDialog ) ) + { + return; + } + + attacker thread wait_play_dialog( mpdialog_value( "enemyKillDelay", 0 ), killDialog, 1, undefined, victim, "cancel_kill_dialog" ); +} + + +function grenade_tracking() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while(1) + { + self waittill ( "grenade_fire", grenade, weapon ); + + if ( !isdefined( grenade.weapon ) || + !isdefined( grenade.weapon.rootweapon ) || + !dialog_chance( "incomingProjectileChance" ) ) + { + continue; + } + + dialogKey = level.bcSounds[ "incoming_alert" ][ grenade.weapon.rootweapon.name ]; + + if ( isdefined( dialogKey ) ) + { + waittime = mpdialog_value( level.bcSounds[ "incoming_delay" ][ grenade.weapon.rootweapon.name ], .05 ); + level thread incoming_projectile_alert( self, grenade, dialogKey, waittime ); + } + } +} + +function missile_tracking() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while(1) + { + self waittill ( "missile_fire", missile, weapon ); + + if ( !isdefined( missile.item ) || + !isdefined( missile.item.rootweapon ) || + !dialog_chance( "incomingProjectileChance" ) ) + { + continue; + } + + dialogKey = level.bcSounds[ "incoming_alert" ][ missile.item.rootweapon.name ]; + + if ( isdefined ( dialogKey ) ) + { + waittime = mpdialog_value( level.bcSounds[ "incoming_delay" ][ missile.item.rootweapon.name ], .05 ); + level thread incoming_projectile_alert( self, missile, dialogKey, waittime ); + } + } +} + +function incoming_projectile_alert( thrower, projectile, dialogKey, waittime ) +{ + level endon( "game_ended" ); + if ( waittime <= 0 ) + { + assert( waittime > 0, "incoming_projectile_alert waittime must be greater than 0" ); + return; + } + + if ( util::isPropHuntGametype() ) + return; + + while(1) + { + wait( waittime ); + + // HACK: This is a crazy way to try and trigger the warning more often + if ( waittime > 0.2 ) + { + waittime = waittime / 2; + } + + // The projectile may have blown up or the like while waiting + if ( !isdefined( projectile ) ) + { + return; + } + + //Check if player threw grenade and then quit or switched to spectator + if( !isdefined( thrower ) || thrower.team == "spectator" ) + { + return; + } + + if( ( level.players.size ) ) + { + closest_enemy = thrower get_closest_player_enemy( projectile.origin ); + + incomingProjectileRadius = mpdialog_value( "incomingProjectileRadius", 0 ); + + if( isdefined( closest_enemy ) && DistanceSquared( projectile.origin, closest_enemy.origin ) < incomingProjectileRadius * incomingProjectileRadius ) + { + closest_enemy thread play_dialog( dialogKey, 6 ); + return; + } + } + } +} + +function sticky_grenade_tracking() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while(1) + { + self waittill ( "grenade_stuck", grenade ); + + if ( IsAlive( self ) && isdefined( grenade ) && isdefined( grenade.weapon ) ) + { + if ( grenade.weapon.rootweapon.name == "sticky_grenade" ) + { + self thread play_dialog( "stuckSticky", 6 ); + } + } + } +} + +function hero_weapon_success_reaction() +{ + self endon( "death" ); + level endon( "game_ended" ); + + if ( !level.teambased ) + { + return; + } + + if ( util::isPropHuntGametype() ) + return; + + allies = []; + + allyRadiusSq = mpdialog_value( "playerVoiceRadius", 0 ); + allyRadiusSq *= allyRadiusSq; + + foreach( player in level.players ) + { + if ( !isdefined( player ) || + !IsAlive( player ) || + player.sessionstate != "playing" || + player == self || + player.team != self.team ) + { + continue; + } + + distSq = DistanceSquared( self.origin, player.origin ); + + if ( distSq > allyRadiusSq ) + { + continue; + } + + allies[allies.size] = player; + } + + // First do the kill delay wait + wait( mpdialog_value( "enemyKillDelay", 0 ) + 0.1 ); + + // Wait for the player to finish talking + while ( self.playingDialog ) + { + wait( 0.5 ); + } + + allies = ArraySort( allies, self.origin ); + + foreach( player in allies ) + { + if ( !IsAlive( player ) || + player.sessionstate != "playing" || + player.playingDialog || + player IsPlayerUnderwater() || + player IsRemoteControlling() || + player IsInVehicle() || + player IsWeaponViewOnlyLinked() ) + { + continue; + } + + distSq = DistanceSquared( self.origin, player.origin ); + + if ( distSq > allyRadiusSq ) + { + break; + } + + player play_dialog( "heroWeaponSuccessReaction", 1 ); + break; + } +} + +function play_promotion_reaction() +{ + self endon( "death" ); + level endon( "game_ended" ); + + if ( !level.teambased ) + { + return; + } + + if ( util::isPropHuntGametype() && self util::isProp() ) + return; + + // Wait for the music cue to finish / start fading + wait 9; + + players = self get_friendly_players(); + players = ArraySort( players, self.origin ); + + selfDialog = self GetMpDialogName(); + voiceRadius = mpdialog_value( "playerVoiceRadius", 0 ); + voiceRadiusSq = voiceRadius * voiceRadius; + + foreach( player in players ) + { + if ( player == self || + player GetMpDialogName() == selfDialog || + !player can_play_dialog( true ) || + DistanceSquared( self.origin, player.origin ) >= voiceRadiusSq ) + { + continue; + } + + dialogAlias = player get_player_dialog_alias( "promotionReaction" ); + + if ( !isdefined( dialogAlias ) ) + { + continue; + } + + ally = player; + break; + } + + if ( isdefined( ally ) ) + { + ally PlaySoundOnTag( dialogAlias, "J_Head", undefined, self ); + // The ally won't know why they can't talk, but they won't interrupt themselves either + ally thread wait_dialog_buffer( mpdialog_value( "playerDialogBuffer", 0 ) ); + } +} + +function gametype_specific_battle_chatter( event, team ) +{ + self endon ( "death" ); + level endon( "game_ended" ); +} + +function play_death_vox( body, attacker, weapon, meansOfDeath ) +{ + dialogKey = self get_death_vox( weapon, meansOfDeath ); + dialogAlias = self get_player_dialog_alias( dialogKey ); + + if ( isdefined( dialogAlias ) ) + { + body PlaySoundOnTag( dialogAlias, "J_Head" ); + } +} + +function get_death_vox( weapon, meansOfDeath ) +{ + // Always play drowned if underwater + if ( self IsPlayerUnderwater() ) + { + return "exertDeathDrowned"; + } + + if ( isdefined( meansOfDeath ) ) + { + switch( meansOfDeath ) + { + case "MOD_BURNED": + return "exertDeathBurned"; + case "MOD_DROWN": + return "exertDeathDrowned"; + } + } + + if ( isdefined( weapon ) && meansOfDeath !== "MOD_MELEE_WEAPON_BUTT" ) + { + switch( weapon.rootweapon.name ) + { + case "knife_loadout": + case "hatchet": + case "hero_armblade": + return "exertDeathStabbed"; + case "hero_firefly_swarm": + return "exertDeathBurned"; + case "hero_lightninggun_arc": + return "exertDeathElectrocuted"; + } + } + + return "exertDeath"; +} + +function play_killstreak_threat( killstreakType ) +{ + if ( !isdefined( killstreakType ) || !isdefined( level.killstreaks[killstreakType] ) ) + { + return; + } + + self thread play_dialog( level.killstreaks[killstreakType].threatDialogKey, 1 ); +} + +function wait_play_dialog( waitTime, dialogKey, dialogFlags, dialogBuffer, enemy, endNotify ) +{ + self endon( "death" ); + level endon( "game_ended" ); + + if (isdefined( waitTime) && waitTime > 0 ) + { + if ( isdefined( endNotify ) ) + { + self endon( endNotify ); + } + + wait ( waitTime ); + } + + self thread play_dialog( dialogKey, dialogFlags, dialogBuffer, enemy ); +} + +function play_dialog( dialogKey, dialogFlags, dialogBuffer, enemy ) +{ + self endon( "death" ); + level endon( "game_ended" ); + + if ( !isdefined( dialogKey ) || + !IsPlayer( self ) || + !IsAlive( self ) || + level.gameEnded ) + { + return; + } + + if ( !isdefined( dialogFlags ) ) + { + dialogFlags = 0; + } + + if ( !level.allowSpecialistDialog && ( dialogFlags & 16 ) == 0 ) + { + return; + } + + if ( !isdefined( dialogBuffer ) ) + { + dialogBuffer = mpdialog_value( "playerDialogBuffer", 0 ); + } + + dialogAlias = self get_player_dialog_alias( dialogKey ); + + if ( !isdefined( dialogAlias ) ) + { + return; + } + + if ( self IsPlayerUnderwater() && !( dialogFlags & 8 ) ) + { + return; + } + + if ( self.playingDialog ) + { + if ( !( dialogFlags & 4 ) ) + { + return; + } + + self StopSounds(); + + {wait(.05);}; + } + + if ( dialogFlags & 32 ) + { + self.playingGadgetReadyDialog = true; + } + + if ( dialogFlags & 64 ) + { + if(!isdefined(self.stolenDialogIndex))self.stolenDialogIndex=0; + + dialogAlias = dialogAlias + "_0" + self.stolenDialogIndex; + + self.stolenDialogIndex++; + self.stolenDialogIndex = self.stolenDialogIndex % 4; + } + + if ( dialogFlags & 2 ) + { + // Plays to all teams + self PlaySoundOnTag( dialogAlias, "J_Head" ); + } + else if ( dialogFlags & 1 ) + { + // Plays to current team + if ( isdefined( enemy ) ) + { + // And the specified enemy + self PlaySoundOnTag( dialogAlias, "J_Head", self.team, enemy ); + } + else + { + self PlaySoundOnTag( dialogAlias, "J_Head", self.team ); + } + } + else + { + self PlayLocalSound( dialogAlias ); + } + + // FUTURE: Pass dialogKey, dialogBuffer if useful + self notify( "played_dialog" ); + + self thread wait_dialog_buffer( dialogBuffer ); +} + +function wait_dialog_buffer( dialogBuffer ) +{ + self endon( "death" ); + self endon( "played_dialog" ); + self endon( "stop_dialog" ); + level endon( "game_ended" ); + + self.playingDialog = true; + + if ( isdefined( dialogBuffer ) && dialogBuffer > 0 ) + { + wait ( dialogBuffer ); + } + + self.playingDialog = false; + self.playingGadgetReadyDialog = false; +} + +function stop_dialog() +{ + self notify( "stop_dialog" ); + + self StopSounds(); + + self.playingDialog = false; + self.playingGadgetReadyDialog = false; +} + +function wait_playback_time( soundAlias ) +{ + //self endon( "death" ); + //level endon( "game_ended" ); +} + +function get_player_dialog_alias( dialogKey ) +{ + if ( !IsPlayer( self ) ) + { + return undefined; + } + + bundleName = self GetMpDialogName(); + + if ( !isdefined( bundleName ) ) + { + return undefined; + } + + playerBundle = struct::get_script_bundle( "mpdialog_player", bundleName ); + + if ( !isdefined( playerBundle ) ) + { + return undefined; + } + + return globallogic_audio::get_dialog_bundle_alias( playerBundle, dialogKey ); +} + +function count_keys( bundle, dialogKey ) +{ + i = 0; + field = dialogKey + i; + fieldValue = GetStructField( bundle, field ); + + while ( isdefined( fieldValue ) ) + { + aliasArray[i] = fieldValue; + + i++; + field = dialogKey + i; + fieldValue = GetStructField( bundle, field ); + } + + if ( !isdefined( bundle.keyCounts ) ) + { + bundle.keyCounts = []; + } + + bundle.keyCounts[dialogKey] = i; +} + +function get_random_key( dialogKey ) +{ + bundleName = self GetMpDialogName(); + + if ( !isdefined( bundleName ) ) + { + return undefined; + } + + playerBundle = struct::get_script_bundle( "mpdialog_player", bundleName ); + + if ( !isdefined( playerBundle ) || + !isdefined( playerBundle.keyCounts ) || + !isdefined( playerBundle.keyCounts[dialogKey] ) ) + { + return dialogKey; + } + + return dialogKey + RandomInt( playerBundle.keyCounts[dialogKey] ); +} + +// These are called from shared scripts via function pointers +function play_gadget_ready( weapon, userFlip = false ) +{ + if ( !isdefined( weapon ) ) + return; + + dialogKey = undefined; + + switch( weapon.name ) + { + case "hero_gravityspikes": + dialogKey = "gravspikesWeaponReady"; + break; + case "gadget_speed_burst": + dialogKey = "overdriveAbilityReady"; + break; + case "hero_bowlauncher": + case "hero_bowlauncher2": + case "hero_bowlauncher3": + case "hero_bowlauncher4": + dialogKey = "sparrowWeaponReady"; + break; + case "gadget_vision_pulse": + dialogKey = "visionpulseAbilityReady"; + break; + case "hero_lightninggun": + case "hero_lightninggun_arc": + dialogKey = "tempestWeaponReady"; + break; + case "gadget_flashback": + dialogKey = "glitchAbilityReady"; + break; + case "hero_pineapplegun": + dialogKey = "warmachineWeaponREady"; + break; + case "gadget_armor": + dialogKey = "kineticArmorAbilityReady"; + break; + case "hero_annihilator": + dialogKey = "annihilatorWeaponReady"; + break; + case "gadget_combat_efficiency": + dialogKey = "combatfocusAbilityReady"; + break; + case "hero_chemicalgelgun": + dialogKey = "hiveWeaponReady"; + break; + case "gadget_resurrect": + dialogKey = "rejackAbilityReady"; + break; + case "hero_minigun": + case "hero_minigun_body3": + dialogKey = "scytheWeaponReady"; + break; + case "gadget_clone": + dialogKey = "psychosisAbilityReady"; + break; + case "hero_armblade": + dialogKey = "ripperWeaponReady"; + break; + case "gadget_camo": + dialogKey = "activeCamoAbilityReady"; + break; + case "hero_flamethrower": + dialogKey = "purifierWeaponReady"; + break; + case "gadget_heat_wave": + dialogKey = "heatwaveAbilityReady"; + break; + default: + return; + } + + // Just a regular ready + if ( !( isdefined( self.isThief ) && self.isThief ) && !( isdefined( self.isRoulette ) && self.isRoulette ) ) + { + self thread play_dialog( dialogKey ); + return; + } + + waitTime = 0; + dialogFlags = 32; + + if ( userFlip ) // User flipped or rerolled, kill the previous gadget dialog + { + minWaitTime = 0; + if ( self.playingGadgetReadyDialog ) + { + self stop_dialog(); + minWaitTime = .05; // Make sure dialog stops + } + + if ( ( isdefined( self.isThief ) && self.isThief ) ) + { + delayKey = "thiefFlipDelay"; + } + else + { + delayKey = "rouletteFlipDelay"; + } + + waitTime = mpdialog_value( delayKey, minWaitTime ); + dialogFlags += 64; + } + else // Initial roll or stolen weapon + { + if ( ( isdefined( self.isThief ) && self.isThief ) ) + { + genericKey = "thiefWeaponReady"; + repeatKey = "thiefWeaponRepeat"; + repeatThresholdKey = "thiefRepeatThreshold"; + chanceKey = "thiefReadyChance"; + delayKey = "thiefRevealDelay"; + } + else + { + genericKey = "rouletteAbilityReady"; + repeatKey = "rouletteAbilityRepeat"; + repeatThresholdKey = "rouletteRepeatThreshold"; + chanceKey = "rouletteReadyChance"; + delayKey = "rouletteRevealDelay"; + } + + if ( RandomInt( 100 ) < mpdialog_value( chanceKey, 0 ) ) + { + // Play generic earned dialog + dialogKey = genericKey; + } + else + { + waitTime = mpdialog_value( delayKey, 0 ); + + if ( self.lastStolenGadget === weapon && + ( self.lastStolenGadgetTime + mpdialog_value( repeatThresholdKey, 0 ) * 1000 ) > GetTime() ) + { + // Play duplicate earned dialog + dialogKey = repeatKey; + } + else + { + dialogFlags += 64; + } + } + } + + self.lastStolenGadget = weapon; + self.lastStolenGadgetTime = GetTime(); + + if ( waitTime ) // Generic lines play instantly + { + // As long as we're not playing dialog, block any kill lines coming in to play the gadget ready + self notify( "cancel_kill_dialog" ); + } + + self thread wait_play_dialog( waitTime, dialogKey, dialogFlags ); +} + +function play_gadget_activate( weapon ) +{ + if ( !isdefined( weapon ) ) + return; + + dialogKey = undefined; + + switch( weapon.name ) + { + case "hero_gravityspikes": + dialogKey = "gravspikesWeaponUse"; + dialogFlags = 6 | 16; + dialogBuffer = 0.05; + break; + case "gadget_speed_burst": + dialogKey = "overdriveAbilityUse"; + break; + case "hero_bowlauncher": + case "hero_bowlauncher2": + case "hero_bowlauncher3": + case "hero_bowlauncher4": + dialogKey = "sparrowWeaponUse"; + break; + case "gadget_vision_pulse": + dialogKey = "visionpulseAbilityUse"; + break; + case "hero_lightninggun": + case "hero_lightninggun_arc": + dialogKey = "tempestWeaponUse"; + break; + case "gadget_flashback": + dialogKey = "glitchAbilityUse"; + break; + case "hero_pineapplegun": + dialogKey = "warmachineWeaponUse"; + break; + case "gadget_armor": + dialogKey = "kineticArmorAbilityUse"; + break; + case "hero_annihilator": + dialogKey = "annihilatorWeaponUse"; + break; + case "gadget_combat_efficiency": + dialogKey = "combatfocusAbilityUse"; + break; + case "hero_chemicalgelgun": + dialogKey = "hiveWeaponUse"; + break; + case "gadget_resurrect": + dialogKey = "rejackAbilityUse"; + break; + case "hero_minigun": + case "hero_minigun_body3": + dialogKey = "scytheWeaponUse"; + break; + case "gadget_clone": + dialogKey = "psychosisAbilityUse"; + break; + case "hero_armblade": + dialogKey = "ripperWeaponUse"; + break; + case "gadget_camo": + dialogKey = "activeCamoAbilityUse"; + break; + case "hero_flamethrower": + dialogKey = "purifierWeaponUse"; + break; + case "gadget_heat_wave": + dialogKey = "heatwaveAbilityUse"; + break; + default: + return; + } + + self thread play_dialog( dialogKey, dialogFlags, dialogBuffer ); +} + +function play_gadget_success( weapon, waitKey, victim ) +{ + if ( !isdefined( weapon ) ) + return; + + dialogKey = undefined; + + switch( weapon.name ) + { + case "hero_gravityspikes": + dialogKey = "gravspikesWeaponSuccess"; + break; + case "gadget_speed_burst": + dialogKey = "overdriveAbilitySuccess"; + break; + case "hero_bowlauncher": + case "hero_bowlauncher2": + case "hero_bowlauncher3": + case "hero_bowlauncher4": + dialogKey = "sparrowWeaponSuccess"; + break; + case "gadget_vision_pulse": + dialogKey = "visionpulseAbilitySuccess"; + break; + case "hero_lightninggun": + case "hero_lightninggun_arc": + dialogKey = "tempestWeaponSuccess"; + break; + case "gadget_flashback": + dialogKey = "glitchAbilitySuccess"; + break; + case "hero_pineapplegun": + dialogKey = "warmachineWeaponSuccess"; + break; + case "gadget_armor": + dialogKey = "kineticArmorAbilitySuccess"; + break; + case "hero_annihilator": + dialogKey = "annihilatorWeaponSuccess"; + break; + case "gadget_combat_efficiency": + dialogKey = "combatfocusAbilitySuccess"; + break; + case "hero_chemicalgelgun": + dialogKey = "hiveWeaponSuccess"; + break; + case "gadget_resurrect": + dialogKey = "rejackAbilitySuccess"; + break; + case "hero_minigun": + case "hero_minigun_body3": + dialogKey = "scytheWeaponSuccess"; + break; + case "gadget_clone": + dialogKey = "psychosisAbilitySuccess"; + break; + case "hero_armblade": + dialogKey = "ripperWeaponSuccess"; + break; + case "gadget_camo": + dialogKey = "activeCamoAbilitySuccess"; + break; + case "hero_flamethrower": + dialogKey = "purifierWeaponSuccess"; + break; + case "gadget_heat_wave": + dialogKey = "heatwaveAbilitySuccess"; + break; + default: + return; + } + + if ( isdefined( waitKey ) ) + { + waitTime = mpdialog_value( waitKey, 0 ); + } + + //dialogKey = get_random_key( dialogKey ); + dialogKey = dialogKey + "0"; + + self.playedGadgetSuccess = true; + self thread wait_play_dialog( waitTime, dialogKey, 1, undefined, victim ); +} + +function play_throw_hatchet() +{ + self thread play_dialog( "exertAxeThrow", 1 | 4 | 16, mpdialog_value( "playerExertBuffer", 0 ) ); +} + +// Utils for getting enemies / allies + +function get_enemy_players() +{ + players = []; + + if ( level.teambased ) + { + foreach( team in level.teams ) + { + if ( team == self.team ) + { + continue; + } + + foreach( player in level.alivePlayers[team] ) + { + players[players.size] = player; + } + } + } + else + { + foreach( player in level.activeplayers ) + { + if ( player != self ) + { + players[players.size] = player; + } + } + } + + return players; +} + +function get_friendly_players() +{ + players = []; + + if ( level.teambased ) + { + foreach( player in level.alivePlayers[self.team] ) + { + players[players.size] = player; + } + } + else + { + players[0] = self; + } + + return players; +} + +function can_play_dialog( teamOnly ) +{ + if ( !IsPlayer( self ) || + !IsAlive( self ) || + self.playingDialog === true || + self IsPlayerUnderwater() || + self IsRemoteControlling() || + self IsInVehicle() || + self IsWeaponViewOnlyLinked() ) + { + return false; + } + + if ( isdefined( teamOnly ) && !teamOnly && self HasPerk( "specialty_quieter" ) ) + { + return false; + } + + return true; +} + +// Call this on the owning/controlling/attacking player to keep them from being picked in non-team games +function get_closest_player_enemy( origin, teamOnly ) +{ + if(!isdefined(origin))origin=self.origin; + + players = self get_enemy_players(); + players = ArraySort( players, origin ); + + foreach( player in players ) + { + if( !player can_play_dialog( teamOnly ) ) + { + continue; + } + + return player; + } + + return undefined; +} + +function get_closest_player_ally( teamOnly ) +{ + if ( !level.teambased ) + { + return undefined; + } + + players = self get_friendly_players(); + players = ArraySort( players, self.origin ); + + foreach( player in players ) + { + if ( player == self || + !player can_play_dialog( teamOnly ) ) + { + continue; + } + + return player; + } + + return undefined; +} + +// Boost Start conversation + +function check_boost_start_conversation() +{ + if ( !level.playStartConversation ) + { + return; + } + + if ( !level.inPrematchPeriod || + !level.teambased || + game["boostPlayersPicked"][ self.team ] ) + { + return; + } + + players = self get_friendly_players(); + + // The spawned player isn't in level.alivePlayers yet + array::add( players, self, false ); + + players = array::randomize( players ); + + playerIndex = 1; + foreach( player in players ) + { + playerDialog = player GetMpDialogName(); + + for( i = playerIndex; i < players.size; i++ ) + { + playerI = players[i]; + + if ( playerDialog != playerI GetMpDialogName() ) + { + pick_boost_players( player, playerI ); + return; + } + } + + playerIndex++; + } +} + +function pick_boost_players( player1, player2 ) +{ + player1 clientfield::set( "play_boost", 1 ); + player2 clientfield::set( "play_boost", 2 ); + + game["boostPlayersPicked"][player1.team] = true; +} + +// Game end dialog + +function game_end_vox( winner ) +{ + if ( !level.allowSpecialistDialog ) + { + return; + } + + gameIsDraw = !isdefined( winner ) || ( level.teamBased && winner == "tie" ); + + foreach( player in level.players ) + { + if ( player IsSplitScreen() ) + { + continue; + } + + if ( gameIsDraw ) + { + dialogKey = "boostDraw"; + } + else if ( ( level.teamBased && isdefined( level.teams[ winner ] ) && player.pers["team"] == winner ) || + ( !level.teamBased && player == winner ) ) + { + dialogKey = "boostWin"; + } + else + { + dialogKey = "boostLoss"; + } + + dialogAlias = player get_player_dialog_alias( dialogKey ); + + if ( isdefined( dialogAlias ) ) + { + player PlayLocalSound( dialogAlias ); + } + } +} + +/# + +function devgui_think() +{ + SetDvar( "devgui_mpdialog", "" ); + + SetDvar( "testalias_player", "vox_ruin_hero_weap_success" ); + SetDvar( "testalias_taacom", "vox_tacb_kls_vsat_timeout" ); + SetDvar( "testalias_commander", "vox_ancb_kls_counteruav_multiple" ); + + while( 1 ) + { + wait( 1 ); + + player = util::getHostPlayer(); + + if ( !isdefined( player ) ) + continue; + + spacing = GetDvarFloat( "testdialog_spacing", 0.25 ); + + switch( GetDvarString( "devgui_mpdialog", "" ) ) + { + case "self-taacom-commander": + player thread test_player_dialog( 0 ); + player thread test_taacom_dialog( spacing ); + player thread test_commander_dialog( 2 * spacing ); + break; + case "self-commander": + player thread test_player_dialog( 0 ); + player thread test_commander_dialog( spacing ); + break; + case "other-commander": + player thread test_other_dialog( 0 ); + player thread test_commander_dialog( spacing ); + break; + case "taacom-commander": + player thread test_taacom_dialog( 0 ); + player thread test_commander_dialog( spacing ); + break; + case "self-taacom": + player thread test_player_dialog( 0 ); + player thread test_taacom_dialog( spacing ); + break; + case "other-taacom": + player thread test_other_dialog( 0 ); + player thread test_taacom_dialog( spacing ); + break; + case "other-self": + player thread test_other_dialog( 0 ); + player thread test_player_dialog( spacing ); + break; + case "conv-self-other": + player thread play_conv_self_other(); + break; + case "conv-other-self": + player thread play_conv_other_self(); + break; + case "conv-other-other": + player thread play_conv_other_other(); + break; + } + + SetDvar( "devgui_mpdialog", "" ); + } +} + +function test_other_dialog( delay ) +{ + players = ArraySort( level.players, self.origin ); + + foreach( player in players ) + { + if ( player != self && IsAlive( player ) ) + { + player thread test_player_dialog( delay ); + return; + } + } +} + +function test_player_dialog( delay ) +{ + if(!isdefined(delay))delay=0; + + wait delay; + + self PlaySoundOnTag( GetDvarString( "testalias_player", "" ), "J_Head" ); +} + +function test_taacom_dialog( delay ) +{ + if(!isdefined(delay))delay=0; + + wait delay; + + self playLocalSound( GetDvarString( "testalias_taacom", "" ) ); +} + +function test_commander_dialog( delay ) +{ + if(!isdefined(delay))delay=0; + + wait delay; + + self playLocalSound( GetDvarString( "testalias_commander", "" ) ); +} + +function play_test_dialog( dialogKey ) +{ + dialogAlias = self get_player_dialog_alias( dialogKey ); + + self PlaySoundOnTag( dialogAlias, "J_Head" ); +} + +function response_key() +{ + switch( self GetMpDialogName() ) + { + case "assassin": + return "Spectre"; + case "grenadier": + return"Grenadier"; + case "outrider": + return"Outrider"; + case "prophet": + return"Technomancer"; + case "pyro": + return"Firebreak"; + case "reaper": + return"Reaper"; + case "ruin": + return"Mercenary"; + case "seraph": + return"Enforcer"; + case "trapper": + return"Trapper"; + } + + return ""; +} + +function play_conv_self_other() +{ + num = RandomIntRange( 0, 4 ); + + self play_test_dialog( "boostStart" + num ); + + wait 4; + + players = ArraySort( level.players, self.origin ); + + foreach( player in players ) + { + if ( player != self && IsAlive( player ) ) + { + player play_test_dialog( "boostStartResp" + self response_key() + num ); + break; + } + } +} + +function play_conv_other_self() +{ + num = RandomIntRange( 0, 4 ); + + players = ArraySort( level.players, self.origin ); + + foreach( player in players ) + { + if ( player != self && IsAlive( player ) ) + { + player play_test_dialog( "boostStart" + num ); + break; + } + } + + wait 4; + + self play_test_dialog( "boostStartResp" + player response_key() + num ); +} + +function play_conv_other_other() +{ + num = RandomIntRange( 0, 4 ); + + players = ArraySort( level.players, self.origin ); + + foreach( player in players ) + { + if ( player != self && IsAlive( player ) ) + { + player play_test_dialog( "boostStart" + num ); + firstPlayer = player; + break; + } + } + + wait 4; + + foreach( player in players ) + { + if ( player != self && player !== firstPlayer && IsAlive( player ) ) + { + player play_test_dialog( "boostStartResp" + firstPlayer response_key() + num ); + break; + } + } +} + +#/ diff --git a/mp/gametypes/_classicmode.csc b/mp/gametypes/_classicmode.csc new file mode 100644 index 0000000..d2cfe96 --- /dev/null +++ b/mp/gametypes/_classicmode.csc @@ -0,0 +1,62 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + +#namespace classicmode; + +function autoexec __init__sytem__() { system::register("classicmode",&__init__,undefined,undefined); } + +function __init__() +{ + level.classicMode = GetGametypeSetting( "classicMode" ); + if( level.classicMode ) + { + EnableClassicMode(); + } +} + +function EnableClassicMode() +{ + // KEEP THESE IN SYNCE WITH CLASSMODE.GSC + + // Melee + SetDvar( "bg_t7BlockMeleeUsageTime", 100 ); + + // Double Jump + SetDvar( "doublejump_enabled", 0 ); + + // Wall Run + SetDvar( "wallRun_enabled", 0 ); + + // Slide + SetDvar( "slide_maxTime", 550 ); + SetDvar( "playerEnergy_slideEnergyEnabled", 0 ); + + //TRM + SetDvar( "trm_maxSideMantleHeight", 0 ); + SetDvar( "trm_maxBackMantleHeight", 0 ); + + // Swimming + SetDvar( "player_swimming_enabled", 0 ); + SetDvar( "player_swimHeightRatio", 0.9 ); + + // Sprint + SetDvar( "player_sprintSpeedScale", 1.5 ); + SetDvar( "jump_slowdownEnable", 1 ); + SetDvar( "player_sprintUnlimited", 0 ); + SetDvar( "sprint_allowRestore", 0 ); + SetDvar( "sprint_allowReload", 0 ); + SetDvar( "sprint_allowRechamber", 0 ); + + // Blur Time + SetDvar( "cg_blur_time", 500 ); + + SetDvar( "tu11_enableClassicMode", 1 ); +} \ No newline at end of file diff --git a/mp/gametypes/_classicmode.gsc b/mp/gametypes/_classicmode.gsc new file mode 100644 index 0000000..2862198 --- /dev/null +++ b/mp/gametypes/_classicmode.gsc @@ -0,0 +1,71 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\spawner_shared; +#using scripts\shared\system_shared; + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_player; +#using scripts\mp\gametypes\_globallogic_utils; + + + + + +#namespace classicmode; + +function autoexec __init__sytem__() { system::register("classicmode",&__init__,undefined,undefined); } + +function __init__() +{ + //clientfield::register( "world", "classicmode", VERSION_SHIP, 1, "int" ); + level.classicMode = GetGametypeSetting( "classicMode" ); + if( level.classicMode ) + { + EnableClassicMode(); + } +} + +function EnableClassicMode() +{ + //if ( GetDvarint( "tu11_enableClassicMode") == 1 ) + { + //level clientfield::set( "classicmode", 1 ); + + // KEEP THESE IN SYNCE WITH CLASSMODE.CSC + + // Melee + SetDvar( "bg_t7BlockMeleeUsageTime", 100 ); + + // Double Jump + SetDvar( "doublejump_enabled", 0 ); + + // Wall Run + SetDvar( "wallRun_enabled", 0 ); + + // Slide + SetDvar( "slide_maxTime", 550 ); + SetDvar( "playerEnergy_slideEnergyEnabled", 0 ); + + //TRM + SetDvar( "trm_maxSideMantleHeight", 0 ); + SetDvar( "trm_maxBackMantleHeight", 0 ); + + // Swimming + SetDvar( "player_swimming_enabled", 0 ); + SetDvar( "player_swimHeightRatio", 0.9 ); + + // Sprint + SetDvar( "player_sprintSpeedScale", 1.5 ); + SetDvar( "jump_slowdownEnable", 1 ); + SetDvar( "player_sprintUnlimited", 0 ); + SetDvar( "sprint_allowRestore", 0 ); + SetDvar( "sprint_allowReload", 0 ); + SetDvar( "sprint_allowRechamber", 0 ); + + // Blur Time + SetDvar( "cg_blur_time", 500 ); + + SetDvar( "tu11_enableClassicMode", 1 ); + } +} \ No newline at end of file diff --git a/mp/gametypes/_clientids.gsc b/mp/gametypes/_clientids.gsc new file mode 100644 index 0000000..2ce8627 --- /dev/null +++ b/mp/gametypes/_clientids.gsc @@ -0,0 +1,38 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; + + + +#namespace clientids; + +function autoexec __init__sytem__() { system::register("clientids",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); + callback::on_connect( &on_player_connect ); +} + +function init() +{ + // this is now handled in code ( not lan ) + // see s_nextScriptClientId + level.clientid = 0; +} + +function on_player_connect() +{ + self.clientid = matchRecordNewPlayer( self ); + if ( !isdefined( self.clientid ) || self.clientid == -1 ) + { + self.clientid = level.clientid; + level.clientid++; // Is this safe? What if a server runs for a long time and many people join/leave + } + + /# + PrintLn( "client: " + self.name + " clientid: " + self.clientid); + #/ +} + \ No newline at end of file diff --git a/mp/gametypes/_deathicons.gsc b/mp/gametypes/_deathicons.gsc new file mode 100644 index 0000000..5ca6d60 --- /dev/null +++ b/mp/gametypes/_deathicons.gsc @@ -0,0 +1,99 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic_utils; + +#precache( "objective", "headicon_dead" ); + +#namespace deathicons; + +function autoexec __init__sytem__() { system::register("deathicons",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); + callback::on_connect( &on_player_connect ); +} + +function init() +{ + if ( !isdefined( level.ragdoll_override ) ) + { + level.ragdoll_override = &ragdoll_override; + } + if (!level.teambased) + return; +} + +function on_player_connect() +{ + self.selfDeathIcons = []; // icons that other people see which point to this player when he's dead +} + +function update_enabled() +{ + //if (!self.enableDeathIcons) + // self removeOtherDeathIcons(); +} + +function add( entity, dyingplayer, team, timeout ) +{ + if ( !level.teambased ) + return; + + iconOrg = entity.origin; + + dyingplayer endon("spawned_player"); + dyingplayer endon("disconnect"); + + wait .05; + util::WaitTillSlowProcessAllowed(); + + assert(isdefined( level.teams[team] )); + assert(isdefined( level.teamIndex[team] )); + + if ( GetDvarString( "ui_hud_showdeathicons" ) == "0" ) + return; + + if ( level.hardcoreMode ) + return; + + deathIconObjId = gameobjects::get_next_obj_id(); + objective_add( deathIconObjId, "active", iconOrg, &"headicon_dead" ); + objective_team( deathIconObjId, team ); + + level thread destroy_slowly( timeout, deathIconObjId ); +} + +function destroy_slowly( timeout, deathIconObjId ) +{ + wait timeout; + + objective_state( deathIconObjId, "done" ); + + wait 1.0; + + objective_delete( deathIconObjId ); + gameobjects::release_obj_id( deathIconObjId ); +} + + +function ragdoll_override( iDamage, sMeansOfDeath, sWeapon, sHitLoc, vDir, vAttackerOrigin, deathAnimDuration, eInflictor, ragdoll_jib, body ) +{ + if ( sMeansOfDeath == "MOD_FALLING" && self IsOnGround() == true ) + { + body startRagDoll(); + + if ( !isDefined( self.switching_teams ) ) + thread add( body, self, self.team, 5.0 ); + return true; + } + return false; +} + diff --git a/mp/gametypes/_dev.gsc b/mp/gametypes/_dev.gsc new file mode 100644 index 0000000..4b88956 --- /dev/null +++ b/mp/gametypes/_dev.gsc @@ -0,0 +1,3509 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\dev_shared; +#using scripts\shared\hud_message_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_dev_class; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hud_message; +#using scripts\mp\gametypes\_killcam; + +#using scripts\mp\_util; +#using scripts\shared\bots\_bot; +#using scripts\mp\killstreaks\_helicopter; +#using scripts\mp\killstreaks\_helicopter_gunner; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_uav; +#using scripts\mp\killstreaks\_supplydrop; +#using scripts\mp\_teamops; + +#precache( "eventstring", "testPlayerScoreForTan" ); + +#namespace dev; +/# +function autoexec __init__sytem__() { system::register("dev",&__init__,undefined,"spawnlogic"); } +#/ + +function __init__() +{ + callback::on_start_gametype( &init ); + callback::on_connect( &on_player_connected ); + + level.devOnGetOrMakeBot = &getOrMakeBot; +} + +function init() +{ + /# + if (GetDvarString( "scr_showspawns") == "") + { + SetDvar("scr_showspawns", "0"); + } + if (GetDvarString( "scr_showstartspawns") == "") + { + SetDvar("scr_showstartspawns", "0"); + } + if (GetDvarString( "scr_botsHasPlayerWeapon") == "") + { + SetDvar("scr_botsHasPlayerWeapon", "0"); + } + if (GetDvarString( "scr_botsGrenadesOnly") == "") + { + SetDvar("scr_botsGrenadesOnly", "0"); + } + if (GetDvarString( "scr_botsSpecialGrenadesOnly") == "") + { + SetDvar("scr_botsSpecialGrenadesOnly", "0"); + } + if (GetDvarString( "scr_show_hq_spawns") == "") + { + SetDvar("scr_show_hq_spawns", ""); + } + if(GetDvarString( "scr_testScriptRuntimeError") == "" ) + { + SetDvar( "scr_testScriptRuntimeError", "0" ); + } + + thread testScriptRuntimeError(); + thread testDvars(); + thread addEnemyHeli(); + thread addTestCarePackage(); + thread watch_botsdvars(); + thread devHeliPathDebugDraw(); + thread devStrafeRunPathDebugDraw(); + thread dev_class::dev_cac_init(); + thread globallogic_score::setPlayerMomentumDebug(); + + + SetDvar( "scr_giveperk", "" ); + SetDvar( "scr_forceevent", "" ); + SetDvar( "scr_draw_triggers", "0" ); + + // SRS 3/19/08: engagement distance debug dvar toggle watcher + thread engagement_distance_debug_toggle(); + + // give equipment through devgui + thread equipment_dev_gui(); + + // give grenades through devgui + thread grenade_dev_gui(); + + SetDvar( "debug_dynamic_ai_spawning", "0" ); + level.bot_overlay = false; + level.bot_threat = false; + level.bot_path = false; + + level.dem_spawns = []; + + if ( level.gametype == "dem" ) + { + extra_spawns = []; + extra_spawns[0] = "mp_dem_spawn_attacker_a"; + extra_spawns[1] = "mp_dem_spawn_attacker_b"; + extra_spawns[2] = "mp_dem_spawn_defender_a"; + extra_spawns[3] = "mp_dem_spawn_defender_b"; + + for ( i = 0; i < extra_spawns.size; i++ ) + { + points = GetEntArray( extra_spawns[i], "classname" ); + + if ( isdefined( points ) && points.size > 0 ) + { + level.dem_spawns = ArrayCombine( level.dem_spawns, points, true, false ); + } + } + } + + for(;;) + { + updateDevSettings(); + wait .5; + } + #/ +} + +function on_player_connected() +{ +/# + if ( ( isdefined( level.devgui_unlimited_ammo ) && level.devgui_unlimited_ammo ) ) + { + wait( 1 ); + self thread devgui_unlimited_ammo(); + } +#/ +} + +/# +function updateHardpoints() +{ + keys = getarraykeys( level.killstreaks ); + for ( i = 0; i < keys.size; i++ ) + { + dvar = level.killstreaks[keys[i]].devDvar; + enemyDvar = level.killstreaks[keys[i]].devEnemyDvar; + + host = util::getHostPlayer(); + + if( isdefined( dvar ) && getdvarint( dvar ) == 1 ) + { + if( keys[i] == "helicopter_gunner" ) + { + if( isdefined( level.vtol ) ) + iprintln( "There is a VTOL still in the air" ); + else + host killstreaks::give( "inventory_" + keys[i] ); + } + else + { + foreach ( player in level.players ) + { + if ( ( isdefined( level.usingMomentum ) && level.usingMomentum ) && ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks ) ) + { + player killstreaks::give( "inventory_" + keys[i] ); + } + else + { + if ( player util::is_bot() ) + { + player.bot[ "killstreaks" ] = []; + player.bot[ "killstreaks" ][0] = killstreaks::get_menu_name( keys[i] ); + + killstreakWeapon = killstreaks::get_killstreak_weapon( keys[i] ); + player killstreaks::give_weapon( killstreakWeapon, true ); + + globallogic_score::_setPlayerMomentum( player, 2000 ); + } + else + { + player killstreaks::give( "inventory_" + keys[i] ); + } + } + } + } + + SetDvar( dvar, "0" ); + } + + if( isdefined( enemyDvar ) && getdvarint( enemyDvar ) == 1 ) + { + team = "autoassign"; + player = util::getHostPlayer(); + if( isdefined( player.team ) ) + { + team = util::getOtherTeam( player.team ); + } + + ent = getOrMakeBot( team ); + + if( !isdefined( ent ) ) + { + println("Could not add test client"); + continue; + } + + wait( 1 ); + ent killstreaks::give( "inventory_" + keys[i] ); + SetDvar( enemyDvar, "0" ); + } + } +} + +function updateTeamOps() +{ + teamops = GetDvarString( level.teamops_dvar ); + if ( GetDvarString( level.teamops_dvar ) != "" ) + { + teamops::startTeamops( teamops ); + SetDvar( level.teamops_dvar, "" ); + } +} + +function warpAllToHost( team ) +{ + host = util::getHostPlayer(); + warpAllToPlayer( team, host.name ); +} + +function warpAllToPlayer( team, player ) +{ + players = GetPlayers(); + target = undefined; + for ( i = 0; i< players.size; i++ ) + { + if ( players[i].name == player ) + { + target = players[i]; + break; + } + } + + if ( isDefined( target ) ) + { + origin = target.origin; + + nodes = GetNodesInRadius( origin, 128, 32, 128, "Path" ); + + angles = target GetPlayerAngles(); + yaw = ( 0.0, angles[1], 0.0 ); + + forward = AnglesToForward( yaw ); + + spawn_origin = origin + ( forward * 128 ) + ( 0, 0, 16 ); + + if ( !BulletTracePassed( target GetEye(), spawn_origin, false, target ) ) + { + spawn_origin = undefined; + } + + for ( i = 0 ; i < players.size ; i++ ) + { + if ( players[i] == target ) + { + continue; + } + + if ( isdefined(team) ) + { + if ( StrStartsWith( team,"enemies_" ) && target.team == players[i].team ) + continue; + if ( StrStartsWith( team, "friendlies_" ) && target.team != players[i].team ) + continue; + } + + if ( isdefined( spawn_origin ) ) + { + players[i] SetOrigin( spawn_origin ); + } + else if ( nodes.size > 0 ) + { + node = array::random( nodes ); + players[i] SetOrigin( node.origin ); + } + else + { + players[i] SetOrigin( origin ); + } + + } + } + SetDvar( "scr_playerwarp", "" ); +} + +function updateDevSettingsZm() +{ + if( level.players.size > 0 ) + { + if( GetDvarString( "r_streamDumpDistance" ) == "3" ) + { + if(!isdefined(level.streamDumpTeamIndex)) + { + level.streamDumpTeamIndex = 0; + } + else + { + level.streamDumpTeamIndex++; + } + + numPoints = 0; + spawnPoints = []; + location = level.scr_zm_map_start_location; + if ((location == "default" || location == "" ) && isdefined(level.default_start_location)) + { + location = level.default_start_location; + } + match_string = level.scr_zm_ui_gametype + "_" + location; + if( level.streamDumpTeamIndex < level.teams.size ) + { + // zombies + structs = struct::get_array("initial_spawn", "script_noteworthy"); + if(isdefined(structs)) + { + foreach(struct in structs) + { + if(isdefined(struct.script_string) ) + { + tokens = strtok(struct.script_string," "); + foreach(token in tokens) + { + if(token == match_string ) + { + spawnPoints[spawnPoints.size] = struct; + } + } + } + + } + } + if(!isdefined(spawnPoints) || spawnPoints.size == 0) // old method, failed new method. + { + spawnPoints = struct::get_array("initial_spawn_points", "targetname"); + } + if( isdefined(spawnPoints) ) + { + numPoints = spawnPoints.size; + } + } + if( numPoints == 0 ) + { + SetDvar("r_streamDumpDistance","0"); + level.streamDumpTeamIndex = -1; + } + else + { + averageOrigin = ( 0, 0, 0 ); + averageAngles = ( 0, 0, 0 ); + foreach( spawnpoint in spawnPoints ) + { + averageOrigin += spawnpoint.origin / numPoints; + averageAngles += spawnpoint.angles / numPoints; + // averageOrigin = spawnpoint.origin; + // averageAngles = spawnpoint.angles; + // break; + } + level.players[0] SetPlayerAngles(averageAngles); + level.players[0] SetOrigin(averageOrigin); + wait(5); + SetDvar("r_streamDumpDistance","2"); + } + } + } +} + +function updateDevSettings() +{ + show_spawns= GetDvarint( "scr_showspawns"); + show_start_spawns= GetDvarint( "scr_showstartspawns"); + + player = util::getHostPlayer(); + + if (show_spawns >= 1) + { + show_spawns= 1; + } + else + { + show_spawns= 0; + } + + if (show_start_spawns >= 1) + { + show_start_spawns= 1; + } + else + { + show_start_spawns= 0; + } + + if (!isdefined(level.show_spawns) || level.show_spawns!=show_spawns) + { + level.show_spawns= show_spawns; + SetDvar("scr_showspawns", level.show_spawns); + + if(level.show_spawns) + { + showSpawnpoints(); + } + else + { + hideSpawnpoints(); + } + } + + if (!isdefined(level.show_start_spawns) || level.show_start_spawns!=show_start_spawns) + { + level.show_start_spawns= show_start_spawns; + SetDvar("scr_showstartspawns", level.show_start_spawns); + + if(level.show_start_spawns) + { + showStartSpawnpoints(); + } + else + { + hideStartSpawnpoints(); + } + } + + dev::updateMinimapSetting(); + + if( level.players.size > 0 ) + { + updateHardpoints(); + updateTeamOps(); + playerwarp_string = GetDvarString( "scr_playerwarp" ); + if ( playerwarp_string == "host" ) + { + warpAllToHost(); + } + else if ( playerwarp_string == "enemies_host" ) + { + warpAllToHost( playerwarp_string ); + } + else if ( playerwarp_string == "friendlies_host" ) + { + warpAllToHost( playerwarp_string ); + } + else if ( strstartswith( playerwarp_string, "enemies_" ) ) + { + name = getSubStr( playerwarp_string, 8 ); + warpAllToPlayer( playerwarp_string, name ); + } + else if ( strstartswith( playerwarp_string, "friendlies_" ) ) + { + name = getSubStr( playerwarp_string, 11 ); + warpAllToPlayer( playerwarp_string, name ); + } + else if ( strstartswith( playerwarp_string, "all_" ) ) + { + name = getSubStr( playerwarp_string, 4 ); + warpAllToPlayer( undefined, name ); + } + else if ( playerwarp_string == "next_start_spawn" ) + { + players = GetPlayers(); + SetDvar( "scr_playerwarp", "" ); + + if ( !isdefined( level.devgui_start_spawn_index ) ) + { + level.devgui_start_spawn_index = 0; + } + + player = util::getHostPlayer(); + spawns = level.spawn_start[player.pers["team"]]; + + if ( !isdefined( spawns ) || spawns.size <= 0 ) + { + return; + } + + for ( i = 0; i < players.size; i++ ) + { + players[i] SetOrigin( spawns[ level.devgui_start_spawn_index ].origin ); + players[i] SetPlayerAngles( spawns[ level.devgui_start_spawn_index ].angles ); + } + + level.devgui_start_spawn_index++; + + if ( level.devgui_start_spawn_index >= spawns.size ) + { + level.devgui_start_spawn_index = 0; + } + } + else if ( playerwarp_string == "prev_start_spawn" ) + { + players = GetPlayers(); + SetDvar( "scr_playerwarp", "" ); + + if ( !isdefined( level.devgui_start_spawn_index ) ) + { + level.devgui_start_spawn_index = 0; + } + + player = util::getHostPlayer(); + spawns = level.spawn_start[player.pers["team"]]; + + if ( !isdefined( spawns ) || spawns.size <= 0 ) + { + return; + } + + for ( i = 0; i < players.size; i++ ) + { + players[i] SetOrigin( spawns[ level.devgui_start_spawn_index ].origin ); + players[i] SetPlayerAngles( spawns[ level.devgui_start_spawn_index ].angles ); + } + + level.devgui_start_spawn_index--; + + if ( level.devgui_start_spawn_index < 0 ) + { + level.devgui_start_spawn_index = spawns.size - 1; + } + } + else if ( playerwarp_string == "next_spawn" ) + { + players = GetPlayers(); + SetDvar( "scr_playerwarp", "" ); + + if ( !isdefined( level.devgui_spawn_index ) ) + { + level.devgui_spawn_index = 0; + } + + spawns = level.spawnpoints; + spawns = ArrayCombine( spawns, level.dem_spawns, true, false ); + + if ( !isdefined( spawns ) || spawns.size <= 0 ) + { + return; + } + + for ( i = 0; i < players.size; i++ ) + { + players[i] SetOrigin( spawns[ level.devgui_spawn_index ].origin ); + players[i] SetPlayerAngles( spawns[ level.devgui_spawn_index ].angles ); + } + + level.devgui_spawn_index++; + + if ( level.devgui_spawn_index >= spawns.size ) + { + level.devgui_spawn_index = 0; + } + } + else if ( playerwarp_string == "prev_spawn" ) + { + players = GetPlayers(); + SetDvar( "scr_playerwarp", "" ); + + if ( !isdefined( level.devgui_spawn_index ) ) + { + level.devgui_spawn_index = 0; + } + + spawns = level.spawnpoints; + spawns = ArrayCombine( spawns, level.dem_spawns, true, false ); + + if ( !isdefined( spawns ) || spawns.size <= 0 ) + { + return; + } + + for ( i = 0; i < players.size; i++ ) + { + players[i] SetOrigin( spawns[ level.devgui_spawn_index ].origin ); + players[i] SetPlayerAngles( spawns[ level.devgui_spawn_index ].angles ); + } + + level.devgui_spawn_index--; + + if ( level.devgui_spawn_index < 0 ) + { + level.devgui_spawn_index = spawns.size - 1; + } + } + else if ( GetDvarString( "scr_devgui_spawn" ) != "" ) + { + player = util::getHostPlayer(); + + if ( !isdefined( player.devgui_spawn_active ) ) + { + player.devgui_spawn_active = false; + } + + if ( !player.devgui_spawn_active ) + { + iprintln( "Previous spawn bound to D-Pad Left" ); + iprintln( "Next spawn bound to D-Pad Right" ); + + player.devgui_spawn_active = true; + player thread devgui_spawn_think(); + } + else + { + player notify( "devgui_spawn_think" ); + player.devgui_spawn_active = false; + + player SetActionSlot( 3, "altMode" ); + } + + SetDvar( "scr_devgui_spawn", "" ); + } + else if ( GetDvarString( "scr_player_ammo" ) != "" ) + { + players = GetPlayers(); + + if ( !isdefined( level.devgui_unlimited_ammo ) ) + { + level.devgui_unlimited_ammo = true; + } + else + { + level.devgui_unlimited_ammo = !level.devgui_unlimited_ammo; + } + + if ( level.devgui_unlimited_ammo ) + { + iprintln( "Giving unlimited ammo to all players" ); + } + else + { + iprintln( "Stopping unlimited ammo for all players" ); + } + + for ( i = 0; i < players.size; i++ ) + { + if ( level.devgui_unlimited_ammo ) + { + players[i] thread devgui_unlimited_ammo(); + } + else + { + players[i] notify( "devgui_unlimited_ammo" ); + } + } + + SetDvar( "scr_player_ammo", "" ); + } + else if ( GetDvarString( "scr_player_momentum" ) != "" ) + { + if ( !isdefined( level.devgui_unlimited_momentum ) ) + { + level.devgui_unlimited_momentum = true; + } + else + { + level.devgui_unlimited_momentum = !level.devgui_unlimited_momentum; + } + + if ( level.devgui_unlimited_momentum ) + { + iprintln( "Giving unlimited momentum to all players" ); + level thread devgui_unlimited_momentum(); + } + else + { + iprintln( "Stopping unlimited momentum for all players" ); + level notify( "devgui_unlimited_momentum" ); + } + + SetDvar( "scr_player_momentum", "" ); + } + else if ( GetDvarString( "scr_give_player_score" ) != "" ) + { + level thread devgui_increase_momentum( getDvarInt( "scr_give_player_score" ) ); + + SetDvar( "scr_give_player_score", "" ); + } + else if ( GetDvarString( "scr_player_zero_ammo" ) != "" ) + { + players = GetPlayers(); + + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + weapons = player GetWeaponsList(); + ArrayRemoveValue( weapons, level.weaponBaseMelee ); + + for ( j = 0; j < weapons.size; j++ ) + { + if ( weapons[j] == level.weaponNone ) + continue; + + player SetWeaponAmmoStock( weapons[j], 0 ); + player SetWeaponAmmoClip( weapons[j], 0 ); + } + } + + SetDvar( "scr_player_zero_ammo", "" ); + } + else if ( GetDvarString( "scr_emp_jammed" ) != "" ) + { + players = GetPlayers(); + + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if ( GetDvarString( "scr_emp_jammed" ) == "0" ) + { + player SetEMPJammed( false ); + } + else + { + player SetEMPJammed( true ); + } + } + + SetDvar( "scr_emp_jammed", "" ); + } + else if ( GetDvarString( "scr_round_pause" ) != "" ) + { + if ( !level.timerStopped ) + { + iprintln( "Pausing Round Timer" ); + globallogic_utils::pauseTimer(); + } + else + { + iprintln( "Resuming Round Timer" ); + globallogic_utils::resumeTimer(); + } + + SetDvar( "scr_round_pause", "" ); + } + else if ( GetDvarString( "scr_round_end" ) != "" ) + { + level globallogic::forceEnd(); + SetDvar( "scr_round_end", "" ); + } + else if ( GetDvarString( "scr_health_debug" ) != "0" ) + { + players = GetPlayers(); + host = util::getHostPlayer(); + + if ( !isdefined( host.devgui_health_debug ) ) + { + host.devgui_health_debug = false; + } + + if ( host.devgui_health_debug ) + { + host.devgui_health_debug = false; + + for ( i = 0; i < players.size; i++ ) + { + players[i] notify( "devgui_health_debug" ); + + if ( isdefined( players[i].debug_health_bar ) ) + { + players[i].debug_health_bar destroy(); + players[i].debug_health_text destroy(); + players[i].debug_health_bar = undefined; + players[i].debug_health_text = undefined; + } + } + } + else + { + host.devgui_health_debug = true; + + for ( i = 0; i < players.size; i++ ) + { + players[i] thread devgui_health_debug(); + } + } + + SetDvar( "scr_health_debug", "" ); + } + else if ( GetDvarString( "scr_show_hq_spawns" ) != "" ) + { + if ( !isdefined( level.devgui_show_hq ) ) + { + level.devgui_show_hq = false; + } + + if ( level.gameType == "koth" && isdefined( level.radios ) ) + { + if ( !level.devgui_show_hq ) + { + for ( i = 0; i < level.radios.size; i++ ) + { + color = ( 1, 0, 0 ); + level showOneSpawnPoint( level.radios[i], color, "hide_hq_points", 32, "hq_spawn" ); + } + } + else + { + level notify( "hide_hq_points" ); + } + + level.devgui_show_hq = !level.devgui_show_hq; + } + + SetDvar( "scr_show_hq_spawns", "" ); + } + + if( GetDvarString( "r_streamDumpDistance" ) == "3" ) + { + if(!isdefined(level.streamDumpTeamIndex)) + { + level.streamDumpTeamIndex = 0; + } + else + { + level.streamDumpTeamIndex++; + } + + numPoints = 0; + if( level.streamDumpTeamIndex < level.teams.size ) + { + teamName = GetArrayKeys(level.teams)[level.streamDumpTeamIndex]; + if( isdefined(level.spawn_start[teamName]) ) + { + numPoints = level.spawn_start[teamName].size; + } + } + if( numPoints == 0 ) + { + SetDvar("r_streamDumpDistance","0"); + level.streamDumpTeamIndex = -1; + } + else + { + averageOrigin = ( 0, 0, 0 ); + averageAngles = ( 0, 0, 0 ); + foreach( spawnpoint in level.spawn_start[teamName] ) + { + averageOrigin += spawnpoint.origin / numPoints; + averageAngles += spawnpoint.angles / numPoints; + // averageOrigin = spawnpoint.origin; + // averageAngles = spawnpoint.angles; + // break; + } + level.players[0] SetPlayerAngles(averageAngles); + level.players[0] SetOrigin(averageOrigin); + wait(5); + SetDvar("r_streamDumpDistance","2"); + } + } + } + if ( GetDvarString( "scr_giveperk") == "0" ) + { + players = GetPlayers(); + + iprintln( "Taking all perks from all players" ); + + for ( i = 0; i < players.size; i++ ) + { + players[i] ClearPerks(); + } + + SetDvar( "scr_giveperk", "" ); + } + if ( GetDvarString( "scr_giveperk") != "" ) + { + perk = GetDvarString( "scr_giveperk"); + specialties = StrTok( perk, "|" ); + + players = GetPlayers(); + + iprintln( "Giving all players perk: '" + perk + "'" ); + + for ( i = 0; i < players.size; i++ ) + { + for( j = 0; j < specialties.size; j++ ) + { + players[i] setPerk( specialties[ j ] ); + players[i].extraPerks[ specialties[ j ] ] = 1; + } + + } + SetDvar( "scr_giveperk", "" ); + } + if ( GetDvarString( "scr_forcegrenade" ) != "" ) + { + force_grenade_throw( GetWeapon( GetDvarString( "scr_forcegrenade" ) ) ); + SetDvar( "scr_forcegrenade", "" ); + } + if( GetDvarString( "scr_forceevent" ) != "" ) + { + event = GetDvarString( "scr_forceevent" ); + player = util::getHostPlayer(); + forward = anglestoforward( player.angles ); + right = anglestoright( player.angles ); + if( event == "painfront" ) + { + player DoDamage(1, player.origin+forward); + } + else if( event == "painback" ) + { + player DoDamage(1, player.origin-forward); + } + else if( event == "painleft" ) + { + player DoDamage(1, player.origin-right); + } + else if( event == "painright" ) + { + player DoDamage(1, player.origin+right); + } + SetDvar( "scr_forceevent", "" ); + } + if ( GetDvarString( "scr_takeperk") != "" ) + { + perk = GetDvarString( "scr_takeperk"); + for ( i = 0; i < level.players.size; i++ ) + { + level.players[i] unsetPerk( perk ); + level.players[i].extraPerks[ perk ] = undefined; + } + SetDvar( "scr_takeperk", "" ); + } + + if ( GetDvarString( "scr_x_kills_y" ) != "" ) + { + nameTokens = strTok( GetDvarString( "scr_x_kills_y" ), " " ); + if ( nameTokens.size > 1 ) + thread xKillsY( nameTokens[0], nameTokens[1] ); + + SetDvar( "scr_x_kills_y", "" ); + } + + if ( GetDvarString( "scr_usedogs") != "" ) + { + ownerName = GetDvarString( "scr_usedogs" ); + SetDvar( "scr_usedogs", "" ); + + owner = undefined; + for ( index = 0; index < level.players.size; index++ ) + { + if ( level.players[index].name == ownerName ) + owner = level.players[index]; + } + + if ( isdefined( owner ) ) + owner killstreaks::trigger_killstreak( "dogs" ); + } + + if ( GetDvarString( "scr_set_level" ) != "" ) + { + player.pers["rank"] = 0; + player.pers["rankxp"] = 0; + + newRank = min( GetDvarint( "scr_set_level" ), 54 ); + newRank = max( newRank, 1 ); + + SetDvar( "scr_set_level", "" ); + + lastXp = 0; + for ( index = 0; index <= newRank; index++ ) + { + newXp = rank::getRankInfoMinXP( index ); + player thread rank::giveRankXP( "kill", newXp - lastXp ); + lastXp = newXp; + wait ( 0.25 ); + self notify ( "cancel_notify" ); + } + } + + if ( GetDvarString( "scr_givexp" ) != "" ) + { + player thread rank::giveRankXP( "challenge", GetDvarint( "scr_givexp" ), true ); + + SetDvar( "scr_givexp", "" ); + } + + if ( GetDvarString( "scr_do_notify" ) != "" ) + { + for ( i = 0; i < level.players.size; i++ ) + level.players[i] hud_message::oldNotifyMessage( GetDvarString( "scr_do_notify" ), GetDvarString( "scr_do_notify" ), game["icons"]["allies"] ); + + announcement( GetDvarString( "scr_do_notify" ), 0 ); + SetDvar( "scr_do_notify", "" ); + } + if ( GetDvarString( "scr_entdebug" ) != "" ) + { + ents = getEntArray(); + level.entArray = []; + level.entCounts = []; + level.entGroups = []; + for ( index = 0; index < ents.size; index++ ) + { + classname = ents[index].classname; + if ( !isSubStr( classname, "_spawn" ) ) + { + curEnt = ents[index]; + + level.entArray[level.entArray.size] = curEnt; + + if ( !isdefined( level.entCounts[classname] ) ) + level.entCounts[classname] = 0; + + level.entCounts[classname]++; + + if ( !isdefined( level.entGroups[classname] ) ) + level.entGroups[classname] = []; + + level.entGroups[classname][level.entGroups[classname].size] = curEnt; + } + } + } + + if( GetDvarString( "debug_dynamic_ai_spawning" ) == "1" && !isdefined( level.larry ) ) + { + thread larry_thread(); + } + else if ( GetDvarString( "debug_dynamic_ai_spawning" ) == "0" ) + { + level notify ( "kill_larry" ); + } + + if ( level.bot_overlay == false && GetDvarint( "scr_bot_overlay" ) == 1 ) + { + level thread bot_overlay_think(); + level.bot_overlay = true; + } + else if ( level.bot_overlay == true && GetDvarint( "scr_bot_overlay" ) == 0 ) + { + level bot_overlay_stop(); + level.bot_overlay = false; + } + + if ( level.bot_threat == false && GetDvarint( "scr_bot_threat" ) == 1 ) + { + level thread bot_threat_think(); + level.bot_threat = true; + } + else if ( level.bot_threat == true && GetDvarint( "scr_bot_threat" ) == 0 ) + { + level bot_threat_stop(); + level.bot_threat = false; + } + + if ( level.bot_path == false && GetDvarint( "scr_bot_path" ) == 1 ) + { + level thread bot_path_think(); + level.bot_path = true; + } + else if ( level.bot_path == true && GetDvarint( "scr_bot_path" ) == 0 ) + { + level bot_path_stop(); + level.bot_path = false; + } + if ( GetDvarint( "scr_force_finalkillcam" ) == 1 ) + { + level thread killcam::do_final_killcam(); + level thread waitThenNotifyFinalKillcam(); + } + if ( GetDvarint( "scr_force_roundkillcam" ) == 1 ) + { + level thread killcam::do_final_killcam(); + level thread waitThenNotifyRoundKillcam(); + } + + if ( !level.bot_overlay && !level.bot_threat && !level.bot_path ) + { + level notify( "bot_dpad_terminate" ); + } +} + +function waitThenNotifyRoundKillcam() +{ + {wait(.05);}; + + level notify ( "play_final_killcam" ); + SetDvar( "scr_force_roundkillcam", 0 ); +} + +function waitThenNotifyFinalKillcam() +{ + {wait(.05);}; + level notify ( "play_final_killcam" ); + {wait(.05);}; + SetDvar( "scr_force_finalkillcam", 0 ); +} + +function devgui_spawn_think() +{ + self notify( "devgui_spawn_think" ); + self endon( "devgui_spawn_think" ); + self endon( "disconnect" ); + + dpad_left = false; + dpad_right = false; + + for ( ;; ) + { + self SetActionSlot( 3, "" ); + self SetActionSlot( 4, "" ); + + if ( !dpad_left && self ButtonPressed( "DPAD_LEFT" ) ) + { + SetDvar( "scr_playerwarp", "prev_spawn" ); + dpad_left = true; + } + else if ( !self ButtonPressed( "DPAD_LEFT" ) ) + { + dpad_left = false; + } + + if ( !dpad_right && self ButtonPressed( "DPAD_RIGHT" ) ) + { + SetDvar( "scr_playerwarp", "next_spawn" ); + dpad_right = true; + } + else if ( !self ButtonPressed( "DPAD_RIGHT" ) ) + { + dpad_right = false; + } + + {wait(.05);}; + } +} + +function devgui_unlimited_ammo() +{ + self notify( "devgui_unlimited_ammo" ); + self endon( "devgui_unlimited_ammo" ); + self endon( "disconnect" ); + + for ( ;; ) + { + wait( 1 ); + + primary_weapons = self GetWeaponsListPrimaries(); + offhand_weapons_and_alts = array::exclude( self GetWeaponsList( true ), primary_weapons ); + weapons = ArrayCombine(primary_weapons, offhand_weapons_and_alts, false, false); + ArrayRemoveValue( weapons, level.weaponBaseMelee ); + + for ( i = 0; i < weapons.size; i++ ) + { + weapon = weapons[i]; + if ( weapon == level.weaponNone ) + continue; + + if ( killstreaks::is_killstreak_weapon( weapon ) ) + continue; + + self GiveMaxAmmo( weapon ); + } + } +} + +function devgui_unlimited_momentum() +{ + level notify( "devgui_unlimited_momentum" ); + level endon( "devgui_unlimited_momentum" ); + + for ( ;; ) + { + wait( 1 ); + + players = GetPlayers(); + + foreach( player in players ) + { + if ( !isdefined( player ) ) + { + continue; + } + + if ( !IsAlive( player ) ) + { + continue; + } + + if ( player.sessionstate != "playing" ) + { + continue; + } + + globallogic_score::_setPlayerMomentum( player, 5000 ); + } + } +} + +function devgui_increase_momentum( score ) +{ + players = GetPlayers(); + + foreach( player in players ) + { + if ( !isdefined( player ) ) + { + continue; + } + + if ( !IsAlive( player ) ) + { + continue; + } + + if ( player.sessionstate != "playing" ) + { + continue; + } + + player globallogic_score::givePlayerMomentumNotification( score, &"testPlayerScoreForTan", "PLAYER_SCORE" ); + } +} + +function devgui_health_debug() +{ + self notify( "devgui_health_debug" ); + self endon( "devgui_health_debug" ); + self endon( "disconnect" ); + + x = 80; + y = 40; + + self.debug_health_bar = NewClientHudElem( self ); + self.debug_health_bar.x = x + 80; + self.debug_health_bar.y = y + 2; + self.debug_health_bar.alignX = "left"; + self.debug_health_bar.alignY = "top"; + self.debug_health_bar.horzAlign = "fullscreen"; + self.debug_health_bar.vertAlign = "fullscreen"; + self.debug_health_bar.alpha = 1; + self.debug_health_bar.foreground = 1; + self.debug_health_bar setshader( "black", 1, 8 ); + + self.debug_health_text = NewClientHudElem( self ); + self.debug_health_text.x = x + 80; + self.debug_health_text.y = y; + self.debug_health_text.alignX = "left"; + self.debug_health_text.alignY = "top"; + self.debug_health_text.horzAlign = "fullscreen"; + self.debug_health_text.vertAlign = "fullscreen"; + self.debug_health_text.alpha = 1; + self.debug_health_text.fontScale = 1; + self.debug_health_text.foreground = 1; + + if( !isdefined( self.maxhealth ) || self.maxhealth <= 0 ) + self.maxhealth = 100; + + for ( ;; ) + { + {wait(.05);}; + + width = self.health / self.maxhealth * 300; + width = int( max( width, 1 ) ); + self.debug_health_bar setShader( "black", width, 8 ); + + self.debug_health_text SetValue( self.health ); + } +} + +function giveExtraPerks() +{ + if ( !isdefined( self.extraPerks ) ) + return; + + perks = getArrayKeys( self.extraPerks ); + + for ( i = 0; i < perks.size; i++ ) + { + /# println( "^5Loadout " + self.name + " setPerk( " + perks[i] + " ) -- devperk" ); #/ + self setPerk( perks[i] ); + } +} + +function xKillsY( attackerName, victimName ) +{ + attacker = undefined; + victim = undefined; + + for ( index = 0; index < level.players.size; index++ ) + { + if ( level.players[index].name == attackerName ) + attacker = level.players[index]; + else if ( level.players[index].name == victimName ) + victim = level.players[index]; + } + + if ( !isAlive( attacker ) || !isAlive( victim ) ) + return; + + victim thread [[level.callbackPlayerDamage]]( + attacker, // eInflictor The entity that causes the damage.(e.g. a turret) + attacker, // eAttacker The entity that is attacking. + 1000, // iDamage Integer specifying the amount of damage done + 0, // iDFlags Integer specifying flags that are to be applied to the damage + "MOD_RIFLE_BULLET", // sMeansOfDeath Integer specifying the method of death + level.weaponNone, // sWeapon The weapon used to inflict the damage + (0,0,0), // vPoint The point the damage is from? + (0,0,0), // vDir The direction of the damage + "none", // sHitLoc The location of the hit + (0,0,0), // vDamageOrigin + 0, // psOffsetTime The time offset for the damage + 0, // boneIndex + (1,0,0) // vSurfaceNormal + ); +} + + +function testScriptRuntimeErrorAssert() +{ + wait(1); + + assert( 0 ); +} + + +function testScriptRuntimeAssertMsgAssert() +{ + wait(1); + + assertmsg( "DEVGUI: Assert Message" ); +} + +function testScriptRuntimeErrorMsgAssert() +{ + wait(1); +/# + errormsg( "DEVGUI: Error Message" ); +#/ +} + +function testScriptRuntimeError2() +{ + myundefined = "test"; + if( myundefined == 1 ) + println( "undefined in testScriptRuntimeError2\n" ); +} + +function testScriptRuntimeError1() +{ + testScriptRuntimeError2(); +} + +function testScriptRuntimeError() +{ + wait 5; + for(;;) + { + if(GetDvarString( "scr_testScriptRuntimeError") != "0" ) + break; + wait 1; + } + + myerror = GetDvarString( "scr_testScriptRuntimeError" ); + + SetDvar( "scr_testScriptRuntimeError", "0" ); + + if( myerror == "assert" ) + testScriptRuntimeErrorAssert(); + else if( myerror == "assertMsg" ) + testScriptRuntimeAssertMsgAssert(); + else if( myerror == "errorMsg" ) + testScriptRuntimeErrorMsgAssert(); + else + testScriptRuntimeError1(); + + thread testScriptRuntimeError(); +} + + +function testDvars() +{ + wait 5; + for(;;) + { + if(GetDvarString( "scr_testdvar") != "" ) + break; + wait 1; + } + tokens = StrTok( GetDvarString( "scr_testdvar"), " " ); + dvarName = tokens[0]; + dvarValue = tokens[1]; + + SetDvar( dvarName, dvarValue ); + SetDvar( "scr_testdvar", "" ); + + thread testDvars(); +} + +function addEnemyHeli() +{ + wait 5; + + for(;;) + { + if(GetDvarint( "scr_spawnenemyheli") > 0) + break; + wait 1; + } + + enemyheli = GetDvarint( "scr_spawnenemyheli"); + SetDvar( "scr_spawnenemyheli", 0 ); + + team = "autoassign"; + player = util::getHostPlayer(); + if( isdefined( player.pers["team"] ) ) + { + team = util::getOtherTeam( player.pers["team"] ); + } + + ent = getOrMakeBot(team); + if( !isdefined( ent ) ) + { + println("Could not add test client"); + wait 1; + thread addEnemyHeli(); + return; + } + + switch( enemyheli ) + { + case 1: + level.helilocation = ent.origin; + ent thread helicopter::useKillstreakHelicopter( "helicopter_comlink" ); + wait(0.5); + ent notify( "confirm_location", level.helilocation ); + break; + case 2: + break; + } + + thread addEnemyHeli(); +} + +function getOrMakeBot(team) +{ + for ( i = 0; i 0) + break; + wait 1; + } + + supplydrop = GetDvarint( "scr_givetestsupplydrop"); + team = "autoassign"; + + player = util::getHostPlayer(); + + if( isdefined( player.pers["team"] ) ) + { + switch( supplydrop ) + { + case 2: // enemy + team = util::getOtherTeam( player.pers["team"] ); + break; + + case 1: // ally + default: + team = player.pers["team"]; + break; + } + } + + SetDvar( "scr_givetestsupplydrop", 0 ); + ent = getOrMakeBot(team); + if( !isdefined( ent ) ) + { + println("Could not add test client"); + wait 1; + thread addTestCarePackage(); + return; + } + + ent killstreakrules::killstreakStart( "supply_drop", team ); + ent thread supplydrop::heliDeliverCrate( ent.origin, GetWeapon( "supplydrop" ), ent, team ); + + thread addTestCarePackage(); +} + +function showOneSpawnPoint( + spawn_point, + color, + notification, + height, + print) +{ + if ( !isdefined( height ) || height <= 0 ) + { + height = util::get_player_height(); + } + + if ( !isdefined( print ) ) + { + if ( level.convert_spawns_to_structs ) + { + print = spawn_point.targetname; + } + else + { + print = spawn_point.classname; + } + } + + center = spawn_point.origin; + forward = anglestoforward(spawn_point.angles); + right = anglestoright(spawn_point.angles); + + forward = VectorScale(forward, 16); + right = VectorScale(right, 16); + + a = center + forward - right; + b = center + forward + right; + c = center - forward + right; + d = center - forward - right; + + thread lineUntilNotified(a, b, color, 0, notification); + thread lineUntilNotified(b, c, color, 0, notification); + thread lineUntilNotified(c, d, color, 0, notification); + thread lineUntilNotified(d, a, color, 0, notification); + + thread lineUntilNotified(a, a + (0, 0, height), color, 0, notification); + thread lineUntilNotified(b, b + (0, 0, height), color, 0, notification); + thread lineUntilNotified(c, c + (0, 0, height), color, 0, notification); + thread lineUntilNotified(d, d + (0, 0, height), color, 0, notification); + + a = a + (0, 0, height); + b = b + (0, 0, height); + c = c + (0, 0, height); + d = d + (0, 0, height); + + thread lineUntilNotified(a, b, color, 0, notification); + thread lineUntilNotified(b, c, color, 0, notification); + thread lineUntilNotified(c, d, color, 0, notification); + thread lineUntilNotified(d, a, color, 0, notification); + + center = center + (0, 0, height/2); + arrow_forward = anglestoforward(spawn_point.angles); + arrowhead_forward = anglestoforward(spawn_point.angles); + arrowhead_right = anglestoright(spawn_point.angles); + + arrow_forward = VectorScale(arrow_forward, 32); + arrowhead_forward = VectorScale(arrowhead_forward, 24); + arrowhead_right = VectorScale(arrowhead_right, 8); + + a = center + arrow_forward; + b = center + arrowhead_forward - arrowhead_right; + c = center + arrowhead_forward + arrowhead_right; + + thread lineUntilNotified(center, a, color, 0, notification); + thread lineUntilNotified(a, b, color, 0, notification); + thread lineUntilNotified(a, c, color, 0, notification); + + thread print3DUntilNotified(spawn_point.origin + (0, 0, height), print, color, 1, 1, notification); + + return; +} + +function showSpawnpoints() +{ + if (isdefined(level.spawnpoints)) + { + // show standard spawn points + color= (1, 1, 1); + for (spawn_point_index= 0; spawn_point_index= engageDistMin ) && ( traceDist <= engageDistMax ) ) + { + // if in the optimal range... + if( ( traceDist >= ( engageDistOptimal - engageDistMulligan ) ) + && ( traceDist <= ( engageDistOptimal + engageDistMulligan ) ) ) + { + hudObjArray engagedist_hud_changetext( "optimal", tracedist ); + hudobj_changecolor( hudObjArray, level.green ); + } + else + { + hudObjArray engagedist_hud_changetext( "ok", tracedist ); + hudobj_changecolor( hudObjArray, level.yellow ); + } + } + else if( traceDist < engageDistMin ) + { + hudobj_changecolor( hudObjArray, level.red ); + hudObjArray engagedist_hud_changetext( "short", tracedist ); + } + else if( traceDist > engageDistMax ) + { + hudobj_changecolor( hudObjArray, level.red ); + hudObjArray engagedist_hud_changetext( "long", tracedist ); + } + } + } + + // draw our trace spot + // plot_circle_fortime(radius1,radius2,time,color,origin,normal) + thread plot_circle_fortime( 1, 5, 0.05, level.debugRTEngageDistColor, tracePoint, traceNormal ); + thread plot_circle_fortime( 1, 1, 0.05, level.debugRTEngageDistColor, tracePoint, traceNormal ); + + {wait(.05);}; + } +} + +function hudobj_changecolor( hudObjArray, newcolor ) +{ + for( i = 0; i < hudObjArray.size; i++ ) + { + hudObj = hudObjArray[i]; + + if( hudObj.color != newcolor ) + { + hudObj.color = newcolor; + level.debugRTEngageDistColor = newcolor; + } + } +} + +// self = an array of hud objects +function engagedist_hud_changetext( engageDistType, units ) +{ + if( !isdefined( level.lastDistType ) ) + { + level.lastDistType = "none"; + } + + if( engageDistType == "optimal" ) + { + self[1] SetValue( units ); + self[2] SetText( "units: OPTIMAL!" ); + self[3].alpha = 0; + } + else if( engageDistType == "ok" ) + { + self[1] SetValue( units ); + self[2] SetText( "units: OK!" ); + self[3].alpha = 0; + } + else if( engageDistType == "short" ) + { + amountUnder = level.weaponEngageDistValues.engageDistMin - units; + self[1] SetValue( units ); + self[3] SetValue( amountUnder ); + self[3].alpha = 1; + + if( level.lastDistType != engageDistType ) + { + self[2] SetText( "units: SHORT by " ); + } + } + else if( engageDistType == "long" ) + { + amountOver = units - level.weaponEngageDistValues.engageDistMax; + self[1] SetValue( units ); + self[3] SetValue( amountOver ); + self[3].alpha = 1; + + if( level.lastDistType != engageDistType ) + { + self[2] SetText( "units: LONG by " ); + } + } + else if( engageDistType == "nodata" ) + { + self[1] SetValue( units ); + self[2] SetText( " units: (NO CURRENT WEAPON VALUES)" ); + self[3].alpha = 0; + } + + level.lastDistType = engageDistType; +} + +// draws print3ds above enemy AI heads to show contact distances +/* +function debug_ai_engage_dist() +{ + level endon( "kill_all_engage_dist_debug" ); + level endon( "kill_ai_engagement_distance_debug" ); + + player = util::getHostPlayer(); + + while( 1 ) + { + axis = GetAITeamArray( "axis" ); + + if( isdefined( axis ) && axis.size > 0 ) + { + playerEye = player GetEye(); + + for( i = 0; i < axis.size; i++ ) + { + ai = axis[i]; + aiEye = ai GetEye(); + + if( SightTracePassed( playerEye, aiEye, false, player ) ) + { + dist = Distance( playerEye, aiEye ); + + drawColor = level.white; + drawString = "-"; + + if( !isdefined( level.weaponEngageDistValues ) ) + { + drawColor = level.white; + } + else + { + // for convenience + engageDistMin = level.weaponEngageDistValues.engageDistMin; + engageDistOptimal = level.weaponEngageDistValues.engageDistOptimal; + engageDistMulligan = level.weaponEngageDistValues.engageDistMulligan; + engageDistMax = level.weaponEngageDistValues.engageDistMax; + + // if inside our engagement distance range... + if( ( dist >= engageDistMin ) && ( dist <= engageDistMax ) ) + { + // if in the optimal range... + if( ( dist >= ( engageDistOptimal - engageDistMulligan ) ) + && ( dist <= ( engageDistOptimal + engageDistMulligan ) ) ) + { + drawColor = level.green; + drawString = "RAD"; + } + // else it's just ok + else + { + drawColor = level.yellow; + drawString = "MEH"; + } + } + else if( dist < engageDistMin ) + { + drawColor = level.red; + drawString = "BAD"; + } + else if( dist > engageDistMax ) + { + drawColor = level.red; + drawString = "BAD"; + } + } + + scale = dist / 525; + Print3d( ai.origin + ( 0, 0, 67 ), drawString, drawColor, 1, scale ); + } + } + } + + WAIT_SERVER_FRAME; + } +} +*/ + +// draws a circle in script +function plot_circle_fortime(radius1,radius2,time,color,origin,normal) +{ + if(!isdefined(color)) + color = (0,1,0); + hangtime = .05; + circleres = 6; + hemires = circleres/2; + circleinc = 360/circleres; + circleres++; + plotpoints = []; + + rad = 0.00; + timer = gettime()+(time*1000); + radius = radius1; + + while(gettime() 0 ) + { + larry.model Hide(); + continue; + } + + // Trace to where the player is looking + direction = self getPlayerAngles(); + direction_vec = anglesToForward( direction ); + eye = self getEye(); + + // offset 2 units on the Z to fix the bug where it would drop through the ground sometimes + trace = bullettrace( eye, eye + VectorScale( direction_vec , 8000 ), 0, undefined ); + + dist = distance (eye, trace["position"]); + position = eye + VectorScale( direction_vec , (dist - 64) ); + + larry.model.origin = position; + larry.model.angles = self.angles + ( 0, 180, 0 ); + + if ( self UseButtonPressed() ) + { + self larry_ai( larry ); + + while ( self UseButtonPressed() ) + {wait(.05);}; + } + } +} + +function larry_ai( larry ) +{ + larry.ai[larry.ai.size] = bot::add_bot( "autoassign" ); + + i = larry.ai.size - 1; + + larry.ai[i] thread larry_ai_thread( larry, larry.model.origin, larry.model.angles ); + larry.ai[i] thread larry_ai_damage( larry ); + larry.ai[i] thread larry_ai_health( larry ); +} + +function larry_ai_thread( larry, origin, angles ) +{ + level endon( "kill_larry" ); + + for ( ;; ) + { + self waittill( "spawned_player" ); + + //larry.clearTextMarker ClearAllTextAfterHudElem(); + + larry.menu[larry.menu_health] SetValue( self.health ); + larry.menu[larry.menu_damage] SetText( "" ); + larry.menu[larry.menu_range] SetText( "" ); + larry.menu[larry.menu_hitloc] SetText( "" ); + larry.menu[larry.menu_weapon] SetText( "" ); + larry.menu[larry.menu_perks] SetText( "" ); + + self SetOrigin( origin ); + self SetPlayerAngles( angles ); + self ClearPerks(); + } +} + +function larry_ai_damage( larry ) +{ + level endon( "kill_larry" ); + + for ( ;; ) + { + self waittill( "damage", damage, attacker, dir, point ); + + if ( !IsDefined( attacker ) ) + { + continue; + } + + player = util::getHostPlayer(); + if ( !isdefined( player ) ) + { + continue; + } + + if ( attacker != player ) + { + continue; + } + + eye = player GetEye(); + range = int( Distance( eye, point ) ); + + larry.menu[larry.menu_health] SetValue( self.health ); + larry.menu[larry.menu_damage] SetValue( damage ); + larry.menu[larry.menu_range] SetValue( range ); + + if ( IsDefined( self.cac_debug_location ) ) + { + larry.menu[larry.menu_hitloc] SetText( self.cac_debug_location ); + } + else + { + larry.menu[larry.menu_hitloc] SetText( "" ); + } + + if ( IsDefined( self.cac_debug_weapon ) ) + { + larry.menu[larry.menu_weapon] SetText( self.cac_debug_weapon ); + } + else + { + larry.menu[larry.menu_weapon] SetText( "" ); + } + } +} + +function larry_ai_health( larry ) +{ + level endon( "kill_larry" ); + + for ( ;; ) + { + {wait(.05);}; + + larry.menu[larry.menu_health] SetValue( self.health ); + } +} + +function larry_hud_init( larry ) +{ + /# + x = -45; + y = 275; + menu_name = "larry_menu"; + + larry.hud = new_hud( menu_name, undefined, x, y, 1 ); + larry.hud SetShader( "white", 135, 65 ); + larry.hud.alignX = "left"; + larry.hud.alignY = "top"; + larry.hud.sort = 10; + larry.hud.alpha = 0.6; + larry.hud.color = ( 0.0, 0.0, 0.5 ); + + larry.menu[0] = new_hud( menu_name, "Larry Health:", x + 5, y + 10, 1 ); + larry.menu[1] = new_hud( menu_name, "Damage:", x + 5, y + 20, 1 ); + larry.menu[2] = new_hud( menu_name, "Range:", x + 5, y + 30, 1 ); + larry.menu[3] = new_hud( menu_name, "Hit Location:", x + 5, y + 40, 1 ); + larry.menu[4] = new_hud( menu_name, "Weapon:", x + 5, y + 50, 1 ); + + larry.clearTextMarker = NewDebugHudElem(); + larry.clearTextMarker.alpha = 0; + larry.clearTextMarker setText( "marker" ); + + larry.menu_health = larry.menu.size; + larry.menu_damage = larry.menu.size + 1; + larry.menu_range = larry.menu.size + 2; + larry.menu_hitloc = larry.menu.size + 3; + larry.menu_weapon = larry.menu.size + 4; + larry.menu_perks = larry.menu.size + 5; + + x_offset = 70; + + larry.menu[larry.menu_health] = new_hud( menu_name, "", x + x_offset, y + 10, 1 ); + larry.menu[larry.menu_damage] = new_hud( menu_name, "", x + x_offset, y + 20, 1 ); + larry.menu[larry.menu_range] = new_hud( menu_name, "", x + x_offset, y + 30, 1 ); + larry.menu[larry.menu_hitloc] = new_hud( menu_name, "", x + x_offset, y + 40, 1 ); + larry.menu[larry.menu_weapon] = new_hud( menu_name, "", x + x_offset, y + 50, 1 ); + larry.menu[larry.menu_perks] = new_hud( menu_name, "", x + x_offset, y + 60, 1 ); + #/ +} + +function larry_hud_destroy( larry ) +{ + if ( isdefined( larry.hud ) ) + { + larry.hud Destroy(); + + for ( i = 0; i < larry.menu.size; i++ ) + { + larry.menu[i] Destroy(); + } + + //larry.clearTextMarker ClearAllTextAfterHudElem(); + larry.clearTextMarker Destroy(); + } +} + +function new_hud( hud_name, msg, x, y, scale ) +{ + if( !isdefined( level.hud_array ) ) + { + level.hud_array = []; + } + + if( !isdefined( level.hud_array[hud_name] ) ) + { + level.hud_array[hud_name] = []; + } + + hud = set_hudelem( msg, x, y, scale ); + level.hud_array[hud_name][level.hud_array[hud_name].size] = hud; + return hud; +} + +//------------------------------------------------------// +// set_hudelem( [text], x, y, [scale], [alpha] ) // +// Actually creates the hudelem // +//------------------------------------------------------// +// self - n/a // +// text - The text to be displayed // +// x - Sets the x position of the hudelem // +// y - Sets the y position of the hudelem // +// scale - Sets the scale of the hudelem // +// alpha - Sets the alpha of the hudelem // +//------------------------------------------------------// +function set_hudelem( text, x, y, scale, alpha, sort, debug_hudelem ) +{ + /# + if( !isdefined( alpha ) ) + { + alpha = 1; + } + + if( !isdefined( scale ) ) + { + scale = 1; + } + + if( !isdefined( sort ) ) + { + sort = 20; + } + + hud = NewDebugHudElem(); + hud.debug_hudelem = true; + + hud.location = 0; + hud.alignX = "left"; + hud.alignY = "middle"; + hud.foreground = 1; + hud.fontScale = scale; + hud.sort = sort; + hud.alpha = alpha; + hud.x = x; + hud.y = y; + hud.og_scale = scale; + + if( isdefined( text ) ) + { + hud SetText( text ); + } + + return hud; + #/ +} + +function watch_botsdvars() +{ + hasplayerweaponprev = GetDvarint( "scr_botsHasPlayerWeapon" ); + grenadesonlyprev = GetDvarint( "scr_botsGrenadesOnly" ); + secondarygrenadesonlyprev = GetDvarint( "scr_botsSpecialGrenadesOnly" ); + while(true) + { + if( hasplayerweaponprev != GetDvarint( "scr_botsHasPlayerWeapon") ) + { + hasplayerweaponprev = GetDvarint( "scr_botsHasPlayerWeapon" ); + if( hasplayerweaponprev ) + { + IPrintLnBold( "LARRY has player weapon: ON" ); + } + else + { + IPrintLnBold( "LARRY has player weapon: OFF" ); + } + } + + if( grenadesonlyprev != GetDvarint( "scr_botsGrenadesOnly") ) + { + grenadesonlyprev = GetDvarint( "scr_botsGrenadesOnly" ); + if( grenadesonlyprev ) + { + IPrintLnBold( "LARRY using grenades only: ON" ); + } + else + { + IPrintLnBold( "LARRY using grenades only: OFF" ); + } + } + + if( secondarygrenadesonlyprev != GetDvarint( "scr_botsSpecialGrenadesOnly") ) + { + secondarygrenadesonlyprev = GetDvarint( "scr_botsSpecialGrenadesOnly" ); + if( secondarygrenadesonlyprev ) + { + IPrintLnBold( "LARRY using secondary grenades only: ON" ); + } + else + { + IPrintLnBold( "LARRY using secondary grenades only: OFF" ); + } + } + + wait(1.0); + } +} + +// -- end dynamic AI spawning -- +function getAttachmentChangeModifierButton() +{ + return "BUTTON_X"; +} + +function watchAttachmentChange() +{ + self endon( "disconnect" ); + + clientNum = self getEntityNumber(); + if ( clientNum != 0 ) + return; + + dpad_left = false; + dpad_right = false; + dpad_up = false; + dpad_down = false; + lstick_down = false; + + dpad_modifier_button = getAttachmentChangeModifierButton(); + + for ( ;; ) + { + if ( self ButtonPressed( dpad_modifier_button ) ) + { + if ( !dpad_left && self ButtonPressed( "DPAD_LEFT" ) ) + { + self giveweaponnextattachment( "muzzle" ); + dpad_left = true; + self thread print_weapon_name(); + } + if ( !dpad_right && self ButtonPressed( "DPAD_RIGHT" ) ) + { + self giveweaponnextattachment( "trigger" ); + dpad_right = true; + self thread print_weapon_name(); + } + if ( !dpad_up && self ButtonPressed( "DPAD_UP" ) ) + { + self giveweaponnextattachment( "top" ); + dpad_up = true; + self thread print_weapon_name(); + } + if ( !dpad_down && self ButtonPressed( "DPAD_DOWN" ) ) + { + self giveweaponnextattachment( "bottom" ); + dpad_down = true; + self thread print_weapon_name(); + } + if ( !lstick_down && self ButtonPressed( "BUTTON_LSTICK" ) ) + { + self giveweaponnextattachment( "gunperk" ); + lstick_down = true; + self thread print_weapon_name(); + } + } + if ( !self ButtonPressed( "DPAD_LEFT" ) ) + { + dpad_left = false; + } + if ( !self ButtonPressed( "DPAD_RIGHT" ) ) + { + dpad_right = false; + } + if ( !self ButtonPressed( "DPAD_UP" ) ) + { + dpad_up = false; + } + if ( !self ButtonPressed( "DPAD_DOWN" ) ) + { + dpad_down = false; + } + if ( !self ButtonPressed( "BUTTON_LSTICK" ) ) + { + lstick_down = false; + } + + {wait(.05);}; + } +} + +function print_weapon_name() // self == player +{ + self notify( "print_weapon_name"); + self endon( "print_weapon_name"); + + wait(0.2); + + if ( self IsSwitchingWeapons() ) + { + self waittill( "weapon_change_complete", weapon ); + fail_safe = 0; + while ( weapon == level.weaponNone ) + { + self waittill( "weapon_change_complete", weapon ); + {wait(.05);}; + fail_safe++; + if( fail_safe > 120 ) + { + break; + } + } + } + else + { + weapon = self getcurrentweapon(); + } + printWeaponName = GetDvarInt( "scr_print_weapon_name", 1 ); + if ( printWeaponName ) + IPrintLnBold( weapon.name ); +} + +function set_equipment_list() +{ + if (isdefined( level.dev_equipment )) + return; + + level.dev_equipment = []; + + //array starts at '1' because I need the first element to empty as GetDvarInt() returns zero if it's undefined. + level.dev_equipment[1] = GetWeapon( "acoustic_sensor" ); + level.dev_equipment[2] = GetWeapon( "camera_spike" ); + level.dev_equipment[3] = GetWeapon( "claymore" ); + level.dev_equipment[4] = GetWeapon( "satchel_charge" ); + level.dev_equipment[5] = GetWeapon( "scrambler" ); + level.dev_equipment[6] = GetWeapon( "tactical_insertion" ); + level.dev_equipment[7] = GetWeapon( "bouncingbetty" ); + level.dev_equipment[8] = GetWeapon( "trophy_system" ); + level.dev_equipment[9] = GetWeapon( "pda_hack" ); + level.dev_equipment[10] = GetWeapon( "threat_detector" ); +} + +function set_grenade_list() +{ + if (isdefined( level.dev_grenade )) + return; + + level.dev_grenade = []; + + //array starts at '1' because I need the first element to empty as GetDvarInt() returns zero if it's undefined. + level.dev_grenade[1] = GetWeapon( "frag_grenade" ); + level.dev_grenade[2] = GetWeapon( "sticky_grenade" ); + level.dev_grenade[3] = GetWeapon( "hatchet" ); + level.dev_grenade[4] = GetWeapon( "willy_pete" ); + level.dev_grenade[5] = GetWeapon( "proximity_grenade" ); + level.dev_grenade[6] = GetWeapon( "flash_grenade" ); + level.dev_grenade[7] = GetWeapon( "concussion_grenade" ); + level.dev_grenade[8] = GetWeapon( "nightingale" ); + level.dev_grenade[9] = GetWeapon( "emp_grenade" ); + level.dev_grenade[10] = GetWeapon( "sensor_grenade" ); + level.dev_grenade[11] = GetWeapon( "incendiary_grenade" ); +} + +function take_all_grenades_and_equipment( player ) +{ + for (i=0; i= max ) + { + level.bot_index = 0; + } + + if ( !players[ level.bot_index ] util::is_bot() ) + { + continue; + } + + dpad_right = true; + } + else if ( !host ButtonPressed( "DPAD_RIGHT" ) ) + { + dpad_right = false; + } + + level notify( "bot_index_changed" ); + } +} + +function bot_overlay_think() +{ + level endon( "bot_overlay_stop" ); + + level thread bot_dpad_think(); + + iprintln( "Previous Bot bound to D-Pad Left" ); + iprintln( "Next Bot bound to D-Pad Right" ); + + for ( ;; ) + { + if ( GetDvarInt( "bot_Debug" ) != level.bot_index ) + { + SetDvar( "bot_Debug", level.bot_index ); + } + + level waittill( "bot_index_changed" ); + } +} + +function bot_threat_think() +{ + level endon( "bot_threat_stop" ); + + level thread bot_dpad_think(); + + iprintln( "Previous Bot bound to D-Pad Left" ); + iprintln( "Next Bot bound to D-Pad Right" ); + + for ( ;; ) + { + if ( GetDvarInt( "bot_DebugThreat" ) != level.bot_index ) + { + SetDvar( "bot_DebugThreat", level.bot_index ); + } + + level waittill( "bot_index_changed" ); + } +} + +function bot_path_think() +{ + level endon( "bot_path_stop" ); + + level thread bot_dpad_think(); + + iprintln( "Previous Bot bound to D-Pad Left" ); + iprintln( "Next Bot bound to D-Pad Right" ); + + for ( ;; ) + { + if ( GetDvarInt( "bot_DebugPaths" ) != level.bot_index ) + { + SetDvar( "bot_DebugPaths", level.bot_index ); + } + + level waittill( "bot_index_changed" ); + } +} + +function bot_overlay_stop() +{ + level notify( "bot_overlay_stop" ); + SetDvar( "bot_Debug", "-1" ); +} + +function bot_path_stop() +{ + level notify( "bot_path_stop" ); + SetDvar( "bot_DebugPaths", "-1" ); +} + +function bot_threat_stop() +{ + level notify( "bot_threat_stop" ); + SetDvar( "bot_DebugThreat", "-1" ); +} + +function devStrafeRunPathDebugDraw() +{ + white = ( 1, 1, 1 ); + red = ( 1, 0, 0 ); + green = ( 0, 1, 0 ); + blue = ( 0, 0, 1 ); + violet = ( 0.4, 0, 0.6 ); + + maxDrawTime = 10; + drawTime = maxDrawTime; + originTextOffset = ( 0, 0, -50 ); + + endonMsg = "devStopStrafeRunPathDebugDraw"; + + while( true ) + { + if( killstreaks::should_draw_debug("planemortar") > 0 ) + { + nodes = []; + end = false; + node = GetVehicleNode( "warthog_start", "targetname" ); + + if ( !isdefined( node ) ) + { + println( "No strafe run path found" ); + SetDvar( "scr_devStrafeRunPathDebugDraw", "0" ); + continue; + } + + while( isdefined( node.target ) ) + { + new_node = GetVehicleNode( node.target, "targetname" ); + + // check for a cyclic connection + foreach( n in nodes ) + { + if ( n == new_node ) + { + end = true; + } + } + + textScale = 30; + if( drawTime == maxDrawTime ) + node thread drawPathSegment( new_node, violet, violet, 1, textScale, originTextOffset, drawTime, endonMsg ); + + if( isdefined( node.script_noteworthy ) ) + { + textScale = 10; + switch( node.script_noteworthy ) + { + case "strafe_start": + textColor = green; + textAlpha = 1; + break; + case "strafe_stop": + textColor = red; + textAlpha = 1; + break; + case "strafe_leave": + textColor = white; + textAlpha = 1; + break; + } + + switch( node.script_noteworthy ) + { + case "strafe_start": + case "strafe_stop": + case "strafe_leave": + // only call this thread every N time +// if( drawTime == maxDrawTime ) +// node thread drawPath( textColor, white, textAlpha, textScale, originTextOffset, drawTime, endonMsg ); + + sides = 10; + radius = 100; + + if( drawTime == maxDrawTime ) + sphere( node.origin, radius, textColor, textAlpha, true, sides, drawTime * 1000 ); + + node drawOriginLines(); + node drawNoteWorthyText( textColor, textAlpha, textScale ); + break; + } + } + + if ( end ) + { + break; + } + + nodes[ nodes.size ] = new_node; + node = new_node; + } + + drawTime -= 0.05; + if( drawTime < 0 ) + drawTime = maxDrawTime; + + {wait(.05);}; + } + else + { + wait( 1 ); + } + } +} + +function devHeliPathDebugDraw() +{ + white = ( 1, 1, 1 ); + red = ( 1, 0, 0 ); + green = ( 0, 1, 0 ); + blue = ( 0, 0, 1 ); + + textColor = white; + textAlpha = 1; + textScale = 1; + + maxDrawTime = 10; + drawTime = maxDrawTime; + + originTextOffset = ( 0, 0, -50 ); + + endonMsg = "devStopHeliPathsDebugDraw"; + + while( true ) + { + if( GetDvarInt( "scr_devHeliPathsDebugDraw" ) > 0 ) + { + // get all script_models, script_origins, what ever else is a script mover, and show them + script_origins = GetEntArray( "script_origin", "classname" ); + + foreach( ent in script_origins ) + { + if( isdefined( ent.targetname ) ) + { + switch( ent.targetname ) + { + case "heli_start": + textColor = blue; + textAlpha = 1; + textScale = 3; + break; + case "heli_loop_start": + textColor = green; + textAlpha = 1; + textScale = 3; + break; + case "heli_attack_area": + textColor = red; + textAlpha = 1; + textScale = 3; + break; + case "heli_leave": + textColor = white; + textAlpha = 1; + textScale = 3; + break; + } + + switch( ent.targetname ) + { + case "heli_start": + case "heli_loop_start": + case "heli_attack_area": + case "heli_leave": + // only call this thread every N time + if( drawTime == maxDrawTime ) + ent thread drawPath( textColor, white, textAlpha, textScale, originTextOffset, drawTime, endonMsg ); + + ent drawOriginLines(); + ent drawTargetNameText( textColor, textAlpha, textScale ); + ent drawOriginText( textColor, textAlpha, textScale, originTextOffset ); + break; + } + } + } + + drawTime -= 0.05; + if( drawTime < 0 ) + drawTime = maxDrawTime; + } + + if( GetDvarInt( "scr_devHeliPathsDebugDraw" ) == 0 ) + { + level notify( endonMsg ); + drawTime = maxDrawTime; + wait( 1 ); + } + + {wait(.05);}; + } +} + +function drawOriginLines() +{ + red = ( 1, 0, 0 ); + green = ( 0, 1, 0 ); + blue = ( 0, 0, 1 ); + + Line( self.origin, self.origin + ( AnglesToForward( self.angles ) * 10 ), red ); + Line( self.origin, self.origin + ( AnglesToRight( self.angles ) * 10 ), green ); + Line( self.origin, self.origin + ( AnglesToUp( self.angles ) * 10 ), blue ); +} + +function drawTargetNameText( textColor, textAlpha, textScale, textOffset ) +{ + if( !isdefined( textOffset ) ) + textOffset = ( 0, 0, 0 ); + Print3d( self.origin + textOffset, self.targetname, textColor, textAlpha, textScale ); +} + +function drawNoteWorthyText( textColor, textAlpha, textScale, textOffset ) +{ + if( !isdefined( textOffset ) ) + textOffset = ( 0, 0, 0 ); + Print3d( self.origin + textOffset, self.script_noteworthy, textColor, textAlpha, textScale ); +} + +function drawOriginText( textColor, textAlpha, textScale, textOffset ) +{ + if( !isdefined( textOffset ) ) + textOffset = ( 0, 0, 0 ); + originString = "(" + self.origin[0] + ", " + self.origin[1] + ", " + self.origin[2] + ")"; + Print3d( self.origin + textOffset, originString, textColor, textAlpha, textScale ); +} + +function drawSpeedAccelText( textColor, textAlpha, textScale, textOffset ) +{ + if( isdefined( self.script_airspeed ) ) + Print3d( self.origin + ( 0, 0, textOffset[2] * 2 ), "script_airspeed:" + self.script_airspeed, textColor, textAlpha, textScale ); + if( isdefined( self.script_accel ) ) + Print3d( self.origin + ( 0, 0, textOffset[2] * 3 ), "script_accel:" + self.script_accel, textColor, textAlpha, textScale ); +} + +function drawPath( lineColor, textColor, textAlpha, textScale, textOffset, drawTime, endonMsg ) // self == starting node +{ + level endon( endonMsg ); + + // draw lines from origin to origin until there is no target + ent = self; + entFirstTarget = ent.targetname; + + while( isdefined( ent.target ) ) + { + entTarget = GetEnt( ent.target, "targetname" ); + ent thread drawPathSegment( entTarget, lineColor, textColor, textAlpha, textScale, textOffset, drawTime, endonMsg ); + + // store the first target because we have the loop nodes that will always have a target + if( ent.targetname == "heli_loop_start" ) + entFirstTarget = ent.target; + else if( ent.target == entFirstTarget ) + break; + + ent = entTarget; + {wait(.05);}; + } +} + +function drawPathSegment( entTarget, lineColor, textColor, textAlpha, textScale, textOffset, drawTime, endonMsg ) +{ + level endon( endonMsg ); + + // draw the line for a certain amount of time + while( drawTime > 0 ) + { + if ( isdefined( self.targetname ) && self.targetname == "warthog_start" ) + { + Print3d( self.origin + textOffset, self.targetname, textColor, textAlpha, textScale ); + } + + Line( self.origin, entTarget.origin, lineColor ); + + + self drawSpeedAccelText( textColor, textAlpha, textScale, textOffset ); + drawTime -= 0.05; + {wait(.05);}; + } +} + +function get_lookat_origin( player ) +{ + angles = player GetPlayerAngles(); + forward = AnglesToForward( angles ); + dir = VectorScale( forward, 8000 ); + + eye = player GetEye(); + trace = BulletTrace( eye, eye + dir, 0, undefined ); + + return trace[ "position" ]; +} + +function draw_pathnode( node, color ) +{ + if ( !isdefined( color ) ) + { + color = ( 1, 0, 1 ); + } + + Box( node.origin, ( -16, -16, 0 ), ( 16, 16, 16 ), 0, color, 1, false, 1 ); +} + +function draw_pathnode_think( node, color ) +{ + level endon( "draw_pathnode_stop" ); + + for ( ;; ) + { + draw_pathnode( node, color ); + {wait(.05);}; + } +} + +function draw_pathnodes_stop() +{ + wait( 5 ); + level notify( "draw_pathnode_stop" ); +} + +function node_get( player ) +{ + for ( ;; ) + { + {wait(.05);}; + + origin = get_lookat_origin( player ); + node = GetNearestNode( origin ); + + if ( !isdefined( node ) ) + { + continue; + } + + if ( player ButtonPressed( "BUTTON_A" ) ) + { + return node; + } + else if ( player ButtonPressed( "BUTTON_B" ) ) + { + return undefined; + } + + if ( node.type == "Path" ) + { + draw_pathnode( node, ( 1, 0, 1 ) ); + } + else + { + draw_pathnode( node, ( 0.85, 0.85, 0.10 ) ); + } + } +} + +function dev_get_node_pair() +{ + player = util::getHostPlayer(); + + start = undefined; + + while ( !isdefined( start ) ) + { + start = node_get( player ); + + if ( player ButtonPressed( "BUTTON_B" ) ) + { + level notify( "draw_pathnode_stop" ); + return undefined; + } + } + + level thread draw_pathnode_think( start, ( 0, 1, 0 ) ); + + while ( player ButtonPressed( "BUTTON_A" ) ) + { + {wait(.05);}; + } + + end = undefined; + + while ( !isdefined( end ) ) + { + end = node_get( player ); + + if ( player ButtonPressed( "BUTTON_B" ) ) + { + level notify( "draw_pathnode_stop" ); + return undefined; + } + } + + level thread draw_pathnode_think( end, ( 0, 1, 0 ) ); + level thread draw_pathnodes_stop(); + + array = []; + array[0] = start; + array[1] = end; + + return array; +} + +function draw_point( origin, color ) +{ + if ( !isdefined( color ) ) + { + color = ( 1, 0, 1 ); + } + + Sphere( origin, 16, color, 0.25, false, 16, 1 ); +} + +function point_get( player ) +{ + for ( ;; ) + { + {wait(.05);}; + + origin = get_lookat_origin( player ); + + if ( player ButtonPressed( "BUTTON_A" ) ) + { + return origin; + } + else if ( player ButtonPressed( "BUTTON_B" ) ) + { + return undefined; + } + + draw_point( origin, ( 1, 0, 1 ) ); + } +} + +function dev_get_point_pair() +{ + player = util::getHostPlayer(); + + start = undefined; + points = []; + + while ( !isdefined( start ) ) + { + start = point_get( player ); + + if ( !IsDefined( start ) ) + { + return points; + } + } + + while ( player ButtonPressed( "BUTTON_A" ) ) + { + {wait(.05);}; + } + + end = undefined; + + while ( !isdefined( end ) ) + { + end = point_get( player ); + + if ( !IsDefined( end ) ) + { + return points; + } + } + + points[0] = start; + points[1] = end; + + return points; +} + +#/ diff --git a/mp/gametypes/_dev_class.gsc b/mp/gametypes/_dev_class.gsc new file mode 100644 index 0000000..0ca29ec --- /dev/null +++ b/mp/gametypes/_dev_class.gsc @@ -0,0 +1,660 @@ +#using scripts\codescripts\struct; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_dev; + +#using scripts\mp\_util; + +// dev module for create-a-class 2.0 +#namespace dev_class; + +/# + +function dev_cac_init() +{ + dev_cac_overlay = false; + dev_cac_camera_on = false; + + level thread dev_cac_gdt_update_think(); + + for ( ;; ) + { + wait( 0.5 ); + + reset = true; + + if ( GetDvarString( "scr_disable_cac_2" ) != "" ) + { + continue; + } + + host = util::getHostPlayer(); + + if ( !isdefined( level.dev_cac_player ) ) + { + level.dev_cac_player = host; + } + + switch( GetDvarString( "devgui_dev_cac" ) ) + { + case "": + reset = false; + break; + + case "dpad_body": + host thread dev_cac_dpad_think( "body",&dev_cac_cycle_body, "" ); + break; + + case "dpad_head": + host thread dev_cac_dpad_think( "head",&dev_cac_cycle_head, "" ); + break; + + case "dpad_character": + host thread dev_cac_dpad_think( "character",&dev_cac_cycle_character, "" ); + break; + + case "next_player": + dev_cac_cycle_player(true); + break; + + case "prev_player": + dev_cac_cycle_player(false); + break; + + case "cac_overlay": + level notify( "dev_cac_overlay_think" ); + + if ( !dev_cac_overlay ) + level thread dev_cac_overlay_think(); + + dev_cac_overlay = !dev_cac_overlay; + break; + + case "best_bullet_armor": + dev_cac_set_model_range(&sort_greatest, "armor_bullet" ); + break; + case "worst_bullet_armor": + dev_cac_set_model_range(&sort_least, "armor_bullet" ); + break; + case "best_explosive_armor": + dev_cac_set_model_range(&sort_greatest, "armor_explosive" ); + break; + case "worst_explosive_armor": + dev_cac_set_model_range(&sort_least, "armor_explosive" ); + break; + case "best_mobility": + dev_cac_set_model_range(&sort_greatest, "mobility" ); + break; + case "worst_mobility": + dev_cac_set_model_range(&sort_least, "mobility" ); + break; + + case "camera": + dev_cac_camera_on = !dev_cac_camera_on; + dev_cac_camera( dev_cac_camera_on ); + break; + + // Weapon Options: + case "dpad_camo": + host thread dev_cac_dpad_think( "camo",&dev_cac_cycle_render_options, "camo" ); + break; + + case "dpad_meleecamo": + host thread dev_cac_dpad_think( "meleecamo",&dev_cac_cycle_render_options, "meleecamo" ); + break; + + case "dpad_lens": + host thread dev_cac_dpad_think( "lens",&dev_cac_cycle_render_options, "lens" ); + break; + + case "dpad_reticle": + host thread dev_cac_dpad_think( "reticle",&dev_cac_cycle_render_options, "reticle" ); + break; + + case "dpad_reticle_color": + host thread dev_cac_dpad_think( "reticle color",&dev_cac_cycle_render_options, "reticle_color" ); + break; + + case "dpad_emblem": + host thread dev_cac_dpad_think( "emblem",&dev_cac_cycle_render_options, "emblem" ); + break; + + case "dpad_tag": + host thread dev_cac_dpad_think( "tag",&dev_cac_cycle_render_options, "tag" ); + break; + + // Facepaint options: + case "dpad_facepaint_pattern": + host thread dev_cac_dpad_think( "facepaint pattern",&dev_cac_cycle_render_options, "facepaint_pattern" ); + break; + + case "dpad_facepaint_color": + host thread dev_cac_dpad_think( "facepaint color",&dev_cac_cycle_render_options, "facepaint_color" ); + break; + + case "dpad_reset": + host notify( "dev_cac_dpad_think" ); + break; + } + + if( reset ) + { + SetDvar( "devgui_dev_cac", "" ); + } + } +} + +function dev_cac_camera( on ) +{ + if ( on ) + { + self SetClientThirdPerson( 1 ); + SetDvar( "cg_thirdPersonAngle", "185" ); + SetDvar( "cg_thirdPersonRange", "138" ); + SetDvar( "cg_fov", "20" ); + } + else + { + self SetClientThirdPerson( 0 ); + SetDvar( "cg_fov", GetDvarString( "cg_fov_default" ) ); + } +} + +function dev_cac_dpad_think( part_name, cycle_function, tag ) +{ + self notify( "dev_cac_dpad_think" ); + self endon( "dev_cac_dpad_think" ); + self endon( "disconnect" ); + + iprintln( "Previous " + part_name + " bound to D-Pad Left" ); + iprintln( "Next " + part_name + " bound to D-Pad Right" ); + + dpad_left = false; + dpad_right = false; + + // Highlight the player in focus + level.dev_cac_player thread highlight_player(); + + for ( ;; ) + { + self SetActionSlot( 3, "" ); + self SetActionSlot( 4, "" ); + + if ( !dpad_left && self ButtonPressed( "DPAD_LEFT" ) ) + { + [[cycle_function]]( false, tag ); + dpad_left = true; + } + else if ( !self ButtonPressed( "DPAD_LEFT" ) ) + { + dpad_left = false; + } + + if ( !dpad_right && self ButtonPressed( "DPAD_RIGHT" ) ) + { + [[cycle_function]]( true, tag ); + dpad_right = true; + } + else if ( !self ButtonPressed( "DPAD_RIGHT" ) ) + { + dpad_right = false; + } + + {wait(.05);}; + } +} + +function next_in_list( value, list ) +{ + if ( !isdefined( value ) ) + return list[0]; + + for ( i = 0; i < list.size; i++ ) + { + if ( value == list[i] ) + { + if ( isdefined( list[ i + 1 ] ) ) + value = list[ i + 1 ]; + else + value = list[0]; + + break; + } + } + + return value; +} + +function prev_in_list( value, list ) +{ + if ( !isdefined( value ) ) + return list[0]; + + for ( i = 0; i < list.size; i++ ) + { + if ( value == list[i] ) + { + if ( isdefined( list[ i - 1 ] ) ) + value = list[ i - 1 ]; + else + value = list[ list.size - 1 ]; + + break; + } + } + + return value; +} + +function dev_cac_set_player_model() +{ +// self ClearPerks(); + self.tag_stowed_back = undefined; + self.tag_stowed_hip = undefined; + + //self teams::set_player_model(); +} + +function dev_cac_cycle_body( forward, tag ) +{ + if ( !dev_cac_player_valid() ) + { + return; + } + + player = level.dev_cac_player; + keys = GetArrayKeys( level.cac_functions[ "set_body_model" ] ); + + if ( forward ) + player.cac_body_type = next_in_list( player.cac_body_type, keys ); + else + player.cac_body_type = prev_in_list( player.cac_body_type, keys ); + + player dev_cac_set_player_model(); +} + +function dev_cac_cycle_head( forward, tag ) +{ + if ( !dev_cac_player_valid() ) + { + return; + } + + player = level.dev_cac_player; + keys = GetArrayKeys( level.cac_functions[ "set_head_model" ] ); + + if ( forward ) + player.cac_head_type = next_in_list( player.cac_head_type, keys ); + else + player.cac_head_type = prev_in_list( player.cac_head_type, keys ); + + player.cac_hat_type = "none"; + player dev_cac_set_player_model(); +} + +function dev_cac_cycle_character( forward, tag ) +{ + if ( !dev_cac_player_valid() ) + { + return; + } + + player = level.dev_cac_player; + keys = GetArrayKeys( level.cac_functions[ "set_body_model" ] ); + + if ( forward ) + player.cac_body_type = next_in_list( player.cac_body_type, keys ); + else + player.cac_body_type = prev_in_list( player.cac_body_type, keys ); + + //player.cac_head_type = player _armor::get_default_head(); + + player.cac_hat_type = "none"; + player dev_cac_set_player_model(); +} + +function dev_cac_cycle_render_options( forward, tag ) +{ + if ( !dev_cac_player_valid() ) + return; + + level.dev_cac_player NextPlayerRenderOption( tag, forward ); +} + +function dev_cac_player_valid() +{ + return ( isdefined( level.dev_cac_player ) && level.dev_cac_player.sessionstate == "playing" ); +} + +function dev_cac_cycle_player( forward ) +{ + players = GetPlayers(); + + for( i = 0; i < players.size; i++ ) + { + if ( forward ) + level.dev_cac_player = next_in_list( level.dev_cac_player, players ); + else + level.dev_cac_player = prev_in_list( level.dev_cac_player, players ); + + if ( dev_cac_player_valid() ) + { + level.dev_cac_player thread highlight_player(); + return; + } + } + + level.dev_cac_player = undefined; +} + +function highlight_player() +{ + self SetHighlighted( true ); + wait( 1.0 ); + self SetHighlighted( false ); +} + +function dev_cac_overlay_think() +{ + hud = dev_cac_overlay_create(); + level thread dev_cac_overlay_update( hud ); + + level waittill( "dev_cac_overlay_think" ); + dev_cac_overlay_destroy( hud ); +} + +function dev_cac_overlay_update( hud ) +{ +/* + level endon( "dev_cac_overlay_think" ); + + for ( ;; ) + { + WAIT_SERVER_FRAME; + + if ( !dev_cac_player_valid() ) + { + SetDvar( "player_debugSprint", "0" ); + continue; + } + + player = level.dev_cac_player; + + if ( player == util::getHostPlayer() ) + { + SetDvar( "player_debugSprint", "1" ); + } + else + { + SetDvar( "player_debugSprint", "0" ); + } + + // name + hud.menu[0] SetText( player.name ); + + // models + hud.menu[25] SetText( player.cac_body_type ); + hud.menu[26] SetText( player.cac_head_type ); + hud.menu[27] SetText( player.cac_hat_type ); + + // mobility + hud.menu[28].color = color( player get_mobility_body() ); + hud.menu[29].color = color( player get_mobility_head() ); + hud.menu[28] SetValue( player get_mobility_body() ); + hud.menu[29] SetValue( player get_mobility_head() ); + hud.menu[30] SetValue( player GetMoveSpeedScale() ); + hud.menu[31] SetValue( player get_sprint_duration() ); + hud.menu[32] SetValue( player get_sprint_cooldown() ); + + // armor - bullet + hud.menu[33].color = color( player get_armor_bullet_body() ); + hud.menu[34].color = color( player get_armor_bullet_head() ); + hud.menu[33] SetValue( player get_armor_bullet_body() ); + hud.menu[34] SetValue( player get_armor_bullet_head() ); + + // armor - explosive + hud.menu[35].color = color( player get_armor_explosive_body() ); + hud.menu[36].color = color( player get_armor_explosive_head() ); + hud.menu[35] SetValue( player get_armor_explosive_body() ); + hud.menu[36] SetValue( player get_armor_explosive_head() ); + + // damage + if ( isdefined( player.cac_debug_damage_type ) ) + { + diff = player.cac_debug_final_damage - player.cac_debug_original_damage; + + hud.menu[37] SetText( player.cac_debug_damage_type ); + hud.menu[38] SetValue( player.cac_debug_original_damage ); + hud.menu[39] SetValue( player.cac_debug_final_damage ); + hud.menu[40] SetValue( diff ); + hud.menu[41] SetText( player.cac_debug_location ); + hud.menu[42] SetText( player.cac_debug_weapon ); + hud.menu[43] SetText( player.cac_debug_range ); + } + else + { + hud.menu[37] SetText( "" ); + hud.menu[38] SetText( "" ); + hud.menu[39] SetText( "" ); + hud.menu[40] SetText( "" ); + hud.menu[41] SetText( "" ); + hud.menu[42] SetText( "" ); + hud.menu[43] SetText( "" ); + } + } +*/ +} + +function dev_cac_overlay_destroy( hud ) +{ + for ( i = 0; i < hud.menu.size; i++ ) + { + hud.menu[i] Destroy(); + } + + hud Destroy(); + + SetDvar( "player_debugSprint", "0" ); +} + +function dev_cac_overlay_create() +{ + x = -80; + y = 140; + menu_name = "dev_cac_debug"; + + hud = dev::new_hud( menu_name, undefined, x, y, 1 ); + hud SetShader( "white", 185, 285 ); + hud.alignX = "left"; + hud.alignY = "top"; + hud.sort = 10; + hud.alpha = 0.6; + hud.color = ( 0.0, 0.0, 0.5 ); + + x_offset = 100; + + hud.menu[0] = dev::new_hud( menu_name, "NAME", x + 5, y + 10, 1.3 ); + + hud.menu[1] = dev::new_hud( menu_name, "MODELS", x + 5, y + 25, 1 ); + hud.menu[2] = dev::new_hud( menu_name, " Body:", x + 5, y + 35, 1 ); + hud.menu[3] = dev::new_hud( menu_name, " Head:", x + 5, y + 45, 1 ); + hud.menu[4] = dev::new_hud( menu_name, " Head Gear:", x + 5, y + 55, 1 ); + + hud.menu[5] = dev::new_hud( menu_name, "MOBILITY", x + 5, y + 70, 1 ); + hud.menu[6] = dev::new_hud( menu_name, " Body:", x + 5, y + 80, 1 ); + hud.menu[7] = dev::new_hud( menu_name, " Head Gear:", x + 5, y + 90, 1 ); + hud.menu[8] = dev::new_hud( menu_name, " Speed Scale:", x + 5, y + 100, 1 ); + hud.menu[9] = dev::new_hud( menu_name, " Sprint Duration:", x + 5, y + 110, 1 ); + hud.menu[10] = dev::new_hud( menu_name, " Sprint Cooldown:", x + 5, y + 120, 1 ); + + hud.menu[11] = dev::new_hud( menu_name, "ARMOR - BULLET", x + 5, y + 135, 1 ); + hud.menu[12] = dev::new_hud( menu_name, " Body:", x + 5, y + 145, 1 ); + hud.menu[13] = dev::new_hud( menu_name, " Head Gear:", x + 5, y + 155, 1 ); + + hud.menu[14] = dev::new_hud( menu_name, "ARMOR - EXPLOSIVE", x + 5, y + 170, 1 ); + hud.menu[15] = dev::new_hud( menu_name, " Body:", x + 5, y + 180, 1 ); + hud.menu[16] = dev::new_hud( menu_name, " Head Gear:", x + 5, y + 190, 1 ); + + hud.menu[17] = dev::new_hud( menu_name, "DAMAGE", x + 5, y + 205, 1 ); + hud.menu[18] = dev::new_hud( menu_name, " Type:", x + 5, y + 215, 1 ); + hud.menu[19] = dev::new_hud( menu_name, " Original:", x + 5, y + 225, 1 ); + hud.menu[20] = dev::new_hud( menu_name, " Final:", x + 5, y + 235, 1 ); + hud.menu[21] = dev::new_hud( menu_name, " Gain/Loss:", x + 5, y + 245, 1 ); + hud.menu[22] = dev::new_hud( menu_name, " Location:", x + 5, y + 255, 1 ); + hud.menu[23] = dev::new_hud( menu_name, " Weapon:", x + 5, y + 265, 1 ); + hud.menu[24] = dev::new_hud( menu_name, " Range:", x + 5, y + 275, 1 ); + + x_offset = 65; + + hud.menu[25] = dev::new_hud( menu_name, "", x + x_offset, y + 35, 1 ); + hud.menu[26] = dev::new_hud( menu_name, "", x + x_offset, y + 45, 1 ); + hud.menu[27] = dev::new_hud( menu_name, "", x + x_offset, y + 55, 1 ); + + x_offset = 100; + + hud.menu[28] = dev::new_hud( menu_name, "", x + x_offset, y + 80, 1 ); + hud.menu[29] = dev::new_hud( menu_name, "", x + x_offset, y + 90, 1 ); + hud.menu[30] = dev::new_hud( menu_name, "", x + x_offset, y + 100, 1 ); + hud.menu[31] = dev::new_hud( menu_name, "", x + x_offset, y + 110, 1 ); + hud.menu[32] = dev::new_hud( menu_name, "", x + x_offset, y + 120, 1 ); + + hud.menu[33] = dev::new_hud( menu_name, "", x + x_offset, y + 145, 1 ); + hud.menu[34] = dev::new_hud( menu_name, "", x + x_offset, y + 155, 1 ); + + hud.menu[35] = dev::new_hud( menu_name, "", x + x_offset, y + 180, 1 ); + hud.menu[36] = dev::new_hud( menu_name, "", x + x_offset, y + 190, 1 ); + + x_offset = 65; + + hud.menu[37] = dev::new_hud( menu_name, "", x + x_offset, y + 215, 1 ); + hud.menu[38] = dev::new_hud( menu_name, "", x + x_offset, y + 225, 1 ); + hud.menu[39] = dev::new_hud( menu_name, "", x + x_offset, y + 235, 1 ); + hud.menu[40] = dev::new_hud( menu_name, "", x + x_offset, y + 245, 1 ); + hud.menu[41] = dev::new_hud( menu_name, "", x + x_offset, y + 255, 1 ); + hud.menu[42] = dev::new_hud( menu_name, "", x + x_offset, y + 265, 1 ); + hud.menu[43] = dev::new_hud( menu_name, "", x + x_offset, y + 275, 1 ); + + return hud; +} + +function color( value ) +{ + r = 1; + g = 1; + b = 0; + + color = ( 0.0, 0.0, 0.0 ); + + if ( value > 0 ) + r = r - value; + else + g = g + value; + + c = ( r, g, b); + return c; +} + +function dev_cac_gdt_update_think() +{ + for ( ;; ) + { + level waittill( "gdt_update", asset, keyValue ); + + keyValue = StrTok( keyValue, "\\" ); + key = keyValue[0]; + + switch( key ) + { + case "armorBullet": + key = "armor_bullet"; + break; + + case "armorExplosive": + key = "armor_explosive"; + break; + + case "moveSpeed": + key = "mobility"; + break; + + case "sprintTimeTotal": + key = "sprint_time_total"; + break; + + case "sprintTimeCooldown": + key = "sprint_time_cooldown"; + break; + + default: + key = undefined; + break; + } + + if ( !isdefined( key ) ) + { + continue; + } + + value = Float( keyValue[1] ); + level.cac_attributes[ key ][ asset ] = value; + + players = GetPlayers(); + + for ( i = 0; i < players.size; i++ ) + { + //players[i] set_movement_scale(); + } + } +} + +function sort_greatest( func, attribute, greatest ) +{ + keys = GetArrayKeys( level.cac_functions[ func ] ); + greatest = keys[0]; + + for ( i = 0; i < keys.size; i++ ) + { + if ( level.cac_attributes[ attribute ][ keys[i] ] > level.cac_attributes[ attribute ][ greatest ] ) + { + greatest = keys[i]; + } + } + + return greatest; +} + +function sort_least( func, attribute, least ) +{ + keys = GetArrayKeys( level.cac_functions[ func ] ); + least = keys[0]; + + for ( i = 0; i < keys.size; i++ ) + { + if ( level.cac_attributes[ attribute ][ keys[i] ] < level.cac_attributes[ attribute ][ least ] ) + { + least = keys[i]; + } + } + + return least; +} + +function dev_cac_set_model_range( sort_function, attribute ) +{ + if ( !dev_cac_player_valid() ) + { + return; + } + + player = level.dev_cac_player; + + player.cac_body_type = [[sort_function]]( "set_body_model", attribute ); + player.cac_head_type = [[sort_function]]( "set_head_model", attribute ); + player.cac_hat_type = [[sort_function]]( "set_hat_model", attribute ); + + player dev_cac_set_player_model(); +} + +#/ \ No newline at end of file diff --git a/mp/gametypes/_dogtags.gsc b/mp/gametypes/_dogtags.gsc new file mode 100644 index 0000000..3194f4b --- /dev/null +++ b/mp/gametypes/_dogtags.gsc @@ -0,0 +1,442 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\objpoints_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\_util; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spectating; + +#namespace dogtags; + + + +#precache( "fx", "ui/fx_kill_confirmed_vanish" ); + +function init() +{ + level.antiBoostDistance = GetGametypeSetting( "antiBoostDistance" ); + level.dogtags = []; +} + +function spawn_dog_tag( victim, attacker, on_use_function, objectives_for_attacker_and_victim_only ) +{ + if ( isdefined( level.dogtags[victim.entnum] ) ) + { + PlayFx( "ui/fx_kill_confirmed_vanish", level.dogtags[victim.entnum].curOrigin ); + level.dogtags[victim.entnum] notify( "reset" ); + } + else + { + visuals[0] = spawn( "script_model", (0,0,0) ); + visuals[0] setModel( victim GetEnemyDogTagModel() ); + visuals[1] = spawn( "script_model", (0,0,0) ); + visuals[1] setModel( victim GetFriendlyDogTagModel() ); + + trigger = spawn( "trigger_radius", (0,0,0), 0, 32, 32 ); + + level.dogtags[victim.entnum] = gameobjects::create_use_object( "any", trigger, visuals, (0,0,16) ); + + level.dogtags[victim.entnum] gameobjects::set_use_time( 0 ); + level.dogtags[victim.entnum].onUse =&onUse; + level.dogtags[victim.entnum].custom_onUse = on_use_function; + level.dogtags[victim.entnum].victim = victim; + level.dogtags[victim.entnum].victimTeam = victim.team; + + level thread clear_on_victim_disconnect( victim ); + victim thread team_updater( level.dogtags[victim.entnum] ); + + foreach( team in level.teams ) + { + objective_add( level.dogtags[victim.entnum].objId[team], "invisible", (0,0,0) ); + objective_icon( level.dogtags[victim.entnum].objId[team], "waypoint_dogtags" ); + Objective_Team( level.dogtags[victim.entnum].objId[team], team ); + if ( team == attacker.team ) + { + objective_setcolor( level.dogtags[victim.entnum].objId[team], &"EnemyOrange" ); + } + else + { + objective_setcolor( level.dogtags[victim.entnum].objId[team], &"FriendlyBlue" ); + } + } + } + + pos = victim.origin + (0,0,14); + level.dogtags[victim.entnum].curOrigin = pos; + level.dogtags[victim.entnum].trigger.origin = pos; + level.dogtags[victim.entnum].visuals[0].origin = pos; + level.dogtags[victim.entnum].visuals[1].origin = pos; + + level.dogtags[victim.entnum].visuals[0] DontInterpolate(); + level.dogtags[victim.entnum].visuals[1] DontInterpolate(); + + level.dogtags[victim.entnum] gameobjects::allow_use( "any" ); + + level.dogtags[victim.entnum].visuals[0] thread show_to_team( level.dogtags[victim.entnum], attacker.team ); + level.dogtags[victim.entnum].visuals[1] thread show_to_enemy_teams( level.dogtags[victim.entnum], attacker.team ); + + level.dogtags[victim.entnum].attacker = attacker; + level.dogtags[victim.entnum].attackerTeam = attacker.team; + level.dogtags[victim.entnum].unreachable = undefined; + level.dogtags[victim.entnum].tacInsert = false; + //level.dogtags[victim.entnum] thread time_out( victim ); + + foreach( team in level.teams ) + { + if ( IsDefined( level.dogtags[victim.entnum].objId[team] ) ) + { + objective_position( level.dogtags[victim.entnum].objId[team], pos ); + objective_state( level.dogtags[victim.entnum].objId[team], "active" ); + } + } + + if ( objectives_for_attacker_and_victim_only ) + { + Objective_SetInvisibleToAll( level.dogtags[victim.entnum].objId[attacker.team] ); + + if ( IsPlayer( attacker ) ) + Objective_SetVisibleToPlayer( level.dogtags[victim.entnum].objId[attacker.team], attacker ); + + Objective_SetInvisibleToAll( level.dogtags[victim.entnum].objId[victim.team] ); + + if ( IsPlayer( victim ) ) + Objective_SetVisibleToPlayer( level.dogtags[victim.entnum].objId[victim.team], victim ); + } + + //PlaySoundAtPosition( "mpl_killconfirm_tags_drop", pos ); + + level.dogtags[victim.entnum] thread bounce(); + level notify( "dogtag_spawned" ); +} + + +function show_to_team( gameObject, show_team ) +{ + self show(); + + foreach( team in level.teams ) + { + self HideFromTeam( team ); + } + self ShowToTeam( show_team ); +} + +function show_to_enemy_teams( gameObject, friend_team ) +{ + self show(); + + foreach( team in level.teams ) + { + self ShowToTeam( team ); + } + self HideFromTeam( friend_team ); +} + +function onUse( player ) +{ + self.visuals[0] playSound( "mpl_killconfirm_tags_pickup" ); + tacInsertBoost = false; + + // friendly pickup + if ( player.team != self.attackerTeam ) + { + player AddPlayerStat( "KILLSDENIED", 1 ); + player RecordGameEvent("return"); + + if ( self.victim == player ) + { + if ( self.tacInsert == false ) + { + event = "retrieve_own_tags"; + } + else + { + tacInsertBoost = true; + } + } + else + { + event = "kill_denied"; + } + + if ( !tacInsertBoost ) + { + player.pers["killsdenied"]++; + player.killsdenied = player.pers["killsdenied"]; + } + } + else + { + event = "kill_confirmed"; + + player AddPlayerStat( "KILLSCONFIRMED", 1 ); + player RecordGameEvent("capture"); + + if ( isdefined( self.attacker ) && self.attacker != player ) + { + self.attacker onPickup( "teammate_kill_confirmed" ); + } + } + + if ( !tacInsertBoost && isdefined( player ) ) + { + player onPickup( event ); + } + + [[self.custom_onUse]]( player ); + + // do all this at the end now so the location doesn't change before playing the sound on the entity + self reset_tags(); +} + + +function reset_tags() +{ + self.attacker = undefined; + self.unreachable = undefined; + self notify( "reset" ); + self.visuals[0] hide(); + self.visuals[1] hide(); + self.curOrigin = (0,0,1000); + self.trigger.origin = (0,0,1000); + self.visuals[0].origin = (0,0,1000); + self.visuals[1].origin = (0,0,1000); + self.tacInsert = false; + self gameobjects::allow_use( "none" ); + + foreach( team in level.teams ) + { + objective_state( self.objId[team], "invisible" ); + } +} + + +function onPickup( event ) +{ + scoreevents::processScoreEvent( event, self ); +} + + +function clear_on_victim_disconnect( victim ) +{ + level endon( "game_ended" ); + + guid = victim.entnum; + victim waittill( "disconnect" ); + + if ( isdefined( level.dogtags[guid] ) ) + { + // block further use + level.dogtags[guid] gameobjects::allow_use( "none" ); + + // play vanish effect, reset, and wait for reset to process + PlayFx( "ui/fx_kill_confirmed_vanish", level.dogtags[guid].curOrigin ); + level.dogtags[guid] notify( "reset" ); + {wait(.05);}; + + // sanity check before removal + if ( isdefined( level.dogtags[guid] ) ) + { + // delete objective and visuals + foreach( team in level.teams ) + { + objective_delete( level.dogtags[guid].objId[team] ); + } + level.dogtags[guid].trigger delete(); + for ( i=0; i GetTime() ) + { + minDist = level.antiBoostDistance; + minDistSqr = minDist * minDist; + + distSqr = DistanceSquared( self.origin, level.dogtags[self.entnum].curOrigin ); + + // tac insert spawn + if ( distSqr < minDistSqr ) + { + level.dogtags[self.entnum].tacInsert = true; + } + } + } +} + +function team_updater( tags ) +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + while( true ) + { + self waittill( "joined_team" ); + + tags.victimTeam = self.team; + tags reset_tags(); + } +} + +function time_out( victim ) +{ + level endon( "game_ended" ); + victim endon( "disconnect" ); + self notify( "timeout" ); + self endon( "timeout" ); + + level hostmigration::waitLongDurationWithHostMigrationPause( 30.0 ); + + self.visuals[0] hide(); + self.visuals[1] hide(); + self.curOrigin = (0,0,1000); + self.trigger.origin = (0,0,1000); + self.visuals[0].origin = (0,0,1000); + self.visuals[1].origin = (0,0,1000); + self.tacInsert = false; + self gameobjects::allow_use( "none" ); +} + +function bounce() +{ + level endon( "game_ended" ); + self endon( "reset" ); + + bottomPos = self.curOrigin; + topPos = self.curOrigin + (0,0,12); + + while( true ) + { + self.visuals[0] moveTo( topPos, 0.5, 0.15, 0.15 ); + self.visuals[0] rotateYaw( 180, 0.5 ); + self.visuals[1] moveTo( topPos, 0.5, 0.15, 0.15 ); + self.visuals[1] rotateYaw( 180, 0.5 ); + + wait( 0.5 ); + + self.visuals[0] moveTo( bottomPos, 0.5, 0.15, 0.15 ); + self.visuals[0] rotateYaw( 180, 0.5 ); + self.visuals[1] moveTo( bottomPos, 0.5, 0.15, 0.15 ); + self.visuals[1] rotateYaw( 180, 0.5 ); + + wait( 0.5 ); + } +} + +function checkAllowSpectating() +{ + self endon("disconnect"); + + {wait(.05);}; + /* + + update = false; + + livesLeft = !(level.numLives && !self.pers["lives"]); + + if ( !level.aliveCount[ game["attackers"] ] && !livesLeft ) + { + level.spectateOverride[game["attackers"]].allowEnemySpectate = 1; + update = true; + } + if ( !level.aliveCount[ game["defenders"] ] && !livesLeft ) + { + level.spectateOverride[game["defenders"]].allowEnemySpectate = 1; + update = true; + } + if ( update ) + */ + spectating::update_settings(); +} + +//self is victim +function should_spawn_tags( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( IsAlive( self ) ) + return false; + + //no on switching teams + if ( IsDefined( self.switching_teams ) ) + return false; + + //no on suicide + if ( isDefined( attacker ) && attacker == self ) + return false; + + //no on TK + if ( level.teamBased && isDefined( attacker ) && isDefined( attacker.team ) && attacker.team == self.team ) + return false; + + //no on world suicides + if( IsDefined( attacker ) && ( !IsDefined( attacker.team ) || attacker.team == "free" ) && ( attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" ) ) + return false; + + return true; +} + +function onUseDogTag( player ) +{ + // friendly pickup + if ( player.pers["team"] == self.victimTeam ) + { + player.pers["rescues"]++; + player.rescues = player.pers["rescues"]; + + if ( isdefined( self.victim ) ) + { + if ( !level.gameEnded ) + self.victim thread dt_respawn(); + } + } +} + +function dt_respawn() +{ + // Need to count this player as alive immediately, because they can wait to spawn whenever they want. If we don't increment this until they become alive, + // then things get screwed up when level.aliveCount becomes 1. The game thinks that there's only one player left alive, and yet there are multiple players + // on the team with self.pers["lives"] greater than 0. +// self maps\mp\gametypes\_playerlogic::incrementAliveCount(self.team); +// self.alreadyAddedToAliveCount = true; + + self thread waitTillCanSpawnClient(); +} + +//fixes a potential race condition with spawning around the same frame as friendly tag pickup +function waitTillCanSpawnClient() +{ + for (;;) + { + wait ( .05 ); + if ( isDefined( self ) && ( self.sessionstate == "spectator" || !isAlive( self ) ) ) + { + self.pers["lives"] = 1; + self thread [[level.spawnClient]](); + + //we need to continue here because spawn client can fail for up to 3 server frames in this instance + continue; + } + + //player either disconnected or has spawned + return; + } +} \ No newline at end of file diff --git a/mp/gametypes/_friendicons.gsc b/mp/gametypes/_friendicons.gsc new file mode 100644 index 0000000..5391e6a --- /dev/null +++ b/mp/gametypes/_friendicons.gsc @@ -0,0 +1,108 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; + + + +#namespace friendicons; + +function autoexec __init__sytem__() { system::register("friendicons",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); +} + +function init() +{ + if ( !level.teamBased ) + { + return; + } + + // Draws a team icon over teammates + if(GetDvarString( "scr_drawfriend") == "") + { + SetDvar("scr_drawfriend", "0"); + } + level.drawfriend = GetDvarint( "scr_drawfriend"); + + assert( isdefined(game["headicon_allies"]), "Allied head icons are not defined. Check the team set for the level."); + assert( isdefined(game["headicon_axis"]), "Axis head icons are not defined. Check the team set for the level."); + + callback::on_spawned( &on_player_spawned ); + callback::on_player_killed( &on_player_killed ); + + for(;;) + { + updateFriendIconSettings(); + wait 5; + } +} + +function on_player_killed() +{ + self endon("disconnect"); + + self.headicon = ""; +} + +function on_player_spawned() +{ + self endon("disconnect"); + + self thread showFriendIcon(); +} + +function showFriendIcon() +{ + if(level.drawfriend) + { + team = self.pers["team"]; + self.headicon = game["headicon_" + team]; + self.headiconteam = team; + } +} + +function updateFriendIconSettings() +{ + drawfriend = GetDvarfloat( "scr_drawfriend"); + if(level.drawfriend != drawfriend) + { + level.drawfriend = drawfriend; + + updateFriendIcons(); + } +} + +function updateFriendIcons() +{ + // for all living players, show the appropriate headicon + players = level.players; + for(i = 0; i < players.size; i++) + { + player = players[i]; + + if(isdefined(player.pers["team"]) && player.pers["team"] != "spectator" && player.sessionstate == "playing") + { + if(level.drawfriend) + { + team = self.pers["team"]; + self.headicon = game["headicon_" + team]; + self.headiconteam = team; + } + else + { + players = level.players; + for(i = 0; i < players.size; i++) + { + player = players[i]; + + if(isdefined(player.pers["team"]) && player.pers["team"] != "spectator" && player.sessionstate == "playing") + player.headicon = ""; + } + } + } + } +} diff --git a/mp/gametypes/_globallogic.csc b/mp/gametypes/_globallogic.csc new file mode 100644 index 0000000..8fd3173 --- /dev/null +++ b/mp/gametypes/_globallogic.csc @@ -0,0 +1,180 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\visionset_mgr_shared; +#using scripts\shared\weapons\_hive_gun; +#using scripts\shared\weapons\_weaponobjects; + + + + + + +#precache( "client_fx", "weapon/fx_hero_annhilatr_death_blood" ); +#precache( "client_fx", "weapon/fx_hero_pineapple_death_blood" ); + +#namespace globallogic; + +function autoexec __init__sytem__() { system::register("globallogic",&__init__,undefined,"visionset_mgr"); } + +function __init__() +{ + visionset_mgr::register_visionset_info( "mpintro", 1, 31, undefined, "mpintro" ); + + //handles actor and player corpse case + clientfield::register( "world", "game_ended", 1, 1, "int", &game_ended, true, true ); + clientfield::register( "world", "post_game", 1, 1, "int", &post_game, true, true ); + RegisterClientField("playercorpse", "firefly_effect", 1, 2, "int", &firefly_effect_cb, false); + RegisterClientField("playercorpse", "annihilate_effect", 1, 1, "int", &annihilate_effect_cb, false); + RegisterClientField("playercorpse", "pineapplegun_effect", 1, 1, "int", &pineapplegun_effect_cb, false); + RegisterClientField("actor", "annihilate_effect", 1, 1, "int", &annihilate_effect_cb, false); + RegisterClientField("actor", "pineapplegun_effect", 1, 1, "int", &pineapplegun_effect_cb, false); + + level._effect[ "annihilate_explosion" ] = "weapon/fx_hero_annhilatr_death_blood"; + level._effect[ "pineapplegun_explosion" ] = "weapon/fx_hero_pineapple_death_blood"; + + level.gameEnded = false; + level.postGame = false; +} + + +function game_ended(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + if( newVal && !level.gameEnded ) + { + level notify("game_ended"); + level.gameEnded = true; + } +} + +function post_game(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + if( newVal && !level.postGame ) + { + level notify("post_game"); + level.postGame = true; + } +} + +function firefly_effect_cb(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + if( bNewEnt && newVal) + { + self thread hive_gun::gib_corpse( localClientNum, newVal ); + } +} + + +function annihilate_effect_cb(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + + if(newVal && !oldVal) + { + where = self GetTagOrigin( "J_SpineLower" ); + if (!isdefined(where)) + where = self.origin ; + where = where + (0,0,-40); + + character_index = self GetCharacterBodyType(); + fields = GetCharacterFields( character_index, CurrentSessionMode() ); + if ( fields.fullbodyexplosion != "" ) + { + if ( util::is_mature() && !util::is_gib_restricted_build() ) + { + Playfx( localClientNum, fields.fullbodyexplosion, where ); + } + Playfx( localClientNum, "explosions/fx_exp_grenade_default", where ); + } + } +} + + +function pineapplegun_effect_cb(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + if(newVal && !oldVal) + { + where = self GetTagOrigin( "J_SpineLower" ); + if (!isdefined(where)) + where = self.origin; + + if ( IsDefined( level._effect[ "pineapplegun_explosion" ] ) ) + { + Playfx( localClientNum, level._effect["pineapplegun_explosion"], where ); + } + + } +} + + + + +function watch_plant_sound( localClientNum ) +{ + self endon ( "entityshutdown" ); + + while( 1 ) + { + self waittill ( "start_plant_sound" ); + self thread play_plant_sound( localClientNum ); + } +} + +function play_plant_sound( localClientNum ) +{ + self notify ( "play_plant_sound" ); + self endon ( "play_plant_sound" ); + self endon ( "entityshutdown" ); + self endon ( "stop_plant_sound" ); + + player = GetLocalPlayer( localClientNum ); + plantWeapon = GetWeapon( "briefcase_bomb" ); + defuseWeapon = GetWeapon( "briefcase_bomb_defuse" ); + + wait .25; + + while( 1 ) + { + if ( !isdefined( player ) ) // this happens when the player has not initially spawned in + { + return; + } + + if( ( player.weapon != plantWeapon ) && ( player.weapon != defuseWeapon ) ) + { + return; + } + + if( ( player != self ) || IsThirdPerson( localClientNum ) ) + { + self PlaySound( localClientNum, "fly_bomb_buttons_npc" ); + } + + wait .15; + } +} + +// Temporarily reduce the watchdog timer while the match is running +// in an attempt to gather dumps when stutters occur. This is reset +// in code with Sys_Watchdog_ResetLive +function live_watchdog() +{ + util::waitforallclients(); + wait( 5 ); // Give a little extra time once in the match + + liveTimer = GetDvarInt( "sys_threadWatchdogTimeoutLive", 30000 ); + SetDvar( "sys_threadWatchdogTimeout", liveTimer ); +} + +function autoexec init_live_watchdog() +{ + if( GetDvarInt( "sys_threadWatchdogTimeoutLive", 0 ) > 0 ) + { + level thread live_watchdog(); + } +} + + diff --git a/mp/gametypes/_globallogic.gsc b/mp/gametypes/_globallogic.gsc new file mode 100644 index 0000000..bcf8eb7 --- /dev/null +++ b/mp/gametypes/_globallogic.gsc @@ -0,0 +1,4569 @@ +#using scripts\shared\bb_shared; +#using scripts\codescripts\struct; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_shared;//DO NOT REMOVE - needed for system registration +#using scripts\shared\hud_message_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\music_shared; +#using scripts\shared\objpoints_shared; +#using scripts\shared\persistence_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\util_shared; +#using scripts\shared\simple_hostmigration; +#using scripts\shared\system_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\visionset_mgr_shared; +#using scripts\shared\lui_shared; +#using scripts\shared\weapons\_hive_gun; +#using scripts\shared\weapons\_weapon_utils; +#using scripts\shared\weapons\_weapons; +#using scripts\mp\gametypes\_dogtags; + +#using scripts\shared\bots\_bot; + +#using scripts\mp\gametypes\_battlechatter;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_clientids;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_deathicons;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_dev; +#using scripts\mp\gametypes\_friendicons; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_defaults; +#using scripts\mp\gametypes\_globallogic_player; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_globallogic_ui; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_healthoverlay;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_hud_message; +#using scripts\mp\gametypes\_killcam;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_menus;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_scoreboard;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_serversettings;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_shellshock; +#using scripts\mp\gametypes\_spawning;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_spawnlogic;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_spectating;//DO NOT REMOVE - needed for system registration +#using scripts\mp\gametypes\_weapon_utils; +#using scripts\mp\gametypes\_weapons;//DO NOT REMOVE - needed for system registration + +#using scripts\mp\_arena; +#using scripts\mp\_behavior_tracker; +#using scripts\mp\_challenges; +#using scripts\mp\_gameadvertisement; +#using scripts\mp\_gamerep; +#using scripts\mp\_rat; +#using scripts\mp\_teamops; +#using scripts\mp\_util; +#using scripts\mp\bots\_bot;//DO NOT REMOVE - needed for system registration +#using scripts\mp\killstreaks\_dogs; +#using scripts\mp\killstreaks\_killstreaks;//DO NOT REMOVE - needed for system registration +#using scripts\mp\teams\_teams;//DO NOT REMOVE - needed for system registration + + + + + + +// must match the stats.ddl quitTypes_e enum + + + + + + + + + + + + + + +#namespace globallogic; + +#precache( "model", "tag_origin" ); + +#precache( "statusicon", "hud_status_dead" ); +#precache( "statusicon", "hud_status_connecting" ); + +#precache( "material", "white" ); +#precache( "material", "black" ); + +#precache( "string", "PLATFORM_PRESS_TO_SPAWN" ); +#precache( "string", "MP_WAITING_FOR_TEAMS" ); +#precache( "string", "MP_OPPONENT_FORFEITING_IN" ); +#precache( "string", "MP_WAITING_FOR_PLAYERS" ); +#precache( "string", "MP_OPPONENT_FORFEITING_IN" ); +#precache( "string", "MP_MATCH_STARTING_IN" ); +#precache( "string", "MP_SPAWN_NEXT_ROUND" ); +#precache( "string", "MP_WAITING_TO_SPAWN" ); +#precache( "string", "MP_WAITING_TO_SPAWN_SS" ); +//#precache( "string", "MP_WAITING_TO_SAFESPAWN" ); +#precache( "string", "MP_YOU_WILL_RESPAWN" ); +#precache( "string", "MP_MATCH_STARTING" ); +#precache( "string", "MP_CHANGE_CLASS_NEXT_SPAWN" ); +#precache( "string", "MPUI_LAST_STAND" ); +#precache( "string", "PLATFORM_COWARDS_WAY_OUT" ); +#precache( "string", "MP_MATCH_TIE" ); +#precache( "string", "MP_ROUND_DRAW" ); +#precache( "string", "MP_ENEMIES_ELIMINATED" ); +#precache( "string", "MP_SCORE_LIMIT_REACHED" ); +#precache( "string", "MP_ROUND_LIMIT_REACHED" ); +#precache( "string", "MP_TIME_LIMIT_REACHED" ); +#precache( "string", "MP_PLAYERS_FORFEITED" ); +#precache( "string", "MP_OTHER_TEAMS_FORFEITED" ); + +#precache( "eventstring", "prematch_waiting_for_players" ); +#precache( "eventstring", "create_prematch_timer" ); +#precache( "eventstring", "prematch_timer_ended" ); +#precache( "eventstring", "force_scoreboard" ); + +#precache( "menu", "T7Hud_MP" ); + +function autoexec __init__sytem__() { system::register("globallogic",&__init__,undefined,"visionset_mgr"); } + +function __init__() +{ + if(!isdefined(level.vsmgr_prio_visionset_mpintro))level.vsmgr_prio_visionset_mpintro=5; + visionset_mgr::register_info( "visionset", "mpintro", 1, level.vsmgr_prio_visionset_mpintro, 31, false, &visionset_mgr::ramp_in_out_thread, false ); + level.host_migration_activate_visionset_func = &mpintro_visionset_activate_func; + level.host_migration_deactivate_visionset_func = &mpintro_visionset_deactivate_func; +} + +function init() +{ + level.splitscreen = isSplitScreen(); + level.xenon = (GetDvarString( "xenonGame") == "true"); + level.ps3 = (GetDvarString( "ps3Game") == "true"); + level.wiiu = (GetDvarString( "wiiuGame") == "true"); + level.orbis = (GetDvarString( "orbisGame") == "true"); + level.durango = (GetDvarString( "durangoGame") == "true"); + + level.onlineGame = SessionModeIsOnlineGame(); + level.systemLink = SessionModeIsSystemlink(); + level.console = (level.xenon || level.ps3 || level.wiiu || level.orbis || level.durango); + + level.rankedMatch = ( GameModeIsUsingXP() ); + level.leagueMatch = false; + level.customMatch = ( GameModeIsMode( 1 ) ); + level.arenaMatch = GameModeIsArena(); + + level.mpCustomMatch = level.customMatch; + + level.contractsEnabled = !GetGametypeSetting( "disableContracts" ); + + level.contractsEnabled = false; + + level.disableVehicleBurnDamage = true; + + /# + if ( GetDvarint( "scr_forcerankedmatch" ) == 1 ) + level.rankedMatch = true; + #/ + + level.script = toLower( GetDvarString( "mapname" ) ); + level.gametype = toLower( GetDvarString( "g_gametype" ) ); + + level.teamBased = false; + level.teamCount = GetGametypeSetting( "teamCount" ); + level.multiTeam = ( level.teamCount > 2 ); + + // used to loop through all valid playing teams ( not spectator ) + // can also be used to check if a team is valid ( isdefined( level.teams[team] ) ) + // NOTE: added in the same order they are defined in code + level.teams = []; + level.teamIndex = []; + + teamCount = level.teamCount; + + if ( level.teamCount == 1 ) + { + teamCount = 18; + level.teams[ "free" ] = "free"; + } + + level.teams[ "allies" ] = "allies"; + level.teams[ "axis" ] = "axis"; + + level.teamIndex[ "neutral" ] = 0; // Neutral team set to 0 so that it can be used by objectives + level.teamIndex[ "allies" ] = 1; + level.teamIndex[ "axis" ] = 2; + + for( teamIndex = 3; teamIndex <= teamCount; teamIndex++ ) + { + level.teams[ "team" + teamIndex ] = "team" + teamIndex; + level.teamIndex[ "team" + teamIndex ] = teamIndex; + } + + level.overrideTeamScore = false; + level.overridePlayerScore = false; + level.displayHalftimeText = false; + level.displayRoundEndText = true; + + level.clampScoreLimit = true; + level.endGameOnScoreLimit = true; + level.endGameOnTimeLimit = true; + level.scoreRoundWinBased = false; + level.resetPlayerScoreEveryRound = false; + level.doEndgameScoreboard = true; + + level.gameForfeited= false; + level.forceAutoAssign = false; + + level.halftimeType = "halftime"; + level.halftimeSubCaption = &"MP_SWITCHING_SIDES_CAPS"; + + level.lastStatusTime = 0; + level.wasWinning = []; + + level.lastSlowProcessFrame = 0; + + level.placement = []; + foreach( team in level.teams ) + { + level.placement[team] = []; + } + level.placement["all"] = []; + + level.postRoundTime = 7.0;//Kevin Sherwood changed to 9 to have enough time for music stingers + + level.inOvertime = false; + + level.defaultOffenseRadius = 560; + level.defaultOffenseRadiusSQ = level.defaultOffenseRadius * level.defaultOffenseRadius; + + level.dropTeam = GetDvarint( "sv_maxclients" ); + + level.inFinalKillcam = false; + + globallogic_ui::init(); + + registerDvars(); + loadout::initPerkDvars(); + + level.oldschool = GetGametypeSetting( "oldschoolMode" ); + + precache_mp_leaderboards(); + + if ( !isdefined( game["tiebreaker"] ) ) + game["tiebreaker"] = false; + + thread gameadvertisement::init(); + thread gamerep::init(); + thread teamops::init(); + + level.disableChallenges = false; + + if ( level.leagueMatch || ( GetDvarInt( "scr_disableChallenges" ) > 0 ) ) + { + level.disableChallenges = true; + } + + level.disableStatTracking = ( GetDvarInt( "scr_disableStatTracking" ) > 0 ); + + setup_callbacks(); + + //handles both actor and player corpse cases + clientfield::register( "playercorpse", "firefly_effect", 1, 2, "int" ); + clientfield::register( "playercorpse", "annihilate_effect", 1, 1, "int" ); + clientfield::register( "playercorpse", "pineapplegun_effect", 1, 1, "int" ); + clientfield::register( "actor", "annihilate_effect", 1, 1, "int" ); + clientfield::register( "actor", "pineapplegun_effect", 1, 1, "int" ); + clientfield::register( "world", "game_ended", 1, 1, "int" ); + clientfield::register( "world", "post_game", 1, 1, "int" ); + clientfield::register( "world", "displayTop3Players", 1, 1, "int" ); + clientfield::register( "world", "triggerScoreboardCamera", 1, 1, "int" ); + clientfield::register( "clientuimodel", "hudItems.hideOutcomeUI", 1, 1, "int" ); + clientfield::register( "clientuimodel", "hudItems.remoteKillstreakActivated", 1, 1, "int" ); + clientfield::register( "world", "playTop0Gesture", 1000, 3, "int" ); + clientfield::register( "world", "playTop1Gesture", 1000, 3, "int" ); + clientfield::register( "world", "playTop2Gesture", 1000, 3, "int" ); + clientfield::register( "clientuimodel", "hudItems.captureCrateState", 5000, 2, "int" ); + clientfield::register( "clientuimodel", "hudItems.captureCrateTotalTime", 5000, 13, "int" ); + + level.playersDrivingVehiclesBecomeInvulnerable = false; + + // for use in shared scripts that do not know which version of figure_out_attacker to use + level.figure_out_attacker = &globallogic_player::figure_out_attacker; + level.figure_out_friendly_fire = &globallogic_player::figure_out_friendly_fire; + level.get_base_weapon_param = &weapon_utils::getBaseWeaponParam; // used to figure out the weapon to as the parameter to GetBaseWeaponItemIndex() + + level thread waitForGameEndDvar(); +} + +function registerDvars() +{ + if ( GetDvarString( "ui_guncycle" ) == "" ) + SetDvar( "ui_guncycle", 0 ); + + //makeDvarServerInfo( "ui_guncycle" ); + + if ( GetDvarString( "ui_weapon_tiers" ) == "" ) + SetDvar( "ui_weapon_tiers", 0 ); + //makeDvarServerInfo( "ui_weapon_tiers" ); + + SetDvar( "ui_text_endreason", ""); + //makeDvarServerInfo( "ui_text_endreason", "" ); + + setMatchFlag( "bomb_timer", 0 ); + + if ( GetDvarString( "scr_vehicle_damage_scalar" ) == "" ) + SetDvar( "scr_vehicle_damage_scalar", "1" ); + + level.vehicleDamageScalar = GetDvarfloat( "scr_vehicle_damage_scalar"); + + level.fire_audio_repeat_duration = GetDvarint( "fire_audio_repeat_duration" ); + level.fire_audio_random_max_duration = GetDvarint( "fire_audio_random_max_duration" ); + + teamName = getcustomteamname( level.teamIndex[ "allies" ] ); + if( isdefined( teamName ) ) + SetDvar( "g_customTeamName_Allies", teamName ); + else + SetDvar( "g_customTeamName_Allies", "" ); + + teamName = getcustomteamname( level.teamIndex[ "axis" ] ); + if( isdefined( teamName ) ) + SetDvar( "g_customTeamName_Axis", teamName ); + else + SetDvar( "g_customTeamName_Axis", "" ); + +} + +function blank( arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 ) +{ +} + +function setup_callbacks() +{ + level.spawnPlayer = &globallogic_spawn::spawnPlayer; + level.spawnPlayerPrediction = &globallogic_spawn::spawnPlayerPrediction; + level.spawnClient = &globallogic_spawn::spawnClient; + level.spawnSpectator = &globallogic_spawn::spawnSpectator; + level.spawnIntermission = &globallogic_spawn::spawnIntermission; + level.scoreOnGivePlayerScore = &globallogic_score::givePlayerScore; + level.onPlayerScore = &globallogic_score::default_onPlayerScore; + level.onTeamScore = &globallogic_score::default_onTeamScore; + + level.waveSpawnTimer =&waveSpawnTimer; + level.spawnMessage = &globallogic_spawn::default_spawnMessage; + + level.onSpawnPlayer =␣ + level.onSpawnPlayer = &spawning::onSpawnPlayer; + level.onSpawnSpectator = &globallogic_defaults::default_onSpawnSpectator; + level.onSpawnIntermission = &globallogic_defaults::default_onSpawnIntermission; + level.onRespawnDelay =␣ + + level.onForfeit = &globallogic_defaults::default_onForfeit; + level.onTimeLimit = &globallogic_defaults::default_onTimeLimit; + level.onScoreLimit = &globallogic_defaults::default_onScoreLimit; + level.onRoundScoreLimit = &globallogic_defaults::default_onRoundScoreLimit; + level.onAliveCountChange = &globallogic_defaults::default_onAliveCountChange; + level.onDeadEvent = undefined; + level.onOneLeftEvent = &globallogic_defaults::default_onOneLeftEvent; + level.giveTeamScore = &globallogic_score::giveTeamScore; + level.onLastTeamAliveEvent = &globallogic_defaults::default_onLastTeamAliveEvent; + + level.getTimePassed = &globallogic_utils::getTimePassed; + level.getTimeLimit = &globallogic_defaults::default_getTimeLimit; + level.getTeamKillPenalty = &globallogic_defaults::default_getTeamKillPenalty; + level.getTeamKillScore = &globallogic_defaults::default_getTeamKillScore; + + level.isKillBoosting = &globallogic_score::default_isKillBoosting; + + level._setTeamScore = &globallogic_score::_setTeamScore; + level._setPlayerScore = &globallogic_score::_setPlayerScore; + + level._getTeamScore = &globallogic_score::_getTeamScore; + level._getPlayerScore = &globallogic_score::_getPlayerScore; + + level.resetPlayerScorestreaks = &globallogic_score::resetPlayerScoreChainAndMomentum; + + level.onPrecacheGametype =␣ + level.onStartGameType =␣ + level.onPlayerConnect =␣ + level.onPlayerDisconnect =␣ + level.onPlayerDamage =␣ + level.onPlayerKilled =␣ + level.onPlayerKilledExtraUnthreadedCBs = []; ///< Array of other CB function pointers + + level.onTeamOutcomeNotify = &hud_message::teamOutcomeNotify; + level.onOutcomeNotify = &hud_message::outcomeNotify; + level.setMatchScoreHUDElemForTeam = &hud_message::setMatchScoreHUDElemForTeam; + level.onEndGame =␣ + level.onRoundEndGame = &globallogic_defaults::default_onRoundEndGame; + level.determineWinner = &globallogic_defaults::default_determineWinner; + level.onMedalAwarded =␣ + level.dogManagerOnGetDogs = &dogs::dog_manager_get_dogs; + + callback::on_joined_team( &globallogic_player::on_joined_team ); + + globallogic_ui::SetupCallbacks(); +} + +function precache_mp_friend_leaderboards() +{ + + hardcoreMode = GetGametypeSetting( "hardcoreMode" ); + if( !isdefined( hardcoreMode ) ) + { + hardcoreMode = false; + } + + arenaMode = IsArenaMode(); + + ///////////////////////////////////////////////////////// + + postfix = ""; // normal + + if( hardcoreMode ) // hardcore + { + postfix = "_HC"; + } + else if( arenaMode ) // arena + { + postfix = "_ARENA"; + } + + friendLeaderboardA = "LB_MP_FRIEND_A" + postfix; + friendLeaderboardB = " LB_MP_FRIEND_B" + postfix; + + precacheLeaderboards( friendLeaderboardA + friendLeaderboardB ); +} + +//Note not all game modes have anticheat so these may not always be found +function precache_mp_anticheat_leaderboards() +{ + + hardcoreMode = GetGametypeSetting( "hardcoreMode" ); + if( !isdefined( hardcoreMode ) ) + { + hardcoreMode = false; + } + + arenaMode = IsArenaMode(); + + ///////////////////////////////////////////////////////// + + postfix = ""; // normal + + if( hardcoreMode ) // hardcore + { + postfix = "_HC"; + } + else if( arenaMode ) // arena + { + postfix = "_ARENA"; + } + + anticheatLeaderboard = "LB_MP_ANTICHEAT_" + level.gametype + postfix; + + if( level.gametype != "fr" ) + { + anticheatLeaderboard = anticheatLeaderboard + " LB_MP_ANTICHEAT_GLOBAL"; + } + + precacheLeaderboards( anticheatLeaderboard ); +} + +function precache_mp_public_leaderboards() +{ + mapname = GetDvarString( "mapname" ); + + hardcoreMode = GetGametypeSetting( "hardcoreMode" ); + if( !isdefined( hardcoreMode ) ) + { + hardcoreMode = false; + } + + arenaMode = IsArenaMode(); + freerunMode = ( level.gametype == "fr" ); + + ///////////////////////////////////////////////////////// + + postfix = ""; // normal + + if( freerunMode ) // freerun + { + // strip the MP_ from the map name + frLeaderboard = " LB_MP_GM_FR_" + getsubstr( mapname, 3, mapname.size ); + + precacheLeaderboards( frLeaderboard ); + return; + } + else if( hardcoreMode ) // hardcore + { + postfix = "_HC"; + } + else if( arenaMode ) // arena + { + postfix = "_ARENA"; + } + + careerLeaderboard = " LB_MP_GB_SCORE" + postfix; + prestigeLB = " LB_MP_GB_XPPRESTIGE"; + gamemodeLeaderboard = "LB_MP_GM_" + level.gametype + postfix; + + arenaLeaderboard = ""; + if( GameModeIsMode( 6 ) ) + { + arenaSlot = ArenaGetSlot(); + arenaLeaderboard = " LB_MP_ARENA_MASTERS_0" + arenaSlot; + } + + precacheLeaderboards( gamemodeLeaderboard + careerLeaderboard + prestigeLB + arenaLeaderboard ); +} + +function precache_mp_custom_leaderboards() +{ + customLeaderboards = "LB_MP_CG_" + level.gametype; + + precacheLeaderboards( "LB_MP_CG_GENERAL " + customLeaderboards ); + + return; +} + +function precache_mp_leaderboards() +{ + if( bot::is_bot_ranked_match() ) + { + return; + } + + if( level.rankedMatch || level.gametype == "fr" ) + { + precache_mp_public_leaderboards(); + precache_mp_friend_leaderboards(); + precache_mp_anticheat_leaderboards(); + } + else + { + precache_mp_custom_leaderboards(); + } +} + +function setvisiblescoreboardcolumns( col1, col2, col3, col4, col5 ) +{ + if ( !level.rankedMatch ) + { + setscoreboardcolumns( col1, col2, col3, col4, col5, "sbtimeplayed", "shotshit", "shotsmissed", "victory" ); + } + else + { + setscoreboardcolumns( col1, col2, col3, col4, col5 ); + } +} + +function compareTeamByGameStat( gameStat, teamA, teamB, previous_winner_score ) +{ + winner = undefined; + + if ( teamA == "tie" ) + { + winner = "tie"; + + if ( previous_winner_score < game[gameStat][teamB] ) + winner = teamB; + } + else if ( game[gameStat][teamA] == game[gameStat][teamB] ) + winner = "tie"; + else if ( game[gameStat][teamB] > game[gameStat][teamA] ) + winner = teamB; + else + winner = teamA; + + return winner; +} + +function determineTeamWinnerByGameStat( gameStat ) +{ + teamKeys = GetArrayKeys(level.teams); + winner = teamKeys[0]; + previous_winner_score = game[gameStat][winner]; + + for ( teamIndex = 1; teamIndex < teamKeys.size; teamIndex++ ) + { + winner = compareTeamByGameStat( gameStat, winner, teamKeys[teamIndex], previous_winner_score); + + if ( winner != "tie" ) + { + previous_winner_score = game[gameStat][winner]; + } + } + + return winner; +} + +function compareTeamByTeamScore( teamA, teamB, previous_winner_score ) +{ + winner = undefined; + teamBScore = [[level._getTeamScore]]( teamB ); + + if ( teamA == "tie" ) + { + winner = "tie"; + + if ( previous_winner_score < teamBScore ) + winner = teamB; + + return winner; + } + + teamAScore = [[level._getTeamScore]]( teamA ); + + if ( teamBScore == teamAScore ) + winner = "tie"; + else if ( teamBScore > teamAScore ) + winner = teamB; + else + winner = teamA; + + return winner; +} + +function determineTeamWinnerByTeamScore( ) +{ + teamKeys = GetArrayKeys(level.teams); + winner = teamKeys[0]; + previous_winner_score = [[level._getTeamScore]]( winner ); + + for ( teamIndex = 1; teamIndex < teamKeys.size; teamIndex++ ) + { + winner = compareTeamByTeamScore( winner, teamKeys[teamIndex], previous_winner_score); + + if ( winner != "tie" ) + { + previous_winner_score = [[level._getTeamScore]]( winner ); + } + } + + return winner; +} + +function forceEnd(hostsucks) +{ + if (!isdefined(hostsucks)) + hostsucks = false; + + if ( level.hostForcedEnd || level.forcedEnd ) + return; + + winner = undefined; + + if ( level.teamBased ) + { + winner = determineTeamWinnerByGameStat("teamScores"); + globallogic_utils::logTeamWinString( "host ended game", winner ); + } + else + { + winner = globallogic_score::getHighestScoringPlayer(); + /# + if ( isdefined( winner ) ) + print( "host ended game, win: " + winner.name ); + else + print( "host ended game, tie" ); + #/ + } + + level.forcedEnd = true; + level.hostForcedEnd = true; + + if (hostsucks) + { + endString = &"MP_HOST_SUCKS"; + } + else + { + if ( level.splitscreen ) + endString = &"MP_ENDED_GAME"; + else + endString = &"MP_HOST_ENDED_GAME"; + } + + setMatchFlag( "disableIngameMenu", 1 ); + //makeDvarServerInfo( "ui_text_endreason", endString ); + SetDvar( "ui_text_endreason", endString ); + thread endGame( winner, endString ); +} + +function killserverPc() +{ + if ( level.hostForcedEnd || level.forcedEnd ) + return; + + winner = undefined; + + if ( level.teamBased ) + { + winner = determineTeamWinnerByGameStat("teamScores"); + globallogic_utils::logTeamWinString( "host ended game", winner ); + } + else + { + winner = globallogic_score::getHighestScoringPlayer(); + /# + if ( isdefined( winner ) ) + print( "host ended game, win: " + winner.name ); + else + print( "host ended game, tie" ); + #/ + } + + level.forcedEnd = true; + level.hostForcedEnd = true; + + level.killserver = true; + + endString = &"MP_HOST_ENDED_GAME"; + + +/# + PrintLn("kill server; ending game\n"); +#/ + + thread endGame( winner, endString ); +} + +function atLeastTwoTeams() +{ + valid_count = 0; + + foreach ( team in level.teams ) + { + if ( level.playerCount[team] != 0 ) + { + valid_count++; + } + } + + if ( valid_count < 2 ) + { + return false; + } + + return true; +} + +function checkIfTeamForfeits( team ) +{ + if ( !game["everExisted"][team] ) + return false; + + if ( level.playerCount[team] < 1 && util::totalPlayerCount() > 0 ) + { + return true; + } + + return false; +} + +function checkForForfeit() +{ + forfeit_count = 0; + valid_team = undefined; + + foreach( team in level.teams ) + { + if ( checkIfTeamForfeits( team ) ) + { + forfeit_count++; + + if ( !level.multiTeam ) + { + thread [[level.onForfeit]]( team ); + return true; + } + } + else + { + valid_team = team; + } + } + + if ( level.multiTeam && ( forfeit_count == ( level.teams.size - 1 ) ) ) + { + thread [[level.onForfeit]]( valid_team ); + return true; + } + + return false; +} + +function doSpawnQueueUpdates() +{ + foreach( team in level.teams ) + { + if ( level.spawnQueueModified[team] ) + { + [[level.onAliveCountChange]]( team ); + } + } +} + +function isTeamAllDead( team ) +{ + return (level.everExisted[team] && !level.aliveCount[team] && !level.playerLives[team] ); +} + +function areAllTeamsDead( ) +{ + foreach( team in level.teams ) + { + // if team was alive and now they are not + if ( !isTeamAllDead( team ) ) + { + return false; + } + } + + return true; +} + +function getLastTeamAlive() +{ + count = 0; + everExistedCount = 0; + aliveTeam = undefined; + foreach( team in level.teams ) + { + // if team was alive and now they are not + if ( level.everExisted[team] ) + { + if ( !isTeamAllDead( team ) ) + { + aliveTeam = team; + count++; + } + everExistedCount++; + } + } + + if ( ( everExistedCount > 1 ) && ( count == 1 ) ) + { + return aliveTeam; + } + + return undefined; +} + +function doDeadEventUpdates() +{ + if ( level.teamBased ) + { + // if all teams were alive and now they are all dead in the same instance + if ( areAllTeamsDead( ) ) + { + [[level.onDeadEvent]]( "all" ); + return true; + } + + if ( !isdefined( level.onDeadEvent ) ) + { + lastTeamAlive = getLastTeamAlive(); + if ( isdefined( lastTeamAlive ) ) + { + [[level.onLastTeamAliveEvent]]( lastTeamAlive ); + return true; + } + } + else + { + foreach( team in level.teams ) + { + // if team was alive and now they are not + if ( isTeamAllDead( team ) ) + { + [[level.onDeadEvent]]( team ); + return true; + } + } + } + } + else + { + // everyone is dead + if ( (totalAliveCount() == 0) && (totalPlayerLives() == 0) && level.maxPlayerCount > 1 ) + { + [[level.onDeadEvent]]( "all" ); + return true;; + } + } + + return false; +} + +function isOnlyOneLeftAliveOnTeam( team ) +{ + return (level.lastAliveCount[team] > 1 && level.aliveCount[team] == 1 && level.playerLives[team] == 1 ); +} + + +function doOneLeftEventUpdates() +{ + if ( level.teamBased ) + { + foreach( team in level.teams ) + { + // one "team" left + if ( isOnlyOneLeftAliveOnTeam( team ) ) + { + [[level.onOneLeftEvent]]( team ); + return true;; + } + } + } + else + { + // last man standing + if ( (totalAliveCount() == 1) && (totalPlayerLives() == 1) && level.maxPlayerCount > 1 ) + { + [[level.onOneLeftEvent]]( "all" ); + return true;; + } + } + + return false; +} + +function updateGameEvents() +{ +/# + if( GetDvarint( "scr_hostmigrationtest" ) == 1 ) + { + return; + } +#/ + if ( ( level.rankedMatch || level.wagerMatch || level.leagueMatch ) && !level.inGracePeriod ) + { + if ( level.teamBased && !util::isInfectedGametype() ) + { + if (!level.gameForfeited ) + { + if( game["state"] == "playing" && checkForForfeit() ) + { + return; + } + } + else // level.gameForfeited==true + { + if ( atLeastTwoTeams() ) + { + level.gameForfeited = false; + level notify( "abort forfeit" ); + } + } + } + else + { + if (!level.gameForfeited) + { + if ( util::totalPlayerCount() == 1 && level.maxPlayerCount > 1 ) + { + thread [[level.onForfeit]](); + return; + } + } + else // level.gameForfeited==true + { + if ( util::totalPlayerCount() > 1 ) + { + level.gameForfeited = false; + level notify( "abort forfeit" ); + } + } + } + } + + if ( !level.playerQueuedRespawn && !level.numLives && !level.inOverTime ) + return; + + if ( level.inGracePeriod ) + return; + + if ( level.playerQueuedRespawn ) + { + doSpawnQueueUpdates(); + } + + if ( doDeadEventUpdates() ) + return; + + if ( doOneLeftEventUpdates() ) + return; +} + +function mpintro_visionset_ramp_hold_func() +{ + level endon( "mpintro_ramp_out_notify" ); + + while ( true ) + { + for ( player_index = 0; player_index < level.players.size; player_index++ ) + { + self visionset_mgr::set_state_active( level.players[player_index], 1 ); + } + + {wait(.05);}; + } +} + +function mpintro_visionset_activate_func() +{ + visionset_mgr::activate( "visionset", "mpintro", undefined, 0, &mpintro_visionset_ramp_hold_func, 2 ); +} + +function mpintro_visionset_deactivate_func() +{ + level notify( "mpintro_ramp_out_notify" ); +} + +function matchStartTimer() +{ + mpintro_visionset_activate_func(); + level thread sndSetMatchSnapshot( 1 ); + waitForPlayers(); + + countTime = int( level.prematchPeriod ); + + if ( countTime >= 2 ) + { + while ( countTime > 0 && !level.gameEnded ) + { + LUINotifyEvent( &"create_prematch_timer", 1, GetTime() + ( countTime * 1000 ) ); + + if ( countTime == 2 ) + { + mpintro_visionset_deactivate_func(); + } + + if( countTime == 3 ) + { + level thread sndSetMatchSnapshot( 0 ); + foreach ( player in level.players ) + { + if ( player.hasSpawned || player.pers["team"] == "spectator" ) + { + player globallogic_audio::set_music_on_player( "spawnPreRise" ); + } + } + } + + countTime--; + + foreach ( player in level.players ) + { + player PlayLocalSound( "uin_start_count_down" ); + } + + wait ( 1.0 ); + } + + LUINotifyEvent( &"prematch_timer_ended", 0 ); + } + else + { + mpintro_visionset_deactivate_func(); + } +} + +function notifyEndOfGameplay() +{ + level waittill( "game_ended" ); + level clientfield::set("gameplay_started", 0); +} + +function matchStartTimerSkip() +{ + visionSetNaked( GetDvarString( "mapname" ), 0 ); +} + +function sndSetMatchSnapshot( num ) +{ + wait(.05); + level clientfield::set( "sndMatchSnapshot", num ); +} + +function notifyTeamWaveSpawn( team, time ) +{ + if ( time - level.lastWave[team] > (level.waveDelay[team] * 1000) ) + { + level notify ( "wave_respawn_" + team ); + level.lastWave[team] = time; + level.wavePlayerSpawnIndex[team] = 0; + } +} + +function waveSpawnTimer() +{ + level endon( "game_ended" ); + + while ( game["state"] == "playing" ) + { + time = getTime(); + + foreach( team in level.teams ) + { + notifyTeamWaveSpawn( team, time ); + } + {wait(.05);}; + } +} + + +function hostIdledOut() +{ + hostPlayer = util::getHostPlayer(); + +/# + if( GetDvarint( "scr_writeconfigstrings" ) == 1 || GetDvarint( "scr_hostmigrationtest" ) == 1 ) + return false; +#/ + + // host never spawned + if ( isdefined( hostPlayer ) && !hostPlayer.hasSpawned && !isdefined( hostPlayer.selectedClass ) ) + return true; + + return false; +} + +function IncrementMatchCompletionStat( gameMode, playedOrHosted, stat ) +{ + self AddDStat( "gameHistory", gameMode, "modeHistory", playedOrHosted, stat, 1 ); +} + +function SetMatchCompletionStat( gameMode, playedOrHosted, stat ) +{ + self SetDStat( "gameHistory", gameMode, "modeHistory", playedOrHosted, stat, 1 ); +} + +function getTeamScoreRatio() +{ + playerTeam = self.pers["team"]; + + score = getTeamScore( playerTeam ); + + otherTeamScore = 0; + + foreach ( team in level.teams ) + { + if ( team == playerTeam ) + continue; + + otherTeamScore += getTeamScore( team ); + } + + if ( level.teams.size > 1 ) + { + otherTeamScore = otherTeamScore / ( level.teams.size - 1 ); + } + + if ( otherTeamScore != 0 ) + return ( float( score ) / float( otherTeamScore ) ); + + // should we just return the flat score here or some other indication of win? + return score; +} + +function getHighestScore() +{ + highestScore = -999999999; + for ( index = 0; index < level.players.size; index++ ) + { + player = level.players[index]; + + if ( player.score > highestScore ) + highestScore = player.score; + } + + return highestScore; +} + +function getNextHighestScore( score ) +{ + highestScore = -999999999; + for ( index = 0; index < level.players.size; index++ ) + { + player = level.players[index]; + + if ( player.score >= score ) + continue; + + if ( player.score > highestScore ) + highestScore = player.score; + } + + return highestScore; +} + +function RecordPlayStyleInformation() +{ + // Record information for determining play styles + avgKillDistance = 0; + percentTimeMoving = 0; + avgSpeedOfPlayerWhenMoving = 0; + totalKillDistances = float( self.pers["kill_distances"] ); + numKillDistanceEntries = float( self.pers["num_kill_distance_entries"] ); + timePlayedMoving = float( self.pers["time_played_moving"] ); + timePlayedAlive = float( self.pers["time_played_alive"] ); + totalSpeedsWhenMoving = float( self.pers["total_speeds_when_moving"] ); + numSpeedsWhenMovingEntries = float( self.pers["num_speeds_when_moving_entries"] ); + totalDistanceTravelled = float( self.pers["total_distance_travelled"] ); + movementUpdateCount = float(self.pers["movement_Update_Count"]); + if ( numKillDistanceEntries > 0 ) + avgKillDistance = totalKillDistances / numKillDistanceEntries; + + movementUpdateDenom = int(movementUpdateCount/5); + if ( movementUpdateDenom > 0 ) + percentTimeMoving = ( numSpeedsWhenMovingEntries / movementUpdateDenom ) * 100.0; + + if ( numSpeedsWhenMovingEntries > 0 ) + avgSpeedOfPlayerWhenMoving = totalSpeedsWhenMoving / numSpeedsWhenMovingEntries; + + recordPlayerStats( self, "totalKillDistances", totalKillDistances ); + recordPlayerStats( self, "numKillDistanceEntries", numKillDistanceEntries ); + recordPlayerStats( self, "timePlayedMoving", timePlayedMoving ); + recordPlayerStats( self, "timePlayedAlive", timePlayedAlive ); + recordPlayerStats( self, "totalSpeedsWhenMoving", totalSpeedsWhenMoving ); + recordPlayerStats( self, "numSpeedsWhenMovingEntries", numSpeedsWhenMovingEntries ); + recordPlayerStats( self, "averageKillDistance", avgKillDistance ); + recordPlayerStats( self, "percentageOfTimeMoving", percentTimeMoving ); + recordPlayerStats( self, "averageSpeedDuringMatch", avgSpeedOfPlayerWhenMoving ); + recordPlayerStats( self, "totalDistanceTravelled", totalDistanceTravelled ); + + bbPrint( "mpplaystyles", "averageKillDistance %f percentageOfTimeMoving %f averageSpeedDuringMatch %f", avgKillDistance, percentTimeMoving, avgSpeedOfPlayerWhenMoving ); +} + +function getPlayerByName( name ) +{ + for ( index = 0; index < level.players.size; index++ ) + { + player = level.players[index]; + + if ( player util::is_bot() ) + { + continue; + } + + if ( player.name == name ) + { + return player; + } + } +} + +function sendAfterActionReport() +{ +/# + if( GetDvarint( "scr_writeconfigstrings" ) == 1 ) + return; +#/ + + //Send After Action Report information to the client + for ( index = 0; index < level.players.size; index++ ) + { + player = level.players[index]; + + if ( player util::is_bot() ) + { + continue; + } + + //Find the Nemesis for each player + nemesis = player.pers["nemesis_name"]; + + if( !isdefined( player.pers["killed_players"][nemesis] ) ) + player.pers["killed_players"][nemesis] = 0; + if( !isdefined( player.pers["killed_by"][nemesis] ) ) + player.pers["killed_by"][nemesis] = 0; + + //Get the kill to death spread of the player + spread = player.kills - player.deaths; + + if( player.pers["cur_kill_streak"] > player.pers["best_kill_streak"] ) + player.pers["best_kill_streak"] = player.pers["cur_kill_streak"]; + + if ( ( level.rankedMatch || level.leagueMatch ) ) + player persistence::set_after_action_report_stat( "privateMatch", 0 ); + else + player persistence::set_after_action_report_stat( "privateMatch", 1 ); + + player setNemesisXuid( player.pers["nemesis_xuid"] ); + player persistence::set_after_action_report_stat( "nemesisName", nemesis ); + player persistence::set_after_action_report_stat( "nemesisRank", player.pers["nemesis_rank"] ); + player persistence::set_after_action_report_stat( "nemesisRankIcon", player.pers["nemesis_rankIcon"] ); + player persistence::set_after_action_report_stat( "nemesisKills", player.pers["killed_players"][nemesis] ); + player persistence::set_after_action_report_stat( "nemesisKilledBy", player.pers["killed_by"][nemesis] ); + nemesisPlayerEnt = getPlayerByName( nemesis ); + + if ( isDefined( nemesisPlayerEnt ) ) + { + player persistence::set_after_action_report_stat( "nemesisHeroIndex", nemesisPlayerEnt GetCharacterBodyType() ); + } + + player persistence::set_after_action_report_stat( "bestKillstreak", player.pers["best_kill_streak"] ); + player persistence::set_after_action_report_stat( "kills", player.kills ); + player persistence::set_after_action_report_stat( "deaths", player.deaths ); + player persistence::set_after_action_report_stat( "headshots", player.headshots ); + player persistence::set_after_action_report_stat( "score", player.score ); + + player persistence::set_after_action_report_stat( "xpEarned", int( player.pers["summary"]["xp"] ) ); + player persistence::set_after_action_report_stat( "cpEarned", int( player.pers["summary"]["codpoints"] ) ); + player persistence::set_after_action_report_stat( "miscBonus", int( player.pers["summary"]["challenge"] + player.pers["summary"]["misc"] ) ); + player persistence::set_after_action_report_stat( "matchBonus", int( player.pers["summary"]["match"] ) ); + player persistence::set_after_action_report_stat( "demoFileID", getDemoFileID() ); + player persistence::set_after_action_report_stat( "leagueTeamID", player getLeagueTeamID() ); + + player persistence::set_after_action_report_stat( "team", teams::getTeamIndex( player.team ) ); + + alliesScore = globallogic_score::_getteamscore( "allies" ); + if( isdefined( alliesScore ) ) + { + player persistence::set_after_action_report_stat( "alliesScore", alliesScore ); + } + + axisScore = globallogic_score::_getteamscore( "axis" ); + if( isdefined( axisScore ) ) + { + player persistence::set_after_action_report_stat( "axisScore", axisScore ); + } + + player persistence::set_after_action_report_stat( "gameTypeRef", level.gametype ); + player persistence::set_after_action_report_stat( "mapname", GetDvarString( "ui_mapname" ) ); + } +} + +function UpdateAndFinalizeMatchRecord() +{ +/# + if( GetDvarint( "scr_writeconfigstrings" ) == 1 ) + return; +#/ + + for ( index = 0; index < level.players.size; index++ ) + { + player = level.players[index]; + + player globallogic_player::record_special_move_data_for_life( undefined ); + + if ( player util::is_bot() ) + { + continue; + } + + player globallogic_player::record_global_mp_stats_for_player_at_match_end(); + + //Find the Nemesis for each player + nemesis = player.pers["nemesis_name"]; + + if( !isdefined( player.pers["killed_players"][nemesis] ) ) + player.pers["killed_players"][nemesis] = 0; + if( !isdefined( player.pers["killed_by"][nemesis] ) ) + player.pers["killed_by"][nemesis] = 0; + + //Get the kill to death spread of the player + spread = player.kills - player.deaths; + + if( player.pers["cur_kill_streak"] > player.pers["best_kill_streak"] ) + player.pers["best_kill_streak"] = player.pers["cur_kill_streak"]; + + if ( level.onlineGame ) + { + teamScoreRatio = player getTeamScoreRatio(); + scoreboardPosition = getPlacementForPlayer( player ); + if ( scoreboardPosition < 0 ) + scoreboardPosition = level.players.size; + + player GameHistoryFinishMatch( 4, player.kills, player.deaths, player.score, scoreboardPosition, teamScoreRatio ); + + placement = level.placement["all"]; + for ( otherPlayerIndex = 0; otherPlayerIndex < placement.size; otherPlayerIndex++ ) + { + if ( level.placement["all"][otherPlayerIndex] == player ) + { + recordPlayerStats( player, "position", otherPlayerIndex ); + } + } + + if ( isdefined( player.pers["matchesPlayedStatsTracked"] ) ) + { + gameMode = util::GetCurrentGameMode(); + player IncrementMatchCompletionStat( gameMode, "played", "completed" ); + + if ( isdefined( player.pers["matchesHostedStatsTracked"] ) ) + { + player IncrementMatchCompletionStat( gameMode, "hosted", "completed" ); + player.pers["matchesHostedStatsTracked"] = undefined; + } + + player.pers["matchesPlayedStatsTracked"] = undefined; + } + + recordPlayerStats( player, "highestKillStreak", player.pers["best_kill_streak"] ); + recordPlayerStats( player, "numUavCalled", player killstreaks::get_killstreak_usage("uav_used") ); + recordPlayerStats( player, "numDogsCalleD", player killstreaks::get_killstreak_usage("dogs_used") ); + recordPlayerStats( player, "numDogsKills", player.pers["dog_kills"] ); + + player RecordPlayStyleInformation(); + + recordPlayerMatchEnd( player ); + RecordPlayerStats(player, "presentAtEnd", 1 ); + } + } + + finalizeMatchRecord(); +} + +function gameHistoryPlayerKicked() +{ + teamScoreRatio = self getTeamScoreRatio(); + scoreboardPosition = getPlacementForPlayer( self ); + if ( scoreboardPosition < 0 ) + scoreboardPosition = level.players.size; +/# + assert( isdefined( self.kills ) ); + assert( isdefined( self.deaths ) ); + assert( isdefined( self.score ) ); + assert( isdefined( scoreboardPosition ) ); + assert( isdefined( teamScoreRatio ) ); +#/ + self GameHistoryFinishMatch( 2, self.kills, self.deaths, self.score, scoreboardPosition, teamScoreRatio ); + + if ( isdefined( self.pers["matchesPlayedStatsTracked"] ) ) + { + gameMode = util::GetCurrentGameMode(); + self IncrementMatchCompletionStat( gameMode, "played", "kicked" ); + + self.pers["matchesPlayedStatsTracked"] = undefined; + } + + UploadStats( self ); + + // wait until the player recieves the new stats + wait(1); +} + +function gameHistoryPlayerQuit() +{ + teamScoreRatio = self getTeamScoreRatio(); + scoreboardPosition = getPlacementForPlayer( self ); + if ( scoreboardPosition < 0 ) + scoreboardPosition = level.players.size; + + self GameHistoryFinishMatch( 3, self.kills, self.deaths, self.score, scoreboardPosition, teamScoreRatio ); + + if ( isdefined( self.pers["matchesPlayedStatsTracked"] ) ) + { + gameMode = util::GetCurrentGameMode(); + self IncrementMatchCompletionStat( gameMode, "played", "quit" ); + + if ( isdefined( self.pers["matchesHostedStatsTracked"] ) ) + { + self IncrementMatchCompletionStat( gameMode, "hosted", "quit" ); + self.pers["matchesHostedStatsTracked"] = undefined; + } + + self.pers["matchesPlayedStatsTracked"] = undefined; + } + + UploadStats( self ); + + if ( !self IsHost() ) + { + // wait until the player recieves the new stats + wait(1); + } +} + +function displayRoundEnd( winner, endReasonText ) +{ + if ( level.displayRoundEndText ) + { + if ( level.teamBased ) + { + if ( winner == "tie" ) + { + demo::gameResultBookmark( "round_result", level.teamIndex[ "neutral" ], level.teamIndex[ "neutral" ] ); + } + else + { + demo::gameResultBookmark( "round_result", level.teamIndex[ winner ], level.teamIndex[ "neutral" ] ); + } + } + + setmatchflag( "cg_drawSpectatorMessages", 0 ); + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + + if( !util::wasLastRound() ) + { + player notify( "round_ended" ); + } + if ( !isdefined( player.pers["team"] ) ) + { + player [[level.spawnIntermission]]( true ); + continue; + } + + if ( level.teamBased ) + { + player thread [[level.onTeamOutcomeNotify]]( winner, "roundend", endReasonText ); + player globallogic_audio::set_music_on_player( "roundEnd" ); + } + else + { + player thread [[level.onOutcomeNotify]]( winner, true, endReasonText ); + player globallogic_audio::set_music_on_player( "roundEnd" ); + } + + player setClientUIVisibilityFlag( "hud_visible", 0 ); + player setClientUIVisibilityFlag( "g_compassShowEnemies", 0 ); + } + } + + if ( util::wasLastRound() ) + { + roundEndWait( level.roundEndDelay, false ); + } + else + { + thread globallogic_audio::announce_round_winner( winner, level.roundEndDelay / 4 ); + roundEndWait( level.roundEndDelay, true ); + } +} + +function displayRoundSwitch( winner, endReasonText ) +{ + switchType = level.halftimeType; + level thread globallogic_audio::set_music_global( "roundSwitch" ); + if ( switchType == "halftime" ) + { + if ( isdefined( level.nextRoundIsOvertime ) && level.nextRoundIsOvertime ) + { + switchType = "overtime"; + } + else + { + if ( level.roundLimit ) + { + if ( (game["roundsplayed"] * 2) == level.roundLimit ) + switchType = "halftime"; + else + switchType = "intermission"; + } + else if ( level.scoreLimit ) + { + if ( isdefined( level.roundswitch ) && level.roundSwitch == 1 ) + { + switchtype = "intermission"; + } + else if ( game["roundsplayed"] == (level.scoreLimit - 1) ) + { + switchType = "halftime"; + } + else + { + switchType = "intermission"; + } + } + else + { + switchType = "intermission"; + } + } + + } + + leaderdialog = globallogic_audio::get_round_switch_dialog( switchType ); + + SetMatchTalkFlag( "EveryoneHearsEveryone", 1 ); + + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + + if ( !isdefined( player.pers["team"] ) ) + { + player [[level.spawnIntermission]]( true ); + continue; + } + + player globallogic_audio::leader_dialog_on_player( leaderdialog ); + + player thread [[level.onTeamOutcomeNotify]]( winner, switchType, level.halftimeSubCaption ); + player setClientUIVisibilityFlag( "hud_visible", 0 ); + } + + roundEndWait( level.halftimeRoundEndDelay, false ); +} + +function displayGameEnd( winner, endReasonText ) +{ + SetMatchTalkFlag( "EveryoneHearsEveryone", 1 ); + setmatchflag( "cg_drawSpectatorMessages", 0 ); + + level thread sndSetMatchSnapshot( 2 ); + + util::setClientSysState("levelNotify", "streamFKsl" ); + + if ( level.teambased ) + { + if ( winner == "tie" ) + { + demo::gameResultBookmark( "game_result", level.teamIndex[ "neutral" ], level.teamIndex[ "neutral" ] ); + } + else + { + demo::gameResultBookmark( "game_result", level.teamIndex[ winner ], level.teamIndex[ "neutral" ] ); + } + } + // catching gametype, since DM forceEnd sends winner as player entity, instead of string + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + + if ( !isdefined( player.pers["team"] ) ) + { + player [[level.spawnIntermission]]( true ); + continue; + } + + if ( level.teamBased ) + { + player thread [[level.onTeamOutcomeNotify]]( winner, "gameend", endReasonText ); + } + else + { + if ( !( isdefined( level.freerun ) && level.freerun ) ) + { + player thread [[level.onOutcomeNotify]]( winner, false, endReasonText ); + } + + if( ( isdefined( level.freerun ) && level.freerun ) ) + { + player globallogic_audio::set_music_on_player( "mp_freerun_gameover" ); + } + else if ( isdefined( winner ) && player == winner ) + { + player globallogic_audio::set_music_on_player( "matchWin" ); + } + else if ( !level.splitScreen ) + { + player globallogic_audio::set_music_on_player( "matchLose" ); + } + } + + player setClientUIVisibilityFlag( "hud_visible", 0 ); + player setClientUIVisibilityFlag( "g_compassShowEnemies", 0 ); + } + + thread globallogic_audio::announce_game_winner( winner ); + + if ( level.teamBased ) + { + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + team = player.pers["team"]; + + if ( level.splitscreen ) + { + if( ( isdefined( level.freerun ) && level.freerun ) ) + { + player globallogic_audio::set_music_on_player( "mp_freerun_gameover" ); + } + else if ( winner == "tie" ) + { + player globallogic_audio::set_music_on_player( "matchDraw" ); + } + else if ( winner == team ) + { + player globallogic_audio::set_music_on_player( "matchWin" ); + } + else + { + player globallogic_audio::set_music_on_player( "matchLose" ); + } + } + else + { + if( ( isdefined( level.freerun ) && level.freerun ) ) + { + player globallogic_audio::set_music_on_player( "mp_freerun_gameover" ); + } + else if ( winner == "tie" ) + { + player globallogic_audio::set_music_on_player( "matchDraw" ); + } + else if ( winner == team ) + { + player globallogic_audio::set_music_on_player( "matchWin" ); + } + else + { + player globallogic_audio::set_music_on_player( "matchLose" ); + } + } + } + } + + bbPrint( "global_session_epilogs", "reason %s", endReasonText ); + + // tagTMR: all round data aggregates that cannot be summed from other tables post-runtime + bbPrint( "mpmatchfacts", "gametime %d winner %s killstreakcount %d", gettime(), winner, level.globalKillstreaksCalled ); + + roundEndWait( level.postRoundTime, true ); +} + +function recordEndGameComScoreEvent( result ) +{ + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + globallogic_player::recordEndGameComScoreEventForPlayer( players[index], result ); + } +} + +function getEndReasonText() +{ + if ( isdefined( level.endReasonText ) ) + { + return level.endReasonText; + } + + if ( util::hitRoundLimit() || util::hitRoundWinLimit() ) + return game["strings"]["round_limit_reached"]; + else if ( util::hitScoreLimit() ) + return game["strings"]["score_limit_reached"]; + else if ( util::hitRoundScoreLimit() ) + return game["strings"]["round_score_limit_reached"]; + + if ( level.forcedEnd ) + { + if ( level.hostForcedEnd ) + return &"MP_HOST_ENDED_GAME"; + else + return &"MP_ENDED_GAME"; + } + return game["strings"]["time_limit_reached"]; +} + +function resetOutcomeForAllPlayers() +{ + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + player notify ( "reset_outcome" ); + } +} + +function hideOutcomeUIForAllPlayers() +{ + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + player clientfield::set_player_uimodel( "hudItems.hideOutcomeUI", 1 ); + } +} + +function startNextRound( winner, endReasonText ) +{ + if ( !util::isOneRound() ) + { + displayRoundEnd( winner, endReasonText ); + + globallogic_utils::executePostRoundEvents(); + + if ( !util::wasLastRound() ) + { + if ( checkRoundSwitch() ) + { + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, game["roundswon"][team] ); + } + } + + displayRoundSwitch( winner, endReasonText ); + } + + if ( isdefined( level.nextRoundIsOvertime ) && level.nextRoundIsOvertime ) + { + if ( !isdefined( game["overtime_round"] ) ) + { + game["overtime_round"] = 1; + } + else + { + game["overtime_round"]++; + } + } + + SetMatchTalkFlag( "DeadChatWithDead", level.voip.deadChatWithDead ); + SetMatchTalkFlag( "DeadChatWithTeam", level.voip.deadChatWithTeam ); + SetMatchTalkFlag( "DeadHearTeamLiving", level.voip.deadHearTeamLiving ); + SetMatchTalkFlag( "DeadHearAllLiving", level.voip.deadHearAllLiving ); + SetMatchTalkFlag( "EveryoneHearsEveryone", level.voip.everyoneHearsEveryone ); + SetMatchTalkFlag( "DeadHearKiller", level.voip.deadHearKiller ); + SetMatchTalkFlag( "KillersHearVictim", level.voip.killersHearVictim ); + + game["state"] = "playing"; + map_restart( true ); + hideOutcomeUIForAllPlayers(); + return true; + } + } + return false; +} + + +function setTopPlayerStats( ) +{ + if( level.rankedMatch ) + { + placement = level.placement["all"]; + topThreePlayers = min( 3, placement.size ); + + for ( index = 0; index < topThreePlayers; index++ ) + { + if ( level.placement["all"][index].score ) + { + if ( !index ) + { + level.placement["all"][index] AddPlayerStatWithGameType( "TOPPLAYER", 1 ); + level.placement["all"][index] notify( "topplayer" ); + } + else + level.placement["all"][index] notify( "nottopplayer" ); + + level.placement["all"][index] AddPlayerStatWithGameType( "TOP3", 1 ); + level.placement["all"][index] AddPlayerStat( "TOP3ANY", 1 ); + if ( level.hardcoreMode ) + { + level.placement["all"][index] AddPlayerStat( "TOP3ANY_HC", 1 ); + } + if ( level.multiTeam ) + { + level.placement["all"][index] AddPlayerStat( "TOP3ANY_MULTITEAM", 1 ); + } + level.placement["all"][index] notify( "top3" ); + } + } + + for ( index = 3 ; index < placement.size ; index++ ) + { + level.placement["all"][index] notify( "nottop3" ); + level.placement["all"][index] notify( "nottopplayer" ); + } + + if ( level.teambased ) + { + foreach ( team in level.teams ) + { + setTopTeamStats(team); + } + } + } +} + +function setTopTeamStats(team) +{ + placementTeam = level.placement[team]; + topThreeTeamPlayers = min( 3, placementTeam.size ); + // should have at least 5 players on the team + if ( placementTeam.size < 5 ) + return; + + for ( index = 0; index < topThreeTeamPlayers; index++ ) + { + if ( placementTeam[index].score ) + { + placementTeam[index] AddPlayerStat( "TOP3TEAM", 1 ); + placementTeam[index] AddPlayerStat( "TOP3ANY", 1 ); + if ( level.hardcoreMode ) + { + placementTeam[index] AddPlayerStat( "TOP3ANY_HC", 1 ); + } + if ( level.multiTeam ) + { + placementTeam[index] AddPlayerStat( "TOP3ANY_MULTITEAM", 1 ); + } + placementTeam[index] AddPlayerStatWithGameType( "TOP3TEAM", 1 ); + } + } +} + +function figureOutWinningTeam( winner ) +{ + if ( !isdefined( winner ) ) + return "tie"; + + if ( IsEntity( winner ) ) + return ( isdefined( winner.team ) ? winner.team : "none" ); + + return winner; +} + +function getRoundLength() +{ + if ( !level.timeLimit || level.forcedEnd ) + { + gameLength = globallogic_utils::getTimePassed() / 1000; + // cap it at 20 minutes to avoid exploiting + gameLength = min( gameLength, 1200 ); + } + else + { + gameLength = level.timeLimit * 60; + } + + return gameLength; +} + +function awardLootXP( ) // self == player +{ + if( !GetDvarInt( "loot_enabled" ) ) + { + return; + } + + maxPlayableGameTime = game["playabletimepassed"] / 1000; + timePlayed = self getTotalTimePlayed( maxPlayableGameTime ); + lootXPAwarded = 0; + + if ( self.pers["participation"] >= 1 ) + { + lootXPPerKey = int( GetDvarInt( "loot_cryptokeyCost", 100 ) ); + avgGameLen = int( GetDvarInt( "loot_earnTime", 267 ) ); + minPlayTime = int( GetDvarInt( "loot_earnPlayThreshold", 10 ) ); + maxLootXp = int( GetDvarInt( "loot_earnMax", 10000 ) ); + minLootXp = int( GetDvarInt( "loot_earnMin", 0 ) ); + winMultiplier = int( GetDvarInt( "loot_winBonusPercent", 30 ) ); + expGroup = -1; + + // experiment groups 1 and 2 get bonus earn rate. + if( int( GetDvarString( "bonus_crypto_earn_rate_enabled", "0" ) ) == 1 ) + { + expGroup = self experimentsgetvariant( "jul_2017_small" ); + if( expGroup == 1 || expGroup == 2 ) + { + avgGameLen = int( GetDvarString( "bonus_crypto_earn_rate", "184" ) ); + } + } + + if( maxPlayableGameTime > 0 && timePlayed > minPlayTime ) + { + // account for time spent in arena ban/protect + if( level.arenaMatch ) + { + draftEnabled = ( GetGametypeSetting( "pregameDraftEnabled" ) == 1 ); + voteEnabled = ( GetGametypeSetting( "pregameItemVoteEnabled" ) == 1 ); + + if( draftEnabled && voteEnabled ) + { + arenaMin = GetDvarInt( "arena_minPregameCryptoSeconds", 0 ); + arenaMax = GetDvarInt( "arena_maxPregameCryptoSeconds", 0 ); + + if( arenaMax > 0 && arenaMin >= 0 && arenaMin <= arenaMax ) + { + bonusTime = RandomIntRange( arenaMin, arenaMax ); + timePlayed += bonusTime; + } + } + } + + // award enough lootxp for 1 cryptokey for each 'avgGameLen' seconds of time played + lootXpScale = timePlayed / avgGameLen; + lootXPAwarded = lootXPPerKey * lootXpScale; + rawLootXP = lootXPAwarded; + + // bonus for winner + if( isDefined( self.lootXpMultiplier ) && self.lootXpMultiplier == true ) + { + lootXPAwarded = lootXPAwarded + ( lootXPAwarded * ( winMultiplier / 100 ) ); + } + + // scale for double lootxp + lootXPAwarded *= math::clamp( self getcryptoscale(), 0.0, 4.0 ); + lootXPAwarded = int( lootXPAwarded ); + + // sanity + if( lootXPAwarded > maxLootXp ) + { + lootXPAwarded = maxLootXp; + } + else if( lootXPAwarded < minLootXp ) + { + lootXPAwarded = minLootXp; + } + + otherLootXPAwarded = self awardOtherLootXP(); + lootXPAwarded += otherLootXPAwarded; + + if( GetDvarInt( "enable_bonus_earn_rate_tracking", 1 ) ) + { + xuid = self GetXUID(true); + RecordComScoreEvent( "bonus_crypto_earn_rate", "xuid", xuid, "game_len", avgGameLen, "exp_group", expGroup, "raw_loot_xp", rawLootXP, "final_loot_xp", lootXPAwarded ); + } + + if( lootXPAwarded > 0 ) + { + self ReportLootReward( 1, lootXPAwarded ); + } + + // if other loot xp was awarded, let client receive stats before requesting an upload + if ( otherLootXPAwarded > 0 ) + { + level thread WaitAndUploadStats( self, GetDvarFloat( "src_other_lootxp_uploadstat_waittime", 1.0 ) ); + } + } + } + + self setAARStat( "lootXpEarned", lootXPAwarded ); + recordPlayerStats( self, "lootXpEarned", lootXPAwarded ); + recordPlayerStats( self, "lootTimePlayed", timePlayed ); +} + +function WaitAndUploadStats( player, waitTime ) +{ + wait waitTime; + + if ( isPlayer( player ) ) + { + UploadStats( player ); + } +} + +function registerOtherLootXPAwards( func ) +{ + if ( !isdefined( level.awardOtherLootXPfunctions ) ) + level.awardOtherLootXPfunctions = []; + + if ( !isdefined( level.awardOtherLootXPfunctions ) ) level.awardOtherLootXPfunctions = []; else if ( !IsArray( level.awardOtherLootXPfunctions ) ) level.awardOtherLootXPfunctions = array( level.awardOtherLootXPfunctions ); level.awardOtherLootXPfunctions[level.awardOtherLootXPfunctions.size]=func;; +} + +function awardOtherLootXP() +{ + player = self; + + if ( !isdefined( level.awardOtherLootXPfunctions ) ) + return 0; + + if ( !isPlayer( player ) ) + return 0; + + lootXP = 0; + + foreach( func in level.awardOtherLootXPfunctions ) + { + if ( !isdefined( func ) ) + continue; + + lootXP += player [[ func ]](); + } + + return lootXP; +} + +function endGame( winner, endReasonText ) +{ + // return if already ending via host quit or victory + if ( game["state"] == "postgame" || level.gameEnded ) + return; + + if ( isdefined( level.onEndGame ) ) + [[level.onEndGame]]( winner ); + + if ( !isdefined( level.disableOutroVisionSet ) || level.disableOutroVisionSet == false ) + { + visionSetNaked( "mpOutro", 2.0 ); + } + + setmatchflag( "cg_drawSpectatorMessages", 0 ); + setmatchflag( "game_ended", 1 ); + + game["state"] = "postgame"; + level.gameEndTime = getTime(); + level.gameEnded = true; + SetDvar( "g_gameEnded", 1 ); + level.inGracePeriod = false; + level notify ( "game_ended" ); + level clientfield::set( "game_ended", 1 ); + globallogic_audio::flush_dialog(); + + foreach( team in level.teams ) + { + game["lastroundscore"][team] = getTeamScore( team ); + } + + if( util::isRoundBased() ) + { + matchRecordRoundEnd(); + } + + winning_team = figureOutWinningTeam( winner ); + + if ( isdefined( game["overtime_round"] ) && isdefined( game["overtimeroundswon"][winning_team] ) ) + { + game["overtimeroundswon"][winning_team]++; + } + + if ( !isdefined( game["overtime_round"] ) || util::wasLastRound() ) // Want to treat all overtime rounds as a single round + { + game["roundsplayed"]++; + game["roundwinner"][game["roundsplayed"]] = winner; + + if ( isdefined( game["roundswon"][winning_team] ) ) + game["roundswon"][winning_team]++; + } + + if ( isdefined( winner ) && isdefined( level.teams[ winning_team ] ) ) + { + level.finalKillCam_winner = winner; + } + else + { + level.finalKillCam_winner = "none"; + } + + level.finalKillCam_winnerPicked = true; // consider "none" as picked too + + setGameEndTime( 0 ); // stop/hide the timers + + updatePlacement(); + + updateRankedMatch( winner ); + + // freeze players + players = level.players; + + newTime = getTime(); + roundLength = getRoundLength(); + + SetMatchTalkFlag( "EveryoneHearsEveryone", 1 ); + + bbGameOver = 0; + if ( util::isOneRound() || util::wasLastRound() ) + { + bbGameOver = 1; + } + + surveyId = 0; + if( RandomFloat(1) <= GetDvarFloat("survey_chance") ) + surveyId = RandomIntRange(1, GetDvarInt( "survey_count" ) + 1 ); + + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + player globallogic_player::freezePlayerForRoundEnd(); + player thread roundEndDoF( 4.0 ); + + player globallogic_ui::freeGameplayHudElems(); + + player.pers["lastroundscore"] = player.pointstowin; + + // Update weapon usage stats + player weapons::update_timings( newTime ); + + if ( bbGameOver ) + player behaviorTracker::Finalize(); + + player bbPlayerMatchEnd( roundLength, endReasonText, bbGameOver ); + + player.pers["totalTimePlayed"] += player.timePlayed["total"]; + + if ( bbGameOver ) + { + if ( level.leagueMatch ) + { + player setAARStat( "lobbyPopup", "leaguesummary" ); + } + else + { + player setAARStat( "lobbyPopup", "summary" ); + } + } + + player setAARStat( "surveyId", surveyId ); + player setAARStat( "hardcore", level.hardcoreMode ); + } + +// music::setmusicstate( "silent" ); + +// temporarily disabling round end sound call to prevent the final killcam from not having sound + if ( !level.inFinalKillcam ) + { +// util::clientNotify( "snd_end_rnd" ); + } + + gamerep::gameRepUpdateInformationForRound(); + thread challenges::roundEnd( winner ); + + // update now + game_winner = winner; + if ( !util::isOneRound() ) + { + game_winner = [[level.determineWinner]]( winner ); + } + update_top_scorers(game_winner); + + if ( startNextRound( winner, endReasonText ) ) + { + return; + } + + /////////////////////////////////////////// + // After this the match is really ending // + /////////////////////////////////////////// + + if ( !util::isOneRound() ) + { + if ( isdefined( level.onRoundEndGame ) ) + winner = [[level.onRoundEndGame]]( winner ); + + endReasonText = getEndReasonText(); + } + + globallogic_score::updateWinLossStats( winner ); + + // this needs to happen after updateWinLossStats to award winner multipliers correctly + if( level.rankedmatch || level.arenaMatch ) + { + thread awardLootXPToPlayers( 3.0, players ); + } + + if( level.arenaMatch ) + { + arena::match_end( winner ); + } + + result = ""; + + if ( level.teambased ) + { + if ( winner == "tie" ) + { + result = "draw"; + } + else + { + result = winner; + } + } + else + { + if ( !isDefined( winner ) ) + { + result = "draw"; + } + else + { + result = winner.team; + } + } + + recordGameResult( result ); + + // we want the end time for each player the moment the match is done, not post-final-killcam-presentation + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + player globallogic_player::record_misc_player_stats(); + } + + skillUpdate( winner, level.teamBased ); + recordLeagueWinner( winner ); + + setTopPlayerStats(); + thread challenges::gameEnd( winner ); + + UpdateAndFinalizeMatchRecord(); + + recordEndGameComScoreEvent( result ); + + if ( GetDvarInt( "ui_enablePromoTracking", 0 ) && ( level.rankedmatch || level.arenaMatch ) ) + { + util::force_upload_live_counters(); + } + + if ( GetDvarInt( "ui_enable_community_challenge", 0 ) && ( level.rankedmatch || level.arenaMatch ) ) + { + util::increment_live_counter( GetDvarString( "community_challenge_counter_name" ), 1 ); + util::force_upload_live_counters(); + } + + level.finalGameEnd = true; + + if ( !isdefined( level.skipGameEnd ) || !level.skipGameEnd ) + displayGameEnd( winner, endReasonText ); + + level.finalGameEnd = undefined; + + if ( util::isOneRound() ) + { + globallogic_utils::executePostRoundEvents(); + } + + level.intermission = true; + + gamerep::gameRepAnalyzeAndReport(); + + util::setClientSysState("levelNotify", "fkcs" ); + + // update again +// update_top_scorers(winner); + + if( persistence::can_set_aar_stat() ) + { + thread sendAfterActionReport(); + } + + stopdemorecording(); + + SetMatchTalkFlag( "EveryoneHearsEveryone", 1 ); + + //regain players array since some might've disconnected during the wait above + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + + player notify ( "reset_outcome" ); + player thread [[level.spawnIntermission]]( false, level.useXCamsForEndGame ); + player setClientUIVisibilityFlag( "hud_visible", 1 ); + } + + level clientfield::set( "post_game", 1 ); + + doEndGameSequence(); + + if ( isdefined ( level.endGameFunction ) ) + { + level thread [[level.endGameFunction]](); + } + //Eckert - Fading out sound + level notify ( "sfade"); + /#print( "game ended" );#/ + + if ( !isdefined( level.skipGameEnd ) || !level.skipGameEnd ) + wait 5.0; + + if( isDefined( level.end_game_video ) ) + { + level thread lui::play_movie( level.end_game_video.name, "fullscreen", true ); + wait( level.end_game_video.duration + 4.5 ); + } + + exit_level(); +} + +function awardLootXPToPlayers( delay, players ) +{ + wait delay; + + foreach( player in players ) + { + if ( !isdefined( player ) ) + continue; + + player awardLootXP(); + } +} + +function exit_level() +{ + if ( level.exitLevel ) + return; + + level.exitLevel = true; + exitLevel( false ); +} + +function update_top_scorers(winner) +{ + topscorers = []; + + winning_team = figureOutWinningTeam( winner ); + + if ( level.teambased && isdefined(winner) && isdefined( level.placement[winning_team] ) ) + { + topscorers = level.placement[winning_team]; + } + else + { + topscorers = level.placement["all"]; + } + + if ( topscorers.size ) + { + level.doTopScorers = true; + } + else + { + level.doTopScorers = false; + } + + ClearTopScorers(); + + for ( i = 0; i < 3 && i < topscorers.size; i++ ) + { + player = topscorers[i]; + + if ( !isdefined( player ) ) + continue; + + player thread checkForGestures( i ); + + showcase_weapon = player weapons::showcaseweapon_get(); + tauntIndex = player GetPlayerSelectedTaunt( 0 ); + gesture0Index = player GetPlayerSelectedGesture( 0 ); + gesture1Index = player GetPlayerSelectedGesture( 1 ); + gesture2Index = player GetPlayerSelectedGesture( 2 ); + // if they dont have a weapon then they never spawned in + if ( !isDefined( showcase_weapon ) ) + { + SetTopScorer( i, player, tauntIndex, gesture0Index, gesture1Index, gesture2Index, GetWeapon("ar_standard") ); + continue; + } + + SetTopScorer( i, player, tauntIndex, gesture0Index, gesture1Index, gesture2Index, showcase_weapon["weapon"], showcase_weapon["options"], showcase_weapon["acvi"] ); + } +} + +// self = player +function checkForGestures( topPlayerIndex ) +{ + self endon( "disconnect" ); + + fieldName = "playTop" + topPlayerIndex + "Gesture"; + + // Initialize the field + level clientfield::set( fieldName, 7 ); + {wait(.05);}; + + while(true) + { + if ( isdefined( self ) ) + { + if ( self actionSlotOneButtonPressed() ) + { + // Up - Good Game + self setGestureClientField( fieldName, 0 ); + } + else if ( self actionSlotThreeButtonPressed() ) + { + // Left - Threaten + self setGestureClientField( fieldName, 1 ); + } + else if ( self actionSlotFourButtonPressed() ) + { + // Right - Boast + self setGestureClientField( fieldName, 2 ); + } + /* + else if ( self actionSlotTwoButtonPressed() ) + { + Down - Unused + self setGestureClientField( topPlayerIndex, 3 ); + } + */ + } + + {wait(.05);}; + } +} + +function setGestureClientField( fieldName, gestureType ) +{ + self notify ( "setGestureClientField" ); + self endon ( "setGestureClientField" ); + + level clientfield::set( fieldName, gestureType ); + + {wait(.05);}; + + // Reset the field + level clientfield::set( fieldName, 7 ); +} + +function doEndGameSequence() +{ + // This should be done only if the end game flow prefab is in the map. + level notify ( "endgame_sequence" ); + + preloadingEnabled = GetDvarInt( "sv_mapSwitchPreloadFrontend", 0 ); + if ( level.doTopScorers && isDefined( struct::get( "endgame_top_players_struct", "targetname" ) ) ) + { + //setMatchFlag( "full_screen", 1 ); + setMatchFlag( "enable_popups", 1 ); // replace full_screen when new exes come in + + // get rid of the corpses so they dont show up in the top scorers screen + ClearPlayerCorpses(); + + level thread sndSetMatchSnapshot( 3 ); + level thread globallogic_audio::set_music_global("endmatch"); + level clientfield::set( "displayTop3Players", 1 ); + + if( preloadingEnabled ) + SwitchMap_Preload( "core_frontend" ); + +/# + while ( GetDvarInt( "pauseWinnersCircle", 0 ) ) + { + {wait(.05);}; + } +#/ + wait 15.0; + level clientfield::set( "triggerScoreboardCamera", 1 ); + wait 5.0; + //setMatchFlag( "full_screen", 0 ); + setMatchFlag( "enable_popups", 0 ); // replace full_screen when new exes come in + } + else if ( level.doEndgameScoreboard ) + { + if( preloadingEnabled ) + SwitchMap_Preload( "core_frontend" ); + LUINotifyEvent( &"force_scoreboard", 0 ); + } +} + +function getTotalTimePlayed( maxLength ) // self == player +{ + totalTimePlayed = 0; + if ( isdefined( self.pers["totalTimePlayed"] ) ) + { + totalTimePlayed = self.pers["totalTimePlayed"]; + if ( totalTimePlayed > maxLength ) + { + totalTimePlayed = maxLength; + } + } + return totalTimePlayed; +} + +function getRoundTimePlayed( roundLength ) // self == player +{ + totalTimePlayed = 0; + if ( isdefined( self.timePlayed ) && isdefined( self.timePlayed["total"] ) ) + { + totalTimePlayed = self.timePlayed["total"]; + if ( totalTimePlayed > roundLength ) + { + totalTimePlayed = roundLength; + } + } + return totalTimePlayed; +} + +function bbPlayerMatchEnd( gameLength, endReasonString, gameOver ) // self == player +{ + playerRank = getPlacementForPlayer( self ); + + totalTimePlayed = self getRoundTimePlayed( gameLength ); + xuid = self GetXUID(); + + bbPrint( "mpplayermatchfacts", "score %d momentum %d endreason %s sessionrank %d playtime %d xuid %s gameover %d team %s", + self.pers["score"], + self.pers["momentum"], + endReasonString, + playerRank, + totalTimePlayed, + xuid, + gameOver, + self.pers["team"] ); +} + +function roundEndWait( defaultDelay, matchBonus ) +{ + notifiesDone = false; + while ( !notifiesDone ) + { + players = level.players; + notifiesDone = true; + for ( index = 0; index < players.size; index++ ) + { + if ( !isdefined( players[index].doingNotify ) || !players[index].doingNotify ) + continue; + + notifiesDone = false; + } + wait ( 0.5 ); + } + + if ( !matchBonus ) + { + wait ( defaultDelay ); + level notify ( "round_end_done" ); + return; + } + + wait ( defaultDelay / 2 ); + level notify ( "give_match_bonus" ); + wait ( defaultDelay / 2 ); + + notifiesDone = false; + while ( !notifiesDone ) + { + players = level.players; + notifiesDone = true; + for ( index = 0; index < players.size; index++ ) + { + if ( !isdefined( players[index].doingNotify ) || !players[index].doingNotify ) + continue; + + notifiesDone = false; + } + wait ( 0.5 ); + } + + level notify ( "round_end_done" ); +} + + +function roundEndDOF( time ) +{ + self setDepthOfField( 0, 128, 512, 4000, 6, 1.8 ); +} + + +function checkTimeLimit() +{ + if ( isdefined( level.timeLimitOverride ) && level.timeLimitOverride ) + return; + + if ( game["state"] != "playing" ) + { + setGameEndTime( 0 ); + return; + } + + if ( level.timeLimit <= 0 ) + { + setGameEndTime( 0 ); + return; + } + + if ( level.inPrematchPeriod ) + { + setGameEndTime( 0 ); + return; + } + + if( ( isdefined( level.timerPaused ) && level.timerPaused ) ) + { + timeRemaining = globallogic_utils::getTimeRemaining(); + if ( timeRemaining > 30000 ) + { + // round down to the nearest number so it doesn't jump back and forth + setGameEndTime( -int(timeRemaining - 999) ); + } + else + { + // we show tenths of a second under 30 + setGameEndTime( -int(timeRemaining - 99) ); + } + return; + } + + if ( level.timerStopped ) + { + setGameEndTime( 0 ); + return; + } + + if ( !isdefined( level.startTime ) ) + return; + + timeLeft = globallogic_utils::getTimeRemaining(); + + // want this accurate to the millisecond + setGameEndTime( getTime() + int(timeLeft) ); + + if ( timeLeft > 0 ) + return; + + [[level.onTimeLimit]](); +} + +function checkScoreLimit() +{ + if ( game["state"] != "playing" ) + return false; + + if ( level.scoreLimit <= 0 ) + return false; + + if ( level.teamBased ) + { + if( !util::any_team_hit_score_limit() ) + return false; + } + else + { + if ( !isPlayer( self ) ) + return false; + + if ( self.pointstowin < level.scoreLimit ) + return false; + } + + [[level.onScoreLimit]](); +} + +function checkSuddenDeathScoreLimit( team ) +{ + if ( game["state"] != "playing" ) + return false; + + if ( level.roundScoreLimit <= 0 ) + return false; + + if ( level.teamBased ) + { + if( !game["teamSuddenDeath"][team] ) + return false; + } + else + { + return false; + } + + [[level.onScoreLimit]](); +} + +function checkRoundScoreLimit() +{ + if ( game["state"] != "playing" ) + return false; + + if ( level.roundScoreLimit <= 0 ) + return false; + + if ( level.teamBased ) + { + if( !util::any_team_hit_round_score_limit() ) + return false; + } + else + { + if ( !isPlayer( self ) ) + return false; + + roundScoreLimit = util::get_current_round_score_limit(); + + if ( self.pointstowin < roundScoreLimit ) + return false; + } + + [[level.onRoundScoreLimit]](); +} + +function updateGameTypeDvars() +{ + level endon ( "game_ended" ); + + while ( game["state"] == "playing" ) + { + roundlimit = math::clamp( GetGametypeSetting( "roundLimit" ), level.roundLimitMin, level.roundLimitMax ); + if ( roundlimit != level.roundlimit ) + { + level.roundlimit = roundlimit; + level notify ( "update_roundlimit" ); + } + + timeLimit = [[level.getTimeLimit]](); + if ( timeLimit != level.timeLimit ) + { + level.timeLimit = timeLimit; + SetDvar( "ui_timelimit", level.timeLimit ); + level notify ( "update_timelimit" ); + } + thread checkTimeLimit(); + + scoreLimit = math::clamp( GetGametypeSetting( "scoreLimit" ), level.scoreLimitMin, level.scoreLimitMax ); + if ( scoreLimit != level.scoreLimit ) + { + level.scoreLimit = scoreLimit; + SetDvar( "ui_scorelimit", level.scoreLimit ); + level notify ( "update_scorelimit" ); + } + thread checkScoreLimit(); + + roundScoreLimit = math::clamp( GetGametypeSetting( "roundScoreLimit" ), level.roundScoreLimitMin, level.roundScoreLimitMax ); + if ( roundScoreLimit != level.roundScoreLimit ) + { + level.roundScoreLimit = roundScoreLimit; + level notify ( "update_roundScoreLimit" ); + } + thread checkRoundScoreLimit(); + + // make sure we check time limit right when game ends + if ( isdefined( level.startTime ) ) + { + if ( globallogic_utils::getTimeRemaining() < 3000 ) + { + wait .1; + continue; + } + } + wait 1; + } +} + + +function removeDisconnectedPlayerFromPlacement() +{ + offset = 0; + numPlayers = level.placement["all"].size; + found = false; + for ( i = 0; i < numPlayers; i++ ) + { + if ( level.placement["all"][i] == self ) + found = true; + + if ( found ) + level.placement["all"][i] = level.placement["all"][ i + 1 ]; + } + if ( !found ) + return; + + level.placement["all"][ numPlayers - 1 ] = undefined; + assert( level.placement["all"].size == numPlayers - 1 ); + + /# + globallogic_utils::assertProperPlacement(); + #/ + + updateTeamPlacement(); + + if ( level.teamBased ) + return; + + numPlayers = level.placement["all"].size; + for ( i = 0; i < numPlayers; i++ ) + { + player = level.placement["all"][i]; + player notify( "update_outcome" ); + } + +} + +function updatePlacement() +{ + + if ( !level.players.size ) + return; + + level.placement["all"] = []; + foreach ( player in level.players ) + { + if ( !level.teambased || isdefined( level.teams[ player.team ] ) ) + level.placement["all"][level.placement["all"].size] = player; + } + + placementAll = level.placement["all"]; + + if ( level.teamBased ) + { + for ( i = 1; i < placementAll.size; i++ ) + { + player = placementAll[i]; + playerScore = player.score; + for ( j = i - 1; j >= 0 && (playerScore > placementAll[j].score || (playerScore == placementAll[j].score && player.deaths < placementAll[j].deaths)); j-- ) + placementAll[j + 1] = placementAll[j]; + placementAll[j + 1] = player; + } + } + else + { + for ( i = 1; i < placementAll.size; i++ ) + { + player = placementAll[i]; + playerScore = player.pointstowin; + for ( j = i - 1; j >= 0 && (playerScore > placementAll[j].pointstowin || (playerScore == placementAll[j].pointstowin && player.deaths < placementAll[j].deaths) || (playerScore == placementAll[j].pointstowin && player.deaths == placementAll[j].deaths && player.lastKillTime > placementAll[j].lastKillTime)); j-- ) + placementAll[j + 1] = placementAll[j]; + placementAll[j + 1] = player; + } + } + + level.placement["all"] = placementAll; + + /# + globallogic_utils::assertProperPlacement(); + #/ + + updateTeamPlacement(); + +} + + +function updateTeamPlacement() +{ + foreach( team in level.teams ) + { + placement[team] = []; + } + placement["spectator"] = []; + + if ( !level.teamBased ) + return; + + placementAll = level.placement["all"]; + placementAllSize = placementAll.size; + + for ( i = 0; i < placementAllSize; i++ ) + { + player = placementAll[i]; + team = player.pers["team"]; + + placement[team][ placement[team].size ] = player; + } + + foreach( team in level.teams ) + { + level.placement[team] = placement[team]; + } +} + +function getPlacementForPlayer( player ) +{ + updatePlacement(); + + playerRank = -1; + placement = level.placement["all"]; + for ( placementIndex = 0; placementIndex < placement.size; placementIndex++ ) + { + if ( level.placement["all"][placementIndex] == player ) + { + playerRank = (placementIndex + 1); + break; + } + } + + return playerRank; +} + +function isTopScoringPlayer( player ) +{ + topScoringPlayer = false; + updatePlacement(); + + assert( level.placement["all"].size > 0 ); + if ( level.placement["all"].size == 0 ) + { + return false; + } + + if ( level.teambased ) + { + topScore = level.placement["all"][0].score; + for ( index = 0; index < level.placement["all"].size; index++ ) + { + if ( level.placement["all"][index].score == 0 ) + { + break; + } + if ( topScore > level.placement["all"][index].score ) + { + break; + } + if ( player == level.placement["all"][index] ) + { + topScoringPlayer = true; + break; + } + } + } + else + { + topScore = level.placement["all"][0].pointsToWin; + for ( index = 0; index < level.placement["all"].size; index++ ) + { + if ( level.placement["all"][index].pointsToWin == 0 ) + { + break; + } + if ( topScore > level.placement["all"][index].pointsToWin ) + { + break; + } + if ( player == level.placement["all"][index] ) + { + topScoringPlayer = true; + break; + } + } + } + return topScoringPlayer; +} + +function sortDeadPlayers( team ) +{ + // only need to sort if we are running queued respawn + if ( !level.playerQueuedRespawn ) + return; + + // sort by death time + for ( i = 1; i < level.deadPlayers[team].size; i++ ) + { + player = level.deadPlayers[team][i]; + for ( j = i - 1; j >= 0 && player.deathTime < level.deadPlayers[team][j].deathTime; j-- ) + level.deadPlayers[team][j + 1] = level.deadPlayers[team][j]; + level.deadPlayers[team][j + 1] = player; + } + + for ( i = 0; i < level.deadPlayers[team].size; i++ ) + { + if ( level.deadPlayers[team][i].spawnQueueIndex != i ) + { + level.spawnQueueModified[team] = true; + } + level.deadPlayers[team][i].spawnQueueIndex = i; + } +} + +function totalAliveCount() +{ + count = 0; + foreach( team in level.teams ) + { + count += level.aliveCount[team]; + } + return count; +} + +function totalPlayerLives() +{ + count = 0; + foreach( team in level.teams ) + { + count += level.playerLives[team]; + } + return count; +} + +function initTeamVariables( team ) +{ + + if ( !isdefined( level.aliveCount ) ) + level.aliveCount = []; + + level.aliveCount[team] = 0; + level.lastAliveCount[team] = 0; + + if ( !isdefined( game["everExisted"] ) ) + { + game["everExisted"] = []; + } + if ( !isdefined( game["everExisted"][team] ) ) + { + game["everExisted"][team] = false; + } + level.everExisted[team] = false; + level.waveDelay[team] = 0; + level.lastWave[team] = 0; + level.wavePlayerSpawnIndex[team] = 0; + + resetTeamVariables( team );} + +function resetTeamVariables( team ) +{ + level.playerCount[team] = 0; + level.botsCount[team] = 0; + level.lastAliveCount[team] = level.aliveCount[team]; + level.aliveCount[team] = 0; + level.playerLives[team] = 0; + level.alivePlayers[team] = []; + level.spawningPlayers[team] = []; + level.deadPlayers[team] = []; + level.squads[team] = []; + level.spawnQueueModified[team] = false; +} + +function updateTeamStatus() +{ + // run only once per frame, at the end of the frame. + level notify("updating_team_status"); + level endon("updating_team_status"); + level endon ( "game_ended" ); + waittillframeend; + + wait 0; // Required for Callback_PlayerDisconnect to complete before updateTeamStatus can execute + + if ( game["state"] == "postgame" ) + return; + + resetTimeout(); + + foreach( team in level.teams ) + { + resetTeamVariables( team ); + } + + if ( !level.teambased ) + { + resetTeamVariables( "free" ); + } + + level.activePlayers = []; + + players = level.players; + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if ( !isdefined( player ) && level.splitscreen ) + continue; + + if ( level.teambased || player.team == "spectator" ) + { + team = player.team; + } + else + { + team = "free"; + } + + playerclass = player.curClass; + + if ( team != "spectator" && (isdefined( playerclass ) && playerclass != "") ) + { + level.playerCount[team]++; + + if( isdefined( player.pers["isBot"] ) ) + level.botsCount[team]++; + + not_quite_dead = false; + + // used by the resurrect gadget to keep players that could self-resurrect from appearing dead + if ( isdefined( player.overridePlayerDeadStatus ) ) + { + not_quite_dead = player [[ player.overridePlayerDeadStatus ]](); + } + + if ( player.sessionstate == "playing" ) + { + level.aliveCount[team]++; + level.playerLives[team]++; + player.spawnQueueIndex = -1; + + if ( isAlive( player ) ) + { + level.alivePlayers[team][level.alivePlayers[team].size] = player; + level.activeplayers[ level.activeplayers.size ] = player; + } + else + { + level.deadPlayers[team][level.deadPlayers[team].size] = player; + } + } + else if ( not_quite_dead ) + { + level.aliveCount[team]++; + level.playerLives[team]++; + level.alivePlayers[team][level.alivePlayers[team].size] = player; + } + else + { + level.deadPlayers[team][level.deadPlayers[team].size] = player; + if ( player globallogic_spawn::maySpawn() ) + level.playerLives[team]++; + } + } + } + + totalAlive = totalAliveCount(); + + if ( totalAlive > level.maxPlayerCount ) + level.maxPlayerCount = totalAlive; + + foreach( team in level.teams ) + { + if ( level.aliveCount[team] ) + { + game["everExisted"][team] = true; + level.everExisted[team] = true; + } + + sortDeadPlayers( team ); + } + + level updateGameEvents(); +} + +function updateAliveTimes(team) +{ + level.aliveTimesAverage[team] = 0; + + if ( game["state"] == "postgame" ) + return; + + total_player_count = 0; + average_player_spawn_time = 0; + total_value_count = 0; + + foreach( player in level.alivePlayers[team] ) + { + average_time = 0; + count = 0; + foreach( time in player.aliveTimes ) + { + if ( time != 0 ) + { + average_time += time; + count++; + } + } + + if ( count ) + { + total_value_count += count; + average_player_spawn_time += average_time / count; + total_player_count++; + } + } + + foreach( player in level.deadPlayers[team] ) + { + average_time = 0; + count = 0; + foreach( time in player.aliveTimes ) + { + if ( time != 0 ) + { + average_time += time; + count++; + } + } + + if ( count ) + { + total_value_count += count; + average_player_spawn_time += average_time / count; + total_player_count++; + } + } + + // if we dont have player or dont have enough data dont bother + if ( total_player_count == 0 || total_value_count < 3 ) + { + level.aliveTimesAverage[team] = 0; + return; + } + + level.aliveTimesAverage[team] = average_player_spawn_time / total_player_count; + +/# + if ( GetDvarInt( "spawnsystem_debug" ) ) + { + iprintln( "Alive Times: allies: " + level.aliveTimesAverage["allies"] + " axis: " + level.aliveTimesAverage["axis"]); + } +#/ +} + +function updateAllAliveTimes() +{ + foreach( team in level.teams ) + { + updateAliveTimes(team); + } +} + +function checkTeamScoreLimitSoon( team ) +{ + assert( isdefined( team ) ); + + if ( level.scoreLimit <= 0 ) + return; + + if ( !level.teamBased ) + return; + + // Give the data a minute to converge/settle + if ( globallogic_utils::getTimePassed() < ( 60 * 1000 ) ) + return; + + timeLeft = globallogic_utils::getEstimatedTimeUntilScoreLimit( team ); + + if ( timeLeft < 1 ) + { + level notify( "match_ending_soon", "score" ); + } +} + +function checkPlayerScoreLimitSoon() +{ + assert( IsPlayer( self ) ); + + if ( level.scoreLimit <= 0 ) + return; + + if ( level.teamBased ) + return; + + // Give the data a minute to converge/settle + if ( globallogic_utils::getTimePassed() < ( 60 * 1000 ) ) + return; + + timeLeft = globallogic_utils::getEstimatedTimeUntilScoreLimit( undefined ); + + if ( timeLeft < 1 ) + { + level notify( "match_ending_soon", "score" ); + } +} + +function timeLimitClock() +{ + level endon ( "game_ended" ); + + wait .05; + + clockObject = spawn( "script_origin", (0,0,0) ); + + while ( game["state"] == "playing" ) + { + if ( !level.timerStopped && level.timeLimit ) + { + timeLeft = globallogic_utils::getTimeRemaining() / 1000; + timeLeftInt = int(timeLeft + 0.5); // adding .5 and flooring rounds it. + + if ( timeLeftInt == 601 ) + util::clientNotify( "notify_10" ); + + if ( timeLeftInt == 301 ) + util::clientNotify( "notify_5" ); + + if ( timeLeftInt == 60 ) + util::clientNotify( "notify_1" ); + + if ( timeLeftInt == 12 ) + util::clientNotify( "notify_count" ); + + if ( timeLeftInt >= 40 && timeLeftInt <= 60 ) + level notify ( "match_ending_soon", "time" ); + + if ( timeLeftInt >= 30 && timeLeftInt <= 40 ) + level notify ( "match_ending_pretty_soon", "time" ); + + if( timeLeftInt <= 32 ) + level notify ( "match_ending_vox" ); + + if ( timeLeftInt <= 10 || (timeLeftInt <= 30 && timeLeftInt % 2 == 0) ) + { + level notify ( "match_ending_very_soon", "time" ); + // don't play a tick at exactly 0 seconds, that's when something should be happening! + if ( timeLeftInt == 0 ) + break; + + clockObject playSound( "mpl_ui_timer_countdown" ); + } + + // synchronize to be exactly on the second + if ( timeLeft - floor(timeLeft) >= .05 ) + wait timeLeft - floor(timeLeft); + } + + wait ( 1.0 ); + } +} + +function timeLimitClock_Intermission( waitTime ) +{ + setGameEndTime( getTime() + int(waitTime*1000) ); + clockObject = spawn( "script_origin", (0,0,0) ); + + if ( waitTime >= 10.0 ) + wait ( waitTime - 10.0 ); + + for ( ;; ) + { + clockObject playSound( "mpl_ui_timer_countdown" ); + wait ( 1.0 ); + } +} +function recordBreadcrumbData() +{ + level endon ( "game_ended" ); + + while(1) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if( isAlive( player ) ) + { + RecordBreadcrumbDataForPlayer( player, player.lastShotBy ); + } + } + + wait( 2.0 ); // make sure #define BREADCRUMBDATA_INTERVAL_SECONDS in code matches this! + } + +} + +function startGame() +{ + thread globallogic_utils::gameTimer(); + level.timerStopped = false; + level.playableTimerStopped = false; + // RF, disabled this, as it is not required anymore. + //thread spawnlogic::spawn_per_frame_update(); + + SetMatchTalkFlag( "DeadChatWithDead", level.voip.deadChatWithDead ); + SetMatchTalkFlag( "DeadChatWithTeam", level.voip.deadChatWithTeam ); + SetMatchTalkFlag( "DeadHearTeamLiving", level.voip.deadHearTeamLiving ); + SetMatchTalkFlag( "DeadHearAllLiving", level.voip.deadHearAllLiving ); + SetMatchTalkFlag( "EveryoneHearsEveryone", level.voip.everyoneHearsEveryone ); + SetMatchTalkFlag( "DeadHearKiller", level.voip.deadHearKiller ); + SetMatchTalkFlag( "KillersHearVictim", level.voip.killersHearVictim ); + + ClearTopScorers(); + + if(IsDefined(level.custom_prematch_period)) + { + [[level.custom_prematch_period]](); + } + else + { + prematchPeriod(); + } + + level notify("prematch_over"); + level.prematch_over = true; + + level clientfield::set("gameplay_started", 1); + + thread notifyEndOfGameplay(); + + thread timeLimitClock(); + thread gracePeriod(); + thread watchMatchEndingSoon(); + + thread globallogic_audio::announcerController(); + thread globallogic_audio::sndMusicFunctions(); + thread recordBreadcrumbData(); + + if( util::isRoundBased() ) + { + if( util::getRoundsPlayed() == 0 ) + { + // true beginning of a round-based game + recordMatchBegin(); + } + matchRecordRoundStart(); + + if ( isdefined( game["overtime_round"] ) ) + { + matchRecordOvertimeRound(); + } + } + else + { + // not round based + recordMatchBegin(); + } +} + +/# +function printPrematchRequirementInfo( activeTeamCount, startTime ) +{ + if ( !isdefined( level.prematchRequirement ) ) + { + PrintLn( "Prematch Requirement Info: none" ); + return; + } + + str = "Prematch Requirement Info: pmr = " + level.prematchRequirement; + + if ( isdefined( level.prematchRequirementTime ) ) + { + str += ", pmrt = " + level.prematchRequirementTime; + } + + if ( isdefined( startTime ) ) + { + str += ", sT = " + startTime + ", elapsed = " + ( GetTime() - startTime ); + } + + if ( isdefined( activeTeamCount ) ) + { + str += ", aTc = " + activeTeamCount.size; + if ( activeTeamCount.size > 1 ) + { + foreach( team, teamCount in activeTeamCount ) + { + str += ", " + team + " = " + teamCount; + } + } + } + + PrintLn( str ); +} +#/ + +function isPrematchRequirementConditionMet(activeTeamCount, startTime) +{ + /# printPrematchRequirementInfo( activeTeamCount, startTime ); #/ + + if ( level.prematchRequirement == 0 ) + return true; + + if ( level.prematchRequirementTime > 0 && ( ( getTime() - startTime ) > ( level.prematchRequirementTime * 1000 ) ) ) + return true; + + if ( util::isInfectedGametype() ) + { + totalActivePlayers = 0; + foreach ( teamCount in activeTeamCount ) + { + totalActivePlayers += teamCount; + } + + if ( totalActivePlayers != level.prematchRequirement ) + { + return false; + } + } + else if ( level.teamBased && level.prematchRequirementTime > 0 ) + { + if ( activeTeamCount.size <= 1 ) + return false; + + foreach( teamCount in activeTeamCount ) + { + if ( teamCount < level.prematchRequirement ) + return false; + } + } + else if ( level.teamBased ) + { + if ( activeTeamCount.size <= 1 ) + return false; + + foreach( teamCount in activeTeamCount ) + { + if ( teamCount != level.prematchRequirement ) + return false; + } + } + else + { + if ( activeTeamCount["free"] != level.prematchRequirement ) + return false; + } + return true; +} + +function waitForPlayers() +{ + level endon( "game_ended" ); + startTime = getTime(); + + playerReady = false; + activePlayerCount = 0; + acceptTestClient = false; + + activeTeamCount = []; + player_ready = []; + playerReadyTime = undefined; + + while ( !playerReady || activePlayerCount == 0 || !isprematchRequirementConditionMet(activeTeamCount, playerReadyTime) ) + { + activePlayerCount = 0; + + if ( level.teamBased ) + { + foreach( team in level.teams ) + { + activeTeamCount[team] = 0; + } + } + else + { + activeTeamCount["free"] = 0; + } + + temp_player_ready = []; + + foreach( player in level.players ) + { + // only consider real players + if ( player istestclient() && acceptTestClient == false ) + continue; + + if ( player.team != "spectator" ) + { + activePlayerCount++; + + player_num = player GetEntityNumber(); + if ( isDefined( player_ready[player_num] ) ) + { + temp_player_ready[player_num] = player_ready[player_num]; + } + else + { + temp_player_ready[player_num] = GetTime(); + } + + // streamer gets marked ready well before the player is done "loading" + // we want to wait until the player is done loading but will have a five second failsafe + // this mechanic should probably be a core system as we have several places that we want to know + // when the player is done loading + if ( ((temp_player_ready[player_num] + 5000) < GetTime()) || player isStreamerReady( -1, true ) ) + { + if ( level.teamBased ) + { + activeTeamCount[player.team]++; + } + else + { + activeTeamCount["free"]++; + } + } + } + + if( player isStreamerReady( -1, true ) ) + { + if ( playerReady == false ) + { + level notify("first_player_ready", player ); + } + playerReady = true; + } + } + + // this causes any players that no longer exist in the list to flush + player_read = temp_player_ready; + + {wait(.05);}; + if( getTime() - startTime > ( 20 * 1000 ) ) + { + if ( level.rankedmatch == false && level.arenamatch == false ) + { + acceptTestClient = true; + } + } + + if( level.rankedMatch && ((getTime() - startTime) > ( 120 * 1000 )) ) + { + //EOughton: if we've been sitting here for 2 minutes without anyone making it in, it's time to call it a night + exit_level(); + + while(1) + { + // dont do anything just let the level exit + wait(10); + } + } + + if ( !isdefined( playerReadyTime ) && playerReady && activePlayerCount != 0 ) + { + playerReadyTime = gettime(); + } + } +} + +function prematchWaitingForPlayers() +{ + if ( level.prematchRequirement != 0 ) + { + level waittill ("first_player_ready", player ); + thread prematchWaitingForPlayersMsg(); + } +} + +function prematchWaitingForPlayersMsg() +{ + foreach ( player in level.players ) + thread prematchWaitingForPlayersMsgOnSpawn( player ); + + thread prematchWaitingForPlayersMsgOnConnect(); +} + +function prematchWaitingForPlayersMsgOnConnect() +{ + level endon( "game_ended" ); + level endon( "wait_for_players_complete" ); + + while ( true ) + { + level waittill( "connected", player ); + + self thread prematchWaitingForPlayersMsgOnSpawn( player ); + } +} + +function prematchWaitingForPlayersMsgOnSpawn( player ) +{ + level endon( "game_ended" ); + level endon( "wait_for_players_complete" ); + player endon( "disconnect" ); + + while ( true ) + { + if ( ( isdefined( player.hasSpawned ) && player.hasSpawned ) ) + { + wait 2; // make sure lua is ready or the event will be lost + + player LUINotifyEvent( &"prematch_waiting_for_players" ); + return; + } + + player waittill( "spawned" ); + } +} + +function prematchPeriod() +{ + setMatchFlag( "hud_hardcore", level.hardcoreMode ); + + level endon( "game_ended" ); + + globallogic_audio::sndMusicSetRandomizer(); + + if ( level.prematchPeriod > 0 ) + { + thread matchStartTimer(); + thread prematchWaitingForPlayers(); + + waitForPlayers(); + + level notify( "wait_for_players_complete" ); + + wait ( level.prematchPeriod ); + } + else + { + matchStartTimerSkip(); + + {wait(.05);}; + } + + level.inPrematchPeriod = false; + level thread sndSetMatchSnapshot( 0 ); + + for ( index = 0; index < level.players.size; index++ ) + { + level.players[index] util::freeze_player_controls( false ); + level.players[index] enableWeapons(); + } + + if ( game["state"] != "playing" ) + return; +} + +function gracePeriod() +{ + level endon("game_ended"); + + if ( isdefined( level.gracePeriodFunc ) ) + { + [[ level.gracePeriodFunc ]](); + } + else + { + wait ( level.gracePeriod ); + } + + level notify ( "grace_period_ending" ); + {wait(.05);}; + + level.inGracePeriod = false; + + if ( game["state"] != "playing" ) + return; + + if ( level.numLives ) + { + // Players on a team but without a weapon show as dead since they can not get in this round + players = level.players; + + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if ( !player.hasSpawned && player.sessionteam != "spectator" && !isAlive( player ) ) + player.statusicon = "hud_status_dead"; + } + } + + level thread updateTeamStatus(); + level thread updateAllAliveTimes(); +} + +function watchMatchEndingSoon() +{ + SetDvar( "xblive_matchEndingSoon", 0 ); + level waittill( "match_ending_soon", reason ); + SetDvar( "xblive_matchEndingSoon", 1 ); +} + +function assertTeamVariables( ) +{ + // these are defined in the teamset file + foreach ( team in level.teams ) + { + Assert( isdefined( game["strings"][ team + "_win"] ), "game[\"strings\"][\"" + team + "_win\"] does not exist" ); + Assert( isdefined( game["strings"][ team + "_win_round"] ), "game[\"strings\"][\"" + team + "_win_round\"] does not exist" ); + Assert( isdefined( game["strings"][ team + "_mission_accomplished"] ), "game[\"strings\"][\"" + team + "_mission_accomplished\"] does not exist" ); + Assert( isdefined( game["strings"][ team + "_eliminated"] ), "game[\"strings\"][\"" + team + "_eliminated\"] does not exist" ); + Assert( isdefined( game["strings"][ team + "_forfeited"] ), "game[\"strings\"][\"" + team + "_forfeited\"] does not exist" ); + Assert( isdefined( game["strings"][ team + "_name"] ), "game[\"strings\"][\"" + team + "_name\"] does not exist" ); + Assert( isdefined( game["music"]["spawn_" + team] ), "game[\"music\"][\"spawn_" + team + "\"] does not exist" ); + Assert( isdefined( game["music"]["victory_" + team] ), "game[\"music\"][\"victory_" + team + "\"] does not exist" ); + Assert( isdefined( game["icons"][team] ), "game[\"icons\"][\"" + team + "\"] does not exist" ); + Assert( isdefined( game["voice"][team] ), "game[\"voice\"][\"" + team + "\"] does not exist" ); + } +} + +function anyTeamHasWaveDelay() +{ + foreach ( team in level.teams ) + { + if ( level.waveDelay[team] ) + return true; + } + + return false; +} + +function Callback_StartGameType() +{ + level.prematchRequirement = 0; + level.prematchPeriod = 0; + level.intermission = false; + + setmatchflag( "cg_drawSpectatorMessages", 1 ); + setmatchflag( "game_ended", 0 ); + + if ( !isdefined( game["gamestarted"] ) ) + { + // defaults if not defined in level script + if ( !isdefined( game["allies"] ) ) + game["allies"] = "seals"; + if ( !isdefined( game["axis"] ) ) + game["axis"] = "pmc"; + if ( !isdefined( game["attackers"] ) ) + game["attackers"] = "allies"; + if ( !isdefined( game["defenders"] ) ) + game["defenders"] = "axis"; + + // if this hits the teams are not setup right + assert( game["attackers"] != game["defenders"] ); + + // TODO MTEAM - need to update this valid team + foreach( team in level.teams ) + { + if ( !isdefined( game[team] ) ) + game[team] = "pmc"; + } + + if ( !isdefined( game["state"] ) ) + game["state"] = "playing"; + + //makeDvarServerInfo( "cg_thirdPersonAngle", 354 ); + + SetDvar( "cg_thirdPersonAngle", 354 ); + + game["strings"]["press_to_spawn"] = &"PLATFORM_PRESS_TO_SPAWN"; + if ( level.teamBased ) + { + game["strings"]["waiting_for_teams"] = &"MP_WAITING_FOR_TEAMS"; + game["strings"]["opponent_forfeiting_in"] = &"MP_OPPONENT_FORFEITING_IN"; + } + else + { + game["strings"]["waiting_for_teams"] = &"MP_WAITING_FOR_PLAYERS"; + game["strings"]["opponent_forfeiting_in"] = &"MP_OPPONENT_FORFEITING_IN"; + } + game["strings"]["match_starting_in"] = &"MP_MATCH_STARTING_IN"; + game["strings"]["spawn_next_round"] = &"MP_SPAWN_NEXT_ROUND"; + game["strings"]["waiting_to_spawn"] = &"MP_WAITING_TO_SPAWN"; + game["strings"]["waiting_to_spawn_ss"] = &"MP_WAITING_TO_SPAWN_SS"; + //game["strings"]["waiting_to_safespawn"] = &"MP_WAITING_TO_SAFESPAWN"; + game["strings"]["you_will_spawn"] = &"MP_YOU_WILL_RESPAWN"; + game["strings"]["match_starting"] = &"MP_MATCH_STARTING"; + game["strings"]["change_class"] = &"MP_CHANGE_CLASS_NEXT_SPAWN"; + game["strings"]["last_stand"] = &"MPUI_LAST_STAND"; + + game["strings"]["cowards_way"] = &"PLATFORM_COWARDS_WAY_OUT"; + + game["strings"]["tie"] = &"MP_MATCH_TIE"; + game["strings"]["round_draw"] = &"MP_ROUND_DRAW"; + + game["strings"]["enemies_eliminated"] = &"MP_ENEMIES_ELIMINATED"; + game["strings"]["score_limit_reached"] = &"MP_SCORE_LIMIT_REACHED"; + game["strings"]["round_score_limit_reached"] = &"MP_SCORE_LIMIT_REACHED"; + game["strings"]["round_limit_reached"] = &"MP_ROUND_LIMIT_REACHED"; + game["strings"]["time_limit_reached"] = &"MP_TIME_LIMIT_REACHED"; + game["strings"]["players_forfeited"] = &"MP_PLAYERS_FORFEITED"; + game["strings"]["other_teams_forfeited"] = &"MP_OTHER_TEAMS_FORFEITED"; + + assertTeamVariables(); + + [[level.onPrecacheGameType]](); + + game["gamestarted"] = true; + + game["totalKills"] = 0; + + foreach( team in level.teams ) + { + if ( !isdefined( game["migratedHost"] ) ) + game["teamScores"][team] = 0; + + game["teamSuddenDeath"][team] = false; + + game["totalKillsTeam"][team] = 0; + } + + level.prematchRequirement = GetGametypeSetting( "prematchRequirement" ); + level.prematchRequirementTime = GetGametypeSetting( "prematchRequirementTime" ); + level.prematchPeriod = GetGametypeSetting( "prematchperiod" ); + + if ( GetDvarint( "xblive_clanmatch" ) != 0 ) + { + // TODO MTEAM is this code used anymore? + foreach( team in level.teams ) + { + game["icons"][team] = "composite_emblem_team_axis"; + } + + game["icons"]["allies"] = "composite_emblem_team_allies"; + game["icons"]["axis"] = "composite_emblem_team_axis"; + } + } + else + { + if ( !level.splitscreen ) + level.prematchPeriod = GetGametypeSetting( "preroundperiod" ); + } + + if(!isdefined(game["timepassed"]))game["timepassed"]=0; + if(!isdefined(game["playabletimepassed"]))game["playabletimepassed"]=0; + if(!isdefined(game["roundsplayed"]))game["roundsplayed"]=0; + + SetRoundsPlayed( game["roundsplayed"] ); + + if ( isdefined( game["overtime_round"] ) ) + { + SetRoundsPlayed( game["roundsplayed"] + game["overtime_round"] - 1 ); + SetMatchFlag( "overtime", 1 ); + } + else + { + SetMatchFlag( "overtime", 0 ); + } + + if(!isdefined(game["roundwinner"] )) + game["roundwinner"] = []; + + if(!isdefined(game["lastroundscore"] )) + game["lastroundscore"] = []; + + if(!isdefined(game["roundswon"] )) + game["roundswon"] = []; + + if(!isdefined(game["roundswon"]["tie"] )) + game["roundswon"]["tie"] = 0; + + if(!isdefined(game["overtimeroundswon"] )) + game["overtimeroundswon"] = []; + + if(!isdefined(game["overtimeroundswon"]["tie"] )) + game["overtimeroundswon"]["tie"] = 0; + + foreach ( team in level.teams ) + { + if(!isdefined(game["roundswon"][team] )) + game["roundswon"][team] = 0; + if(!isdefined(game["overtimeroundswon"][team] )) + game["overtimeroundswon"][team] = 0; + + level.teamSpawnPoints[team] = []; + level.spawn_point_team_class_names[team] = []; + } + + level.skipVote = false; + level.gameEnded = false; + level.exitLevel = false; + //level clientfield::set( "game_ended", 0 ); + SetDvar( "g_gameEnded", 0 ); + + level.objIDStart = 0; + level.forcedEnd = false; + level.hostForcedEnd = false; + + level.hardcoreMode = GetGametypeSetting( "hardcoreMode" ); + if ( level.hardcoreMode ) + { + /#print( "game mode: hardcore" );#/ + + //set up friendly fire delay for hardcore + if( !isdefined(level.friendlyFireDelayTime) ) + level.friendlyFireDelayTime = 0; + } + + if ( GetDvarString( "scr_max_rank" ) == "" ) + SetDvar( "scr_max_rank", "0" ); + level.rankCap = GetDvarint( "scr_max_rank" ); + + if ( GetDvarString( "scr_min_prestige" ) == "" ) + { + SetDvar( "scr_min_prestige", "0" ); + } + level.minPrestige = GetDvarint( "scr_min_prestige" ); + + // this gets set to false when someone takes damage or a gametype-specific event happens. + level.useStartSpawns = true; + level.alwaysUseStartSpawns = false; + level.useXCamsForEndGame = true; + + level.cumulativeRoundScores = GetGametypeSetting( "cumulativeRoundScores" ); + + level.allowHitMarkers = GetGametypeSetting( "allowhitmarkers" ); + level.playerQueuedRespawn = GetGametypeSetting( "playerQueuedRespawn" ); + level.playerForceRespawn = GetGametypeSetting( "playerForceRespawn" ); + + level.roundStartExplosiveDelay = GetGametypeSetting( "roundStartExplosiveDelay" ); + level.roundStartKillstreakDelay = GetGametypeSetting( "roundStartKillstreakDelay" ); + + level.perksEnabled = GetGametypeSetting( "perksEnabled" ); + level.disableAttachments = GetGametypeSetting( "disableAttachments" ); + level.disableTacInsert = GetGametypeSetting( "disableTacInsert" ); + level.disableCAC = GetGametypeSetting( "disableCAC" ); + level.disableClassSelection = GetGametypeSetting( "disableClassSelection" ); + level.disableWeaponDrop = GetGametypeSetting( "disableweapondrop" ); + level.onlyHeadShots = GetGametypeSetting( "onlyHeadshots" ); + + // set to 0 to disable + level.minimumAllowedTeamKills = GetGametypeSetting( "teamKillPunishCount" ) - 1; + level.teamKillReducedPenalty = GetGametypeSetting( "teamKillReducedPenalty" ); + level.teamKillPointLoss = GetGametypeSetting( "teamKillPointLoss" ); + level.teamKillSpawnDelay = GetGametypeSetting( "teamKillSpawnDelay" ); + + level.deathPointLoss = GetGametypeSetting( "deathPointLoss" ); + level.leaderBonus = GetGametypeSetting( "leaderBonus" ); + level.forceRadar = GetGametypeSetting( "forceRadar" ); + level.playerSprintTime = GetGametypeSetting( "playerSprintTime" ); + level.bulletDamageScalar = GetGametypeSetting( "bulletDamageScalar" ); + + level.playerMaxHealth = GetGametypeSetting( "playerMaxHealth" ); + level.playerHealthRegenTime = GetGametypeSetting( "playerHealthRegenTime" ); + level.scoreResetOnDeath = GetGametypeSetting( "scoreResetOnDeath" ); + + level.playerRespawnDelay = GetGametypeSetting( "playerRespawnDelay" ); + level.playerIncrementalRespawnDelay = GetGametypeSetting( "incrementalSpawnDelay" ); + + level.playerObjectiveHeldRespawnDelay = GetGametypeSetting( "playerObjectiveHeldRespawnDelay" ); + level.waveRespawnDelay = GetGametypeSetting( "waveRespawnDelay" ); + level.suicideSpawnDelay = GetGametypeSetting( "spawnsuicidepenalty" ); + level.teamKilledSpawnDelay = GetGametypeSetting( "spawnteamkilledpenalty" ); + level.maxSuicidesBeforeKick = GetGametypeSetting( "maxsuicidesbeforekick" ); + + level.spectateType = GetGametypeSetting( "spectateType" ); + + level.voip = SpawnStruct(); + level.voip.deadChatWithDead = GetGametypeSetting( "voipDeadChatWithDead" ); + level.voip.deadChatWithTeam = GetGametypeSetting( "voipDeadChatWithTeam" ); + level.voip.deadHearAllLiving = GetGametypeSetting( "voipDeadHearAllLiving" ); + level.voip.deadHearTeamLiving = GetGametypeSetting( "voipDeadHearTeamLiving" ); + level.voip.everyoneHearsEveryone = GetGametypeSetting( "voipEveryoneHearsEveryone" ); + level.voip.deadHearKiller = GetGametypeSetting( "voipDeadHearKiller" ); + level.voip.killersHearVictim = GetGametypeSetting( "voipKillersHearVictim" ); + + level.droppedTagRespawn = GetGametypeSetting( "droppedTagRespawn" ); + + level.disableVehicleSpawners = GetGametypeSetting( "disableVehicleSpawners" ); + + if( ( isdefined( level.droppedTagRespawn ) && level.droppedTagRespawn ) ) + dogtags::init(); + + // this will clear out any non game mode entities before the gametype callbacks happen + gameobjects::main(); + + callback::callback( #"on_start_gametype" ); + + thread hud_message::init(); + + foreach( team in level.teams ) + { + initTeamVariables( team ); + } + + if ( !level.teambased ) + { + initTeamVariables( "free" ); + } + + level.maxPlayerCount = 0; + level.activePlayers = []; + + level.aliveTimeMaxCount = 3; + level.aliveTimesAverage = []; + foreach( team in level.teams ) + { + level.aliveTimesAverage[team] = 0; + } + + if( !isdefined( level.livesDoNotReset ) || !level.livesDoNotReset ) + { + foreach( team in level.teams ) + { + game[team+"_lives"] = level.numTeamLives; + } + } + + level.allowAnnouncer = GetGametypeSetting( "allowAnnouncer" ); + + if ( !isdefined( level.timeLimit ) ) + util::registerTimeLimit( 1, 1440 ); + + if ( !isdefined( level.scoreLimit ) ) + util::registerScoreLimit( 1, 500 ); + + if ( !isdefined( level.roundScoreLimit ) ) + util::registerRoundScoreLimit( 0, 500 ); + + if ( !isdefined( level.roundLimit ) ) + util::registerRoundLimit( 0, 10 ); + + if ( !isdefined( level.roundWinLimit ) ) + util::registerRoundWinLimit( 0, 10 ); + + // The order the following functions are registered in are the order they will get called + globallogic_utils::registerPostRoundEvent( &killcam::post_round_final_killcam ); + + //makeDvarServerInfo( "ui_scorelimit" ); + //makeDvarServerInfo( "ui_timelimit" ); + //makeDvarServerInfo( "ui_allow_classchange", GetDvarString( "ui_allow_classchange" ) ); + + waveDelay = level.waveRespawnDelay; + if ( waveDelay ) + { + foreach ( team in level.teams ) + { + level.waveDelay[team] = waveDelay; + level.lastWave[team] = 0; + } + + level thread [[level.waveSpawnTimer]](); + } + + level.inPrematchPeriod = true; + + if ( level.prematchPeriod > 2.0 && level.rankedMatch ) + level.prematchPeriod = level.prematchPeriod + (randomFloat( 4 ) - 2); // live host obfuscation + + if ( !isdefined( level.gracePeriod ) ) + { + if ( level.numLives || anyTeamHasWaveDelay() || level.playerQueuedRespawn ) + level.gracePeriod = 15; + else + level.gracePeriod = 5; + } + + level.inGracePeriod = true; + + level.roundEndDelay = 5; + level.halftimeRoundEndDelay = 3; + + globallogic_score::updateAllTeamScores(); + + level.killstreaksenabled = 1; + level.missileLockPlaySpaceCheckEnabled = true; + level.missileLockPlaySpaceCheckExtraRadius = 5000; + + if ( GetDvarString( "scr_game_rankenabled" ) == "" ) + SetDvar( "scr_game_rankenabled", true ); + level.rankEnabled = GetDvarint( "scr_game_rankenabled" ); + + if ( GetDvarString( "scr_game_medalsenabled" ) == "" ) + SetDvar( "scr_game_medalsenabled", true ); + level.medalsEnabled = GetDvarint( "scr_game_medalsenabled" ); + + if( level.hardcoreMode && level.rankedMatch && GetDvarString( "scr_game_friendlyFireDelay" ) == "" ) + SetDvar( "scr_game_friendlyFireDelay", true ); + level.friendlyFireDelay = GetDvarint( "scr_game_friendlyFireDelay" ); + + // level gametype and features globals should be defaulted before this, and level.onstartgametype should reset them if desired + [[level.onStartGameType]](); + + // disable killstreaks for custom game modes + if( GetDvarInt( "custom_killstreak_mode" ) == 1 ) + { + level.killstreaksenabled = 0; + } + + level thread killcam::do_final_killcam(); + + thread startGame(); + level thread updateGameTypeDvars(); + level thread simple_hostmigration::UpdateHostMigrationData(); + +/# + if( GetDvarint( "scr_writeconfigstrings" ) == 1 ) + { + level.skipGameEnd = true; + level.roundLimit = 1; + + // let things settle + wait(1); +// level.forcedEnd = true; + thread forceEnd( false ); +// thread endgame( "tie","" ); + } + if( GetDvarint( "scr_hostmigrationtest" ) == 1 ) + { + thread ForceDebugHostMigration(); + } +#/ +} + +/# +function ForceDebugHostMigration() +{ + while (1) + { + hostmigration::waitTillHostMigrationDone(); + wait(60); + starthostmigration(); + hostmigration::waitTillHostMigrationDone(); + //thread forceEnd( false ); + } +} +#/ + +function registerFriendlyFireDelay( dvarString, defaultValue, minValue, maxValue ) +{ + dvarString = ("scr_" + dvarString + "_friendlyFireDelayTime"); + if ( GetDvarString( dvarString ) == "" ) + SetDvar( dvarString, defaultValue ); + + if ( getDvarInt( dvarString ) > maxValue ) + SetDvar( dvarString, maxValue ); + else if ( getDvarInt( dvarString ) < minValue ) + SetDvar( dvarString, minValue ); + + level.friendlyFireDelayTime = getDvarInt( dvarString ); +} + +function checkRoundSwitch() +{ + if ( !isdefined( level.roundSwitch ) || !level.roundSwitch ) + return false; + if ( !isdefined( level.onRoundSwitch ) ) + return false; + + assert( game["roundsplayed"] > 0 ); + + if ( game["roundsplayed"] % level.roundswitch == 0 ) + { + [[level.onRoundSwitch]](); + return true; + } + + return false; +} + +function waitForGameEndDvar() +{ + while( 1 ) + { + endmatch = GetDvarInt( "sv_endmatch", 0 ); + if( isdefined( endmatch ) && endMatch != 0 ) + { + SetDvar( "sv_endmatch", 0 ); + level thread globallogic::forceEnd(); + return; + } + wait .05; + } +} + +function listenForGameEnd() +{ + self waittill( "host_sucks_end_game" ); + //if ( level.console ) + // endparty(); + level.skipVote = true; + + if ( !level.gameEnded ) + level thread globallogic::forceEnd(true); +} + + +function getKillStreaks( player ) +{ + for ( killstreakNum = 0; killstreakNum < level.maxKillstreaks; killstreakNum++ ) + { + killstreak[ killstreakNum ] = "killstreak_null"; + } + + if ( isPlayer( player ) && + level.disableClassSelection != 1 && + !isdefined( player.pers["isBot"] ) && + isdefined( player.killstreak) ) + { + currentKillstreak = 0; + for ( killstreakNum = 0; killstreakNum < level.maxKillstreaks; killstreakNum++ ) + { + if ( isdefined( player.killstreak[ killstreakNum ] ) ) + { + killstreak[ currentKillstreak ] = player.killstreak[ killstreakNum ]; + currentKillstreak++; + } + } + } + + return killstreak; +} + +function updateRankedMatch(winner) +{ + if ( level.rankedMatch ) + { + if ( hostIdledOut() ) + { + level.hostForcedEnd = true; + /#print( "host idled out" );#/ + endLobby(); + } + } + globallogic_score::updateMatchBonusScores( winner ); +} + + +function annihilatorGunPlayerKillEffect( attacker, weapon ) +{ + if ( weapon.fusetime != 0 ) + wait( weapon.fusetime * 0.001 ); + else + wait(0.45); + + if ( !isdefined( self ) ) + { + return; + } + + self playsoundtoplayer( "evt_annihilation", attacker ); + self playsoundtoallbutplayer( "evt_annihilation_npc", attacker ); + + CodeSetClientField(self, "annihilate_effect", 1); + self shake_and_rumble(0, 0.3, 0.75, 1); + + wait 0.1; + + if ( !isdefined( self ) ) + { + return; + } + + self NotSolid(); + self Ghost(); +} + +function annihilatorGunActorKillEffect( attacker, weapon ) +{ + self waittill("actor_corpse",body); //now a corpse! + + if ( weapon.fusetime != 0 ) + wait( weapon.fusetime * 0.001 ); + else + wait(0.45); + + if ( !isdefined( self ) ) + { + return; + } + + self playsoundtoplayer( "evt_annihilation", attacker ); + self playsoundtoallbutplayer( "evt_annihilation_npc", attacker ); + + if ( !isdefined( body ) ) + { + return; + } + + CodeSetClientField(body, "annihilate_effect", 1); + body shake_and_rumble(0, 0.6, 0.2, 1); + body NotSolid(); + body Ghost(); +} + +function pineappleGunPlayerKillEffect( attacker ) +{ + wait 0.1; + + if ( !isdefined( self ) ) + { + return; + } + + playsoundatposition ("evt_annihilation_npc", (self.origin)); + CodeSetClientField(self, "pineapplegun_effect", 1); + self shake_and_rumble(0, 0.3, 0.35, 1); + wait 0.1; + + if ( !isdefined( self ) ) + { + return; + } + + self NotSolid(); + self Ghost(); +} + +function BowPlayerKillEffect() +{ + wait 0.05; + if ( !isdefined( self ) ) + { + return; + } + + playsoundatposition ("evt_annihilation_npc", (self.origin)); + CodeSetClientField(self, "annihilate_effect", 1); + self shake_and_rumble(0, 0.3, 0.35, 1); + + if ( !isdefined( self ) ) + { + return; + } + + self NotSolid(); + self Ghost(); +} + +function pineappleGunActorKillEffect() +{ + self waittill("actor_corpse",body); //now a corpse! + wait(0.75); + + if ( !isdefined( self ) ) + { + return; + } + + playsoundatposition ("evt_annihilation_npc", (self.origin)); + + if ( !isdefined( body ) ) + { + return; + } + + CodeSetClientField(body, "pineapplegun_effect", 1); + body shake_and_rumble(0, 0.3, 0.75, 1); + body NotSolid(); + body Ghost(); +} + +// ============================================================================ +// self = player +function shake_and_rumble( n_delay, shake_size, shake_time, rumble_num ) +{ + if( IsDefined(n_delay) && (n_delay > 0) ) + { + wait( n_delay ); + } + + // Earthquake + nMagnitude = shake_size; + nduration = shake_time; + nRadius = 500; + v_pos = self.origin; + Earthquake( nMagnitude, nDuration, v_pos, nRadius ); + + // Pad Rumble + for( i=0; i 0 && shitloc !== "riotshield" ) + { + eAttacker thread damagefeedback::update( sMeansOfDeath, eInflictor, undefined, weapon, self ); + } + } + } + } + + /# + // Do debug print if it's enabled + if(GetDvarint( "g_debugDamage")) + println("actor:" + self getEntityNumber() + " health:" + self.health + " attacker:" + eAttacker.clientid + " inflictor is player:" + isPlayer(eInflictor) + " damage:" + iDamage + " hitLoc:" + sHitLoc); + #/ + if(1) // self.sessionstate != "dead") + { + lpselfnum = self getEntityNumber(); + lpselfteam = self.team; + lpattackerteam = ""; + + if(isPlayer(eAttacker)) + { + lpattacknum = eAttacker getEntityNumber(); + lpattackGuid = eAttacker getGuid(); + lpattackname = eAttacker.name; + lpattackerteam = eAttacker.pers["team"]; + } + else + { + lpattacknum = -1; + lpattackGuid = ""; + lpattackname = ""; + lpattackerteam = "world"; + } + + /#logPrint("AD;" + lpselfnum + ";" + lpselfteam + ";" + lpattackGuid + ";" + lpattacknum + ";" + lpattackerteam + ";" + lpattackname + ";" + weapon.name + ";" + iDamage + ";" + sMeansOfDeath + ";" + sHitLoc + ";" + boneIndex + "\n");#/ + } + +} + +function Callback_ActorKilled(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime) +{ + if ( game["state"] == "postgame" ) + return; + + if( isai(attacker) && isdefined( attacker.script_owner ) ) + { + // if the person who called the dogs in switched teams make sure they don't + // get penalized for the kill + if ( attacker.script_owner.team != self.team ) + attacker = attacker.script_owner; + } + + if( attacker.classname == "script_vehicle" && isdefined( attacker.owner ) ) + attacker = attacker.owner; + + _gadget_clone::ProcessCloneScoreEvent( self, attacker, weapon ); + + globallogic::DoWeaponSpecificKillEffects(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime); + globallogic::DoWeaponSpecificCorpseEffects(self, eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime); + + +} + +function Callback_ActorCloned( original ) +{ + DestructServerUtils::CopyDestructState( original, self ); + GibServerUtils::CopyGibState( original, self ); +} diff --git a/mp/gametypes/_globallogic_audio.gsc b/mp/gametypes/_globallogic_audio.gsc new file mode 100644 index 0000000..68377cd --- /dev/null +++ b/mp/gametypes/_globallogic_audio.gsc @@ -0,0 +1,1084 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\music_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\audio_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_utils; + +#using scripts\codescripts\struct; + +#using scripts\mp\_util; + +#namespace globallogic_audio; + +function autoexec __init__sytem__() { system::register("globallogic_audio",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); + + level.playLeaderDialogOnPlayer = &leader_dialog_on_player; + level.playEquipmentDestroyedOnPlayer = &play_equipment_destroyed_on_player; + level.playEquipmentHackedOnPlayer = &play_equipment_hacked_on_player; +} + +function init() +{ + game["music"]["defeat"] = "mus_defeat"; + game["music"]["victory_spectator"] = "mus_defeat"; + game["music"]["winning"] = "mus_time_running_out_winning"; + game["music"]["losing"] = "mus_time_running_out_losing"; + game["music"]["match_end"] = "mus_match_end"; + game["music"]["victory_tie"] = "mus_defeat"; + game["music"]["spawn_short"] = "SPAWN_SHORT"; + + game["music"]["suspense"] = []; + game["music"]["suspense"][game["music"]["suspense"].size] = "mus_suspense_01"; + game["music"]["suspense"][game["music"]["suspense"].size] = "mus_suspense_02"; + game["music"]["suspense"][game["music"]["suspense"].size] = "mus_suspense_03"; + game["music"]["suspense"][game["music"]["suspense"].size] = "mus_suspense_04"; + game["music"]["suspense"][game["music"]["suspense"].size] = "mus_suspense_05"; + game["music"]["suspense"][game["music"]["suspense"].size] = "mus_suspense_06"; + + level thread post_match_snapshot_watcher(); + + level.multipleDialogKeys = []; + + // Play 'multiple incoming' lines for these + level.multipleDialogKeys["enemyAiTank"] = "enemyAiTankMultiple"; + level.multipleDialogKeys["enemySupplyDrop"] = "enemySupplyDropMultiple"; + level.multipleDialogKeys["enemyCombatRobot"] = "enemyCombatRobotMultiple"; + level.multipleDialogKeys["enemyCounterUav"] = "enemyCounterUavMultiple"; + level.multipleDialogKeys["enemyDart"] = "enemyDartMultiple"; + level.multipleDialogKeys["enemyEmp"] = "enemyEmpMultiple"; + level.multipleDialogKeys["enemySentinel"] = "enemySentinelMultiple"; + level.multipleDialogKeys["enemyMicrowaveTurret"] = "enemyMicrowaveTurretMultiple"; + level.multipleDialogKeys["enemyRcBomb"] = "enemyRcBombMultiple"; + level.multipleDialogKeys["enemyPlaneMortar"] = "enemyPlaneMortarMultiple"; + level.multipleDialogKeys["enemyHelicopterGunner"] = "enemyHelicopterGunnerMultiple"; + level.multipleDialogKeys["enemyRaps"] = "enemyRapsMultiple"; + level.multipleDialogKeys["enemyDroneStrike"] = "enemyDroneStrikeMultiple"; + level.multipleDialogKeys["enemyTurret"] = "enemyTurretMultiple"; + level.multipleDialogKeys["enemyHelicopter"] = "enemyHelicopterMultiple"; + level.multipleDialogKeys["enemyUav"] = "enemyUavMultiple"; + level.multipleDialogKeys["enemySatellite"] = "enemySatelliteMultiple"; + + // Skip these + level.multipleDialogKeys["friendlyAiTank"] = ""; + level.multipleDialogKeys["friendlySupplyDrop"] = ""; + level.multipleDialogKeys["friendlyCombatRobot"] = ""; + level.multipleDialogKeys["friendlyCounterUav"] = ""; + level.multipleDialogKeys["friendlyDart"] = ""; + level.multipleDialogKeys["friendlyEmp"] = ""; + level.multipleDialogKeys["friendlySentinel"] = ""; + level.multipleDialogKeys["friendlyMicrowaveTurret"] = ""; + level.multipleDialogKeys["friendlyRcBomb"] = ""; + level.multipleDialogKeys["friendlyPlaneMortar"] = ""; + level.multipleDialogKeys["friendlyHelicopterGunner"] = ""; + level.multipleDialogKeys["friendlyRaps"] = ""; + level.multipleDialogKeys["friendlyDroneStrike"] = ""; + level.multipleDialogKeys["friendlyTurret"] = ""; + level.multipleDialogKeys["friendlyHelicopter"] = ""; + level.multipleDialogKeys["friendlyUav"] = ""; + level.multipleDialogKeys["friendlySatellite"] = ""; +} + +function set_leader_gametype_dialog( startGameDialogKey, startHcGameDialogKey, offenseOrderDialogKey, defenseOrderDialogKey ) +{ + level.leaderDialog = SpawnStruct(); + + level.leaderDialog.startGameDialog = startGameDialogKey; + level.leaderDialog.startHcGameDialog = startHcGameDialogKey; + + level.leaderDialog.offenseOrderDialog = offenseOrderDialogKey; + level.leaderDialog.defenseOrderDialog = defenseOrderDialogKey; +} + +function announce_round_winner( winner, delay ) +{ + if ( delay > 0 ) + { + wait delay; + } + + if ( !isdefined( winner ) || isPlayer( winner ) ) + { + return; + } + + if ( isdefined( level.teams[ winner ] ) ) + { + //thread sound::play_on_players( "mus_round_win"+"_"+level.teamPostfix[winner] ); + //thread sound::play_on_players( "mus_round_loss"+"_"+level.teamPostfix[team] ); + leader_dialog( "roundEncourageWon", winner ); + leader_dialog_for_other_teams( "roundEncourageLost", winner ); + } + else + { + foreach ( team in level.teams ) + { + thread sound::play_on_players( "mus_round_draw"+"_"+level.teamPostfix[team] ); + } + leader_dialog( "roundDraw" ); + } +} + + +function announce_game_winner( winner ) +{ + if( level.gametype == "fr" ) + { + return; + } + + wait ( battlechatter::mpdialog_value( "announceWinnerDelay", 0 ) ); + + if ( level.teamBased ) + { + if ( isdefined( level.teams[ winner ] ) ) + { + leader_dialog( "gameWon", winner ); + leader_dialog_for_other_teams( "gameLost", winner ); + } + else + { + leader_dialog( "gameDraw" ); + } + + wait( battlechatter::mpdialog_value( "commanderDialogBuffer", 0 ) ); + } + + battlechatter::game_end_vox( winner ); +} + +function flush_dialog() +{ + foreach( player in level.players ) + { + player flush_dialog_on_player(); + } +} + +function flush_dialog_on_player() +{ + self.leaderDialogQueue = []; + self.currentLeaderDialog = undefined; + + self.killstreakDialogQueue = []; + self.scorestreakDialogPlaying = false; + + self notify( "flush_dialog" ); +} + +function flush_killstreak_dialog_on_player( killstreakId ) +{ + if ( !isdefined( killstreakId ) ) + { + return; + } + + for( i = self.killstreakDialogQueue.size - 1; i >=0; i-- ) + { + if ( killstreakId === self.killstreakDialogQueue[i].killstreakId ) + { + ArrayRemoveIndex( self.killstreakDialogQueue, i ); + } + } +} + +function killstreak_dialog_queued( dialogKey, killstreakType, killstreakId ) +{ + if ( !isdefined( dialogKey ) || + !isdefined( killstreakType ) ) + { + return; + } + if ( isdefined( self.currentKillstreakDialog ) ) + { + if ( dialogKey === self.currentKillstreakDialog.dialogKey && + killstreakType === self.currentKillstreakDialog.killstreakType && + killstreakId === self.currentKillstreakDialog.killstreakId ) + { + return true; + } + } + + for( i = 0; i < self.killstreakDialogQueue.size; i++ ) + { + if ( dialogKey === self.killstreakDialogQueue[i].dialogKey && + killstreakType === self.killstreakDialogQueue[i].killstreakType && + killstreakType === self.killstreakDialogQueue[i].killstreakType ) + { + return true; + } + } + + return false; +} + +function flush_objective_dialog( objectiveKey ) +{ + foreach( player in level.players ) + { + player flush_objective_dialog_on_player( objectiveKey ); + } +} + +function flush_objective_dialog_on_player( objectiveKey ) +{ + if ( !isdefined( objectiveKey ) ) + { + return; + } + + for( i = self.leaderDialogQueue.size - 1; i >=0; i-- ) + { + if ( objectiveKey === self.leaderDialogQueue[i].objectiveKey ) + { + ArrayRemoveIndex( self.leaderDialogQueue, i ); + break; // There should never be more than one objective key of a given type + } + } +} + +function flush_leader_dialog_key( dialogKey ) +{ + foreach ( player in level.players ) + { + player flush_leader_dialog_key_on_player( dialogKey ); + } +} + +function flush_leader_dialog_key_on_player( dialogKey ) +{ + if ( !isdefined( dialogKey ) ) + { + return; + } + + for( i = self.leaderDialogQueue.size - 1; i >=0; i-- ) + { + if ( dialogKey === self.leaderDialogQueue[i].dialogKey ) + { + ArrayRemoveIndex( self.leaderDialogQueue, i ); + } + } +} + +function play_taacom_dialog( dialogKey, killstreakType, killstreakId ) +{ + self killstreak_dialog_on_player( dialogKey, killstreakType, killstreakId ); +} + +function killstreak_dialog_on_player( dialogKey, killstreakType, killstreakId, pilotIndex ) +{ + if ( !isdefined( dialogKey ) ) + { + return; + } + + if ( !level.allowAnnouncer ) + { + return; + } + + if ( level.gameEnded ) + { + return; + } + + newDialog = SpawnStruct(); + + newDialog.dialogKey = dialogKey; + newDialog.killstreakType = killstreakType; + newDialog.pilotIndex = pilotIndex; + newDialog.killstreakId = killstreakId; + + self.killstreakDialogQueue[self.killstreakDialogQueue.size] = newDialog; + + // Queue is already in motion + if ( self.killstreakDialogQueue.size > 1 || isdefined( self.currentKillstreakDialog ) ) + { + return; + } + + // HACK: Don't start up the killstreak queue with taacom dialog while the player is speaking + // Keeps the request and arrival lines from getting jumbled up in quiet moments + if ( self.playingDialog === true && dialogKey == "arrive" ) + { + self thread wait_for_player_dialog(); + } + else + { + self thread play_next_killstreak_dialog(); + } +} + +function wait_for_player_dialog() +{ + self endon( "disconnect" ); + self endon( "flush_dialog" ); + level endon( "game_ended" ); + + while ( self.playingDialog ) + { + wait( 0.5 ); + } + + self thread play_next_killstreak_dialog(); +} + +function play_next_killstreak_dialog() +{ + self endon( "disconnect" ); + self endon( "flush_dialog" ); + level endon( "game_ended" ); + + if ( self.killstreakDialogQueue.size == 0 ) + { + self.currentKillstreakDialog = undefined; + return; + } + + nextDialog = self.killstreakDialogQueue[0]; + + ArrayRemoveIndex( self.killstreakDialogQueue, 0 ); + + if ( isdefined( self.pers["mptaacom"] ) ) + { + taacomBundle = struct::get_script_bundle( "mpdialog_taacom", self.pers["mptaacom"] ); + } + + if ( isdefined( taacomBundle ) ) + { + if ( isdefined( nextDialog.killstreakType ) ) + { + if ( isdefined( nextDialog.pilotIndex ) ) + { + pilotArray = taacomBundle.pilotBundles[nextDialog.killstreakType]; + + if ( isdefined( pilotArray ) && nextDialog.pilotIndex < pilotArray.size ) + { + killstreakBundle = struct::get_script_bundle( "mpdialog_scorestreak", pilotArray[nextDialog.pilotIndex] ); + + if ( isdefined( killstreakBundle ) ) + { + dialogAlias = globallogic_audio::get_dialog_bundle_alias( killstreakBundle, nextDialog.dialogKey ); + } + } + } + else if ( isdefined( level.killstreaks[nextDialog.killstreakType] ) ) + { + bundleName = GetStructField( taacomBundle, level.killstreaks[nextDialog.killstreakType].taacomDialogBundleKey ); + + if ( isdefined( bundleName ) ) + { + killstreakBundle = struct::get_script_bundle( "mpdialog_scorestreak", bundleName ); + + if ( isdefined( killstreakBundle ) ) + { + dialogAlias = self globallogic_audio::get_dialog_bundle_alias( killstreakBundle, nextDialog.dialogKey ); + } + } + } + } + else + { + dialogAlias = self globallogic_audio::get_dialog_bundle_alias( taacomBundle, nextDialog.dialogKey ); + } + } + + if ( !isdefined( dialogAlias ) ) + { + self play_next_killstreak_dialog(); + return; + } + + self playLocalSound( dialogAlias ); + + self.currentKillstreakDialog = nextDialog; + + self thread wait_next_killstreak_dialog(); +} + +function wait_next_killstreak_dialog() +{ + self endon( "disconnect" ); + self endon( "flush_dialog" ); + level endon( "game_ended" ); + + wait ( battlechatter::mpdialog_value( "killstreakDialogBuffer", 0 ) ); + + self thread play_next_killstreak_dialog(); +} + + +function leader_dialog_for_other_teams( dialogKey, skipTeam, objectiveKey, killstreakId, dialogBufferKey ) +{ + assert( isdefined( skipTeam ) ); + + foreach( team in level.teams ) + { + if ( team != skipTeam ) + { + leader_dialog( dialogKey, team, undefined, objectiveKey, killstreakId, dialogBufferKey ); + } + } +} + +function leader_dialog( dialogKey, team, excludeList, objectiveKey, killstreakId, dialogBufferKey ) +{ + assert( isdefined( level.players ) ); + + foreach ( player in level.players ) + { + if ( !isdefined( player.pers["team"] ) ) + { + continue; + } + + if ( isdefined( team ) && team != player.pers["team"] ) + { + continue; + } + + if ( isdefined( excludeList ) && globallogic_utils::isExcluded( player, excludeList ) ) + { + continue; + } + + player leader_dialog_on_player( dialogKey, objectiveKey, killstreakId, dialogBufferKey ); + } +} + +function leader_dialog_on_player( dialogKey, objectiveKey, killstreakId, dialogBufferKey, introDialog ) +{ + if ( !isdefined( dialogKey ) ) + { + return; + } + + if ( !level.allowAnnouncer ) + { + return; + } + + if ( !( isdefined( self.playLeaderDialog ) && self.playLeaderDialog ) && !( isdefined( introDialog ) && introDialog ) ) + { + return; + } + + self flush_objective_dialog_on_player( objectiveKey ); + + // Don't queue up the same objective dialog that is currently playing + if ( self.leaderDialogQueue.size == 0 && + isdefined( self.currentLeaderDialog ) && + isdefined( objectiveKey ) && + self.currentLeaderDialog.objectiveKey === objectiveKey && + self.currentLeaderDialog.dialogKey == dialogKey ) + { + return; + } + + // Multiple enemy scorestreak conversion + if ( isdefined( killstreakId ) ) + { + // Look for an already queued item + foreach( item in self.leaderDialogQueue ) + { + if ( item.dialogKey == dialogKey ) + { + item.killstreakIds[item.killstreakIds.size] = killstreakId; + return; + } + } + + // Flag the new item to play multiple if it's playing up against the current dialog + if ( self.leaderDialogQueue.size == 0 && + isdefined( self.currentLeaderDialog ) && + self.currentLeaderDialog.dialogKey == dialogKey ) + { + // We're already playing a multiple so just skip this new one + if ( self.currentLeaderDialog.playMultiple === true ) + { + return; + } + + playMultiple = true; + } + } + + newItem = SpawnStruct(); + + newItem.priority = dialogkey_priority( dialogKey ); + newItem.dialogKey = dialogKey; + newItem.multipleDialogKey = level.multipleDialogKeys[dialogKey]; + newItem.playMultiple = playMultiple; + newItem.objectiveKey = objectiveKey; + + if( isdefined( killstreakId ) ) + { + newItem.killstreakIds = []; + newItem.killstreakIds[0] = killstreakId; + } + + newItem.dialogBufferKey = dialogBufferKey; + // TODO: Add Expiration time + + itemInserted = false; + + if ( isdefined( newItem.priority ) ) + { + for ( i = 0; i < self.leaderDialogQueue.size; i++ ) + { + // Lower number = higher priority + if ( isdefined( self.leaderDialogQueue[i].priority ) && self.leaderDialogQueue[i].priority <= newItem.priority ) + { + continue; + } + + ArrayInsert( self.leaderDialogQueue, newItem, i ); + itemInserted = true; + break; + } + } + + if ( !itemInserted ) + { + self.leaderDialogQueue[self.leaderDialogQueue.size] = newItem; + } + + if ( isdefined( self.currentLeaderDialog ) ) + { + return; + } + + self thread play_next_leader_dialog(); +} + +function play_next_leader_dialog() +{ + self endon( "disconnect" ); + self endon( "flush_dialog" ); + level endon( "game_ended" ); + + if ( self.leaderDialogQueue.size == 0 ) + { + self.currentLeaderDialog = undefined; + return; + } + + nextDialog = self.leaderDialogQueue[0]; + + ArrayRemoveIndex( self.leaderDialogQueue, 0 ); + + dialogKey = nextDialog.dialogKey; + + if ( isdefined( nextDialog.killstreakIds ) ) + { + triggeredCount = 0; + + foreach( killstreakId in nextDialog.killstreakIds ) + { + if( isdefined ( level.killstreaks_triggered[ killstreakId ] ) ) + { + triggeredCount++; + } + } + + // No active scorestreaks for this item, don't play it + if ( triggeredCount == 0 ) + { + self thread play_next_leader_dialog(); + return; + } + else if ( triggeredCount > 1 || nextDialog.playMultiple === true ) + { + // Found more than one, or we want to play the multiple for some reason + if( isdefined( level.multipleDialogKeys[ dialogKey ] ) ) + { + dialogKey = level.multipleDialogKeys[ dialogKey ]; + } + } + } + + dialogAlias = self get_commander_dialog_alias( dialogKey ); + + if ( !isdefined( dialogAlias ) ) + { + self thread play_next_leader_dialog(); + return; + } + + self playLocalSound( dialogAlias ); + + nextDialog.playTime = GetTime(); + self.currentLeaderDialog = nextDialog; + + // Get the provided buffer, default buffer, or 0 + dialogBuffer = battlechatter::mpdialog_value( nextDialog.dialogBufferKey, battlechatter::mpdialog_value( "commanderDialogBuffer", 0 ) ); + + self thread wait_next_leader_dialog( dialogBuffer ); +} + +function wait_next_leader_dialog( dialogBuffer ) +{ + self endon( "disconnect" ); + self endon( "flush_dialog" ); + level endon( "game_ended" ); + + wait ( dialogBuffer ); + + self thread play_next_leader_dialog(); +} + +function dialogkey_priority( dialogKey ) +{ + switch( dialogKey ) + { + // Lethal enemy killstreaks + case "enemyRemoteMissile": + case "enemyRemoteMissileMultiple": + case "enemyPlaneMortar": + case "enemyPlaneMortarMultiple": + case "enemyPlaneMortarUsed": + case "enemyDroneStrike": + case "enemyDroneStrikeMultiple": + case "enemyHelicopterGunner": + case "enemyHelicopterGunnerMultiple": + case "enemyRaps": + case "enemyRapsMultiple": + case "enemyCombatRobot": + case "enemyCombatRobotMultiple": + case "enemyHelicopter": + case "enemyHelicopterMultiple": + case "enemySentinel": + case "enemySentinelMultiple": + case "enemyAiTank": + case "enemyAiTankMultiple": + case "enemyDart": + case "enemyDartMultiple": + case "enemyTurret": + case "enemyTurretMultiple": + case "enemyMicrowaveTurret": + case "enemyMicrowaveTurretMultiple": + case "enemyRcBomb": + case "enemyRcBombMultiple": + return 1; + // Score announcments + case "gameLosing": + case "gameWinning": + case "gameLeadLost": + case "gameLeadTaken": + case "nearWinning": + case "nearDrawing": + case "nearLosing": + case "roundEncourageLastPlayer": + return 1; + // Game Events + case "ctfFriendlyFlagCaptured": + case "ctfFriendlyFlagDropped": + case "ctfFriendlyFlagReturned": + case "ctfFriendlyFlagTaken": + case "ctfEnemyFlagCaptured": + case "ctfEnemyFlagDropped": + case "ctfEnemyFlagReturned": + case "ctfEnemyFlagTaken": + case "domFriendlySecuringA": + case "domFriendlySecuringB": + case "domFriendlySecuringC": + case "domFriendlySecuredA": + case "domFriendlySecuredB": + case "domFriendlySecuredC": + case "domFriendlySecuredAll": + case "domEnemyHasA": + case "domEnemyHasB": + case "domEnemyHasC": + case "domEnemySecuringA": + case "domEnemySecuringB": + case "domEnemySecuringC": + case "domEnemySecuredA": + case "domEnemySecuredB": + case "domEnemySecuredC": + case "kothCaptured": + case "kothContested": + case "kothLocated": + case "kothLost": + case "kothOnline": + case "kothSecured": + case "sfgStartAttack": + case "sfgStartDefend": + case "sfgStartTow": + case "sfgStartHrAttack": + case "sfgStartHrDefend": + case "sfgRobotDisabledAttacker": + case "sfgRobotDisabledDefender": + case "sfgRobotUnderFire": + case "sfgRobotUnderFireNeutral": + case "sfgRobotNeedReboot": + case "sfgRobotRebootedAttacker": + case "sfgRobotRebootedDefender": + case "sfgRobotRebootedTowAttacker": + case "sfgRobotRebootedTowDefender": + case "sfgRobotCloseAttacker": + case "sfgRobotCloseDefender": + case "sfgTheyReturn": + case "sfgWeReturn": + case "bombDefused": + case "bombPlanted": + case "bombFriendlyDropped": + case "bombFriendlyTaken": + case "bombEnemyTaken": + case "hubOnline": + case "hubOffline": + case "hubsOnline": + case "hubsOffline": + case "hubMoved": + case "hubsMoved": + case "uplOrders": + case "uplWeTake": + case "uplTheyTake": + case "uplTheyDrop": + case "uplWeDrop": + case "uplWeUplink": + case "uplTheyUplink": + case "uplWeUplinkRemote": + case "uplTheyUplinkRemote": + case "uplTransferred": + case "uplReset": + return 1; + } + + return undefined; +} + +// Commander callbacks + +function play_equipment_destroyed_on_player( ) +{ + self play_taacom_dialog( "equipmentDestroyed" ); +} + +function play_equipment_hacked_on_player( ) +{ + self play_taacom_dialog( "equipmentHacked" ); +} + +// Alias Lookups + +function get_commander_dialog_alias( dialogKey ) +{ + if ( !isdefined( self.pers["mpcommander"] ) ) + { + return undefined; + } + + commanderBundle = struct::get_script_bundle( "mpdialog_commander", self.pers["mpcommander"] ); + + return get_dialog_bundle_alias( commanderBundle, dialogKey ); +} + +function get_dialog_bundle_alias( dialogBundle, dialogKey ) +{ + if ( !isdefined( dialogBundle ) || !isdefined( dialogKey ) ) + { + return undefined; + } + + dialogAlias = GetStructField( dialogBundle, dialogKey ); + + if ( !isdefined( dialogAlias ) ) + { + return; + } + + voicePrefix = GetStructField( dialogBundle, "voiceprefix" ); + + if ( isdefined( voicePrefix ) ) + { + dialogAlias = voicePrefix + dialogAlias; + } + + return dialogAlias; +} + +function is_team_winning( checkTeam ) +{ + score = game["teamScores"][checkTeam]; + + foreach ( team in level.teams ) + { + if ( team != checkTeam ) + { + if ( game["teamScores"][team] >= score ) + { + return false; + } + } + } + + return true; +} + +function announce_team_is_winning() +{ + foreach ( team in level.teams ) + { + if ( is_team_winning( team ) ) + { + leader_dialog( "gameWinning", team ); + leader_dialog_for_other_teams( "gameLosing", team ); + return true; + } + } + + return false; +} + + +function play_2d_on_team( alias, team ) +{ + assert( isdefined( level.players ) ); + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if ( isdefined( player.pers["team"] ) && (player.pers["team"] == team ) ) + { + player playLocalSound( alias ); + } + } +} + +function get_round_switch_dialog( switchType ) +{ + switch( switchType ) + { + case "halftime": + return "roundHalftime"; + case "overtime": + return "roundOvertime"; + default: + return "roundSwitchSides"; + } +} +//Eckert - does a 2 stage sound fade out during and after AAR +function post_match_snapshot_watcher() +{ + level waittill ( "game_ended" ); + level util::clientNotify( "pm" ); + level waittill ( "sfade" ); + level util::clientNotify( "pmf" ); +} + +function announcerController() +{ + level endon ( "game_ended" ); + + level waittill ( "match_ending_soon" ); + + if ( util::isLastRound() || util::isOneRound() ) + { + if( level.teamBased ) + { + if( !announce_team_is_winning() ) + { + leader_dialog( "min_draw" ); + } + } + + level waittill ( "match_ending_very_soon" ); + + foreach ( team in level.teams ) + { + leader_dialog( "roundTimeWarning", team, undefined, undefined ); + } + } + else + { + level waittill ( "match_ending_vox" ); + + leader_dialog( "roundTimeWarning" ); + } +} + +/*------------------------------------------------------------------------------------------------------ + *------------------------------------------------------------------------------------------------------ + *----------------------------------------MUSIC SYSTEM SCRIPTS------------------------------------------ + *------------------------------------------------------------------------------------------------------ + *------------------------------------------------------------------------------------------------------ + */ + +/* + * spawnPreLoop + * spawnPreRise + * spawnFull + * spawnShort + * RandomOneshot + * timeOut + * roundEnd + * roundSwitch + * matchWin + * matchLose + * matchDraw + * silent* + * endgame + */ + +function sndMusicFunctions() +{ + level thread sndMusicTimesOut(); + level thread sndMusicHalfway(); + level thread sndMusicTimeLimitWatcher(); + level thread sndMusicUnlock(); +} +function sndMusicSetRandomizer() +{ + if( game[ "roundsplayed" ] == 0 ) + { + game[ "musicSet" ] = randomintrange(1,8); + if( game[ "musicSet" ] <= 9 ) + { + game[ "musicSet" ] = "0"+game[ "musicSet" ]; + } + game[ "musicSet" ] = "_"+game[ "musicSet" ]; + + if( ( isdefined( level.freerun ) && level.freerun )) + { + game[ "musicSet" ] = ""; + } + } +} + +function sndMusicUnlock() +{ + level waittill( "game_ended" ); + + unlockName = undefined; + + switch(game[ "musicSet" ]) + { + case "_01": + unlockName = "mus_dystopia_intro"; + break; + case "_02": + unlockName = "mus_filter_intro"; + break; + case "_03": + unlockName = "mus_immersion_intro"; + break; + case "_04": + unlockName = "mus_ruin_intro"; + break; + case "_05": + unlockName = "mus_cod_bites_intro"; + break; + } + + if( isdefined( unlockName ) ) + { + level thread audio::unlockFrontendMusic(unlockName); + } +} + +function sndMusicTimesOut() +{ + level endon( "game_ended" ); + level endon( "musicEndingOverride" ); + + level waittill ( "match_ending_very_soon" ); + + if( isdefined( level.gametype ) && ( level.gametype == "sd" || level.gametype == "prop" ) ) + level thread set_music_on_team( "timeOutQuiet" ); + else + level thread set_music_on_team( "timeOut" ); +} +function sndMusicHalfway() +{ + level endon( "game_ended" ); + level endon( "match_ending_soon" ); + level endon( "match_ending_very_soon" ); + + level waittill( "sndMusicHalfway" ); + + level thread set_music_on_team( "underscore" ); +} +function sndMusicTimeLimitWatcher() +{ + level endon( "game_ended" ); + level endon( "match_ending_soon" ); + level endon( "match_ending_very_soon" ); + level endon( "sndMusicHalfway" ); + + if( !isdefined( level.timeLimit ) || level.timeLimit == 0 ) + return; + + halfway = (level.timeLimit*60)*.5; + + while(1) + { + timeLeft = globallogic_utils::getTimeRemaining() / 1000; + if( timeLeft <= halfway ) + { + level notify( "sndMusicHalfway" ); + return; + } + wait(2); + } +} + +function set_music_on_team( state, team = "both", wait_time = 0, save_state = false, return_state = false ) +{ + // Assert if there are no players + assert( isdefined( level.players ) ); + + foreach( player in level.players ) + { + if ( team == "both" ) + { + player thread set_music_on_player( state, wait_time, save_state, return_state ); + } + else if ( isdefined( player.pers["team"] ) && (player.pers["team"] == team ) ) + { + player thread set_music_on_player( state, wait_time, save_state, return_state ); + } + } +} +function set_music_on_player( state, wait_time = 0, save_state = false, return_state = false ) +{ + self endon( "disconnect" ); + + assert( isplayer (self) ); + + if ( !isdefined( state ) ) + { + return; + } + + if( !isdefined( game[ "musicSet" ] ) ) + { + return; + } + + music::setmusicstate( state+game[ "musicSet" ], self ); +} +function set_music_global( state, wait_time = 0, save_state = false, return_state = false ) +{ + if ( !isdefined( state ) ) + { + return; + } + + if( !isdefined( game[ "musicSet" ] ) ) + { + return; + } + + music::setmusicstate( state+game[ "musicSet" ] ); +} \ No newline at end of file diff --git a/mp/gametypes/_globallogic_defaults.gsc b/mp/gametypes/_globallogic_defaults.gsc new file mode 100644 index 0000000..d375b6f --- /dev/null +++ b/mp/gametypes/_globallogic_defaults.gsc @@ -0,0 +1,355 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\killstreaks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\rank_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_killstreaks; + +#namespace globallogic_defaults; + +function getWinningTeamFromLoser( losing_team ) +{ + if ( level.multiTeam ) + { + return "tie"; + } + return util::getotherteam(losing_team); +} + +// when a team leaves completely, that team forfeited, team left wins round, ends game +function default_onForfeit( team ) +{ + level.gameForfeited= true; + + level notify ( "forfeit in progress" ); //ends all other forfeit threads attempting to run + level endon( "forfeit in progress" ); //end if another forfeit thread is running + level endon( "abort forfeit" ); //end if the team is no longer in forfeit status + + forfeit_delay = 20.0; //forfeit wait, for switching teams and such + + announcement( game["strings"]["opponent_forfeiting_in"], forfeit_delay, 0 ); + wait (10.0); + announcement( game["strings"]["opponent_forfeiting_in"], 10.0, 0 ); + wait (10.0); + + endReason = &""; + if ( level.multiTeam ) + { + SetDvar( "ui_text_endreason", game["strings"]["other_teams_forfeited"] ); + endReason = game["strings"]["other_teams_forfeited"]; + winner = team; + } + else if ( !isdefined( team ) ) + { + SetDvar( "ui_text_endreason", game["strings"]["players_forfeited"] ); + endReason = game["strings"]["players_forfeited"]; + winner = level.players[0]; + } + else if ( isdefined( level.teams[team] ) ) + { + endReason = game["strings"][team+"_forfeited"]; + SetDvar( "ui_text_endreason", endReason ); + winner = getWinningTeamFromLoser( team ); + } + else + { + //shouldn't get here + assert( isdefined( team ), "Forfeited team is not defined" ); + assert( 0, "Forfeited team " + team + " is not allies or axis" ); + winner = "tie"; + } + //exit game, last round, no matter if round limit reached or not + level.forcedEnd = true; + /# + if ( isPlayer( winner ) ) + print( "forfeit, win: " + winner getXuid() + "(" + winner.name + ")" ); + else + globallogic_utils::logTeamWinString( "forfeit", winner ); + #/ + thread globallogic::endGame( winner, endReason ); +} + + +function default_onDeadEvent( team ) +{ + if ( isdefined( level.teams[team] ) ) + { + eliminatedString = game["strings"][team + "_eliminated"]; + iPrintLn( eliminatedString ); + //makeDvarServerInfo( "ui_text_endreason", eliminatedString ); + SetDvar( "ui_text_endreason", eliminatedString ); + + winner = getWinningTeamFromLoser( team ); + globallogic_utils::logTeamWinString( "team eliminated", winner ); + + thread globallogic::endGame( winner, eliminatedString ); + } + else + { + //makeDvarServerInfo( "ui_text_endreason", game["strings"]["tie"] ); + SetDvar( "ui_text_endreason", game["strings"]["tie"] ); + + globallogic_utils::logTeamWinString( "tie" ); + + if ( level.teamBased ) + thread globallogic::endGame( "tie", game["strings"]["tie"] ); + else + thread globallogic::endGame( undefined, game["strings"]["tie"] ); + } +} + +function default_onLastTeamAliveEvent( team ) +{ + if ( isdefined( level.teams[team] ) ) + { + eliminatedString = game["strings"]["enemies_eliminated"]; + iPrintLn( eliminatedString ); + //makeDvarServerInfo( "ui_text_endreason", eliminatedString ); + SetDvar( "ui_text_endreason", eliminatedString ); + + winner = globallogic::determineTeamWinnerByGameStat( "teamScores" ); + globallogic_utils::logTeamWinString( "team eliminated", winner ); + + thread globallogic::endGame( winner, eliminatedString ); + } + else + { + //makeDvarServerInfo( "ui_text_endreason", game["strings"]["tie"] ); + SetDvar( "ui_text_endreason", game["strings"]["tie"] ); + + globallogic_utils::logTeamWinString( "tie" ); + + if ( level.teamBased ) + thread globallogic::endGame( "tie", game["strings"]["tie"] ); + else + thread globallogic::endGame( undefined, game["strings"]["tie"] ); + } +} + +function default_onAliveCountChange( team ) +{ +} + +function default_onRoundEndGame( winner ) +{ + return winner; +} + +// T8 - We should get rid of the return from onRoundEndGame in favor of this +function default_determineWinner( roundWinner ) +{ + if ( isdefined( game["overtime_round"] ) ) + { + if ( ( isdefined( level.doubleOvertime ) && level.doubleOvertime ) && + isdefined( roundWinner ) && + roundWinner != "tie" ) + { + return roundWinner; + } + + return globallogic::determineTeamWinnerByGameStat( "overtimeroundswon" ); + } + + if ( level.scoreRoundWinBased ) + { + winner = globallogic::determineTeamWinnerByGameStat( "roundswon" ); + } + else + { + winner = globallogic::determineTeamWinnerByTeamScore(); + } + + return winner; +} + +function default_onOneLeftEvent( team ) +{ + if ( !level.teamBased ) + { + winner = globallogic_score::getHighestScoringPlayer(); + /# + if ( isdefined( winner ) ) + print( "last one alive, win: " + winner.name ); + else + print( "last one alive, win: unknown" ); + #/ + thread globallogic::endGame( winner, &"MP_ENEMIES_ELIMINATED" ); + } + else + { + for ( index = 0; index < level.players.size; index++ ) + { + player = level.players[index]; + + if ( !isAlive( player ) ) + continue; + + if ( !isdefined( player.pers["team"] ) || player.pers["team"] != team ) + continue; + +// player globallogic_audio::leader_dialog_on_player( "last_alive" ); + player globallogic_audio::leader_dialog_on_player( "sudden_death" ); + } + } +} + + +function default_onTimeLimit() +{ + winner = undefined; + + if ( level.teamBased ) + { + winner = globallogic::determineTeamWinnerByGameStat( "teamScores" ); + + globallogic_utils::logTeamWinString( "time limit", winner ); + } + else + { + winner = globallogic_score::getHighestScoringPlayer(); + /# + if ( isdefined( winner ) ) + print( "time limit, win: " + winner.name ); + else + print( "time limit, tie" ); + #/ + } + + // i think these two lines are obsolete + //makeDvarServerInfo( "ui_text_endreason", game["strings"]["time_limit_reached"] ); + SetDvar( "ui_text_endreason", game["strings"]["time_limit_reached"] ); + + thread globallogic::endGame( winner, game["strings"]["time_limit_reached"] ); +} + +function default_onScoreLimit() +{ + if ( !level.endGameOnScoreLimit ) + return false; + + winner = undefined; + + if ( level.teamBased ) + { + winner = globallogic::determineTeamWinnerByGameStat( "teamScores" ); + + globallogic_utils::logTeamWinString( "scorelimit", winner ); + } + else + { + winner = globallogic_score::getHighestScoringPlayer(); + /# + if ( isdefined( winner ) ) + print( "scorelimit, win: " + winner.name ); + else + print( "scorelimit, tie" ); + #/ + } + + //makeDvarServerInfo( "ui_text_endreason", game["strings"]["score_limit_reached"] ); + SetDvar( "ui_text_endreason", game["strings"]["score_limit_reached"] ); + + thread globallogic::endGame( winner, game["strings"]["score_limit_reached"] ); + return true; +} + + +function default_onRoundScoreLimit() +{ + winner = undefined; + + if ( level.teamBased ) + { + winner = globallogic::determineTeamWinnerByGameStat( "teamScores" ); + + globallogic_utils::logTeamWinString( "roundscorelimit", winner ); + } + else + { + winner = globallogic_score::getHighestScoringPlayer(); + /# + if ( isdefined( winner ) ) + print( "roundscorelimit, win: " + winner.name ); + else + print( "roundscorelimit, tie" ); + #/ + } + + //makeDvarServerInfo( "ui_text_endreason", game["strings"]["round_score_limit_reached"] ); + SetDvar( "ui_text_endreason", game["strings"]["round_score_limit_reached"] ); + + thread globallogic::endGame( winner, game["strings"]["round_score_limit_reached"] ); + return true; +} + + +function default_onSpawnSpectator( origin, angles) +{ + if( isdefined( origin ) && isdefined( angles ) ) + { + self spawn(origin, angles); + return; + } + + spawnpoints = spawnlogic::_get_spawnpoint_array( "mp_global_intermission" ); + assert( spawnpoints.size, "There are no mp_global_intermission spawn points in the map. There must be at least one." ); + spawnpoint = spawnlogic::get_spawnpoint_random(spawnpoints); + + self spawn(spawnpoint.origin, spawnpoint.angles); +} + +function default_onSpawnIntermission( endGame ) +{ + if ( ( isdefined( endGame ) && endGame ) ) + { + // The client camera is handled in client script via an xcam + return; + } + + spawnpoint = spawnlogic::get_random_intermission_point(); + + if( isdefined( spawnpoint ) ) + { + self spawn( spawnpoint.origin, spawnpoint.angles ); + } + else + { + /# + util::error( "NO mp_global_intermission SPAWNPOINTS IN MAP" ); + #/ + } +} + +function default_getTimeLimit() +{ + return math::clamp( GetGametypeSetting( "timeLimit" ), level.timeLimitMin, level.timeLimitMax ); +} + +function default_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_penalty = 1; + + if ( killstreaks::is_killstreak_weapon( weapon ) ) + { + teamkill_penalty *= killstreaks::get_killstreak_team_kill_penalty_scale( weapon ); + } + + return teamkill_penalty; +} + +function default_getTeamKillScore( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + return rank::getScoreInfoValue( "team_kill" ); +} + + diff --git a/mp/gametypes/_globallogic_player.gsc b/mp/gametypes/_globallogic_player.gsc new file mode 100644 index 0000000..ed7d6f6 --- /dev/null +++ b/mp/gametypes/_globallogic_player.gsc @@ -0,0 +1,4500 @@ +#using scripts\shared\abilities\_ability_player; +#using scripts\shared\abilities\_ability_power; +#using scripts\shared\abilities\_ability_util; +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_message_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\medals_shared; +#using scripts\shared\persistence_shared; +#using scripts\shared\player_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons_shared; +#using scripts\shared\weapons\_weapon_utils; +#using scripts\shared\weapons\_weapons; +#using scripts\shared\ai\systems\gib; + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_deathicons; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_globallogic_ui; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_globallogic_vehicle; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_hud_message; +#using scripts\mp\gametypes\_killcam; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\_spectating; +#using scripts\mp\gametypes\_weapons; + +#using scripts\mp\_armor; +#using scripts\mp\_behavior_tracker; +#using scripts\shared\_burnplayer; +#using scripts\mp\_challenges; +#using scripts\mp\_contracts; +#using scripts\mp\_gamerep; +#using scripts\mp\_laststand; +#using scripts\mp\_teamops; +#using scripts\mp\_util; +#using scripts\mp\_vehicle; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\teams\_teams; + + + + + + + + + + + +#namespace globallogic_player; + +function on_joined_team() +{ + // for cod caster update the top scorers + if ( !level.rankedMatch && !level.teambased ) + { + level thread update_ffa_top_scorers(); + } +} + +function freezePlayerForRoundEnd() +{ + self util::clearLowerMessage(); + + self closeInGameMenu(); + + self util::freeze_player_controls( true ); + + if( !SessionModeIsZombiesGame() ) + { + currentWeapon = self GetCurrentWeapon(); + if ( killstreaks::is_killstreak_weapon( currentWeapon ) && !currentWeapon.isCarriedKillstreak ) + self takeWeapon( currentWeapon ); + } + + //self util::_disableWeapon(); +} + +function ArrayToString( inputArray ) +{ + targetString = ""; + for (i=0; i < inputArray.size; i++) + { + targetString += inputArray[i]; + if (i != inputArray.size-1) + targetString += ","; + } + return targetString; +} + + +function recordEndGameComScoreEventForPlayer( player, result ) +{ + lpselfnum = player getEntityNumber(); + lpXuid = player getxuid(true); + bbPrint( "global_leave", "name %s client %s xuid %s", player.name, lpselfnum, lpXuid ); + + weeklyAContractId = 0; + weeklyAContractTarget = 0; + weeklyAContractCurrent = 0; + weeklyAContractCompleted = 0; + + weeklyBContractId = 0; + weeklyBContractTarget = 0; + weeklyBContractCurrent = 0; + weeklyBContractCompleted = 0; + + dailyContractId = 0; + dailyContractTarget = 0; + dailyContractCurrent = 0; + dailyContractCompleted = 0; + + specialContractId = 0; + specialContractTarget = 0; + specialContractCurent = 0; + specialContractCompleted = 0; + + if ( player util::is_bot() ) + { + currXP = 0; + prevXP = 0; + } + else + { + currXP = player rank::getRankXpStat(); + prevXP = player.pers["rankxp"]; + + if ( globallogic_score::canUpdateWeaponContractStats( player ) ) + { + specialContractId = 1; // this is a one-off contract. Use any non-zero value to indicate that it is populated. + specialContractTarget = GetDvarInt( "weapon_contract_target_value", 100 ); + specialContractCurent = player GetDStat( "weaponContractData", "currentValue" ); + if ( (isdefined(player GetDStat( "weaponContractData", "completeTimestamp" ))?player GetDStat( "weaponContractData", "completeTimestamp" ):0) != 0 ) + { + specialContractCompleted = 1; + } + } + + if ( player contracts::can_process_contracts() ) + { + contractId = player contracts::get_contract_stat( 0, "index" ); + if ( player contracts::is_contract_active( contractId ) ) + { + weeklyAContractId = contractId; + weeklyAContractTarget = player.pers[ "contracts" ][ weeklyAContractId ].target_value; + weeklyAContractCurrent = player contracts::get_contract_stat( 0, "progress" ); + weeklyAContractCompleted = player contracts::get_contract_stat( 0, "award_given" ); + } + + contractId = player contracts::get_contract_stat( 1, "index" ); + if ( player contracts::is_contract_active( contractId ) ) + { + weeklyBContractId = contractId; + weeklyBContractTarget = player.pers["contracts" ][ weeklyBContractId ].target_value; + weeklyBContractCurrent = player contracts::get_contract_stat( 1, "progress" ); + weeklyBContractCompleted = player contracts::get_contract_stat( 1, "award_given" ); + } + + contractId = player contracts::get_contract_stat( 2, "index" ); + if ( player contracts::is_contract_active( contractId ) ) + { + dailyContractId = contractId; + dailyContractTarget = player.pers[ "contracts" ][ dailyContractId ].target_value; + dailyContractCurrent = player contracts::get_contract_stat( 2, "progress" ); + dailyContractCompleted = player contracts::get_contract_stat( 2, "award_given" ); + } + } + } + + if ( !isdefined( prevXP ) ) + { + // most likely the player never fully connected in the first place + // it is possible to specify a default value for prevXP, + // however, the call to RecordComScoreEvent() will SRE as most of player.pers[] is missing + return; + } + + resultStr = result; + if ( isDefined(player.team) && result == player.team ) + resultStr ="win"; + else if ( result == "allies" || result == "axis" ) + resultStr = "lose"; + + xpEarned = currXP - prevXP; + + perkStr = ArrayToString( player GetPerks() ); + + primaryWeaponName = ""; + primaryWeaponAttachStr=""; + secondaryWeaponName = ""; + secondaryWeaponAttachStr=""; + grenadePrimaryName = ""; + grenadeSecondaryName = ""; + + + + if (isdefined( player.primaryLoadoutWeapon )) + { + primaryWeaponName = player.primaryLoadoutWeapon.name; + primaryWeaponAttachStr = ArrayToString( GetArrayKeys(player.primaryLoadoutWeapon.attachments) ); + } + if (isdefined( player.secondaryLoadoutWeapon )) + { + secondaryWeaponName = player.secondaryLoadoutWeapon.name; + secondaryWeaponAttachStr = ArrayToString( GetArrayKeys(player.secondaryLoadoutWeapon.attachments) ); + } + + if (isdefined( player.grenadeTypePrimary )) + grenadePrimaryName = player.grenadeTypePrimary.name; + if (isdefined( player.grenadeTypeSecondary )) + grenadeSecondaryName = player.grenadeTypeSecondary.name; + + killStreakStr = ArrayToString( player.killstreak ); + + gameLength = game["timepassed"] / 1000; + timePlayed = player globallogic::getTotalTimePlayed( gameLength ); + + totalKills = 0; + totalHits = 0; + totalDeaths = 0; + totalWins = 0; + totalXP = 0; + + if ( level.gametype != "fr" ) + { + totalKills = player GetDStat( "playerstatslist", "kills", "statValue" ); + totalHits = player GetDStat( "playerstatslist", "hits", "statValue" ); + totalDeaths = player GetDStat( "playerstatslist", "deaths", "statValue" ); + totalWins = player GetDStat( "playerstatslist", "wins", "statValue" ); + totalXP = player GetDStat( "playerstatslist", "rankxp", "statValue" ); + } + + killCount = 0; + hitCount = 0; + + if( level.mpCustomMatch ) + { + killCount = player.kills; + hitCount = player.shotshit; + } + else + { + if ( isdefined( player.startKills ) ) + killCount = totalKills - player.startKills; + + if ( isdefined( player.startHits ) ) + hitCount = totalHits - player.startHits; + } + + bestScore = "0"; + if ( isdefined( player.pers["lastHighestScore"] ) && player.score > player.pers["lastHighestScore"] ) + bestScore = "1"; + + bestKills = "0"; + if ( isdefined( player.pers["lastHighestKills"] ) && killCount > player.pers["lastHighestKills"] ) + bestKills = "1"; + + totalMatchShots = 0; + if ( isdefined( player.totalMatchShots) ) + totalMatchShots = player.totalMatchShots; + + deaths = player.deaths; + if (deaths == 0) + deaths = 1; + kdRatio = player.kills*1000/deaths; + bestKDRatio = "0"; + if ( isdefined( player.pers["lastHighestKDRatio"] ) && kdRatio > player.pers["lastHighestKDRatio"] ) + bestKDRatio = "1"; + + showcaseWeapon = player GetPlayerShowcaseWeapon(); + + RecordComScoreEvent( "end_match", + "match_id", getDemoFileID(), + "game_variant", "mp", + "game_mode", level.gametype, + "private_match", SessionModeIsPrivate(), + "esports_flag", level.leagueMatch, + "ranked_play_flag", level.arenaMatch, + "league_team_id", player getLeagueTeamID(), + "game_map", GetDvarString( "mapname" ), + "player_xuid", player getxuid(true), + "player_ip", player getipaddress(), + "match_kills", killCount, + "match_deaths", player.deaths, + "match_xp", xpEarned, + "match_score", player.score, + "match_streak", player.pers["best_kill_streak"], + "match_captures", player.pers["captures"], + "match_defends", player.pers["defends"], + "match_headshots", player.pers["headshots"], + "match_longshots", player.pers["longshots"], + "match_objtime", player.pers["objtime"], + "match_plants", player.pers["plants"], + "match_defuses", player.pers["defuses"], + "match_throws", player.pers["throws"], + "match_carries", player.pers["carries"], + "match_returns", player.pers["returns"], + "prestige_max", player.pers["plevel"], + "level_max", player.pers["rank"], + "match_result", resultStr, + "match_duration", timePlayed, + "match_shots", totalMatchShots, + "match_hits", hitCount, + "player_gender", player GetPlayerGenderType( CurrentSessionMode() ), + "specialist_kills", player.heroweaponKillCount, + "specialist_used", player GetMpDialogName(), + "season_pass_owned", player HasSeasonPass(0), + "loadout_perks", perkStr, + "loadout_lethal", grenadePrimaryName, + "loadout_tactical", grenadeSecondaryName, + "loadout_scorestreaks", killStreakStr, + "loadout_primary_weapon", primaryWeaponName, + "loadout_secondary_weapon", secondaryWeaponName, + "dlc_owned", player GetDLCAvailable(), + "loadout_primary_attachments", primaryWeaponAttachStr, + "loadout_secondary_attachments",secondaryWeaponAttachStr, + "best_score", bestScore, + "best_kills", bestKills, + "best_kd", bestKDRatio, + "total_kills", totalKills, + "total_deaths", totalDeaths, + "total_wins", totalWins, + "total_xp", totalXP, + "daily_contract_id", dailyContractId, + "daily_contract_target", dailyContractTarget, + "daily_contract_current", dailyContractCurrent, + "daily_contract_completed", dailyContractCompleted, + "weeklyA_contract_id", weeklyAContractId, + "weeklyA_contract_target", weeklyAContractTarget, + "weeklyA_contract_current", weeklyAContractCurrent, + "weeklyA_contract_completed", weeklyAContractCompleted, + "weeklyB_contract_id", weeklyBContractId, + "weeklyB_contract_target", weeklyBContractTarget, + "weeklyB_contract_current", weeklyBContractCurrent, + "weeklyB_contract_completed", weeklyBContractCompleted, + "special_contract_id ", specialContractId, + "special_contract_target", specialContractTarget, + "special_contract_curent", specialContractCurent, + "special_contract_completed", specialContractCompleted, + "specialist_power", player.heroabilityname, + "specialist_head", player GetCharacterHelmetModel(), + "specialist_body", player GetCharacterBodyModel(), + "specialist_taunt", player GetPlayerSelectedTauntName( 0 ), + "specialist_goodgame", player GetPlayerSelectedGestureName( 0 ), + "specialist_threaten", player GetPlayerSelectedGestureName( 1 ), + "specialist_boast", player GetPlayerSelectedGestureName( 2 ), + "specialist_showcase", showcaseWeapon.weapon.name + ); +} + +function player_monitor_travel_dist() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + waitTime = 1; + minimumMoveDistance = 16; + + //Ignore data immediatly after spawn + wait 4; + + prevpos = self.origin; + positionPTM = self.origin; + while( 1 ) + { + wait waitTime; + + if (self util::isUsingRemote()) + { + self waittill ("stopped_using_remote"); + prevpos = self.origin; + positionPTM = self.origin; + continue; + } + + distance = distance( self.origin, prevpos ); + self.pers["total_distance_travelled"] += distance; + self.pers["movement_Update_Count"]++; + prevpos = self.origin; + + if ((self.pers["movement_Update_Count"] % 5) == 0) + { + distanceMoving = distance(self.origin, positionPTM); + positionPTM = self.origin; + if ( distanceMoving > minimumMoveDistance ) + { + self.pers["num_speeds_when_moving_entries"]++; + self.pers["total_speeds_when_moving"] += ( distanceMoving / waitTime ); + self.pers["time_played_moving"] += waitTime; + } + } + + + } +} + +function record_special_move_data_for_life( killer ) +{ + // safe to assume fields on self exist? + if( !isDefined( self.lastSwimmingStartTime) || !isDefined( self.lastWallRunStartTime) || !isDefined( self.lastSlideStartTime) || !isDefined( self.lastDoubleJumpStartTime) || + !isDefined( self.timeSpentSwimmingInLife) || !isDefined( self.timeSpentWallRunningInLife) || !isDefined( self.numberOfDoubleJumpsInLife) || !isDefined( self.numberOfSlidesInLife) ) + { + /# + println( "record_special_move_data_for_life - fields on self not defined!"); + #/ + return; + } + + if( isDefined(killer) ) + { + if( !isDefined( killer.lastSwimmingStartTime) || !isDefined( killer.lastWallRunStartTime) || !isDefined( killer.lastSlideStartTime) || !isDefined( killer.lastDoubleJumpStartTime) ) + { + /# + println( "record_special_move_data_for_life - fields one killer not defined!"); + #/ + return; + } + matchRecordLogSpecialMoveDataForLife( self, self.lastSwimmingStartTime, self.lastWallRunStartTime, self.lastSlideStartTime, self.lastDoubleJumpStartTime, + self.timeSpentSwimmingInLife, self.timeSpentWallRunningInLife, self.numberOfDoubleJumpsInLife, self.numberOfSlidesInLife, + killer, killer.lastSwimmingStartTime, killer.lastWallRunStartTime, killer.lastSlideStartTime, killer.lastDoubleJumpStartTime ); + } + else + { + matchRecordLogSpecialMoveDataForLife( self, self.lastSwimmingStartTime, self.lastWallRunStartTime, self.lastSlideStartTime, self.lastDoubleJumpStartTime, + self.timeSpentSwimmingInLife, self.timeSpentWallRunningInLife, self.numberOfDoubleJumpsInLife, self.numberOfSlidesInLife ); + } + +} + +function player_monitor_wall_run() +{ + self endon ( "disconnect" ); + + // make sure no other stray threads running on this dude + self notify("stop_player_monitor_wall_run"); + self endon("stop_player_monitor_wall_run"); + + self.lastWallRunStartTime = 0; + self.timeSpentWallRunningInLife = 0; + while ( true ) + { + notification = self util::waittill_any_return( "wallrun_begin", "death", "disconnect", "stop_player_monitor_wall_run" ); + if( notification == "death" ) + break; // end thread + + self.lastWallRunStartTime = getTime(); + + notification = self util::waittill_any_return( "wallrun_end", "death", "disconnect", "stop_player_monitor_wall_run" ); + + self.timeSpentWallRunningInLife += (getTime() - self.lastWallRunStartTime); + + if( notification == "death" ) + break; // end thread + + } +} + +function player_monitor_swimming() +{ + self endon ( "disconnect" ); + + // make sure no other stray threads running on this dude + self notify("stop_player_monitor_swimming"); + self endon("stop_player_monitor_swimming"); + + self.lastSwimmingStartTime = 0; + self.timeSpentSwimmingInLife = 0; + while ( true ) + { + notification = self util::waittill_any_return( "swimming_begin", "death", "disconnect", "stop_player_monitor_swimming" ); + if( notification == "death" ) + break; // end thread + + self.lastSwimmingStartTime = getTime(); + + notification = self util::waittill_any_return( "swimming_end", "death", "disconnect", "stop_player_monitor_swimming" ); + + self.timeSpentSwimmingInLife += (getTime() - self.lastSwimmingStartTime); + + if( notification == "death" ) + break; // end thread + + } +} + +function player_monitor_slide() +{ + self endon ( "disconnect" ); + + // make sure no other stray threads running on this dude + self notify("stop_player_monitor_slide"); + self endon("stop_player_monitor_slide"); + + self.lastSlideStartTime = 0; + self.numberOfSlidesInLife = 0; + while ( true ) + { + notification = self util::waittill_any_return( "slide_begin", "death", "disconnect", "stop_player_monitor_slide" ); + if( notification == "death" ) + break; // end thread + + self.lastSlideStartTime = getTime(); + self.numberOfSlidesInLife++; + + notification = self util::waittill_any_return( "slide_end", "death", "disconnect", "stop_player_monitor_slide" ); + + if( notification == "death" ) + break; // end thread + } +} + +function player_monitor_doublejump() +{ + self endon ( "disconnect" ); + + // make sure no other stray threads running on this dude + self notify("stop_player_monitor_doublejump"); + self endon("stop_player_monitor_doublejump"); + + self.lastDoubleJumpStartTime = 0; + self.numberOfDoubleJumpsInLife = 0; + while ( true ) + { + notification = self util::waittill_any_return( "doublejump_begin", "death", "disconnect", "stop_player_monitor_doublejump" ); + if( notification == "death" ) + break; // end thread + + self.lastDoubleJumpStartTime = getTime(); + self.numberOfDoubleJumpsInLife++; + + notification = self util::waittill_any_return( "doublejump_end", "death", "disconnect", "stop_player_monitor_doublejump" ); + + if( notification == "death" ) + break; // end thread + } +} + + +function player_monitor_inactivity() +{ + self endon ( "disconnect" ); + + self notify( "player_monitor_inactivity" ); + self endon( "player_monitor_inactivity" ); + + wait 10; + + while( true ) + { + if ( isdefined( self ) ) + { + if ( self isRemoteControlling() || self util::isUsingRemote() ) + { + self ResetInactivityTimer(); + } + } + wait 5; + } +} + +function Callback_PlayerConnect() +{ + thread notifyConnecting(); + + self.statusicon = "hud_status_connecting"; + self waittill( "begin" ); + + if( isdefined( level.reset_clientdvars ) ) + self [[level.reset_clientdvars]](); + + waittillframeend; + self.statusicon = ""; + + self.guid = self getGuid(); + + self.killstreak = []; + + self.leaderDialogQueue = []; + self.killstreakDialogQueue = []; + + profilelog_begintiming( 4, "ship" ); + + level notify( "connected", self ); + callback::callback( #"on_player_connect" ); + + if ( self IsHost() ) + self thread globallogic::listenForGameEnd(); + + // only print that we connected if we haven't connected in a previous round + if( !level.splitscreen && !isdefined( self.pers["score"] ) ) + { + iPrintLn(&"MP_CONNECTED", self); + } + + if( !isdefined( self.pers["score"] ) ) + { + self thread persistence::adjust_recent_stats(); + self persistence::set_after_action_report_stat( "valid", 0 ); + if ( GameModeIsMode( 3 ) && !( self IsHost() ) ) + self persistence::set_after_action_report_stat( "wagerMatchFailed", 1 ); + else + self persistence::set_after_action_report_stat( "wagerMatchFailed", 0 ); + } + + // track match and hosting stats once per match + if( ( level.rankedMatch || level.wagerMatch || level.leagueMatch ) && !isdefined( self.pers["matchesPlayedStatsTracked"] ) ) + { + gameMode = util::GetCurrentGameMode(); + self globallogic::IncrementMatchCompletionStat( gameMode, "played", "started" ); + + if ( !isdefined( self.pers["matchesHostedStatsTracked"] ) && self IsLocalToHost() ) + { + self globallogic::IncrementMatchCompletionStat( gameMode, "hosted", "started" ); + self.pers["matchesHostedStatsTracked"] = true; + } + + self.pers["matchesPlayedStatsTracked"] = true; + self thread persistence::upload_stats_soon(); + } + + self gamerep::gameRepPlayerConnected(); + + lpselfnum = self getEntityNumber(); + lpGuid = self getGuid(); + lpXuid = self getxuid(true); + /#logPrint("J;" + lpGuid + ";" + lpselfnum + ";" + self.name + "\n");#/ + bbPrint( "global_joins", "name %s client %s xuid %s", self.name, lpselfnum, lpXuid ); + + // needed for cross-referencing into player breadcrumb buffer + // will get out of sync with self.clientId with disconnects/connects + recordPlayerStats( self, "codeClientNum", lpselfnum); + + if( !SessionModeIsZombiesGame() ) // it will be set after intro screen is faded out for zombie + { + self setClientUIVisibilityFlag( "hud_visible", 1 ); + self setClientUIVisibilityFlag( "weapon_hud_visible", 1 ); + } + + self SetClientPlayerSprintTime( level.playerSprintTime ); + self SetClientNumLives( level.numLives ); + + //makeDvarServerInfo( "cg_drawTalk", 1 ); + + if ( level.hardcoreMode ) + { + self SetClientDrawTalk( 3 ); + } + + if( SessionModeIsZombiesGame() ) + { + // initial zombies stats + self [[level.player_stats_init]](); + } + else + { + + self globallogic_score::initPersStat( "score" ); + if ( level.resetPlayerScoreEveryRound ) + { + self.pers["score"] = 0; + } + self.score = self.pers["score"]; + + self globallogic_score::initPersStat( "pointstowin" ); + if ( level.scoreRoundWinBased ) + { + self.pers["pointstowin"] = 0; + } + self.pointstowin = self.pers["pointstowin"]; + + self globallogic_score::initPersStat( "momentum", false ); + self.momentum = self globallogic_score::getPersStat( "momentum" ); + + self globallogic_score::initPersStat( "suicides" ); + self.suicides = self globallogic_score::getPersStat( "suicides" ); + + self globallogic_score::initPersStat( "headshots" ); + self.headshots = self globallogic_score::getPersStat( "headshots" ); + + self globallogic_score::initPersStat( "challenges" ); + self.challenges = self globallogic_score::getPersStat( "challenges" ); + + self globallogic_score::initPersStat( "kills" ); + self.kills = self globallogic_score::getPersStat( "kills" ); + + self globallogic_score::initPersStat( "deaths" ); + self.deaths = self globallogic_score::getPersStat( "deaths" ); + + self globallogic_score::initPersStat( "assists" ); + self.assists = self globallogic_score::getPersStat( "assists" ); + + self globallogic_score::initPersStat( "defends", false ); + self.defends = self globallogic_score::getPersStat( "defends" ); + + self globallogic_score::initPersStat( "offends", false ); + self.offends = self globallogic_score::getPersStat( "offends" ); + + self globallogic_score::initPersStat( "plants", false ); + self.plants = self globallogic_score::getPersStat( "plants" ); + + self globallogic_score::initPersStat( "defuses", false ); + self.defuses = self globallogic_score::getPersStat( "defuses" ); + + self globallogic_score::initPersStat( "returns", false ); + self.returns = self globallogic_score::getPersStat( "returns" ); + + self globallogic_score::initPersStat( "captures", false ); + self.captures = self globallogic_score::getPersStat( "captures" ); + + self globallogic_score::initPersStat( "objtime", false ); + self.objtime = self globallogic_score::getPersStat( "objtime" ); + + self globallogic_score::initPersStat( "carries", false ); + self.carries = self globallogic_score::getPersStat( "carries" ); + + self globallogic_score::initPersStat( "throws", false ); + self.throws = self globallogic_score::getPersStat( "throws" ); + + self globallogic_score::initPersStat( "destructions", false ); + self.destructions = self globallogic_score::getPersStat( "destructions" ); + + self globallogic_score::initPersStat( "disables", false ); + self.disables = self globallogic_score::getPersStat( "disables" ); + + self globallogic_score::initPersStat( "escorts", false ); + self.escorts = self globallogic_score::getPersStat( "escorts" ); + + self globallogic_score::initPersStat( "infects", false ); + self.infects = self globallogic_score::getPersStat( "infects" ); + + self globallogic_score::initPersStat( "sbtimeplayed", false ); + self.sbtimeplayed = self globallogic_score::getPersStat( "sbtimeplayed" ); + + self globallogic_score::initPersStat( "backstabs", false ); + self.backstabs = self globallogic_score::getPersStat( "backstabs" ); + + self globallogic_score::initPersStat( "longshots", false ); + self.longshots = self globallogic_score::getPersStat( "longshots" ); + + self globallogic_score::initPersStat( "survived", false ); + self.survived = self globallogic_score::getPersStat( "survived" ); + + self globallogic_score::initPersStat( "stabs", false ); + self.stabs = self globallogic_score::getPersStat( "stabs" ); + + self globallogic_score::initPersStat( "tomahawks", false ); + self.tomahawks = self globallogic_score::getPersStat( "tomahawks" ); + + self globallogic_score::initPersStat( "humiliated", false ); + self.humiliated = self globallogic_score::getPersStat( "humiliated" ); + + self globallogic_score::initPersStat( "x2score", false ); + self.x2score = self globallogic_score::getPersStat( "x2score" ); + + self globallogic_score::initPersStat( "agrkills", false ); + self.x2score = self globallogic_score::getPersStat( "agrkills" ); + + self globallogic_score::initPersStat( "hacks", false ); + self.x2score = self globallogic_score::getPersStat( "hacks" ); + + self globallogic_score::initPersStat( "killsconfirmed", false ); + self.killsconfirmed = self globallogic_score::getPersStat( "killsconfirmed" ); + + self globallogic_score::initPersStat( "killsdenied", false ); + self.killsdenied = self globallogic_score::getPersStat( "killsdenied" ); + + self globallogic_score::initPersStat( "rescues", false ); + self.rescues = self globallogic_score::getPersStat( "rescues" ); + + self globallogic_score::initPersStat( "shotsfired", false ); + self.shotsfired = self globallogic_score::getPersStat( "shotsfired" ); + + self globallogic_score::initPersStat( "shotshit", false ); + self.shotshit = self globallogic_score::getPersStat( "shotshit" ); + + self globallogic_score::initPersStat( "shotsmissed", false ); + self.shotsmissed = self globallogic_score::getPersStat( "shotsmissed" ); + + self globallogic_score::initPersStat( "cleandeposits", false ); + self.cleandeposits = self globallogic_score::getPersStat( "cleandeposits" ); + + self globallogic_score::initPersStat( "cleandenies", false ); + self.cleandenies = self globallogic_score::getPersStat( "cleandenies" ); + + self globallogic_score::initPersStat( "victory", false ); + self.victory = self globallogic_score::getPersStat( "victory" ); + + self globallogic_score::initPersStat( "sessionbans", false ); + self.sessionbans = self globallogic_score::getPersStat( "sessionbans" ); + self globallogic_score::initPersStat( "gametypeban", false ); + self globallogic_score::initPersStat( "time_played_total", false ); + self globallogic_score::initPersStat( "time_played_alive", false ); + + self globallogic_score::initPersStat( "teamkills", false ); + self globallogic_score::initPersStat( "teamkills_nostats", false ); + + // used by match recorder for analyzing play styles + self globallogic_score::initPersStat( "kill_distances", false ); + self globallogic_score::initPersStat( "num_kill_distance_entries", false ); + self globallogic_score::initPersStat( "time_played_moving", false ); + self globallogic_score::initPersStat( "total_speeds_when_moving", false ); + self globallogic_score::initPersStat( "num_speeds_when_moving_entries", false ); + self globallogic_score::initPersStat( "total_distance_travelled", false ); + self globallogic_score::initPersStat( "movement_Update_Count", false ); + + self.teamKillPunish = false; + if ( level.minimumAllowedTeamKills >= 0 && self.pers["teamkills_nostats"] > level.minimumAllowedTeamKills ) + self thread reduceTeamKillsOverTime(); + + self behaviorTracker::Initialize(); + } + + self.killedPlayersCurrent = []; + + if ( !isdefined( self.pers["totalTimePlayed"] ) ) + { + self setEnterTime( getTime() ); + self.pers["totalTimePlayed"] = 0; + } + + if ( !isdefined( self.pers["totalMatchBonus"] ) ) + { + self.pers["totalMatchBonus"] = 0; + } + + if( !isdefined( self.pers["best_kill_streak"] ) ) + { + self.pers["killed_players"] = []; + self.pers["killed_by"] = []; + self.pers["nemesis_tracking"] = []; + self.pers["artillery_kills"] = 0; + self.pers["dog_kills"] = 0; + self.pers["nemesis_name"] = ""; + self.pers["nemesis_rank"] = 0; + self.pers["nemesis_rankIcon"] = 0; + self.pers["nemesis_xp"] = 0; + self.pers["nemesis_xuid"] = ""; + self.pers["killed_players_with_specialist"] = []; + + /*self.killstreakKills["artillery"] = 0; + self.killstreakKills["dogs"] = 0; + self.killstreaksUsed["radar"] = 0; + self.killstreaksUsed["artillery"] = 0; + self.killstreaksUsed["dogs"] = 0;*/ + self.pers["best_kill_streak"] = 0; + } + +// Adding Music tracking per player CDC + if( !isdefined( self.pers["music"] ) ) + { + self.pers["music"] = spawnstruct(); + self.pers["music"].spawn = false; + self.pers["music"].inque = false; + self.pers["music"].currentState = "SILENT"; + self.pers["music"].previousState = "SILENT"; + self.pers["music"].nextstate = "UNDERSCORE"; + self.pers["music"].returnState = "UNDERSCORE"; + + } + + if ( self.team != "spectator" ) + { + self thread globallogic_audio::set_music_on_player( "spawnPreLoop" ); + } + + if ( !isdefined( self.pers["cur_kill_streak"] ) ) + { + self.pers["cur_kill_streak"] = 0; + } + + if ( !isdefined( self.pers["cur_total_kill_streak"] ) ) + { + self.pers["cur_total_kill_streak"] = 0; + self setplayercurrentstreak( 0 ); + } + + if ( !isdefined( self.pers["totalKillstreakCount"] ) ) + self.pers["totalKillstreakCount"] = 0; + + //Keep track of how many killstreaks have been earned in the current streak + if ( !isdefined( self.pers["killstreaksEarnedThisKillstreak"] ) ) + self.pers["killstreaksEarnedThisKillstreak"] = 0; + + if ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks && !isdefined( self.pers["killstreak_quantity"] ) ) + self.pers["killstreak_quantity"] = []; + + if ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks && !isdefined( self.pers["held_killstreak_ammo_count"] ) ) + self.pers["held_killstreak_ammo_count"] = []; + + if ( IsDefined( level.usingScoreStreaks ) && level.usingScoreStreaks && !IsDefined( self.pers["held_killstreak_clip_count"] ) ) + self.pers["held_killstreak_clip_count"] = []; + + if( !isDefined( self.pers["changed_class"] ) ) + self.pers["changed_class"] = false; + + if( !isDefined( self.pers["lastroundscore"] ) ) + self.pers["lastroundscore"] = 0; + + self.lastKillTime = 0; + + self.cur_death_streak = 0; + self disabledeathstreak(); + self.death_streak = 0; + self.kill_streak = 0; + self.gametype_kill_streak = 0; + self.spawnQueueIndex = -1; + self.deathTime = 0; + + self.aliveTimes = []; + for( index = 0; index < level.aliveTimeMaxCount; index++ ) + { + self.aliveTimes[index] = 0; + } + + self.aliveTimeCurrentIndex = 0; + + if ( level.onlineGame && !( isdefined( level.freerun ) && level.freerun ) ) + { + self.death_streak = self getDStat( "HighestStats", "death_streak" ); + self.kill_streak = self getDStat( "HighestStats", "kill_streak" ); + self.gametype_kill_streak = self persistence::stat_get_with_gametype( "kill_streak" ); + } + + self.lastGrenadeSuicideTime = -1; + + self.teamkillsThisRound = 0; + + if ( !isdefined( level.livesDoNotReset ) || !level.livesDoNotReset || !isdefined( self.pers["lives"] ) ) + { + self.pers["lives"] = level.numLives; + } + + // multi round FFA games in custom game mode should maintain team in-between rounds + if ( !level.teamBased ) + { + self.pers["team"] = undefined; + } + + self.hasSpawned = false; + self.waitingToSpawn = false; + self.wantSafeSpawn = false; + self.deathCount = 0; + + self.wasAliveAtMatchStart = false; + + level.players[level.players.size] = self; + + if( level.splitscreen ) + SetDvar( "splitscreen_playerNum", level.players.size ); + // removed underscore for debug CDC + // When joining a game in progress, if the game is at the post game state (scoreboard) the connecting player should spawn into intermission + if ( game["state"] == "postgame" ) + { + self.pers["needteam"] = 1; + self.pers["team"] = "spectator"; + self.team = self.sessionteam; + + self setClientUIVisibilityFlag( "hud_visible", 0 ); + + self [[level.spawnIntermission]](); + self closeInGameMenu(); + profilelog_endtiming( 4, "gs=" + game["state"] + " zom=" + SessionModeIsZombiesGame() ); + return; + } + + // don't count losses for CTF and S&D and War at each round. + if ( ( level.rankedMatch || level.wagerMatch || level.leagueMatch ) && !isdefined( self.pers["lossAlreadyReported"] ) ) + { + if ( level.leagueMatch ) + { + self recordLeaguePreLoser(); + } + + globallogic_score::updateLossStats( self ); + + self.pers["lossAlreadyReported"] = true; + } + if ((level.rankedMatch || level.leagueMatch) && !isDefined( self.pers["lateJoin"] ) ) + { + if (game["state"] == "playing" && !level.inPrematchPeriod ) + { + self.pers["lateJoin"] = true; + } + else + { + self.pers["lateJoin"] = false; + } + } + + // don't redo winstreak save to pers array for each round of round based games. + if ( !isdefined( self.pers["winstreakAlreadyCleared"] ) ) + { + self globallogic_score::backupAndClearWinStreaks(); + self.pers["winstreakAlreadyCleared"] = true; + } + + if( self istestclient() ) + { + self.pers[ "isBot" ] = true; + recordPlayerStats( self, "isBot", true); + } + + if ( level.rankedMatch || level.leagueMatch ) + { + self persistence::set_after_action_report_stat( "demoFileID", "0" ); + } + + level endon( "game_ended" ); + + if ( isdefined( level.hostMigrationTimer ) ) + self thread hostmigration::hostMigrationTimerThink(); + + if ( isdefined( self.pers["team"] ) ) + self.team = self.pers["team"]; + + if ( isdefined( self.pers["class"] ) ) + self.curClass = self.pers["class"]; + + if ( !isdefined( self.pers["team"] ) || isdefined( self.pers["needteam"] ) ) + { + // Don't set .sessionteam until we've gotten the assigned team from code, + // because it overrides the assigned team. + self.pers["needteam"] = undefined; + self.pers["team"] = "spectator"; + self.team = "spectator"; + self.sessionstate = "dead"; + + self globallogic_ui::updateObjectiveText(); + + [[level.spawnSpectator]](); + + [[level.autoassign]]( false ); + if ( level.rankedMatch || level.leagueMatch ) + { + self thread globallogic_spawn::kickIfDontSpawn(); + } + + if ( self.pers["team"] == "spectator" ) + { + self.sessionteam = "spectator"; + self thread spectate_player_watcher(); + } + + if ( level.teamBased ) + { + // set team and spectate permissions so the map shows waypoint info on connect + self.sessionteam = self.pers["team"]; + if ( !isAlive( self ) ) + self.statusicon = "hud_status_dead"; + self thread spectating::set_permissions(); + } + } + else if ( self.pers["team"] == "spectator" ) + { + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + [[level.spawnSpectator]](); + self.sessionteam = "spectator"; + self.sessionstate = "spectator"; + self thread spectate_player_watcher(); + } + else + { + self.sessionteam = self.pers["team"]; + self.sessionstate = "dead"; + + self globallogic_ui::updateObjectiveText(); + + [[level.spawnSpectator]](); + + if ( globallogic_utils::isValidClass( self.pers["class"] ) || util::IsPropHuntGametype() ) + { + if ( !globallogic_utils::isValidClass( self.pers["class"] ) ) + { + self.pers["class"] = level.defaultClass; + self.curClass = level.defaultClass; + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + } + self thread [[level.spawnClient]](); + } + else + { + self globallogic_ui::showMainMenuForTeam(); + } + + self thread spectating::set_permissions(); + } + + if ( self.sessionteam != "spectator" ) + { + self thread spawning::onSpawnPlayer(true); + } + + if ( level.forceRadar == 1 ) // radar always sweeping + { + self.pers["hasRadar"] = true; + self.hasSpyplane = true; + + if ( level.teambased ) + { + level.activeUAVs[self.team]++; + } + else + { + level.activeUAVs[self getEntityNumber()]++; + } + + level.activePlayerUAVs[self getEntityNumber()]++; + } + + if ( level.forceRadar == 2 ) // radar constant + { + self setClientUIVisibilityFlag( "g_compassShowEnemies", level.forceRadar ); + } + else + { + self SetClientUIVisibilityFlag( "g_compassShowEnemies", 0 ); + } + + profilelog_endtiming( 4, "gs=" + game["state"] + " zom=" + SessionModeIsZombiesGame() ); + + if ( isdefined( self.pers["isBot"] ) ) + return; + + self record_global_mp_stats_for_player_at_match_start(); + + //T7 - moved from load_shared to make sure this doesn't get set on CP until level.players is ready + num_con = getnumconnectedplayers(); + num_exp = getnumexpectedplayers(); + /#println( "all_players_connected(): getnumconnectedplayers=", num_con, "getnumexpectedplayers=", num_exp );#/ + + if(num_con == num_exp && (num_exp != 0)) + { + level flag::set( "all_players_connected" ); + // CODER_MOD: GMJ (08/28/08): Setting dvar for use by code + SetDvar( "all_players_are_connected", "1" ); + } + + globallogic_score::updateWeaponContractStart( self ); +} + +function record_global_mp_stats_for_player_at_match_start() +{ + // not sure if we even want/need this test + // if ( level.onlinegame && !SessionModeIsPrivate() ) ? + // if ( level.rankedMatch ) ? + // + // just copy from mp_stats, and it will do what it's supposed to + // (i.e. return something or 0) + + if( isdefined( level.disableStatTracking ) && level.disableStatTracking == true ) + { + return; + } + + startKills = self GetDStat( "playerstatslist", "kills", "statValue" ); + startDeaths = self GetDStat( "playerstatslist", "deaths", "statValue" ); + startWins = self GetDStat( "playerstatslist", "wins", "statValue" ); + startLosses = self GetDStat( "playerstatslist", "losses", "statValue" ); + startHits = self GetDStat( "playerstatslist", "hits", "statValue" ); + startMisses = self GetDStat( "playerstatslist", "misses", "statValue" ); + startTimePlayedTotal = self GetDStat( "playerstatslist", "time_played_total", "statValue" ); + startScore = self GetDStat( "playerstatslist", "score", "statValue" ); + startPrestige = self GetDStat( "playerstatslist", "plevel", "statValue" ); + startUnlockPoints = self GetDStat( "unlocks", 0); + + ties = self GetDStat( "playerstatslist", "ties", "statValue" ); + startGamesPlayed = startWins + startLosses + ties; + + self.startKills = startKills; + self.startHits = startHits; + self.totalMatchShots = 0; + + // note: xp_start - already exists - written in code - reads RANKXP + + recordPlayerStats( self, "startKills", startKills ); + recordPlayerStats( self, "startDeaths", startDeaths ); + recordPlayerStats( self, "startWins", startWins ); + recordPlayerStats( self, "startLosses", startLosses ); + recordPlayerStats( self, "startHits", startHits ); + recordPlayerStats( self, "startMisses", startMisses ); + recordPlayerStats( self, "startTimePlayedTotal", startTimePlayedTotal ); + recordPlayerStats( self, "startScore", startScore ); + recordPlayerStats( self, "startPrestige", startPrestige ); + recordPlayerStats( self, "startUnlockPoints", startUnlockPoints ); + recordPlayerStats( self, "startGamesPlayed", startGamesPlayed ); + + // temp commenting out; the getdstat calls here fail + lootXPBeforeMatch = self GetDStat( "AfterActionReportStats", "lootXPBeforeMatch" ); + cryptoKeysBeforeMatch = self GetDStat( "AfterActionReportStats", "cryptoKeysBeforeMatch" ); + recordPlayerStats( self, "lootXPBeforeMatch", lootXPBeforeMatch ); + recordPlayerStats( self, "cryptoKeysBeforeMatch", cryptoKeysBeforeMatch ); + +} + +function record_global_mp_stats_for_player_at_match_end() +{ + if( isdefined( level.disableStatTracking ) && level.disableStatTracking == true ) + { + return; + } + + endKills = self GetDStat( "playerstatslist", "kills", "statValue" ); + endDeaths = self GetDStat( "playerstatslist", "deaths", "statValue" ); + endWins = self GetDStat( "playerstatslist", "wins", "statValue" ); + endLosses = self GetDStat( "playerstatslist", "losses", "statValue" ); + endHits = self GetDStat( "playerstatslist", "hits", "statValue" ); + endMisses = self GetDStat( "playerstatslist", "misses", "statValue" ); + endTimePlayedTotal = self GetDStat( "playerstatslist", "time_played_total", "statValue" ); + endScore = self GetDStat( "playerstatslist", "score", "statValue" ); + endPrestige = self GetDStat( "playerstatslist", "plevel", "statValue" ); + endUnlockPoints = self GetDStat( "unlocks", 0); + + ties = self GetDStat( "playerstatslist", "ties", "statValue" ); + endGamesPlayed = endWins + endLosses + ties; + + // note: xp_end - already exists - written in code - reads RANKXP + + recordPlayerStats( self, "endKills", endKills ); + recordPlayerStats( self, "endDeaths", endDeaths ); + recordPlayerStats( self, "endWins", endWins ); + recordPlayerStats( self, "endLosses", endLosses ); + recordPlayerStats( self, "endHits", endHits ); + recordPlayerStats( self, "endMisses", endMisses ); + recordPlayerStats( self, "endTimePlayedTotal", endTimePlayedTotal ); + recordPlayerStats( self, "endScore", endScore ); + recordPlayerStats( self, "endPrestige", endPrestige ); + recordPlayerStats( self, "endUnlockPoints", endUnlockPoints ); + recordPlayerStats( self, "endGamesPlayed", endGamesPlayed ); + +} + +function record_misc_player_stats() +{ + if( isdefined( level.disableStatTracking ) && level.disableStatTracking == true ) + { + return; + } + + // common either for match end or on disconnect + recordPlayerStats( self, "UTCEndTimeSeconds", getUTC() ); + if( isdefined( self.weaponPickupsCount ) ) + { + recordPlayerStats( self, "weaponPickupsCount", self.weaponPickupsCount ); + } + if( isdefined( self.killcamsSkipped) ) + { + recordPlayerStats( self, "totalKillcamsSkipped", self.killcamsSkipped ); + } + if( isdefined( self.matchBonus) ) + { + recordPlayerStats( self, "matchXp", self.matchBonus ); + } + if( isdefined( self.killsdenied ) ) + { + recordPlayerStats( self, "killsDenied", self.killsdenied ); + } + if( isdefined( self.killsconfirmed ) ) + { + recordPlayerStats( self, "killsConfirmed", self.killsconfirmed ); + } + if( self IsSplitscreen() ) + { + recordPlayerStats( self, "isSplitscreen", true ); + } + if( self.objtime ) + { + recordPlayerStats( self, "objectiveTime", self.objtime ); + } + if( self.escorts ) + { + recordPlayerStats( self, "escortTime", self.escorts ); + } +} + +function spectate_player_watcher() +{ + self endon( "disconnect" ); + + // Setup the perks hud elem for the spectator if its not yet initalized + // We have to do it here, since the perk hudelem is generally initalized only on spawn, and the spectator will not able able to + // look at the perk loadout of some player. + if ( !level.splitscreen && !level.hardcoreMode && GetDvarint( "scr_showperksonspawn" ) == 1 && game["state"] != "postgame" && !isdefined( self.perkHudelem ) ) + { + if ( level.perksEnabled == 1 ) + { + self hud::showPerks( ); + } + } + + self.watchingActiveClient = true; + self.waitingForPlayersText = undefined; + + while ( 1 ) + { + if ( self.pers["team"] != "spectator" || level.gameEnded ) + { + self hud_message::clearShoutcasterWaitingMessage(); + if ( !( isdefined( level.inPrematchPeriod ) && level.inPrematchPeriod ) ) + { + self FreezeControls( false ); + } + self.watchingActiveClient = false; + break; + } + else + { + count = 0; + for ( i = 0; i < level.players.size; i++ ) + { + if ( level.players[i].team != "spectator" ) + { + count++; + break; + } + } + + if ( count > 0 ) + { + if ( !self.watchingActiveClient ) + { + self hud_message::clearShoutcasterWaitingMessage(); + self FreezeControls( false ); + + // Make sure that the player spawned notify happens when we start watching a player. + self LUINotifyEvent( &"player_spawned", 0 ); + } + + self.watchingActiveClient = true; + } + else + { + if ( self.watchingActiveClient ) + { + [[level.onSpawnSpectator]](); + self FreezeControls( true ); + self hud_message::setShoutcasterWaitingMessage(); + } + + self.watchingActiveClient = false; + } + + wait( 0.5 ); + } + } +} + +function Callback_PlayerMigrated() +{ +/# println( "Player " + self.name + " finished migrating at time " + gettime() ); #/ + + if ( isdefined( self.connected ) && self.connected ) + { + self globallogic_ui::updateObjectiveText(); +// self globallogic_ui::updateObjectiveText(); +// self updateMainMenu(); + +// if ( level.teambased ) +// self updateScores(); + } + + level.hostMigrationReturnedPlayerCount++; + if ( level.hostMigrationReturnedPlayerCount >= level.players.size * 2 / 3 ) + { + /# println( "2/3 of players have finished migrating" ); #/ + level notify( "hostmigration_enoughplayers" ); + } +} + +function Callback_PlayerDisconnect() +{ + profilelog_begintiming( 5, "ship" ); + + if ( game["state"] != "postgame" && !level.gameEnded ) + { + gameLength = game["timepassed"]; + self globallogic::bbPlayerMatchEnd( gameLength, "MP_PLAYER_DISCONNECT", 0 ); + + if( util::isRoundBased() ) + { + recordPlayerStats( self, "playerQuitRoundNumber", game["roundsplayed"] + 1 ); + } + + if( level.teambased ) + { + ourTeam = self.team; // only expecting: "allies" or "axis" + if( ourTeam == "allies" || ourTeam == "axis" ) + { + theirTeam = ""; + if( ourTeam == "allies" ) + { + theirTeam = "axis"; + } + else if( ourTeam == "axis" ) + { + theirTeam = "allies"; + } + recordPlayerStats( self, "playerQuitTeamScore", getTeamScore( ourTeam ) ); + recordPlayerStats( self, "playerQuitOpposingTeamScore", getTeamScore( theirTeam ) ); + } + } + + recordEndGameComScoreEventForPlayer( self, "disconnect" ); + + } + + self behaviorTracker::Finalize(); + + ArrayRemoveValue( level.players, self ); + + if ( level.splitscreen ) + { + players = level.players; + + if ( players.size <= 1 ) + level thread globallogic::forceEnd(); + + // passing number of players to menus in splitscreen to display leave or end game option + SetDvar( "splitscreen_playerNum", players.size ); + } + + if ( isdefined( self.score ) && isdefined( self.pers["team"] ) ) + { + /#print( "team: score " + self.pers["team"] + ":" + self.score );#/ + level.dropTeam += 1; + } + + [[level.onPlayerDisconnect]](); + + lpselfnum = self getEntityNumber(); + lpGuid = self getGuid(); + /#logPrint("Q;" + lpGuid + ";" + lpselfnum + ";" + self.name + "\n");#/ + + self record_global_mp_stats_for_player_at_match_end(); + self record_special_move_data_for_life( undefined ); + + self record_misc_player_stats(); + + self gamerep::gameRepPlayerDisconnected(); + + for ( entry = 0; entry < level.players.size; entry++ ) + { + if ( level.players[entry] == self ) + { + while ( entry < level.players.size-1 ) + { + level.players[entry] = level.players[entry+1]; + entry++; + } + level.players[entry] = undefined; + break; + } + } + for ( entry = 0; entry < level.players.size; entry++ ) + { + if ( isdefined( level.players[entry].pers["killed_players"][self.name] ) ) + level.players[entry].pers["killed_players"][self.name] = undefined; + + if ( isdefined( level.players[entry].pers["killed_players_with_specialist"][self.name] ) ) + level.players[entry].pers["killed_players_with_specialist"][self.name] = undefined; + + if ( isdefined( level.players[entry].killedPlayersCurrent[self.name] ) ) + level.players[entry].killedPlayersCurrent[self.name] = undefined; + + if ( isdefined( level.players[entry].pers["killed_by"][self.name] ) ) + level.players[entry].pers["killed_by"][self.name] = undefined; + + if ( isdefined( level.players[entry].pers["nemesis_tracking"][self.name] ) ) + level.players[entry].pers["nemesis_tracking"][self.name] = undefined; + + // player that disconnected was our nemesis + if ( level.players[entry].pers["nemesis_name"] == self.name ) + { + level.players[entry] chooseNextBestNemesis(); + } + } + + if ( level.gameEnded ) + self globallogic::removeDisconnectedPlayerFromPlacement(); + + level thread globallogic::updateTeamStatus(); + level thread globallogic::updateAllAliveTimes(); + + profilelog_endtiming( 5, "gs=" + game["state"] + " zom=" + SessionModeIsZombiesGame() ); +} + +function Callback_PlayerMelee( eAttacker, iDamage, weapon, vOrigin, vDir, boneIndex, shieldHit, fromBehind ) +{ + hit = true; + + if ( level.teamBased && self.team == eAttacker.team ) + { + if ( level.friendlyfire == 0 ) // no one takes damage + { + hit = false; + } + } + + self finishMeleeHit( eAttacker, weapon, vOrigin, vDir, boneIndex, shieldHit, hit, fromBehind ); +} + +function chooseNextBestNemesis() +{ + nemesisArray = self.pers["nemesis_tracking"]; + nemesisArrayKeys = getArrayKeys( nemesisArray ); + nemesisAmount = 0; + nemesisName = ""; + + if ( nemesisArrayKeys.size > 0 ) + { + for ( i = 0; i < nemesisArrayKeys.size; i++ ) + { + nemesisArrayKey = nemesisArrayKeys[i]; + if ( nemesisArray[nemesisArrayKey] > nemesisAmount ) + { + nemesisName = nemesisArrayKey; + nemesisAmount = nemesisArray[nemesisArrayKey]; + } + + } + } + + self.pers["nemesis_name"] = nemesisName; + + if ( nemesisName != "" ) + { + playerIndex = 0; + for( ; playerIndex < level.players.size; playerIndex++ ) + { + if ( level.players[playerIndex].name == nemesisName ) + { + nemesisPlayer = level.players[playerIndex]; + self.pers["nemesis_rank"] = nemesisPlayer.pers["rank"]; + self.pers["nemesis_rankIcon"] = nemesisPlayer.pers["rankxp"]; + self.pers["nemesis_xp"] = nemesisPlayer.pers["prestige"]; + self.pers["nemesis_xuid"] = nemesisPlayer GetXUID(); + break; + } + } + } + else + { + self.pers["nemesis_xuid"] = ""; + } +} + +function custom_gamemodes_modified_damage( victim, eAttacker, iDamage, sMeansOfDeath, weapon, eInflictor, sHitLoc ) +{ + // regular public matches should early out + if ( level.onlinegame && !SessionModeIsPrivate() ) + { + return iDamage; + } + + if( isdefined( eAttacker) && isdefined( eAttacker.damageModifier ) ) + { + iDamage *= eAttacker.damageModifier; + } + if ( ( sMeansOfDeath == "MOD_PISTOL_BULLET" ) || ( sMeansOfDeath == "MOD_RIFLE_BULLET" ) ) + { + iDamage = int( iDamage * level.bulletDamageScalar ); + } + + return iDamage; +} + +function figure_out_attacker( eAttacker ) +{ + if ( isdefined(eAttacker) ) + { + if( isai(eAttacker) && isdefined( eAttacker.script_owner ) ) + { + team = self.team; + + if ( eAttacker.script_owner.team != team ) + eAttacker = eAttacker.script_owner; + } + + if( eAttacker.classname == "script_vehicle" && isdefined( eAttacker.owner ) ) + eAttacker = eAttacker.owner; + else if( eAttacker.classname == "auto_turret" && isdefined( eAttacker.owner ) ) + eAttacker = eAttacker.owner; + else if( eAttacker.classname == "actor_spawner_bo3_robot_grunt_assault_mp" && isdefined( eAttacker.owner ) ) + eAttacker = eAttacker.owner; + } + + return eAttacker; +} + +function player_damage_figure_out_weapon( weapon, eInflictor ) +{ + // explosive barrel/car detection + if ( weapon == level.weaponNone && isdefined( eInflictor ) ) + { + if ( isdefined( eInflictor.targetname ) && eInflictor.targetname == "explodable_barrel" ) + { + weapon = GetWeapon( "explodable_barrel" ); + } + else if ( isdefined( eInflictor.destructible_type ) && isSubStr( eInflictor.destructible_type, "vehicle_" ) ) + { + weapon = GetWeapon( "destructible_car" ); + } + else if( isdefined( eInflictor.scriptvehicletype ) ) + { + veh_weapon = GetWeapon( eInflictor.scriptvehicletype ); + if( isdefined( veh_weapon ) ) + { + weapon = veh_weapon; + } + } + } + + if ( isdefined( eInflictor ) && isdefined( eInflictor.script_noteworthy ) ) + { + if ( IsDefined( level.overrideWeaponFunc ) ) + { + weapon = [[level.overrideWeaponFunc]]( weapon, eInflictor.script_noteworthy ); + } + } + + return weapon; +} + +function figure_out_friendly_fire( victim ) +{ + if ( level.hardcoreMode && level.friendlyfire > 0 && isdefined( victim ) && victim.is_capturing_own_supply_drop === true ) + { + return 2; // FF 2 = reflect; design wants reflect friendly fire whenever a player is capturing their own supply drop + } + + if ( killstreaks::is_ricochet_protected( victim ) ) + { + return 2; + } + + // note, keep, non-gametype specific friendly fire logic above this line + + if ( isdefined( level.figure_out_gametype_friendly_fire ) ) + { + return [[ level.figure_out_gametype_friendly_fire ]]( victim ); + } + + return level.friendlyfire; +} + +function isPlayerImmuneToKillstreak( eAttacker, weapon ) +{ + if ( level.hardcoreMode ) + return false; + + if ( !isdefined( eAttacker ) ) + return false; + + if ( self != eAttacker ) + return false; + + return weapon.doNotDamageOwner; +} + + +function should_do_player_damage( eAttacker, weapon, sMeansOfDeath, iDFlags ) +{ + if ( game["state"] == "postgame" ) + return false; + + if ( self.sessionteam == "spectator" ) + return false; + + if ( isdefined( self.canDoCombat ) && !self.canDoCombat ) + return false; + + if ( isdefined( eAttacker ) && isPlayer( eAttacker ) && isdefined( eAttacker.canDoCombat ) && !eAttacker.canDoCombat ) + return false; + + if ( isdefined( level.hostMigrationTimer ) ) + return false; + + if ( level.onlyHeadShots ) + { + if ( sMeansOfDeath == "MOD_PISTOL_BULLET" || sMeansOfDeath == "MOD_RIFLE_BULLET" ) + return false; + } + + // Make all vehicle drivers invulnerable to bullets + if ( self vehicle::player_is_occupant_invulnerable( sMeansOfDeath ) ) + return false; + + if ( weapon.isSupplyDropWeapon && !weapon.isGrenadeWeapon && ( smeansofdeath != "MOD_TRIGGER_HURT" ) ) + return false; + + if ( self.scene_takedamage === false ) + return false; + + // prevent spawn kill wall bangs + if ( (iDFlags & 8) && self player::is_spawn_protected() ) + return false; + +return true; +} + +function apply_damage_to_armor( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, sHitLoc, friendlyFire, ignore_round_start_friendly_fire ) +{ + victim = self; + + if ( friendlyFire && !player_damage_does_friendly_fire_damage_victim( ignore_round_start_friendly_fire ) ) + return iDamage; + + // Handle armor damage + if( IsDefined( victim.lightArmorHP ) ) + { + if ( weapon.ignoresLightArmor && sMeansOfDeath != "MOD_MELEE" ) + { + return iDamage; + } + else if ( weapon.meleeIgnoresLightArmor && sMeansOfDeath == "MOD_MELEE" ) + { + return iDamage; + } + // anything stuck to the player does health damage + else if( IsDefined( eInflictor ) && IsDefined( eInflictor.stuckToPlayer ) && eInflictor.stuckToPlayer == victim ) + { + iDamage = victim.health; + } + else + { + // Handle Armor Damage + // no armor damage in case of falling, melee, fmj or head shots + if ( sMeansOfDeath != "MOD_FALLING" + && !weapon_utils::isMeleeMOD( sMeansOfDeath ) + && !globallogic_utils::isHeadShot( weapon, sHitLoc, sMeansOfDeath, eAttacker ) + ) + { + victim armor::setLightArmorHP( victim.lightArmorHP - ( iDamage ) ); + + iDamage = 0; + if ( victim.lightArmorHP <= 0 ) + { + // since the light armor is gone, adjust the damage to be the excess damage that happens after the light armor hp is reduced + iDamage = abs( victim.lightArmorHP ); + armor::unsetLightArmor(); + } + } + } + } + + return iDamage; +} + +function make_sure_damage_is_not_zero( iDamage ) +{ + // Make sure at least one point of damage is done & give back 1 health because of this if you have power armor + if ( iDamage < 1 ) + { + if( ( self ability_util::gadget_power_armor_on() ) && isDefined( self.maxHealth ) && ( self.health < self.maxHealth ) ) + { + self.health += 1; + } + iDamage = 1; + } + + return int(iDamage); +} + +function modify_player_damage_friendlyfire( iDamage ) +{ + friendlyfire = [[ level.figure_out_friendly_fire ]]( self ); + + // half damage + if ( friendlyfire == 2 || friendlyfire == 3 ) + { + iDamage = int(iDamage * .5); + } + + return iDamage; +} + +function modify_player_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) +{ + if ( isdefined( self.overridePlayerDamage ) ) + { + iDamage = self [[self.overridePlayerDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ); + } + else if ( isdefined( level.overridePlayerDamage ) ) + { + iDamage = self [[level.overridePlayerDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ); + } + + assert(isdefined(iDamage), "You must return a value from a damage override function."); + + if ( isdefined( eAttacker ) ) + { + iDamage = loadout::cac_modified_damage( self, eAttacker, iDamage, sMeansOfDeath, weapon, eInflictor, sHitLoc ); + + if( isdefined( eAttacker.pickup_damage_scale ) && eAttacker.pickup_damage_scale_time > GetTime() ) + { + iDamage = iDamage * eAttacker.pickup_damage_scale; + } + } + iDamage = custom_gamemodes_modified_damage( self, eAttacker, iDamage, sMeansOfDeath, weapon, eInflictor, sHitLoc ); + + if ( level.onPlayerDamage != &globallogic::blank ) + { + modifiedDamage = [[level.onPlayerDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime ); + + if ( isdefined( modifiedDamage ) ) + { + if ( modifiedDamage <= 0 ) + return; + + iDamage = modifiedDamage; + } + } + + if ( level.onlyHeadShots ) + { + if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + iDamage = 150; + } + + if ( weapon.damageAlwaysKillsPlayer ) + { + iDamage = self.maxHealth + 1; + } + + if ( sHitLoc == "riotshield" ) + { + if ( iDFlags & 32 ) + { + if ( !(iDFlags & 64) ) + { + iDamage *= 0.0; + } + } + else if ( iDFlags & 128 ) + { + if ( isdefined( eInflictor ) && isdefined( eInflictor.stuckToPlayer ) && eInflictor.stuckToPlayer == self ) + { + //does enough damage to shield carrier to ensure death + iDamage = self.maxhealth + 1; + } + } + } + + return int(iDamage); +} + +function modify_player_damage_meansofdeath( eInflictor, eAttacker, sMeansOfDeath, weapon, sHitLoc ) +{ + if ( globallogic_utils::isHeadShot( weapon, sHitLoc, sMeansOfDeath, eInflictor ) && isPlayer(eAttacker) && !weapon_utils::ismeleemod( sMeansOfDeath ) ) + { + sMeansOfDeath = "MOD_HEAD_SHOT"; + } + + if ( isdefined( eInflictor ) && isdefined( eInflictor.script_noteworthy ) ) + { + if ( eInflictor.script_noteworthy == "ragdoll_now" ) + { + sMeansOfDeath = "MOD_FALLING"; + } + } + + return sMeansOfDeath; +} + +function player_damage_update_attacker( eInflictor, eAttacker, sMeansOfDeath ) +{ + if ( isdefined( eInflictor ) && isPlayer( eAttacker ) && eAttacker == eInflictor ) + { + if ( sMeansOfDeath == "MOD_HEAD_SHOT" || sMeansOfDeath == "MOD_PISTOL_BULLET" || sMeansOfDeath == "MOD_RIFLE_BULLET" ) + { + //if ( isPlayer( eAttacker ) ) already tested for above + { + eAttacker.hits++; + } + } + } + + if ( isPlayer( eAttacker ) ) + eAttacker.pers["participation"]++; +} + +function player_is_spawn_protected_from_explosive( eInflictor, weapon, sMeansOfDeath ) +{ + if ( !self player::is_spawn_protected() ) + return false; + + // if we are using this as a impact damage only projectile then no protection + // we should probably add a bool to the weapon to indicate that it spawn protects + if ( weapon.explosionradius == 0 ) + return false; + + distSqr = ( ( isdefined( eInflictor ) && isdefined( self.lastSpawnPoint ) ) ? DistanceSquared( eInflictor.origin, self.lastSpawnPoint.origin ) : 0 ); + + // protect players from spawnkill grenades, tabun and incendiary + if ( distSqr < ( (250) * (250) ) ) + { + if ( sMeansOfDeath == "MOD_GRENADE" || sMeansOfDeath == "MOD_GRENADE_SPLASH" ) + { + return true; + } + + if ( sMeansOfDeath == "MOD_PROJECTILE" || sMeansOfDeath == "MOD_PROJECTILE_SPLASH" ) + { + return true; + } + + if ( sMeansOfDeath == "MOD_EXPLOSIVE" ) + { + return true; + } + } + + if ( killstreaks::is_killstreak_weapon( weapon ) ) + { + return true; + } + + return false; +} + +function player_damage_update_explosive_info( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) +{ + is_explosive_damage = loadout::isExplosiveDamage( sMeansOfDeath ); + + if ( is_explosive_damage ) + { + // protect players from spawnkill grenades, tabun, incendiaries, and scorestreaks + if ( self player_is_spawn_protected_from_explosive( eInflictor, weapon, sMeansOfDeath ) ) + { + return false; + } + + // protect players from their own non-player controlled killstreaks + if ( self isPlayerImmuneToKillstreak( eAttacker, weapon ) ) + { + return false; + } + } + + if ( isdefined( eInflictor ) && ( sMeansOfDeath == "MOD_GAS" || is_explosive_damage ) ) + { + self.explosiveInfo = []; + self.explosiveInfo["damageTime"] = getTime(); + self.explosiveInfo["damageId"] = eInflictor getEntityNumber(); + self.explosiveInfo["originalOwnerKill"] = false; + self.explosiveInfo["bulletPenetrationKill"] = false; + self.explosiveInfo["chainKill"] = false; + self.explosiveInfo["damageExplosiveKill"] = false; + self.explosiveInfo["chainKill"] = false; + self.explosiveInfo["cookedKill"] = false; + self.explosiveInfo["weapon"] = weapon; + self.explosiveInfo["originalowner"] = eInflictor.originalowner; + + isFrag = ( weapon.rootweapon.name == "frag_grenade" ); + + if ( isdefined( eAttacker ) && eAttacker != self ) + { + if ( isdefined( eAttacker ) && isdefined( eInflictor.owner ) && (weapon.name == "satchel_charge" || weapon.name == "claymore" || weapon.name == "bouncingbetty") ) + { + self.explosiveInfo["originalOwnerKill"] = (eInflictor.owner == self); + self.explosiveInfo["damageExplosiveKill"] = isdefined( eInflictor.wasDamaged ); + self.explosiveInfo["chainKill"] = isdefined( eInflictor.wasChained ); + self.explosiveInfo["wasJustPlanted"] = isdefined( eInflictor.wasJustPlanted ); + self.explosiveInfo["bulletPenetrationKill"] = isdefined( eInflictor.wasDamagedFromBulletPenetration ); + self.explosiveInfo["cookedKill"] = false; + } + if ( isdefined( eInflictor ) && isdefined( eInflictor.stuckToPlayer ) && weapon.projExplosionType == "grenade" ) + { + self.explosiveInfo["stuckToPlayer"] = eInflictor.stuckToPlayer; + } + if ( weapon.doStun ) + { + self.lastStunnedBy = eAttacker; + self.lastStunnedTime = self.iDFlagsTime; + } + if ( isdefined( eAttacker.lastGrenadeSuicideTime ) && eAttacker.lastGrenadeSuicideTime >= gettime() - 50 && isFrag ) + { + self.explosiveInfo["suicideGrenadeKill"] = true; + } + else + { + self.explosiveInfo["suicideGrenadeKill"] = false; + } + } + + if ( isFrag ) + { + self.explosiveInfo["cookedKill"] = isdefined( eInflictor.isCooked ); + self.explosiveInfo["throwbackKill"] = isdefined( eInflictor.threwBack ); + } + + if( isdefined( eAttacker ) && isPlayer( eAttacker ) && eAttacker != self ) + { + self globallogic_score::setInflictorStat( eInflictor, eAttacker, weapon ); + } + } + + if( sMeansOfDeath == "MOD_IMPACT" && isdefined( eAttacker ) && isPlayer( eAttacker ) && eAttacker != self ) + { + if ( weapon != level.weaponBallisticKnife ) + { + self globallogic_score::setInflictorStat( eInflictor, eAttacker, weapon ); + } + + if ( weapon.rootweapon.name == "hatchet" && isdefined( eInflictor ) ) + { + self.explosiveInfo["projectile_bounced"] = isdefined( eInflictor.bounced ); + } + } + + return true; +} + +function player_damage_is_friendly_fire_at_round_start() +{ + //check for friendly fire at the begining of the match. apply the damage to the attacker only + if( level.friendlyFireDelay && level.friendlyFireDelayTime >= ( ( ( gettime() - level.startTime ) - level.discardTime ) / 1000 ) ) + { + return true; + } + + return false; +} + +function player_damage_does_friendly_fire_damage_attacker( eAttacker, ignore_round_start_friendly_fire ) +{ + if ( !IsAlive( eAttacker ) ) + return false; + + friendlyfire = [[ level.figure_out_friendly_fire ]]( self ); + + if ( friendlyfire == 1 ) // the friendly takes damage + { + //check for friendly fire at the begining of the match. apply the damage to the attacker only + if ( player_damage_is_friendly_fire_at_round_start() && ( ignore_round_start_friendly_fire == false ) ) + { + return true; + } + } + + if ( friendlyfire == 2 ) // only the attacker takes damage + { + return true; + } + + if ( friendlyfire == 3 ) // both friendly and attacker take damage + { + return true; + } + + return false; +} + +function player_damage_does_friendly_fire_damage_victim( ignore_round_start_friendly_fire ) +{ + friendlyfire = [[ level.figure_out_friendly_fire ]]( self ); + + if ( friendlyfire == 1 ) // the friendly takes damage + { + //check for friendly fire at the begining of the match. apply the damage to the attacker only + if ( player_damage_is_friendly_fire_at_round_start() && ( ignore_round_start_friendly_fire == false ) ) + { + return false; + } + + return true; + } + + if ( friendlyfire == 3 ) // both friendly and attacker take damage + { + return true; + } + + return false; +} + +function player_damage_riotshield_hit( eAttacker, iDamage, sMeansOfDeath, weapon, attackerIsHittingTeammate) +{ + if (( sMeansOfDeath == "MOD_PISTOL_BULLET" || sMeansOfDeath == "MOD_RIFLE_BULLET" ) && + ( !killstreaks::is_killstreak_weapon( weapon )) && + ( !attackerIsHittingTeammate ) ) + { + if ( self.hasRiotShieldEquipped ) + { + if ( isPlayer( eAttacker )) + { + eAttacker.lastAttackedShieldPlayer = self; + eAttacker.lastAttackedShieldTime = getTime(); + } + + previous_shield_damage = self.shieldDamageBlocked; + self.shieldDamageBlocked += iDamage; + + if (( self.shieldDamageBlocked % 400 /*riotshield_damage_score_threshold*/ ) < ( previous_shield_damage % 400 /*riotshield_damage_score_threshold*/ )) + { + score_event = "shield_blocked_damage"; + + if (( self.shieldDamageBlocked > 2000 /*riotshield_damage_score_max*/ )) + { + score_event = "shield_blocked_damage_reduced"; + } + + if ( isdefined( level.scoreInfo[ score_event ]["value"] ) ) + { + // need to get the actual riot shield weapon here + self AddWeaponStat( level.weaponRiotshield, "score_from_blocked_damage", level.scoreInfo[ score_event ]["value"] ); + } + + scoreevents::processScoreEvent( score_event, self ); + } + } + } + +} + +function does_player_completely_avoid_damage(iDFlags, sHitLoc, weapon, friendlyFire, attackerIsHittingSelf, sMeansOfDeath ) +{ + if( iDFlags & 2048 ) + return true; + + if ( friendlyFire && level.friendlyfire == 0 ) + return true; + + if ( sHitLoc == "riotshield" ) + { + if ( !(iDFlags & (32|128)) ) + return true; + } + + + if( weapon.isEmp && sMeansOfDeath == "MOD_GRENADE_SPLASH" ) + { + if( self hasperk("specialty_immuneemp") ) + return true; + } + + if ( isdefined( level.playerAvoidDamageGameMode ) && self [[ level.playerAvoidDamageGameMode ]]( iDFlags, sHitLoc, weapon, friendlyFire, attackerIsHittingSelf, sMeansOfDeath ) ) + return true; + + return false; +} + +function player_damage_log( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) +{ + pixbeginevent( "PlayerDamage log" ); + +/# + // Do debug print if it's enabled + if(GetDvarint( "g_debugDamage")) + println("client:" + self getEntityNumber() + " health:" + self.health + " attacker:" + eAttacker.clientid + " inflictor is player:" + isPlayer(eInflictor) + " damage:" + iDamage + " hitLoc:" + sHitLoc); +#/ + + if(self.sessionstate != "dead") + { + lpselfnum = self getEntityNumber(); + lpselfname = self.name; + lpselfteam = self.team; + lpselfGuid = self getGuid(); + lpattackerteam = ""; + lpattackerorigin = ( 0, 0, 0 ); + + if(isPlayer(eAttacker)) + { + lpattacknum = eAttacker getEntityNumber(); + lpattackGuid = eAttacker getGuid(); + lpattackname = eAttacker.name; + lpattackerteam = eAttacker.team; + lpattackerorigin = eAttacker.origin; + isusingheropower = 0; + + if ( eAttacker ability_player::is_using_any_gadget() ) + isusingheropower = 1; + + bbPrint( "mpattacks", "gametime %d attackerspawnid %d attackerweapon %s attackerx %d attackery %d attackerz %d victimspawnid %d victimx %d victimy %d victimz %d damage %d damagetype %s damagelocation %s death %d isusingheropower %d", + gettime(), getplayerspawnid( eAttacker ), weapon.name, lpattackerorigin, getplayerspawnid( self ), self.origin, iDamage, sMeansOfDeath, sHitLoc, 0, isusingheropower ); + } + else + { + lpattacknum = -1; + lpattackGuid = ""; + lpattackname = ""; + lpattackerteam = "world"; + bbPrint( "mpattacks", "gametime %d attackerweapon %s victimspawnid %d victimx %d victimy %d victimz %d damage %d damagetype %s damagelocation %s death %d isusingheropower %d", + gettime(), weapon.name, getplayerspawnid( self ), self.origin, iDamage, sMeansOfDeath, sHitLoc, 0, 0 ); + } + /#logPrint("D;" + lpselfGuid + ";" + lpselfnum + ";" + lpselfteam + ";" + lpselfname + ";" + lpattackGuid + ";" + lpattacknum + ";" + lpattackerteam + ";" + lpattackname + ";" + weapon.name + ";" + iDamage + ";" + sMeansOfDeath + ";" + sHitLoc + "\n");#/ + } + + pixendevent(); // "END: PlayerDamage log" +} + +function should_allow_postgame_damage( sMeansOfDeath ) +{ + if ( sMeansOfDeath == "MOD_TRIGGER_HURT" || sMeansOfDeath == "MOD_CRUSH" ) + return true; + + return false; +} + +function do_post_game_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ) +{ + if ( game["state"] != "postgame" ) + return; + + if ( !should_allow_postgame_damage( sMeansOfDeath ) ) + return; + + // just pass it along + self finishPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, "MOD_POST_GAME", weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ); +} + +function Callback_PlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ) +{ + profilelog_begintiming( 6, "ship" ); + + do_post_game_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ); + + if ( sMeansOfDeath == "MOD_CRUSH" && isdefined( eInflictor ) && ( eInflictor.deal_no_crush_damage === true ) ) + { + return; + } + + if ( isdefined( eInflictor ) && eInflictor.killstreakType === "siegebot" ) + { + if ( eInflictor.team === "neutral" ) + return; + } + + self.iDFlags = iDFlags; + self.iDFlagsTime = getTime(); + + // determine if we should treat owner damage as friendly fire + if ( !IsPlayer( eAttacker ) && isdefined( eAttacker ) && eAttacker.owner === self ) + { + treat_self_damage_as_friendly_fire = eAttacker.treat_owner_damage_as_friendly_fire; + } + + // determine if we should ignore_round_start_friendly_fire + ignore_round_start_friendly_fire = ( isdefined( eInflictor ) && ( sMeansOfDeath == "MOD_CRUSH" ) || sMeansOfDeath == "MOD_HIT_BY_OBJECT" ); + + eAttacker = figure_out_attacker( eAttacker ); + + // no damage from people who have dropped into laststand + if ( IsPlayer( eAttacker ) && ( isdefined( eAttacker.laststand ) && eAttacker.laststand ) ) + { + return; + } + + sMeansOfDeath = modify_player_damage_meansofdeath( eInflictor, eAttacker, sMeansOfDeath, weapon, sHitLoc ); + + if ( !(self should_do_player_damage( eAttacker, weapon, sMeansOfDeath, iDFlags )) ) + return; + + player_damage_update_attacker( eInflictor, eAttacker, sMeansOfDeath ); + + weapon = player_damage_figure_out_weapon( weapon, eInflictor ); + + pixbeginevent( "PlayerDamage flags/tweaks" ); + + // Don't do knockback if the damage direction was not specified + if( !isdefined( vDir ) ) + iDFlags |= 4; + + attackerIsHittingTeammate = isPlayer( eAttacker ) && ( self util::IsEnemyPlayer( eAttacker ) == false ); + attackerIsHittingSelf = IsPlayer( eAttacker ) && (self == eAttacker); + + friendlyFire = ( ( attackerIsHittingSelf && treat_self_damage_as_friendly_fire === true ) // some killstreaks treak owner damage as friendly-fire + || ( level.teamBased && !attackerIsHittingSelf && attackerIsHittingTeammate ) ); // teammates are always friendly-fire, but self is handled above + + pixendevent(); // "END: PlayerDamage flags/tweaks" + + iDamage = modify_player_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ); + if ( friendlyFire ) + { + iDamage = modify_player_damage_friendlyfire( iDamage ); + } + + if( ( isdefined( self.power_armor_took_damage ) && self.power_armor_took_damage ) ) + { + iDFlags |= 1024; + } + + if ( sHitLoc == "riotshield" ) + { + // do we want all of the damage modifiers that get applied for the player to get applied to this damage? + // or friendly fire? + player_damage_riotshield_hit( eAttacker, iDamage, sMeansOfDeath, weapon, attackerIsHittingTeammate); + } + + // check for completely getting out of the damage + if ( self does_player_completely_avoid_damage(iDFlags, sHitLoc, weapon, friendlyFire, attackerIsHittingSelf, sMeansOfDeath ) ) + { + return; + } + + // do we want this called pre or post damage application? + self callback::callback( #"on_player_damage" ); + + armor = self armor::getArmor(); + + iDamage = apply_damage_to_armor( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, sHitLoc, friendlyFire, ignore_round_start_friendly_fire ); + iDamage = make_sure_damage_is_not_zero( iDamage ); + + armor_damaged = (armor != self armor::getArmor()); + + // this must be below the damage modification functions as they use this to determine riotshield hits + if ( sHitLoc == "riotshield" ) + { + sHitLoc = "none"; // code ignores any damage to a "shield" bodypart. + } + + if ( !player_damage_update_explosive_info( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) ) + return; + + prevHealthRatio = self.health / self.maxhealth; + + if ( friendlyFire ) + { + pixmarker( "BEGIN: PlayerDamage player" ); // profs automatically end when the function returns + + if ( player_damage_does_friendly_fire_damage_victim( ignore_round_start_friendly_fire ) ) + { + self.lastDamageWasFromEnemy = false; + + self finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal); + } + else if ( weapon.forceDamageShellshockAndRumble ) + { + self damageShellshockAndRumble( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage ); + } + + if ( player_damage_does_friendly_fire_damage_attacker( eAttacker, ignore_round_start_friendly_fire ) ) + { + eAttacker.lastDamageWasFromEnemy = false; + + eAttacker.friendlydamage = true; + eAttacker finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal); + eAttacker.friendlydamage = undefined; + } + + pixmarker( "END: PlayerDamage player" ); + } + else + { + behaviorTracker::UpdatePlayerDamage( eAttacker, self, iDamage ); + + self.lastAttackWeapon = weapon; + + giveAttackerAndInflictorOwnerAssist( eAttacker, eInflictor, iDamage, sMeansOfDeath, weapon ); + + if ( isdefined( eAttacker ) ) + level.lastLegitimateAttacker = eAttacker; + + if ( ( sMeansOfDeath == "MOD_GRENADE" || sMeansOfDeath == "MOD_GRENADE_SPLASH" ) && isdefined( eInflictor ) && isdefined( eInflictor.isCooked ) ) + self.wasCooked = getTime(); + else + self.wasCooked = undefined; + + self.lastDamageWasFromEnemy = (isdefined( eAttacker ) && (eAttacker != self)); + + if ( self.lastDamageWasFromEnemy ) + { + if ( isplayer( eAttacker ) ) + { + if ( isdefined ( eAttacker.damagedPlayers[ self.clientId ] ) == false ) + eAttacker.damagedPlayers[ self.clientId ] = spawnstruct(); + + eAttacker.damagedPlayers[ self.clientId ].time = getTime(); + eAttacker.damagedPlayers[ self.clientId ].entity = self; + } + } + + if( isPlayer( eAttacker ) && isDefined(weapon.gadget_type) && weapon.gadget_type == 14 ) + { + if( isDefined(eAttacker.heroweaponHits) ) + { + eAttacker.heroweaponHits++; + } + } + + self finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal); + } + + if ( isdefined( eAttacker ) && !attackerIsHittingSelf ) + { + if ( damagefeedback::doDamageFeedback( weapon, eInflictor, iDamage, sMeansOfDeath ) ) + { + // the perk feedback should be shown only if the enemy is damaged and not killed. + if ( iDamage > 0 && self.health > 0 ) + { + perkFeedback = doPerkFeedBack( self, weapon, sMeansOfDeath, eInflictor, armor_damaged ); + } + + eAttacker thread damagefeedback::update( sMeansOfDeath, eInflictor, perkFeedback, weapon, self, psOffsetTime, sHitLoc ); + } + } + + if( !isdefined(eAttacker) || !friendlyFire || ( isdefined( level.hardcoreMode ) && level.hardcoreMode ) ) + { + if ( isdefined( level.customPlayPainSound ) ) + self [[ level.customPlayPainSound ]]( sMeansOfDeath ); + else + self battlechatter::pain_vox( sMeansOfDeath ); + } + + self.hasDoneCombat = true; + + if( weapon.isEmp && sMeansOfDeath == "MOD_GRENADE_SPLASH" ) + { + if( !self hasperk("specialty_immuneemp") ) + { + self notify( "emp_grenaded", eAttacker, vPoint ); + } + } + + if ( isdefined( eAttacker ) && eAttacker != self && !friendlyFire ) + level.useStartSpawns = false; + + player_damage_log( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ); + + profilelog_endtiming( 6, "gs=" + game["state"] + " zom=" + SessionModeIsZombiesGame() ); +} + +function resetAttackerList() +{ + self.attackers = []; + self.attackerData = []; + self.attackerDamage = []; + self.firstTimeDamaged = 0; +} + +function resetAttackersThisSpawnList() +{ + self.attackersThisSpawn = []; +} + +function doPerkFeedBack( player, weapon, sMeansOfDeath, eInflictor, armor_damaged ) +{ + perkFeedback = undefined; + hasTacticalMask = loadout::hasTacticalMask( player ); + hasFlakJacket = ( player HasPerk( "specialty_flakjacket" ) ); + isExplosiveDamage = loadout::isExplosiveDamage( sMeansOfDeath ); + isFlashOrStunDamage = weapon_utils::isFlashOrStunDamage( weapon, sMeansOfDeath ); + + if ( isFlashOrStunDamage && hasTacticalMask ) + { + perkFeedback = "tacticalMask"; + } + else if ( player HasPerk( "specialty_fireproof" ) && loadout::isFireDamage( weapon, sMeansOfDeath ) ) + { + perkFeedback = "flakjacket"; + } + else if ( isExplosiveDamage && hasFlakJacket && !weapon.ignoresFlakJacket && ( !isAIKillstreakDamage( weapon, eInflictor ) ) ) + { + perkFeedback = "flakjacket"; + } + else if ( armor_damaged ) + { + perkFeedback = "armor"; + } + + return perkFeedback; +} + +function isAIKillstreakDamage( weapon, eInflictor ) +{ + if ( weapon.isAIKillstreakDamage ) + { + if ( weapon.name != "ai_tank_drone_rocket" || isdefined( eInflictor.firedByAI ) ) + { + return true; + } + } + + return false; +} + +function finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ) +{ + pixbeginevent("finishPlayerDamageWrapper"); + + if( !level.console && iDFlags & 8 && isplayer ( eAttacker ) ) + { + /# + println("penetrated:" + self getEntityNumber() + " health:" + self.health + " attacker:" + eAttacker.clientid + " inflictor is player:" + isPlayer(eInflictor) + " damage:" + iDamage + " hitLoc:" + sHitLoc); + #/ + eAttacker AddPlayerStat( "penetration_shots", 1 ); + } + + if ( GetDvarString( "scr_csmode" ) != "" ) + self shellShock( "damage_mp", 0.2 ); + + if ( isdefined( level.customDamageShellshockAndRumble ) ) + self [[ level.customDamageShellshockAndRumble ]]( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage, vPoint ); + else + self damageShellshockAndRumble( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage ); + + self ability_power::power_loss_event_took_damage( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage ); + + if( isPlayer( eAttacker) ) + { + self.lastShotBy = eAttacker.clientid; + } + + if ( sMeansOfDeath == "MOD_BURNED" ) + { + self burnplayer::TakingBurnDamage( eAttacker, weapon, sMeansOfDeath ); + } + + self.gadget_was_active_last_damage = self GadgetIsActive( 0 ); + + self finishPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ); + + pixendevent(); +} + +function allowedAssistWeapon( weapon ) +{ + if ( !killstreaks::is_killstreak_weapon( weapon ) ) + return true; + + if (killstreaks::is_killstreak_weapon_assist_allowed( weapon ) ) + return true; + + return false; +} + +function PlayerKilled_Killstreaks( attacker, weapon ) +{ + if( !isdefined( self.switching_teams ) ) + { + // if team killed we reset kill streak, but dont count death and death streak + if ( isPlayer( attacker ) && level.teamBased && ( attacker != self ) && ( self.team == attacker.team ) ) + { + + self.pers["cur_kill_streak"] = 0; + self.pers["cur_total_kill_streak"] = 0; + self.pers["totalKillstreakCount"] = 0; + self.pers["killstreaksEarnedThisKillstreak"] = 0; + self setplayercurrentstreak( 0 ); + } + else + { + self globallogic_score::incPersStat( "deaths", 1, true, true ); + self.deaths = self globallogic_score::getPersStat( "deaths" ); + self UpdateStatRatio( "kdratio", "kills", "deaths" ); + + if( self.pers["cur_kill_streak"] > self.pers["best_kill_streak"] ) + self.pers["best_kill_streak"] = self.pers["cur_kill_streak"]; + + // need to keep the current killstreak to see if this was a buzzkill later + self.pers["kill_streak_before_death"] = self.pers["cur_kill_streak"]; + + + self.pers["cur_kill_streak"] = 0; + self.pers["cur_total_kill_streak"] = 0; + self.pers["totalKillstreakCount"] = 0; + self.pers["killstreaksEarnedThisKillstreak"] = 0; + self setplayercurrentstreak( 0 ); + + self.cur_death_streak++; + + if ( self.cur_death_streak > self.death_streak ) + { + if ( level.rankedMatch && !level.disableStatTracking ) + { + self setDStat( "HighestStats", "death_streak", self.cur_death_streak ); + } + self.death_streak = self.cur_death_streak; + } + + if( self.cur_death_streak >= GetDvarint( "perk_deathStreakCountRequired" ) ) + { + self enabledeathstreak(); + } + } + } + else + { + self.pers["totalKillstreakCount"] = 0; + self.pers["killstreaksEarnedThisKillstreak"] = 0; + } + + if ( !SessionModeIsZombiesGame() && killstreaks::is_killstreak_weapon( weapon ) ) + { + level.globalKillstreaksDeathsFrom++; + } +} + +function PlayerKilled_WeaponStats( attacker, weapon, sMeansOfDeath, wasInLastStand, lastWeaponBeforeDroppingIntoLastStand, inflictor ) +{ + // Don't increment weapon stats for team kills or deaths + if ( isPlayer( attacker ) && attacker != self && ( !level.teamBased || ( level.teamBased && self.team != attacker.team ) ) ) + { + attackerWeaponPickedUp = false; + if( isdefined( attacker.pickedUpWeapons ) && isdefined( attacker.pickedUpWeapons[weapon] ) ) + { + attackerWeaponPickedUp = true; + } + self AddWeaponStat( weapon, "deaths", 1, self.class_num, attackerWeaponPickedUp, undefined, self.primaryLoadoutGunSmithVariantIndex, self.secondaryLoadoutGunSmithVariantIndex ); + + if ( wasInLastStand && isdefined( lastWeaponBeforeDroppingIntoLastStand ) ) + victim_weapon = lastWeaponBeforeDroppingIntoLastStand; + else + victim_weapon = self.lastdroppableweapon; + + if ( isdefined( victim_weapon ) ) + { + victimWeaponPickedUp = false; + if( isdefined( self.pickedUpWeapons ) && isdefined( self.pickedUpWeapons[victim_weapon] ) ) + { + victimWeaponPickedUp = true; + } + self AddWeaponStat( victim_weapon, "deathsDuringUse", 1, self.class_num, victimWeaponPickedUp, undefined, self.primaryLoadoutGunSmithVariantIndex, self.secondaryLoadoutGunSmithVariantIndex ); + } + + + recordWeaponStatKills = true; + if ( ( attacker.isThief === true ) && isdefined( weapon ) && ( weapon.isHeroWeapon === true ) ) + { + recordWeaponStatKills = false; // Blackjack's Rogue kills are tracked as specialiststats[9].stats.kills_weapon + } + + if ( sMeansOfDeath != "MOD_FALLING" && recordWeaponStatKills ) + { + if ( weapon.name == "explosive_bolt" && IsDefined( inflictor ) && IsDefined( inflictor.ownerWeaponAtLaunch ) && inflictor.ownerAdsAtLaunch ) + { + inflictorOwnerWeaponAtLaunchPickedUp = false; + if( isdefined( attacker.pickedUpWeapons ) && isdefined( attacker.pickedUpWeapons[inflictor.ownerWeaponAtLaunch] ) ) + { + inflictorOwnerWeaponAtLaunchPickedUp = true; // ever the case? + } + attacker AddWeaponStat( inflictor.ownerWeaponAtLaunch, "kills", 1, attacker.class_num, inflictorOwnerWeaponAtLaunchPickedUp, true, attacker.primaryLoadoutGunSmithVariantIndex, attacker.secondaryLoadoutGunSmithVariantIndex ); + } + else + { + if ( isdefined( attacker ) && isdefined( attacker.class_num ) ) + attacker AddWeaponStat( weapon, "kills", 1, attacker.class_num, attackerWeaponPickedUp, undefined, attacker.primaryLoadoutGunSmithVariantIndex, attacker.secondaryLoadoutGunSmithVariantIndex ); + } + } + + if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + { + attacker AddWeaponStat( weapon, "headshots", 1, attacker.class_num, attackerWeaponPickedUp, undefined, attacker.primaryLoadoutGunSmithVariantIndex, attacker.secondaryLoadoutGunSmithVariantIndex ); + } + + if ( sMeansOfDeath == "MOD_PROJECTILE" || ( ( sMeansOfDeath == "MOD_GRENADE" || sMeansOfDeath == "MOD_IMPACT" ) && weapon.rootWeapon.statIndex == level.weaponLauncherEx41.statIndex ) ) + { + attacker AddWeaponStat( weapon, "direct_hit_kills", 1 ); + } + + victimIsRoulette = ( self.isRoulette === true ); + if ( self ability_player::gadget_CheckHeroAbilityKill( attacker ) && !victimIsRoulette ) + { + attacker AddWeaponStat( attacker.heroAbility, "kills_while_active", 1 ); + } + } +} + +function PlayerKilled_Obituary( attacker, eInflictor, weapon, sMeansOfDeath ) +{ + if ( !isplayer( attacker ) || ( self util::IsEnemyPlayer( attacker ) == false ) || ( isdefined ( weapon ) && killstreaks::is_killstreak_weapon( weapon ) ) ) + { + level notify( "reset_obituary_count" ); + level.lastObituaryPlayerCount = 0; + level.lastObituaryPlayer = undefined; + } + else + { + if ( isdefined( level.lastObituaryPlayer ) && level.lastObituaryPlayer == attacker ) + { + level.lastObituaryPlayerCount++; + } + else + { + level notify( "reset_obituary_count" ); + level.lastObituaryPlayer = attacker; + level.lastObituaryPlayerCount = 1; + } + + level thread scoreevents::decrementLastObituaryPlayerCountAfterFade(); + + if ( level.lastObituaryPlayerCount >= 4 ) + { + level notify( "reset_obituary_count" ); + level.lastObituaryPlayerCount = 0; + level.lastObituaryPlayer = undefined; + self thread scoreevents::uninterruptedObitFeedKills( attacker, weapon ); + } + } + + if ( !isplayer( attacker ) || ( isdefined( weapon ) && !killstreaks::is_killstreak_weapon( weapon ) ) ) + { + behaviorTracker::UpdatePlayerKilled( attacker, self ); + } + + overrideEntityCamera = killstreaks::should_override_entity_camera_in_demo( attacker, weapon ); + + if( isdefined( eInflictor ) && ( eInflictor.archetype === "robot" ) ) + { + if( sMeansOfDeath == "MOD_HIT_BY_OBJECT" ) + weapon = GetWeapon( "combat_robot_marker" ); + sMeansOfDeath = "MOD_RIFLE_BULLET"; + } + // send out an obituary message to all clients about the kill + if( level.teamBased && isdefined( attacker.pers ) && self.team == attacker.team && sMeansOfDeath == "MOD_GRENADE" && level.friendlyfire == 0 ) + { + obituary(self, self, weapon, sMeansOfDeath); + demo::bookmark( "kill", gettime(), self, self, 0, eInflictor, overrideEntityCamera ); + } + else + { + obituary(self, attacker, weapon, sMeansOfDeath); + demo::bookmark( "kill", gettime(), attacker, self, 0, eInflictor, overrideEntityCamera ); + } +} + +function PlayerKilled_Suicide( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ) +{ + awardAssists = false; + self.suicide = false; + + // switching teams + if ( isdefined( self.switching_teams ) ) + { + + if ( !level.teamBased && ( isdefined( level.teams[ self.leaving_team ] ) && isdefined( level.teams[ self.joining_team ] ) && level.teams[ self.leaving_team ] != level.teams[ self.joining_team ] ) ) + { + playerCounts = self teams::count_players(); + playerCounts[self.leaving_team]--; + playerCounts[self.joining_team]++; + + if( (playerCounts[self.joining_team] - playerCounts[self.leaving_team]) > 1 ) + { + scoreevents::processScoreEvent( "suicide", self ); + self thread rank::giveRankXP( "suicide" ); + self globallogic_score::incPersStat( "suicides", 1 ); + self.suicides = self globallogic_score::getPersStat( "suicides" ); + self.suicide = true; + } + } + } + else + { + scoreevents::processScoreEvent( "suicide", self ); + self globallogic_score::incPersStat( "suicides", 1 ); + self.suicides = self globallogic_score::getPersStat( "suicides" ); + + if ( sMeansOfDeath == "MOD_SUICIDE" && sHitLoc == "none" && self.throwingGrenade ) + { + self.lastGrenadeSuicideTime = gettime(); + } + + if ( level.maxSuicidesBeforeKick > 0 && level.maxSuicidesBeforeKick <= self.suicides ) + { + // should change "teamKillKicked" to just kicked for the next game + self notify( "teamKillKicked" ); + self SuicideKick(); + } + + //Check for player death related battlechatter + thread battlechatter::on_player_suicide_or_team_kill( self, "suicide" ); //Play suicide battlechatter + + //check if assist points should be awarded + awardAssists = true; + self.suicide = true; + } + + if( isdefined( self.friendlydamage ) ) + { + self iPrintLn(&"MP_FRIENDLY_FIRE_WILL_NOT"); + if ( level.teamKillPointLoss ) + { + scoreSub = self [[level.getTeamKillScore]]( eInflictor, attacker, sMeansOfDeath, weapon); + + score = globallogic_score::_getPlayerScore( attacker ) - scoreSub; + + if ( score < 0 ) + score = 0; + + globallogic_score::_setPlayerScore( attacker, score ); + } + } + + return awardAssists; +} + +function PlayerKilled_TeamKill( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ) +{ + scoreevents::processScoreEvent( "team_kill", attacker ); + + self.teamKilled = true; + + if ( !IgnoreTeamKills( weapon, sMeansOfDeath, eInflictor ) ) + { + teamkill_penalty = self [[level.getTeamKillPenalty]]( eInflictor, attacker, sMeansOfDeath, weapon); + + attacker globallogic_score::incPersStat( "teamkills_nostats", teamkill_penalty, false ); + attacker globallogic_score::incPersStat( "teamkills", 1 ); //save team kills to player stats + attacker.teamkillsThisRound++; + + if ( level.teamKillPointLoss ) + { + scoreSub = self [[level.getTeamKillScore]]( eInflictor, attacker, sMeansOfDeath, weapon); + + score = globallogic_score::_getPlayerScore( attacker ) - scoreSub; + + if ( score < 0 ) + { + score = 0; + } + + globallogic_score::_setPlayerScore( attacker, score ); + } + + if ( globallogic_utils::getTimePassed() < 5000 ) + teamKillDelay = 1; + else if ( attacker.pers["teamkills_nostats"] > 1 && globallogic_utils::getTimePassed() < (8000 + (attacker.pers["teamkills_nostats"] * 1000)) ) + teamKillDelay = 1; + else + teamKillDelay = attacker TeamKillDelay(); + + if ( teamKillDelay > 0 ) + { + attacker.teamKillPunish = true; + attacker thread wait_and_suicide(); // can't eject the teamkilling player same frame bc it purges EV_FIRE_WEAPON fx + + if ( attacker ShouldTeamKillKick(teamKillDelay) ) + { + // should change "teamKillKicked" to just kicked for the next game + attacker notify( "teamKillKicked" ); + attacker thread TeamKillKick(); + } + + attacker thread reduceTeamKillsOverTime(); + } + + //Play teamkill battlechatter + if( isPlayer( attacker ) ) + thread battlechatter::on_player_suicide_or_team_kill( attacker, "teamkill" ); + } +} + +function wait_and_suicide() // self == player +{ + self endon( "disconnect" ); + self util::freeze_player_controls( true ); + + wait .25; + + self suicide(); +} + +function PlayerKilled_AwardAssists( eInflictor, attacker, weapon, lpattackteam ) +{ + pixbeginevent( "PlayerKilled assists" ); + + if ( isdefined( self.attackers ) ) + { + for ( j = 0; j < self.attackers.size; j++ ) + { + player = self.attackers[j]; + + if ( !isdefined( player ) ) + continue; + + if ( player == attacker ) + continue; + + if ( player.team != lpattackteam ) + continue; + + damage_done = self.attackerDamage[player.clientId].damage; + player thread globallogic_score::processAssist( self, damage_done, self.attackerDamage[player.clientId].weapon ); + } + } + + if ( level.teamBased ) + { + self globallogic_score::processKillstreakAssists( attacker, eInflictor, weapon ); + } + + if ( isdefined( self.lastAttackedShieldPlayer ) && isdefined( self.lastAttackedShieldTime ) && self.lastAttackedShieldPlayer != attacker ) + { + if ( gettime() - self.lastAttackedShieldTime < 4000 ) + { + self.lastAttackedShieldPlayer thread globallogic_score::processShieldAssist( self ); + } + } + + pixendevent(); //"END: PlayerKilled assists" +} + +function PlayerKilled_Kill( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ) +{ + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + globallogic_score::incTotalKills(attacker.team); + + if( GetDvarInt( "teamOpsEnabled" ) == 1 ) + { + if( isdefined( eInflictor ) && ( isdefined( eInflictor.teamops ) && eInflictor.teamops ) ) + { + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + globallogic_score::giveTeamScore( "kill", attacker.team, undefined, self ); + return; + } + } + + attacker thread globallogic_score::giveKillStats( sMeansOfDeath, weapon, self ); + + + if ( isAlive( attacker ) ) + { + pixbeginevent("killstreak"); + + if ( !isdefined( eInflictor ) || !isdefined( eInflictor.requiredDeathCount ) || attacker.deathCount == eInflictor.requiredDeathCount ) + { + shouldGiveKillstreak = killstreaks::should_give_killstreak( weapon ); + //attacker thread _properks::earnedAKill(); + + if ( shouldGiveKillstreak ) + { + attacker killstreaks::add_to_killstreak_count( weapon ); + } + + attacker.pers["cur_total_kill_streak"]++; + attacker setplayercurrentstreak( attacker.pers["cur_total_kill_streak"] ); + + //Kills gotten through killstreak weapons should not the players killstreak + if ( isdefined( level.killstreaks ) && shouldGiveKillstreak ) + { + attacker.pers["cur_kill_streak"]++; + + if ( attacker.pers["cur_kill_streak"] >= 2 ) + { + if ( attacker.pers["cur_kill_streak"] == 10 ) + { + attacker challenges::killstreakTen(); + } + if ( attacker.pers["cur_kill_streak"] <= 30 ) + { + scoreevents::processScoreEvent( "killstreak_" + attacker.pers["cur_kill_streak"], attacker, self, weapon ); + + if ( attacker.pers["cur_kill_streak"] == 30 ) + { + attacker challenges::killstreak_30_noscorestreaks(); + } + } + else + { + scoreevents::processScoreEvent( "killstreak_more_than_30", attacker, self, weapon ); + } + } + + if ( !isdefined( level.usingMomentum ) || !level.usingMomentum ) + { + if( GetDvarInt( "teamOpsEnabled" ) == 0 ) + attacker thread killstreaks::give_for_streak(); + } + } + } + + pixendevent(); // "killstreak" + } + + if ( attacker.pers["cur_kill_streak"] > attacker.kill_streak ) + { + if ( level.rankedMatch && !level.disableStatTracking ) + { + attacker setDStat( "HighestStats", "kill_streak", attacker.pers["totalKillstreakCount"] ); + } + attacker.kill_streak = attacker.pers["cur_kill_streak"]; + } + + + if ( attacker.pers["cur_kill_streak"] > attacker.gametype_kill_streak ) + { + attacker persistence::stat_set_with_gametype( "kill_streak", attacker.pers["cur_kill_streak"] ); + attacker.gametype_kill_streak = attacker.pers["cur_kill_streak"]; + } + + killstreak = killstreaks::get_killstreak_for_weapon( weapon ); + + if ( isdefined( killstreak ) ) + { + if ( scoreevents::isRegisteredEvent( killstreak ) ) + { + scoreevents::processScoreEvent( killstreak, attacker, self, weapon ); + } + + if( isdefined( eInflictor ) && ( killstreak == "dart" || killstreak == "inventory_dart" ) ) + { + eInflictor notify( "veh_collision" ); + } + } + else + { + scoreevents::processScoreEvent( "kill", attacker, self, weapon ); + + // if ( sMeansOfDeath == "MOD_HEAD_SHOT" || ( sMeansOfDeath == "MOD_IMPACT" && sHitLoc == "head" ) ) // TODO: add back when applicable LOOT6 weapon is ready + if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + { + scoreevents::processScoreEvent( "headshot", attacker, self, weapon ); + attacker util::player_contract_event( "headshot" ); + } + else if ( weapon_utils::isMeleeMOD( sMeansOfDeath ) ) + { + scoreevents::processScoreEvent( "melee_kill", attacker, self, weapon ); + } + } + + attacker thread globallogic_score::trackAttackerKill( self.name, self.pers["rank"], self.pers["rankxp"], self.pers["prestige"], self getXuid(), weapon ); + + attackerName = attacker.name; + self thread globallogic_score::trackAttackeeDeath( attackerName, attacker.pers["rank"], attacker.pers["rankxp"], attacker.pers["prestige"], attacker getXuid() ); + self thread medals::setLastKilledBy( attacker ); + + attacker thread globallogic_score::incKillstreakTracker( weapon ); + + // to prevent spectator gain score for team-spectator after throwing a granade and killing someone before he switched + if ( level.teamBased && attacker.team != "spectator") + { + if( !isdefined( killstreak ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + globallogic_score::giveTeamScore( "kill", attacker.team, attacker, self ); + } + + scoreSub = level.deathPointLoss; + if ( scoreSub != 0 ) + { + globallogic_score::_setPlayerScore( self, globallogic_score::_getPlayerScore( self ) - scoreSub ); + } + + level thread playKillBattleChatter( attacker, weapon, self, eInflictor ); +} + +function should_allow_postgame_death( sMeansOfDeath ) +{ + if ( sMeansOfDeath == "MOD_POST_GAME" ) + return true; + + return false; +} + +function do_post_game_death(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration) +{ + if ( !should_allow_postgame_death( sMeansOfDeath ) ) + return; + + self weapons::detach_carry_object_model(); + + self.sessionstate = "dead"; + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + + clone_weapon = weapon; + + // we do not want the weapon death fx to play if this is not a melee weapon and its a melee attack + // ideally the mod be passed to the client side and let it decide but this is post ship t7 and this is safest + if ( weapon_utils::isMeleeMOD(sMeansOfDeath) && clone_weapon.type != "melee" ) + { + clone_weapon = level.weaponNone; + } + body = self clonePlayer( deathAnimDuration, clone_weapon, attacker ); + + if ( isdefined( body ) ) + { + self createDeadBody( attacker, iDamage, sMeansOfDeath, weapon, sHitLoc, vDir, (0,0,0), deathAnimDuration, eInflictor, body ); + } +} + +function Callback_PlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration, enteredResurrect = false) +{ + profilelog_begintiming( 7, "ship" ); + + self endon( "spawned" ); + + + if ( game["state"] == "postgame" ) + { + do_post_game_death(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + return; + } + + if ( self.sessionteam == "spectator" ) + return; + + self notify( "killed_player" ); + self callback::callback( #"on_player_killed" ); + + self needsRevive( false ); + + if ( isdefined( self.burning ) && self.burning == true ) + { + self setburn( 0 ); + } + + self.suicide = false; + self.teamKilled = false; + + if ( isdefined( level.takeLivesOnDeath ) && ( level.takeLivesOnDeath == true ) ) + { + if ( self.pers["lives"] ) + { + self.pers["lives"]--; + if ( self.pers["lives"] == 0 ) + { + level notify( "player_eliminated" ); + self notify( "player_eliminated" ); + } + } + if ( game[self.team + "_lives"] ) + { + game[self.team + "_lives"]--; + if ( game[self.team + "_lives"] == 0 ) + { + level notify( "player_eliminated" ); + self notify( "player_eliminated" ); + } + } + } + + self thread globallogic_audio::flush_leader_dialog_key_on_player( "equipmentDestroyed" ); + //self thread globallogic_audio::flush_leader_dialog_key_on_player( "equipmentHacked" ); + + weapon = updateWeapon( eInflictor, weapon ); + + pixbeginevent( "PlayerKilled pre constants" ); + + wasInLastStand = false; + bledOut = false; + deathTimeOffset = 0; + lastWeaponBeforeDroppingIntoLastStand = undefined; + attackerStance = undefined; + self.lastStandThisLife = undefined; + self.vAttackerOrigin = undefined; + + // need to get this before changing the sessionstate + weapon_at_time_of_death = self GetCurrentWeapon(); + + if ( isdefined( self.useLastStandParams ) && enteredResurrect == false ) + { + self.useLastStandParams = undefined; + + assert( isdefined( self.lastStandParams ) ); + if ( !level.teamBased || ( !isdefined( attacker ) || !isplayer( attacker ) || attacker.team != self.team || attacker == self ) ) + { + eInflictor = self.lastStandParams.eInflictor; + attacker = self.lastStandParams.attacker; + attackerStance = self.lastStandParams.attackerStance; + iDamage = self.lastStandParams.iDamage; + sMeansOfDeath = self.lastStandParams.sMeansOfDeath; + weapon = self.lastStandParams.sWeapon; + vDir = self.lastStandParams.vDir; + sHitLoc = self.lastStandParams.sHitLoc; + self.vAttackerOrigin = self.lastStandParams.vAttackerOrigin; + self.killcam_entity_info_cached = self.lastStandParams.killcam_entity_info_cached; + deathTimeOffset = (gettime() - self.lastStandParams.lastStandStartTime) / 1000; + bledOut = true; + if ( isdefined( self.previousPrimary ) ) + { + wasInLastStand = true; + lastWeaponBeforeDroppingIntoLastStand = self.previousPrimary; + } + } + self.lastStandParams = undefined; + } + + self StopSounds(); + + bestPlayer = undefined; + bestPlayerMeansOfDeath = undefined; + obituaryMeansOfDeath = undefined; + bestPlayerWeapon = undefined; + obituaryWeapon = weapon; + assistedSuicide = false; + + if ( isdefined( level.gameModeAssistedSuicide ) ) + { + result = self [[ level.gameModeAssistedSuicide ]]( attacker, sMeansOfDeath, weapon ); + if ( isdefined( result ) ) + { + bestPlayer = result["bestPlayer"]; + bestPlayerMeansOfDeath = result["bestPlayerMeansOfDeath"]; + bestPlayerWeapon = result["bestPlayerWeapon"]; + } + } + + if ( (!isdefined( attacker ) || attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" || ( isdefined( attacker.isMagicBullet ) && attacker.isMagicBullet == true ) || attacker == self ) && isdefined( self.attackers ) && !self IsPlayerUnderwater() ) + { + if ( !isdefined(bestPlayer) ) + { + for ( i = 0; i < self.attackers.size; i++ ) + { + player = self.attackers[i]; + if ( !isdefined( player ) ) + continue; + + if (!isdefined( self.attackerDamage[ player.clientId ] ) || ! isdefined( self.attackerDamage[ player.clientId ].damage ) ) + continue; + + if ( player == self || (level.teamBased && player.team == self.team ) ) + continue; + + if ( self.attackerDamage[ player.clientId ].lasttimedamaged + 2500 < getTime() ) + continue; + + if ( !allowedAssistWeapon( self.attackerDamage[ player.clientId ].weapon ) ) + continue; + + if ( self.attackerDamage[ player.clientId ].damage > 1 && ! isdefined( bestPlayer ) ) + { + bestPlayer = player; + bestPlayerMeansOfDeath = self.attackerDamage[ player.clientId ].meansOfDeath; + bestPlayerWeapon = self.attackerDamage[ player.clientId ].weapon; + } + else if ( isdefined( bestPlayer ) && self.attackerDamage[ player.clientId ].damage > self.attackerDamage[ bestPlayer.clientId ].damage ) + { + bestPlayer = player; + bestPlayerMeansOfDeath = self.attackerDamage[ player.clientId ].meansOfDeath; + bestPlayerWeapon = self.attackerDamage[ player.clientId ].weapon; + } + } + } + if ( isdefined ( bestPlayer ) ) + { + scoreevents::processScoreEvent( "assisted_suicide", bestPlayer, self, weapon ); + self RecordKillModifier("assistedsuicide"); + assistedSuicide = true; + } + } + + if ( isdefined ( bestPlayer ) ) + { + attacker = bestPlayer; + obituaryMeansOfDeath = bestPlayerMeansOfDeath; + obituaryWeapon = bestPlayerWeapon; + if ( isdefined( bestPlayerWeapon ) ) + { + weapon = bestPlayerWeapon; + } + } + + if ( isplayer( attacker ) && isdefined( attacker.damagedPlayers ) ) + attacker.damagedPlayers[self.clientid] = undefined; + + if ( enteredResurrect == false ) + { + globallogic::DoWeaponSpecificKillEffects(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime); + } + + self.deathTime = getTime(); + + if ( attacker != self && (!level.teamBased || attacker.team != self.team )) + { + assert( IsDefined( self.lastspawntime ) ); + self.aliveTimes[self.aliveTimeCurrentIndex] = self.deathTime - self.lastspawntime; + self.aliveTimeCurrentIndex = (self.aliveTimeCurrentIndex + 1) % level.aliveTimeMaxCount; + } + + attacker = updateAttacker( attacker, weapon ); + eInflictor = updateInflictor( eInflictor ); + + sMeansOfDeath = self PlayerKilled_UpdateMeansOfDeath( attacker, eInflictor, weapon, sMeansOfDeath, sHitLoc ); + + if ( !isdefined( obituaryMeansOfDeath ) ) + obituaryMeansOfDeath = sMeansOfDeath; + + self.hasRiotShield = false; + self.hasRiotShieldEquipped = false; + + self thread updateGlobalBotKilledCounter(); + + self PlayerKilled_WeaponStats( attacker, weapon, sMeansOfDeath, wasInLastStand, lastWeaponBeforeDroppingIntoLastStand, eInflictor ); + + if ( bledOut == false ) + { + if( GetDvarInt( "teamOpsEnabled" ) == 1 && ( isdefined( eInflictor ) && ( isdefined( eInflictor.teamops ) && eInflictor.teamops ) ) ) + { + self PlayerKilled_Obituary( eInflictor, eInflictor, obituaryWeapon, obituaryMeansOfDeath ); + } + else + { + self PlayerKilled_Obituary( attacker, eInflictor, obituaryWeapon, obituaryMeansOfDeath ); + } + } + + if ( enteredResurrect == false ) + { +// spawnlogic::death_occured(self, attacker); + + self.sessionstate = "dead"; + self.statusicon = "hud_status_dead"; + } + + self.pers["weapon"] = undefined; + + self.killedPlayersCurrent = []; + + self.deathCount++; + +/# + println( "players("+self.clientId+") death count ++: " + self.deathCount ); +#/ + + if ( bledout == false ) + { + self PlayerKilled_Killstreaks( attacker, weapon ); + } + + lpselfnum = self getEntityNumber(); + lpselfname = self.name; + lpattackGuid = ""; + lpattackname = ""; + lpselfteam = self.team; + lpselfguid = self getGuid(); + lpattackteam = ""; + lpattackorigin = ( 0, 0, 0 ); + + lpattacknum = -1; + + //check if we should award assist points + awardAssists = false; + wasTeamKill = false; + wasSuicide = false; + + pixendevent(); // "PlayerKilled pre constants" ); + + scoreevents::processScoreEvent( "death", self, self, weapon ); + self.pers["resetMomentumOnSpawn"] = level.scoreResetOnDeath; + + + if( isPlayer( attacker ) ) + { + lpattackGuid = attacker getGuid(); + lpattackname = attacker.name; + lpattackteam = attacker.team; + lpattackorigin = attacker.origin; + + if ( attacker == self || assistedSuicide == true ) // killed himself + { + doKillcam = false; + wasSuicide = true; + + awardAssists = self PlayerKilled_Suicide( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ); + if( assistedSuicide == true ) + attacker thread globallogic_score::giveKillStats( sMeansOfDeath, weapon, self ); + } + else + { + pixbeginevent( "PlayerKilled attacker" ); + + lpattacknum = attacker getEntityNumber(); + + doKillcam = true; + + if ( level.teamBased && self.team == attacker.team && sMeansOfDeath == "MOD_GRENADE" && level.friendlyfire == 0 ) + { + } + else if ( level.teamBased && self.team == attacker.team ) // killed by a friendly + { + wasTeamKill = true; + + self PlayerKilled_TeamKill( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ); + } + else + { + if ( bledOut == false ) + { + self PlayerKilled_Kill( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ); + + if ( level.teamBased ) + { + //check if assist points should be awarded + awardAssists = true; + } + } + } + + pixendevent(); //"PlayerKilled attacker" + } + } + else if ( isdefined( attacker ) && ( attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" ) ) + { + doKillcam = false; + + lpattacknum = -1; + lpattackguid = ""; + lpattackname = ""; + lpattackteam = "world"; + + scoreevents::processScoreEvent( "suicide", self ); + self globallogic_score::incPersStat( "suicides", 1 ); + self.suicides = self globallogic_score::getPersStat( "suicides" ); + + self.suicide = true; + + //Check for player death related battlechatter + thread battlechatter::on_player_suicide_or_team_kill( self, "suicide" ); //Play suicide battlechatter + + //check if assist points should be awarded + awardAssists = true; + + if ( level.maxSuicidesBeforeKick > 0 && level.maxSuicidesBeforeKick <= self.suicides ) + { + // should change "teamKillKicked" to just kicked for the next game + self notify( "teamKillKicked" ); + self SuicideKick(); + } + } + else + { + doKillcam = false; + + lpattacknum = -1; + lpattackguid = ""; + lpattackname = ""; + lpattackteam = "world"; + + wasSuicide = true; + + // we may have a killcam on an world entity like the rocket in cosmodrome + if ( isdefined( eInflictor ) && isdefined( eInflictor.killCamEnt ) ) + { + doKillcam = true; + lpattacknum = self getEntityNumber(); + wasSuicide = false; + } + + // even if the attacker isn't a player, it might be on a team + if ( isdefined( attacker ) && isdefined( attacker.team ) && ( isdefined( level.teams[attacker.team] ) ) ) + { + if ( attacker.team != self.team ) + { + if ( level.teamBased ) + { + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + globallogic_score::giveTeamScore( "kill", attacker.team, attacker, self ); + } + + wasSuicide = false; + } + } + + //check if assist points should be awarded + awardAssists = true; + } + + if ( !level.inGracePeriod && enteredResurrect == false ) + { + if ( sMeansOfDeath != "MOD_GRENADE" && sMeansOfDeath != "MOD_GRENADE_SPLASH" && sMeansOfDeath != "MOD_EXPLOSIVE" && sMeansOfDeath != "MOD_EXPLOSIVE_SPLASH" && sMeansOfDeath != "MOD_PROJECTILE_SPLASH" && sMeansOfDeath != "MOD_FALLING" ) + { + if ( weapon.name != "incendiary_fire" ) + { + self weapons::drop_scavenger_for_death( attacker ); + } + } + + if ( should_drop_weapon_on_death( wasTeamkill, wasSuicide, weapon_at_time_of_death, sMeansOfDeath ) ) + { + self weapons::drop_for_death( attacker, weapon, sMeansOfDeath ); + } + } + + //award assist points if needed + if( awardAssists ) + { + self PlayerKilled_AwardAssists( eInflictor, attacker, weapon, lpattackteam ); + } + + pixbeginevent( "PlayerKilled post constants" ); + + self.lastAttacker = attacker; + self.lastDeathPos = self.origin; + + if ( isdefined( attacker ) && isPlayer( attacker ) && attacker != self && (!level.teambased || attacker.team != self.team) ) + { + attacker notify( "killed_enemy_player", self, weapon ); + if( isDefined( attacker.gadget_thief_kill_callback ) ) + { + attacker [[attacker.gadget_thief_kill_callback]]( self, weapon ); + } + self thread challenges::playerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, sHitLoc, attackerStance, bledOut ); + } + else + { + + self notify("playerKilledChallengesProcessed"); + } + + if ( isdefined ( self.attackers )) + self.attackers = []; + + + // minimize repeat checks of things like isPlayer + killerHeroPowerActive = 0; + killer = undefined; + killerLoadoutIndex = -1; + killerWasADS = false; + killerInVictimFOV = false; + victimInKillerFOV = false; + + if( isPlayer( attacker ) ) + { + attacker.lastKillTime = gettime(); + + killer = attacker; + if ( isdefined( attacker.class_num ) ) + killerLoadoutIndex = attacker.class_num; + killerWasADS = attacker playerADS() >= 1; + + killerInVictimFOV = util::within_fov( self.origin, self.angles, attacker.origin, self.fovcosine ); + victimInKillerFOV = util::within_fov( attacker.origin, attacker.angles, self.origin, attacker.fovcosine ); + + if ( attacker ability_player::is_using_any_gadget() ) + killerHeroPowerActive = 1; + + if( killstreaks::is_killstreak_weapon( weapon ) ) + { + killstreak = killstreaks::get_killstreak_for_weapon_for_stats( weapon ); + + bbPrint( "mpattacks", "gametime %d attackerspawnid %d attackerweapon %s attackerx %d attackery %d attackerz %d victimspawnid %d victimx %d victimy %d victimz %d damage %d damagetype %s damagelocation %s death %d isusingheropower %d killstreak %s", + gettime(), getplayerspawnid( attacker ), weapon.name, lpattackorigin, getplayerspawnid( self ), self.origin, iDamage, sMeansOfDeath, sHitLoc, 1, killerHeroPowerActive, killstreak ); + } + else + { + bbPrint( "mpattacks", "gametime %d attackerspawnid %d attackerweapon %s attackerx %d attackery %d attackerz %d victimspawnid %d victimx %d victimy %d victimz %d damage %d damagetype %s damagelocation %s death %d isusingheropower %d", + gettime(), getplayerspawnid( attacker ), weapon.name, lpattackorigin, getplayerspawnid( self ), self.origin, iDamage, sMeansOfDeath, sHitLoc, 1, killerHeroPowerActive ); + } + + attacker thread weapons::bestweapon_kill( weapon ); + } + else + { + bbPrint( "mpattacks", "gametime %d attackerweapon %s victimspawnid %d victimx %d victimy %d victimz %d damage %d damagetype %s damagelocation %s death %d isusingheropower %d", + gettime(), weapon.name, getplayerspawnid( self ), self.origin, iDamage, sMeansOfDeath, sHitLoc, 1, 0 ); + } + + victimWeapon = undefined; + victimWeaponPickedUp = false; + victimKillstreakWeaponIndex = 0; + if( isdefined( weapon_at_time_of_death ) ) + { + victimWeapon = weapon_at_time_of_death; + if( isdefined( self.pickedUpWeapons ) && isdefined( self.pickedUpWeapons[victimWeapon] ) ) + { + victimWeaponPickedUp = true; + } + + if( killstreaks::is_killstreak_weapon( victimWeapon ) ) + { + killstreak = killstreaks::get_killstreak_for_weapon_for_stats( victimWeapon ); + if( isdefined( level.killstreaks[killstreak].menuname ) ) + { + victimKillstreakWeaponIndex = level.killstreakindices[level.killstreaks[killstreak].menuname]; + } + } + } + victimWasADS = self playerADS() >= 1; + victimHeroPowerActive = self ability_player::is_using_any_gadget(); + + killerWeaponPickedUp = false; + killerKillstreakWeaponIndex = 0; + killerKillstreakEventIndex = 125; // 125 = not a killstreak + if( isdefined( weapon ) ) + { + if( isdefined( killer ) && isdefined( killer.pickedUpWeapons ) && isdefined( killer.pickedUpWeapons[weapon] ) ) + { + killerWeaponPickedUp = true; + } + + if( killstreaks::is_killstreak_weapon( weapon ) ) + { + killstreak = killstreaks::get_killstreak_for_weapon_for_stats( weapon ); + if( isdefined( level.killstreaks[killstreak].menuname ) ) + { + killerKillstreakWeaponIndex = level.killstreakindices[level.killstreaks[killstreak].menuname]; + + if( isdefined( killer.killstreakEvents ) && isdefined( killer.killstreakEvents[ killerkillstreakweaponindex ] ) ) + { + killerKillstreakEventIndex = killer.killstreakEvents[killerkillstreakweaponindex]; + } + else + { + killerkillstreakeventindex = 126; // 126 = was a killstreak but no event index + } + } + } + } + + // + // Log additional stuff in match record on death. + // Mostly values we can't easily access in the existing MatchRecordDeath function in code. + // + + matchRecordLogAdditionalDeathInfo( self, killer, victimWeapon, weapon, + self.class_num, victimWeaponPickedUp, victimWasADS, + killerLoadoutIndex, killerWeaponPickedUp, killerWasADS, + victimHeroPowerActive, killerHeroPowerActive, + victimInKillerFOV, killerInVictimFOV, + killerKillstreakWeaponIndex, victimKillstreakWeaponIndex, + killerkillstreakeventindex); + + + self record_special_move_data_for_life( killer ); + + self.pickedUpWeapons = []; // reset on each death + + + /#logPrint( "K;" + lpselfguid + ";" + lpselfnum + ";" + lpselfteam + ";" + lpselfname + ";" + lpattackguid + ";" + lpattacknum + ";" + lpattackteam + ";" + lpattackname + ";" + weapon.name + ";" + iDamage + ";" + sMeansOfDeath + ";" + sHitLoc + "\n" );#/ + attackerString = "none"; + if ( isPlayer( attacker ) ) // attacker can be the worldspawn if it's not a player + attackerString = attacker getXuid() + "(" + lpattackname + ")"; + /#print( "d " + sMeansOfDeath + "(" + weapon.name + ") a:" + attackerString + " d:" + iDamage + " l:" + sHitLoc + " @ " + int( self.origin[0] ) + " " + int( self.origin[1] ) + " " + int( self.origin[2] ) );#/ + + // for cod caster update the top scorers + if ( !level.rankedMatch && !level.teambased ) + { + level thread update_ffa_top_scorers(); + } + + level thread globallogic::updateTeamStatus(); + level thread globallogic::updateAliveTimes(self.team); + + if ( isdefined( self.killcam_entity_info_cached ) ) + { + killcam_entity_info = self.killcam_entity_info_cached; + self.killcam_entity_info_cached = undefined; + } + else + { + killcam_entity_info = killcam::get_killcam_entity_info( attacker, eInflictor, weapon ); + } + + + // no killcam if the player is still involved with a killstreak + if ( isdefined( self.killstreak_delay_killcam ) ) + doKillcam = false; + + self weapons::detach_carry_object_model(); + + pixendevent(); //"END: PlayerKilled post constants" + + pixbeginevent( "PlayerKilled body and gibbing" ); + vAttackerOrigin = undefined; + if ( isdefined( attacker ) ) + { + vAttackerOrigin = attacker.origin; + } + + if ( enteredResurrect == false ) + { + clone_weapon = weapon; + + // we do not want the weapon death fx to play if this is not a melee weapon and its a melee attack + // ideally the mod be passed to the client side and let it decide but this is post ship t7 and this is safest + if ( weapon_utils::isMeleeMOD(sMeansOfDeath) && clone_weapon.type != "melee" ) + { + clone_weapon = level.weaponNone; + } + body = self clonePlayer( deathAnimDuration, clone_weapon, attacker ); + + if ( isdefined( body ) ) + { + self createDeadBody( attacker, iDamage, sMeansOfDeath, weapon, sHitLoc, vDir, vAttackerOrigin, deathAnimDuration, eInflictor, body ); + + if ( isdefined( level.customPlayDeathSound ) ) + self [[ level.customPlayDeathSound ]]( body, attacker, weapon, sMeansOfDeath ); + else + self battlechatter::play_death_vox( body, attacker, weapon, sMeansOfDeath ); + + globallogic::DoWeaponSpecificCorpseEffects(body, eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime); + } + } + + + pixendevent();// "END: PlayerKilled body and gibbing" + + if ( enteredResurrect ) + { + thread globallogic_spawn::spawnQueuedClient( self.team, attacker ); + } + + self.switching_teams = undefined; + self.joining_team = undefined; + self.leaving_team = undefined; + + if ( bledOut == false ) // handled in PlayerLastStand + { + self thread [[level.onPlayerKilled]](eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + } + + if ( isdefined( level.teamopsOnPlayerKilled ) ) + { + self [[level.teamopsOnPlayerKilled]]( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + } + + for ( iCB = 0; iCB < level.onPlayerKilledExtraUnthreadedCBs.size; iCB++ ) + { + self [[ level.onPlayerKilledExtraUnthreadedCBs[ iCB ] ]]( + eInflictor, + attacker, + iDamage, + sMeansOfDeath, + weapon, + vDir, + sHitLoc, + psOffsetTime, + deathAnimDuration ); + } + + self.wantSafeSpawn = false; + perks = []; + // perks = globallogic::getPerks( attacker ); + killstreaks = globallogic::getKillstreaks( attacker ); + + if( !isdefined( self.killstreak_delay_killcam ) ) + { + // start the prediction now so the client gets updates while waiting to spawn + self thread [[level.spawnPlayerPrediction]](); + } + + profilelog_endtiming( 7, "gs=" + game["state"] + " zom=" + SessionModeIsZombiesGame() ); + + // record the kill cam values for the final kill cam + if ( wasTeamKill == false && assistedSuicide == false && sMeansOfDeath != "MOD_SUICIDE" && !( !isdefined( attacker ) || attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" || attacker == self || isdefined ( attacker.disableFinalKillcam ) ) ) + { + level thread killcam::record_settings( lpattacknum, self getEntityNumber(), weapon, sMeansOfDeath, self.deathTime, deathTimeOffset, psOffsetTime, killcam_entity_info, perks, killstreaks, attacker ); + } + if ( enteredResurrect ) + { + return; + } + + // let the player watch themselves die + wait ( 0.25 ); + + //check if killed by a sniper + weaponClass = util::getWeaponClass( weapon ); + if( isdefined( weaponClass ) && weaponClass == "weapon_sniper" ) + { + self thread battlechatter::killed_by_sniper( attacker ); + } + else + { + self thread battlechatter::player_killed( attacker, killstreak ); + } + self.cancelKillcam = false; + self thread killcam::cancel_on_use(); + + // initial death cam + self playerkilled_watch_death(weapon, sMeansOfDeath, deathAnimDuration); + + // killcam +/# + if ( GetDvarint( "scr_forcekillcam" ) != 0 ) + { + doKillcam = true; + + if ( lpattacknum < 0 ) + lpattacknum = self getEntityNumber(); + } +#/ + + if ( game["state"] != "playing" ) + { + return; + } + + self.respawnTimerStartTime = gettime(); + keep_deathcam = false; + if ( isdefined( self.overridePlayerDeadStatus ) ) + { + keep_deathcam = self [[ self.overridePlayerDeadStatus ]](); + } + + if ( !self.cancelKillcam && doKillcam && level.killcam && ( wasTeamKill == false ) ) + { + livesLeft = !(level.numLives && !self.pers["lives"]) && !(level.numTeamLives && !game[self.team+"_lives"]); + timeUntilSpawn = globallogic_spawn::TimeUntilSpawn( true ); + willRespawnImmediately = livesLeft && (timeUntilSpawn <= 0) && !level.playerQueuedRespawn; + + self killcam::killcam( lpattacknum, self getEntityNumber(), killcam_entity_info, weapon, sMeansOfDeath, self.deathTime, deathTimeOffset, psOffsetTime, willRespawnImmediately, globallogic_utils::timeUntilRoundEnd(), perks, killstreaks, attacker, keep_deathcam ); + } + else if( self.cancelKillcam ) + { + // copy of code from wait_skip_killcam_button + // because fast button mashers (not hard to do) will "skip" the killcam + // before it even starts + if( isdefined( self.killcamsSkipped) ) + { + self.killcamsSkipped++; + } + else + { + self.killcamsSkipped = 1; + } + } + + // secondary deathcam for resurrection + + secondary_deathcam = 0.0; + + timeUntilSpawn = globallogic_spawn::TimeUntilSpawn( true ); + shouldDoSecondDeathCam = timeUntilSpawn > 0; + + if ( shouldDoSecondDeathCam && IsDefined(self.secondaryDeathCamTime) ) + { + secondary_deathcam = self [[self.secondaryDeathCamTime]](); + } + + if ( secondary_deathcam > 0.0 && !self.cancelKillcam ) + { + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + globallogic_utils::waitForTimeOrNotify( secondary_deathcam, "end_death_delay" ); + self notify ( "death_delay_finished" ); + } + + // secondary deathcam is complete + + if ( !self.cancelKillcam && doKillcam && level.killcam && keep_deathcam ) + { + self.sessionstate = "dead"; + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + } + + if ( game["state"] != "playing" ) + { + self.sessionstate = "dead"; + self.spectatorclient = -1; + self.killcamtargetentity = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + return; + } + + WaitTillKillStreakDone(); + useRespawnTime = true; + if( isDefined( level.hostMigrationTimer ) ) + { + useRespawnTime = false; + } + hostmigration::waittillHostMigrationCountDown(); + //if ( isDefined( level.hostMigrationTimer ) ) + //return; + + // class may be undefined if we have changed teams + if ( globallogic_utils::isValidClass( self.curClass ) ) + { + timePassed = undefined; + + if ( isdefined( self.respawnTimerStartTime ) && useRespawnTime ) + { + timePassed = (gettime() - self.respawnTimerStartTime) / 1000; + } + + self thread [[level.spawnClient]]( timePassed ); + self.respawnTimerStartTime = undefined; + } +} + +function update_ffa_top_scorers() +{ + waittillframeend; + + if ( !level.players.size || level.gameEnded ) + return; + + placement = []; + foreach ( player in level.players ) + { + if ( player.team != "spectator" ) + placement[placement.size] = player; + } + + for ( i = 1; i < placement.size; i++ ) + { + player = placement[i]; + playerScore = player.pointstowin; + for ( j = i - 1; j >= 0 && (playerScore > placement[j].pointstowin || (playerScore == placement[j].pointstowin && player.deaths < placement[j].deaths) || (playerScore == placement[j].pointstowin && player.deaths == placement[j].deaths && player.lastKillTime > placement[j].lastKillTime)); j-- ) + placement[j + 1] = placement[j]; + placement[j + 1] = player; + } + + ClearTopScorers(); + for ( i = 0; i < placement.size && i < 3; i++ ) + { + SetTopScorer( i, placement[i], 0, 0, 0, 0, level.weaponNone ); + } +} + +function playerkilled_watch_death(weapon, sMeansOfDeath, deathAnimDuration) +{ + defaultPlayerDeathWatchTime = 1.75; + if ( sMeansOfDeath == "MOD_MELEE_ASSASSINATE" || 0 > weapon.deathCamTime ) + { + defaultPlayerDeathWatchTime = (deathAnimDuration * 0.001) + 0.5; + } + else if ( 0 < weapon.deathCamTime ) + { + defaultPlayerDeathWatchTime = weapon.deathCamTime; + } + + if ( isdefined ( level.overridePlayerDeathWatchTimer ) ) + { + defaultPlayerDeathWatchTime = [[level.overridePlayerDeathWatchTimer]]( defaultPlayerDeathWatchTime ); + } + + globallogic_utils::waitForTimeOrNotify( defaultPlayerDeathWatchTime, "end_death_delay" ); + + self notify ( "death_delay_finished" ); +} + +function should_drop_weapon_on_death( wasTeamKill, wasSuicide, current_weapon, sMeansOfDeath ) +{ + // to avoid exploits dont allow weapon drops on suicide or teamkills. + if ( wasTeamKill ) + return false; + + if ( wasSuicide ) + return false; + + // assuming this means that they are in a death trigger out of bounds and falling + if ( sMeansOfDeath == "MOD_TRIGGER_HURT" && !self IsOnGround()) + return false; + + // dont drop any weapon if they were holding a hero weapon + if ( IsDefined(current_weapon) && current_weapon.isHeroWeapon ) + return false; + + return true; +} + +function updateGlobalBotKilledCounter() +{ + if ( isdefined( self.pers["isBot"] ) ) + { + level.globalLarrysKilled++; + } +} + + +function WaitTillKillStreakDone() +{ + if( isdefined( self.killstreak_delay_killcam ) ) + { + while( isdefined( self.killstreak_delay_killcam ) ) + { + wait( 0.1 ); + } + + //Plus a small amount so we can see our dead body + wait( 2.0 ); + + self killstreaks::reset_killstreak_delay_killcam(); + } +} + +function SuicideKick() +{ + self globallogic_score::incPersStat( "sessionbans", 1 ); + + self endon("disconnect"); + waittillframeend; + + globallogic::gameHistoryPlayerKicked(); + + ban( self getentitynumber() ); + globallogic_audio::leader_dialog( "gamePlayerKicked" ); +} + +function TeamKillKick() +{ + self globallogic_score::incPersStat( "sessionbans", 1 ); + + self endon("disconnect"); + waittillframeend; + + //for test purposes lets lock them out of certain game type for 2mins + + playlistbanquantum = tweakables::getTweakableValue( "team", "teamkillerplaylistbanquantum" ); + playlistbanpenalty = tweakables::getTweakableValue( "team", "teamkillerplaylistbanpenalty" ); + if ( playlistbanquantum > 0 && playlistbanpenalty > 0 ) + { + timeplayedtotal = self GetDStat( "playerstatslist", "time_played_total", "StatValue" ); + minutesplayed = timeplayedtotal / 60; + + freebees = 2; + + banallowance = int( floor(minutesplayed / playlistbanquantum) ) + freebees; + + if ( self.sessionbans > banallowance ) + { + self SetDStat( "playerstatslist", "gametypeban", "StatValue", timeplayedtotal + (playlistbanpenalty * 60) ); + } + } + + globallogic::gameHistoryPlayerKicked(); + + ban( self getentitynumber() ); + globallogic_audio::leader_dialog( "gamePlayerKicked" ); +} + +function TeamKillDelay() +{ + teamkills = self.pers["teamkills_nostats"]; + if ( level.minimumAllowedTeamKills < 0 || teamkills <= level.minimumAllowedTeamKills ) + return 0; + + exceeded = (teamkills - level.minimumAllowedTeamKills); + return level.teamKillSpawnDelay * exceeded; +} + + +function ShouldTeamKillKick(teamKillDelay) +{ + if ( teamKillDelay && ( level.minimumAllowedTeamKills >= 0 ) ) + { + // if its more then 5 seconds into the match and we have a delay then just kick them + if ( globallogic_utils::getTimePassed() >= 5000 ) + { + return true; + } + + // if its under 5 seconds into the match only kick them if they have killed more then one players so far + if ( self.pers["teamkills_nostats"] > 1 ) + { + return true; + } + } + + return false; +} + +function reduceTeamKillsOverTime() +{ + timePerOneTeamkillReduction = 20.0; + reductionPerSecond = 1.0 / timePerOneTeamkillReduction; + + while(1) + { + if ( isAlive( self ) ) + { + self.pers["teamkills_nostats"] -= reductionPerSecond; + if ( self.pers["teamkills_nostats"] < level.minimumAllowedTeamKills ) + { + self.pers["teamkills_nostats"] = level.minimumAllowedTeamKills; + break; + } + } + wait 1; + } +} + + +function IgnoreTeamKills( weapon, sMeansOfDeath, eInflictor ) +{ + if ( weapon_utils::isMeleeMOD( sMeansOfDeath ) ) + return false; + + if ( weapon.ignoreTeamKills ) + return true; + + if ( isdefined( eInflictor ) && eInflictor.ignore_team_kills === true ) + return true; + + if( isDefined( eInflictor ) && isDefined( eInflictor.destroyedBy ) && isDefined( eInflictor.owner ) && eInflictor.destroyedBy != eInflictor.owner ) + return true; + + if ( isDefined( eInflictor ) && eInflictor.classname == "worldspawn" ) + return true; + + return false; +} + + +function Callback_PlayerLastStand( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + laststand::PlayerLastStand( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ); +} + +function damageShellshockAndRumble( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage ) +{ + self thread weapons::on_damage( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage ); + + if ( !self util::isUsingRemote() ) + { + self PlayRumbleOnEntity( "damage_heavy" ); + } +} + + +function createDeadBody( attacker, iDamage, sMeansOfDeath, weapon, sHitLoc, vDir, vAttackerOrigin, deathAnimDuration, eInflictor, body ) +{ + if ( sMeansOfDeath == "MOD_HIT_BY_OBJECT" && self GetStance() == "prone" ) + { + self.body = body; + if ( !isdefined( self.switching_teams ) ) + thread deathicons::add( body, self, self.team, 5.0 ); + + return; + } + + ragdoll_now = false; + if( isdefined(self.usingvehicle) && self.usingvehicle && isdefined(self.vehicleposition) && self.vehicleposition == 1 ) + { + ragdoll_now = true; + } + + if ( isdefined( level.ragdoll_override ) && self [[level.ragdoll_override]]( iDamage, sMeansOfDeath, weapon, sHitLoc, vDir, vAttackerOrigin, deathAnimDuration, eInflictor, ragdoll_now, body ) ) + { + return; + } + + if ( ( ragdoll_now ) || self isOnLadder() || self isMantling() || sMeansOfDeath == "MOD_CRUSH" || sMeansOfDeath == "MOD_HIT_BY_OBJECT" ) + body startRagDoll(); + + if ( !self IsOnGround() && sMeansOfDeath != "MOD_FALLING" ) + { + if ( GetDvarint( "scr_disable_air_death_ragdoll" ) == 0 ) + { + body startRagDoll(); + } + } + + if( sMeansOfDeath == "MOD_MELEE_ASSASSINATE" && !attacker isOnGround() ) + { + body start_death_from_above_ragdoll( vDir ); + } + + if ( self is_explosive_ragdoll( weapon, eInflictor ) ) + { + body start_explosive_ragdoll( vDir, weapon ); + } + + thread delayStartRagdoll( body, sHitLoc, vDir, weapon, eInflictor, sMeansOfDeath ); + + if ( sMeansOfDeath == "MOD_CRUSH" ) + { + body globallogic_vehicle::vehicleCrush(); + } + + self.body = body; + if ( !isdefined( self.switching_teams ) ) + thread deathicons::add( body, self, self.team, 5.0 ); +} + +function is_explosive_ragdoll( weapon, inflictor ) +{ + if ( !isdefined( weapon ) ) + { + return false; + } + + // destructible explosives + if ( weapon.name == "destructible_car" || weapon.name == "explodable_barrel" ) + { + return true; + } + + // special explosive weapons + if ( weapon.projExplosionType == "grenade" ) + { + if ( isdefined( inflictor ) && isdefined( inflictor.stuckToPlayer ) ) + { + if ( inflictor.stuckToPlayer == self ) + { + return true; + } + } + } + + return false; +} + +function start_explosive_ragdoll( dir, weapon ) +{ + if ( !isdefined( self ) ) + { + return; + } + + x = RandomIntRange( 50, 100 ); + y = RandomIntRange( 50, 100 ); + z = RandomIntRange( 10, 20 ); + + if ( isdefined( weapon ) && (weapon.name == "sticky_grenade" || weapon.name == "explosive_bolt") ) + { + if ( isdefined( dir ) && LengthSquared( dir ) > 0 ) + { + x = dir[0] * x; + y = dir[1] * y; + } + } + else + { + if ( math::cointoss() ) + { + x = x * -1; + } + if ( math::cointoss() ) + { + y = y * -1; + } + } + + self StartRagdoll(); + self LaunchRagdoll( ( x, y, z ) ); +} + +function start_death_from_above_ragdoll( dir ) +{ + if ( !isdefined( self ) ) + { + return; + } + + self StartRagdoll(); + self LaunchRagdoll( ( 0, 0, -100 ) ); +} + + +function notifyConnecting() +{ + waittillframeend; + + if( isdefined( self ) ) + { + level notify( "connecting", self ); + } + + callback::callback( #"on_player_connecting" ); +} + + +function delayStartRagdoll( ent, sHitLoc, vDir, weapon, eInflictor, sMeansOfDeath ) +{ + if ( isdefined( ent ) ) + { + deathAnim = ent getcorpseanim(); + if ( animhasnotetrack( deathAnim, "ignore_ragdoll" ) ) + return; + } + + waittillframeend; + + if ( !isdefined( ent ) ) + return; + + if ( ent isRagDoll() ) + return; + + deathAnim = ent getcorpseanim(); + + startFrac = 0.35; + + if ( animhasnotetrack( deathAnim, "start_ragdoll" ) ) + { + times = getnotetracktimes( deathAnim, "start_ragdoll" ); + if ( isdefined( times ) ) + startFrac = times[0]; + } + + waitTime = startFrac * getanimlength( deathAnim ); + + //waitTime -= 0.2; // account for the wait above + if( waitTime > 0 ) + wait( waitTime ); + + if ( isdefined( ent ) ) + { + ent startragdoll(); + } +} + +function trackAttackerDamage( eAttacker, iDamage, sMeansOfDeath, weapon ) +{ + if( !IsDefined( eAttacker ) ) + return; + + if ( !IsPlayer( eAttacker ) ) + return; + + if ( self.attackerData.size == 0 ) + { + self.firstTimeDamaged = getTime(); + } + if ( !isdefined( self.attackerData[eAttacker.clientid] ) ) + { + self.attackerDamage[eAttacker.clientid] = spawnstruct(); + self.attackerDamage[eAttacker.clientid].damage = iDamage; + self.attackerDamage[eAttacker.clientid].meansOfDeath = sMeansOfDeath; + self.attackerDamage[eAttacker.clientid].weapon = weapon; + self.attackerDamage[eAttacker.clientid].time = getTime(); + + self.attackers[ self.attackers.size ] = eAttacker; + + // we keep an array of attackers by their client ID so we can easily tell + // if they're already one of the existing attackers in the above if(). + // we store in this array data that is useful for other things, like challenges + self.attackerData[eAttacker.clientid] = false; + } + else + { + self.attackerDamage[eAttacker.clientid].damage += iDamage; + self.attackerDamage[eAttacker.clientid].meansOfDeath = sMeansOfDeath; + self.attackerDamage[eAttacker.clientid].weapon = weapon; + if ( !isdefined( self.attackerDamage[eAttacker.clientid].time ) ) + self.attackerDamage[eAttacker.clientid].time = getTime(); + } + + if ( IsArray( self.attackersThisSpawn ) ) + { + self.attackersThisSpawn[ eAttacker.clientid ] = eAttacker; + } + + self.attackerDamage[eAttacker.clientid].lasttimedamaged = getTime(); + if ( weapons::is_primary_weapon( weapon ) ) + self.attackerData[eAttacker.clientid] = true; +} + +function giveAttackerAndInflictorOwnerAssist( eAttacker, eInflictor, iDamage, sMeansOfDeath, weapon ) +{ + if ( !allowedAssistWeapon( weapon ) ) + return; + + self trackAttackerDamage( eAttacker, iDamage, sMeansOfDeath, weapon ); + + if ( !isdefined( eInflictor ) ) + return; + + if ( !isdefined( eInflictor.owner ) ) + return; + + if ( !isdefined( eInflictor.ownerGetsAssist ) ) + return; + + if ( !eInflictor.ownerGetsAssist ) + return; + + // if attacker and inflictor owner are the same no additional points + // I dont ever know if they are different + if ( isdefined( eAttacker ) && eAttacker == eInflictor.owner ) + return; + + self trackAttackerDamage( eInflictor.owner, iDamage, sMeansOfDeath, weapon ); +} + +function PlayerKilled_UpdateMeansOfDeath( attacker, eInflictor, weapon, sMeansOfDeath, sHitLoc ) +{ + if( globallogic_utils::isHeadShot( weapon, sHitLoc, sMeansOfDeath, eInflictor ) && isPlayer( attacker ) && !weapon_utils::ismeleemod( sMeansOfDeath ) ) + { + return "MOD_HEAD_SHOT"; + } + + // we do not want the melee icon to show up for dog attacks + switch( weapon.name ) + { + case "dog_bite": + sMeansOfDeath = "MOD_PISTOL_BULLET"; + break; + case "destructible_car": + sMeansOfDeath = "MOD_EXPLOSIVE"; + break; + case "explodable_barrel": + sMeansOfDeath = "MOD_EXPLOSIVE"; + break; + } + + return sMeansOfDeath; +} + +function updateAttacker( attacker, weapon ) +{ + if( isai(attacker) && isdefined( attacker.script_owner ) ) + { + // if the person who called the dogs in switched teams make sure they don't + // get penalized for the kill + if ( !level.teambased || attacker.script_owner.team != self.team ) + attacker = attacker.script_owner; + } + + if( attacker.classname == "script_vehicle" && isdefined( attacker.owner ) ) + { + attacker notify("killed",self); + + attacker = attacker.owner; + } + + if( isai(attacker) ) + attacker notify("killed",self); + + if ( ( isdefined ( self.capturingLastFlag ) ) && ( self.capturingLastFlag == true ) ) + { + attacker.lastCapKiller = true; + } + + if( isdefined( attacker ) && attacker != self && isdefined( weapon ) ) + { + if ( weapon.name == "planemortar" ) + { + if(!isdefined(attacker.planeMortarBda))attacker.planeMortarBda=0; + attacker.planeMortarBda++; + } + else if( weapon.name == "dart" || + weapon.name == "dart_turret" ) + { + if(!isdefined(attacker.dartBda))attacker.dartBda=0; + attacker.dartBda++; + } + else if( weapon.name == "straferun_rockets" || weapon.name == "straferun_gun") + { + if( isdefined( attacker.strafeRunbda ) ) + { + attacker.strafeRunbda++; + } + } + else if ( weapon.name == "remote_missile_missile" || weapon.name == "remote_missile_bomblet" ) + { + if(!isdefined(attacker.remotemissileBda))attacker.remotemissileBda=0; + attacker.remotemissileBda++; + } + } + + return attacker; +} + +function updateInflictor( eInflictor ) +{ + if( isdefined( eInflictor ) && eInflictor.classname == "script_vehicle" ) + { + eInflictor notify("killed",self); + + if ( isdefined( eInflictor.bda ) ) + { + eInflictor.bda++; + } + } + + return eInflictor; +} + +function updateWeapon( eInflictor, weapon ) +{ + // explosive barrel/car detection + if ( weapon == level.weaponNone && isdefined( eInflictor ) ) + { + if ( isdefined( eInflictor.targetname ) && eInflictor.targetname == "explodable_barrel" ) + weapon = GetWeapon( "explodable_barrel" ); + else if ( isdefined( eInflictor.destructible_type ) && isSubStr( eInflictor.destructible_type, "vehicle_" ) ) + weapon = GetWeapon( "destructible_car" ); + } + + return weapon; +} + +function playKillBattleChatter( attacker, weapon, victim, eInflictor ) +{ + if( IsPlayer( attacker ) ) + { + if ( !killstreaks::is_killstreak_weapon( weapon ) ) + { + level thread battlechatter::say_kill_battle_chatter( attacker, weapon, victim, eInflictor ); + } + } + + if( isdefined( eInflictor ) ) + { + eInflictor notify( "bhtn_action_notify", "attack_kill" ); + } +} diff --git a/mp/gametypes/_globallogic_score.gsc b/mp/gametypes/_globallogic_score.gsc new file mode 100644 index 0000000..81827ac --- /dev/null +++ b/mp/gametypes/_globallogic_score.gsc @@ -0,0 +1,1848 @@ +#using scripts\shared\bb_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\persistence_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; +#using scripts\shared\abilities\_ability_player; +#using scripts\shared\abilities\_ability_util; + +#using scripts\shared\bots\_bot; + + + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_wager; + +#using scripts\mp\_challenges; +#using scripts\mp\_scoreevents; +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_killstreak_weapons; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_counteruav; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\_teamops; + +#precache( "eventstring", "track_victim_death" ); +#precache( "string", "SCORE_BLANK" ); + +#namespace globallogic_score; + +function updateMatchBonusScores( winner ) +{ + if ( !game["timepassed"] ) + { + return; + } + if ( !level.rankedMatch ) + { + updateCustomGameWinner( winner ); + return; + } + // dont give the bonus until the game is over + if ( level.teamBased && isdefined( winner ) ) + { + if ( winner == "endregulation" ) + return; + } + + if ( !level.timeLimit || level.forcedEnd ) + { + gameLength = globallogic_utils::getTimePassed() / 1000; + // cap it at 20 minutes to avoid exploiting + gameLength = min( gameLength, 1200 ); + + // the bonus for final fight needs to be based on the total time played + if ( level.gameType == "twar" && game["roundsplayed"] > 0 ) + gameLength += level.timeLimit * 60; + } + else + { + gameLength = level.timeLimit * 60; + } + + if ( level.teamBased ) + { + winningTeam = "tie"; + + // TODO MTEAM - not sure if this is absolutely necessary but I dont know + // if "winner" is anything other then a valid team or "tie" at this point + foreach ( team in level.teams ) + { + if ( winner == team ) + { + winningTeam = team; + break; + } + } + + if ( winningTeam != "tie" ) + { + winnerScale = 1.0; + loserScale = 0.5; + } + else + { + winnerScale = 0.75; + loserScale = 0.75; + } + + players = level.players; + for( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if ( player.timePlayed["total"] < 1 || player.pers["participation"] < 1 ) + { + player thread rank::endGameUpdate(); + continue; + } + + totalTimePlayed = player.timePlayed["total"]; + + // make sure the players total time played is no + // longer then the game length to prevent exploits + if ( totalTimePlayed > gameLength ) + { + totalTimePlayed = gameLength; + } + + // no bonus for hosts who force ends + if ( level.hostForcedEnd && player IsHost() ) + continue; + + // no match bonus if negative game score + if ( player.pers["score"] < 0 ) + continue; + + spm = player rank::getSPM(); + if ( winningTeam == "tie" ) + { + playerScore = int( (winnerScale * ((gameLength/60) * spm)) * (totalTimePlayed / gameLength) ); + player thread giveMatchBonus( "tie", playerScore ); + player.matchBonus = playerScore; + } + else if ( isdefined( player.pers["team"] ) && player.pers["team"] == winningTeam ) + { + playerScore = int( (winnerScale * ((gameLength/60) * spm)) * (totalTimePlayed / gameLength) ); + player thread giveMatchBonus( "win", playerScore ); + player.matchBonus = playerScore; + } + else if ( isdefined(player.pers["team"] ) && player.pers["team"] != "spectator" ) + { + playerScore = int( (loserScale * ((gameLength/60) * spm)) * (totalTimePlayed / gameLength) ); + player thread giveMatchBonus( "loss", playerScore ); + player.matchBonus = playerScore; + } + player.pers["totalMatchBonus"] += player.matchBonus; + } + } + else + { + if ( isdefined( winner ) ) + { + winnerScale = 1.0; // win + loserScale = 0.5; // loss + } + else + { + winnerScale = 0.75; // tie + loserScale = 0.75; // tie + } + + players = level.players; + for( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if ( player.timePlayed["total"] < 1 || player.pers["participation"] < 1 ) + { + player thread rank::endGameUpdate(); + continue; + } + + totalTimePlayed = player.timePlayed["total"]; + + // make sure the players total time played is no + // longer then the game length to prevent exploits + if ( totalTimePlayed > gameLength ) + { + totalTimePlayed = gameLength; + } + + spm = player rank::getSPM(); + + isWinner = false; + for ( pIdx = 0; pIdx < min( level.placement["all"][0].size, 3 ); pIdx++ ) + { + if ( level.placement["all"][pIdx] != player ) + continue; + isWinner = true; + } + + if ( isWinner ) + { + playerScore = int( (winnerScale * ((gameLength/60) * spm)) * (totalTimePlayed / gameLength) ); + player thread giveMatchBonus( "win", playerScore ); + player.matchBonus = playerScore; + } + else + { + playerScore = int( (loserScale * ((gameLength/60) * spm)) * (totalTimePlayed / gameLength) ); + player thread giveMatchBonus( "loss", playerScore ); + player.matchBonus = playerScore; + } + player.pers["totalMatchBonus"] += player.matchBonus; + } + } +} + +function updateCustomGameWinner( winner ) +{ + if( !level.mpCustomMatch ) + { + return; + } + + for( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + + if ( !IsDefined( winner ) ) + { + player.pers["victory"] = 0; + } + else if ( level.teambased ) + { + if( player.team == winner ) + { + player.pers["victory"] = 2; + } + else if( winner == "tie") + { + player.pers["victory"] = 1; + } + else + { + player.pers["victory"] = 0; + } + } + else + { + isWinner = false; + for ( pIdx = 0; pIdx < min( level.placement["all"].size, 3 ); pIdx++ ) + { + if ( level.placement["all"][pIdx] != player ) + continue; + isWinner = true; + } + + if( isWinner ) + { + player.pers["victory"] = 2; + } + else + { + player.pers["victory"] = 0; + } + } + + player.victory = player.pers["victory"]; + player.pers["sbtimeplayed"] = player.timeplayed["total"]; + player.sbtimeplayed = player.pers["sbtimeplayed"]; + } +} + +function giveMatchBonus( scoreType, score ) +{ + self endon ( "disconnect" ); + + level waittill ( "give_match_bonus" ); + + if ( scoreevents::shouldAddRankXP( self ) ) + { + self AddRankXPValue( scoreType, score ); + } + + self rank::endGameUpdate(); +} + +function getHighestScoringPlayer() +{ + players = level.players; + winner = undefined; + tie = false; + + for( i = 0; i < players.size; i++ ) + { + if ( !isdefined( players[i].pointstowin ) ) + continue; + + if ( players[i].pointstowin < 1 ) + continue; + + if ( !isdefined( winner ) || players[i].pointstowin > winner.pointstowin ) + { + winner = players[i]; + tie = false; + } + else if ( players[i].pointstowin == winner.pointstowin ) + { + tie = true; + } + } + + if ( tie || !isdefined( winner ) ) + return undefined; + else + return winner; +} + +function resetPlayerScoreChainAndMomentum( player ) +{ + player thread _setPlayerMomentum( self, 0 ); + player thread resetScoreChain(); +} + +function resetScoreChain() +{ + self notify( "reset_score_chain" ); + + //self LUINotifyEvent( &"score_event", 3, 0, 0, 0 ); + self.scoreChain = 0; + self.rankUpdateTotal = 0; +} + +function scoreChainTimer() +{ + self notify( "score_chain_timer" ); + self endon( "reset_score_chain" ); + self endon( "score_chain_timer" ); + self endon( "death" ); + self endon( "disconnect" ); + + wait 20; + + self thread resetScoreChain(); +} + +function roundToNearestFive( score ) +{ + rounding = score % 5; + if ( rounding <= 2 ) + { + return score - rounding; + } + else + { + return score + ( 5 - rounding ); + } +} + +function givePlayerMomentumNotification( score, label, descValue, countsTowardRampage, weapon, combatEfficiencyBonus ) +{ + if( !isDefined( combatEfficiencyBonus ) ) + combatEfficiencyBonus = 0; + + score = score + combatEfficiencyBonus; + + if ( score != 0 ) + { + self LUINotifyEvent( &"score_event", 3, label, score, combatEfficiencyBonus ); + self LUINotifyEventToSpectators( &"score_event", 3, label, score, combatEfficiencyBonus ); + } + + score = score; + + if ( ( score > 0 ) && self HasPerk( "specialty_earnmoremomentum" ) ) + { + score = roundToNearestFive( int( score * GetDvarFloat( "perk_killstreakMomentumMultiplier" ) + 0.5 ) ); + } + + if ( IsAlive( self ) ) + { + _setPlayerMomentum( self, self.pers["momentum"] + score ); + } +} + +function resetPlayerMomentumOnSpawn() +{ + if ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks ) + { + _setPlayerMomentum( self, 0 ); + self thread resetScoreChain(); + } +} + +function givePlayerMomentum( event, player, victim, descValue, weapon ) +{ + if( isdefined(level.disableMomentum) && (level.disableMomentum==true) ) + return; + + score = rank::getScoreInfoValue( event ); + assert( isdefined( score ) ); + label = rank::getScoreInfoLabel( event ); + countsTowardRampage = rank::doesScoreInfoCountTowardRampage( event ); + + combatEfficiencyEvent = rank::getCombatEfficiencyEvent( event ); + if( IsDefined( combatefficiencyevent ) && ( player ability_util::gadget_combat_efficiency_enabled() ) ) + { + combatEfficiencyScore = rank::getScoreInfoValue( combatEfficiencyEvent ); + assert( isdefined( combatEfficiencyScore ) ); + player ability_util::gadget_combat_efficiency_power_drain( combatEfficiencyScore ); + } + + if ( event == "death" ) + { + _setPlayerMomentum( victim, victim.pers["momentum"] + score ); + } + + if ( score == 0 ) + { + return; + } + + if ( level.gameEnded ) + { + return; + } + + if ( !isdefined( label ) ) + { +/# + errormsg( event + " score string is undefined, you need to add a string to the scoreinfo.csv table when you add a non zero score" ); +#/ + player givePlayerMomentumNotification( score, &"SCORE_BLANK", descValue, countsTowardRampage, weapon, combatEfficiencyScore ); + return; + } + + player givePlayerMomentumNotification( score, label, descValue, countsTowardRampage, weapon, combatEfficiencyScore ); +} + +function givePlayerScore( event, player, victim, descValue, weapon ) +{ + scoreDiff = 0; + momentum = player.pers["momentum"]; + givePlayerMomentum( event, player, victim, descValue, weapon ); + newMomentum = player.pers["momentum"]; + + if ( level.overridePlayerScore ) + return 0; + + pixbeginevent("level.onPlayerScore"); + score = player.pers["score"]; + [[level.onPlayerScore]]( event, player, victim ); + newScore = player.pers["score"]; + pixendevent(); + + isusingheropower = 0; + if ( player ability_player::is_using_any_gadget() ) + isusingheropower = 1; + + bbPrint( "mpplayerscore", "spawnid %d gametime %d type %s player %s delta %d deltamomentum %d team %s isusingheropower %d", getplayerspawnid( player ), getTime(), event, player.name, (newScore - score), ( newMomentum - momentum ), player.team, isusingheropower); + player bb::add_to_stat( "score", newScore - score ); + + if ( score == newScore ) + return 0; + + pixbeginevent("givePlayerScore"); + recordPlayerStats( player, "score", newScore ); + + scoreDiff = (newScore - score); + + challengesEnabled = !level.disableChallenges; + + player AddPlayerStatWithGameType( "score", scoreDiff ); + if ( challengesEnabled ) + { + player AddPlayerStat( "CAREER_SCORE", scoreDiff ); + } + + if ( level.hardcoreMode ) + { + player AddPlayerStat( "SCORE_HC", scoreDiff ); + if ( challengesEnabled ) + { + player AddPlayerStat( "CAREER_SCORE_HC", scoreDiff ); + } + } + if ( level.multiTeam ) + { + player AddPlayerStat( "SCORE_MULTITEAM", scoreDiff ); + if ( challengesEnabled ) + { + player AddPlayerStat( "CAREER_SCORE_MULTITEAM", scoreDiff ); + } + } + if ( !level.disableStatTracking && isdefined( player.pers["lastHighestScore"] ) && newScore > player.pers["lastHighestScore"] ) + { + player setDStat( "HighestStats", "highest_score", newScore ); + } + + player persistence::add_recent_stat( false, 0, "score", scoreDiff ); + + player util::player_contract_event( "score", scoreDiff ); + + if ( isdefined( weapon ) && killstreaks::is_killstreak_weapon( weapon ) ) + { + killstreak = killstreaks::get_from_weapon( weapon ); + killstreakPurchased = false; + if ( isdefined( killstreak ) && isdefined( level.killstreaks[ killstreak ] ) ) + { + killstreakPurchased = player util::is_item_purchased( level.killstreaks[ killstreak ].menuname ); + } + player util::player_contract_event( "killstreak_score", scoreDiff, killstreakPurchased ); + } + + pixendevent(); + return scoreDiff; +} + +function default_onPlayerScore( event, player, victim ) +{ + score = rank::getScoreInfoValue( event ); + + assert( isdefined( score ) ); + + if ( level.wagerMatch ) + { + player thread rank::updateRankScoreHUD( score ); + } + + _setPlayerScore( player, player.pers["score"] + score ); +} + + +function _setPlayerScore( player, score ) +{ + if ( score == player.pers["score"] ) + return; + + if ( !level.rankedMatch ) + { + player thread rank::updateRankScoreHUD( score - player.pers["score"] ); + } + + player.pers["score"] = score; + player.score = player.pers["score"]; + recordPlayerStats( player, "score" , player.pers["score"] ); + + if ( level.wagerMatch ) + player thread wager::player_scored(); +} + + +function _getPlayerScore( player ) +{ + return player.pers["score"]; +} + +function playTop3Sounds() +{ + {wait(.05);}; // Let other simultaneous sounds play first + + globallogic::updatePlacement(); + for ( i = 0 ; i < level.placement["all"].size ; i++ ) + { + prevScorePlace = level.placement["all"][i].prevScorePlace; + if ( !isdefined( prevScorePlace ) ) + prevScorePlace = 1; + currentScorePlace = i + 1; + for ( j = i - 1 ; j >= 0 ; j-- ) + { + if ( level.placement["all"][i].score == level.placement["all"][j].score ) + currentScorePlace--; + } + wasInTheMoney = ( prevScorePlace <= 3 ); + isInTheMoney = ( currentScorePlace <= 3 ); + +// Sounds for "in the money" not relevant anymore +// if ( !wasInTheMoney && isInTheMoney ) +// { +// level.placement["all"][i] PlayLocalSound( game["dialog"]["wm_in_the_money"] ); +// } +// else if ( wasInTheMoney && !isInTheMoney ) +// { +// level.placement["all"][i] PlayLocalSound( game["dialog"]["wm_oot_money"] ); +// } + level.placement["all"][i].prevScorePlace = currentScorePlace; + } +} + +function setPointsToWin( points ) +{ + self.pers["pointstowin"] = math::clamp( points, 0, 65000 ); + self.pointstowin = self.pers["pointstowin"]; + self thread globallogic::checkScoreLimit(); + self thread globallogic::checkRoundScoreLimit(); + self thread globallogic::checkPlayerScoreLimitSoon(); + level thread playTop3Sounds(); +} + +function givePointsToWin( points ) +{ + self setPointsToWin( self.pers["pointstowin"] + points ); +} + + + + +function _setPlayerMomentum( player, momentum, updateScore = true ) +{ + if( GetDvarInt( "teamOpsEnabled" ) == 1 ) + return; + + momentum = math::clamp( momentum, 0, 2000 ); + + oldMomentum = player.pers["momentum"]; + if ( momentum == oldMomentum ) + return; + + if ( updateScore ) + { + player bb::add_to_stat( "momentum", ( momentum - oldMomentum )); + } + + if ( momentum > oldMomentum ) + { + highestMomentumCost = 0; + numKillstreaks = 0; + if ( isdefined( player.killstreak ) ) + { + numKillstreaks = player.killstreak.size; + } + killStreakTypeArray = []; + + for ( currentKillstreak = 0 ; currentKillstreak < numKillstreaks ; currentKillstreak++ ) + { + killstreakType = killstreaks::get_by_menu_name( player.killstreak[ currentKillstreak ] ); + if ( isdefined( killstreakType ) ) + { + momentumCost = level.killstreaks[killstreakType].momentumCost; + if ( momentumCost > highestMomentumCost ) + { + highestMomentumCost = momentumCost; + } + killStreakTypeArray[killStreakTypeArray.size] = killstreakType; + } + } + + _givePlayerKillstreakInternal( player, momentum, oldMomentum, killStreakTypeArray ); + + while ( highestMomentumCost > 0 && momentum >= highestMomentumCost ) + { + oldMomentum = 0; + momentum = momentum - highestMomentumCost; + _givePlayerKillstreakInternal( player, momentum, oldMomentum, killStreakTypeArray ); + } + } + + player.pers["momentum"] = momentum; + player.momentum = player.pers["momentum"]; +} + +function _givePlayerKillstreakInternal( player, momentum, oldMomentum, killStreakTypeArray ) +{ + for ( killstreakTypeIndex = 0 ; killstreakTypeIndex < killStreakTypeArray.size; killstreakTypeIndex++ ) + { + killstreakType = killStreakTypeArray[killstreakTypeIndex]; + + momentumCost = level.killstreaks[killstreakType].momentumCost; + if ( momentumCost > oldMomentum && momentumCost <= momentum ) + { + + weapon = killstreaks::get_killstreak_weapon( killstreakType ); + was_already_at_max_stacking = false; + if ( ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks ) ) + { + if( weapon.isCarriedKillstreak ) + { + if( !isdefined( player.pers["held_killstreak_ammo_count"][weapon] ) ) + player.pers["held_killstreak_ammo_count"][weapon] = 0; + + if( !isdefined( player.pers["killstreak_quantity"][weapon] ) ) + player.pers["killstreak_quantity"][weapon] = 0; + + currentWeapon = player getCurrentWeapon(); + + //If the player is currently using the killstreak weapon, allow them to finish using it before replacing it otherwise give them max ammo + if( currentWeapon == weapon ) + { + if( player.pers["killstreak_quantity"][weapon] < level.scoreStreaksMaxStacking ) + player.pers["killstreak_quantity"][weapon]++; + } + else + { + player.pers["held_killstreak_clip_count"][weapon] = weapon.clipSize; + player.pers["held_killstreak_ammo_count"][weapon] = weapon.maxAmmo; + player loadout::setWeaponAmmoOverall( weapon, player.pers["held_killstreak_ammo_count"][weapon] ); + } + } + else + { + + old_killstreak_quantity = player killstreaks::get_killstreak_quantity( weapon ); + new_killstreak_quantity = player killstreaks::change_killstreak_quantity( weapon, 1 ); + was_already_at_max_stacking = ( new_killstreak_quantity == old_killstreak_quantity ); + + if ( !was_already_at_max_stacking ) + { + player challenges::earnedKillstreak(); + if ( player ability_util::gadget_is_active( 15 ) ) + { + scoreevents::processScoreEvent( "focus_earn_scorestreak", player ); + player scoreevents::specialistMedalAchievement(); + player scoreevents::specialistStatAbilityUsage( 4, true ); + if ( player.heroAbility.name == "gadget_combat_efficiency" ) + { + player addWeaponStat( player.heroAbility, "scorestreaks_earned", 1 ); + if ( !isdefined( player.scoreStreaksEarnedPerUse ) ) + { + player.scoreStreaksEarnedPerUse = 0; + } + + player.scoreStreaksEarnedPerUse++; + if ( player.scoreStreaksEarnedPerUse >= 3 ) + { + scoreevents::processScoreEvent( "focus_earn_multiscorestreak", player ); + player.scoreStreaksEarnedPerUse = 0; + } + } + } + } + } + + if ( !was_already_at_max_stacking ) + { + player killstreaks::add_to_notification_queue( level.killstreaks[killstreakType].menuname, new_killstreak_quantity, killstreakType ); + } + } + else + { + player killstreaks::add_to_notification_queue( level.killstreaks[killstreakType].menuname, 0, killstreakType ); + activeEventName = "reward_active"; + if ( isdefined( weapon ) ) + { + newEventName = weapon.name + "_active"; + if ( scoreevents::isRegisteredEvent( newEventName ) ) + { + activeEventName = newEventName; + } + } + //scoreevents::processScoreEvent( activeEventName, player ); + } + } + } +} + +/# +function setPlayerMomentumDebug() +{ + SetDvar( "sv_momentumPercent", 0.0 ); + + while( true ) + { + wait( 1 ); + momentumPercent = GetDvarFloat( "sv_momentumPercent", 0.0 ); + if ( momentumPercent != 0.0 ) + { + player = util::getHostPlayer(); + if ( !isdefined( player ) ) + { + return; + } + if ( isdefined( player.killstreak ) ) + { + _setPlayerMomentum( player, int( 2000 * ( momentumPercent / 100 ) ) ); + } + } + } +} +#/ + +function giveTeamScore( event, team, player, victim ) +{ + if ( level.overrideTeamScore ) + return; + + pixbeginevent("level.onTeamScore"); + teamScore = game["teamScores"][team]; + [[level.onTeamScore]]( event, team ); + pixendevent(); + + newScore = game["teamScores"][team]; + + bbPrint( "mpteamscores", "gametime %d event %s team %d diff %d score %d", getTime(), event, team, newScore - teamScore, newScore ); + + if ( teamScore == newScore ) + return; + + updateTeamScores( team ); + + thread globallogic::checkScoreLimit(); + thread globallogic::checkRoundScoreLimit(); +} + +function giveTeamScoreForObjective_DelayPostProcessing( team, score ) +{ + teamScore = game["teamScores"][team]; + + onTeamScore_IncrementScore( score, team ); + + newScore = game["teamScores"][team]; + + bbPrint( "mpteamobjscores", "gametime %d team %d diff %d score %d", getTime(), team, newScore - teamScore, newScore ); + + if ( teamScore == newScore ) + return; + + updateTeamScores( team ); +} + +function postProcessTeamScores( teams ) +{ + foreach( team in teams ) + { + OnTeamScore_PostProcess( team ); + } + + thread globallogic::checkScoreLimit(); + thread globallogic::checkRoundScoreLimit(); +} + +function giveTeamScoreForObjective( team, score ) +{ + if ( !isdefined( level.teams[team] ) ) + return; + + teamScore = game["teamScores"][team]; + + onTeamScore( score, team ); + + newScore = game["teamScores"][team]; + + bbPrint( "mpteamobjscores", "gametime %d team %d diff %d score %d", getTime(), team, newScore - teamScore, newScore ); + + if ( teamScore == newScore ) + return; + + updateTeamScores( team ); + + thread globallogic::checkScoreLimit(); + thread globallogic::checkRoundScoreLimit(); + thread globallogic::checkSuddenDeathScoreLimit( team ); +} + +function _setTeamScore( team, teamScore ) +{ + if ( teamScore == game["teamScores"][team] ) + return; + + game["teamScores"][team] = math::clamp( teamScore, 0, 1000000 ); + + updateTeamScores( team ); + + thread globallogic::checkScoreLimit(); + thread globallogic::checkRoundScoreLimit(); +} + +function resetTeamScores() +{ + if ( level.scoreRoundWinBased || util::isFirstRound() ) + { + foreach( team in level.teams ) + { + game["teamScores"][team] = 0; + } + } + + globallogic_score::updateAllTeamScores(); +} + +function resetAllScores() +{ + resetTeamScores(); + resetPlayerScores(); + teamops::stopTeamops(); +} + +function resetPlayerScores() +{ + players = level.players; + winner = undefined; + tie = false; + + for( i = 0; i < players.size; i++ ) + { + + if ( isdefined( players[i].pers["score"] ) ) + _setPlayerScore( players[i], 0 ); + + } +} + +function updateTeamScores( team ) +{ + setTeamScore( team, game["teamScores"][team] ); + level thread globallogic::checkTeamScoreLimitSoon( team ); +} + +function updateAllTeamScores( ) +{ + foreach( team in level.teams ) + { + updateTeamScores( team ); + } +} + +function _getTeamScore( team ) +{ + return game["teamScores"][team]; +} + +function getHighestTeamScoreTeam() +{ + score = 0; + winning_teams = []; + + foreach( team in level.teams ) + { + team_score = game["teamScores"][team]; + if ( team_score > score ) + { + score = team_score; + winning_teams = []; + } + + if ( team_score == score ) + { + winning_teams[team] = team; + } + } + + return winning_teams; +} + +function areTeamArraysEqual( teamsA, teamsB ) +{ + if ( teamsA.size != teamsB.size ) + return false; + + foreach( team in teamsA ) + { + if ( !isdefined( teamsB[team] ) ) + return false; + } + + return true; +} + +function onTeamScore( score, team ) +{ + onTeamScore_IncrementScore( score, team ); + onTeamScore_PostProcess( team ); +} + +function onTeamScore_IncrementScore( score, team ) +{ + game["teamScores"][team] += score; + if ( game["teamScores"][team] < 0 ) + { + game["teamScores"][team] = 0; + } + + if ( level.clampScoreLimit ) + { + if ( level.scoreLimit && game["teamScores"][team] > level.scoreLimit ) + game["teamScores"][team] = level.scoreLimit; + + if ( level.roundScoreLimit && game["teamScores"][team] > util::get_current_round_score_limit() ) + game["teamScores"][team] = util::get_current_round_score_limit(); + } +} + +function onTeamScore_PostProcess( team ) +{ + if ( level.splitScreen ) + return; + + if ( level.scoreLimit == 1 ) + return; + + isWinning = getHighestTeamScoreTeam(); + + if ( isWinning.size == 0 ) + return; + + if ( getTime() - level.lastStatusTime < 5000 ) + return; + + if ( areTeamArraysEqual( isWinning, level.wasWinning ) ) + return; + + // dont say anything if they are the tied for the lead currently because they are not really winning + if ( isWinning.size == 1 ) + { + level.lastStatusTime = getTime(); + + // looping because its easier but there is only one iteration (size == 1) + foreach( team in isWinning ) + { + // dont say anything if you were already winning + if ( isdefined( level.wasWinning[team] ) ) + { + // ... and there was no one tied with you + if ( level.wasWinning.size == 1 ) + continue; + } + + // you have just taken the lead and you are the only one in the lead + globallogic_audio::leader_dialog( "lead_taken", team, undefined, "status" ); + } + } + else // This else statement makes it so that no one will hear 'loss' VO until one team has taken the lead + { + return; + } + + // dont say anything if they were the tied for the lead previously because they were not really winning + if ( level.wasWinning.size == 1) + { + // looping because its easier but there is only one iteration (size == 1) + foreach( team in level.wasWinning ) + { + // dont say anything if you are still winning + if ( isdefined( isWinning[team] ) ) + { + // and you are not currently tied for the lead + if ( isWinning.size == 1 ) + continue; + + // or you were previously tied for the lead (already told) + if ( level.wasWinning.size > 1 ) + continue; + } + + + // you are either no longer winning or you were winning and now are tied for the lead + globallogic_audio::leader_dialog( "lead_lost", team, undefined, "status" ); + } + } + + level.wasWinning = isWinning; +} + + +function default_onTeamScore( event, team ) +{ + score = rank::getScoreInfoValue( event ); + + assert( isdefined( score ) ); + + onTeamScore( score, team ); +} + +function initPersStat( dataName, record_stats ) +{ + if( !isdefined( self.pers[dataName] ) ) + { + self.pers[dataName] = 0; + } + + if ( !isdefined(record_stats) || record_stats == true ) + { + recordPlayerStats( self, dataName, int(self.pers[dataName]) ); + } +} + + +function getPersStat( dataName ) +{ + return self.pers[dataName]; +} + + +function incPersStat( dataName, increment, record_stats, includeGametype ) +{ + pixbeginevent( "incPersStat" ); + + self.pers[dataName] += increment; + + if ( isdefined( includeGameType ) && includeGameType ) + { + self AddPlayerStatWithGameType( dataName, increment ); + } + else + { + self AddPlayerStat( dataName, increment ); + } + + if ( !isdefined(record_stats) || record_stats == true ) + { + self thread threadedRecordPlayerStats( dataName ); + } + + pixendevent(); +} + +function threadedRecordPlayerStats( dataName ) +{ + self endon("disconnect"); + waittillframeend; + + recordPlayerStats( self, dataName, self.pers[dataName] ); +} + +function updateWinStats( winner ) +{ + winner AddPlayerStatWithGameType( "losses", -1 ); + + winner AddPlayerStatWithGameType( "wins", 1 ); + + if ( level.hardcoreMode ) + { + winner AddPlayerStat( "wins_HC", 1 ); + } + if ( level.multiTeam ) + { + winner AddPlayerStat( "wins_MULTITEAM", 1 ); + } + winner UpdateStatRatio( "wlratio", "wins", "losses" ); + + // restore winstreak, this is set to 0 on connect + restoreWinStreaks( winner ); + + winner AddPlayerStatWithGameType( "cur_win_streak", 1 ); + // This notify is used for the contracts system. It allows it to reset a contract's progress on this notify. + winner notify( "win" ); + + winner.lootXpMultiplier = true; + + cur_gamemode_win_streak = winner persistence::stat_get_with_gametype( "cur_win_streak" ); + gamemode_win_streak = winner persistence::stat_get_with_gametype( "win_streak" ); + + cur_win_streak = winner GetDStat( "playerstatslist", "cur_win_streak", "StatValue" ); + if ( !level.disableStatTracking && cur_win_streak > winner getDStat( "HighestStats", "win_streak" ) ) + { + winner setDStat( "HighestStats", "win_streak", cur_win_streak ); + } + + if ( cur_gamemode_win_streak > gamemode_win_streak ) + { + winner persistence::stat_set_with_gametype( "win_streak", cur_gamemode_win_streak ); + } + + if( bot::is_bot_ranked_match() ) + { + combatTrainingWins = winner GetDStat( "combatTrainingWins" ); + + winner setDStat( "combatTrainingWins", combatTrainingWins + 1 ); + } + + updateWeaponContractWin( winner ); + + updateContractWin( winner ); +} + +function canUpdateWeaponContractStats( player ) +{ + if ( GetDvarInt( "enable_weapon_contract", 0 ) == 0 ) + return false; + + if ( !level.rankedMatch && !level.arenaMatch ) + return false; + + if ( player GetDStat( "contracts", 3, "index" ) != 0 ) + return false; + + return true; +} + +function updateWeaponContractStart( player ) +{ + if ( !canUpdateWeaponContractStats( player ) ) + return; + + if ( player GetDStat( "weaponContractData", "startTimestamp" ) == 0 ) + { + player SetDStat( "weaponContractData", "startTimestamp", GetUTC() ); + } +} + +function updateWeaponContractWin( winner ) +{ + if ( !canUpdateWeaponContractStats( winner ) ) + return; + + matchesWon = winner GetDStat( "weaponContractData", "currentValue" ) + 1; + winner SetDStat( "weaponContractData", "currentValue", matchesWon ); + + if ( (isdefined(winner GetDStat( "weaponContractData", "completeTimestamp" ))?winner GetDStat( "weaponContractData", "completeTimestamp" ):0) == 0 ) + { + targetValue = GetDvarInt( "weapon_contract_target_value", 100 ); + if ( matchesWon >= targetValue ) + { + winner SetDStat( "weaponContractData", "completeTimestamp", GetUTC() ); + } + } +} + +function updateWeaponContractPlayed() +{ + foreach( player in level.players ) + { + if ( !isdefined( player ) ) + continue; + + if ( !canUpdateWeaponContractStats( player ) ) + continue; + + // must at least spawned into a team to get matches played credit, otherwise players could potentially exploit this by never spawning in + if ( !isdefined( player.pers["team"] ) ) + continue; + + matchesPlayed = player GetDStat( "weaponContractData", "matchesPlayed" ) + 1; + player SetDStat( "weaponContractData", "matchesPlayed", matchesPlayed ); + } +} + +function updateContractWin( winner ) +{ + if ( !isdefined( level.updateContractWinEvents ) ) + return; + + foreach( contractWinEvent in level.updateContractWinEvents ) + { + if ( !isdefined( contractWinEvent ) ) + continue; + + [[ contractWinEvent ]]( winner ); + } +} + +function registerContractWinEvent( event ) +{ + if ( !isdefined( level.updateContractWinEvents ) ) + level.updateContractWinEvents = []; + + if ( !isdefined( level.updateContractWinEvents ) ) level.updateContractWinEvents = []; else if ( !IsArray( level.updateContractWinEvents ) ) level.updateContractWinEvents = array( level.updateContractWinEvents ); level.updateContractWinEvents[level.updateContractWinEvents.size]=event;; +} + +function updateLossStats( loser ) +{ + loser AddPlayerStatWithGameType( "losses", 1 ); + loser UpdateStatRatio( "wlratio", "wins", "losses" ); + // This notify is used for the contracts system. It allows it to reset a contract's progress on this notify. + loser notify( "loss" ); +} + +function updateLossLateJoinStats( loser ) +{ + loser AddPlayerStatWithGameType( "losses", -1 ); + loser AddPlayerStatWithGameType( "losses_late_join", 1 ); + + loser UpdateStatRatio( "wlratio", "wins", "losses" ); +} + +function updateTieStats( loser ) +{ + loser AddPlayerStatWithGameType( "losses", -1 ); + + loser AddPlayerStatWithGameType( "ties", 1 ); + loser UpdateStatRatio( "wlratio", "wins", "losses" ); + + if ( !level.disableStatTracking ) + { + loser SetDStat( "playerstatslist", "cur_win_streak", "StatValue", 0 ); + } + // This notify is used for the contracts system. It allows it to reset a contract's progress on this notify. + loser notify( "tie" ); +} + +function updateWinLossStats( winner ) +{ + if ( !util::wasLastRound() && !level.hostForcedEnd ) + return; + + players = level.players; + + updateWeaponContractPlayed(); + + if ( !isdefined( winner ) || ( isdefined( winner ) && !isPlayer( winner ) && winner == "tie" ) ) + { + for ( i = 0; i < players.size; i++ ) + { + if ( !isdefined( players[i].pers["team"] ) ) + continue; + + if ( level.hostForcedEnd && players[i] IsHost() ) + continue; + + updateTieStats( players[i] ); + } + } + else if ( isPlayer( winner ) ) + { + if ( level.hostForcedEnd && winner IsHost() ) + return; + + updateWinStats( winner ); + + if ( !level.teamBased ) + { + placement = level.placement["all"]; + topThreePlayers = min( 3, placement.size ); + + for ( index = 1; index < topThreePlayers; index++ ) + { + nextTopPlayer = placement[index]; + + updateWinStats( nextTopPlayer ); + } + + for ( i = 0; i < players.size; i++ ) + { + if( winner == players[i] ) + continue; + + index = 1; + for( ; index < topThreePlayers; index++ ) + { + if( players[i] == placement[index] ) + break; + } + if( index < topThreePlayers ) + continue; + + if ( level.rankedMatch && !level.leagueMatch && ( players[i].pers["lateJoin"] === true ) ) + { + updateLossLateJoinStats( players[i] ); + } + } + } + } + else + { + for ( i = 0; i < players.size; i++ ) + { + if ( !isdefined( players[i].pers["team"] ) ) + continue; + + if ( level.hostForcedEnd && players[i] IsHost() ) + continue; + + if ( winner == "tie" ) + updateTieStats( players[i] ); + else if ( players[i].pers["team"] == winner ) + updateWinStats( players[i] ); + else + { + // need to add condition for arena late join loss prevention + if ( level.rankedMatch && !level.leagueMatch && ( players[i].pers["lateJoin"] === true ) ) + { + updateLossLateJoinStats( players[i] ); + } + + if ( !level.disableStatTracking ) + { + players[i] SetDStat( "playerstatslist", "cur_win_streak", "StatValue", 0 ); + } + } + } + } +} + +// self is the player +function backupAndClearWinStreaks() +{ + if ( ( isdefined( level.freerun ) && level.freerun ) ) + return; + + // Global + self.pers[ "winStreak" ] = self GetDStat( "playerstatslist", "cur_win_streak", "StatValue" ); + if ( !level.disableStatTracking ) + { + self SetDStat( "playerstatslist", "cur_win_streak", "StatValue", 0 ); + } + + // Gametype + self.pers[ "winStreakForGametype" ] = persistence::stat_get_with_gametype( "cur_win_streak" ); + self persistence::stat_set_with_gametype( "cur_win_streak", 0 ); +} + +function restoreWinStreaks( winner ) +{ + if ( !level.disableStatTracking ) + { + // Global + winner SetDStat( "playerstatslist", "cur_win_streak", "StatValue", winner.pers[ "winStreak" ] ); + } + + // Gametype + winner persistence::stat_set_with_gametype( "cur_win_streak", winner.pers[ "winStreakForGametype" ] ); +} + +function incKillstreakTracker( weapon ) +{ + self endon("disconnect"); + + waittillframeend; + + if( weapon.name == "artillery" ) + self.pers["artillery_kills"]++; + + if( weapon.name == "dog_bite" ) + self.pers["dog_kills"]++; +} + +function trackAttackerKill( name, rank, xp, prestige, xuid, weapon ) +{ + self endon("disconnect"); + attacker = self; + + waittillframeend; + + pixbeginevent("trackAttackerKill"); + + if ( !isdefined( attacker.pers["killed_players"][name] ) ) + attacker.pers["killed_players"][name] = 0; + + if ( !isdefined( attacker.pers["killed_players_with_specialist"][name] ) ) + attacker.pers["killed_players_with_specialist"][name] = 0; + + if ( !isdefined( attacker.killedPlayersCurrent[name] ) ) + attacker.killedPlayersCurrent[name] = 0; + + if ( !isdefined( attacker.pers["nemesis_tracking"][name] ) ) + attacker.pers["nemesis_tracking"][name] = 0; + + attacker.pers["killed_players"][name]++; + + + attacker.killedPlayersCurrent[name]++; + attacker.pers["nemesis_tracking"][name] += 1.0; + if ( attacker.pers["nemesis_name"] == name ) + { + attacker challenges::killedNemesis(); + } + + if ( isdefined( weapon.isHeroWeapon ) && weapon.isHeroWeapon == true ) + { + attacker.pers["killed_players_with_specialist"][name]++; + } + + + if( attacker.pers["nemesis_name"] == "" || attacker.pers["nemesis_tracking"][name] > attacker.pers["nemesis_tracking"][attacker.pers["nemesis_name"]] ) + { + attacker.pers["nemesis_name"] = name; + attacker.pers["nemesis_rank"] = rank; + attacker.pers["nemesis_rankIcon"] = prestige; + attacker.pers["nemesis_xp"] = xp; + attacker.pers["nemesis_xuid"] = xuid; + } + else if( isdefined( attacker.pers["nemesis_name"] ) && ( attacker.pers["nemesis_name"] == name ) ) + { + attacker.pers["nemesis_rank"] = rank; + attacker.pers["nemesis_xp"] = xp; + } + + if ( !isdefined( attacker.lastKilledVictim ) || !isdefined( attacker.lastKilledVictimCount ) ) + { + attacker.lastKilledVictim = name; + attacker.lastKilledVictimCount = 0; + } + + if ( attacker.lastKilledVictim == name ) + { + attacker.lastKilledVictimCount++; + if ( attacker.lastKilledVictimCount >= 5 ) + { + attacker.lastKilledVictimCount = 0; + attacker AddPlayerStat( "streaker", 1 ); + } + } + else + { + attacker.lastKilledVictim = name; + attacker.lastKilledVictimCount = 1; + } + + pixendevent(); +} + +function trackAttackeeDeath( attackerName, rank, xp, prestige, xuid ) +{ + self endon("disconnect"); + + waittillframeend; + + pixbeginevent("trackAttackeeDeath"); + + if ( !isdefined( self.pers["killed_by"][attackerName] ) ) + self.pers["killed_by"][attackerName] = 0; + + self.pers["killed_by"][attackerName]++; + + if ( !isdefined( self.pers["nemesis_tracking"][attackerName] ) ) + self.pers["nemesis_tracking"][attackerName] = 0; + + self.pers["nemesis_tracking"][attackerName] += 1.5; + + if( self.pers["nemesis_name"] == "" || self.pers["nemesis_tracking"][attackerName] > self.pers["nemesis_tracking"][self.pers["nemesis_name"]] ) + { + self.pers["nemesis_name"] = attackerName; + self.pers["nemesis_rank"] = rank; + self.pers["nemesis_rankIcon"] = prestige; + self.pers["nemesis_xp"] = xp; + self.pers["nemesis_xuid"] =xuid; + } + else if( isdefined( self.pers["nemesis_name"] ) && ( self.pers["nemesis_name"] == attackerName ) ) + { + self.pers["nemesis_rank"] = rank; + self.pers["nemesis_xp"] = xp; + } + + //Nemesis Killcam - ( hopefully even with the wait it gets there with enough time not to cause a flicker) + if( self.pers["nemesis_name"] == attackerName && self.pers["nemesis_tracking"][attackerName] >= 2 ) + self setClientUIVisibilityFlag( "killcam_nemesis", 1 ); + else + self setClientUIVisibilityFlag( "killcam_nemesis", 0 ); + + selfKillsTowardsAttacker = 0; + + if ( isDefined( self.pers["killed_players"][attackerName] ) ) + { + selfKillsTowardsAttacker = self.pers["killed_players"][attackerName]; + } + + self LUINotifyEvent( &"track_victim_death", 2, self.pers["killed_by"][attackerName], selfKillsTowardsAttacker ); + + pixendevent(); +} + +function default_isKillBoosting() +{ + return false; +} + +function giveKillStats( sMeansOfDeath, weapon, eVictim ) +{ + self endon("disconnect"); + + // setting this now so the scoreboard gets updated immediatly + self.kills = self.kills + 1; + + waittillframeend; + + if ( level.rankedMatch && self [[level.isKillBoosting]]() ) + { + /# + self IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU OFFENSIVE CREDIT AS BOOSTING PREVENTION" ); + #/ + return; + } + + pixbeginevent("giveKillStats"); + + self globallogic_score::incPersStat( "kills", 1, true, true ); + self.kills = self globallogic_score::getPersStat( "kills" ); + self UpdateStatRatio( "kdratio", "kills", "deaths" ); + + attacker = self; + if ( sMeansOfDeath == "MOD_HEAD_SHOT" && !killstreaks::is_killstreak_weapon( weapon ) ) + { + attacker thread incPersStat( "headshots", 1 , true, false ); + attacker.headshots = attacker.pers["headshots"]; + if ( isdefined( eVictim ) ) + eVictim RecordKillModifier("headshot"); + } + + pixendevent(); +} + +function incTotalKills( team ) +{ + if ( level.teambased && isdefined( level.teams[team] ) ) + { + game["totalKillsTeam"][team]++; + } + + game["totalKills"]++; +} + +function setInflictorStat( eInflictor, eAttacker, weapon ) +{ + if ( !isdefined( eAttacker ) ) + return; + + weaponPickedUp = false; + if( isdefined( eAttacker.pickedUpWeapons ) && isdefined( eAttacker.pickedUpWeapons[weapon] ) ) + { + weaponPickedUp = true; + } + + if ( !isdefined( eInflictor ) ) + { + eAttacker AddWeaponStat( weapon, "hits", 1, eAttacker.class_num, weaponPickedUp ); + return; + } + + if ( !isdefined( eInflictor.playerAffectedArray ) ) + eInflictor.playerAffectedArray = []; + + foundNewPlayer = true; + for ( i = 0 ; i < eInflictor.playerAffectedArray.size ; i++ ) + { + if ( eInflictor.playerAffectedArray[i] == self ) + { + foundNewPlayer = false; + break; + } + } + + if ( foundNewPlayer ) + { + eInflictor.playerAffectedArray[eInflictor.playerAffectedArray.size] = self; + if( weapon.rootweapon.name == "tabun_gas" ) + { + eAttacker AddWeaponStat( weapon, "used", 1 ); + } + eAttacker AddWeaponStat( weapon, "hits", 1, eAttacker.class_num, weaponPickedUp ); + } +} + +//****************************************************************** +// * +// * +//****************************************************************** +function processShieldAssist( killedplayer ) // self == riotshield player +{ + self endon( "disconnect" ); + killedplayer endon( "disconnect" ); + + wait .05; // don't ever run on the same frame as the playerkilled callback. + util::WaitTillSlowProcessAllowed(); + + if ( !isdefined( level.teams[ self.pers["team"] ] ) ) + return; + + if ( self.pers["team"] == killedplayer.pers["team"] ) + return; + + if ( !level.teambased ) + return; + + self globallogic_score::incPersStat( "assists", 1, true, true ); + + self.assists = self globallogic_score::getPersStat( "assists" ); + + scoreevents::processScoreEvent( "shield_assist", self, killedplayer, "riotshield" ); +} + +function processAssist( killedplayer, damagedone, weapon ) +{ + self endon("disconnect"); + killedplayer endon("disconnect"); + + wait .05; // don't ever run on the same frame as the playerkilled callback. + util::WaitTillSlowProcessAllowed(); + + if ( !isdefined( level.teams[ self.pers["team"] ] ) ) + return; + + if ( self.pers["team"] == killedplayer.pers["team"] ) + return; + + if ( !level.teambased ) + return; + + assist_level = "assist"; + + assist_level_value = int( ceil( damagedone / 25 ) ); + + if ( assist_level_value < 1 ) + { + assist_level_value = 1; + } + else if ( assist_level_value > 3 ) + { + assist_level_value = 3; + } + assist_level = assist_level + "_" + ( assist_level_value * 25 ); + + self globallogic_score::incPersStat( "assists", 1, true, true ); + + self.assists = self globallogic_score::getPersStat( "assists" ); + + if ( isdefined( weapon ) ) + { + weaponPickedUp = false; + if( isdefined( self.pickedUpWeapons ) && isdefined( self.pickedUpWeapons[weapon] ) ) + { + weaponPickedUp = true; + } + self AddWeaponStat( weapon, "assists", 1, self.class_num, weaponPickedUp ); + } + + switch( weapon.name ) + { + case "concussion_grenade": + assist_level = "assist_concussion"; + break; + case "flash_grenade": + assist_level = "assist_flash"; + break; + case "emp_grenade": + assist_level = "assist_emp"; + break; + case "proximity_grenade": + case "proximity_grenade_aoe": + assist_level = "assist_proximity"; + break; + } + self challenges::assisted(); + scoreevents::processScoreEvent( assist_level, self, killedplayer, weapon ); +} + +function processKillstreakAssists( attacker, inflictor, weapon ) +{ + if ( !isdefined( attacker ) || !isdefined( attacker.team ) || self util::IsEnemyPlayer( attacker ) == false ) + return; + + // if the player suicided, dont give the assist + if ( self == attacker || ( attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" ) ) + return; + + enemyCUAVActive = false; + if ( attacker hasperk( "specialty_immunecounteruav" ) == false ) + { + foreach( team in level.teams ) + { + if ( team == attacker.team ) + { + continue; + } + + if( counteruav::TeamHasActiveCounterUAV( team ) ) + { + enemyCUAVActive = true; + } + } + } + + foreach( player in level.players ) + { + if ( player.team != attacker.team ) + continue; + + if ( player.team == "spectator" ) + continue; + + if ( player == attacker ) + continue; + + if ( player.sessionstate != "playing" ) + continue; + + assert( isdefined ( level.activePlayerCounterUAVs[ player.entNum ] ) ); + assert( isdefined ( level.activePlayerUAVs[ player.entNum ] ) ); + assert( isdefined ( level.activePlayerSatellites[ player.entNum ] ) ); + + is_killstreak_weapon = killstreaks::is_killstreak_weapon( weapon ); + if ( level.activePlayerCounterUAVs[ player.entNum ] > 0 && !is_killstreak_weapon ) + { + scoreGiven = scoreevents::processScoreEvent( "counter_uav_assist", player ); + if ( isdefined ( scoreGiven ) ) + { + player challenges::earnedCUAVAssistScore( scoreGiven ); + killstreakIndex = level.killstreakindices["killstreak_counteruav"]; + killstreaks::killstreak_assist(player, self, killstreakIndex); + player process_killstreak_assist_score( "counteruav", scoreGiven ); + } + } + + if ( enemyCUAVActive == false ) + { + activeUAV = level.activePlayerUAVs[ player.entNum ]; + if( level.forceRadar == 1 ) + activeUAV--; + + if( activeUAV > 0 && !is_killstreak_weapon ) + { + scoreGiven = scoreevents::processScoreEvent( "uav_assist", player ); + if ( isdefined ( scoreGiven ) ) + { + player challenges::earnedUAVAssistScore( scoreGiven ); + killstreakIndex = level.killstreakindices["killstreak_uav"]; + killstreaks::killstreak_assist(player, self, killstreakIndex); + player process_killstreak_assist_score( "uav", scoreGiven ); + } + } + + if ( level.activePlayerSatellites[ player.entNum ] > 0 && !is_killstreak_weapon ) + { + scoreGiven = scoreevents::processScoreEvent( "satellite_assist", player ); + if ( isdefined ( scoreGiven ) ) + { + player challenges::earnedSatelliteAssistScore( scoreGiven ); + killstreakIndex = level.killstreakindices["killstreak_satellite"]; + killstreaks::killstreak_assist(player, self, killstreakIndex); + player process_killstreak_assist_score( "satellite", scoreGiven ); + } + } + } + + + if( player EMP::HasActiveEMP() ) + { + scoreGiven = scoreevents::processScoreEvent( "emp_assist", player ); + if ( isdefined ( scoreGiven ) ) + { + player challenges::earnedEMPAssistScore( scoreGiven ); + killstreakIndex = level.killstreakindices["killstreak_emp"]; + killstreaks::killstreak_assist(player, self, killstreakIndex); + player process_killstreak_assist_score( "emp", scoreGiven ); + } + } + } +} + +function process_killstreak_assist_score( killstreak, scoreGiven ) +{ + player = self; + killstreakPurchased = false; + if ( isdefined( killstreak ) && isdefined( level.killstreaks[ killstreak ] ) ) + { + killstreakPurchased = player util::is_item_purchased( level.killstreaks[ killstreak ].menuname ); + } + player util::player_contract_event( "killstreak_score", scoreGiven, killstreakPurchased ); +} + + +/# +function xpRateThread() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + while ( level.inPrematchPeriod ) + {wait(.05);}; + + for ( ;; ) + { + wait ( 5.0 ); + if ( isdefined( level.teams[ level.players[0].pers["team"] ] ) ) + self rank::giveRankXP( "kill", int(min( GetDvarint( "scr_xprate" ), 50 )) ); + } +} +#/ diff --git a/mp/gametypes/_globallogic_spawn.gsc b/mp/gametypes/_globallogic_spawn.gsc new file mode 100644 index 0000000..06e6056 --- /dev/null +++ b/mp/gametypes/_globallogic_spawn.gsc @@ -0,0 +1,1113 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_message_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\util_shared; +#using scripts\shared\clientfield_shared; + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_defaults; +#using scripts\mp\gametypes\_globallogic_player; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_ui; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_hud_message; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\_spectating; + +#using scripts\mp\_util; +#using scripts\mp\_vehicle; +#using scripts\mp\bots\_bot; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\_teamops; + +#namespace globallogic_spawn; + +#precache( "eventstring", "player_spawned" ); +#precache( "eventstring", "show_gametype_objective_hint" ); + +function TimeUntilSpawn( includeTeamkillDelay ) +{ + if ( level.inGracePeriod && !self.hasSpawned ) + return 0; + + respawnDelay = 0; + if ( self.hasSpawned ) + { + result = self [[level.onRespawnDelay]](); + if ( isdefined( result ) ) + respawnDelay = result; + else + respawnDelay = level.playerRespawnDelay; + + if( isdefined( level.playerIncrementalRespawnDelay ) && isdefined( self.pers["spawns"] ) ) + respawnDelay += level.playerIncrementalRespawnDelay * self.pers["spawns"]; + + if ( self.suicide && level.suicideSpawnDelay > 0 ) + { + respawnDelay += level.suicideSpawnDelay; + } + + if ( self.teamKilled && level.teamKilledSpawnDelay > 0 ) + { + respawnDelay += level.teamKilledSpawnDelay; + } + + if ( includeTeamkillDelay && ( isdefined( self.teamKillPunish ) && self.teamKillPunish ) ) + respawnDelay += globallogic_player::TeamKillDelay(); + } + + waveBased = (level.waveRespawnDelay > 0); + + if ( waveBased ) + return self TimeUntilWaveSpawn( respawnDelay ); + + //Spawn straight away if player used resurrect ability + if ( ( isdefined( self.usedResurrect ) && self.usedResurrect ) ) + { + return 0; + } + + return respawnDelay; +} + +function allTeamsHaveExisted() +{ + foreach( team in level.teams ) + { + if ( !level.everExisted[ team ] ) + return false; + } + + return true; +} + +function maySpawn() +{ + if ( isdefined( level.maySpawn ) && !( self [[level.maySpawn]]() ) ) + { + return false; + } + + if ( isdefined( level.maySpawnGameMode ) ) + { + return self [[level.maySpawnGameMode]](); + } + + if ( level.inOvertime ) + return false; + + if ( level.playerQueuedRespawn && !isdefined(self.allowQueueSpawn) && !level.inGracePeriod && !level.useStartSpawns ) + return false; + + if ( level.numLives || level.numTeamLives ) + { + if ( level.teamBased ) + gameHasStarted = ( allTeamsHaveExisted() ); + else + gameHasStarted = (level.maxPlayerCount > 1) || ( !util::isOneRound() && !util::isFirstRound() ); + + if ( ( level.numLives && !self.pers["lives"] ) || ( level.numTeamLives && !game[self.team+"_lives"] ) ) + { + return false; + } + else if ( gameHasStarted ) + { + // disallow spawning for late comers + if ( !level.inGracePeriod && !self.hasSpawned && !level.wagerMatch ) + return false; + } + } + return true; +} + +function TimeUntilWaveSpawn( minimumWait ) +{ + // the time we'll spawn if we only wait the minimum wait. + earliestSpawnTime = gettime() + minimumWait * 1000; + + lastWaveTime = level.lastWave[self.pers["team"]]; + waveDelay = level.waveDelay[self.pers["team"]] * 1000; + + if( waveDelay == 0 ) + return 0; + + // the number of waves that will have passed since the last wave happened, when the minimum wait is over. + numWavesPassedEarliestSpawnTime = (earliestSpawnTime - lastWaveTime) / waveDelay; + // rounded up + numWaves = ceil( numWavesPassedEarliestSpawnTime ); + + timeOfSpawn = lastWaveTime + numWaves * waveDelay; + + // avoid spawning everyone on the same frame + if ( isdefined( self.waveSpawnIndex ) ) + timeOfSpawn += 50 * self.waveSpawnIndex; + + return (timeOfSpawn - gettime()) / 1000; +} + +function stopPoisoningAndFlareOnSpawn() +{ + self endon("disconnect"); + + self.inPoisonArea = false; + self.inBurnArea = false; + self.inFlareVisionArea = false; + self.inGroundNapalm = false; +} + +function spawnPlayerPrediction() +{ + self endon ( "disconnect" ); + self endon ( "end_respawn" ); + self endon ( "game_ended" ); + self endon ( "joined_spectators"); + self endon ( "spawned"); + + // no prediction if we have no lives left. + livesLeft = !(level.numLives && !self.pers["lives"]) && !(level.numTeamLives && !game[self.team+"_lives"]); + if ( !livesLeft ) + return; + + while (1) + { + // wait some time between predictions + wait( 0.5 ); + + //pixbeginevent("onSpawnPlayer_Prediction"); + + // this is slightly inaccurate, we should probably call the gametype specific spawn, however this is prevents us having to pass the arg all the way through each gametype. + spawning::onSpawnPlayer( true ); + + //pixendevent(); + } +} + +function doInitialSpawnMessaging() +{ + self endon("disconnect" ); + + self.playLeaderDialog = true; + + if( isdefined( level.disablePrematchMessages ) && level.disablePrematchMessages ) + { + return; + } + + team = self.pers["team"]; + thread hud_message::showInitialFactionPopup( team ); + + while ( level.inPrematchPeriod ) + { + {wait(.05);}; + } + + hintMessage = util::getObjectiveHintText( team ); + if ( isdefined( hintMessage ) ) + { + self LUINotifyEvent( &"show_gametype_objective_hint", 1, hintMessage ); + } + + if ( isdefined( level.leaderDialog ) ) + { + if ( self.pers["playedGameMode"] !== true ) + { + if( level.hardcoreMode ) + self globallogic_audio::leader_dialog_on_player( level.leaderDialog.startHcGameDialog ); + else + self globallogic_audio::leader_dialog_on_player( level.leaderDialog.startGameDialog ); + } + + if ( team == game["attackers"] ) + self globallogic_audio::leader_dialog_on_player( level.leaderDialog.offenseOrderDialog ); + else + self globallogic_audio::leader_dialog_on_player( level.leaderDialog.defenseOrderDialog ); + } +} + +function spawnPlayer() +{ + pixbeginevent( "spawnPlayer_preUTS" ); + + //profilelog_begintiming( 3, "ship" ); + + self endon("disconnect"); + self endon("joined_spectators"); + self notify("spawned"); + level notify("player_spawned"); + self notify("end_respawn"); + self notify( "started_spawnPlayer" ); + + self setSpawnVariables(); + self LUINotifyEvent( &"player_spawned", 0 ); + self LUINotifyEventToSpectators( &"player_spawned", 0 ); + + if ( !isdefined( self.pers["resetMomentumOnSpawn"] ) || self.pers["resetMomentumOnSpawn"] ) + { + self globallogic_score::resetPlayerMomentumOnSpawn(); + self.pers["resetMomentumOnSpawn"] = false; + } + + if( globallogic_utils::getroundstartdelay() ) + { + self thread globallogic_utils::ApplyRoundStartDelay(); + } + + self.sessionteam = self.team; + + hadSpawned = self.hasSpawned; + + self.sessionstate = "playing"; + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + self.statusicon = ""; + self.damagedPlayers = []; + if ( GetDvarint( "scr_csmode" ) > 0 ) + self.maxhealth = GetDvarint( "scr_csmode" ); + else + self.maxhealth = level.playerMaxHealth; + self.health = self.maxhealth; + self.friendlydamage = undefined; + self.lastStunnedBy = undefined; + self.hasSpawned = true; + self.spawnTime = getTime(); + self.afk = false; + if ( self.pers["lives"] && ( !isdefined( level.takeLivesOnDeath ) || ( level.takeLivesOnDeath == false ) ) ) + { + self.pers["lives"]--; + if ( self.pers["lives"] == 0 ) + { + level notify( "player_eliminated" ); + self notify( "player_eliminated" ); + } + } + if ( game[self.team + "_lives"] && ( !isdefined( level.takeLivesOnDeath ) || ( level.takeLivesOnDeath == false ) ) ) + { + game[self.team + "_lives"]--; + if ( game[self.team + "_lives"] == 0 ) + { + level notify( "player_eliminated" ); + self notify( "player_eliminated" ); + } + } + + self.lastStand = undefined; + self.resurrect_not_allowed_by = undefined; + self.revivingTeammate = false; + self.burning = undefined; + self.nextKillstreakFree = undefined; + + self.deathMachineKills = 0; + + self.disabledWeapon = 0; + self util::resetUsability(); + + self globallogic_player::resetAttackerList(); + self globallogic_player::resetAttackersThisSpawnList(); + + self.diedOnVehicle= undefined; + + self.lastShotBy = 127; // 0b1111111 == no last shot by + + if ( !self.wasAliveAtMatchStart ) + { + if ( level.inGracePeriod || globallogic_utils::getTimePassed() < 20 * 1000 ) + self.wasAliveAtMatchStart = true; + } + + self setDepthOfField( 0, 0, 512, 512, 4, 0 ); + self resetFov(); + + + { + pixbeginevent("onSpawnPlayer"); + self [[level.onSpawnPlayer]](false); + if( isdefined( game["teamops"].teamopsName ) ) + { + teamops = game["teamops"].data[game["teamops"].teamopsName]; + TeamOpsStart( game["teamops"].teamOpsID, game["teamops"].teamopsRewardIndex, game["teamops"].teamopsStartTime, teamops.time ); + teamops::updateTeamOps( undefined, undefined, self.team ); + } + + if ( isdefined( level.playerSpawnedCB ) ) + { + self [[level.playerSpawnedCB]](); + } + pixendevent(); + } + + pixendevent();// "END: spawnPlayer_preUTS" + + level thread globallogic::updateTeamStatus(); + + pixbeginevent( "spawnPlayer_postUTS" ); + + self thread stopPoisoningAndFlareOnSpawn(); + + self.sensorGrenadeData = undefined; + + assert( globallogic_utils::isValidClass( self.curClass ) ); + + self.pers["momentum_at_spawn_or_game_end"] = (isdefined(self.pers["momentum"])?self.pers["momentum"]:0); + + if( SessionModeIsZombiesGame() ) + { + self loadout::giveLoadoutLevelSpecific( self.team, self.curClass ); + } + else + { + self loadout::setClass( self.curClass ); + self loadout::giveLoadout( self.team, self.curClass ); + + if ( GetDvarint( "tu11_enableClassicMode") == 1 ) + { + if ( self.team == "allies" ) + { + self SetCharacterBodyType( 0 ); + } + else + { + self SetCharacterBodyType( 2 ); + } + } + } + + if ( level.inPrematchPeriod ) + { + if ( isdefined( level.prematchPeriodSpawnFunc ) ) + { + self [[ level.prematchPeriodSpawnFunc ]](); + } + else + { + self util::freeze_player_controls( true ); + + team = self.pers["team"]; + +//CDC New music system + if( isdefined( self.pers["music"].spawn ) && self.pers["music"].spawn == false ) + { + + if (level.wagerMatch) + { + music = "SPAWN_WAGER"; + } + else + { + music = game["music"]["spawn_" + team]; + } + + if( game[ "roundsplayed" ] == 0 ) + { + self thread sndDelayedMusicStart("spawnFull"); + } + else + { + self thread sndDelayedMusicStart("spawnShort"); + } + + self.pers["music"].spawn = true; + } + + if ( level.splitscreen ) + { + if ( isdefined( level.playedStartingMusic ) ) + music = undefined; + else + level.playedStartingMusic = true; + } + + self thread doInitialSpawnMessaging(); + } + } + else + { + self util::freeze_player_controls( false ); + self enableWeapons(); + if ( !hadSpawned && game["state"] == "playing" ) + { + pixbeginevent("sound"); + team = self.team; + +//CDC New music system TODO add short spawn music + if( isdefined( self.pers["music"].spawn ) && self.pers["music"].spawn == false ) + { + music = game["music"]["spawn_" + team]; + + self thread sndDelayedMusicStart( "spawnShort" ); + + self.pers["music"].spawn = true; + } + if ( level.splitscreen ) + { + if ( isdefined( level.playedStartingMusic ) ) + music = undefined; + else + level.playedStartingMusic = true; + } + + self thread doInitialSpawnMessaging(); + + pixendevent();//"sound" + } + } + + if ( self HasPerk( "specialty_anteup" ) ) + { + anteup_bonus = GetDvarInt( "perk_killstreakAnteUpResetValue" ); + if ( self.pers["momentum_at_spawn_or_game_end"] < anteup_bonus ) + { + globallogic_score::_setPlayerMomentum( self, anteup_bonus, false ); + } + } + + if ( GetDvarString( "scr_showperksonspawn" ) == "" ) + SetDvar( "scr_showperksonspawn", "0" ); + + // Make sure that we dont show the perks on spawn if we are in hardcore. + if ( level.hardcoreMode ) + SetDvar( "scr_showperksonspawn", "0" ); + + if ( GetDvarint( "scr_showperksonspawn" ) == 1 && game["state"] != "postgame" ) + { + pixbeginevent("showperksonspawn"); + + //Checks to make sure perks are allowed. - Leif + if ( level.perksEnabled == 1) + { + self hud::showPerks( ); + } + + pixendevent();//"showperksonspawn" + } + + if ( isdefined( self.pers["momentum"] ) ) + { + self.momentum = self.pers["momentum"]; + } + + self thread noteMomentumOnGameEnded(); + + pixendevent();//"END: spawnPlayer_postUTS" + + waittillframeend; + + self notify( "spawned_player" ); + callback::callback( #"on_player_spawned" ); + + self thread globallogic_player::player_monitor_travel_dist(); + + self thread globallogic_player::player_monitor_wall_run(); + self thread globallogic_player::player_monitor_swimming(); + self thread globallogic_player::player_monitor_slide(); + self thread globallogic_player::player_monitor_doublejump(); + + self thread globallogic_player::player_monitor_inactivity(); + + /#print( "S " + self.origin[0] + " " + self.origin[1] + " " + self.origin[2] + "\n" );#/ + + SetDvar( "scr_selecting_location", "" ); + + if( !SessionModeIsZombiesGame() ) + { + self thread killstreaks::killstreak_waiter(); + } + + /# + if ( GetDvarint( "scr_xprate" ) > 0 ) + self thread globallogic_score::xpRateThread(); + #/ + + + if ( game["state"] == "postgame" ) + { + assert( !level.intermission ); + // We're in the victory screen, but before intermission + self globallogic_player::freezePlayerForRoundEnd(); + } + + self util::set_lighting_state(); +} + +function noteMomentumOnGameEnded() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "joined_spectators" ); + self endon( "joined_team" ); + + self notify( "noteMomentumOnGameEnded" ); + self endon( "noteMomentumOnGameEnded" ); + + level waittill( "game_ended" ); + + self.pers["momentum_at_spawn_or_game_end"] = (isdefined(self.pers["momentum"])?self.pers["momentum"]:0); +} + +function sndDelayedMusicStart(music) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + while(level.inPrematchPeriod) + { + wait(.05); + } + + if( !( isdefined( level.freerun ) && level.freerun ) ) + self thread globallogic_audio::set_music_on_player( music ); +} + +function spawnSpectator( origin, angles ) +{ + self notify("spawned"); + self notify("end_respawn"); + in_spawnSpectator( origin, angles ); +} + +// spawnSpectator clone without notifies for spawning between respawn delays +function respawn_asSpectator( origin, angles ) +{ + in_spawnSpectator( origin, angles ); +} + +// spawnSpectator helper +function in_spawnSpectator( origin, angles ) +{ + pixmarker("BEGIN: in_spawnSpectator"); + self setSpawnVariables(); + + // don't clear lower message if not actually a spectator, + // because it probably has important information like when we'll spawn + if ( self.pers["team"] == "spectator" ) + self util::clearLowerMessage(); + + self.sessionstate = "spectator"; + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + self.friendlydamage = undefined; + + if(self.pers["team"] == "spectator") + self.statusicon = ""; + else + self.statusicon = "hud_status_dead"; + + spectating::set_permissions_for_machine(); + + [[level.onSpawnSpectator]]( origin, angles ); + + if ( level.teamBased && !level.splitscreen ) + self thread spectatorThirdPersonness(); + + level thread globallogic::updateTeamStatus(); + pixmarker("END: in_spawnSpectator"); +} + +function spectatorThirdPersonness() +{ + self endon("disconnect"); + self endon("spawned"); + + self notify("spectator_thirdperson_thread"); + self endon("spectator_thirdperson_thread"); + + self.spectatingThirdPerson = false; +} + +function forceSpawn( time ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "spawned" ); + + if ( !isdefined( time ) ) + { + time = 60; + } + + wait ( time ); + + if ( self.hasSpawned ) + return; + + if ( self.pers["team"] == "spectator" ) + return; + + if ( !globallogic_utils::isValidClass( self.pers["class"] ) ) + { + self.pers["class"] = "CLASS_CUSTOM1"; + self.curClass = self.pers["class"]; + } + + self globallogic_ui::closeMenus(); + self thread [[level.spawnClient]](); +} + + +function kickIfDontSpawn() +{ +/# + if( GetDvarint( "scr_hostmigrationtest" ) == 1 ) + { + return; + } +#/ + + if ( self IsHost() ) + { + // don't try to kick the host + return; + } + + self kickIfIDontSpawnInternal(); + // clear any client dvars here, + // like if we set anything to change the menu appearance to warn them of kickness +} + +function kickIfIDontSpawnInternal() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "spawned" ); + + waittime = 90; + if ( GetDvarString( "scr_kick_time") != "" ) + waittime = GetDvarfloat( "scr_kick_time"); + mintime = 45; + if ( GetDvarString( "scr_kick_mintime") != "" ) + mintime = GetDvarfloat( "scr_kick_mintime"); + + starttime = gettime(); + + kickWait( waittime ); + + timePassed = (gettime() - starttime)/1000; + if ( timePassed < waittime - .1 && timePassed < mintime ) + return; + + if ( self.hasSpawned ) + return; + + if ( SessionModeIsPrivate() ) + { + return; + } + + if ( self.pers["team"] == "spectator" ) + return; + + if ( !maySpawn() && ( ( self.pers["time_played_total"] > 0 ) || util::IsPropHuntGametype() ) ) + return; + + globallogic::gameHistoryPlayerKicked(); + + kick( self getEntityNumber(), "EXE_PLAYERKICKED_NOTSPAWNED" ); +} + +function kickWait( waittime ) +{ + level endon("game_ended"); + hostmigration::waitLongDurationWithHostMigrationPause( waittime ); +} + +function spawnInterRoundIntermission() +{ + self notify("spawned"); + self notify("end_respawn"); + + self setSpawnVariables(); + + self util::clearLowerMessage(); + + self util::freeze_player_controls( false ); + + self.sessionstate = "spectator"; + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + self.friendlydamage = undefined; + + self globallogic_defaults::default_onSpawnIntermission(); + self SetOrigin( self.origin ); + self SetPlayerAngles( self.angles ); + self setDepthOfField( 0, 128, 512, 4000, 6, 1.8 ); +} + +function spawnIntermission( useDefaultCallback, endGame ) +{ + self notify("spawned"); + self notify("end_respawn"); + self endon("disconnect"); + + self setSpawnVariables(); + + self util::clearLowerMessage(); + + self util::freeze_player_controls( false ); + + self.sessionstate = "intermission"; + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + self.friendlydamage = undefined; + + if ( isdefined( useDefaultCallback ) && useDefaultCallback ) + globallogic_defaults::default_onSpawnIntermission(); + else + [[level.onSpawnIntermission]]( endGame ); + self setDepthOfField( 0, 128, 512, 4000, 6, 1.8 ); +} + +function spawnQueuedClientOnTeam( team ) +{ + player_to_spawn = undefined; + + // find the first dead player who is not already waiting to spawn + for (i = 0; i < level.deadPlayers[team].size; i++) + { + player = level.deadPlayers[team][i]; + + if ( player.waitingToSpawn ) + continue; + + player_to_spawn = player; + break; + } + + if ( isdefined(player_to_spawn) ) + { + player_to_spawn.allowQueueSpawn = true; + + player_to_spawn globallogic_ui::closeMenus(); + player_to_spawn thread [[level.spawnClient]](); + } +} + +function spawnQueuedClient( dead_player_team, killer ) +{ + if ( !level.playerQueuedRespawn ) + return; + + util::WaitTillSlowProcessAllowed(); + + spawn_team = undefined; + + if ( isdefined( killer ) && isdefined( killer.team ) && isdefined( level.teams[ killer.team ] ) ) + spawn_team = killer.team; + + if ( isdefined( spawn_team ) ) + { + spawnQueuedClientOnTeam( spawn_team ); + return; + } + + // we could not determine the killer team so spawn a player from each team + // there may be ways to exploit this by killing someone and the switching to spectator + foreach( team in level.teams ) + { + if ( team == dead_player_team ) + continue; + + spawnQueuedClientOnTeam( team ); + } +} + +function allTeamsNearScoreLimit() +{ + if ( !level.teambased ) + return false; + + if ( level.scoreLimit <= 1 ) + return false; + + foreach( team in level.teams ) + { + if ( !(game["teamScores"][team] >= level.scoreLimit - 1) ) + return false; + } + + return true; +} + +function shouldShowRespawnMessage() +{ + if ( util::wasLastRound() ) + return false; + + if ( util::isOneRound() ) + return false; + + if ( ( isdefined( level.livesDoNotReset ) && level.livesDoNotReset ) ) + return false; + + if ( allTeamsNearScoreLimit() ) + return false; + + return true; +} + +function default_spawnMessage() +{ + util::setLowerMessage( game["strings"]["spawn_next_round"] ); + self thread globallogic_ui::removeSpawnMessageShortly( 3 ); +} + +function showSpawnMessage() +{ + if ( shouldShowRespawnMessage() ) + { + self thread [[level.spawnMessage]](); + } +} + +function spawnClient( timeAlreadyPassed ) +{ + pixbeginevent("spawnClient"); + assert( isdefined( self.team ) ); + assert( globallogic_utils::isValidClass( self.curClass ) ); + + if ( !self maySpawn() && !( isdefined( self.usedResurrect ) && self.usedResurrect ) ) + { + currentorigin = self.origin; + currentangles = self.angles; + + self showSpawnMessage(); + + self thread [[level.spawnSpectator]]( currentorigin + (0, 0, 60), currentangles ); + pixendevent(); + return; + } + + if ( self.waitingToSpawn ) + { + pixendevent(); + return; + } + self.waitingToSpawn = true; + self.allowQueueSpawn = undefined; + + self waitAndSpawnClient( timeAlreadyPassed ); + + if ( isdefined( self ) ) + self.waitingToSpawn = false; + + pixendevent(); +} + +function waitAndSpawnClient( timeAlreadyPassed ) +{ + self endon ( "disconnect" ); + self endon ( "end_respawn" ); + level endon ( "game_ended" ); + + if ( !isdefined( timeAlreadyPassed ) ) + timeAlreadyPassed = 0; + + spawnedAsSpectator = false; + + if ( ( isdefined( self.teamKillPunish ) && self.teamKillPunish ) ) + { + teamKillDelay = globallogic_player::TeamKillDelay(); + if ( teamKillDelay > timeAlreadyPassed ) + { + teamKillDelay -= timeAlreadyPassed; + timeAlreadyPassed = 0; + } + else + { + timeAlreadyPassed -= teamKillDelay; + teamKillDelay = 0; + } + + if ( teamKillDelay > 0 ) + { + util::setLowerMessage( &"MP_FRIENDLY_FIRE_WILL_NOT", teamKillDelay ); + + self thread respawn_asSpectator( self.origin + (0, 0, 60), self.angles ); + spawnedAsSpectator = true; + + wait( teamKillDelay ); + } + + self.teamKillPunish = false; + } + + if ( !isdefined( self.waveSpawnIndex ) && isdefined( level.wavePlayerSpawnIndex[self.team] ) ) + { + self.waveSpawnIndex = level.wavePlayerSpawnIndex[self.team]; + level.wavePlayerSpawnIndex[self.team]++; + } + + timeUntilSpawn = TimeUntilSpawn( false ); + if ( timeUntilSpawn > timeAlreadyPassed ) + { + timeUntilSpawn -= timeAlreadyPassed; + timeAlreadyPassed = 0; + } + else + { + timeAlreadyPassed -= timeUntilSpawn; + timeUntilSpawn = 0; + } + + if ( timeUntilSpawn > 0 ) + { + // spawn player into spectator on death during respawn delay, if he switches teams during this time, he will respawn next round + if ( level.playerQueuedRespawn ) + util::setLowerMessage( game["strings"]["you_will_spawn"], timeUntilSpawn ); + else if ( self IsSplitscreen() ) + util::setLowerMessage( game["strings"]["waiting_to_spawn_ss"], timeUntilSpawn, true ); + else + util::setLowerMessage( game["strings"]["waiting_to_spawn"], timeUntilSpawn ); + + if ( !spawnedAsSpectator ) + { + spawnOrigin = self.origin + (0, 0, 60); + spawnAngles = self.angles; + if ( isdefined ( level.useIntermissionPointsOnWaveSpawn ) && [[level.useIntermissionPointsOnWaveSpawn]]() == true ) + { + spawnPoint = spawnlogic::get_random_intermission_point(); + if ( isdefined( spawnPoint ) ) + { + spawnOrigin = spawnPoint.origin; + spawnAngles = spawnPoint.angles; + } + } + + self thread respawn_asSpectator( spawnOrigin, spawnAngles ); + } + spawnedAsSpectator = true; + + self globallogic_utils::waitForTimeOrNotify( timeUntilSpawn, "force_spawn" ); + + self notify("stop_wait_safe_spawn_button"); + } + + if ( isdefined( level.gametypeSpawnWaiter ) ) + { + if ( !spawnedAsSpectator ) + self thread respawn_asSpectator( self.origin + (0, 0, 60), self.angles ); + spawnedAsSpectator = true; + + if ( !self [[level.gametypeSpawnWaiter]]() ) + { + self.waitingToSpawn = false; + + self util::clearLowerMessage(); + + self.waveSpawnIndex = undefined; + self.respawnTimerStartTime = undefined; + + return; + } + } + + waveBased = (level.waveRespawnDelay > 0); + if ( !level.playerForceRespawn && self.hasSpawned && !waveBased && !self.wantSafeSpawn && !level.playerQueuedRespawn ) + { + util::setLowerMessage( game["strings"]["press_to_spawn"] ); + + if ( !spawnedAsSpectator ) + self thread respawn_asSpectator( self.origin + (0, 0, 60), self.angles ); + spawnedAsSpectator = true; + + self waitRespawnOrSafeSpawnButton(); + } + + self.waitingToSpawn = false; + + self util::clearLowerMessage(); + + self.waveSpawnIndex = undefined; + self.respawnTimerStartTime = undefined; + + if( isdefined( level.playerIncrementalRespawnDelay ) ) + { + if( isdefined( self.pers["spawns"] ) ) + self.pers["spawns"]++; + else + self.pers["spawns"] = 1; + } + + self thread [[level.spawnPlayer]](); +} + +function waitRespawnOrSafeSpawnButton() +{ + self endon("disconnect"); + self endon("end_respawn"); + + while (1) + { + if ( self useButtonPressed() ) + break; + + wait .05; + } +} + +function waitInSpawnQueue( ) +{ + self endon("disconnect"); + self endon("end_respawn"); + + if ( !level.inGracePeriod && !level.useStartSpawns ) + { + currentorigin = self.origin; + currentangles = self.angles; + + self thread [[level.spawnSpectator]]( currentorigin + (0, 0, 60), currentangles ); + + self waittill("queue_respawn" ); + } +} + +function setThirdPerson( value ) +{ + if( !level.console ) + return; + + if ( !isdefined( self.spectatingThirdPerson ) || value != self.spectatingThirdPerson ) + { + self.spectatingThirdPerson = value; + if ( value ) + { + self SetClientThirdPerson( 1 ); + self setDepthOfField( 0, 128, 512, 4000, 6, 1.8 ); + } + else + { + self SetClientThirdPerson( 0 ); + self setDepthOfField( 0, 0, 512, 4000, 4, 0 ); + } + self resetFov(); + } +} + + +function setSpawnVariables() +{ + resetTimeout(); + + // Stop shellshock and rumble + self StopShellshock(); + self StopRumble( "damage_heavy" ); +} diff --git a/mp/gametypes/_globallogic_ui.gsc b/mp/gametypes/_globallogic_ui.gsc new file mode 100644 index 0000000..16a93d6 --- /dev/null +++ b/mp/gametypes/_globallogic_ui.gsc @@ -0,0 +1,701 @@ +#using scripts\shared\array_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_player; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_spectating; + +#using scripts\mp\_util; +#using scripts\mp\bots\_bot; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\teams\_teams; + +#precache( "string", "MP_HALFTIME" ); +#precache( "string", "MP_OVERTIME" ); +#precache( "string", "MP_ROUNDEND" ); +#precache( "string", "MP_INTERMISSION" ); +#precache( "string", "MP_SWITCHING_SIDES_CAPS" ); +#precache( "string", "MP_FRIENDLY_FIRE_WILL_NOT" ); +#precache( "string", "MP_RAMPAGE" ); +#precache( "string", "MP_ENDED_GAME" ); +#precache( "string", "MP_HOST_ENDED_GAME" ); + +#precache( "eventstring", "medal_received" ); +#precache( "eventstring", "killstreak_received" ); +#precache( "eventstring", "player_callout" ); +#precache( "eventstring", "score_event" ); +#precache( "eventstring", "rank_up" ); +#precache( "eventstring", "gun_level_complete" ); +#precache( "eventstring", "challenge_complete" ); +#precache( "eventstring", "clear_notification_queue" ); + +#namespace globallogic_ui; + +function init() +{ +} + +function SetupCallbacks() +{ + level.autoassign =&menuAutoAssign; + level.spectator =&menuSpectator; + level.curClass =&menuClass; + level.teamMenu =&menuTeam; +} + +function freeGameplayHudElems() +{ + // free up some hud elems so we have enough for other things. + + // perk icons + if ( isdefined( self.perkicon ) ) + { + for ( numSpecialties = 0; numSpecialties < level.maxSpecialties; numSpecialties++ ) + { + if ( isdefined( self.perkicon[ numSpecialties ] ) ) + { + self.perkicon[ numSpecialties ] hud::destroyElem(); + self.perkname[ numSpecialties ] hud::destroyElem(); + } + } + } + + if ( isdefined( self.perkHudelem ) ) + { + self.perkHudelem hud::destroyElem(); + } + + // Killstreak icons + if ( isdefined( self.killstreakicon ) ) + { + if ( isdefined( self.killstreakicon[0] ) ) + { + self.killstreakicon[0] hud::destroyElem(); + } + if ( isdefined( self.killstreakicon[1] ) ) + { + self.killstreakicon[1] hud::destroyElem(); + } + if ( isdefined( self.killstreakicon[2] ) ) + { + self.killstreakicon[2] hud::destroyElem(); + } + if ( isdefined( self.killstreakicon[3] ) ) + { + self.killstreakicon[3] hud::destroyElem(); + } + if ( isdefined( self.killstreakicon[4] ) ) + { + self.killstreakicon[4] hud::destroyElem(); + } + } + + // lower message + if ( isdefined( self.lowerMessage ) ) + self.lowerMessage hud::destroyElem(); + if ( isdefined( self.lowerTimer ) ) + self.lowerTimer hud::destroyElem(); + + // progress bar + if ( isdefined( self.proxBar ) ) + self.proxBar hud::destroyElem(); + if ( isdefined( self.proxBarText ) ) + self.proxBarText hud::destroyElem(); + + // carry icon + if ( isdefined( self.carryIcon ) ) + self.carryIcon hud::destroyElem(); +} + +function teamPlayerCountsEqual( playerCounts ) +{ + count = undefined; + + foreach( team in level.teams ) + { + if ( !isdefined( count ) ) + { + count = playerCounts[team]; + continue; + } + if ( count != playerCounts[team] ) + return false; + } + + return true; +} + +function teamWithLowestPlayerCount( playerCounts, ignore_team ) +{ + count = 9999; + lowest_team = undefined; + + foreach( team in level.teams ) + { + if ( count > playerCounts[team] ) + { + count = playerCounts[team]; + lowest_team = team; + } + } + + return lowest_team; +} + +function shouldBeSpectatorLateJoin( teamName, comingFromMenu ) +{ + if ( level.rankedMatch ) + return false; + + if (teamName != "free") + return false; + + if ( comingFromMenu ) + return false; + + if ( self IsHost() ) + return false; + + if ( level.forceAutoAssign) + return false; + + if ( self util::is_bot() ) + return false; + + if ( self IsSplitscreen() ) + return false; + + return true; +} + +function menuAutoAssign( comingFromMenu ) +{ + teamKeys = GetArrayKeys( level.teams ); + assignment = teamKeys[randomInt(teamKeys.size)]; + + self closeMenus(); + + self LUINotifyEvent( &"clear_notification_queue" ); + + if ( level.teamBased ) + { + if ( bot::is_bot_comp_stomp() ) + { + host = util::getHostPlayerForBots(); + assert( isdefined( host ) ); + + if ( !isdefined( host.team ) || host.team == "spectator" ) + { + host.team = array::random( teamKeys ); + } + + if ( !self util::is_bot() ) + { + assignment = host.team; + } + else + { + assignment = util::getOtherTeam( host.team ); + } + } + else if ( isdefined( level.getAutoAssignTeamName ) ) + { + assignment = [[level.getAutoAssignTeamName]]( self, comingFromMenu ); + } + else + { + //get the team from the lobby system or party system + teamName = GetAssignedTeamName( self ); + + if ( isdefined( teamName ) && teamName != "free" && !comingFromMenu ) + { + assignment = teamName; + } + // if we are in a custom game and the game has already started do not automatically put the player on a team + // make them choose their team + else if ( shouldBeSpectatorLateJoin( teamName, comingFromMenu ) ) + { + assignment = "spectator"; + } + else + { + //lobby didn't have a proper team. Have the server decide. + playerCounts = self teams::count_players(); + + // if teams are equal return the team with the lowest score + if ( teamPlayerCountsEqual( playerCounts ) ) + { + // try to keep online splitscreen players together + if ( !level.splitscreen && self IsSplitScreen() ) + { + assignment = self get_splitscreen_team(); + + if ( assignment == "" ) + assignment = pickTeamFromScores(teamKeys); + } + else + { + assignment = pickTeamFromScores(teamKeys); + } + } + else + { + assignment = teamWithLowestPlayerCount( playerCounts, "none" ); + } + } + } + + if ( assignment == self.pers["team"] && (self.sessionstate == "playing" || self.sessionstate == "dead") ) + { + self beginClassChoice(); + return; + } + } + else + { + // in ffa the team is assigned in code from the players client number + if ( !comingFromMenu ) + { + assignment = self.sessionteam; + } + else + { + clientNum = self getEntityNumber(); + count = 0; + foreach ( team in level.teams ) + { + if ( team == "free" ) + { + continue; + } + + count++; + if ( count == clientNum + 1 ) + { + assignment = team; + break; + } + } + } + + // dont do anything. + if (self.sessionstate == "playing" || self.sessionstate == "dead") + { + return; + } + } + + if ( assignment == "spectator" && (!level.forceAutoAssign) ) + { + self.pers["team"] = assignment; + self.team = assignment; + self.sessionteam = assignment; + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + return; + } + + if ( assignment != self.pers["team"] && (self.sessionstate == "playing" || self.sessionstate == "dead") ) + { + self.switching_teams = true; + self.switchedTeamsResetGadgets = true; + self.joining_team = assignment; + self.leaving_team = self.pers["team"]; + self suicide(); + } + + self.pers["team"] = assignment; + self.team = assignment; + self.pers["class"] = undefined; + self.curClass = undefined; + self.pers["weapon"] = undefined; + self.pers["savedmodel"] = undefined; + + self updateObjectiveText(); + + self.sessionteam = assignment; + + if ( !isAlive( self ) ) + self.statusicon = "hud_status_dead"; + + self notify("joined_team"); + level notify( "joined_team" ); + callback::callback( #"on_joined_team" ); + self notify("end_respawn"); + + self beginClassChoice(); + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + +} + +function teamScoresEqual( ) +{ + score = undefined; + + foreach( team in level.teams ) + { + if ( !isdefined( score ) ) + { + score = getTeamScore(team); + continue; + } + if ( score != getTeamScore(team) ) + return false; + } + + return true; +} + +function teamWithLowestScore() +{ + score = 99999999; + lowest_team = undefined; + + foreach( team in level.teams ) + { + if ( score > getTeamScore(team ) ) + lowest_team = team; + } + + return lowest_team; +} + +function pickTeamFromScores(teams) +{ + assignment = "allies"; + + if ( teamScoresEqual() ) + assignment = teams[randomInt(teams.size)]; + else + assignment = teamWithLowestScore(); + + return assignment; +} + +function get_splitscreen_team() +{ + for ( index = 0; index < level.players.size; index++ ) + { + if ( !isdefined(level.players[index]) ) + continue; + + if ( level.players[index] == self ) + continue; + + if ( !(self IsPlayerOnSameMachine( level.players[index] )) ) + continue; + + team = level.players[index].sessionteam; + + // going to assume first non-spectator + if ( team != "spectator" ) + return team; + } + + return ""; +} + +function updateObjectiveText() +{ + if ( (self.pers["team"] == "spectator") ) + { + self SetClientCGObjectiveText( "" ); + return; + } + + if( level.scorelimit > 0 || level.roundScoreLimit > 0 ) + { + self SetClientCGObjectiveText( util::getObjectiveScoreText( self.pers["team"] ) ); + } + else + { + self SetClientCGObjectiveText( util::getObjectiveText( self.pers["team"] ) ); + } +} + + +function closeMenus() +{ + self closeInGameMenu(); +} + +function beginClassChoice() +{ + assert( isdefined( level.teams[self.pers["team"]] ) ); + + team = self.pers["team"]; + + if ( level.disableClassSelection == 1 || ( isdefined( self.disableClassSelection ) && self.disableClassSelection ) || GetDvarint( "migration_soak" ) == 1 ) + { + // skip class choice and just spawn. + + started_waiting = GetTime(); + + // try to let them finish loading the level + while ( (!self isStreamerReady( -1, true )) && started_waiting + 90000 > GetTime() ) + { + {wait(.05);}; + } + + self.pers["class"] = level.defaultClass; + self.curClass = level.defaultClass; + + if ( self.sessionstate != "playing" && game["state"] == "playing" ) + self thread [[level.spawnClient]](); + + level thread globallogic::updateTeamStatus(); + self thread spectating::set_permissions_for_machine(); + + return; + } + + // menu_changeclass_team is the one where you choose one of the n classes to play as. + // menu_class_team is where you can choose to change your team, class, controls, or leave game. + + // need to wait a network frame before opening the choose-class menu, as CG_ExecuteNewServerCommands is called + // before the team is updated in CG_SetNextSnap_Clients. + util::wait_network_frame(); + + self openMenu( game[ "menu_changeclass_" + team ] ); + + //if ( level.rankedMatch ) + // self openMenu( game[ "menu_changeclass_" + team ] ); + //else + // self openMenu( game[ "menu_start_menu" ] ); +} + +function showMainMenuForTeam() +{ + assert( isdefined( level.teams[self.pers["team"]] ) ); + + team = self.pers["team"]; + + // menu_changeclass_team is the one where you choose one of the n classes to play as. + // menu_class_team is where you can choose to change your team, class, controls, or leave game. + + self openMenu( game[ "menu_changeclass_" + team ] ); +} + +function menuTeam( team ) +{ + self closeMenus(); + + if ( !level.console && level.allow_teamchange == "0" && (isdefined(self.hasDoneCombat) && self.hasDoneCombat) ) + { + return; + } + + if(self.pers["team"] != team) + { + // allow respawn when switching teams during grace period. + if ( level.inGracePeriod && (!isdefined(self.hasDoneCombat) || !self.hasDoneCombat) ) + self.hasSpawned = false; + + if(self.sessionstate == "playing") + { + self.switching_teams = true; + self.switchedTeamsResetGadgets = true; + self.joining_team = team; + self.leaving_team = self.pers["team"]; + self suicide(); + } + + self LUINotifyEvent( &"clear_notification_queue" ); + + self.pers["team"] = team; + self.team = team; + self.pers["class"] = undefined; + self.curClass = undefined; + self.pers["weapon"] = undefined; + self.pers["savedmodel"] = undefined; + + self updateObjectiveText(); + + if ( !level.rankedMatch && !level.leagueMatch ) + { + self.sessionstate = "spectator"; + } + + self.sessionteam = team; + + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + + self notify("joined_team"); + level notify( "joined_team" ); + callback::callback( #"on_joined_team" ); + self notify("end_respawn"); + } + + self beginClassChoice(); +} + +function menuSpectator() +{ + self closeMenus(); + + if(self.pers["team"] != "spectator") + { + if(isAlive(self)) + { + self.switching_teams = true; + self.switchedTeamsResetGadgets = true; + self.joining_team = "spectator"; + self.leaving_team = self.pers["team"]; + self suicide(); + } + + self.pers["team"] = "spectator"; + self.team = "spectator"; + self.pers["class"] = undefined; + self.curClass = undefined; + self.pers["weapon"] = undefined; + self.pers["savedmodel"] = undefined; + + self updateObjectiveText(); + + self.sessionteam = "spectator"; + + [[level.spawnSpectator]](); + + self thread globallogic_player::spectate_player_watcher(); + + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + + self notify("joined_spectators"); + callback::callback( #"on_joined_spectate" ); + } +} + +function menuClass( response, forcedClass ) +{ + self closeMenus(); + + // this should probably be an assert + if(!isdefined(self.pers["team"]) || !(isdefined( level.teams[self.pers["team"]] ) ) ) + return; + + if ( !isdefined( forcedClass ) ) + playerclass = self loadout::getClassChoice( response ); + else + playerClass = forcedClass; + + if( (isdefined( self.pers["class"] ) && self.pers["class"] == playerclass) ) + return; + + self.pers["changed_class"] = true; + self notify ( "changed_class" ); + + //The player selected the class they are currently using + if( isdefined(self.curClass) && self.curClass == playerclass ) + { + self.pers["changed_class"] = false; + } + + self.pers["class"] = playerClass; + self.curClass = playerclass; + + self.pers["weapon"] = undefined; + + if ( game["state"] == "postgame" ) + return; + + if ( self.sessionstate == "playing" ) + { + supplyStationClassChange = isdefined( self.usingSupplyStation ) && self.usingSupplyStation; + self.usingSupplyStation = false; + + if ( ( level.inGracePeriod && !self.hasDoneCombat ) || supplyStationClassChange ) // used weapons check? + { + self loadout::setClass( self.pers["class"] ); + self.tag_stowed_back = undefined; + self.tag_stowed_hip = undefined; + self loadout::giveLoadout( self.pers["team"], self.pers["class"] ); + self killstreaks::give_owned(); + } + else if ( !( self IsSplitScreen() ) && !util::isInfectedGametype() ) + { + self IPrintLnBold( game["strings"]["change_class"] ); + } + } + else + { + if ( self.sessionstate != "spectator" ) + { + if ( self IsInVehicle() ) + return; + + if ( self IsRemoteControlling() ) + return; + + if ( self IsWeaponViewOnlyLinked() ) + return false; + } + + if ( game["state"] == "playing" ) + { + timePassed = undefined; + + if ( isdefined( self.respawnTimerStartTime ) ) + { + timePassed = (gettime() - self.respawnTimerStartTime) / 1000; + } + + self thread [[level.spawnClient]](timePassed); + + self.respawnTimerStartTime = undefined; + + } + } + + level thread globallogic::updateTeamStatus(); + + self thread spectating::set_permissions_for_machine(); +} + + +/* +function showSafeSpawnMessage() +{ + if ( level.splitscreen ) + return; + + // don't show it if they've already asked for a safe spawn + if ( self.wantSafeSpawn ) + return; + + if ( !isdefined( self.safeSpawnMsg ) ) + { + self.safeSpawnMsg = hud::createFontString( "default", 1.4 ); + self.safeSpawnMsg hud::setPoint( "CENTER", level.lowerTextYAlign, 0, level.lowerTextY + 50 ); + self.safeSpawnMsg setText( &"PLATFORM_PRESS_TO_SAFESPAWN" ); + self.safeSpawnMsg.archived = false; + } + self.safeSpawnMsg.alpha = 1; +} +function hideSafeSpawnMessage() +{ + if ( !isdefined( self.safeSpawnMsg ) ) + return; + + self.safeSpawnMsg.alpha = 0; +} +*/ + + +function removeSpawnMessageShortly( delay ) +{ + self endon("disconnect"); + + waittillframeend; // so we don't endon the end_respawn from spawning as a spectator + + self endon("end_respawn"); + + wait delay; + + self util::clearLowerMessage( 2.0 ); +} + diff --git a/mp/gametypes/_globallogic_utils.gsc b/mp/gametypes/_globallogic_utils.gsc new file mode 100644 index 0000000..5825f0e --- /dev/null +++ b/mp/gametypes/_globallogic_utils.gsc @@ -0,0 +1,566 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_message_shared; +#using scripts\shared\killstreaks_shared; + + + +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_hud_message; + +#using scripts\mp\killstreaks\_killstreaks; + +#namespace globallogic_utils; + +function testMenu() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + for ( ;; ) + { + wait ( 10.0 ); + + notifyData = spawnStruct(); + notifyData.titleText = &"MP_CHALLENGE_COMPLETED"; + notifyData.notifyText = "wheee"; + notifyData.sound = "mp_challenge_complete"; + + self thread hud_message::notifyMessage( notifyData ); + } +} + +function testShock() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + for ( ;; ) + { + wait ( 3.0 ); + + numShots = randomInt( 6 ); + + for ( i = 0; i < numShots; i++ ) + { + iPrintLnBold( numShots ); + self shellShock( "frag_grenade_mp", 0.2 ); + wait ( 0.1 ); + } + } +} + +function testHPs() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + hps = []; + hps[hps.size] = "radar"; + hps[hps.size] = "artillery"; + hps[hps.size] = "dogs"; + + for ( ;; ) + { +// hp = hps[randomInt(hps.size)]; + hp = "radar"; + if ( self thread killstreaks::give( hp ) ) + { + self playLocalSound( level.killstreaks[hp].informDialog ); + } + +// self thread killstreaks::upgradeHardpointItem(); + + wait ( 20.0 ); + } +} + + +// returns the best guess of the exact time until the scoreboard will be displayed and player control will be lost. +// returns undefined if time is not known +function timeUntilRoundEnd() +{ + if ( level.gameEnded ) + { + timePassed = (getTime() - level.gameEndTime) / 1000; + timeRemaining = level.postRoundTime - timePassed; + + if ( timeRemaining < 0 ) + return 0; + + return timeRemaining; + } + + if ( level.inOvertime ) + return undefined; + + if ( level.timeLimit <= 0 ) + return undefined; + + if ( !isdefined( level.startTime ) ) + return undefined; + + timePassed = (getTimePassed() - level.startTime)/1000; + timeRemaining = (level.timeLimit * 60) - timePassed; + + return timeRemaining + level.postRoundTime; +} + + +function getTimeRemaining() +{ + return level.timeLimit * 60 * 1000 - getTimePassed(); +} + +function registerPostRoundEvent( eventFunc ) +{ + if ( !isdefined( level.postRoundEvents ) ) + level.postRoundEvents = []; + + level.postRoundEvents[level.postRoundEvents.size] = eventFunc; +} + +function executePostRoundEvents() +{ + if ( !isdefined( level.postRoundEvents ) ) + return; + + for ( i = 0 ; i < level.postRoundEvents.size ; i++ ) + { + [[level.postRoundEvents[i]]](); + } +} + +function getValueInRange( value, minValue, maxValue ) +{ + if ( value > maxValue ) + return maxValue; + else if ( value < minValue ) + return minValue; + else + return value; +} + + +/# +function assertProperPlacement() +{ + numPlayers = level.placement["all"].size; + if ( level.teamBased ) + { + for ( i = 0; i < numPlayers - 1; i++ ) + { + if ( level.placement["all"][i].score < level.placement["all"][i + 1].score ) + { + println("^1Placement array:"); + for ( i = 0; i < numPlayers; i++ ) + { + player = level.placement["all"][i]; + println("^1" + i + ". " + player.name + ": " + player.score ); + } + assertmsg( "Placement array was not properly sorted" ); + break; + } + } + } + else + { + for ( i = 0; i < numPlayers - 1; i++ ) + { + if ( level.placement["all"][i].pointstowin < level.placement["all"][i + 1].pointstowin ) + { + println("^1Placement array:"); + for ( i = 0; i < numPlayers; i++ ) + { + player = level.placement["all"][i]; + println("^1" + i + ". " + player.name + ": " + player.pointstowin ); + } + assertmsg( "Placement array was not properly sorted" ); + break; + } + } + } +} +#/ + + +function isValidClass( c ) +{ + if ( SessionModeIsZombiesGame() ) + { + assert( !isdefined( c ) ); + return true; + } + return isdefined( c ) && c != ""; +} + + +function playTickingSound( gametype_tick_sound ) +{ + self endon("death"); + self endon("stop_ticking"); + level endon("game_ended"); + + time = level.bombTimer; + + while(1) + { + self playSound( gametype_tick_sound ); + + if ( time > 10 ) + { + time -= 1; + wait 1; + } + else if ( time > 4 ) + { + time -= .5; + wait .5; + } + else if ( time > 1 ) + { + time -= .4; + wait .4; + } + else + { + time -= .3; + wait .3; + } + hostmigration::waitTillHostMigrationDone(); + } +} + +function stopTickingSound() +{ + self notify("stop_ticking"); +} + +function gameTimer() +{ + level endon ( "game_ended" ); + + level waittill("prematch_over"); + // moving music to post spawn to allow for intro stingers + //music::setmusicstate( "UNDERSCORE" ); + + level.startTime = getTime(); + level.discardTime = 0; + + if ( isdefined( game["roundMillisecondsAlreadyPassed"] ) ) + { + level.startTime -= game["roundMillisecondsAlreadyPassed"]; + game["roundMillisecondsAlreadyPassed"] = undefined; + } + + prevtime = gettime() - 1000; // need to subtract a second the first time prevtime is set + + while ( game["state"] == "playing" ) + { + if ( !level.timerStopped ) + { + // the wait isn't always exactly 1 second. dunno why. + game["timepassed"] += gettime() - prevtime; + } + if ( !level.playableTimerStopped ) + { + game["playabletimepassed"] += gettime() - prevtime; + } + prevtime = gettime(); + wait ( 1.0 ); + } +} + +function DisablePlayerRoundStartDelay() +{ + player = self; + + player endon( "death" ); + player endon( "disconnect" ); + + if( GetRoundStartDelay() ) + { + wait( GetRoundStartDelay() ); + } + + player DisableRoundStartDelay(); +} + +function GetRoundStartDelay() +{ + waitTime = level.roundStartExplosiveDelay - ([[level.getTimePassed]]() / 1000); + if( waitTime > 0 ) + { + return waitTime; + } + + return 0; +} + +function ApplyRoundStartDelay() +{ + self endon("disconnect"); + self endon("joined_spectators"); + self endon("death"); + + if( isDefined( level.prematch_over ) && level.prematch_over ) + { + {wait(.05);}; // Delay one frame because the ps->weapFlags are geting cleared on spawn + } + else + { + level waittill("prematch_over"); + } + + self EnableRoundStartDelay(); + self thread globallogic_utils::DisablePlayerRoundStartDelay(); +} + +function getTimePassed() +{ + if ( !isdefined( level.startTime ) ) + return 0; + + if ( level.timerStopped ) + return (level.timerPauseTime - level.startTime) - level.discardTime; + else + return (gettime() - level.startTime) - level.discardTime; + +} + + +function pauseTimer( pausePlayableTimer = false ) +{ + level.playableTimerStopped = pausePlayableTimer; + + if ( level.timerStopped ) + return; + + level.timerStopped = true; + level.timerPauseTime = gettime(); +} + + +function resumeTimer() +{ + if ( !level.timerStopped ) + return; + + level.timerStopped = false; + level.playableTimerStopped = false; + level.discardTime += gettime() - level.timerPauseTime; +} + +function resumeTimerDiscardOverride( discardTime ) +{ + if ( !level.timerStopped ) + return; + + level.timerStopped = false; + level.discardTime = discardTime; +} + +function getScoreRemaining( team ) +{ + assert( IsPlayer( self ) || isdefined( team ) ); + + scoreLimit = level.scoreLimit; + + if ( IsPlayer( self ) ) + return scoreLimit - globallogic_score::_getPlayerScore( self ); + else + return scoreLimit - GetTeamScore( team ); +} + +function GetTeamScoreForRound( team ) +{ + if ( level.cumulativeRoundScores && isdefined( game["lastroundscore"][team] ) ) + { + return GetTeamScore( team ) - game["lastroundscore"][team]; + } + + return GetTeamScore( team ); +} + +function getScorePerMinute( team ) +{ + assert( IsPlayer( self ) || isdefined( team ) ); + + minutesPassed = ( getTimePassed() / ( 60 * 1000 ) ) + 0.0001; + + if ( IsPlayer( self ) ) + return globallogic_score::_getPlayerScore( self ) / minutesPassed; + else + return GetTeamScoreForRound( team ) / minutesPassed; +} + + +function getEstimatedTimeUntilScoreLimit( team ) +{ + assert( IsPlayer( self ) || isdefined( team ) ); + + scorePerMinute = self getScorePerMinute( team ); + scoreRemaining = self getScoreRemaining( team ); + + if ( !scorePerMinute ) + return 999999; + + return scoreRemaining / scorePerMinute; +} + + +function rumbler() +{ + self endon("disconnect"); + while(1) + { + wait(0.1); + self PlayRumbleOnEntity( "damage_heavy" ); + } +} + + +function waitForTimeOrNotify( time, notifyname ) +{ + self endon( notifyname ); + wait time; +} + + +function waitForTimeOrNotifyNoArtillery( time, notifyname ) +{ + self endon( notifyname ); + wait time; + while( isdefined( level.artilleryInProgress ) ) + { + assert( level.artilleryInProgress ); // undefined or true + wait .25; + } +} + + +function isHeadShot( weapon, sHitLoc, sMeansOfDeath, eInflictor ) +{ + if( sHitLoc != "head" && sHitLoc != "helmet" ) + { + return false; + } + + switch( sMeansOfDeath ) + { + case "MOD_MELEE": + case "MOD_MELEE_ASSASSINATE": + return false; + case "MOD_IMPACT": + if( weapon.rootWeapon != level.weaponBallisticKnife ) + return false; + } + + if ( killstreaks::is_killstreak_weapon( weapon ) ) + { + if ( !isdefined( eInflictor ) || !isdefined( eInflictor.controlled ) || eInflictor.controlled == false ) + { + return false; + } + } + + return true; +} + + +function getHitLocHeight( sHitLoc ) +{ + switch( sHitLoc ) + { + case "helmet": + case "head": + case "neck": + return 60; + case "torso_upper": + case "right_arm_upper": + case "left_arm_upper": + case "right_arm_lower": + case "left_arm_lower": + case "right_hand": + case "left_hand": + case "gun": + return 48; + case "torso_lower": + return 40; + case "right_leg_upper": + case "left_leg_upper": + return 32; + case "right_leg_lower": + case "left_leg_lower": + return 10; + case "right_foot": + case "left_foot": + return 5; + } + return 48; +} + +function debugLine( start, end ) +{ + /# + for ( i = 0; i < 50; i++ ) + { + line( start, end ); + wait .05; + } + #/ +} + + +function isExcluded( entity, entityList ) +{ + for ( index = 0; index < entityList.size; index++ ) + { + if ( entity == entityList[index] ) + return true; + } + return false; +} + + +function waitForTimeOrNotifies( desiredDelay ) +{ + startedWaiting = getTime(); + +// while( self.doingNotify ) +// WAIT_SERVER_FRAME; + + waitedTime = (getTime() - startedWaiting)/1000; + + if ( waitedTime < desiredDelay ) + { + wait desiredDelay - waitedTime; + return desiredDelay; + } + else + { + return waitedTime; + } +} + +function logTeamWinString( wintype, winner ) +{ + /# + log_string = wintype; + + if( isdefined( winner ) ) + { + log_string = log_string + ", win: " + winner; + } + + foreach ( team in level.teams ) + { + log_string = log_string + ", " + team + ": " + game["teamScores"][team]; + } + + print( log_string ); + #/ +} + diff --git a/mp/gametypes/_globallogic_vehicle.gsc b/mp/gametypes/_globallogic_vehicle.gsc new file mode 100644 index 0000000..5a93262 --- /dev/null +++ b/mp/gametypes/_globallogic_vehicle.gsc @@ -0,0 +1,422 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\spawner_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\weapons\_weapons; + + + +#using scripts\mp\gametypes\_globallogic_player; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreaks; + +#using scripts\mp\_vehicle; + +#namespace globallogic_vehicle; + +function Callback_VehicleSpawned( spawner ) +{ + self.health = self.healthdefault; + + if ( IsSentient( self ) ) + { + self spawner::spawn_think( spawner ); + } + else + { + vehicle::init( self ); + } + + if ( isdefined( level.vehicle_main_callback ) ) + { + if( isdefined( level.vehicle_main_callback[ self.vehicleType ] ) ) + { + self thread [[level.vehicle_main_callback[ self.vehicleType ]]](); + } + else if( isdefined( self.scriptVehicleType ) && isdefined( level.vehicle_main_callback[ self.scriptVehicleType ] ) ) + { + self thread [[level.vehicle_main_callback[ self.scriptVehicleType ]]](); + } + } +} + +function Callback_VehicleDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) +{ + selfEntNum = self getEntityNumber(); // need to cache off as "self" will become a removed entity after finishVehicleDamage executes. + eAttackerNotSelf = ( isdefined( eAttacker ) && ( eAttacker != self ) ); + eAttackerIsNotOwner = ( isdefined( eAttacker ) && isdefined( self.owner ) && ( eAttacker != self.owner ) ); + tryToDoDamageFeedback = !isdefined( self.noDamageFeedback ) || !self.noDamageFeedback; + + // already applied in the Callback_VehicleDamage + if ( !(1 & iDFlags) ) + { + // create a class specialty checks; CAC:bulletdamage, CAC:armorvest + iDamage = loadout::cac_modified_vehicle_damage( self, eAttacker, iDamage, sMeansOfDeath, weapon, eInflictor ); + } + + self.iDFlags = iDFlags; + self.iDFlagsTime = getTime(); + + if ( game["state"] == "postgame" ) + { + // at the end of a game, all vehicles should explode without any game logic firing off + self finishVehicleDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, damageFromUnderneath, modelIndex, partName, false); + return; + } + + if ( isdefined( eAttacker ) && isPlayer( eAttacker ) && isdefined( eAttacker.canDoCombat ) && !eAttacker.canDoCombat ) + return; + + if ( self weapons::should_suppress_damage( weapon, eInflictor ) ) + return; + + if ( isdefined( self.overrideVehicleDamage ) ) + { + iDamage = self [[self.overrideVehicleDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ); + } + else if ( isdefined( level.overrideVehicleDamage ) ) + { + iDamage = self [[level.overrideVehicleDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ); + } + + assert(isdefined(iDamage), "You must return a value from a damage override function."); + + if( iDamage == 0 ) + return; + + // Don't do knockback if the damage direction was not specified + if( !isdefined( vDir ) ) + iDFlags |= 4; + + if ( ( isdefined( self.maxhealth ) && (self.health == self.maxhealth)) || !isdefined( self.attackers ) ) + { + self.attackers = []; + self.attackerData = []; + self.attackerDamage = []; + } + + // explosive barrel/car detection + if ( weapon == level.weaponNone && isdefined( eInflictor ) ) + { + if ( isdefined( eInflictor.targetname ) && eInflictor.targetname == "explodable_barrel" ) + weapon = GetWeapon( "explodable_barrel" ); + else if ( isdefined( eInflictor.destructible_type ) && isSubStr( eInflictor.destructible_type, "vehicle_" ) ) + weapon = GetWeapon( "destructible_car" ); + } + + + // check for completely getting out of the damage + if( !(iDFlags & 2048) ) + { + if ( self IsVehicleImmuneToDamage( iDFlags, sMeansOfDeath, weapon ) ) + { + return; + } + + // This handles direct damage only. Splash is done in VehicleRadiusDamage + if ( sMeansOfDeath == "MOD_PROJECTILE" || sMeansOfDeath == "MOD_GRENADE" ) + { + iDamage *= weapon.vehicleProjectileDamageScalar; + iDamage = int(iDamage); + + if ( iDamage == 0 ) + { + return; + } + } + // Except for splash that we want to modify additionally based on "underneath" + else if ( sMeansOfDeath == "MOD_GRENADE_SPLASH" ) + { + iDamage *= GetVehicleUnderneathSplashScalar( weapon ); + iDamage = int(iDamage); + + if ( iDamage == 0 ) + { + return; + } + } + + iDamage *= level.vehicleDamageScalar; + iDamage = int(iDamage); + + if ( isPlayer( eAttacker ) ) + eAttacker.pers["participation"]++; + + if( !IsDefined( self.maxhealth ) ) + { + self.maxhealth = self.healthdefault; // healthdefault if from the GDT + } + + prevHealthRatio = self.health / self.maxhealth; + + //add some damage indicators + //driver = self GetSeatOccupant( 0 ); + //if( IsPlayer( driver ) && driver IsRemoteControlling() ) + //{ + // damagePct = iDamage / self.maxhealth; + // damagePct = int( max( damagePct, 3 ) ); + // driver AddToDamageIndicator( damagePct, vDir ); + //} + + if ( isdefined( self.owner ) && IsPlayer(self.owner) ) + { + team = self.owner.pers["team"]; + } + else + { + team = self vehicle::vehicle_get_occupant_team(); + } + + if ( level.teamBased && isPlayer( eAttacker ) && (team == eAttacker.pers["team"]) ) + { + // do not use level.friendlyfire per design as it allows for griefing + // we still need to ask though so that killstreaks can self destruct as needed + if( !AllowFriendlyFireDamage( eInflictor, eAttacker, sMeansOfDeath, weapon ) ) + return; + + self.lastDamageWasFromEnemy = false; + self finishVehicleDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, damageFromUnderneath, modelIndex, partName, false); + } + else if( !level.hardcoreMode && isdefined( self.owner ) && isdefined( eAttacker ) && isdefined( eAttacker.owner ) && ( self.owner == eAttacker.owner ) && ( self != eAttacker ) ) + { + //don't allow your killstreaks to kill your other killstreaks + return; + } + else + { + if ( !level.teamBased && isdefined( self.archetype ) && self.archetype == "raps" ) + { + // allow raps to be damaged by owners in FFA games + } + else if ( !level.teamBased && isdefined( self.targetname ) && self.targetname == "rcbomb" ) + { + // allow the rc bomb to be damaged by owners in FFA games (DT 75676) + } + else if ( isdefined( self.owner ) && isdefined( eAttacker ) && self.owner == eAttacker ) + { + return; + } + + // Make sure at least one point of damage is done + if(iDamage < 1) + iDamage = 1; + + if ( issubstr( sMeansOfDeath, "MOD_GRENADE" ) && isdefined( eInflictor ) && isdefined( eInflictor.isCooked ) ) + self.wasCooked = getTime(); + else + self.wasCooked = undefined; + + attacker_seat = undefined; + if ( isdefined( eAttacker ) ) + attacker_seat = self GetOccupantSeat( eAttacker ); + + self.lastDamageWasFromEnemy = (isdefined( eAttacker ) && !isdefined(attacker_seat)); + + self globallogic_player::giveAttackerAndInflictorOwnerAssist( eAttacker, eInflictor, iDamage, sMeansOfDeath, weapon ); + + self finishVehicleDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, damageFromUnderneath, modelIndex, partName, false); + + // IMPORTANT NOTE: "self" is a removed entity from here on out!!! + + if ( level.gameType == "hack" && !weapon.isEmp ) + { + iDamage = 0; + } + } + + if ( eAttackerNotSelf && ( eAttackerIsNotOwner || ( isdefined( self.selfDestruct ) && !self.selfDestruct ) || ( self.forceDamageFeedback === true ) ) ) + { + if ( tryToDoDamageFeedback ) + { + dofeedback = true; + if( isdefined( self.damageTaken ) && isdefined( self.maxhealth ) && self.damageTaken > self.maxhealth ) + dofeedback = false; + + if( ( isdefined( self.shuttingDown ) && self.shuttingDown ) ) + dofeedback = false; + + if ( dofeedback && damagefeedback::doDamageFeedback( weapon, eInflictor ) ) + { + if ( iDamage > 0 ) + eAttacker thread damagefeedback::update( sMeansOfDeath, eInflictor ); + } + } + } + } + + /# + // Do debug print if it's enabled + if(GetDvarint( "g_debugDamage")) + println("actor:" + self getEntityNumber() + " health:" + self.health + " attacker:" + eAttacker.clientid + " inflictor is player:" + isPlayer(eInflictor) + " damage:" + iDamage + " hitLoc:" + sHitLoc); + #/ + if(1) // self.sessionstate != "dead") + { + lpselfnum = selfEntNum; + lpselfteam = ""; + lpattackerteam = ""; + + if(isPlayer(eAttacker)) + { + lpattacknum = eAttacker getEntityNumber(); + lpattackGuid = eAttacker getGuid(); + lpattackname = eAttacker.name; + lpattackerteam = eAttacker.pers["team"]; + } + else + { + lpattacknum = -1; + lpattackGuid = ""; + lpattackname = ""; + lpattackerteam = "world"; + } + + /#logPrint("VD;" + lpselfnum + ";" + lpselfteam + ";" + lpattackGuid + ";" + lpattacknum + ";" + lpattackerteam + ";" + lpattackname + ";" + weapon.name + ";" + iDamage + ";" + sMeansOfDeath + ";" + sHitLoc + "\n");#/ + } + +} + +function Callback_VehicleRadiusDamage( eInflictor, eAttacker, iDamage, fInnerDamage, fOuterDamage, iDFlags, sMeansOfDeath, weapon, vPoint, fRadius, fConeAngleCos, vConeDir, psOffsetTime ) +{ + // create a class specialty checks; CAC:bulletdamage, CAC:armorvest + iDamage = loadout::cac_modified_vehicle_damage( self, eAttacker, iDamage, sMeansOfDeath, weapon, eInflictor ); + fInnerDamage = loadout::cac_modified_vehicle_damage( self, eAttacker, fInnerDamage, sMeansOfDeath, weapon, eInflictor ); + fOuterDamage = loadout::cac_modified_vehicle_damage( self, eAttacker, fOuterDamage, sMeansOfDeath, weapon, eInflictor ); + self.iDFlags = iDFlags; + self.iDFlagsTime = getTime(); + + if ( game["state"] == "postgame" ) + return; + + if ( isdefined( eAttacker ) && isPlayer( eAttacker ) && isdefined( eAttacker.canDoCombat ) && !eAttacker.canDoCombat ) + return; + + if ( isdefined( self.killstreakType ) ) + { + maxhealth = (isdefined(self.maxhealth)?self.maxhealth:self.health); + if(!isdefined(maxhealth))maxhealth=200; + iDamage = self killstreaks::OnDamagePerWeapon( self.killstreakType, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, maxhealth, undefined, maxhealth * 0.4, undefined, 0, undefined, true, 1.0 ); + } + + // check for completely getting out of the damage + if( !(iDFlags & 2048) ) + { + if ( self IsVehicleImmuneToDamage( iDFlags, sMeansOfDeath, weapon ) ) + { + return; + } + + // THIS HANDLES SPLASH DAMAGE ONLY. SPLASH IS DONE IN VehicleRadiusDamage + if ( sMeansOfDeath == "MOD_PROJECTILE_SPLASH" || sMeansOfDeath == "MOD_GRENADE_SPLASH" || sMeansOfDeath == "MOD_EXPLOSIVE" ) + { + scalar = weapon.vehicleProjectileSplashDamageScalar; + iDamage = int(iDamage * scalar); + fInnerDamage = (fInnerDamage * scalar); + fOuterDamage = (fOuterDamage * scalar); + + if ( fInnerDamage == 0 ) + { + return; + } + if ( iDamage < 1 ) + { + iDamage = 1; + } + } + + occupant_team = self vehicle::vehicle_get_occupant_team(); + + if ( level.teamBased && isPlayer( eAttacker ) && (occupant_team == eAttacker.pers["team"]) ) + { + // do not use level.friendlyfire per design as it allows for griefing + // we still need to ask though so that killstreaks can self destruct as needed + if( !AllowFriendlyFireDamage( eInflictor, eAttacker, sMeansOfDeath, weapon ) ) + return; + + self.lastDamageWasFromEnemy = false; + self finishVehicleRadiusDamage(eInflictor, eAttacker, iDamage, fInnerDamage, fOuterDamage, iDFlags, sMeansOfDeath, weapon, vPoint, fRadius, fConeAngleCos, vConeDir, psOffsetTime); + } + else if( !level.hardcoreMode && isdefined( self.owner ) && isdefined( eAttacker.owner ) && self.owner == eAttacker.owner )//don't allow your killstreaks to kill your other killstreaks + { + return; + } + else + { + // Make sure at least one point of damage is done + if(iDamage < 1) + iDamage = 1; + + self finishVehicleRadiusDamage(eInflictor, eAttacker, iDamage, fInnerDamage, fOuterDamage, iDFlags, sMeansOfDeath, weapon, vPoint, fRadius, fConeAngleCos, vConeDir, psOffsetTime); + } + } +} + +function Callback_VehicleKilled( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime ) +{ + if ( game["state"] == "postgame" && ( !isdefined( self.selfDestruct ) || !self.selfDestruct ) ) + { + if( isdefined( self.overrideVehicleDeathPostGame ) ) + { + self [[self.overrideVehicleDeathPostGame]]( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime ); + } + return; + } + + // do vehicle killed callback + // generally this should be used as system callback, and the callback "on_vehicle_killed" callback can be used by level script + if ( isdefined( self.overrideVehicleKilled ) ) + { + self [[self.overrideVehicleKilled]]( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime ); + } + + if( isdefined( self.overrideVehicleDeath ) ) + { + self [[self.overrideVehicleDeath]]( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime ); + } +} + +function vehicleCrush() +{ + self endon("disconnect"); + + if(isdefined( level._effect ) && isdefined( level._effect["tanksquish"] ) ) + { + PlayFX( level._effect["tanksquish"], self.origin + (0, 0, 30)); + } + + self playsound( "chr_crunch" ); +} + +// damage going through this function will have already passed through the +// GetVehicleProjectileSplashScalar so keep that in mind when adjusting values +function GetVehicleUnderneathSplashScalar( weapon ) +{ + if ( isdefined( self ) && isdefined( self.ignore_vehicle_underneath_splash_scalar ) ) + return 1.0; + + if ( weapon.name == "satchel_charge" ) + { + // canceling all splash scaling done by the other function + scale = 10.0; + + // making it really deadly + scale *= 3.0; + } + else + { + scale = 1.0; + } + + return scale; +} + +function AllowFriendlyFireDamage( eInflictor, eAttacker, sMeansOfDeath, weapon ) +{ + if (isdefined( self.allowFriendlyFireDamageOverride ) ) + { + return [[self.allowFriendlyFireDamageOverride]](eInflictor, eAttacker, sMeansOfDeath, weapon ); + } + + return false; +} \ No newline at end of file diff --git a/mp/gametypes/_healthoverlay.gsc b/mp/gametypes/_healthoverlay.gsc new file mode 100644 index 0000000..d0e1198 --- /dev/null +++ b/mp/gametypes/_healthoverlay.gsc @@ -0,0 +1,291 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic_player; + +//#precache( "material", "overlay_low_health" ); + +#namespace healthoverlay; + +function autoexec __init__sytem__() { system::register("healthoverlay",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); + + callback::on_joined_team( &end_health_regen ); + callback::on_joined_spectate( &end_health_regen ); + callback::on_spawned( &player_health_regen ); + callback::on_disconnect( &end_health_regen ); + callback::on_player_killed( &end_health_regen ); + + level.start_player_health_regen = &player_health_regen; +} + +function init() +{ + level.healthOverlayCutoff = 0.55; // getting the dvar value directly doesn't work right because it's a client dvar GetDvarfloat( "hud_healthoverlay_pulseStart"); + + regenTime = level.playerHealthRegenTime; + + level.playerHealth_RegularRegenDelay = regenTime * 1000; + + level.healthRegenDisabled = (level.playerHealth_RegularRegenDelay <= 0); +} + +function end_health_regen() +{ + self notify("end_healthregen"); +} + +function player_health_regen() +{ + self endon("end_healthregen"); + + if ( self.health <= 0 ) + { + assert( !isalive( self ) ); + return; + } + + maxhealth = self.health; + oldhealth = maxhealth; + player = self; + health_add = 0; + + regenRate = 0.1; // 0.017; + + useTrueRegen = false; + + veryHurt = false; + + player.breathingStopTime = -10000; + + thread player_breathing_sound(maxhealth * 0.35); + thread player_heartbeat_sound(maxhealth * 0.35); + + lastSoundTime_Recover = 0; + hurtTime = 0; + newHealth = 0; + + for (;;) + { + {wait(.05);}; + + if ( ( isdefined( self.healthRegenDisabled ) && self.healthRegenDisabled ) ) + { + continue; + } + + if( isdefined( player.regenRate ) ) + { + regenRate = player.regenRate; + useTrueRegen = true; + } + + if (player.health == maxhealth) + { + veryHurt = false; + if ( isdefined( self.atBrinkOfDeath ) && self.atBrinkOfDeath == true ) + { + self notify( "challenge_survived_from_death" ); + } + self.atBrinkOfDeath = false; + continue; + } + + if (player.health <= 0) + return; + + if ( isdefined(player.laststand) && player.laststand ) + continue; + + wasVeryHurt = veryHurt; + ratio = player.health / maxHealth; + if (ratio <= level.healthOverlayCutoff) + { + veryHurt = true; + self.atBrinkOfDeath = true; + self.isNearDeath = true; + if (!wasVeryHurt) + { + hurtTime = gettime(); + } + } + else + { + self.isNearDeath = false; + } + + if (player.health >= oldhealth) + { + regenTime = level.playerHealth_RegularRegenDelay; + + if ( player HasPerk( "specialty_healthregen" ) ) + { + regenTime = Int( regenTime / GetDvarfloat( "perk_healthRegenMultiplier" ) ); + } + + if (gettime() - hurttime < regenTime) + continue; + + if ( level.healthRegenDisabled ) + continue; + + if (gettime() - lastSoundTime_Recover > regenTime) + { + lastSoundTime_Recover = gettime(); + self notify ("snd_breathing_better"); + } + + if (veryHurt) + { + newHealth = ratio; + veryHurtTime = 3000; + + if ( player HasPerk( "specialty_healthregen" ) ) + { + veryHurtTime = Int( veryHurtTime / GetDvarfloat( "perk_healthRegenMultiplier" ) ); + } + + if (gettime() > hurtTime + veryHurtTime) + newHealth += regenRate; + } + else + { + if( useTrueRegen ) + { + newHealth = ratio + regenRate; + } + else + { + newHealth = 1; + } + } + + if ( newHealth >= 1.0 ) + { + self globallogic_player::resetAttackerList(); + newHealth = 1.0; + } + + if (newHealth <= 0) + { + // Player is dead + return; + } + + player setnormalhealth (newHealth); + change = player.health - oldhealth; + if ( change > 0 ) + { + player decay_player_damages( change ); + } + oldhealth = player.health; + continue; + } + + oldhealth = player.health; + + health_add = 0; + hurtTime = gettime(); + player.breathingStopTime = hurtTime + 6000; + } +} + +function decay_player_damages( decay ) +{ + if ( !isdefined( self.attackerDamage ) ) + return; + + for ( i = 0; i < self.attackerDamage.size; i++ ) + { + if ( !isdefined( self.attackerDamage[i] ) || !isdefined( self.attackerDamage[i].damage ) ) + continue; + + self.attackerDamage[i].damage -= decay; + if ( self.attackerDamage[i].damage < 0 ) + self.attackerDamage[i].damage = 0; + } +} + +function player_breathing_sound(healthcap) +{ + self endon("end_healthregen"); + + wait (2); + player = self; + for (;;) + { + wait (0.2); + if (player.health <= 0) + return; + + // no breathing sounds when using remote + if ( player util::isUsingRemote() ) + continue; + + // Player still has a lot of health so no breathing sound + if (player.health >= healthcap) + continue; + + if ( level.healthRegenDisabled && gettime() > player.breathingStopTime ) + continue; + +// changing to notify to enable voice specfic hurt sounds. +// player playLocalSound("chr_breathing_hurt"); + player notify ("snd_breathing_hurt"); + + wait .784; + wait (0.1 + randomfloat (0.8)); + } +} +function player_heartbeat_sound(healthcap) +{ + self endon("end_healthregen"); + self.hearbeatwait = .2; + wait (2); + player = self; + for (;;) + { + wait (0.2); + if (player.health <= 0) + return; + + // no heartbeat sounds when using remote + if ( player util::isUsingRemote() ) + { + continue; + } + + // Player still has a lot of health so no hearbeat sound + if (player.health >= healthcap) + { + self.hearbeatwait = .3; + continue; + } + + + if ( level.healthRegenDisabled && gettime() > player.breathingStopTime ) + { + self.hearbeatwait = .3; + continue; + } + + player playLocalSound("mpl_player_heartbeat"); + + wait (self.hearbeatwait); + //println ("snd heart wait =" + self.hearbeatwait); + + if (self.hearbeatwait <= .6) + { + self.hearbeatwait = (self.hearbeatwait + .1); + //println ("snd heart new wait =" + self.hearbeatwait); + } + } +} diff --git a/mp/gametypes/_hostmigration.gsc b/mp/gametypes/_hostmigration.gsc new file mode 100644 index 0000000..d6512c7 --- /dev/null +++ b/mp/gametypes/_hostmigration.gsc @@ -0,0 +1,80 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_shared; +#using scripts\shared\hud_util_shared; + + + +#namespace hostmigration; + +function Callback_HostMigrationSave() +{ +// /# +// hostmigration::debug_script_structs(); +// #/ +} + +function Callback_PreHostMigrationSave() +{ +// debug_script_structs(); +} + +function Callback_HostMigration() +{ +// /# +// hostmigration::debug_script_structs(); +// #/ + + setSlowMotion( 1, 1, 0 ); + //makeDvarServerInfo( "ui_guncycle", 0 ); + + level.hostMigrationReturnedPlayerCount = 0; + + if (level.inPrematchPeriod) + level waittill("prematch_over"); + + if ( level.gameEnded ) + { +/# + println( "Migration starting at time " + gettime() + ", but game has ended, so no countdown." ); +#/ + return; + } + +/# + println( "Migration starting at time " + gettime() ); +#/ + + level.hostMigrationTimer = true; + sethostmigrationstatus(true); + + level notify( "host_migration_begin" ); + + thread hostmigration::lockTimer(); + + players = level.players; + for(i = 0; i < players.size; i++) + { + player = players[i]; + player thread hostmigration::hostMigrationTimerThink(); + } + + level endon( "host_migration_begin" ); + hostMigrationWait(); + + level.hostMigrationTimer = undefined; + sethostmigrationstatus(false); +/# + println( "Migration finished at time " + gettime() ); +#/ + + // Setup match recorder for the new host + // Actually don't want to call here; this does not initialize + // the MatchRecorder, it just sets some match-starting values, like UTCStartTimeSeconds + // and calling it here will clobber the existing fields. + // + //recordMatchBegin(); + + level notify( "host_migration_end" ); +} diff --git a/mp/gametypes/_hud_message.gsc b/mp/gametypes/_hud_message.gsc new file mode 100644 index 0000000..b2aa5d5 --- /dev/null +++ b/mp/gametypes/_hud_message.gsc @@ -0,0 +1,955 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\hud_util_shared; +#using scripts\shared\music_shared; +#using scripts\shared\util_shared; + + + +#using scripts\shared\hud_message_shared; +#using scripts\shared\system_shared; + +#using scripts\mp\gametypes\_globallogic_audio; + +#using scripts\mp\_util; + +#precache( "string", "MP_FIRSTPLACE_NAME" ); +#precache( "string", "MP_SECONDPLACE_NAME" ); +#precache( "string", "MP_THIRDPLACE_NAME" ); +#precache( "string", "MP_WAGER_PLACE_NAME" ); +#precache( "string", "MP_MATCH_BONUS_IS" ); +#precache( "string", "MP_CODPOINTS_MATCH_BONUS_IS" ); +#precache( "string", "MP_WAGER_WINNINGS_ARE" ); +#precache( "string", "MP_WAGER_SIDEBET_WINNINGS_ARE" ); +#precache( "string", "MP_WAGER_IN_THE_MONEY" ); +#precache( "string", "MP_DRAW_CAPS" ); +#precache( "string", "MP_ROUND_DRAW_CAPS" ); +#precache( "string", "MP_ROUND_WIN_CAPS" ); +#precache( "string", "MP_ROUND_LOSS_CAPS" ); +#precache( "string", "MP_VICTORY_CAPS" ); +#precache( "string", "MP_DEFEAT_CAPS" ); +#precache( "string", "MP_TOP3_CAPS" ); +#precache( "string", "MP_GAME_OVER_CAPS" ); +#precache( "string", "MP_HALFTIME_CAPS" ); +#precache( "string", "MP_OVERTIME_CAPS" ); +#precache( "string", "MP_ROUNDEND_CAPS" ); +#precache( "string", "MP_INTERMISSION_CAPS" ); +#precache( "string", "MP_SWITCHING_SIDES_CAPS" ); +#precache( "string", "MP_MATCH_BONUS_IS" ); +#precache( "string", "MP_CODPOINTS_MATCH_BONUS_IS" ); +#precache( "string", "MP_WAGER_WINNINGS_ARE" ); +#precache( "string", "MP_WAGER_SIDEBET_WINNINGS_ARE" ); +#precache( "string", "MP_WAGER_IN_THE_MONEY_CAPS" ); +#precache( "string", "MP_WAGER_LOSS_CAPS" ); +#precache( "string", "MP_WAGER_TOPWINNERS" ); +#precache( "string", "MP_JOIN_IN_PROGRESS_LOSS" ); +#precache( "string", "MP_WINS" ); +#precache( "string", "MP_TEAM_ELIMINATED" ); + +#precache( "eventstring", "show_outcome" ); + +#namespace hud_message; + +function init() +{ + game["strings"]["draw"] = &"MP_DRAW_CAPS"; + game["strings"]["round_draw"] = &"MP_ROUND_DRAW_CAPS"; + game["strings"]["round_win"] = &"MP_ROUND_WIN_CAPS"; + game["strings"]["round_loss"] = &"MP_ROUND_LOSS_CAPS"; + game["strings"]["victory"] = &"MP_VICTORY_CAPS"; + game["strings"]["defeat"] = &"MP_DEFEAT_CAPS"; + game["strings"]["top3"] = &"MP_TOP3_CAPS"; + game["strings"]["game_over"] = &"MP_GAME_OVER_CAPS"; + game["strings"]["halftime"] = &"MP_HALFTIME_CAPS"; + game["strings"]["overtime"] = &"MP_OVERTIME_CAPS"; + game["strings"]["roundend"] = &"MP_ROUNDEND_CAPS"; + game["strings"]["intermission"] = &"MP_INTERMISSION_CAPS"; + game["strings"]["side_switch"] = &"MP_SWITCHING_SIDES_CAPS"; + game["strings"]["match_bonus"] = &"MP_MATCH_BONUS_IS"; + game["strings"]["codpoints_match_bonus"] = &"MP_CODPOINTS_MATCH_BONUS_IS"; + game["strings"]["wager_winnings"] = &"MP_WAGER_WINNINGS_ARE"; + game["strings"]["wager_sidebet_winnings"] = &"MP_WAGER_SIDEBET_WINNINGS_ARE"; + game["strings"]["wager_inthemoney"] = &"MP_WAGER_IN_THE_MONEY_CAPS"; + game["strings"]["wager_loss"] = &"MP_WAGER_LOSS_CAPS"; + game["strings"]["wager_topwinners"] = &"MP_WAGER_TOPWINNERS"; + game["strings"]["join_in_progress_loss"] = &"MP_JOIN_IN_PROGRESS_LOSS"; + game["strings"]["cod_caster_team_wins"] = &"MP_WINS"; + game["strings"]["cod_caster_team_eliminated"] = &"MP_TEAM_ELIMINATED"; +} + +function teamOutcomeNotify( winner, endType, endReasonText ) +{ + self endon ( "disconnect" ); + self notify ( "reset_outcome" ); + + team = self.pers["team"]; + //if ( isdefined( team ) && team == "spectator" ) + //{ + // for ( i = 0; i < level.players.size; i++ ) + // { + // if ( self.currentspectatingclient == level.players[i].clientId ) + // { + // team = level.players[i].pers["team"]; + // break; + // } + // } + //} + + if ( team != "spectator" && ( !isdefined( team ) || !isdefined( level.teams[team] ) ) ) + team = "allies"; + + // wait for notifies to finish + while ( self.doingNotify ) + {wait(.05);}; + + self endon ( "reset_outcome" ); + + outcomeText = ""; + notifyRoundEndToUI = undefined; + + overrideSpectator = false; + + if ( endType == "halftime" ) + { + outcomeText = game["strings"]["halftime"]; + notifyRoundEndToUI = true; + } + else if ( endType == "intermission" ) + { + outcomeText = game["strings"]["intermission"]; + notifyRoundEndToUI = true; + } + else if ( endType == "overtime" ) + { + outcomeText = game["strings"]["overtime"]; + notifyRoundEndToUI = true; + } + else if ( endType == "roundend" ) + { + if ( winner == "tie" ) + { + outcomeText = game["strings"]["round_draw"]; + } + else if ( isdefined( self.pers["team"] ) && winner == team ) + { + outcomeText = game["strings"]["round_win"]; + overrideSpectator = true; + } + else + { + outcomeText = game["strings"]["round_loss"]; + + if ( isdefined( level.endDefeatReasonText ) ) + { + endReasonText = level.endDefeatReasonText; + } + + overrideSpectator = true; + } + notifyRoundEndToUI = true; + } + else if ( endType == "gameend" ) + { + if ( winner == "tie" ) + { + outcomeText = game["strings"]["draw"]; + } + else if ( isdefined( self.pers["team"] ) && winner == team ) + { + outcomeText = game["strings"]["victory"]; + overrideSpectator = true; + } + else + { + outcomeText = game["strings"]["defeat"]; + + if ( isdefined( level.endDefeatReasonText ) ) + { + endReasonText = level.endDefeatReasonText; + } + + if ( (level.rankedMatch || level.leagueMatch ) && ( self.pers["lateJoin"] === true ) ) + { + endReasonText = game["strings"]["join_in_progress_loss"]; + } + + overrideSpectator = true; + + } + notifyRoundEndToUI = false; + } + + matchBonus = 0; + if ( isdefined( self.pers["totalMatchBonus"] ) ) + { + bonus = Ceil( self.pers["totalMatchBonus"] * level.xpScale ); + + if ( bonus > 0 ) + { + matchBonus = bonus; + } + } + + winnerEnum = 0; + + if ( winner == "allies" ) + { + winnerEnum = 1; + } + else if ( winner == "axis" ) + { + winnerEnum = 2; + } + + if ( IsDefined( level.gametypeRoundEndScoreHud ) && [[ level.gametypeRoundEndScoreHud ]]( winner, endType, endReasonText, outcomeText, team, winnerEnum, notifyRoundEndToUI, matchbonus ) ) + { + // gametype handled setting the score + } + else if( ( ( level.gameType == "ctf" ) || ( level.gameType == "escort" ) || ( level.gameType == "ball" ) ) && isdefined( game["overtime_round"] ) ) + { + if( game["overtime_round"] == 1 ) + { + if( isdefined( game[level.gameType + "_overtime_first_winner"]) ) + { + winner = game[level.gameType + "_overtime_first_winner"]; + //winnerEnum = 0; + //if ( winner == "allies" ) + // winnerEnum = TEAM_ALLIES; + //else if ( winner == "axis" ) + // winnerEnum = TEAM_AXIS; + } + + if( isdefined( winner ) && ( winner != "tie" ) ) + { + winningTime = game[level.gameType + "_overtime_time_to_beat"]; + } + } + else + { + if( isdefined(game[level.gameType + "_overtime_first_winner"]) && game[level.gameType + "_overtime_first_winner"] == "tie" ) + { + winningTime = game[level.gameType + "_overtime_best_time"]; + } + else + { + winningTime = undefined; + + if ( winner == "tie" && isdefined(game[level.gameType + "_overtime_first_winner"]) ) + { + if ( game[level.gameType + "_overtime_first_winner"] == "allies" ) + { + winnerEnum = 1; + } + else if ( game[level.gameType + "_overtime_first_winner"] == "axis" ) + { + winnerEnum = 2; + } + } + + if ( isdefined(game[level.gameType + "_overtime_time_to_beat"]) ) + { + winningTime = game[level.gameType + "_overtime_time_to_beat"]; + } + + if( isdefined( game[level.gameType + "_overtime_best_time"] ) && ( !isDefined( winningTime ) || (winningTime > game[level.gameType + "_overtime_best_time"]) ) ) + { + if( game[level.gameType + "_overtime_first_winner"] !== winner ) + { + losingTime = winningTime; + } + + winningTime = game[level.gameType + "_overtime_best_time"]; + + if( winner === "tie" ) + { + winningTime = 0; + } + } + } + + if( ( level.gameType == "escort" ) && ( winner === "tie" ) ) + { + winnerEnum = 0; + if( !( isdefined( level.finalGameEnd ) && level.finalGameEnd ) ) + { + if( game["defenders"] == team ) + outcomeText = game["strings"]["round_win"]; + else + outcomeText = game["strings"]["round_loss"]; + } + } + } + if(!isdefined(winningTime))winningTime=0; + if(!isdefined(losingTime))losingTime=0; + + if( ( winningTime == 0 ) && ( losingTime == 0 ) ) + winnerEnum = 0; + + if ( team == "spectator" && overrideSpectator ) + { + outcomeText = game["strings"]["cod_caster_team_wins"]; + notifyRoundEndToUI = false; + } + + self LUINotifyEvent( &"show_outcome", 7, outcomeText, endReasonText, int( matchBonus ), winnerEnum, notifyRoundEndToUI, int( winningTime / 1000 ), int( losingTime / 1000 ) ); + } + else if( level.gameType == "ball" && + isdefined( winner ) && ( winner != "tie" ) && + game["roundsplayed"] < level.roundLimit && + isdefined( game["round_time_to_beat"] ) && + !isdefined( game["overtime_round"] ) ) + { + winningTime = game["round_time_to_beat"]; + + if(!isdefined(losingTime))losingTime=0; + + switch( winner ) + { + case "allies": + winnerEnum = 1; + break; + case "axis": + winnerEnum = 2; + break; + default: + winnerEnum = 0; + } + + if ( team == "spectator" && overrideSpectator ) + { + outcomeText = game["strings"]["cod_caster_team_wins"]; + notifyRoundEndToUI = false; + } + + self LUINotifyEvent( &"show_outcome", 7, outcomeText, endReasonText, int( matchBonus ), winnerEnum, notifyRoundEndToUI, int( winningTime / 1000 ), int( losingTime / 1000 ) ); + } + else + { + // Codcaster Specific Outcome Override + if ( team == "spectator" && overrideSpectator ) + { + // Special case handling of end reason text + foreach( team in level.teams ) + { + if ( endreasontext == game["strings"][team + "_eliminated"] ) + { + endReasonText = game["strings"]["cod_caster_team_eliminated"]; + break; + } + } + + outcomeText = game["strings"]["cod_caster_team_wins"]; + notifyRoundEndToUI = false; + } + + self LUINotifyEvent( &"show_outcome", 5, outcomeText, endReasonText, int( matchBonus ), winnerEnum, notifyRoundEndToUI ); + } +} + +function teamOutcomeNotifyZombie( winner, isRound, endReasonText ) +{ + self endon ( "disconnect" ); + self notify ( "reset_outcome" ); + + team = self.pers["team"]; + if ( isdefined( team ) && team == "spectator" ) + { + for ( i = 0; i < level.players.size; i++ ) + { + if ( self.currentspectatingclient == level.players[i].clientId ) + { + team = level.players[i].pers["team"]; + break; + } + } + } + + if ( !isdefined( team ) || !isdefined( level.teams[team] ) ) + team = "allies"; + + // wait for notifies to finish + while ( self.doingNotify ) + {wait(.05);}; + + self endon ( "reset_outcome" ); + + + if ( level.splitscreen ) + { + titleSize = 2.0; + spacing = 10; + font = "default"; + } + else + { + titleSize = 3.0; + spacing = 50; + font = "objective"; + } + + const duration = 60000; + + outcomeTitle = hud::createFontString( font, titleSize ); + outcomeTitle hud::setPoint( "TOP", undefined, 0, spacing ); + outcomeTitle.glowAlpha = 1; + outcomeTitle.hideWhenInMenu = false; + outcomeTitle.archived = false; + outcomeTitle.immunetodemogamehudsettings = true; + outcomeTitle.immunetodemofreecamera = true; + + outcomeTitle setText( endReasonText ); + outcomeTitle setPulseFX( 100, duration, 1000 ); + + self thread resetOutcomeNotify( undefined, undefined, outcomeTitle ); +} + +function outcomeNotify( winner, isRoundEnd, endReasonText ) +{ + self endon ( "disconnect" ); + self notify ( "reset_outcome" ); + + // wait for notifies to finish + while ( self.doingNotify ) + {wait(.05);}; + + self endon ( "reset_outcome" ); + + outcomeText = ""; + players = level.placement["all"]; + numClients = players.size; + + overrideSpectator = false; + + if ( !util::isOneRound() && !isRoundEnd ) + { + outcomeText = game["strings"]["game_over"]; + } + else if ( players[0].pointstowin == 0 ) + { + outcomeText = game["strings"]["tie"]; + } + else if ( self isInTop( players, 1 ) ) + { + outcomeText = game["strings"]["victory"]; + overrideSpectator = true; + } + else if ( self isInTop( players, 3 ) ) + { + outcomeText = game["strings"]["top3"]; + } + else + { + outcomeText = game["strings"]["defeat"]; + overrideSpectator = true; + } + + matchBonus = 0; + + if ( isdefined( self.pers["totalMatchBonus"] ) ) + { + matchBonus = self.pers["totalMatchBonus"]; + } + + wait( 2 ); + + team = self.pers["team"]; + if( isdefined( team ) && team == "spectator" && overrideSpectator ) + { + outcomeText = game["strings"]["cod_caster_team_wins"]; + self LUINotifyEvent( &"show_outcome", 5, outcomeText, endReasonText, matchBonus, winner, false ); + } + else + { + self LUINotifyEvent( &"show_outcome", 4, outcomeText, endReasonText, matchBonus, numClients ); + } + +} + +function wagerOutcomeNotify( winner, endReasonText ) +{ + self endon ( "disconnect" ); + self notify ( "reset_outcome" ); + + // wait for notifies to finish + while ( self.doingNotify ) + {wait(.05);}; + + self endon ( "reset_outcome" ); + + headerFont = "extrabig"; + font = "objective"; + if ( self IsSplitscreen() ) + { + titleSize = 2.0; + winnerSize = 1.5; + otherSize = 1.5; + iconSize = 30; + spacing = 2; + } + else + { + titleSize = 3.0; + winnerSize = 2.0; + otherSize = 1.5; + iconSize = 30; + spacing = 20; + } + + halftime = false; + if ( isdefined( level.sidebet ) && level.sidebet ) + halftime = true; + + duration = 60000; + + players = level.placement["all"]; + + outcomeTitle = hud::createFontString( headerFont, titleSize ); + outcomeTitle hud::setPoint( "TOP", undefined, 0, spacing ); + if( halftime ) + { + outcomeTitle setText( game["strings"]["intermission"] ); + outcomeTitle.color = (1.0, 1.0, 0.0); + outcomeTitle.glowColor = (1.0, 0.0, 0.0); + } + else if( isdefined(level.dontCalcWagerWinnings) && level.dontCalcWagerWinnings == true ) + { + outcomeTitle setText( game["strings"]["wager_topwinners"] ); + outcomeTitle.color = (0.42, 0.68, 0.46); + } + else + { + if ( isdefined( self.wagerWinnings ) && ( self.wagerWinnings > 0 ) ) + { + outcomeTitle setText( game["strings"]["wager_inthemoney"] ); + outcomeTitle.color = (0.42, 0.68, 0.46); + } + else + { + outcomeTitle setText( game["strings"]["wager_loss"] ); + outcomeTitle.color = (0.73, 0.29, 0.19); + } + } + outcomeTitle.glowAlpha = 1; + outcomeTitle.hideWhenInMenu = false; + outcomeTitle.archived = false; + outcomeTitle.immunetodemogamehudsettings = true; + outcomeTitle.immunetodemofreecamera = true; + outcomeTitle setCOD7DecodeFX( 200, duration, 600 ); + + outcomeText = hud::createFontString( font, 2.0 ); + outcomeText hud::setParent( outcomeTitle ); + outcomeText hud::setPoint( "TOP", "BOTTOM", 0, 0 ); + outcomeText.glowAlpha = 1; + outcomeText.hideWhenInMenu = false; + outcomeText.archived = false; + outcomeText.immunetodemogamehudsettings = true; + outcomeText.immunetodemofreecamera = true; + //outcomeText.glowColor = (0.2, 0.3, 0.7); + outcomeText setText( endReasonText ); + + playerNameHudElems = []; + playerCPHudElems = []; + numPlayers = players.size; + for ( i = 0 ; i < numPlayers ; i++ ) + { + if ( !halftime && isdefined( players[i] ) ) + { + secondTitle = hud::createFontString( font, otherSize ); + if ( playerNameHudElems.size == 0 ) + { + secondTitle hud::setParent( outcomeText ); + secondTitle hud::setPoint( "TOP_LEFT", "BOTTOM", -175, spacing*3 ); + } + else + { + secondTitle hud::setParent( playerNameHudElems[playerNameHudElems.size-1] ); + secondTitle hud::setPoint( "TOP_LEFT", "BOTTOM_LEFT", 0, spacing ); + } + //secondTitle.glowColor = (0.2, 0.3, 0.7); + secondTitle.glowAlpha = 1; + secondTitle.hideWhenInMenu = false; + secondTitle.archived = false; + secondTitle.immunetodemogamehudsettings = true; + secondTitle.immunetodemofreecamera = true; + secondTitle.label = &"MP_WAGER_PLACE_NAME"; + secondTitle.playerNum = i; + secondTitle setPlayerNameString( players[i] ); + playerNameHudElems[playerNameHudElems.size] = secondTitle; + + secondCP = hud::createFontString( font, otherSize ); + secondCP hud::setParent( secondTitle ); + secondCP hud::setPoint( "TOP_RIGHT", "TOP_LEFT", 350, 0 ); + secondCP.glowAlpha = 1; + secondCP.hideWhenInMenu = false; + secondCP.archived = false; + secondCP.immunetodemogamehudsettings = true; + secondCP.immunetodemofreecamera = true; + secondCP.label = &"MENU_POINTS"; + secondCP.currentValue = 0; + if ( isdefined( players[i].wagerWinnings ) ) + secondCP.targetValue = players[i].wagerWinnings; + else + secondCP.targetValue = 0; + if ( secondCP.targetValue > 0 ) + secondCP.color = (0.42, 0.68, 0.46); + secondCP setValue( 0 ); + playerCPHudElems[playerCPHudElems.size] = secondCP; + } + } + + /*matchBonus = hud::createFontString( font, 2.0 ); + matchBonus hud::setParent( thirdTitle ); + matchBonus hud::setPoint( "TOP", "BOTTOM_LEFT", 0, spacing ); + matchBonus.glowAlpha = 1; + matchBonus.hideWhenInMenu = false; + matchBonus.archived = false; + + sidebetWinnings = undefined; + if ( !halftime && isdefined( self.wagerWinnings ) ) + { + if ( isdefined( game["side_bets"] ) && game["side_bets"] ) + { + sidebetWinnings = hud::createFontString( font, 2.0 ); + sidebetWinnings hud::setParent( matchBonus ); + sidebetWinnings hud::setPoint( "TOP", "BOTTOM", 0, spacing ); + sidebetWinnings.glowAlpha = 1; + sidebetWinnings.hideWhenInMenu = false; + sidebetWinnings.archived = false; + sidebetWinnings.label = game["strings"]["wager_sidebet_winnings"]; + sidebetWinnings setValue( self.pers["wager_sideBetWinnings"] ); + } + }*/ + + self thread updateWagerOutcome( playerNameHudElems, playerCPHudElems ); + self thread resetWagerOutcomeNotify( playerNameHudElems, playerCPHudElems, outcomeTitle, outcomeText ); + + if ( halftime ) + return; + + stillUpdating = true; + countUpDuration = 2; + CPIncrement = 9999; + if ( isdefined( playerCPHudElems[0] ) ) + { + CPIncrement = int( playerCPHudElems[0].targetValue / ( countUpDuration / 0.05 ) ); + if ( CPIncrement < 1 ) + CPIncrement = 1; + } + while( stillUpdating ) + { + stillUpdating = false; + for ( i = 0 ; i < playerCPHudElems.size ; i++ ) + { + if ( isdefined( playerCPHudElems[i] ) && ( playerCPHudElems[i].currentValue < playerCPHudElems[i].targetValue ) ) + { + playerCPHudElems[i].currentValue += CPIncrement; + if ( playerCPHudElems[i].currentValue > playerCPHudElems[i].targetValue ) + playerCPHudElems[i].currentValue = playerCPHudElems[i].targetValue; + playerCPHudElems[i] SetValue( playerCPHudElems[i].currentValue ); + stillUpdating = true; + } + } + {wait(.05);}; + } +} + +function teamWagerOutcomeNotify( winner, isRoundEnd, endReasonText ) +{ + self endon ( "disconnect" ); + self notify ( "reset_outcome" ); + + team = self.pers["team"]; + if ( !isdefined( team ) || (!isdefined( level.teams[team] ) ) ) + team = "allies"; + + {wait(.05);}; + + // wait for notifies to finish + while ( self.doingNotify ) + {wait(.05);}; + + self endon ( "reset_outcome" ); + + headerFont = "extrabig"; + font = "objective"; + if ( self IsSplitscreen() ) + { + titleSize = 2.0; + textSize = 1.5; + iconSize = 30; + spacing = 10; + } + else + { + titleSize = 3.0; + textSize = 2.0; + iconSize = 70; + spacing = 15; + } + + halftime = false; + if ( isdefined( level.sidebet ) && level.sidebet ) + halftime = true; + + duration = 60000; + + outcomeTitle = hud::createFontString( headerFont, titleSize ); + outcomeTitle hud::setPoint( "TOP", undefined, 0, spacing ); + outcomeTitle.glowAlpha = 1; + outcomeTitle.hideWhenInMenu = false; + outcomeTitle.archived = false; + outcomeTitle.immunetodemogamehudsettings = true; + outcomeTitle.immunetodemofreecamera = true; + + outcomeText = hud::createFontString( font, 2.0 ); + outcomeText hud::setParent( outcomeTitle ); + outcomeText hud::setPoint( "TOP", "BOTTOM", 0, 0 ); + outcomeText.glowAlpha = 1; + outcomeText.hideWhenInMenu = false; + outcomeText.archived = false; + outcomeText.immunetodemogamehudsettings = true; + outcomeText.immunetodemofreecamera = true; + + if ( winner == "tie" ) + { + //outcomeTitle.glowColor = (0.2, 0.3, 0.7); + if ( isRoundEnd ) + outcomeTitle setText( game["strings"]["round_draw"] ); + else + outcomeTitle setText( game["strings"]["draw"] ); + outcomeTitle.color = (1, 1, 1); + + winner = "allies"; + } + else if ( winner == "overtime" ) + { + outcomeTitle setText( game["strings"]["overtime"] ); + outcomeTitle.color = (1, 1, 1); + + } + else if ( isdefined( self.pers["team"] ) && winner == team ) + { + //outcomeTitle.glowColor = (0, 0, 0); + if ( isRoundEnd ) + outcomeTitle setText( game["strings"]["round_win"] ); + else + outcomeTitle setText( game["strings"]["victory"] ); + outcomeTitle.color = (0.42, 0.68, 0.46); + } + else + { + //outcomeTitle.glowColor = (0, 0, 0); + if ( isRoundEnd ) + outcomeTitle setText( game["strings"]["round_loss"] ); + else + outcomeTitle setText( game["strings"]["defeat"] ); + outcomeTitle.color = (0.73, 0.29, 0.19); + } + if( !isdefined( level.dontShowEndReason ) || !level.dontShowEndReason ) + { + outcomeText setText( endReasonText ); + } + + outcomeTitle setPulseFX( 100, duration, 1000 ); + outcomeText setPulseFX( 100, duration, 1000 ); + + teamIcons = []; + teamIcons[team] = hud::createIcon( game["icons"][team], iconSize, iconSize ); + teamIcons[team] hud::setParent( outcomeText ); + teamIcons[team] hud::setPoint( "TOP", "BOTTOM", -60, spacing ); + teamIcons[team].hideWhenInMenu = false; + teamIcons[team].archived = false; + teamIcons[team].alpha = 0; + teamIcons[team] fadeOverTime( 0.5 ); + teamIcons[team].alpha = 1; + teamIcons[team].immunetodemogamehudsettings = true; + teamIcons[team].immunetodemofreecamera = true; + + foreach( enemyTeam in level.teams ) + { + if ( team == enemyTeam ) + continue; + + teamIcons[enemyTeam] = hud::createIcon( game["icons"][enemyTeam], iconSize, iconSize ); + teamIcons[enemyTeam] hud::setParent( outcomeText ); + teamIcons[enemyTeam] hud::setPoint( "TOP", "BOTTOM", 60, spacing ); + teamIcons[enemyTeam].hideWhenInMenu = false; + teamIcons[enemyTeam].archived = false; + teamIcons[enemyTeam].alpha = 0; + teamIcons[enemyTeam] fadeOverTime( 0.5 ); + teamIcons[enemyTeam].alpha = 1; + teamIcons[enemyTeam].immunetodemogamehudsettings = true; + teamIcons[enemyTeam].immunetodemofreecamera = true; + } + + teamScores = []; + teamScores[team] = hud::createFontString( font, titleSize ); + teamScores[team] hud::setParent( teamIcons[team] ); + teamScores[team] hud::setPoint( "TOP", "BOTTOM", 0, spacing ); + //teamScores[team].glowColor = game["colors"][team]; + teamScores[team].glowAlpha = 1; + teamScores[team] setValue( getTeamScore( team ) ); + teamScores[team].hideWhenInMenu = false; + teamScores[team].archived = false; + teamScores[team].immunetodemogamehudsettings = true; + teamScores[team].immunetodemofreecamera = true; + teamScores[team] setPulseFX( 100, duration, 1000 ); + + foreach( enemyTeam in level.teams ) + { + if ( team == enemyTeam ) + continue; + + teamScores[enemyTeam] = hud::createFontString( font, titleSize ); + teamScores[enemyTeam] hud::setParent( teamIcons[enemyTeam] ); + teamScores[enemyTeam] hud::setPoint( "TOP", "BOTTOM", 0, spacing ); + //teamScores[enemyTeam].glowColor = game["colors"][enemyTeam]; + teamScores[enemyTeam].glowAlpha = 1; + teamScores[enemyTeam] setValue( getTeamScore( enemyTeam ) ); + teamScores[enemyTeam].hideWhenInMenu = false; + teamScores[enemyTeam].archived = false; + teamScores[enemyTeam].immunetodemogamehudsettings = true; + teamScores[enemyTeam].immunetodemofreecamera = true; + teamScores[enemyTeam] setPulseFX( 100, duration, 1000 ); + } + + matchBonus = undefined; + sidebetWinnings = undefined; + if ( !isRoundEnd && !halftime && isdefined( self.wagerWinnings ) ) + { + matchBonus = hud::createFontString( font, 2.0 ); + matchBonus hud::setParent( outcomeText ); + matchBonus hud::setPoint( "TOP", "BOTTOM", 0, iconSize + (spacing * 3) + teamScores[team].height ); + matchBonus.glowAlpha = 1; + matchBonus.hideWhenInMenu = false; + matchBonus.archived = false; + matchBonus.immunetodemogamehudsettings = true; + matchBonus.immunetodemofreecamera = true; + matchBonus.label = game["strings"]["wager_winnings"]; + matchBonus setValue( self.wagerWinnings ); + + if ( isdefined( game["side_bets"] ) && game["side_bets"] ) + { + sidebetWinnings = hud::createFontString( font, 2.0 ); + sidebetWinnings hud::setParent( matchBonus ); + sidebetWinnings hud::setPoint( "TOP", "BOTTOM", 0, spacing ); + sidebetWinnings.glowAlpha = 1; + sidebetWinnings.hideWhenInMenu = false; + sidebetWinnings.archived = false; + sidebetWinnings.immunetodemogamehudsettings = true; + sidebetWinnings.immunetodemofreecamera = true; + sidebetWinnings.label = game["strings"]["wager_sidebet_winnings"]; + sidebetWinnings setValue( self.pers["wager_sideBetWinnings"] ); + } + } + self thread resetOutcomeNotify( teamIcons, teamScores, outcomeTitle, outcomeText, matchBonus, sidebetWinnings ); +} + +function resetOutcomeNotify( hudElemList1, hudElemList2, hudElem3, hudElem4, hudElem5, hudElem6, hudElem7, hudElem8, hudElem9, hudElem10 ) +{ + self endon ( "disconnect" ); + self waittill ( "reset_outcome" ); + + destroyHudElem( hudElem3 ); + destroyHudElem( hudElem4 ); + destroyHudElem( hudElem5 ); + destroyHudElem( hudElem6 ); + destroyHudElem( hudElem7 ); + destroyHudElem( hudElem8 ); + destroyHudElem( hudElem9 ); + destroyHudElem( hudElem10 ); + + if ( isdefined( hudElemList1 ) ) + { + foreach( elem in hudElemList1 ) + { + destroyHudElem( elem ); + } + } + + if ( isdefined( hudElemList2 ) ) + { + foreach( elem in hudElemList2 ) + { + destroyHudElem( elem ); + } + } +} + +function resetWagerOutcomeNotify( playerNameHudElems, playerCPHudElems, outcomeTitle, outcomeText ) +{ + self endon( "disconnect" ); + self waittill( "reset_outcome" ); + + for ( i = playerNameHudElems.size - 1 ; i >= 0 ; i-- ) + { + if ( isdefined( playerNameHudElems[i] ) ) + playerNameHudElems[i] destroy(); + } + + for ( i = playerCPHudElems.size - 1 ; i >= 0 ; i-- ) + { + if ( isdefined( playerCPHudElems[i] ) ) + playerCPHudElems[i] destroy(); + } + + if ( isdefined( outcomeText ) ) + outcomeText destroy(); + + if ( isdefined( outcomeTitle ) ) + outcomeTitle destroy(); +} + +function updateOutcome( firstTitle, secondTitle, thirdTitle ) +{ + self endon( "disconnect" ); + self endon( "reset_outcome" ); + + while( true ) + { + self waittill( "update_outcome" ); + + players = level.placement["all"]; + + if ( isdefined( firstTitle ) && isdefined( players[0] ) ) + firstTitle setPlayerNameString( players[0] ); + else if ( isdefined( firstTitle ) ) + firstTitle.alpha = 0; + + if ( isdefined( secondTitle ) && isdefined( players[1] ) ) + secondTitle setPlayerNameString( players[1] ); + else if ( isdefined( secondTitle ) ) + secondTitle.alpha = 0; + + if ( isdefined( thirdTitle ) && isdefined( players[2] ) ) + thirdTitle setPlayerNameString( players[2] ); + else if ( isdefined( thirdTitle ) ) + thirdTitle.alpha = 0; + } +} + +function updateWagerOutcome( playerNameHudElems, playerCPHudElems ) +{ + self endon( "disconnect" ); + self endon( "reset_outcome" ); + + while ( true ) + { + self waittill( "update_outcome" ); + + players = level.placement["all"]; + + for ( i = 0 ; i < playerNameHudElems.size ; i++ ) + { + if ( isdefined( playerNameHudElems[i] ) && isdefined( players[playerNameHudElems[i].playerNum] ) ) + playerNameHudElems[i] SetPlayerNameString( players[playerNameHudElems[i].playerNum] ); + else + { + if ( isdefined( playerNameHudElems[i] ) ) + playerNameHudElems[i].alpha = 0; + if ( isdefined( playerCPHudElems[i] ) ) + playerCPHudElems[i].alpha = 0; + } + } + } +} diff --git a/mp/gametypes/_killcam.gsc b/mp/gametypes/_killcam.gsc new file mode 100644 index 0000000..1187f76 --- /dev/null +++ b/mp/gametypes/_killcam.gsc @@ -0,0 +1,1129 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\killcam_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_tacticalinsertion; +#using scripts\shared\visionset_mgr_shared; + + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_spectating; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; + +#precache( "material", "white" ); +#precache( "string", "PLATFORM_PRESS_TO_SKIP" ); +#precache( "string", "PLATFORM_PRESS_TO_RESPAWN" ); +#precache( "eventstring", "pre_killcam_transition" ); +#precache( "eventstring", "post_killcam_transition" ); + +#namespace killcam; + +function autoexec __init__sytem__() { system::register("killcam",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); + // clientuimodels are registered client-side in raw/ui/uieditor/clientfieldmodels.lua + clientfield::register( "clientuimodel", "hudItems.killcamAllowRespawn", 1, 1, "int" ); +} + +function init() +{ + + level.killcam = GetGametypeSetting( "allowKillcam" ); + level.finalkillcam = GetGametypeSetting( "allowFinalKillcam" ); + + init_final_killCam(); +} + +function init_final_killCam() +{ + level.finalKillcamSettings = []; + + init_final_killCam_team( "none" ); + + foreach( team in level.teams ) + { + init_final_killCam_team( team ); + } + + level.finalKillCam_winner = undefined; + level.finalKillCam_winnerPicked = undefined; +} + +function init_final_killCam_team( team ) +{ + level.finalKillcamSettings[team] = SpawnStruct(); + + clear_final_killcam_team( team ); +} + +function clear_final_killcam_team( team ) +{ + level.finalKillcamSettings[team].spectatorclient = undefined; + level.finalKillcamSettings[team].weapon = undefined; + level.finalKillcamSettings[team].meansOfDeath = undefined; + level.finalKillcamSettings[team].deathTime = undefined; + level.finalKillcamSettings[team].deathTimeOffset = undefined; + level.finalKillcamSettings[team].offsettime = undefined; + level.finalKillcamSettings[team].killcam_entity_info = undefined; + level.finalKillcamSettings[team].targetentityindex = undefined; + level.finalKillcamSettings[team].perks = undefined; + level.finalKillcamSettings[team].killstreaks = undefined; + level.finalKillcamSettings[team].attacker = undefined; +} + +function record_settings( spectatorclient, targetentityindex, weapon, meansOfDeath, deathTime, deathTimeOffset, offsettime, killcam_entity_info, perks, killstreaks, attacker ) +{ + if( isdefined( attacker ) && isdefined( attacker.team ) && isdefined( level.teams[attacker.team] ) ) + { + team = attacker.team; + + level.finalKillcamSettings[ team ].spectatorclient = spectatorclient; + level.finalKillcamSettings[ team ].weapon = weapon; + level.finalKillcamSettings[ team ].meansOfDeath = meansOfDeath; + level.finalKillcamSettings[ team ].deathTime = deathTime; + level.finalKillcamSettings[ team ].deathTimeOffset = deathTimeOffset; + level.finalKillcamSettings[ team ].offsettime = offsettime; + level.finalKillcamSettings[ team ].killcam_entity_info = killcam_entity_info; + level.finalKillcamSettings[ team ].targetentityindex = targetentityindex; + level.finalKillcamSettings[ team ].perks = perks; + level.finalKillcamSettings[ team ].killstreaks = killstreaks; + level.finalKillcamSettings[ team ].attacker = attacker; + } + + level.finalKillcamSettings[ "none" ].spectatorclient = spectatorclient; + level.finalKillcamSettings[ "none" ].weapon = weapon; + level.finalKillcamSettings[ "none" ].meansOfDeath = meansOfDeath; + level.finalKillcamSettings[ "none" ].deathTime = deathTime; + level.finalKillcamSettings[ "none" ].deathTimeOffset = deathTimeOffset; + level.finalKillcamSettings[ "none" ].offsettime = offsettime; + level.finalKillcamSettings[ "none" ].killcam_entity_info = killcam_entity_info; + level.finalKillcamSettings[ "none" ].targetentityindex = targetentityindex; + level.finalKillcamSettings[ "none" ].perks = perks; + level.finalKillcamSettings[ "none" ].killstreaks = killstreaks; + level.finalKillcamSettings[ "none" ].attacker = attacker; +} + +function erase_final_killcam() +{ + clear_final_killcam_team( "none" ); + + foreach( team in level.teams ) + { + clear_final_killcam_team( team ); + } + + level.finalKillCam_winner = undefined; + level.finalKillCam_winnerPicked = undefined; +} + +function final_killcam_waiter() +{ + if ( level.finalKillCam_winnerPicked === true ) + { + level waittill( "final_killcam_done" ); + } +} + +function post_round_final_killcam() +{ + if ( isdefined( level.sidebet ) && level.sidebet ) + { + return; + } + level notify( "play_final_killcam" ); + globallogic::resetOutcomeForAllPlayers(); + final_killcam_waiter(); +} + +function do_final_killcam() +{ + level waittill ( "play_final_killcam" ); + + LUINotifyEvent( &"pre_killcam_transition" ); + + wait( 0.35 ); + + level.inFinalKillcam = true; + + winner = "none"; + if( isdefined( level.finalKillCam_winner ) ) + { + winner = level.finalKillCam_winner; + } + + winning_team = globallogic::figureOutWinningTeam( winner ); + + if( !isdefined( level.finalKillcamSettings[ winning_team ].targetentityindex ) ) + { + level.inFinalKillcam = false; + level notify( "final_killcam_done" ); + return; + } + + attacker = level.finalKillcamSettings[ winning_team ].attacker; + if( isDefined( attacker ) && isDefined( attacker.archetype ) && ( attacker.archetype == "mannequin" ) ) + { + level.inFinalKillcam = false; + level notify( "final_killcam_done" ); + return; + } + + if ( isdefined ( attacker ) ) + { + challenges::getFinalKill( attacker ); + } + + visionSetNaked( GetDvarString( "mapname" ), 0.0 ); + + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + player closeInGameMenu(); + player thread final_killcam( winner ); + } + + wait( 0.1 ); + + while ( are_any_players_watching() ) + {wait(.05);}; + + level notify( "final_killcam_done" ); + level.inFinalKillcam = false; +} + +function startLastKillcam() +{ +} + + +function are_any_players_watching() +{ + players = level.players; + for ( index = 0; index < players.size; index++ ) + { + player = players[index]; + if ( isdefined( player.killcam ) ) + return true; + } + + return false; +} + +function watch_for_skip_killcam() +{ + self endon("begin_killcam"); + self util::waittill_any("disconnect","spawned"); + + wait 0.05; // Allow all players entering killcam to increment 'level.numPlayersWaitingToEnterKillcam' + level.numPlayersWaitingToEnterKillcam--; +} + +function killcam( + attackerNum, // entity number of the attacker + targetNum, // entity number of the target + killcam_entity_info, // struct containing killcam entity info + weapon, // killing weapon + meansOfDeath, + deathTime, // time when the player died + deathTimeOffset, // time between player death and beginning of killcam + offsetTime, // something to do with how far back in time the killer was seeing the world when he made the kill; latency related, sorta + respawn, // will the player be allowed to respawn after the killcam? + maxtime, // time remaining until map ends; the killcam will never last longer than this. undefined = no limit + perks, // the perks the attacker had at the time of the kill + killstreaks, // the killstreaks the attacker had at the time of the kill + attacker, // entity object of attacker + keep_deathcam // remain in death-cam when the killcam ends +) +{ + // monitors killcam and hides HUD elements during killcam session + //if ( !level.splitscreen ) + // self thread killcam_HUD_off(); + + self endon("disconnect"); + self endon("spawned"); + level endon("game_ended"); + + if(attackerNum < 0) + return; + + //this is to track and amortize number of killcams per frame + self thread watch_for_skip_killcam(); + level.numPlayersWaitingToEnterKillcam++; + assert( level.numPlayersWaitingToEnterKillcam < 20 ); + + if ( level.numPlayersWaitingToEnterKillcam > 1 ) + { + /# + println( "[KILLCAM] more than one client entering killcam this frame" ); + #/ + + wait 0.05 * (level.numPlayersWaitingToEnterKillcam - 1); + } + + wait 0.05; // Allow all players entering killcam to increment 'level.numPlayersWaitingToEnterKillcam' + level.numPlayersWaitingToEnterKillcam--; + assert( level.numPlayersWaitingToEnterKillcam > -1 ); + + postDeathDelay = (getTime() - deathTime) / 1000; + predelay = postDeathDelay + deathTimeOffset; + + killcamentitystarttime = get_killcam_entity_info_starttime( killcam_entity_info ); + + camtime = calc_time( weapon, killcamentitystarttime, predelay, respawn, maxtime ); + postdelay = calc_post_delay(); + + /* timeline: + + | camtime | postdelay | + | | predelay | + + ^ killcam start ^ player death ^ killcam end + ^ player starts watching killcam + + */ + + killcamlength = camtime + postdelay; + + // don't let the killcam last past the end of the round. + if (isdefined(maxtime) && killcamlength > maxtime) + { + // first trim postdelay down to a minimum of 1 second. + // if that doesn't make it short enough, trim camtime down to a minimum of 1 second. + // if that's still not short enough, cancel the killcam. + if (maxtime < 2) + return; + + if (maxtime - camtime >= 1) { + // reduce postdelay so killcam ends at end of match + postdelay = maxtime - camtime; + } + else { + // distribute remaining time over postdelay and camtime + postdelay = 1; + camtime = maxtime - 1; + } + + // recalc killcamlength + killcamlength = camtime + postdelay; + } + + killcamoffset = camtime + predelay; + + self notify ( "begin_killcam", getTime() ); + self util::clientnotify( "sndDEDe" ); + + killcamstarttime = (gettime() - killcamoffset * 1000); + + self.sessionstate = "spectator"; + self.spectatekillcam = true; + self.spectatorclient = attackerNum; + self.killcamentity = -1; + self thread set_killcam_entities( killcam_entity_info, killcamstarttime ); + self.killcamtargetentity = targetNum; + self.killcamweapon = weapon; + self.killcammod = meansOfDeath; + self.archivetime = killcamoffset; + self.killcamlength = killcamlength; + self.psoffsettime = offsetTime; + + //record_settings( attackerNum, targetNum, weapon, deathTime, deathTimeOffset, offsetTime, killcam_entity_info, perks, killstreaks, attacker ); + + // ignore spectate permissions + foreach( team in level.teams ) + { + self allowSpectateTeam(team, true); + } + self allowSpectateTeam("freelook", true); + self allowSpectateTeam("none", true); + + self thread ended_killcam_cleanup(); + + // wait till the next server frame to allow code a chance to update archivetime if it needs trimming + {wait(.05);}; + + if ( self.archivetime <= predelay ) // if we're not looking back in time far enough to even see the death, cancel + { + self.sessionstate = "dead"; + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + + self notify ( "end_killcam" ); + + return; + } + + self thread check_for_abrupt_end(); + + self.killcam = true; + + //self init_kc_elements(); + + self add_skip_text(respawn); + + if ( !( self IsSplitscreen() ) && level.perksEnabled == 1 ) + { + self add_timer(camtime); + self hud::showPerks( ); +// for ( numSpecialties = 0; numSpecialties < perks.size; numSpecialties++ ) +// { +// self hud::showPerk( numSpecialties, perks[ numSpecialties ], 10); +// } + } + + self thread spawned_killcam_cleanup(); + self thread wait_skip_killcam_button(); + self thread wait_team_change_end_killcam(); + //self thread wait_skip_killcam_safe_spawn_button(); + self thread wait_killcam_time(); + self thread tacticalinsertion::cancel_button_think(); + + self waittill("end_killcam"); + + self end(false); + + if ( ( isdefined( keep_deathcam ) && keep_deathcam ) ) + return; + + self.sessionstate = "dead"; + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; +} + +function set_entity( killcamentityindex, delayms ) +{ + self endon("disconnect"); + self endon("end_killcam"); + self endon("spawned"); + + if ( delayms > 0 ) + wait delayms / 1000; + + self.killcamentity = killcamentityindex; +} + +function set_killcam_entities( entity_info, killcamstarttime ) +{ + for ( index = 0; index < entity_info.entity_indexes.size; index++ ) + { + delayms = entity_info.entity_spawntimes[index] - killcamstarttime - 100; + + thread set_entity(entity_info.entity_indexes[index], delayms ); + + // return if this entity spawned before the killcam start time + // we dont want any older entities + if ( delayms <=0 ) + return; + } +} + +function wait_killcam_time() +{ + self endon("disconnect"); + self endon("end_killcam"); + + wait(self.killcamlength - 0.05); + self notify("end_killcam"); +} + +function wait_final_killcam_slowdown( deathTime, startTime ) +{ + self endon("disconnect"); + self endon("end_killcam"); + + if ( ( isdefined( level.skipKillcamSlowdown ) && level.skipKillcamSlowdown ) ) + return; + + secondsUntilDeath = ( ( deathTime - startTime ) / 1000 ); + deathTime = getTime() + secondsUntilDeath * 1000; + waitBeforeDeath = 2; + + wait( max(0, (secondsUntilDeath - waitBeforeDeath) ) ); + + util::setClientSysState("levelNotify", "sndFKsl" ); + + setSlowMotion( 1.0, 0.25, waitBeforeDeath ); // start timescale, end timescale, lerp duration + wait( waitBeforeDeath + .5 ); + setSlowMotion( 0.25, 1, 1.0 ); + + wait(.5); +} + +function wait_skip_killcam_button() +{ + self endon("disconnect"); + self endon("end_killcam"); + + while(self useButtonPressed()) + wait .05; + + while(!(self useButtonPressed())) + wait .05; + + if( isdefined( self.killcamsSkipped) ) + { + self.killcamsSkipped++; + } + else + { + self.killcamsSkipped = 1; + } + + self notify("end_killcam"); + self util::clientNotify("fkce"); +} + + + +function wait_team_change_end_killcam() +{ + self endon("disconnect"); + self endon("end_killcam"); + + self waittill("changed_class"); + + end( false ); +} + + +function wait_skip_killcam_safe_spawn_button() +{ + self endon("disconnect"); + self endon("end_killcam"); + + while(self fragButtonPressed()) + wait .05; + + while(!(self fragButtonPressed())) + wait .05; + + self.wantSafeSpawn = true; + + self notify("end_killcam"); +} + +function end( final ) +{ + if(isdefined(self.kc_skiptext)) + self.kc_skiptext.alpha = 0; + //if(isdefined(self.kc_skiptext2)) + // self.kc_skiptext2.alpha = 0; + if(isdefined(self.kc_timer)) + self.kc_timer.alpha = 0; + + self.killcam = undefined; + + self thread spectating::set_permissions(); +} + +function check_for_abrupt_end() +{ + self endon("disconnect"); + self endon("end_killcam"); + + while(1) + { + // code may trim our archivetime to zero if there is nothing "recorded" to show. + // this can happen when the person we're watching in our killcam goes into killcam himself. + // in this case, end the killcam. + if ( self.archivetime <= 0 ) + break; + wait .05; + } + + self notify("end_killcam"); +} + +function spawned_killcam_cleanup() +{ + self endon("end_killcam"); + self endon("disconnect"); + + self waittill("spawned"); + self end(false); +} + +function spectator_killcam_cleanup( attacker ) +{ + self endon("end_killcam"); + self endon("disconnect"); + attacker endon ( "disconnect" ); + + attacker waittill ( "begin_killcam", attackerKcStartTime ); + waitTime = max( 0, (attackerKcStartTime - self.deathTime) - 50 ); + wait (waitTime); + self end(false); +} + +function ended_killcam_cleanup() +{ + self endon("end_killcam"); + self endon("disconnect"); + + level waittill("game_ended"); + + self end(false); + + // spawn at an intermission point + self [[level.spawnIntermission]]( false ); +} + +function ended_final_killcam_cleanup() +{ + self endon("end_killcam"); + self endon("disconnect"); + + level waittill("game_ended"); + self end(true); +} + +function cancel_use_button() +{ + return self useButtonPressed(); +} + +function cancel_safe_spawn_button() +{ + return self fragButtonPressed(); +} + +function cancel_callback() +{ + self.cancelKillcam = true; +} + +function cancel_safe_spawn_callback() +{ + self.cancelKillcam = true; + self.wantSafeSpawn = true; +} + +function cancel_on_use() +{ + self thread cancel_on_use_specific_button(&cancel_use_button,&cancel_callback ); + //self thread cancel_on_use_specific_button(&cancel_safe_spawn_button,&cancel_safe_spawn_callback ); +} + +function cancel_on_use_specific_button( pressingButtonFunc, finishedFunc ) +{ + self endon ( "death_delay_finished" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + for ( ;; ) + { + if ( !self [[pressingButtonFunc]]() ) + { + {wait(.05);}; + continue; + } + + buttonTime = 0; + while( self [[pressingButtonFunc]]() ) + { + buttonTime += 0.05; + {wait(.05);}; + } + + if ( buttonTime >= 0.5 ) + continue; + + buttonTime = 0; + + while ( !self [[pressingButtonFunc]]() && buttonTime < 0.5 ) + { + buttonTime += 0.05; + {wait(.05);}; + } + + if ( buttonTime >= 0.5 ) + continue; + + self [[finishedFunc]](); + return; + } +} + +function final_killcam_internal( winner ) +{ + winning_team = globallogic::figureOutWinningTeam( winner ); + + killcamSettings = level.finalKillcamSettings[ winning_team ]; + + postDeathDelay = (getTime() - killcamSettings.deathTime) / 1000; + predelay = postDeathDelay + killcamSettings.deathTimeOffset; + + killcamentitystarttime = get_killcam_entity_info_starttime( killcamSettings.killcam_entity_info ); + + camtime = calc_time( killcamSettings.weapon, killcamentitystarttime, predelay, false, undefined ); + postdelay = calc_post_delay(); + + killcamoffset = camtime + predelay; + killcamlength = camtime + postdelay - 0.05; // We do the -0.05 since we are doing a wait below. + + killcamstarttime = (gettime() - killcamoffset * 1000); + + self notify ( "begin_killcam", getTime() ); + util::setClientSysState("levelNotify", "sndFKs" ); + + self.sessionstate = "spectator"; + self.spectatorclient = killcamSettings.spectatorclient; + self.killcamentity = -1; + self thread set_killcam_entities( killcamSettings.killcam_entity_info, killcamstarttime ); + self.killcamtargetentity = killcamSettings.targetentityindex; + self.killcamweapon = killcamSettings.weapon; + self.killcammod = killcamSettings.meansOfDeath; + self.archivetime = killcamoffset; + self.killcamlength = killcamlength; + self.psoffsettime = killcamSettings.offsettime; + + // ignore spectate permissions + foreach( team in level.teams ) + { + self allowSpectateTeam(team, true); + } + self allowSpectateTeam("freelook", true); + self allowSpectateTeam("none", true); + + self thread ended_final_killcam_cleanup(); + + // wait till the next server frame to allow code a chance to update archivetime if it needs trimming + {wait(.05);}; + + if ( !util::isPropHuntGametype() && self.archivetime <= predelay ) // if we're not looking back in time far enough to even see the death, cancel + { + // self.sessionstate = "dead"; // DO NOT SET to state "dead" in final killcam + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + + self notify ( "end_killcam" ); + + return; + } + + self thread check_for_abrupt_end(); + + self.killcam = true; + + if ( !( self IsSplitscreen() ) ) + { + self add_timer(camtime); + } + + self thread wait_killcam_time(); + self thread wait_final_killcam_slowdown( level.finalKillcamSettings[ winning_team ].deathTime, killcamstarttime ); + + self waittill("end_killcam"); +} + +function final_killcam( winner ) +{ + self endon("disconnect"); + level endon("game_ended"); + + if ( util::wasLastRound() ) + { + setMatchFlag( "final_killcam", 1 ); + setMatchFlag( "round_end_killcam", 0 ); + } + else + { + setMatchFlag( "final_killcam", 0 ); + setMatchFlag( "round_end_killcam", 1 ); + } + +/# + if ( GetDvarint( "scr_force_finalkillcam" ) == 1 ) + { + setMatchFlag( "final_killcam", 1 ); + setMatchFlag( "round_end_killcam", 0 ); + } +#/ + + if( !util::isPropHuntGametype() && level.console ) + self globallogic_spawn::setThirdPerson( false ); + +/# + while( GetDvarint( "scr_endless_finalkillcam" ) == 1 ) + { + final_killcam_internal( winner ); + } +#/ + + final_killcam_internal( winner ); + + util::setClientSysState("levelNotify", "sndFKe" ); + + LUINotifyEvent( &"post_killcam_transition" ); + + self FreezeControls( true ); + + wait 1.5; + + self end(true); + + setMatchFlag( "final_killcam", 0 ); + setMatchFlag( "round_end_killcam", 0 ); + + self spawn_end_of_final_killcam(); +} + +// This puts the player to the intermission point as a spectator once the killcam is over. +function spawn_end_of_final_killcam() +{ + [[level.spawnSpectator]](); + self FreezeControls( true ); + self visionset_mgr::player_shutdown(); +} + +function is_entity_weapon( weapon ) +{ + if ( weapon.name == "planemortar" ) + { + return true; + } + + return false; +} + +function calc_time( weapon, entitystarttime, predelay, respawn, maxtime ) +{ + camtime = 0.0; + + // length from killcam start to killcam end + if (GetDvarString( "scr_killcam_time") == "") + { + if ( is_entity_weapon( weapon ) ) + { + camtime = (gettime() - entitystarttime) / 1000 - predelay - .1; + } + else if ( !respawn ) // if we're not going to respawn, we can take more time to watch what happened + { + camtime = 5.0; + } + else if ( weapon.isGrenadeWeapon ) + { + camtime = 4.25; // show long enough to see grenade thrown + } + else + camtime = 2.5; + } + else + camtime = GetDvarfloat( "scr_killcam_time"); + + if (isdefined(maxtime)) { + if (camtime > maxtime) + camtime = maxtime; + if (camtime < .05) + camtime = .05; + } + + return camtime; +} + +function calc_post_delay() +{ + postdelay = 0; + + // time after player death that killcam continues for + if (GetDvarString( "scr_killcam_posttime") == "") + { + postdelay = 2; + } + else + { + postdelay = GetDvarfloat( "scr_killcam_posttime"); + if (postdelay < 0.05) + postdelay = 0.05; + } + + return postdelay; +} + +function add_skip_text(respawn) +{ + self clientfield::set_player_uimodel( "hudItems.killcamAllowRespawn", respawn ); +} + +function add_timer(camtime) +{ + /*if ( !isdefined( self.kc_timer ) ) + { + self.kc_timer = hud::createFontString( "extrabig", 3.0 ); + if ( level.console ) + self.kc_timer hud::setPoint( "TOP", undefined, 0, 45 ); + else + self.kc_timer hud::setPoint( "TOP", undefined, 0, 55 ); + self.kc_timer.archived = false; + self.kc_timer.foreground = true; + } + + self.kc_timer.alpha = 0.2; + self.kc_timer setTenthsTimer(camtime);*/ +} + + +function init_kc_elements() +{ + if ( !isdefined( self.kc_skiptext ) ) + { + self.kc_skiptext = newClientHudElem(self); + self.kc_skiptext.archived = false; + self.kc_skiptext.x = 0; + self.kc_skiptext.alignX = "center"; + self.kc_skiptext.alignY = "top"; + self.kc_skiptext.horzAlign = "center_adjustable"; + self.kc_skiptext.vertAlign = "top_adjustable"; + self.kc_skiptext.sort = 1; // force to draw after the bars + self.kc_skiptext.font = "default"; + self.kc_skiptext.foreground = true; + self.kc_skiptext.hideWhenInMenu = true; + + if ( self IsSplitscreen() ) + { + self.kc_skiptext.y = 20; + self.kc_skiptext.fontscale = 1.2; // 1.8/1.5 + } + else + { + self.kc_skiptext.y = 32; + self.kc_skiptext.fontscale = 1.8; + } + } + + if ( !isdefined( self.kc_othertext ) ) + { + self.kc_othertext = newClientHudElem(self); + self.kc_othertext.archived = false; + self.kc_othertext.y = 48; + self.kc_othertext.alignX = "left"; + self.kc_othertext.alignY = "top"; + self.kc_othertext.horzAlign = "center"; + self.kc_othertext.vertAlign = "middle"; + self.kc_othertext.sort = 10; // force to draw after the bars + self.kc_othertext.font = "small"; + self.kc_othertext.foreground = true; + self.kc_othertext.hideWhenInMenu = true; + + if ( self IsSplitscreen() ) + { + self.kc_othertext.x = 16; + self.kc_othertext.fontscale = 1.2; + } + else + { + self.kc_othertext.x = 32; + self.kc_othertext.fontscale = 1.6; + } + } + + if ( !isdefined( self.kc_icon ) ) + { + self.kc_icon = newClientHudElem(self); + self.kc_icon.archived = false; + self.kc_icon.x = 16; + self.kc_icon.y = 16; + self.kc_icon.alignX = "left"; + self.kc_icon.alignY = "top"; + self.kc_icon.horzAlign = "center"; + self.kc_icon.vertAlign = "middle"; + self.kc_icon.sort = 1; // force to draw after the bars + self.kc_icon.foreground = true; + self.kc_icon.hideWhenInMenu = true; + } + + if ( !( self IsSplitscreen() ) ) + { + if ( !isdefined( self.kc_timer ) ) + { + self.kc_timer = hud::createFontString( "hudbig", 1.0 ); + self.kc_timer.archived = false; + self.kc_timer.x = 0; + self.kc_timer.alignX = "center"; + self.kc_timer.alignY = "middle"; + self.kc_timer.horzAlign = "center_safearea"; + self.kc_timer.vertAlign = "top_adjustable"; + self.kc_timer.y = 42; + self.kc_timer.sort = 1; // force to draw after the bars + self.kc_timer.font = "hudbig"; + self.kc_timer.foreground = true; + self.kc_timer.color = (0.85,0.85,0.85); + self.kc_timer.hideWhenInMenu = true; + } + } +} + +function get_closest_killcam_entity( attacker, killCamEntities, depth ) +{ + if ( !isdefined( depth ) ) + depth = 0; + + closestKillcamEnt = undefined; + closestKillcamEntIndex = undefined; + closestKillcamEntDist = undefined; + origin = undefined; + + foreach( killcamEntIndex, killcamEnt in killCamEntities ) + { + if ( killcamEnt == attacker ) + continue; + + origin = killcamEnt.origin; + if ( isdefined( killcamEnt.offsetPoint ) ) + origin += killcamEnt.offsetPoint; + + dist = DistanceSquared( self.origin, origin ); + + if ( !isdefined( closestKillcamEnt ) || dist < closestKillcamEntDist ) + { + closestKillcamEnt = killcamEnt; + closestKillcamEntDist = dist; + closestKillcamEntIndex = killcamEntIndex; + } + } + + // check to see if the player is visible at time of death + if ( depth < 3 && isdefined( closestKillcamEnt ) ) + { + if ( !BulletTracePassed( closestKillcamEnt.origin, self.origin, false, self ) ) + { + killCamEntities[closestKillcamEntIndex] = undefined; + + betterKillcamEnt = get_closest_killcam_entity( attacker, killCamEntities, depth + 1 ); + + if ( isdefined( betterKillcamEnt ) ) + { + closestKillcamEnt = betterKillcamEnt; + } + } + } + + return closestKillcamEnt; +} + +function get_killcam_entity( attacker, eInflictor, weapon ) +{ + if ( !isdefined( eInflictor ) ) + return undefined; + + // if there is a killcam entity stored on the player who died + if ( isdefined(self.killcamKilledByEnt) ) + return self.killcamKilledByEnt; + + if ( eInflictor == attacker ) + { + if( !isdefined( eInflictor.isMagicBullet ) ) + return undefined; + if( isdefined( eInflictor.isMagicBullet ) && !eInflictor.isMagicBullet ) + return undefined; + } + else if ( isdefined( level.levelSpecificKillcam ) ) + { + levelSpecificKillcamEnt = self [[level.levelSpecificKillcam]](); + if ( isdefined( levelSpecificKillcamEnt ) ) + return levelSpecificKillcamEnt; + } + + if ( weapon.name == "hero_gravityspikes" ) + return undefined; + + if ( isdefined( attacker ) && IsPlayer( attacker ) && attacker IsRemoteControlling() && ( eInflictor.controlled === true || eInflictor.occupied === true ) ) + { + if ( ( weapon.name == "sentinel_turret" ) + || ( weapon.name == "helicopter_gunner_turret_primary" ) + || ( weapon.name == "helicopter_gunner_turret_secondary" ) + || ( weapon.name == "helicopter_gunner_turret_tertiary" ) + || ( weapon.name == "amws_gun_turret_mp_player" ) + || ( weapon.name == "auto_gun_turret" ) ) + return undefined; + } + + if ( weapon.name == "dart" ) + return undefined; + + if ( isdefined(eInflictor.killCamEnt) ) + { + // this is the case with the player helis + if ( eInflictor.killCamEnt == attacker ) + return undefined; + + return eInflictor.killCamEnt; + } + else if ( isdefined(eInflictor.killCamEntities) ) + { + return get_closest_killcam_entity( attacker, eInflictor.killCamEntities ); + } + + if ( isdefined( eInflictor.script_gameobjectname ) && eInflictor.script_gameobjectname == "bombzone" ) + return eInflictor.killCamEnt; + + return eInflictor; +} + +function get_secondary_killcam_entity(entity, entity_info ) +{ + if( !isdefined(entity) || !isdefined( entity.killcamentityindex ) ) + return; + + entity_info.entity_indexes[entity_info.entity_indexes.size] = entity.killcamentityindex; + entity_info.entity_spawntimes[entity_info.entity_spawntimes.size] = entity.killcamentitystarttime; +} + +function get_primary_killcam_entity(attacker, eInflictor, weapon, entity_info ) +{ + killcamentity = self get_killcam_entity( attacker, eInflictor, weapon ); + killcamentitystarttime = killcam::get_killcam_entity_start_time( killcamentity ); + killcamentityindex = -1; + + if ( isdefined( killcamentity ) ) + { + killcamentityindex = killcamentity getEntityNumber(); // must do this before any waiting lest the entity be deleted + } + + entity_info.entity_indexes[entity_info.entity_indexes.size] = killcamentityindex; + entity_info.entity_spawntimes[entity_info.entity_spawntimes.size] = killcamentitystarttime; + + get_secondary_killcam_entity( killcamentity, entity_info ); +} + +function get_killcam_entity_info(attacker, eInflictor, weapon) +{ + entity_info = SpawnStruct(); + + entity_info.entity_indexes = []; + entity_info.entity_spawntimes = []; + + get_primary_killcam_entity(attacker, eInflictor, weapon, entity_info); + + return entity_info; +} + +function get_killcam_entity_info_starttime( entity_info ) +{ + if ( entity_info.entity_spawntimes.size == 0 ) + return 0; + + // the last one should be the oldest + return entity_info.entity_spawntimes[entity_info.entity_spawntimes.size - 1]; +} diff --git a/mp/gametypes/_loadout.gsc b/mp/gametypes/_loadout.gsc new file mode 100644 index 0000000..6f43f47 --- /dev/null +++ b/mp/gametypes/_loadout.gsc @@ -0,0 +1,1572 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\dev_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\loadout_shared; +#using scripts\shared\system_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\util_shared; +#using scripts\shared\abilities\_ability_util; +#using scripts\shared\weapons\_weapon_utils; +#using scripts\shared\abilities\gadgets\_gadget_roulette; + + + + +#using scripts\mp\_armor; +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_dev; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_weapons; +#using scripts\mp\killstreaks\_killstreak_weapons; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\teams\_teams; + + + +#namespace loadout; + +function autoexec __init__sytem__() { system::register("loadout",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); + callback::on_connect( &on_connect ); + +} + +function on_connect() +{ +} + +function init() +{ + level.classMap["class_smg"] = "CLASS_SMG"; + level.classMap["class_cqb"] = "CLASS_CQB"; + level.classMap["class_assault"] = "CLASS_ASSAULT"; + level.classMap["class_lmg"] = "CLASS_LMG"; + level.classMap["class_sniper"] = "CLASS_SNIPER"; + + level.classMap["custom0"] = "CLASS_CUSTOM1"; + level.classMap["custom1"] = "CLASS_CUSTOM2"; + level.classMap["custom2"] = "CLASS_CUSTOM3"; + level.classMap["custom3"] = "CLASS_CUSTOM4"; + level.classMap["custom4"] = "CLASS_CUSTOM5"; + level.classMap["custom5"] = "CLASS_CUSTOM6"; + level.classMap["custom6"] = "CLASS_CUSTOM7"; + level.classMap["custom7"] = "CLASS_CUSTOM8"; + level.classMap["custom8"] = "CLASS_CUSTOM9"; + level.classMap["custom9"] = "CLASS_CUSTOM10"; + + level.maxKillstreaks = 4; + level.maxSpecialties = 6; + level.maxBonuscards = 3; + level.maxAllocation = GetGametypeSetting( "maxAllocation" ); + level.loadoutKillstreaksEnabled = GetGametypeSetting( "loadoutKillstreaksEnabled" ); + if( GetDvarInt( "teamOpsEnabled" ) == 1 ) + level.loadoutKillstreaksEnabled = 1; + + //override the base melee weapon from _weapons.gsc + level.weaponBaseMeleeHeld = GetWeapon( "bare_hands" ); + level.weaponKnifeLoadout = GetWeapon( "knife_loadout" ); + level.weaponMeleeKnuckles = GetWeapon( "melee_knuckles" ); + level.weaponMeleeButterfly = GetWeapon( "melee_butterfly" ); + level.weaponMeleeWrench = GetWeapon( "melee_wrench" ); + level.weaponMeleeSword = GetWeapon( "melee_sword" ); + level.weaponMeleeCrowbar = GetWeapon( "melee_crowbar" ); + level.weaponSpecialCrossbow = GetWeapon( "special_crossbow" ); + level.weaponMeleeDagger = GetWeapon( "melee_dagger" ); + level.weaponMeleeBat = GetWeapon( "melee_bat" ); + level.weaponMeleeBowie = GetWeapon( "melee_bowie" ); + level.weaponMeleeMace = GetWeapon( "melee_mace" ); + level.weaponMeleeFireAxe = GetWeapon( "melee_fireaxe" ); + level.weaponMeleeBoneGlass = GetWeapon( "melee_boneglass" ); + level.weaponMeleeImprovise = GetWeapon( "melee_improvise" ); + level.weaponShotgunEnergy = GetWeapon( "shotgun_energy" ); + level.weaponPistolEnergy = GetWeapon( "pistol_energy" ); + level.weaponMeleeShockBaton = GetWeapon( "melee_shockbaton" ); + level.weaponMeleeNunchuks = GetWeapon( "melee_nunchuks" ); + level.weaponMeleeBoxing = GetWeapon( "melee_boxing" ); + level.weaponMeleeKatana = GetWeapon( "melee_katana" ); + level.weaponMeleeShovel = GetWeapon( "melee_shovel" ); + level.weaponMeleeProsthetic = GetWeapon( "melee_prosthetic" ); + level.weaponMeleeChainsaw = GetWeapon( "melee_chainsaw" ); + level.weaponSpecialDiscGun = GetWeapon( "special_discgun" ); + level.weaponSmgNailGun = GetWeapon( "smg_nailgun" ); + level.weaponLauncherMulti = GetWeapon( "launcher_multi" ); + level.weaponMeleeCrescent = GetWeapon( "melee_crescent" ); + level.weaponLauncherEx41 = GetWeapon( "launcher_ex41" ); + + level.meleeWeapons = []; // intended for use with medals and challenges, namely "kill_enemy_with_their_weapon" + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponKnifeLoadout;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeKnuckles;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeButterfly;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeWrench;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeSword;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeCrowbar;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeDagger;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeBat;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeBowie;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeMace;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeFireAxe;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeBoneGlass;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeImprovise;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeShockBaton;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeNunchuks;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeBoxing;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeKatana;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeShovel;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeProsthetic;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeChainsaw;; + if ( !isdefined( level.meleeWeapons ) ) level.meleeWeapons = []; else if ( !IsArray( level.meleeWeapons ) ) level.meleeWeapons = array( level.meleeWeapons ); level.meleeWeapons[level.meleeWeapons.size]=level.weaponMeleeCrescent;; + + // placed here for easier integration + level.weaponBouncingBetty = GetWeapon( "bouncingbetty" ); + + level.PrestigeNumber = 5; + + level.defaultClass = "CLASS_ASSAULT"; + + if ( tweakables::getTweakableValue( "weapon", "allowfrag" ) ) + { + level.weapons["frag"] = GetWeapon( "frag_grenade" ); + } + else + { + level.weapons["frag"] = ""; + } + + if ( tweakables::getTweakableValue( "weapon", "allowsmoke" ) ) + { + level.weapons["smoke"] = GetWeapon( "smoke_grenade" ); + } + else + { + level.weapons["smoke"] = ""; + } + + if ( tweakables::getTweakableValue( "weapon", "allowflash" ) ) + { + level.weapons["flash"] = GetWeapon( "flash_grenade" ); + } + else + { + level.weapons["flash"] = ""; + } + + level.weapons["concussion"] = GetWeapon( "concussion_grenade" ); + + // SRS 11/28/2007: changed c4 to satchel + if ( tweakables::getTweakableValue( "weapon", "allowsatchel" ) ) + { + level.weapons["satchel_charge"] = GetWeapon( "satchel_charge" ); + } + else + { + level.weapons["satchel_charge"] = ""; + } + + if ( tweakables::getTweakableValue( "weapon", "allowbetty" ) ) + { + level.weapons["betty"] = GetWeapon( "mine_bouncing_betty" ); + } + else + { + level.weapons["betty"] = ""; + } + + if ( tweakables::getTweakableValue( "weapon", "allowrpgs" ) ) + { + level.weapons["rpg"] = GetWeapon( "rpg" ); + } + else + { + level.weapons["rpg"] = ""; + } + + create_class_exclusion_list(); + + // initializes create a class settings + cac_init(); + + load_default_loadout( "CLASS_SMG", 10 ); + load_default_loadout( "CLASS_CQB", 11 ); + load_default_loadout( "CLASS_ASSAULT", 12 ); + load_default_loadout( "CLASS_LMG", 13 ); + load_default_loadout( "CLASS_SNIPER", 14 ); + + // generating weapon type arrays which classifies the weapon as primary (back stow), pistol, or inventory (side pack stow) + // using mp/statstable.csv's weapon grouping data ( numbering 0 - 149 ) + level.primary_weapon_array = []; + level.side_arm_array = []; + level.grenade_array = []; + level.inventory_array = []; + max_weapon_num = 147; + for( i = 0; i < max_weapon_num; i++ ) + { + if( !isdefined( level.tbl_weaponIDs[i] ) || level.tbl_weaponIDs[i]["group"] == "" ) + { + continue; + } + if( !isdefined( level.tbl_weaponIDs[i] ) || level.tbl_weaponIDs[i]["reference"] == "" ) + { + continue; + } + + weapon_type = level.tbl_weaponIDs[i]["group"]; + weapon = level.tbl_weaponIDs[i]["reference"]; + attachment = level.tbl_weaponIDs[i]["attachment"]; + + weapon_class_register( weapon, weapon_type ); + + if( isdefined( attachment ) && attachment != "" ) + { + attachment_tokens = strtok( attachment, " " ); + if( isdefined( attachment_tokens ) ) + { + if( attachment_tokens.size == 0 ) + weapon_class_register( weapon+"_"+attachment, weapon_type ); + else + { + // multiple attachment options + for( k = 0; k < attachment_tokens.size; k++ ) + weapon_class_register( weapon+"_"+attachment_tokens[k], weapon_type ); + } + } + } + } + + callback::on_connecting( &on_player_connecting ); + callback::add_weapon_damage( level.weaponSpecialDiscGun, &on_damage_special_discgun ); + +} +function on_damage_special_discgun( eAttacker, eInflictor, weapon, meansOfDeath, damage ) +{ + if ( weapon != level.weaponSpecialDiscGun ) + { + return; + } + playsoundatposition ("wpn_disc_bounce_fatal", self.origin); +} + +function create_class_exclusion_list() +{ + currentDvar = 0; + + level.itemExclusions = []; + + while( GetDvarInt( "item_exclusion_" + currentDvar ) ) + { + level.itemExclusions[ currentDvar ] = GetDvarInt( "item_exclusion_" + currentDvar ); + currentDvar++; + } + + level.attachmentExclusions = []; + + currentDvar = 0; + while( GetDvarString( "attachment_exclusion_" + currentDvar ) !="" ) + { + level.attachmentExclusions[ currentDvar ] = GetDvarString( "attachment_exclusion_" + currentDvar ); + currentDvar++; + } + +} + +function is_attachment_excluded( attachment ) +{ + numExclusions = level.attachmentExclusions.size; + + for ( exclusionIndex = 0; exclusionIndex < numExclusions; exclusionIndex++ ) + { + if ( attachment == level.attachmentExclusions[ exclusionIndex ] ) + { + return true; + } + } + + return false; +} + +function set_statstable_id() +{ + if ( !isdefined( level.statsTableID ) ) + { + statsTableName = util::getStatsTableName(); + level.statsTableID = TableLookupFindCoreAsset( statsTableName ); + } +} + +function get_item_count( itemReference ) +{ + set_statstable_id(); + + itemCount = int( tableLookup( level.statsTableID, 4, itemReference, 5 ) ); + if ( itemCount < 1 ) + { + itemCount = 1; + } + + return itemCount; +} + +function getDefaultClassSlotWithExclusions( className, slotName ) +{ + itemReference = GetDefaultClassSlot( className, slotName ); + + set_statstable_id(); + + itemIndex = int( tableLookup( level.statsTableID, 4, itemReference, 0 ) ); + + if ( loadout::is_item_excluded( itemIndex ) ) + { + itemReference = tableLookup( level.statsTableID, 0, 0, 4 ); + } + + return itemReference; +} + +function load_default_loadout( weaponclass, classNum ) +{ + level.classToClassNum[ weaponclass ] = classNum; +} + +function weapon_class_register( weaponName, weapon_type ) +{ + if( isSubstr( "weapon_smg weapon_cqb weapon_assault weapon_lmg weapon_sniper weapon_shotgun weapon_launcher weapon_knife weapon_special", weapon_type ) ) + level.primary_weapon_array[GetWeapon( weaponName )] = 1; + else if( isSubstr( "weapon_pistol", weapon_type ) ) + level.side_arm_array[GetWeapon( weaponName )] = 1; + else if( weapon_type == "weapon_grenade" ) + level.grenade_array[GetWeapon( weaponName )] = 1; + else if( weapon_type == "weapon_explosive" ) + level.inventory_array[GetWeapon( weaponName )] = 1; + else if( weapon_type == "weapon_rifle" ) // COD5 WEAPON TEST + level.inventory_array[GetWeapon( weaponName )] = 1; + else if ( weapon_type != "hero" ) // just ignore 'hero' + assert( false, "Weapon group info is missing from statsTable for: " + weapon_type ); +} + +function hero_register_dialog( weapon ) +{ + readyVO = weapon.name + "_ready"; + + game["dialog"][readyVO] = readyVO; +} + +// create a class init +function cac_init() +{ + level.tbl_weaponIDs = []; + level.heroWeaponsTable = []; + + set_statstable_id(); + + for( i = 0; i < 256; i++ ) + { + itemRow = tableLookupRowNum( level.statsTableID, 0, i ); + + if ( itemRow > -1 ) + { + group_s = tableLookupColumnForRow( level.statsTableID, itemRow, 2 ); + + if ( isSubStr( group_s, "weapon_" ) || group_s == "hero" ) + { + reference_s = tableLookupColumnForRow( level.statsTableID, itemRow, 4 ); + if( reference_s != "" ) + { + weapon = GetWeapon( reference_s ); + + if ( weapon.inventoryType == "hero" ) + { + level.heroWeaponsTable[reference_s] = []; + level.heroWeaponsTable[reference_s]["index"] = i; + + hero_register_dialog( weapon ); + } + + level.tbl_weaponIDs[i]["reference"] = reference_s; + level.tbl_weaponIDs[i]["group"] = group_s; + level.tbl_weaponIDs[i]["count"] = int( tableLookupColumnForRow( level.statsTableID, itemRow, 5 ) ); + level.tbl_weaponIDs[i]["attachment"] = tableLookupColumnForRow( level.statsTableID, itemRow, 8 ); + } + } + } + } + + level.perkNames = []; + level.perkIcons = []; + level.perkSpecialties = []; + + // generating perk data vars collected form statsTable.csv + for( i = 0; i < 256; i++ ) + { + itemRow = tableLookupRowNum( level.statsTableID, 0, i ); + + if ( itemRow > -1 ) + { + group_s = tableLookupColumnForRow( level.statsTableID, itemRow, 2 ); + + if ( group_s == "specialty" ) + { + reference_s = tableLookupColumnForRow( level.statsTableID, itemRow, 4 ); + + if( reference_s != "" ) + { + perkIcon = tableLookupColumnForRow( level.statsTableID, itemRow, 6 ); + perkName = tableLookupIString( level.statsTableID, 0, i, 3 ); + + level.perkNames[ perkIcon ] = perkName; + + perk_name = tableLookupColumnForRow( level.statsTableID, itemRow, 3 ); + level.perkIcons[ perk_name ] = perkIcon; + level.perkSpecialties[ perk_name ] = reference_s; +/# + dev::add_perk_devgui( perkName, reference_s ); +#/ + } + } + } + } + + level.killStreakNames = []; + level.killStreakIcons = []; + level.KillStreakIndices = []; + + // generating kill streak data vars collected form statsTable.csv + for( i = 0; i < 256; i++ ) + { + itemRow = tableLookupRowNum( level.statsTableID, 0, i ); + + if ( itemRow > -1 ) + { + group_s = tableLookupColumnForRow( level.statsTableID, itemRow, 2 ); + + if ( group_s == "killstreak" ) + { + reference_s = tableLookupColumnForRow( level.statsTableID, itemRow, 4 ); + + if( reference_s != "" ) + { + level.tbl_KillStreakData[i] = reference_s; + level.killStreakIndices[ reference_s ] = i; + icon = tableLookupColumnForRow( level.statsTableID, itemRow, 6 ); + name = tableLookupIString( level.statsTableID, 0, i, 3 ); + + level.killStreakNames[ reference_s ] = name; + level.killStreakIcons[ reference_s ] = icon; + level.killStreakIndices[ reference_s ] = i; + } + } + } + } +} + +function getClassChoice( response ) +{ + assert( isdefined( level.classMap[ response ] ) ); + + return ( level.classMap[ response ] ); +} + + +// ============================================================================ + + +function getAttachmentString( weaponNum, attachmentNum ) +{ + attachmentString = GetItemAttachment( weaponNum, attachmentNum ); + + if ( attachmentString != "none" && ( !is_attachment_excluded( attachmentString ) ) ) + { + attachmentString = attachmentString + "_"; + } + else + { + attachmentString = ""; + } + + return attachmentString; +} + +function getAttachmentsDisabled() +{ + if ( !isdefined( level.attachmentsDisabled ) ) + { + return false; + } + + return level.attachmentsDisabled; + +} + +function getKillStreakIndex( weaponclass, killstreakNum ) +{ + killstreakNum++; + + killstreakString = "killstreak" + killstreakNum; + + // custom game mode killstreaks + if ( GetDvarInt( "custom_killstreak_mode" ) == 2 ) + { + return GetDvarInt( "custom_" + killstreakString ); + } + else + { + return( self GetLoadoutItem( weaponclass, killstreakString ) ); + } +} + +function giveKillstreaks() +{ + self.killstreak = []; + + if ( !level.loadoutKillstreaksEnabled ) + return; + + classNum = self.class_num_for_global_weapons; + + sortedKillstreaks = []; + currentKillstreak = 0; + + for ( killstreakNum = 0; killstreakNum < level.maxKillstreaks; killstreakNum++ ) + { + killstreakIndex = getKillStreakIndex( classNum, killstreakNum ); + + if ( isdefined( killstreakIndex ) && ( killstreakIndex > 0 ) ) + { + assert( isdefined( level.tbl_KillStreakData[ killstreakIndex ] ), "KillStreak #:" + killstreakIndex + "'s data is undefined" ); + + if ( isdefined( level.tbl_KillStreakData[ killstreakIndex ] ) ) + { + self.killstreak[ currentKillstreak ] = level.tbl_KillStreakData[ killstreakIndex ]; + if ( isdefined( level.usingMomentum ) && level.usingMomentum ) + { + killstreakType = killstreaks::get_by_menu_name( self.killstreak[ currentKillstreak ] ); + if ( isdefined( killstreakType ) ) + { + weapon = killstreaks::get_killstreak_weapon( killstreakType ); + +// /# println( "^5Loadout " + self.name + " GiveWeapon( " + weapon.name + " ) -- killstreak" ); #/ + + self GiveWeapon( weapon ); + + if ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks ) + { + if( weapon.isCarriedKillstreak ) + { + if( !isdefined( self.pers["held_killstreak_ammo_count"][weapon] ) ) + self.pers["held_killstreak_ammo_count"][weapon] = 0; + + if( !isDefined( self.pers["held_killstreak_clip_count"][weapon] ) ) + self.pers["held_killstreak_clip_count"][weapon] = 0; + + if( self.pers["held_killstreak_ammo_count"][weapon] > 0 ) + { + self setWeaponAmmoClip( weapon, self.pers["held_killstreak_clip_count"][weapon] ); + self setWeaponAmmoStock( weapon, self.pers["held_killstreak_ammo_count"][weapon] - self.pers["held_killstreak_clip_count"][weapon] ); + } + else + { + self setWeaponAmmoOverall( weapon, 0 ); + } + } + else + { + quantity = self.pers["killstreak_quantity"][weapon]; + if ( !isdefined( quantity ) ) + { + quantity = 0; + } + self setWeaponAmmoClip( weapon, quantity ); + + } + } + // Put killstreak in sorted order from lowest to highest momentum cost + sortData = spawnstruct(); + sortData.cost = level.killstreaks[killstreakType].momentumCost; + sortData.weapon = weapon; + sortIndex = 0; + for ( sortIndex = 0 ; sortIndex < sortedKillstreaks.size ; sortIndex++ ) + { + if ( sortedKillstreaks[sortIndex].cost > sortData.cost ) + break; + } + for ( i = sortedKillstreaks.size ; i > sortIndex ; i-- ) + { + sortedKillstreaks[i] = sortedKillstreaks[i-1]; + } + sortedKillstreaks[sortIndex] = sortData; + } + } + currentKillstreak++; + } + } + } + + actionSlotOrder = []; + actionSlotOrder[0] = 4; + actionSlotOrder[1] = 2; + actionSlotOrder[2] = 1; + // action slot 3 ( left ) is for alt weapons + if ( isdefined( level.usingMomentum ) && level.usingMomentum ) + { + for ( sortIndex = 0 ; (sortIndex < sortedKillstreaks.size && sortIndex < actionSlotOrder.size) ; sortIndex++ ) + { + if( sortedKillstreaks[sortIndex].weapon != level.weaponNone ) + self SetActionSlot( actionSlotOrder[sortIndex], "weapon", sortedKillstreaks[sortIndex].weapon ); + } + } +} + +function isPerkGroup( perkName ) +{ + return ( isdefined( perkName ) && IsString( perkName ) ); +} + +// clears all player's custom class variables, prepare for update with new stat data +function reset_specialty_slots( class_num ) +{ + self.specialty = []; // clear all specialties +} + +//------------------------------------------------------------------------------ +// self = player +//------------------------------------------------------------------------------ + +function initStaticWeaponsTime() +{ + self.staticWeaponsStartTime = getTime(); +} + +function isEquipmentAllowed( equipment_name ) +{ + if ( equipment_name == level.weapontacticalInsertion.name && level.disableTacInsert ) + return false; + + return true; +} + +function isLeagueItemRestricted( item ) +{ + if ( level.leagueMatch ) + { + return ( IsItemRestricted( item ) ); + } + + return false; +} + +function giveLoadoutLevelSpecific( team, weaponclass ) +{ + pixbeginevent("giveLoadoutLevelSpecific"); + + if ( isdefined( level.giveCustomCharacters ) ) + { + self [[level.giveCustomCharacters]](); + } + + if ( isdefined( level.giveCustomLoadout ) ) + { + self [[level.giveCustomLoadout]](); + } + + pixendevent(); +} + +function giveLoadout_init( takeAllWeapons ) +{ + if( takeAllWeapons ) + { + self takeAllWeapons(); + } + + // initialize specialty array + self.specialty = []; + self.killstreak = []; + self.primaryWeaponKill = false; + self.secondaryWeaponKill = false; + // reset offhand + self.grenadeTypePrimary = level.weaponNone; + self.grenadeTypePrimaryCount = 0; + self.grenadeTypeSecondary = level.weaponNone; + self.grenadeTypeSecondaryCount = 0; + + + self notify( "give_map" ); +} + +function givePerks() +{ + pixbeginevent("givePerks"); + + self.specialty = self GetLoadoutPerks( self.class_num ); + + + // SJC: set the player state to have bonus card and primary/secondary weapon info, for codcaster to view it + self SetPlayerStateLoadoutBonusCards( self.class_num ); + self SetPlayerStateLoadoutWeapons( self.class_num ); + + if ( level.leagueMatch ) + { + for ( i = 0; i < self.specialty.size; i++ ) + { + if ( isLeagueItemRestricted( self.specialty[i] ) ) + { + ArrayRemoveIndex( self.specialty, i ); + i--; + } + } + } + + // re-registering perks to code since perks are cleared after respawn in case if players switch classes + self register_perks(); + + // now that perks are re-registered... + // update player momentum taking into account anteup perk; any score less than the initial value is boosted to that value + anteup_bonus = GetDvarInt( "perk_killstreakAnteUpResetValue" ); + momentum_at_spawn_or_game_end = (isdefined(self.pers["momentum_at_spawn_or_game_end"])?self.pers["momentum_at_spawn_or_game_end"]:0); + hasNotDoneCombat = !( self.hasDoneCombat === true ); // fixes dev gui bot spawning in grace period + if ( ( level.inPreMatchPeriod || ( level.inGracePeriod && hasNotDoneCombat ) ) && momentum_at_spawn_or_game_end < anteup_bonus ) + { + new_momentum = ( self HasPerk( "specialty_anteup" ) ? anteup_bonus : momentum_at_spawn_or_game_end ); + globallogic_score::_setPlayerMomentum( self, new_momentum, false ); + } + + pixendevent(); // "givePerks" +} + +function setClassNum( weaponClass ) +{ + if( isSubstr( weaponclass, "CLASS_CUSTOM" ) ) + { + pixbeginevent("custom class"); + // ============= custom class selected ============== + // gets custom class data from stat bytes + // obtains the custom class number + self.class_num = int( weaponclass[weaponclass.size-1] )-1; + + //hacky patch to the system since when it was written it was never thought of that there could be 10 custom slots + if( -1 == self.class_num ) + { + self.class_num = 9; + } + + + self.class_num_for_global_weapons = self.class_num; + + // clear of specialty slots, repopulate the current selected class' setup + self reset_specialty_slots( self.class_num ); + + playerRenderOptions = self CalcPlayerOptions( self.class_num ); + self SetPlayerRenderOptions( playerRenderOptions ); + + pixendevent(); // "custom class" + } + else + { + pixbeginevent("default class"); + // ============= selected one of the default classes ============== + + // load the selected default class's specialties + assert( isdefined(self.pers["class"]), "Player during spawn and loadout got no class!" ); + + self.class_num = level.classToClassNum[ weaponclass ]; + self.class_num_for_global_weapons = 0; + + self SetPlayerRenderOptions( 0 ); + + pixendevent(); // "default class" + } + + self recordLoadoutIndex( self.class_num ); +} + +function giveBaseWeapon() +{ + //TODO - add melee weapon selection to the CAC stuff and default loadouts + self.spawnWeapon = level.weaponBaseMeleeHeld; + +// /# println( "^5Loadout " + self.name + " GiveWeapon( " + level.weaponBaseMelee.name + " ) -- level.weaponBaseMelee 0" ); #/ + knifeWeaponOptions = self CalcWeaponOptions( self.class_num, 2 ); + self GiveWeapon( level.weaponBaseMeleeHeld, knifeWeaponOptions ); + + self.pers["spawnWeapon"] = self.spawnWeapon; + + switchImmediate = isdefined( self.alreadySetSpawnWeaponOnce ); + self setSpawnWeapon( self.spawnWeapon, switchImmediate ); + self.alreadySetSpawnWeaponOnce = true; +} + +function giveWeapons() + { + pixbeginevent("giveWeapons"); + + spawnWeapon = level.weaponNull; + initialWeaponCount = 0; + + // weapon override for round based gametypes + // TODO: if they switched to a sidearm, we shouldn't give them that as their primary! + if ( isdefined( self.pers["weapon"] ) && self.pers["weapon"] != level.weaponNone && !self.pers["weapon"].isCarriedKillstreak ) + { + primaryWeapon = self.pers["weapon"]; + } + else + { + primaryWeapon = self GetLoadoutWeapon( self.class_num, "primary" ); + } + + if( primaryWeapon.isCarriedKillstreak ) + { + primaryWeapon = level.weaponNull; + } + + self.pers["primaryWeapon"] = primaryWeapon; + + // give primary weapon + if ( primaryWeapon != level.weaponNull ) + { + primaryWeaponOptions = self CalcWeaponOptions( self.class_num, 0 ); + +// /# println( "^5Loadout " + self.name + " GiveWeapon( " + primaryWeapon.name + " ) -- primary" ); #/ + + acvi = self GetAttachmentCosmeticVariantForWeapon( self.class_num, "primary" ); + self GiveWeapon( primaryWeapon, primaryWeaponOptions, acvi ); + + self weapons::bestweapon_spawn(primaryWeapon, primaryWeaponOptions, acvi); + + self.primaryLoadoutWeapon = primaryWeapon; + self.primaryLoadoutAltWeapon = primaryWeapon.altWeapon; + self.primaryLoadoutGunSmithVariantIndex = self GetLoadoutGunSmithVariantIndex( self.class_num, 0 ); + if( self HasPerk( "specialty_extraammo" ) ) + { + self giveMaxAmmo( primaryWeapon ); + } + + spawnWeapon = primaryWeapon; + initialWeaponCount++; + } + + // give seconday weapon + sidearm = self GetLoadoutWeapon( self.class_num, "secondary" ); + + if( sidearm.isCarriedKillstreak ) + sidearm = level.weaponNull; + + // CO-253: Anti-cheat - Add Black Market items and Zombies Bowie Knife to the CAC validation + // Although the exploit is handled UI-side, this ensures that anyone who already equipped the bowie knife somehow doesn't get to use it. + if( sidearm.name == "bowie_knife" ) + sidearm = level.weaponNull; + + if ( sidearm != level.weaponNull ) + { + secondaryWeaponOptions = self CalcWeaponOptions( self.class_num, 1 ); + +// /# println( "^5Loadout " + self.name + " GiveWeapon( " + sidearm.name + " ) -- sidearm" ); #/ + + acvi = self GetAttachmentCosmeticVariantForWeapon( self.class_num, "secondary" ); + self GiveWeapon( sidearm, secondaryWeaponOptions, acvi ); + self.secondaryLoadoutWeapon = sidearm; + self.secondaryLoadoutAltWeapon = sidearm.altWeapon; + self.secondaryLoadoutGunSmithVariantIndex = self GetLoadoutGunSmithVariantIndex( self.class_num, 1 ); + + if ( self HasPerk( "specialty_extraammo" ) ) + { + self giveMaxAmmo( sidearm ); + } + + if ( spawnWeapon == level.weaponNull ) + { + spawnWeapon = sidearm; + } + + initialWeaponCount++; + } + + if ( !self HasMaxPrimaryWeapons() ) + { + if ( !isUsingT7Melee() ) + { +// /# println( "^5Loadout " + self.name + " GiveWeapon( " + level.weaponBaseMeleeHeld.name + " ) -- level.weaponBaseMeleeHeld 1" ); #/ + knifeWeaponOptions = self CalcWeaponOptions( self.class_num, 2 ); + self GiveWeapon( level.weaponBaseMeleeHeld, knifeWeaponOptions ); + } + + if ( initialWeaponCount == 0 ) + { + spawnWeapon = level.weaponBaseMeleeHeld; + } + } + + if ( !isdefined( self.spawnWeapon ) && isdefined( self.pers["spawnWeapon"] ) ) + { + self.spawnWeapon = self.pers["spawnWeapon"]; + } + + if ( isdefined( self.spawnWeapon ) && DoesWeaponReplaceSpawnWeapon( self.spawnWeapon, spawnWeapon ) && !self.pers["changed_class"] ) + { + spawnWeapon = self.spawnWeapon; + } + + self thread loadout::initWeaponAttachments( spawnWeapon ); + + self.pers["changed_class"] = false; + self.spawnWeapon = spawnWeapon; + self.pers["spawnWeapon"] = self.spawnWeapon; + + switchImmediate = isdefined( self.alreadySetSpawnWeaponOnce ); + self setSpawnWeapon( spawnWeapon, switchImmediate ); + self.alreadySetSpawnWeaponOnce = true; + + self initStaticWeaponsTime(); + + self bbClassChoice( self.class_num, primaryWeapon, sidearm ); + + pixendevent(); // "giveWeapons" +} + +function givePrimaryOffhand() +{ + pixbeginevent("givePrimaryOffhand"); + + changedClass = self.pers["changed_class"]; + roundBased = !util::isOneRound(); + firstRound = util::isFirstRound(); + + primaryOffhand = level.weaponNone; + primaryOffhandCount = 0; + + if ( GetDvarint( "gadgetEnabled") == 1 || GetDvarint( "equipmentAsGadgets") == 1 ) + { + primaryOffhand = self GetLoadoutWeapon( self.class_num, "primaryGadget" ); + primaryOffhandCount = primaryOffhand.startammo; + } + else + { + primaryOffhandName = self GetLoadoutItemRef( self.class_num, "primarygrenade" ); + + if ( primaryOffhandName != "" && primaryOffhandName != "weapon_null" ) + { + primaryOffhand = GetWeapon( primaryOffhand ); + primaryOffhandCount = self GetLoadoutItem( self.class_num, "primarygrenadecount" ); + } + } + + if ( isLeagueItemRestricted( primaryOffhand.name ) || !isEquipmentAllowed( primaryOffhand.name ) ) + { + primaryOffhand = level.weaponNone; + primaryOffhandCount = 0; + } + + if ( primaryOffhand == level.weaponNone ) + { + primaryOffhand = GetWeapon( "null_offhand_primary" ); + primaryOffhandCount = 0; + } + + if ( primaryOffhand != level.weaponNull ) + { + // /# println( "^5Loadout " + self.name + " GiveWeapon( " + primaryOffhand.name + " ) -- primaryOffhand" ); #/ + + self GiveWeapon( primaryOffhand ); + + self SetWeaponAmmoClip( primaryOffhand, primaryOffhandCount ); + self SwitchToOffhand( primaryOffhand ); + self.grenadeTypePrimary = primaryOffhand; + self.grenadeTypePrimaryCount = primaryOffhandCount; + + self ability_util::gadget_reset( primaryOffhand, changedClass, roundBased, firstRound ); + } + + pixendevent(); // "givePrimaryOffhand" +} + +function giveSecondaryOffhand( ) +{ + pixbeginevent("giveSecondaryOffhand"); + + changedClass = self.pers["changed_class"]; + roundBased = !util::isOneRound(); + firstRound = util::isFirstRound(); + + secondaryOffhand = level.weaponNone; + secondaryOffhandCount = 0; + + if ( GetDvarint( "gadgetEnabled") == 1 || GetDvarint( "equipmentAsGadgets") == 1 ) + { + secondaryOffhand = self GetLoadoutWeapon( self.class_num, "secondaryGadget" ); + secondaryOffhandCount = secondaryOffhand.startammo; + } + else + { + secondaryOffhandName = self GetLoadoutItemRef( self.class_num, "specialgrenade" ); + + if ( secondaryOffhandName != "" && secondaryOffhandName != "weapon_null" ) + { + secondaryOffhand = GetWeapon( secondaryOffhand ); + secondaryOffhandCount = self GetLoadoutItem( self.class_num, "specialgrenadecount" ); + } + } + + if ( isLeagueItemRestricted( secondaryOffhand.name ) || !isEquipmentAllowed( secondaryOffhand.name ) ) + { + secondaryOffhand = level.weaponNone; + secondaryOffhandCount = 0; + } + + if ( secondaryOffhand == level.weaponNone ) + { + secondaryOffhand = GetWeapon( "null_offhand_secondary" ); + secondaryOffhandCount = 0; + } + + if ( secondaryOffhand != level.weaponNull ) + { +// /# println( "^5Loadout " + self.name + " GiveWeapon( " + secondaryOffhand.name + " ) -- secondaryOffhand" ); #/ + + self GiveWeapon( secondaryOffhand ); + + self SetWeaponAmmoClip( secondaryOffhand, secondaryOffhandCount ); + self SwitchToOffhand( secondaryOffhand ); + self.grenadeTypeSecondary = secondaryOffhand; + self.grenadeTypeSecondaryCount = secondaryOffhandCount; + + self ability_util::gadget_reset( secondaryOffhand, changedClass, roundBased, firstRound ); + } + + pixendevent(); // "giveSecondaryOffhand" +} + +function giveSpecialOffhand() +{ + pixbeginevent("giveSpecialOffhand"); + + changedClass = self.pers["changed_class"]; + roundBased = !util::isOneRound(); + firstRound = util::isFirstRound(); + + classNum = self.class_num_for_global_weapons; + + specialOffhand = level.weaponNone; + specialOffhandCount = 0; + + specialOffhand = self GetLoadoutWeapon( self.class_num_for_global_weapons, "herogadget" ); + specialOffhandCount = specialOffhand.startammo; + + /# + if ( GetDvarString( "scr_heroGadgetName_debug" ) != "" ) + { + heroGagdetName = GetDvarString( "scr_heroGadgetName_debug" ); + + specialOffhand = level.weaponNone; + if ( heroGagdetName != "weapon_null" ) + { + specialOffhand = GetWeapon( heroGagdetName ); + } + + } + #/ + + if ( isdefined( self.pers[#"rouletteWeapon"] ) ) + { + assert( specialOffhand.name == "gadget_roulette" ); + specialOffhand = self.pers[#"rouletteWeapon"]; + roulette::gadget_roulette_give_earned_specialist( specialOffhand, false ) ; + } + + if ( isLeagueItemRestricted( specialOffhand.name ) || !isEquipmentAllowed( specialOffhand.name ) ) + { + specialOffhand = level.weaponNone; + specialOffhandCount = 0; + } + + if ( specialOffhand == level.weaponNone ) + { + specialOffhand = level.weaponNull; + specialOffhandCount = 0; + } + + if ( specialOffhand != level.weaponNull ) + { +// /# println( "^5Loadout " + self.name + " GiveWeapon( " + specialOffhand.name + " ) -- specialOffhand" ); #/ + + self GiveWeapon( specialOffhand ); + + self SetWeaponAmmoClip( specialOffhand, specialOffhandCount ); + self SwitchToOffhand( specialOffhand ); + self.grenadeTypeSpecial = specialOffhand; + self.grenadeTypeSpecialCount = specialOffhandCount; + + self ability_util::gadget_reset( specialOffhand, changedClass, roundBased, firstRound ); + } + + pixendevent(); // "giveSpecialOffhand" +} + +function giveHeroWeapon() +{ + pixbeginevent("giveHeroWeapon"); + + changedClass = self.pers["changed_class"]; + roundBased = !util::isOneRound(); + firstRound = util::isFirstRound(); + + classNum = self.class_num_for_global_weapons; + heroWeapon = level.weaponNone; + heroWeaponName = self GetLoadoutItemRef( self.class_num_for_global_weapons, "heroWeapon" ); + + /# + if ( GetDvarString( "scr_heroWeaponName_debug" ) != "" ) + { + heroWeaponName = GetDvarString( "scr_heroWeaponName_debug" ); + } + #/ + + if ( heroWeaponName != "" && heroWeaponName != "weapon_null" ) + { + if ( heroWeaponName == "hero_minigun" ) + { + model = self GetCharacterBodyModel(); + if ( IsDefined( model ) && IsSubStr(model, "body3") ) + { + heroWeaponName = "hero_minigun_body3"; + } + } + + heroWeapon = GetWeapon( heroWeaponName ); + } + + if ( heroWeapon != level.weaponNone ) + { +// /# println( "^5Loadout " + self.name + " GiveWeapon( " + heroWeapon.name + " ) -- heroWeapon" ); #/ + + self.heroWeapon = heroWeapon; + self GiveWeapon( heroWeapon ); + + self ability_util::gadget_reset( heroWeapon, changedClass, roundBased, firstRound ); + } + + pixendevent(); // "giveHeroWeapon" +} + +function giveLoadout( team, weaponclass ) +{ + pixbeginevent("giveLoadout"); + + if ( isdefined( level.giveCustomLoadout ) ) + { + spawnWeapon = self [[level.giveCustomLoadout]](); + if( isdefined( spawnWeapon ) ) + { + self thread loadout::initWeaponAttachments( spawnWeapon ); + } + self.spawnWeapon = spawnWeapon; + } + else + { + self giveLoadout_init( true ); + + setClassNum( weaponClass ); + + self SetActionSlot( 3, "altMode" ); + self SetActionSlot( 4, "" ); + + allocationSpent = self GetLoadoutAllocation( self.class_num ); + overAllocation = ( allocationSpent > level.maxAllocation ); + + if( !overAllocation ) + { + //Perks must come first in case other give-functions check for perks + givePerks(); + + giveWeapons(); + givePrimaryOffhand(); + } + else + { + giveBaseWeapon(); + } + + giveSecondaryOffhand(); + + if ( GetDvarint( "tu11_enableClassicMode") == 0 ) + { + giveSpecialOffhand(); + giveHeroWeapon(); + } + + giveKillStreaks(); + } + + self teams::set_player_model( undefined, undefined ); + + if( isdefined( self.movementSpeedModifier ) ) + { + self setMoveSpeedScale( self.movementSpeedModifier * self getMoveSpeedScale() ); + } + + // cac specialties that require loop threads + self cac_selector(); + + self giveLoadout_finalize( self.spawnWeapon, self.pers["primaryWeapon"] ); + + pixendevent(); // "giveLoadout" +} + +function giveLoadout_finalize(spawnWeapon, primaryWeapon) +{ + // tagTMR: force first raise anim on initial spawn of match + if ( !isdefined( self.firstSpawn ) ) + { + if ( isdefined( spawnWeapon ) ) + self InitialWeaponRaise( spawnWeapon ); + else + self InitialWeaponRaise( primaryWeapon ); + } + else + { + // ... and eliminate first raise anim for all other spawns + self SetEverHadWeaponAll( true ); + } + + self.firstSpawn = false; + self.switchedTeamsResetGadgets = false; + + self flagsys::set( "loadout_given" ); +} + +// sets the amount of ammo in the gun. +// if the clip maxs out, the rest goes into the stock. +function setWeaponAmmoOverall( weapon, amount ) +{ + if ( weapon.isClipOnly ) + { + self setWeaponAmmoClip( weapon, amount ); + } + else + { + self setWeaponAmmoClip( weapon, amount ); + diff = amount - self getWeaponAmmoClip( weapon ); + assert( diff >= 0 ); + self setWeaponAmmoStock( weapon, diff ); + } +} + +function on_player_connecting() +{ + if ( !isdefined( self.pers["class"] ) ) + { + self.pers["class"] = ""; + } + self.curClass = self.pers["class"]; + self.lastClass = ""; + + self.detectExplosives = false; + self.bombSquadIcons = []; + self.bombSquadIds = []; + self.reviveIcons = []; + self.reviveIds = []; +} + + +function fadeAway( waitDelay, fadeDelay ) +{ + wait waitDelay; + + self fadeOverTime( fadeDelay ); + self.alpha = 0; +} + + +function setClass( newClass ) +{ + self.curClass = newClass; +} + +// ============================================================================================ +// ======= ======= +// ======= Create a Class Specialties ======= +// ======= ======= +// ============================================================================================ + +function initPerkDvars() +{ + level.cac_armorpiercing_data = GetDvarInt( "perk_armorpiercing", 40 ) / 100;// increased bullet damage by this % + level.cac_bulletdamage_data = GetDvarInt( "perk_bulletDamage", 35 ); // increased bullet damage by this % + level.cac_fireproof_data = GetDvarInt( "perk_fireproof", 20 ); // reduced flame damage by this % + level.cac_armorvest_data = GetDvarInt( "perk_armorVest", 80 ); // multipy damage by this % + level.cac_flakjacket_data = GetDvarInt( "perk_flakJacket", 35 ); // explosive damage is this % of original + level.cac_flakjacket_hardcore_data = GetDvarInt( "perk_flakJacket_hardcore", 9 ); // explosive damage is this % of original for hardcore +} + +// CAC: Selector function, calls the individual cac features according to player's class settings +// Info: Called every time player spawns during loadout stage +function cac_selector() +{ + + self.detectExplosives = false; + + + if ( IsDefined( self.specialty ) ) + { + perks = self.specialty; + for( i=0; i " + attacker.name + "'s bullet damage did extra damage to vehicle" ); + #/ + } + else + { + final_damage = old_damage; + } + + // debug + /# + if ( GetDvarint( "scr_perkdebug") ) + println( "Perk/> Damage Factor: " + final_damage/old_damage + " - Pre Damage: " + old_damage + " - Post Damage: " + final_damage ); + #/ + + // return unchanged damage + return int( final_damage ); +} + +function cac_modified_damage( victim, attacker, damage, mod, weapon, inflictor, hitloc ) +{ + assert( isdefined( victim ) ); + assert( isdefined( attacker ) ); + assert( IsPlayer( victim ) ); + + attacker_is_player = IsPlayer( attacker ); + + if ( damage <= 0 ) + { + return damage; + } + +/# + debug = false; + + if ( GetDvarint( "scr_perkdebug" ) ) + { + debug = true; + + if ( !isdefined( attacker.name ) ) + attacker.name = "unknown"; + } +#/ + + final_damage = damage; + + if ( victim != attacker ) + { + if ( attacker_is_player && attacker HasPerk( "specialty_bulletdamage" ) && isPrimaryDamage( mod ) ) + { + // if victim has armor then do not change damage, it is cancelled out, else damage is increased + if( victim HasPerk( "specialty_armorvest" ) && !isHeadDamage( hitloc ) ) + { + /# + if ( debug ) + { + println( "Perk/> " + victim.name + "'s armor countered " + attacker.name + "'s increased bullet damage" ); + } + #/ + } + else + { + final_damage = damage * ( 100 + level.cac_bulletdamage_data ) / 100; + + /# + if ( debug ) + { + println( "Perk/> " + attacker.name + "'s bullet damage did extra damage to " + victim.name ); + } + #/ + } + } + else if ( victim HasPerk( "specialty_armorvest" ) && isPrimaryDamage( mod ) && !isHeadDamage( hitloc ) ) + { + //If victim has body armor, reduce the damage by the cac armor vest value as a percentage + final_damage = damage * ( level.cac_armorvest_data * .01 ); + + /# + if ( debug ) + { + println( "Perk/> " + attacker.name + "'s bullet damage did less damage to " + victim.name ); + } + #/ + } + else if ( victim HasPerk( "specialty_fireproof" ) && isFireDamage( weapon, mod ) ) + { + final_damage = damage * ( level.cac_fireproof_data * .01 ); + + /# + if ( debug ) + { + println( "Perk/> " + attacker.name + "'s flames did less damage to " + victim.name ); + } + #/ + } + else if ( victim HasPerk( "specialty_flakjacket" ) && isExplosiveDamage( mod ) && !weapon.ignoresFlakJacket && !victim grenadeStuck( inflictor ) ) + { + // put these back in to make FlakJacket a perk again (for easy tuning of system when not body type specfic) + cac_data = ( level.hardcoreMode ? level.cac_flakjacket_hardcore_data : level.cac_flakjacket_data ); + + if ( victim util::has_flak_jacket_perk_purchased_and_equipped() ) + { + if ( level.teambased && attacker.team != victim.team ) + { + victim thread challenges::flakjacketProtectedMP( weapon, attacker ); + } + else if ( attacker != victim ) + { + victim thread challenges::flakjacketProtectedMP( weapon, attacker ); + } + } + + final_damage = int( damage * ( cac_data / 100 ) ); + + /# + if ( debug ) + { + println( "Perk/> " + victim.name + "'s flak jacket decreased " + attacker.name + "'s grenade damage" ); + } + #/ + } + } + +/# + victim.cac_debug_damage_type = tolower( mod ); + victim.cac_debug_original_damage = damage; + victim.cac_debug_final_damage = final_damage; + victim.cac_debug_location = tolower( hitloc ); + victim.cac_debug_weapon = tolower( weapon.name ); + victim.cac_debug_range = int( Distance( attacker.origin, victim.origin ) ); + + if ( debug ) + { + println( "Perk/> Damage Factor: " + final_damage / damage + " - Pre Damage: " + damage + " - Post Damage: " + final_damage ); + } +#/ + + final_damage = int( final_damage ); + + if ( final_damage < 1 ) + { + final_damage = 1; + } + + return ( final_damage ); +} + +// including grenade launcher, grenade, bazooka, betty, satchel charge +function isExplosiveDamage( meansofdeath ) +{ + switch( meansofdeath ) + { + case "MOD_GRENADE": + case "MOD_GRENADE_SPLASH": + case "MOD_PROJECTILE": + case "MOD_PROJECTILE_SPLASH": + case "MOD_EXPLOSIVE": + return true; + } + + return false; +} + +function hasTacticalMask( player ) +{ + return ( player HasPerk( "specialty_stunprotection" ) || player HasPerk( "specialty_flashprotection" ) || player HasPerk( "specialty_proximityprotection" ) ); +} + +// if primary weapon damage +function isPrimaryDamage( meansofdeath ) +{ + return( meansofdeath == "MOD_RIFLE_BULLET" || meansofdeath == "MOD_PISTOL_BULLET" ); +} + +function isBulletDamage( meansofdeath ) +{ + return( meansofdeath == "MOD_RIFLE_BULLET" || meansofdeath == "MOD_PISTOL_BULLET" || meansofdeath == "MOD_HEAD_SHOT" ); +} + +function isFMJDamage( sWeapon, sMeansOfDeath, attacker ) +{ + // The Bullet Penetration perk comes from the fmj attachment in the attachmentTable.csv or from the weapon in statstable.csv + return IsDefined( attacker ) && IsPlayer( attacker ) && attacker HasPerk( "specialty_armorpiercing" ) && IsDefined( sMeansOfDeath ) && isBulletDamage( sMeansOfDeath ); +} + +function isFireDamage( weapon, meansofdeath ) +{ + if ( weapon.doesFireDamage && (meansofdeath == "MOD_BURNED" || meansofdeath == "MOD_GRENADE" || meansofdeath == "MOD_GRENADE_SPLASH") ) + return true; + + return false; +} + +function isHeadDamage( hitloc ) +{ + return ( hitloc == "helmet" || hitloc == "head" || hitloc == "neck" ); +} + +function grenadeStuck( inflictor ) +{ + return ( isdefined( inflictor ) && isdefined( inflictor.stucktoplayer ) && inflictor.stucktoplayer == self ); +} diff --git a/mp/gametypes/_menus.gsc b/mp/gametypes/_menus.gsc new file mode 100644 index 0000000..a26a709 --- /dev/null +++ b/mp/gametypes/_menus.gsc @@ -0,0 +1,197 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\system_shared; + + + +#using scripts\mp\gametypes\_globallogic; + +#using scripts\mp\_util; + +#precache( "menu", "ChangeTeam" ); +#precache( "menu", "class" ); +#precache( "menu", "ChooseClass_InGame" ); +#precache( "menu", "ingame_controls" ); +#precache( "menu", "ingame_options" ); +#precache( "menu", "popup_leavegame" ); +#precache( "menu", "spectate" ); +#precache( "string", "MP_HOST_ENDED_GAME" ); +#precache( "string", "MP_HOST_ENDGAME_RESPONSE" ); +#precache( "eventstring", "open_ingame_menu" ); + +#namespace menus; + +function autoexec __init__sytem__() { system::register("menus",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); + callback::on_connect( &on_player_connect ); +} + +function init() +{ + game["menu_start_menu"] = "StartMenu_Main"; + game["menu_team"] = "ChangeTeam"; + game["menu_class"] = "class"; + game["menu_changeclass"] = "ChooseClass_InGame"; + game["menu_changeclass_offline"] = "ChooseClass_InGame"; + + foreach( team in level.teams ) + { + game["menu_changeclass_" + team ] = "ChooseClass_InGame"; + } + + game["menu_controls"] = "ingame_controls"; + game["menu_options"] = "ingame_options"; + game["menu_leavegame"] = "popup_leavegame"; +} + +function on_player_connect() +{ + self thread on_menu_response(); +} + +function on_menu_response() +{ + self endon("disconnect"); + + for(;;) + { + self waittill("menuresponse", menu, response); + + //println( self getEntityNumber() + " menuresponse: " + menu + " " + response ); + + //iprintln("^6", response); + + if ( response == "back" ) + { + self closeInGameMenu(); + + if ( level.console ) + { + if( menu == game["menu_changeclass"] || menu == game["menu_changeclass_offline"] || menu == game["menu_team"] || menu == game["menu_controls"] ) + { +// assert( isdefined( level.teams[self.pers["team"]] ) ); + + if( isdefined( level.teams[self.pers["team"]] ) ) + self openMenu( game[ "menu_start_menu" ] ); + } + } + continue; + } + + if(response == "changeteam" && level.allow_teamchange == "1") + { + self closeInGameMenu(); + self openMenu(game["menu_team"]); + } + + if(response == "endgame") + { + // TODO: replace with onSomethingEvent call + if(level.splitscreen) + { + //if ( level.console ) + // endparty(); + level.skipVote = true; + + if ( !level.gameEnded ) + { + level thread globallogic::forceEnd(); + } + } + + continue; + } + + if(response == "killserverpc") + { + level thread globallogic::killserverPc(); + + continue; + } + + if ( response == "endround" ) + { + if ( !level.gameEnded ) + { + self globallogic::gameHistoryPlayerQuit(); + level thread globallogic::forceEnd(); + } + else + { + self closeInGameMenu(); + self iprintln( &"MP_HOST_ENDGAME_RESPONSE" ); + } + continue; + } + + if(menu == game["menu_team"] && level.allow_teamchange == "1") + { + switch(response) + { + case "autoassign": + self [[level.autoassign]]( true ); + break; + + case "spectator": + self [[level.spectator]](); + break; + + default: + self [[level.teamMenu]](response); + break; + } + } // the only responses remain are change class events + else if( menu == game["menu_changeclass"] || menu == game["menu_changeclass_offline"] ) + { + if ( response != "cancel" ) + { + self closeInGameMenu(); + + if( level.rankedMatch && isSubstr(response, "custom") ) + { + if ( self IsItemLocked( rank::GetItemIndex( "feature_cac" ) ) ) + kick( self getEntityNumber() ); + } + + self.selectedClass = true; + self [[level.curClass]](response); + } + } + else if ( menu == "spectate" ) + { + player = util::getPlayerFromClientNum( int( response ) ); + if ( isdefined ( player ) ) + { + self SetCurrentSpectatorClient( player ); + } + } + } +} + +/* +// sort response message from CAC menu +function cacMenuStatOffset( menu, response ) +{ + stat_offset = -1; + + if( menu == "menu_cac_assault" ) + stat_offset = 0; + else if( menu == "menu_cac_specops" ) + stat_offset = 10; + else if( menu == "menu_cac_heavygunner" ) + stat_offset = 20; + else if( menu == "menu_cac_demolitions" ) + stat_offset = 30; + else if( menu == "menu_cac_sniper" ) + stat_offset = 40; + + assert( stat_offset >= 0, "The response: " + response + " came from non-CAC menu" ); + + return stat_offset; +} +*/ diff --git a/mp/gametypes/_perplayer.gsc b/mp/gametypes/_perplayer.gsc new file mode 100644 index 0000000..1da8980 --- /dev/null +++ b/mp/gametypes/_perplayer.gsc @@ -0,0 +1,214 @@ +#using scripts\codescripts\struct; + + + +#precache( "material", "objpoint_default" ); + +#namespace perplayer; + +// calls a callback every time a player starts playing (spawns), +// and another callback every time they stop (die / switch teams / disconnect). +// handles the annoying logic of players joining and leaving the game, +// so that other scripts don't have to. + +/* Usage example: + +// init() must be called during loading, or players already connected won't be considered! +function objectiveCreator = perplayer::init("objective_creator",&showObjective,&hideObjective); + +... + +// calls showObjective for all players in game +// (this can safely be called immediately after init(), if desired) +function perplayer::enable(objectiveCreator); + +... // during this time, showObjective and hideObjective will be called for players as they join and leave gameplay + +// calls hideObjective for all players in game +function perplayer::disable(objectiveCreator); + + +// the function playerBeginCallback takes no arguments. self = the player. +// the function playerEndCallback takes one argument, a boolean which says whether the player has disconnected. +// if the player has disconnected, it is not safe to change properties of the player. + +*/ + +// set id to be some unique string indicating the purpose of the callbacks. +function init(id, playerBeginCallback, playerEndCallback) +{ + handler = spawnstruct(); + handler.id = id; + handler.playerBeginCallback = playerBeginCallback; + handler.playerEndCallback = playerEndCallback; + handler.enabled = false; + handler.players = []; + + thread onPlayerConnect(handler); + + level.handlerGlobalFlagVal = 0; + + return handler; +} + +function enable(handler) +{ + if (handler.enabled) + return; + handler.enabled = true; + + level.handlerGlobalFlagVal++; + // mark all players with the global flag value; if a player isn't marked later on, we know they're not in the game. + // this could happen if they disconnected, causing this function to be called from elsewhere in script, but we haven't + // yet recieved the disconnect notify so they're still in the handler.players array. + players = GetPlayers(); + for (i = 0; i < players.size; i++) + players[i].handlerFlagVal = level.handlerGlobalFlagVal; + + // handle all players who are ready to be handled + players = handler.players; + + for (i = 0; i < players.size; i++) { + if (players[i].handlerFlagVal != level.handlerGlobalFlagVal) + continue; + + if (players[i].handlers[handler.id].ready) + players[i] handlePlayer(handler); + } +} +function disable(handler) +{ + if (!handler.enabled) + return; + handler.enabled = false; + + level.handlerGlobalFlagVal++; + // mark all players with the global flag value; if a player isn't marked later on, we know they're not in the game. + // this could happen if they disconnected, causing this function to be called from elsewhere in script, but we haven't + // yet recieved the disconnect notify so they're still in the handler.players array. + players = GetPlayers(); + for (i = 0; i < players.size; i++) + players[i].handlerFlagVal = level.handlerGlobalFlagVal; + + // unhandle all players who are being handled + players = handler.players; + + for (i = 0; i < players.size; i++) { + if (players[i].handlerFlagVal != level.handlerGlobalFlagVal) + continue; + + if (players[i].handlers[handler.id].ready) + players[i] unHandlePlayer(handler, false, false); // first false means don't set ready to false + } +} + +function onPlayerConnect(handler) +{ + for(;;) + { + level waittill("connecting", player); + + if (!isdefined(player.handlers)) + player.handlers = []; + player.handlers[handler.id] = spawnstruct(); + player.handlers[handler.id].ready = false; + player.handlers[handler.id].handled = false; + player.handlerFlagVal = -1; + + handler.players[handler.players.size] = player; + + player thread onPlayerDisconnect(handler); + + player thread onPlayerSpawned(handler); + player thread onJoinedTeam(handler); + player thread onJoinedSpectators(handler); + player thread onPlayerKilled(handler); + } +} +function onPlayerDisconnect(handler) +{ + self waittill("disconnect"); + + newplayers = []; + for (i = 0; i < handler.players.size; i++) + if (handler.players[i] != self) + newplayers[newplayers.size] = handler.players[i]; + handler.players = newplayers; + + self thread unHandlePlayer(handler, true, true); +} + +function onJoinedTeam(handler) +{ + self endon("disconnect"); + + for(;;) + { + self waittill("joined_team"); + self thread unHandlePlayer(handler, true, false); + } +} +function onJoinedSpectators(handler) +{ + self endon("disconnect"); + + for(;;) + { + self waittill("joined_spectators"); + self thread unHandlePlayer(handler, true, false); + } +} +function onPlayerSpawned(handler) +{ + self endon("disconnect"); + + for(;;) + { + self waittill("spawned_player"); + self thread handlePlayer(handler); + } +} +function onPlayerKilled(handler) +{ + self endon("disconnect"); + + for(;;) + { + self waittill("killed_player"); + self thread unHandlePlayer(handler, true, false); + } +} + +// ["_perplayer handler " + handler.id + " ready"] is set to true +// if this player would be handled, even if the handler is disabled. +// ["_perplayer handler " + handler.id + " handled"] is only set to true +// when the player is being handled *and* the handler is enabled +function handlePlayer(handler) +{ + self.handlers[handler.id].ready = true; + + if (!handler.enabled) + return; + + // if player already handled, stop + if (self.handlers[handler.id].handled) + return; + self.handlers[handler.id].handled = true; + + self thread [[handler.playerBeginCallback]](); +} +function unHandlePlayer(handler, unsetready, disconnected) +{ + if (!disconnected && unsetready) + self.handlers[handler.id].ready = false; + + + + // if player not handled, stop + if (!self.handlers[handler.id].handled) + return; + if (!disconnected) + self.handlers[handler.id].handled = false; + + self thread [[handler.playerEndCallback]](disconnected); +} \ No newline at end of file diff --git a/mp/gametypes/_prop_controls.gsc b/mp/gametypes/_prop_controls.gsc new file mode 100644 index 0000000..09b17a3 --- /dev/null +++ b/mp/gametypes/_prop_controls.gsc @@ -0,0 +1,1946 @@ +#using scripts\mp\_teamops; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_dogtags; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_prop_dev; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\prop; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\shared\clientfield_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\fx_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\util_shared; + + + + +#namespace prop_controls; + +/* + Prop Hunt +*/ + +function NotifyOnPlayerCommand( command, key ) +{ + assert( isPlayer( self ) ); + self thread NotifyOnPlayerCommandInternal( command, key ); +} + +function NotifyOnPlayerCommandRemove( command, key ) +{ + self notify( command + "_" + key ); +} + +function NotifyOnPlayerCommandInternal( command, key ) +{ + self endon( "disconnect" ); + level endon( "game_ended" ); + + self notify( command + "_" + key ); + self endon( command + "_" + key ); + + switch( key ) + { + case "+attack": + PressButtonNotify( &AttackButtonPressed, command ); + break; + case "+toggleads_throw": + PressButtonNotify( &AdsButtonPressed, command ); + break; + case "weapnext": + PressButtonNotify( &WeaponSwitchButtonPressed, command ); + break; + case "+usereload": + PressButtonNotify( &UseButtonPressed, command ); + break; + case "+smoke": + PressButtonNotify( &SecondaryOffhandButtonPressed, command ); + break; + case "+frag": + PressButtonNotify( &FragButtonPressed, command ); + break; + case "+actionslot 1": + PressButtonNotify( &ActionSlotOneButtonPressed, command ); + break; + case "+actionslot 2": + PressButtonNotify( &ActionSlotTwoButtonPressed, command ); + break; + case "+actionslot 3": + PressButtonNotify( &ActionSlotThreeButtonPressed, command ); + break; + case "+actionslot 4": + PressButtonNotify( &ActionSlotFourButtonPressed, command ); + break; + case "-actionslot 1": + ReleaseButtonNotify( &ActionSlotOneButtonPressed, command ); + break; + case "-actionslot 2": + ReleaseButtonNotify( &ActionSlotTwoButtonPressed, command ); + break; + case "-actionslot 3": + ReleaseButtonNotify( &ActionSlotThreeButtonPressed, command ); + break; + case "-actionslot 4": + ReleaseButtonNotify( &ActionSlotFourButtonPressed, command ); + break; + case "+stance": + ReleaseButtonNotify( &StanceButtonPressed, command ); + break; + case "+breath_sprint": + ReleaseButtonNotify( &SprintButtonPressed, command ); + break; + case "+melee": + ReleaseButtonNotify( &MeleeButtonPressed, command ); + break; + } +} + +function PressButtonNotify( buttonFunc, command ) +{ + while ( true ) + { + while ( !self [[ buttonFunc ]]() ) + {wait(.05);}; + + self notify( command ); + + while ( self [[ buttonFunc ]]() ) + {wait(.05);}; + } +} + +function ReleaseButtonNotify( buttonFunc, command ) +{ + while ( true ) + { + while ( !self [[ buttonFunc ]]() ) + {wait(.05);}; + + while ( self [[ buttonFunc ]]() ) + {wait(.05);}; + + self notify( command ); + } +} + +function setupKeyBindings() // self == player +{ + if ( IsAI( self ) ) + return; + + self NotifyOnPlayerCommand( "lock", "+attack" ); + self NotifyOnPlayerCommand( "spin", "+toggleads_throw" ); + self NotifyOnPlayerCommand( "changeProp", "weapnext" ); + self NotifyOnPlayerCommand( "setToSlope", "+usereload" ); + self NotifyOnPlayerCommand( "propAbility", "+smoke" ); + self NotifyOnPlayerCommand( "cloneProp", "+actionslot 2" ); + self NotifyOnPlayerCommand( "zoomin", "+actionslot 3" ); + self NotifyOnPlayerCommand( "zoomout", "+actionslot 4" ); + self NotifyOnPlayerCommand( "hide", "+melee" ); +} + +function cleanupPropKeyBindings() +{ + if ( IsAI( self ) ) + return; + + self NotifyOnPlayerCommandRemove( "lock", "+attack" ); + self NotifyOnPlayerCommandRemove( "spin", "+toggleads_throw" ); + self NotifyOnPlayerCommandRemove( "changeProp", "+weapnext" ); + self NotifyOnPlayerCommandRemove( "setToSlope", "+usereload" ); + self NotifyOnPlayerCommandRemove( "propAbility", "+smoke" ); + self NotifyOnPlayerCommandRemove( "cloneProp", "+actionslot 2" ); + self NotifyOnPlayerCommandRemove( "zoomin", "+actionslot 3" ); + self NotifyOnPlayerCommandRemove( "zoomout", "+actionslot 4" ); + self NotifyOnPlayerCommandRemove( "hide", "+melee" ); +} + +function is_player_gamepad_enabled() +{ + return self GamepadUsedLast(); +} + +function addUpperRightHudElem( label, value, text, labelPC ) +{ + hudElem = hud::createFontString( "default", 1.2 ); + hudElem.x = -15; + hudElem.y = self.currentHudY; + hudElem.alignX = "right"; + hudElem.alignY = "bottom"; + hudElem.horzAlign = "right"; + hudElem.vertAlign = "bottom"; + hudElem.archived = true; + hudElem.alpha = 1; + hudElem.glowAlpha = 0; + hudElem.hidewheninmenu = true; + hudelem.hidewheninkillcam = true; + hudelem.startFontScale = hudelem.fontscale; + + if ( IsDefined( label ) && IsDefined( labelPC ) ) + { + if ( self is_player_gamepad_enabled() ) + hudElem.label = label; + else + hudElem.label = labelPC; + } + else if ( IsDefined( label ) ) + { + hudElem.label = label; + } + else if ( IsDefined( text ) ) + { + hudElem SetText( text ); + } + + if ( IsDefined( value ) ) + hudElem SetValue( value ); + + self.currentHudY -= 20; + + return hudElem; +} + +// Prop Controls +// --------------------------------------------------------------------------------------------- + +function propControlsHUD() +{ + Assert( !IsDefined( self.changePropKey ) ); + if ( self IsSplitscreen() ) + self.currentHudY = -10; + else + self.currentHudY = -80; + + self.abilityKey = addUpperRightHudElem(); + self.cloneKey = addUpperRightHudElem( &"MP_PH_CLONE" ); + self.changePropKey = addUpperRightHudElem( &"MP_PH_CHANGE", 0 ); + + self.currentHudY -= 20; + + self.hidePropKey = addUpperRightHudElem( &"MP_PH_HIDEPROP" ); + self.matchSlopeKey = addUpperRightHudElem( &"MP_PH_SLOPE", undefined, undefined, &"MP_PH_SLOPE_PC" ); + self.lockPropKey = addUpperRightHudElem( &"MP_PH_LOCK" ); + self.spinPropKey = addUpperRightHudElem( &"MP_PH_SPIN", undefined, undefined, &"MP_PH_SPIN_PC" ); + + self setNewAbilityHUD(); + self.zoomKey = addUpperRightHudElem( &"MP_PH_ZOOM" ); + self thread updateTextOnGamepadChange(); +} + +function cleanupPropControlsHUDOnDeath() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + self waittill( "death" ); + + self thread cleanupPropKeyBindings(); + self thread cleanupPropControlsHUD(); +} + +function safeDestroy( hudElem ) +{ + if ( IsDefined( hudElem ) ) + { + hudElem Destroy(); + } +} + +function cleanupPropControlsHUD() +{ + safeDestroy( self.changePropKey ); + safeDestroy( self.spinPropKey ); + safeDestroy( self.lockPropKey ); + safeDestroy( self.matchSlopeKey ); + safeDestroy( self.abilityKey ); + safeDestroy( self.zoomKey ); + safeDestroy( self.spectateKey ); + safeDestroy( self.cloneKey ); + safeDestroy( self.hidePropKey ); +} + +function updateTextOnGamepadChange() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + self endon( "death" ); + + if ( level.Console ) + return; + + waittillframeend; + + lastGamepad = self is_player_gamepad_enabled(); + + while ( true ) + { + nowGamepad = self is_player_gamepad_enabled(); + if ( nowGamepad != lastGamepad ) + { + lastGamepad = nowGamepad; + + if ( nowGamepad ) + { + if ( !( isdefined( self.slopeLocked ) && self.slopeLocked ) ) + self.matchSlopeKey.label = &"MP_PH_SLOPE"; + else + self.matchSlopeKey.label = &"MP_PH_SLOPED"; + + self.spinPropKey.label = &"MP_PH_SPIN"; + } + else + { + if ( !( isdefined( self.slopeLocked ) && self.slopeLocked ) ) + self.matchSlopeKey.label = &"MP_PH_SLOPE_PC"; + else + self.matchSlopeKey.label = &"MP_PH_SLOPED_PC"; + + self.spinPropKey.label = &"MP_PH_SPIN_PC"; + } + + } + + {wait(.05);}; + } +} + +function propInputWatch() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( IsAI( self ) ) + return; + + self.lock = false; + self.slopeLocked = false; + + prop::waittillPrematchDone(); + + self thread propMoveUnlock(); + self thread propCameraZoom(); + + self.debugNextPropIndex = 1; + + while(1) + { + msg = self util::waittill_any_return( "lock", "spin", "changeProp", "setToSlope", "propAbility", "cloneProp", "hide" ); + + if ( !IsDefined( msg ) ) + continue; + + waittillframeend; + + if ( msg == "lock" ) + self propLockUnlock(); + else if ( msg == "spin" ) + self propSpin(); + else if ( msg == "changeProp" ) + self propChange(); + else if ( msg == "setToSlope" ) + self propMatchSlope(); + else if ( msg == "propAbility" ) + self propAbility(); + else if ( msg == "cloneProp" ) + self propClonePower(); + else if ( msg == "hide" ) + self propLocalHide(); + } +} + +function propLockUnlock() +{ + if ( self IsMantling() ) + return; + + if( self.lock ) + { + self unlockProp(); + } + else + { + self lockProp(); + } +} + +function propSpin() +{ + self.propEnt Unlink(); + + self.propEnt.angles += ( 0, 45, 0 ); + self.propEnt.origin = self.propAnchor.origin; + + if ( self.slopeLocked && ( isdefined( self.lock ) && self.lock ) ) + { + self.propEnt set_pitch_roll_for_ground_normal( self.prop ); + } + + self.propEnt LinkTo( self.propAnchor ); +} + +function registerPreviousProp( inPlayer ) +{ + CONST_MAX_REMEMBERED_PROPS = 3; + + if (!IsDefined(inPlayer.usedPropsIndex)) + inPlayer.usedPropsIndex = 0; + + inPlayer.usedProps[inPlayer.usedPropsIndex] = inPlayer.prop.info; + + inPlayer.usedPropsIndex++; + if ( inPlayer.usedPropsIndex >= CONST_MAX_REMEMBERED_PROPS ) + inPlayer.usedPropsIndex = 0; +} + +function propChange() +{ + if ( !self propHasChangesLeft() ) + { + return; + } + + // Debounce prop changes on PC due to the mouse wheel being the default binding + if ( !level.console ) + { + debounceMS = 300; + if ( IsDefined( self.lastPropChangeTime ) && ( ( GetTime() - self.lastPropChangeTime ) < debounceMS ) ) + { + return; + } + + self.lastPropChangeTime = GetTime(); + } + + self notify( "changed_prop" ); + + registerPreviousProp(self); + + self.prop.info = prop::getNextProp( self ); + + /# + if ( GetDvarInt( "scr_ph_cycleModels", 0 ) != 0 ) + { + self.prop.info = level.propList[ self.debugNextPropIndex ]; + self.debugNextPropIndex++; + if ( self.debugNextPropIndex >= level.propList.size ) + self.debugNextPropIndex = 0; + } + #/ + + self propChangeTo( self.prop.info ); + + if ( level.phSettings.nuketownVariant ) + PlayFxOnTag( "player/fx_plyr_clone_reaper_appear", self.prop, "tag_origin" ); + + self.maxhealth = Int( prop::getPropHealth( self.prop.info ) ); + self SetNormalHealth( 1.0 ); + + self setNewAbilityCount( self.currentAbility ); + self setNewAbilityCount( "CLONE" ); + + if ( prop::usePropHudServer() ) + { + self.abilityKey.alpha = 1; + self.cloneKey.alpha = 1; + } + + /# + if ( GetDvarInt( "scr_ph_cycleModels", 0 ) != 0 ) + { + return; + } + #/ + + self propDeductChange(); +} + +function propHasChangesLeft() +{ + /# + if ( ( isdefined( self.unlimitedChanges ) && self.unlimitedChanges ) ) + { + return true; + } + #/ + + return ( self.changesLeft > 0 ); +} + +function propDeductChange() +{ + /# + if ( ( isdefined( self.unlimitedChanges ) && self.unlimitedChanges ) ) + { + return; + } + #/ + + propSetChangesLeft( self.changesLeft - 1 ); +} + +function propSetChangesLeft( newValue ) +{ + self.changesLeft = newValue; + + if ( prop::usePropHudServer() ) + { + self.changePropKey setvalue( self.changesLeft ); + if ( self.changesLeft <= 0 ) + self.changePropKey.alpha = 0.5; + } +} + +function propChangeTo( info ) +{ + self.prop.info = info; + self.propInfo = info; + + if ( level.phSettings.nuketownVariant ) + { + NTX_old_propEnt_ang = self.propEnt.angles; + NTX_old_prop_ang = self.prop.angles; + NTX_old_ang = self.angles; + } + + self.prop SetModel( info.modelName ); + self.prop.xyzOffset = info.xyzOffset; + self.prop.anglesOffset = info.anglesOffset; + self.prop SetScale( info.propScale, true ); + + self.prop Unlink(); + self.propEnt Unlink(); + + self.propEnt.origin = self.propAnchor.origin; + self.prop.origin = self.propEnt.origin; + + self.propEnt.angles = ( self.angles[ 0 ], self.propEnt.angles[ 1 ], self.angles[ 2 ] ); + self.prop.angles = self.propEnt.angles; + + if ( ( isdefined( self.isAngleOffset ) && self.isAngleOffset ) ) + { + self.prop.angles = self.angles; + self.isAngleOffset = false; + } + + self prop::applyXYZOffset(); + self prop::applyAnglesOffset(); + + if ( level.phSettings.nuketownVariant ) + { + self.propEnt.angles = NTX_old_propEnt_ang; + self.prop.angles = NTX_old_prop_ang; + self.angles = NTX_old_ang; + } + + self.prop LinkTo( self.propEnt ); + + if ( self.slopeLocked && ( isdefined( self.lock ) && self.lock ) ) + { + self.propEnt set_pitch_roll_for_ground_normal( self.prop ); + } + + self.propEnt LinkTo( self.propAnchor ); + + self.thirdPersonRange = info.propRange; + self.thirdPersonHeightOffset = info.propHeight; + self SetClientThirdPerson( true, self.thirdPersonRange, self.thirdPersonHeightOffset ); +} + +function propMatchSlope() +{ + if ( !( isdefined( self.slopeLocked ) && self.slopeLocked ) ) + { + self.slopeLocked = true; + + if ( ( isdefined( self.lock ) && self.lock ) ) + { + self.propEnt Unlink(); + self.propEnt set_pitch_roll_for_ground_normal( self.prop ); + self.propEnt LinkTo( self.propAnchor ); + } + + if ( prop::usePropHudServer() ) + { + if ( self is_player_gamepad_enabled() ) + //"[{+usereload}]: Matching Slope" + self.matchSlopeKey.label = &"MP_PH_SLOPED"; + else + //"[{+activate}]: Matching Slope" + self.matchSlopeKey.label = &"MP_PH_SLOPED_PC"; + } + } + else + { + self.slopeLocked = false; + + if ( ( isdefined( self.lock ) && self.lock ) ) + { + self.propEnt Unlink(); + self.propEnt.angles = ( self.angles[ 0 ], self.propEnt.angles[ 1 ], self.angles[ 2 ] ); + self.propEnt.origin = self.propAnchor.origin; + self.propEnt LinkTo( self.propAnchor ); + } + + if ( prop::usePropHudServer() ) + { + if ( self is_player_gamepad_enabled() ) + //"[{+usereload}]: Match Slope" + self.matchSlopeKey.label = &"MP_PH_SLOPE"; + else + //"[{+activate}]: Match Slope" + self.matchSlopeKey.label = &"MP_PH_SLOPE_PC"; + } + } +} + +function propAbility() +{ + if ( !level flag::get( "props_hide_over" ) ) + { + return; + } + + if ( self propHasFlashesLeft() ) + { + self thread flashEnemies(); + self propDeductFlash(); + } +} + +function propClonePower() +{ + if ( propHasClonesLeft() ) + { + self thread cloneProp(); + self thread propDeductCloneChange(); + } +} + +function propHasClonesLeft() +{ + /# + if ( ( isdefined( self.unlimitedClones ) && self.unlimitedClones ) ) + { + return true; + } + #/ + + return ( self.clonesLeft > 0 ); +} + +function propDeductCloneChange() +{ + /# + if ( ( isdefined( self.unlimitedClones ) && self.unlimitedClones ) ) + { + return; + } + #/ + + propSetClonesLeft( self.clonesLeft - 1 ); +} + +function propSetClonesLeft( newValue ) +{ + self.clonesLeft = newValue; + + if ( prop::usePropHudServer() && isdefined( self ) && IsAlive( self ) && isdefined( self.cloneKey ) ) + { + self.cloneKey setvalue( self.clonesLeft ); + if ( self.clonesLeft <= 0 ) + self.cloneKey.alpha = 0.5; + else + self.cloneKey.alpha = 1; + } +} + +function propHasFlashesLeft() +{ + /# + if ( ( isdefined( self.unlimitedFlashes ) && self.unlimitedFlashes ) ) + { + return true; + } + #/ + + return ( self.abilityLeft > 0 ); +} + +function propDeductFlash() +{ + /# + if ( ( isdefined( self.unlimitedFlashes ) && self.unlimitedFlashes ) ) + { + return; + } + #/ + + propSetFlashesLeft( self.abilityLeft - 1 ); +} + +function propSetFlashesLeft( newValue ) +{ + self.abilityLeft = newValue; + + if ( prop::usePropHudServer() ) + { + self.abilityKey setvalue( self.abilityLeft ); + if ( self.abilityLeft <= 0 ) + self.abilityKey.alpha = 0.5; + } +} + +function set_pitch_roll_for_ground_normal( traceIgnore ) +{ + groundNormal = get_ground_normal( traceIgnore, false ); + if(!IsDefined(groundNormal)) + return; + + ovf = AnglesToForward( self.angles ); + ovr = AnglesToRight( self.angles ); + + new_angles = VectorToAngles( groundNormal ); + pitch = AngleClamp180( new_angles[0] + 90 ); + new_angles = (0, new_angles[1], 0 ); + + nvf = AnglesToForward( new_angles ); + + mod = VectorDot( nvf, ovr ); + + if ( mod < 0 ) + mod = -1; + else + mod = 1; + + dot = VectorDot( nvf, ovf ); + + newPitch = dot * pitch; + newRoll = ((1-abs(dot)) * pitch * mod); + + self.angles = (newPitch, self.angles[1], newRoll ); +} + +function allPropsSetIgnoreBulletCollision( shouldIgnore ) +{ + foreach ( player in level.players ) + { + if ( isdefined( player.prop ) ) + { + if ( shouldIgnore ) + player.prop NotSolid(); + else + player.prop Solid(); + } + } +} + +function allClonesSetIgnoreBulletCollision( shouldIgnore ) +{ + foreach ( player in level.players ) + { + if ( isdefined( player.propClones ) ) + { + foreach ( clone in player.propClones ) + { + if ( isdefined( clone ) ) + { + if ( shouldIgnore ) + clone NotSolid(); + else + clone Solid(); + } + } + } + } +} + +function get_ground_normal( traceIgnore, debug ) +{ + if( !isDefined( traceIgnore ) ) + ignore = self; + else + ignore = traceIgnore; + + tracePoints = Array( self.origin ); + + if( GetDvarInt("scr_ph_useBoundsForGroundNormal", 1) ) + { + for(i=-1.0; i<=1.0; i+=2.0) + { + for(j=-1.0; j<=1.0; j+=2.0) + { + corner = ignore GetPointInBounds( i, j, 0.0 ); + corner = (corner[0], corner[1], self.origin[2]); + + tracePoints[tracePoints.size] = corner; + } + } + } + + allPropsSetIgnoreBulletCollision( true ); + + avgNormal = (0,0,0); + traceHitCount = 0; + foreach(point in tracePoints) + { + trace = BulletTrace( point+(0,0,4), point+(0,0,-16), false, ignore ); + traceHit = (trace["fraction"] > 0.0) && (trace["fraction"] < 1); + if ( traceHit ) + { + avgNormal += trace["normal"]; + traceHitCount++; + } + + /# + if( debug ) + { + if( traceHit ) + line(point, point + trace["normal"] * 30, (0,1,0)); + else + Sphere(point, 3.0, (1,0,0)); + } + #/ + } + + allPropsSetIgnoreBulletCollision( false ); + + if ( traceHitCount > 0 ) + { + avgNormal /= traceHitCount; + /# + if( debug ) + line(self.origin, self.origin + avgNormal * 20, (1,1,1)); + #/ + + return avgNormal; + } + else + { + /# + if( debug ) + Sphere(self.origin, 5.0, (1,0,0)); + #/ + return undefined; + } +} + +function propMoveUnlock() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + wasLocked = false; + wasMoving = false; + waitingToUnlock = false; + + while( 1 ) + { + {wait(.05);}; + + movement = self GetNormalizedMovement(); + jumping = self JumpButtonPressed(); + + if( !isdefined( movement ) ) + continue; + + isMoving = ( movement[ 0 ] != 0 || movement[ 1 ] != 0 || jumping ); + + if ( self.lock && waitingToUnlock && !isMoving ) + { + waitingToUnlock = false; + } + else if ( self.lock && !wasLocked && isMoving ) + { + waitingToUnlock = true; + } + else if( self.lock && isMoving && !waitingToUnlock ) + { + self unlockProp(); + } + wasLocked = self.lock; + wasMoving = isMoving; + } +} + +function unlockProp() +{ + self unlink(); + self ResetDoubleJumpRechargeTime(); + + if( self.slopeLocked ) + { + self.propEnt unlink(); + self.propEnt.angles = ( self.angles[0], self.propEnt.angles[1], self.angles[2] ) ; + + self.propEnt.origin = self.propAnchor.origin; + self.propEnt LinkTo( self.propAnchor ); + } + + self.propAnchor LinkTo( self ); + + self.lock = false; + if ( prop::usePropHudServer() ) + { + self.lockPropKey.label = &"MP_PH_LOCK"; + self thread flashLockPropKey(); + } +} + +function lockProp() +{ + if ( !canLock() ) + return; + + self.propAnchor unlink(); + self.propAnchor.origin = self.origin; + + self PlayerLinkTo( self.propAnchor ); + + if( self.slopeLocked ) + { + self.propEnt unlink(); + self.propEnt set_pitch_roll_for_ground_normal( self.prop ); + self.propEnt.origin = self.origin; + self.propEnt LinkTo(self.propAnchor); + } + + self.lock = true; + self notify( "locked" ); + if ( prop::usePropHudServer() ) + { + self.lockPropKey.label = &"MP_PH_LOCKED"; + self thread flashLockPropKey(); + } +} + +function flashLockPropKey() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + self notify( "flashLockPropKey" ); + self endon( "flashLockPropKey" ); + + newScale = ( self.lockPropKey.startFontScale + 0.75 ); + + self.lockPropKey changefontscaleovertime( 0.1 ); + self.lockPropKey.fontScale = newScale; + + wait 0.1; + + if ( isdefined( self.lockPropKey ) ) + { + self.lockPropKey changefontscaleovertime( 0.1 ); + self.lockPropKey.fontScale = self.lockPropKey.startFontScale; + } +} + +function GetPhysicsBoxGroundPosition() +{ + assert( IsPlayer( self ) ); + + start = self.origin; + end = start + ( 0, 0, -2000 ); + return PlayerPhysicsTrace( start, end ); +} + +function GetBulletGroundTrace() +{ + assert( IsPlayer( self ) ); + + start = self.origin; + end = start + ( 0, 0, -2000 ); + trace = BulletTrace( start, end, false, self.prop ); + return trace; +} + +/# +function recordDebugLock( success, type, player, origin1, text1, origin2, text2, origin3, text3 ) +{ + if ( !isdefined( level.phLockDebug ) ) + level.phLockDebug = SpawnStruct(); + + level.phLockDebug.success = success; + level.phLockDebug.type = type; + level.phLockDebug.playerOrg = player.origin; + level.phLockDebug.playerAngles = player.angles; + level.phLockDebug.playerMins = player GetMins(); + level.phLockDebug.playerMaxs = player GetMaxs(); + level.phLockDebug.origin1 = origin1; + level.phLockDebug.text1 = text1; + level.phLockDebug.origin2 = origin2; + level.phLockDebug.text2 = text2; + level.phLockDebug.origin3 = origin3; + level.phLockDebug.text3 = text3; +} +#/ + +function canLock() +{ + killTriggers = GetEntArray( "trigger_hurt", "classname" ); + oobTriggers = GetEntArray( "trigger_out_of_bounds","classname" ); + triggers = ArrayCombine( killtriggers, oobTriggers, false, false ); + propNoLockTriggers = GetEntArray( "prop_no_lock", "targetname" ); + if ( propNoLockTriggers.size > 0 ) + triggers = ArrayCombine( triggers, propNoLockTriggers, false, false ); + + foreach ( trigger in triggers ) + { + if ( trigger IsTouchingVolume( self.origin, self GetMins(), self GetMaxs() ) ) + { + /# recordDebugLock( false, "touch", self, trigger.origin, trigger.classname ); #/ + return false; + } + } + + // don't allow the player to lock above kill or oob triggers + if ( self IsPlayerSwimming() ) + { + /# recordDebugLock( true, "swim", self ); #/ + return true; + } + else if ( !self IsOnGround() || self IsWallRunning() ) + { + trace1 = self GetBulletGroundTrace(); + frac = trace1["fraction"]; + org1 = trace1["position"]; + + if ( frac == 1 ) + { + /# recordDebugLock( false, "frac=1", self, org1, ""); #/ + return false; + } + + foreach ( trigger in triggers ) + { + if ( trigger IsTouchingVolume( org1, self GetMins(), self GetMaxs() ) ) + { + /# recordDebugLock( false, "bullet", self, trigger.origin, trigger.classname ); #/ + return false; + } + } + + point = GetNearestPathPoint( org1, 256 ); + if ( !isdefined( point ) ) + { + /# recordDebugLock( false, "path", self, org1 ); #/ + return false; + } + distZ = point[2] - org1[2]; + if ( distZ > 50 ) + { + point2 = GetNearestPathPoint( org1, 50 ); + if ( !isdefined( point2 ) ) + { + /# recordDebugLock( false, "belowPath", self, org1, "trace", point, "path" ); #/ + return false; + } + } + dist2d = Distance2d( point, org1 ); + if ( dist2d > 100 ) + { + /# recordDebugLock( false, "2dPath", self, org1, "trace", point, "path" ); #/ + return false; + } + + org2 = self GetPhysicsBoxGroundPosition(); + + foreach ( trigger in triggers ) + { + if ( trigger IsTouchingVolume( org2, self GetMins(), self GetMaxs() ) ) + { + /# recordDebugLock( false, "box", self, trigger.origin, trigger.classname ); #/ + return false; + } + } + + /# recordDebugLock( true, "air", self, org1, "bullet", org2, "box", point, "path " + Distance( org1, point ) ); #/ + return true; + } + else + { + /# recordDebugLock( true, "ground", self ); #/ + return true; + } +} + +function propCameraZoom() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + CONST_PROP_ZOOM_INCREMENT = 10; + + self.thirdPersonRange = self.prop.info.propRange; + + while( 1 ) + { + zoom = self util::waittill_any_return( "zoomin", "zoomout" ); + + if( !isdefined( zoom ) ) + continue; + + if( zoom == "zoomin" ) + { + if( ( self.thirdPersonRange - CONST_PROP_ZOOM_INCREMENT ) < 50 ) + continue; + + self.thirdPersonRange -= CONST_PROP_ZOOM_INCREMENT; + self SetClientThirdPerson( true, self.thirdPersonRange, self.thirdPersonHeightOffset ); + } + else if( zoom == "zoomout" ) + { + maxZoomOutRange = math::clamp( self.prop.info.propRange + 50, 50, 360 ); + + if( ( self.thirdPersonRange + CONST_PROP_ZOOM_INCREMENT ) > maxZoomOutRange ) + continue; + + self.thirdPersonRange += CONST_PROP_ZOOM_INCREMENT; + self SetClientThirdPerson( true, self.thirdPersonRange, self.thirdPersonHeightOffset ); + } + } +} + +function setNewAbilityHUD() +{ + switch( self.currentAbility ) + { + case "FLASH": + self.abilityKey.label = &"MP_PH_FLASH"; + break; + default: + AssertMsg( "Only flash is supported" ); + break; + } +} + +function setNewAbilityCount( propPower, count ) +{ + switch( propPower ) + { + case "FLASH": + if ( !isdefined( count ) ) + count = level.phSettings.propNumFlashes; + propSetFlashesLeft( count ); + break; + case "CLONE": + if ( !isdefined( count ) ) + count = level.phSettings.propNumClones; + propSetClonesLeft( count ); + break; + default: + AssertMsg( "Unknown ability: " + propPower ); + break; + } +} + +function endOnDeath() +{ + self waittill( "death" ); + waittillframeend; + self notify ( "end_explode" ); +} + +function flashTheProps( thrownBy ) +{ + level endon( "game_ended" ); + thrownBy endon( "disconnect" ); + + self thread endOnDeath(); + self endon( "end_explode" ); + + self waittill( "explode", position ); + + if( !isDefined( thrownBy ) ) + return; + + flashEnemies( thrownBy, position ); +} + +function flashEnemies( thrownBy, position ) +{ + if( !isDefined( thrownBy ) ) + thrownBy = self; + + if( !isDefined( position ) ) + position = self.origin; + + PlayFX( fx::get( "propFlash" ), ( position + ( 0, 0, 4 ) ) ); + PlaySoundAtPosition( "mpl_emp_equip_stun", position ); + + foreach( otherPlayer in level.players ) + { + if ( otherPlayer == thrownBy ) + continue; + + if( ( isdefined( otherPlayer.flashImmune ) && otherPlayer.flashImmune ) ) + continue; + + if ( !IsDefined( otherPlayer ) || !IsAlive( otherPlayer ) || !IsDefined( otherPlayer.team ) || otherPlayer.team != game[ "attackers" ] ) + continue; + + vec = ( position + (0,0,4) ) - (otherPlayer GetEye()); + dist = Length( vec ); + radius_max = 500.0; + radius_min = 150.0; + if ( dist <= radius_max ) + { + if ( dist <= radius_min ) + dist_amt = 1; + else + dist_amt = 1 - (dist-radius_min)/(radius_max-radius_min); + dir = VectorNormalize( vec ); + fwd = AnglesToForward( (otherPlayer GetPlayerAngles()) ); + fov_amt = VectorDot( fwd, dir ); + otherPlayer notify( "flashbang", dist_amt, fov_amt, thrownBy ); + thrownBy thread damagefeedback::update(); + } + } +} + +function deletePropsIfAtMax() // self == player +{ + MAX_CLONES_PER_PLAYER = 9; + + if ( level.phSettings.altModeManyClones ) + MAX_CLONES_PER_PLAYER = 27; + + if ( ( self.propClones.size + 1 ) <= MAX_CLONES_PER_PLAYER ) + return; + + numValidClones = 0; + foreach ( clone in self.propClones ) + { + if ( IsDefined( clone ) ) + numValidClones++; + } + + if ( ( numValidClones + 1 ) <= MAX_CLONES_PER_PLAYER ) + return; + + clones = []; + cloneToDelete = undefined; + for ( i = 0; i < self.propClones.size; i++ ) + { + clone = self.propClones[i]; + if ( !IsDefined( clone ) ) + continue; + + if ( !IsDefined( cloneToDelete ) ) + { + cloneToDelete = clone; + continue; + } + + clones[ clones.size ] = clone; + } + + Assert( IsDefined( cloneToDelete ) ); + cloneToDelete notify( "maxDelete" ); + cloneToDelete Delete(); + + self.propClones = clones; +} + +function cloneProp() +{ + if( !isDefined( self.propClones ) ) + self.propClones = []; + else + deletePropsIfAtMax(); + + newClone = Spawn("script_model", self.prop.origin); + newClone.targetname = "propClone"; + newClone setModel( self.prop.model ); + newClone SetScale( self.prop.info.propScale, true ); + newClone.angles = self.prop.angles; + newClone SetCanDamage( true ); + newClone.fakeHealth = 50; + newClone.health = 99999; + newClone.maxhealth = 99999; + newClone.playerOwner = self; + newClone thread prop::entityDamageWatcher( &damageCloneWatch ); + newClone SetPlayerCollision( false ); + newClone MakeSentient(); + newClone NotSolidCapsule(); + newClone SetTeam( self.team ); + newClone clientfield::set( "enemyequip", 2 ); + + if ( prop::propMiniGameActive() ) + { + newClone HideFromTeam( game["attackers"] ); + newClone NotSolid(); + } + + if ( level.phSettings.altModeManyClones ) + newClone.fakeHealth = 100; + + self.propClones[self.propClones.size] = newClone; +} + +function damageCloneWatch( damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags ) +{ + if ( !IsDefined( attacker ) ) + return; + + if ( isPlayer( attacker ) ) + { + if( ( isdefined( self.isDying ) && self.isDying ) ) + { + return; + } + + if ( isdefined( weapon ) && weapon.rootWeapon.name == "concussion_grenade" && isdefined( meansOfDeath ) && meansOfDeath != "MOD_IMPACT" ) + registerConcussionEvent( attacker, undefined, meansOfDeath, damage, point, weapon ); + + attacker thread damagefeedback::update(); + + self.lastattacker = attacker; + + self.fakeHealth -= damage; + if ( self.fakeHealth <= 0 ) + { + self deleteClone(); + return; + } + } + + self.health += damage; +} + +function flashCloneDestroyedIndicator() +{ + self.decoyDestroyedIndicator.alpha = 1; + self.decoyDestroyedIndicator FadeOverTime( 3 ); + self.decoyDestroyedIndicator.alpha = 0; +} + +function createAltDecoyDestroyedIndicator() +{ + self.decoyDestroyedIndicator = hud::createFontString( "objective", 1 ); + self.decoyDestroyedIndicator.label = &"SCORE_DECOY_KILLED"; + self.decoyDestroyedIndicator.x = 0; + self.decoyDestroyedIndicator.y = 20; + self.decoyDestroyedIndicator.alignX = "center"; + self.decoyDestroyedIndicator.alignY = "middle"; + self.decoyDestroyedIndicator.horzAlign = "user_center"; + self.decoyDestroyedIndicator.vertAlign = "middle"; + self.decoyDestroyedIndicator.archived = true; + self.decoyDestroyedIndicator.fontscale = 1; + self.decoyDestroyedIndicator.alpha = 0; + self.decoyDestroyedIndicator.glowAlpha = 0.5; + self.decoyDestroyedIndicator.hidewheninmenu = false; +} + +function altDecoyDestroyedIndicator( decoyOwner, attacker ) +{ + if ( isdefined( decoyOwner ) ) + { + if ( !isdefined( decoyOwner.decoyDestroyedIndicator ) ) + decoyOwner createAltDecoyDestroyedIndicator(); + + decoyOwner flashCloneDestroyedIndicator(); + } + + if ( isdefined( attacker ) ) + { + if ( !isdefined( attacker.decoyDestroyedIndicator ) ) + attacker createAltDecoyDestroyedIndicator(); + + attacker flashCloneDestroyedIndicator(); + } +} + +function deleteClone() +{ + if ( level.phSettings.altModeManyClones ) + { + thread altDecoyDestroyedIndicator( self.playerOwner, self.lastattacker ); + + if ( isdefined( self.playerOwner ) ) + self.playerOwner propSetClonesLeft( self.playerOwner.clonesLeft + 1 ); + } + else if( isDefined( self.lastattacker ) ) + { + scoreevents::processScoreEvent( "clone_destroyed", self.lastattacker ); + if ( isdefined( self.playerOwner ) ) + scoreevents::processScoreEvent( "clone_was_destroyed", self.playerOwner ); + } + + if( !isDefined( self.isDying ) ) + self.isDying = true; + + PlaySoundAtPosition( "wpn_flash_grenade_explode", self.origin + ( 0, 0, 4 ) ); + PlayFx( fx::get( "propDeathFX" ), ( self.origin + ( 0, 0, 4 ) ) ); + + if( isDefined( self ) ) + self Delete(); +} + +function countdownFadeToBlackForXSec( overlay_time, fade_in_time, fade_out_time ) +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + if( !isDefined( overlay_time ) ) + overlay_time = 5; + + if ( !IsDefined( fade_in_time ) ) + fade_in_time = 1; + + if ( !IsDefined( fade_out_time ) ) + fade_out_time = 1; + + Assert( fade_out_time + fade_in_time < overlay_time ); + + overlay = NewClientHudElem( self ); + overlay.foreground = false; + overlay.x = 0; + overlay.y = 0; + overlay setshader( "black", 640, 480 ); + overlay.alignX = "left"; + overlay.alignY = "top"; + overlay.horzAlign = "fullscreen"; + overlay.vertAlign = "fullscreen"; + overlay.alpha = 0; + + {wait(.05);}; + + if ( fade_in_time > 0 ) + { + overlay fadeovertime( fade_in_time ); + } + overlay.alpha = 1; + + self setClientUIVisibilityFlag( "hud_visible", 0 ); + + self prop::waittillCountdownComplete( fade_in_time ); + + self UseServerVisionset( true ); // cover behind the scoreboard + self SetVisionSetForPlayer( "blackout_ph", fade_in_time ); + + self prop::waittillCountdownComplete( overlay_time - fade_out_time - fade_in_time ); + + if ( fade_out_time > 0 ) + { + overlay fadeovertime( fade_out_time ); + } + overlay.alpha = 0; + + self UseServerVisionset( false ); + self SetVisionSetForPlayer( "blackout_ph", fade_out_time ); + + self prop::waittillCountdownComplete( fade_out_time ); + + self setClientUIVisibilityFlag( "hud_visible", 1 ); + + {wait(.05);}; + + safeDestroy( overlay ); +} + +function watchSpecialGrenadeThrow() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + self notify( "watchSpecialGrenadeThrow" ); + self endon( "watchSpecialGrenadeThrow" ); + + while ( true ) + { + self waittill ( "grenade_fire", grenade, weapon ); + + self thread trackGrenade( grenade ); + + self.thrownSpecialCount = self.thrownSpecialCount + 1; + } +} + +function trackGrenade( grenade ) +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + weapon = grenade.weapon; + + grenade waittill( "explode", damageOrigin ); + + if ( !isdefined( level.phConcussionEvents ) ) + level.phConcussionEvents = []; + + index = getConcussionEventIndex( damageOrigin ); + + if ( !isdefined( index ) ) + index = initializeConcussionEvent( self, damageOrigin, weapon, 1, "MOD_GRENADE_SPLASH" ); + + {wait(.05);}; // wait for concussion notifies to process + {wait(.05);}; + + self concussOtherProps( index ); // run the radius damage again under certain conditions to concuss props hiding insside of decoys and other props + + {wait(.05);}; // wait for other radius damage notifies to finish + + level.phConcussionEvents[index] = undefined; +} + +function initializeConcussionEvent( attacker, damageOrigin, weapon, damage, meansOfDeath ) +{ + index = level.phConcussionEvents.size; + level.phConcussionEvents[index] = SpawnStruct(); + level.phConcussionEvents[index].players = []; + level.phConcussionEvents[index].attacker = self; + level.phConcussionEvents[index].damageOrigin = damageOrigin; + level.phConcussionEvents[index].damage = damage; + level.phConcussionEvents[index].meansOfDeath = meansOfDeath; + level.phConcussionEvents[index].weapon = weapon; + + return index; +} + +function registerConcussionEvent( attacker, playerConcussed, meansOfDeath, damage, damageOrigin, weapon ) +{ + if ( !isdefined( level.phConcussionEvents ) ) + level.phConcussionEvents = []; + + index = getConcussionEventIndex( damageOrigin ); + + if ( !isdefined( index ) ) + index = initializeConcussionEvent( attacker, damageOrigin, weapon, damage, meansOfDeath ); + + if ( isdefined( playerConcussed ) ) + { + playerIndex = level.phConcussionEvents[index].players.size; + level.phConcussionEvents[index].players[playerIndex] = playerConcussed; + } +} + +function getConcussionEventIndex( damageOrigin ) +{ + if ( !isdefined( level.phConcussionEvents ) ) + return; + + foreach ( index, event in level.phConcussionEvents ) + { + if ( event.damageOrigin == damageOrigin ) + { + return index; + } + } +} + +function concussOtherProps( index ) +{ + if ( !isdefined( level.phConcussionEvents ) || !isdefined( level.phConcussionEvents[index].attacker ) ) + return; + + weapon = level.phConcussionEvents[index].weapon; + damageOrigin = level.phConcussionEvents[index].damageOrigin; + expRadius = weapon.explosionradius; + expRadiusSq = expRadius * expRadius; + + foreach ( player in level.players ) + { + if ( !player util::IsProp() || !IsAlive( player ) || player wasConcussed( index ) ) + continue; + + distSq = DistanceSquared( damageOrigin, player.origin ); + + if ( distSq <= expRadiusSq ) + { + // turn off everything that previously got in the way of the radius trace + allPropsSetSolidContents( false ); + allClonesSetSolidContents( false ); + allPropPlayersSetSolidContents( true ); + allHunterPlayersSetSolidContents( false ); + + damage = level.phConcussionEvents[index].damage; + attacker = level.phConcussionEvents[index].attacker; + meansOfDeath = level.phConcussionEvents[index].meansOfDeath; + + attacker RadiusDamage( damageOrigin, expRadius, damage, damage, attacker, meansOfDeath, weapon ); + + allPropsSetSolidContents( true ); + allClonesSetSolidContents( true ); + allPropPlayersSetSolidContents( false ); + allHunterPlayersSetSolidContents( true ); + + break; + } + } +} + +function allPropsSetSolidContents( isSolid ) +{ + foreach ( player in level.players ) + { + if ( isdefined( player.prop ) ) + { + if ( isSolid ) + { + if ( isdefined( player.prop.propContents ) ) + player.prop SetContents( player.prop.propContents ); + + player.prop Solid(); + } + else + { + if ( !isdefined( player.prop.propContents ) ) + player.prop.propContents = player.prop SetContents( 0 ); + else + player.prop SetContents( 0 ); + + player.prop NotSolid(); + } + } + } +} + +function allClonesSetSolidContents( isSolid ) +{ + foreach ( player in level.players ) + { + if ( isdefined( player.propClones ) ) + { + foreach ( clone in player.propClones ) + { + if ( isdefined( clone ) ) + { + if ( isSolid ) + { + if ( isdefined( clone.cloneContents ) ) + clone SetContents( clone.cloneContents); + + clone Solid(); + } + else + { + if ( !isdefined( clone.cloneContents ) ) + clone.cloneContents = clone SetContents( 0 ); + else + clone SetContents( 0 ); + + clone NotSolid(); + } + } + } + } + } +} + +function allPropPlayersSetSolidContents( isSolid ) +{ + foreach ( player in level.players ) + { + if ( !player util::IsProp() || !IsAlive( player ) ) + continue; + + if ( isSolid ) + { + player SetContents( level.phSettings.playerContents ); + player Solid(); + } + else + { + player SetContents( 0 ); + player NotSolid(); + } + } +} + +function allHunterPlayersSetSolidContents( isSolid ) +{ + foreach ( player in level.players ) + { + if ( !player prop::IsHunter() || !IsAlive( player ) ) + continue; + + if ( isSolid ) + { + player SetContents( level.phSettings.playerContents ); + player Solid(); + } + else + { + player SetContents( 0 ); + player NotSolid(); + } + } +} + +function wasConcussedAtOrigin( damageOrigin ) +{ + index = getConcussionEventIndex( damageOrigin ); + + if ( isdefined( index ) ) + { + return self wasConcussed( index ); + } + + return false; +} + +function wasConcussed( index ) +{ + foreach ( concussedPlayer in level.phConcussionEvents[index].players ) + { + if ( isdefined( concussedPlayer ) && concussedPlayer == self ) + return true; + } + + return false; +} + +function safeSetAlpha( hudElem, newAlphaValue ) +{ + if ( IsDefined( hudElem ) ) + { + hudElem.alpha = newAlphaValue; + } +} + +function propAbilityKeysVisible( visible, override ) +{ + if( ( isdefined( visible ) && visible ) ) + { + alphaValue = 1; + } + else + { + alphaValue = 0; + } + + if ( prop::usePropHudServer() || ( isdefined( override ) && override ) ) + { + safeSetAlpha( self.changePropKey, alphaValue ); + safeSetAlpha( self.spinPropKey, alphaValue ); + safeSetAlpha( self.lockPropKey, alphaValue ); + safeSetAlpha( self.matchSlopeKey, alphaValue ); + safeSetAlpha( self.abilityKey, alphaValue ); + safeSetAlpha( self.cloneKey, alphaValue ); + safeSetAlpha( self.zoomKey, alphaValue ); + safeSetAlpha( self.hidePropKey, alphaValue ); + + if ( !( isdefined( level.noPropsSpectate ) && level.noPropsSpectate ) ) + { + safeSetAlpha( self.spectateKey, alphaValue ); + } + } +} + +function propLocalHide() +{ + if ( !isdefined( self.propHidden ) ) + self.propHidden = true; + else + self.propHidden = !self.propHidden; + + if ( self.propHidden ) + { + self.prop clientfield::set( "retrievable", 1 ); + if ( prop::usePropHudServer() ) + self.hidePropKey.label = &"MP_PH_SHOWPROP"; + } + else + { + self.prop clientfield::set( "retrievable", 0 ); + if ( prop::usePropHudServer() ) + self.hidePropKey.label = &"MP_PH_HIDEPROP"; + } +} + +// Hunter Controls +// --------------------------------------------------------------------------------------------- + +function hunterControlsHUD() +{ + Assert( !IsDefined( self.pingKey ) ); + + if ( self IsSplitscreen() ) + self.currentHudY = -50; + else + self.currentHudY = -100; + + self.pingKey = addUpperRightHudElem( &"MP_PH_PING" ); +} + +function cleanupHunterControlsHUDOnDeath() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + self waittill( "death" ); + + self thread cleanupHunterKeyBindings(); + self thread cleanupHunterControlsHUD(); +} + +function cleanupHunterControlsHUD() +{ + safeDestroy( self.pingKey ); +} + +function setupHunterKeyBindings() // self == player +{ + if ( IsAI( self ) ) + return; + + self NotifyOnPlayerCommand( "ping", "+smoke" ); +} + +function cleanupHunterKeyBindings() +{ + if ( IsAI( self ) ) + return; + + self NotifyOnPlayerCommandRemove( "ping", "+smoke" ); +} + +function hunterHasPingsLeft() +{ + /# + if ( ( isdefined( self.unlimitedPings ) && self.unlimitedPings ) ) + { + return true; + } + #/ + + return ( self.pingsLeft > 0 ); +} + +function hunterDeductPing() +{ + /# + if ( ( isdefined( self.unlimitedPings ) && self.unlimitedPings ) ) + { + return; + } + #/ + + hunterSetPingsLeft( self.pingsLeft - 1 ); +} + +function hunterSetPingsLeft( newValue ) +{ + self.pingsLeft = newValue; + + if ( prop::usePropHudServer() ) + { + self.pingKey setvalue( self.pingsLeft ); + if ( self.pingsLeft <= 0 ) + self.pingKey.alpha = 0.5; + } +} + +function hunterInputWatch() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( IsAI( self ) ) + return; + + prop::waittillPrematchDone(); + + while(1) + { + msg = self util::waittill_any_return( "ping" ); + + if ( !IsDefined( msg ) ) + continue; + + if ( msg == "ping" ) + self hunterPing(); + } +} + +function hunterPing() +{ + if ( !level flag::get( "props_hide_over" ) ) + { + return; + } + + if ( ( isdefined( self.pingRunning ) && self.pingRunning ) ) + { + return; + } + + if ( self hunterHasPingsLeft() ) + { + self thread hunterDoPing(); + self hunterDeductPing(); + } +} + +function hunterDoPing() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + self endon( "death" ); + + self.pingRunning = true; + + if ( prop::usePropHudServer() ) + self.pingKey.alpha = 0.5; + + pingObjectiveId = gameobjects::get_next_obj_id(); + Objective_Add( pingObjectiveId, "active" ); + Objective_Team( pingObjectiveId, game["attackers"] ); + Objective_Position( pingObjectiveId, self.origin ); + Objective_Icon( pingObjectiveId, "t7_hud_waypoints_safeguard_location" ); + Objective_SetColor( pingObjectiveId, &"FriendlyBlue" ); + Objective_OnEntity( pingObjectiveId, self ); + + self thread pingIconCleanupOnDisconnect( pingObjectiveId ); + + self clientfield::set( "pingHighlight", 1 ); + + foreach ( player in level.players ) + { + if ( IsAlive( player ) && player.team == self.team ) + player PlayLocalSound( "evt_hacker_raise" ); + } + // if using a 3D sound, do this instead of above +// self PlaySoundToTeam( HUNTER_PING_SND, game["attackers"] ); + + hostmigration::waitLongDurationWithHostMigrationPause( 4 ); + + self clientfield::set( "pingHighlight", 0 ); + + gameobjects::release_obj_id( pingObjectiveId ); + + if ( prop::usePropHudServer() && isdefined( self.pingKey ) ) + self.pingKey.alpha = 1; + + self.pingRunning = false; + + self notify( "pingComplete" ); +} + +function pingIconCleanupOnDisconnect( pingObjectiveId ) +{ + level endon( "game_ended" ); + self endon( "pingComplete" ); + + self util::waittill_either( "disconnect", "death" ); + + gameobjects::release_obj_id( pingObjectiveId ); +} \ No newline at end of file diff --git a/mp/gametypes/_prop_dev.gsc b/mp/gametypes/_prop_dev.gsc new file mode 100644 index 0000000..88754a6 --- /dev/null +++ b/mp/gametypes/_prop_dev.gsc @@ -0,0 +1,1373 @@ +#using scripts\mp\_teamops; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_dogtags; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_prop_controls; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\prop; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\math_shared; +#using scripts\shared\bots\_bot; + + + + +#namespace prop_dev; + +/* + Prop Hunt +*/ + +/# +function AddDevguiCommand( path, executeCmd ) +{ + pathStr = "\"" + path + "\""; + cmdStr = "\"" + executeCmd + "\"\n"; + debugCommand = "devgui_cmd " + pathStr + " " + cmdStr; + AddDebugCommand( debugCommand ); +} + +function propDevGui() +{ + REMOVE_UPPER_RIGHT_PRINTS_DEBUG = 0; + PLACEMENT_MODE = 0; + MORE_CHANGES = 0; + MORE_CLONES = 0; + MORE_FLASHES = 0; + MORE_BOOST = 0; + MORE_PINGS = 0; + MINIGAME_FORCE_TYPE = 0; + MINIGAME_ON = GetDvarInt( "scr_prop_minigame", 1 ); + SERVER_HUD = GetDvarInt( "scr_ph_usePropHudServer", true ); + + CONST_SWIM_DAMAGE = GetDvarFloat( "player_swimDamage" );; + + util::set_dvar_int_if_unset( "scr_ph_moreChanges", 0 ); + util::set_dvar_int_if_unset( "scr_ph_moreClones", 0 ); + util::set_dvar_int_if_unset( "scr_ph_moreFlashes", 0 ); + util::set_dvar_int_if_unset( "scr_ph_morePings", 0 ); + util::set_dvar_int_if_unset( "scr_ph_moreBoost", 0 ); + util::set_dvar_int_if_unset( "scr_ph_removeDebugUR", 0 ); + util::set_dvar_int_if_unset( "scr_ph_holdTimelimitZero", 0 ); + util::set_dvar_int_if_unset( "scr_ph_allowTeamChange", 0 ); + util::set_dvar_int_if_unset( "scr_ph_alwaysSpawn", 0 ); + util::set_dvar_int_if_unset( "scr_ph_showHealth", 0 ); + util::set_dvar_int_if_unset( "scr_ph_debugGroundTrace", 0 ); + util::set_dvar_int_if_unset( "scr_ph_useBoundsForGroundNormal", 1 ); + util::set_dvar_int_if_unset( "scr_ph_usePropHudServer", true ); + util::set_dvar_int_if_unset( "scr_ph_debugLastLock", 0 ); + util::set_dvar_int_if_unset( "scr_ph_debugCanLock", 0 ); + util::set_dvar_int_if_unset( "scr_ph_cancelCountdown", 0 ); + util::set_dvar_int_if_unset( "scr_ph_pauseCountdown", 0 ); + util::set_dvar_int_if_unset( "scr_ph_debugSpeed", 0 ); + util::set_dvar_int_if_unset( "scr_ph_debugShowPlayers", 0 ); + util::set_dvar_int_if_unset( "scr_ph_debugShowTargets", 0 ); + util::set_dvar_int_if_unset( "scr_ph_debugDoMinigameType", 0 ); + SetDvar( "scr_ph_showModels", 0 ); + SetDvar( "scr_ph_placementMode", 0 ); + SetDvar( "scr_ph_placementWrite", 0 ); + SetDvar( "scr_ph_placementWriteGSH", 0 ); + SetDvar( "scr_ph_placementWriteEmpty", 0 ); + SetDvar( "scr_ph_makeClone", 0 ); + SetDvar( "scr_ph_makeClones", 0 ); + SetDvar( "scr_ph_debugPlayerCard", 0 ); + + if ( GetDvarInt( "scr_ph_holdTimelimitZero", 0 ) != 0 ) + AddDebugCommand( "gametype_setting timelimit 0" ); + + AddDevguiCommand( "Prop/Remove Debug Upper Right:1", "toggle scr_ph_removeDebugUR 1 0" ); + AddDevguiCommand( "Prop/Timelimit:2/Clear:1", "gametype_setting timelimit 0; set scr_ph_holdTimelimitZero 1" ); + AddDevguiCommand( "Prop/Timelimit:2/Reset:2", "gametype_setting timelimit " + 4 + "; set scr_ph_holdTimelimitZero 0" ); + AddDevguiCommand( "Prop/Timelimit:2/15 Sec:3", "gametype_setting timelimit " + 0.25 + "; set scr_ph_holdTimelimitZero 0" ); + AddDevguiCommand( "Prop/Timelimit:2/Cancel Prop Hide:4", "set scr_ph_cancelCountdown 1" ); + AddDevguiCommand( "Prop/Timelimit:2/Pause Prop Hide:5", "toggle scr_ph_pauseCountdown 1 0" ); + AddDevguiCommand( "Prop/Toggle Unlimited:3/Clones:1", "toggle scr_ph_moreClones 1 0" ); + AddDevguiCommand( "Prop/Toggle Unlimited:3/Flashes:2", "toggle scr_ph_moreFlashes 1 0" ); + AddDevguiCommand( "Prop/Toggle Unlimited:3/Changes:3", "toggle scr_ph_moreChanges 1 0" ); + AddDevguiCommand( "Prop/Toggle Unlimited:3/Boost:4", "toggle scr_ph_moreBoost 1 0" ); + AddDevguiCommand( "Prop/Toggle Unlimited:3/Pings:5", "toggle scr_ph_morePings 1 0" ); + AddDevguiCommand( "Prop/Team Change:4/Allow", "set scr_ph_allowTeamChange 1" ); + AddDevguiCommand( "Prop/Team Change:4/Block", "set scr_ph_allowTeamChange 0" ); + AddDevguiCommand( "Prop/Spawning:5/Allow", "set scr_ph_alwaysSpawn 1" ); + AddDevguiCommand( "Prop/Spawning:5/Block", "set scr_ph_alwaysSpawn 0" ); + AddDevguiCommand( "Prop/Find Models:6", "toggle r_showSModelNames 200 0; toggle r_drawInfo 3 0; toggle ui_enabled 0 1" ); + AddDevguiCommand( "Prop/Debug:8/Health:1", "toggle scr_ph_showHealth 1 0" ); + AddDevguiCommand( "Prop/Debug:8/Speed:2", "toggle scr_ph_debugSpeed 1 0" ); + AddDevguiCommand( "Prop/Debug:8/Models:3", "set scr_ph_showModels 1" ); + AddDevguiCommand( "Prop/Debug:8/Make All Clones:4", "set scr_ph_makeClones 1" ); + AddDevguiCommand( "Prop/Debug:8/Make One Clone:5", "set scr_ph_makeClone 1" ); + AddDevguiCommand( "Prop/Debug:8/Last Lock:5", "toggle scr_ph_debugLastLock 1 0" ); + AddDevguiCommand( "Prop/Debug:8/Can Lock:6", "toggle scr_ph_debugCanLock 1 0" ); + AddDevguiCommand( "Prop/Debug:8/Player Boxes:7", "toggle scr_ph_debugShowPlayers 1 0" ); + AddDevguiCommand( "Prop/Debug:8/Targets:8", "toggle scr_ph_debugShowTargets 1 0" ); + AddDevguiCommand( "Prop/Debug:8/Player Card:9", "set scr_ph_debugPlayerCard 1" ); + AddDevguiCommand( "Prop/Prop Setup:9/Toggle Placement Mode:1", "toggle scr_ph_placementMode 1 0" ); + AddDevguiCommand( "Prop/Prop Setup:9/Write Prop Data CSV File:2", "set scr_ph_placementWrite 1" ); + AddDevguiCommand( "Prop/Prop Setup:9/Write Ordered Prop GSH File:3", "set scr_ph_placementWriteGSH 1" ); + AddDevguiCommand( "Prop/Prop Setup:9/Write Empty Prop Files:4", "set scr_ph_placementWriteEmpty 1" ); + AddDevguiCommand( "Prop/Toggle Server Hud:10", "toggle scr_ph_usePropHudServer 1 0" ); + AddDevguiCommand( "Prop/Minigame:8/Toggle:1", "toggle scr_prop_minigame 1 0" ); + AddDevguiCommand( "Prop/Minigame:8/Toggle Force Type:2", "toggle scr_ph_debugDoMinigameType 2 1 0" ); + while ( true ) + { + if ( ( isdefined( level.prematch_over ) && level.prematch_over ) ) + { + level.allow_teamchange = "" + GetDvarInt( "scr_ph_allowTeamChange", 0 ); + level.alwaysAllowSpawn = GetDvarInt( "scr_ph_alwaysSpawn", 0 ) != 0; + } + + if ( GetDvarInt( "scr_ph_usePropHudServer", 0 ) != SERVER_HUD && IsDefined( level.players ) ) + { + SERVER_HUD = GetDvarInt( "scr_ph_usePropHudServer", 0 ); + if ( !IsDefined( level.players[0].changePropKey ) ) + { + IPrintLnBold( "Map Restart to show hud" ); + } + else + { + foreach ( player in level.players ) + { + if ( IsDefined( player.team ) && player util::isProp() ) + player prop_controls::propAbilityKeysVisible( SERVER_HUD, true ); + } + level.elim_hud.alpha = SERVER_HUD; + } + } + + if ( GetDvarInt( "scr_ph_moreChanges", 0 ) != MORE_CHANGES && IsDefined( level.players ) ) + { + foreach ( player in level.players ) + { + if ( player util::isProp() ) + { + MORE_CHANGES = GetDvarInt( "scr_ph_moreChanges", 0 ); + player.unlimitedChanges = !( isdefined( player.unlimitedChanges ) && player.unlimitedChanges ); + player IPrintLnBold( (player.unlimitedChanges ? "Unlimited changes" : "Finite changes") ); + } + } + } + + if ( GetDvarInt( "scr_ph_moreClones", 0 ) != MORE_CLONES && IsDefined( level.players ) ) + { + foreach ( player in level.players ) + { + if ( player util::isProp() ) + { + MORE_CLONES = GetDvarInt( "scr_ph_moreClones", 0 ); + player.unlimitedClones = !( isdefined( player.unlimitedClones ) && player.unlimitedClones ); + player IPrintLnBold( (player.unlimitedClones ? "Unlimited clones" : "Finite clones") ); + } + } + } + + if ( GetDvarInt( "scr_ph_moreFlashes", 0 ) != MORE_FLASHES && IsDefined( level.players ) ) + { + foreach ( player in level.players ) + { + if ( player util::isProp() ) + { + MORE_FLASHES = GetDvarInt( "scr_ph_moreFlashes", 0 ); + player.unlimitedFlashes = !( isdefined( player.unlimitedFlashes ) && player.unlimitedFlashes ); + player IPrintLnBold( (player.unlimitedFlashes ? "Unlimited flashes" : "Finite flashes") ); + } + } + } + + if ( GetDvarInt( "scr_ph_moreBoost", 0 ) != MORE_BOOST ) + { + MORE_BOOST = GetDvarInt( "scr_ph_moreBoost", 0 ); + if ( MORE_BOOST ) + { + SetDvar( "doublejump_time_before_recharge", 1 ); + SetDvar( "doublejump_time_before_recharge_fast", 1 ); + SetDvar( "playerEnergy_restRate", 10000 ); + } + else + { + SetDvar( "doublejump_time_before_recharge", 1600 ); + SetDvar( "doublejump_time_before_recharge_fast", 1000 ); + SetDvar( "playerEnergy_restRate", 400 ); + IPrintLnBold( (MORE_BOOST ? "Unlimited boost" : "Finite boost") ); + } + } + + if ( GetDvarInt( "scr_ph_morePings", 0 ) != MORE_PINGS && IsDefined( level.players ) ) + { + foreach ( player in level.players ) + { + if ( player prop::isHunter() ) + { + MORE_PINGS = GetDvarInt( "scr_ph_morePings", 0 ); + player.unlimitedPings = !( isdefined( player.unlimitedPings ) && player.unlimitedPings ); + player IPrintLnBold( (player.unlimitedPings ? "Unlimited pings" : "Finite pings") ); + } + } + } + + isRemoved = GetDvarInt( "scr_ph_removeDebugUR", 0 ); + if ( isRemoved != REMOVE_UPPER_RIGHT_PRINTS_DEBUG ) + { + REMOVE_UPPER_RIGHT_PRINTS_DEBUG = isRemoved; + + toggleDebugUpperRight( !isRemoved ); + } + + inPlacementMode = GetDvarInt( "scr_ph_placementMode", 0 ); + if ( inPlacementMode != PLACEMENT_MODE ) + { + PLACEMENT_MODE = inPlacementMode; + + result = toggleDebugPlacmentMode( inPlacementMode ); + if ( !result ) + PLACEMENT_MODE = !inPlacementMode; + + if ( PLACEMENT_MODE ) + level.drown_damage = 0; + else + level.drown_damage = CONST_SWIM_DAMAGE; + } + + if ( GetDvarInt( "scr_ph_placementWrite", 0 ) != 0 ) + { + writePlacementFile(); + + SetDvar( "scr_ph_placementWrite", 0 ); + } + + if ( GetDvarInt( "scr_ph_placementWriteGSH", 0 ) != 0 ) + { + writePlacementGSHFile(); + + SetDvar( "scr_ph_placementWriteGSH", 0 ); + } + + if ( GetDvarInt( "scr_ph_placementWriteEmpty", 0 ) != 0 ) + { + writeEmptyPlacementFiles(); + + SetDvar( "scr_ph_placementWriteEmpty", 0 ); + } + + if ( GetDvarInt( "scr_ph_debugPlayerCard", 0 ) != 0 ) + { + if ( isdefined( level.players ) && isdefined( level.players[0] ) ) + level thread prop::playerCardSplash( &"SCORE_LAST_ALIVE", level.players[0] ); + + SetDvar( "scr_ph_debugPlayerCard", 0 ); + } + + if ( GetDvarInt( "scr_ph_showHealth", 0 ) != 0 ) + { + showHealth(); + } + + if ( GetDvarInt( "scr_ph_debugGroundTrace", 0) ) + { + debugGroundTrace(); + } + + if ( GetDvarInt( "scr_ph_showModels", 0 ) != 0 ) + { + showModels(); + + SetDvar( "scr_ph_showModels", 0 ); + } + + if ( GetDvarInt( "scr_ph_debugCanLock", 0 ) != 0 ) + { + if ( isdefined( level.players ) && isdefined( level.players[0] ) ) + level.players[0] prop_controls::canLock(); + } + + if ( GetDvarInt( "scr_ph_debugLastLock", 0 ) != 0 || GetDvarInt( "scr_ph_debugCanLock", 0 ) != 0 ) + { + debugDrawLastLock(); + } + + if ( GetDvarInt( "scr_ph_makeClone", 0 ) != 0 ) + { + makeClone(); + + SetDvar( "scr_ph_makeClone", 0 ); + } + + if ( GetDvarInt( "scr_ph_makeClones", 0 ) != 0 ) + { + makeClones(); + + SetDvar( "scr_ph_makeClones", 0 ); + } + + if ( GetDvarInt( "scr_ph_cancelCountdown", 0 ) != 0 && IsDefined( level.players ) ) + { + foreach ( player in level.players ) + player notify( "cancelCountdown" ); + + SetDvar( "scr_ph_cancelCountdown", 0 ); + } + + if ( GetDvarInt( "scr_ph_debugSpeed", 0 ) != 0 ) + { + showPlayerSpeed(); + } + + if ( GetDvarInt( "scr_ph_debugShowPlayers", 0 ) != 0 ) + { + showPlayers(); + } + + if ( GetDvarInt( "scr_ph_debugShowTargets", 0 ) != 0 ) + { + showTargets(); + } + + if ( GetDvarInt( "scr_prop_minigame", 1 ) != MINIGAME_ON && IsDefined( level.players ) && level.players.size > 0 ) + { + MINIGAME_ON = GetDvarInt( "scr_prop_minigame", 1 ); + IPrintLnBold( (MINIGAME_ON ? "Minigame On" : "Minigame Off") ); + } + + if ( GetDvarInt( "scr_ph_debugDoMinigameType", 0 ) != MINIGAME_FORCE_TYPE && IsDefined( level.players ) && level.players.size > 0 ) + { + MINIGAME_FORCE_TYPE = GetDvarInt( "scr_ph_debugDoMinigameType", 0 ); + if ( MINIGAME_FORCE_TYPE == 2 ) + IPrintLnBold( "Minigame Type Hunt" ); + else if ( MINIGAME_FORCE_TYPE == 1 ) + IPrintLnBold( "Minigame Type Chase" ); + else + IPrintLnBold( "Minigame Type Random" ); + } + + {wait(.05);}; + } +} + +function toggleDebugUpperRight( enabled ) +{ + SetDvar( "com_statmon", enabled ); + SetDvar( "con_minicon", enabled ); + SetDvar( "cg_drawfps", enabled ); + SetDvar( "cg_drawTime", enabled ); + SetDvar( "cg_drawviewpos", enabled ); + SetDvar( "cg_drawBuildname", enabled ); + SetDvar( "cg_drawLimits", enabled ); +} + +function toggleDebugPlacmentMode( enabled ) +{ + if ( !IsDefined( level.players ) || level.players.size == 0 ) + return false; + + player = level.players[0]; + + if ( !IsDefined( player ) || !IsAlive( player ) || IsDefined( player.placementOffset ) || !IsDefined( player.prop ) ) + return false; + + if ( enabled ) + { + player addPlacementHud(); + } + else + { + player removePlacementHud(); + } + + return true; +} + +function addUpperRightHudElemDebug( color, label, value, text, textPC ) +{ + hudElem = prop_controls::addUpperRightHudElem( label, value, text, textPC ); + hudElem.alpha = 0.5; + hudElem.color = color; + + return hudElem; +} + +function addPlacementHud() +{ + self prop_controls::cleanupPropControlsHUD(); + self prop_controls::cleanupPropKeyBindings(); + + if ( self IsSplitscreen() ) + self.currentHudY = -10; + else + self.currentHudY = -80; + + self.currentPropIndex = getPropIndex(self.prop.info.modelName); + + white = ( 1, 1, 1 ); + red = ( 1, 0, 0 ); + green = ( 0, 1, 0 ); + blue = ( 0, 0.5, 1 ); + + self.placementRange = addUpperRightHudElemDebug( white, &"Range: &&1", self.prop.info.propRange ); + self.placementHeight = addUpperRightHudElemDebug( white, &"Height: &&1", self.prop.info.propHeight ); + self.placementOffsetAngR = addUpperRightHudElemDebug( white, &"Angle Offset Roll: &&1", self.prop.info.anglesOffset[2] ); + self.placementOffsetAngY = addUpperRightHudElemDebug( white, &"Angle Offset Yaw: &&1", self.prop.info.anglesOffset[1] ); + self.placementOffsetAngP = addUpperRightHudElemDebug( white, &"Angle Offset Pitch: &&1", self.prop.info.anglesOffset[0] ); + self.placementOffsetOrgZ = addUpperRightHudElemDebug( blue, &"Origin Offset Z: &&1", self.prop.info.xyzOffset[2] ); + self.placementOffsetOrgY = addUpperRightHudElemDebug( green, &"Origin Offset Y: &&1", self.prop.info.xyzOffset[1] ); + self.placementOffsetOrgX = addUpperRightHudElemDebug( red, &"Origin Offset X: &&1", self.prop.info.xyzOffset[0] ); + self.placementScale = addUpperRightHudElemDebug( white, &"Scale: &&1", self.prop.info.propScale ); + self.placementHealth = addUpperRightHudElemDebug( white, &"Health: &&1", self.prop.info.propSize ); + self.placementSize = addUpperRightHudElemDebug( white, undefined, undefined, "Size: " + self.prop.info.propSizeText ); + self.placementModel = addUpperRightHudElemDebug( white, undefined, undefined, "Model [" + self.currentPropIndex + "]" + self.prop.info.modelName ); + + self.placementHud = Array( self.placementModel, self.placementSize, self.placementHealth, self.placementScale, self.placementOffsetOrgX, self.placementOffsetOrgY, self.placementOffsetOrgZ, self.placementOffsetAngP, self.placementOffsetAngY, self.placementOffsetAngR, self.placementHeight, self.placementRange ); + self.placementIndex = 0; + + self setupPlacementBindings(); + self thread drawPlacementDebug(); + self thread handlePlacementInput(); + self thread removePlacementHudOnDeath(); +} + +function removePlacementHud() +{ + self notify( "removePlacementHud" ); + + prop_controls::safeDestroy( self.placementModel ); + prop_controls::safeDestroy( self.placementSize ); + prop_controls::safeDestroy( self.placementHealth ); + prop_controls::safeDestroy( self.placementScale ); + prop_controls::safeDestroy( self.placementOffsetOrgX ); + prop_controls::safeDestroy( self.placementOffsetOrgY ); + prop_controls::safeDestroy( self.placementOffsetOrgZ ); + prop_controls::safeDestroy( self.placementOffsetAngP ); + prop_controls::safeDestroy( self.placementOffsetAngY ); + prop_controls::safeDestroy( self.placementOffsetAngR ); + prop_controls::safeDestroy( self.placementHeight ); + prop_controls::safeDestroy( self.placementRange ); + + self cleanupPlacementBindings(); + self prop_controls::propControlsHUD(); + self prop_controls::setupKeyBindings(); +} + +function removePlacementHudOnDeath() +{ + self endon( "game_ended" ); + self endon( "disconnect" ); + self endon( "removePlacementHud" ); + + self waittill( "death" ); + + SetDvar( "scr_ph_placementMode", 0 ); +} + +function debugAxis( origin, angles, size, alpha, depthTest, duration ) +{ + axisX = AnglesToForward( angles ) * size; + axisY = AnglesToRight( angles ) * size; + axisZ = AnglesToUp( angles ) * size; + + Line( origin, origin + axisX, ( 1, 0, 0 ), alpha, false, duration ); + Line( origin, origin + axisY, ( 0, 1, 0 ), alpha, false, duration ); + Line( origin, origin + axisZ, ( 0, 0, 1 ), alpha, false, duration ); +} + +function drawPlacementDebug() +{ + self endon( "removePlacementHud" ); + + while ( true ) + { + debugAxis( self.origin, self.angles, 100, 1, false, 1 ); + Box( self.origin, self GetMins(), self GetMaxs(), self.angles[1], ( 1, 0, 1 ), 1, false, 1 ); + {wait(.05);}; + } +} + +function handlePlacementInput() +{ + self endon( "removePlacementHud" ); + + self changeSelectedPlacementOption( 0 ); + + while ( 1 ) + { + msg = self util::waittill_any_return( "up", "down", "left", "right", "shot" ); + + if ( !IsDefined( msg ) ) + continue; + + if ( msg == "up" ) + self changeSelectedPlacementOptionHold( -1 ); + else if ( msg == "down" ) + self changeSelectedPlacementOptionHold( 1 ); + else if ( msg == "right" ) + self adjustSelectedPlacementOptionHold( 1 ); + else if ( msg == "left" ) + self adjustSelectedPlacementOptionHold( -1 ); + else if ( msg == "shot" ) + debugShotTest(); + } +} + +function changeSelectedPlacementOptionHold( val ) +{ + self endon( "letgo" ); + + changeSelectedPlacementOption( val ); + + wait 0.5; + + while ( true ) + { + changeSelectedPlacementOption( val ); + + wait 0.05; + } +} + +function changeSelectedPlacementOption( val ) +{ + hudElem = self.placementHud[self.placementIndex]; + hudElem.alpha = 0.5; + hudElem.fontscale = 1; + + self.placementIndex += val; + if ( self.placementIndex >= self.placementHud.size ) + self.placementIndex = 0; + else if ( self.placementIndex < 0 ) + self.placementIndex = self.placementHud.size - 1; + + hudElem = self.placementHud[self.placementIndex]; + hudElem.alpha = 1; + hudElem.fontscale = 1.3; +} + +function adjustSelectedPlacementOptionHold( val ) +{ + self endon( "letgo" ); + + adjustSelectedPlacementOption( val ); + + wait 0.5; + + while ( true ) + { + adjustSelectedPlacementOption( val ); + + wait 0.05; + } +} + +function setCurrentPropIndex(inVal) +{ + tempIndex = self.currentPropIndex + inVal; + + if (tempIndex >= level.propIndex.size) + tempIndex = 0; + else if (tempIndex < 0) + tempIndex = (level.propIndex.size-1); + + self.currentPropIndex = tempIndex; +} + +function getPropIndex(inModelName) +{ + for (index = 0; index < level.propIndex.size; index++) + { + if ( level.propList[level.propIndex[index][0]][level.propIndex[index][1]].modelName == inModelName ) + return index; + } +} + +function adjustSelectedPlacementOption( val ) +{ + hudElem = self.placementHud[self.placementIndex]; + + if ( hudElem == self.placementModel ) + { + setCurrentPropIndex(val); + + self.prop.info = level.propList[level.propIndex[self.currentPropIndex][0]][level.propIndex[self.currentPropIndex][1]]; + + prop_controls::propChangeTo( self.prop.info ); + + self.placementModel SetText( "Model (" + self.currentPropIndex + "): " + self.prop.info.modelName ); + self.placementSize SetText( "Size: " + self.prop.info.propSizeText ); + self.placementHealth SetValue( self.prop.info.propSize ); + self.placementScale SetValue( self.prop.info.propScale ); + self.placementOffsetOrgX SetValue( self.prop.info.xyzOffset[0] ); + self.placementOffsetOrgY SetValue( self.prop.info.xyzOffset[1] ); + self.placementOffsetOrgZ SetValue( self.prop.info.xyzOffset[2] ); + self.placementOffsetAngP SetValue( self.prop.info.anglesOffset[0] ); + self.placementOffsetAngY SetValue( self.prop.info.anglesOffset[1] ); + self.placementOffsetAngR SetValue( self.prop.info.anglesOffset[2] ); + self.placementHeight SetValue( self.prop.info.propHeight ); + self.placementRange SetValue( self.prop.info.propRange ); + } + else if ( hudElem == self.placementSize || hudElem == self.placementHealth ) + { + sizes = Array( "xsmall", "small", "medium", "large", "xlarge", "remove" ); + index = 0; + for ( i = 0; i < sizes.size; i++ ) + { + if ( sizes[i] == self.prop.info.propSizeText ) + { + index = i; + break; + } + } + + index += val; + if ( index < 0 ) + index = sizes.size -1; + else if ( index >= sizes.size ) + index = 0; + + self.prop.info.propSizeText = sizes[index]; + self.prop.info.propSize = prop::getPropSize( self.prop.info.propSizeText ); + + self.placementSize SetText( "Size: " + self.prop.info.propSizeText ); + self.placementHealth SetValue( self.prop.info.propSize ); + self.health = self.prop.info.propSize; + self.maxhealth = self.health; + } + else if ( hudElem == self.placementScale ) + { + CONST_MIN_SCALE = 0.1; + CONST_MAX_SCALE = 10.0; + scaleAdjust = 0.01; + + self.prop.info.propScale += (scaleadjust * val); + self.prop.info.propScale = math::clamp( self.prop.info.propScale, CONST_MIN_SCALE, CONST_MAX_SCALE ); + self.prop SetScale( self.prop.info.propScale, true ); + self.placementScale SetValue( self.prop.info.propScale ); + } + else if ( hudElem == self.placementOffsetOrgX ) + { + self.prop Unlink(); + self.prop.info.xyzOffset = ( self.prop.info.xyzOffset[0] + val, self.prop.info.xyzOffset[1], self.prop.info.xyzOffset[2] ); + self.prop.xyzOffset = self.prop.info.xyzOffset; + self.placementOffsetOrgX SetValue( self.prop.info.xyzOffset[0] ); + linkAndApply(); + } + else if ( hudElem == self.placementOffsetOrgY ) + { + self.prop Unlink(); + self.prop.info.xyzOffset = ( self.prop.info.xyzOffset[0], self.prop.info.xyzOffset[1] + val, self.prop.info.xyzOffset[2] ); + self.prop.xyzOffset = self.prop.info.xyzOffset; + self.placementOffsetOrgY SetValue( self.prop.info.xyzOffset[1] ); + linkAndApply(); + } + else if ( hudElem == self.placementOffsetOrgZ ) + { + self.prop Unlink(); + self.prop.info.xyzOffset = ( self.prop.info.xyzOffset[0], self.prop.info.xyzOffset[1], self.prop.info.xyzOffset[2] + val ); + self.prop.xyzOffset = self.prop.info.xyzOffset; + self.placementOffsetOrgZ SetValue( self.prop.info.xyzOffset[2] ); + linkAndApply(); + } + else if ( hudElem == self.placementOffsetAngP ) + { + self.prop Unlink(); + self.prop.info.anglesOffset = ( self.prop.info.anglesOffset[0] + val, self.prop.info.anglesOffset[1], self.prop.info.anglesOffset[2] ); + self.prop.anglesOffset = self.prop.info.anglesOffset; + self.placementOffsetAngP SetValue( self.prop.info.anglesOffset[0] ); + linkAndApply(); + } + else if ( hudElem == self.placementOffsetAngY ) + { + self.prop Unlink(); + self.prop.info.anglesOffset = ( self.prop.info.anglesOffset[0], self.prop.info.anglesOffset[1] + val, self.prop.info.anglesOffset[2] ); + self.prop.anglesOffset = self.prop.info.anglesOffset; + self.placementOffsetAngY SetValue( self.prop.info.anglesOffset[1] ); + linkAndApply(); + } + else if ( hudElem == self.placementOffsetAngR ) + { + self.prop Unlink(); + self.prop.info.anglesOffset = ( self.prop.info.anglesOffset[0], self.prop.info.anglesOffset[1], self.prop.info.anglesOffset[2] + val ); + self.prop.anglesOffset = self.prop.info.anglesOffset; + self.placementOffsetAngR SetValue( self.prop.info.anglesOffset[2] ); + linkAndApply(); + } + else if ( hudElem == self.placementHeight ) + { + adjust = 10; + + self.prop.info.propHeight += (adjust * val); + self.prop.info.propHeight = math::clamp( self.prop.info.propHeight, -30, 40 ); + self.thirdPersonHeightOffset = self.prop.info.propHeight; + self SetClientThirdPerson( true, self.thirdPersonRange, self.thirdPersonHeightOffset ); + self.placementHeight SetValue( self.prop.info.propHeight ); + } + else if ( hudElem == self.placementRange ) + { + adjust = 10; + + self.prop.info.propRange += (adjust * val); + self.prop.info.propRange = math::clamp( self.prop.info.propRange, 50, 360 ); + self.thirdPersonRange = self.prop.info.propRange; + self SetClientThirdPerson( true, self.thirdPersonRange, self.thirdPersonHeightOffset ); + self.placementRange SetValue( self.prop.info.propRange ); + } +} + +function linkAndApply() +{ + self.prop.origin = self.propEnt.origin; + self prop::applyXYZOffset(); + self prop::applyAnglesOffset(); + self.prop LinkTo( self.propEnt ); +} + +function setupPlacementBindings() // self == player +{ + self prop_controls::NotifyOnPlayerCommand( "up", "+actionslot 1" ); + self prop_controls::NotifyOnPlayerCommand( "down", "+actionslot 2" ); + self prop_controls::NotifyOnPlayerCommand( "left", "+actionslot 3" ); + self prop_controls::NotifyOnPlayerCommand( "right", "+actionslot 4" ); + self prop_controls::NotifyOnPlayerCommand( "letgo", "-actionslot 1" ); + self prop_controls::NotifyOnPlayerCommand( "letgo", "-actionslot 2" ); + self prop_controls::NotifyOnPlayerCommand( "letgo", "-actionslot 3" ); + self prop_controls::NotifyOnPlayerCommand( "letgo", "-actionslot 4" ); + self prop_controls::NotifyOnPlayerCommand( "shot", "+attack" ); + self prop_controls::NotifyOnPlayerCommand( "zoomOut", "weapnext" ); + self prop_controls::NotifyOnPlayerCommand( "zoomIn", "+usereload" ); + self prop_controls::NotifyOnPlayerCommand( "zoomIn", "+activate" ); +} + +function cleanupPlacementBindings() // self == player +{ + self prop_controls::NotifyOnPlayerCommandRemove( "up", "+actionslot 1" ); + self prop_controls::NotifyOnPlayerCommandRemove( "down", "+actionslot 2" ); + self prop_controls::NotifyOnPlayerCommandRemove( "left", "+actionslot 3" ); + self prop_controls::NotifyOnPlayerCommandRemove( "right", "+actionslot 4" ); + self prop_controls::NotifyOnPlayerCommandRemove( "letgo", "-actionslot 1" ); + self prop_controls::NotifyOnPlayerCommandRemove( "letgo", "-actionslot 2" ); + self prop_controls::NotifyOnPlayerCommandRemove( "letgo", "-actionslot 3" ); + self prop_controls::NotifyOnPlayerCommandRemove( "letgo", "-actionslot 4" ); + self prop_controls::NotifyOnPlayerCommandRemove( "shot", "+attack" ); + self prop_controls::NotifyOnPlayerCommandRemove( "zoomOut", "weapnext" ); + self prop_controls::NotifyOnPlayerCommandRemove( "zoomIn", "+usereload" ); + self prop_controls::NotifyOnPlayerCommandRemove( "zoomIn", "+activate" ); +} + +function offsetIsDefined( vec ) +{ + return isDefined( vec ) && ( vec[0] != 0 || vec[1] != 0 || vec[2] != 0 ); +} + +function heightIsDefined( propInfo ) +{ + return isdefined( propInfo.propHeight ) && propInfo.propHeight != prop::getThirdPersonHeightOffsetForSize( propInfo.propSize ); +} + +function rangeIsDefined( propInfo ) +{ + return isdefined( propInfo.propRange ) && propInfo.propRange != prop::getThirdPersonRangeForSize( propInfo.propSize ); +} + +function propFPrintLn( file, propInfo ) +{ + propStr = "" + propInfo.modelName + "," + propInfo.propSizeText + "," + propInfo.propScale; + + if ( offsetIsDefined( propInfo.xyzOffset ) ) + propStr += "," + propInfo.xyzOffset[0] + "," + propInfo.xyzOffset[1] + "," + propInfo.xyzOffset[2]; + else + propStr += ",0,0,0"; + + if ( offsetIsDefined( propInfo.anglesOffset ) ) + propStr += "," + propInfo.anglesOffset[0] + "," + propInfo.anglesOffset[1] + "," + propInfo.anglesOffset[2]; + else + propStr += ",0,0,0"; + + if ( heightisDefined( propInfo ) ) + propStr += "," + propInfo.propHeight; + else + propStr += "," + prop::getThirdPersonHeightOffsetForSize( propInfo.propSize ); + + if ( rangeIsDefined( propInfo ) ) + propStr += "," + propInfo.propRange; + else + propStr += "," + prop::getThirdPersonRangeForSize( propInfo.propSize ); + + FPrintLn( file, propStr ); +} + +function propGSHFPrintLn( file, propInfo ) +{ + propStr = "#precache( \"model\", \"" + propInfo.modelName + "\" );"; + + FPrintLn( file, propStr ); +} + +function FPrintPropsOfSize( file, propSizeText ) +{ + foreach ( sizeType in level.propList ) + { + foreach ( propInfo in sizeType ) + { + if ( propInfo.propSizeText == propSizeText ) + propFPrintLn( file, propInfo ); + } + } +} + +function FPrintPropsOfSizeGSH( file, propSizeText ) +{ + foreach ( sizeType in level.propList ) + { + foreach ( propInfo in sizeType ) + { + if ( propInfo.propSizeText == propSizeText ) + propGSHFPrintLn( file, propInfo ); + } + } +} + +function writePlacementFileHeader( file, filename_base ) +{ + filename_stringtable = filename_base + ".csv"; + filename_header = filename_base + ".gsh"; + filename_zpkg = filename_base + ".zpkg"; + + filename_zone = level.script + ".zone"; + filename_gsc = level.script + ".gsc"; + + dir_zone = "\\game\\share\\zone_source\\"; + dir_gsc = "\\game\\share\\raw\\scripts\\mp\\"; + dir_csv = "\\game\\share\\raw\\gamedata\\tables\\mp\\"; + + FPrintLn( file, "#prop name,#prop size,#scale,#origin offset x,#origin offset y,#origin offset z,#angle offset pitch,#angle offset yaw,#angle offset roll,#height offset,#range offset" ); + FPrintLn( file, "" ); + FPrintLn( file, "#SETUP:" ); + FPrintLn( file, "# 1) Fill in the file " + filename_stringtable + " with model names" ); + FPrintLn( file, "# 2) Fill in the file " + filename_header + " with model names" ); + FPrintLn( file, "# 3) Put the file " + filename_zpkg + " in " + dir_zone ); + FPrintLn( file, "# 4) Put the file " + filename_stringtable + " in " + dir_csv ); + FPrintLn( file, "# 5) Put the file " + filename_header + " in " + dir_gsc ); + FPrintLn( file, "# 6) Add the 3 files to perforce" ); + FPrintLn( file, "# 7) Add the following to " + filename_zone + ": include," + filename_base ); + FPrintLn( file, "# 8) Add the following to " + filename_gsc + ": #insert scripts\\mp\\" + filename_header + ";" ); + FPrintLn( file, "" ); + FPrintLn( file, "#NOTE: prop size correlates to amount of health" ); + FPrintLn( file, "" ); +} + +function writePlacementFile() +{ + platform = "pc"; + if ( level.orbis ) + platform = "orbis"; + else if ( level.durango ) + platform = "durango"; + + filename_base = level.script + "_ph"; + filename_stringtable = filename_base + ".csv"; + + dir_scriptdata = "game\\" + platform + "\\scriptdata\\"; + dir_csv = "\\game\\share\\raw\\gamedata\\tables\\mp\\"; + + file = OpenFile( filename_stringtable, "write" ); + if ( file == -1 ) + { + iprintlnbold( "Error: can't open " + dir_scriptdata + filename_stringtable + " for writing" ); + Println( "Error: can't open " + dir_scriptdata + filename_stringtable + " for writing" ); + return; + } + + writePlacementFileHeader( file, filename_base ); + + FPrintLn( file, "#Extra Small Props" ); + FPrintPropsOfSize( file, "xsmall" ); + FPrintLn( file, "" ); + + FPrintLn( file, "#Small Props" ); + FPrintPropsOfSize( file, "small" ); + FPrintLn( file, "" ); + + FPrintLn( file, "#Medium Props" ); + FPrintPropsOfSize( file, "medium" ); + FPrintLn( file, "" ); + + FPrintLn( file, "#Large Props" ); + FPrintPropsOfSize( file, "large" ); + FPrintLn( file, "" ); + + FPrintLn( file, "#Extra Large Props" ); + FPrintPropsOfSize( file, "xlarge" ); + + iprintlnbold( "Wrote " + dir_scriptdata + filename_stringtable + "; move to " + dir_csv ); + PrintLn( "Wrote " + dir_scriptdata + filename_stringtable + "; move to " + dir_csv ); + + CloseFile( file ); +} + +function writePlacementGSHFile() +{ + platform = "pc"; + if ( level.orbis ) + platform = "orbis"; + else if ( level.durango ) + platform = "durango"; + + filename_base = level.script + "_ph"; + filename_stringtable = filename_base + ".gsh"; + + dir_scriptdata = "game\\" + platform + "\\scriptdata\\"; + dir_csv = "\\game\\share\\raw\\scripts\\mp\\"; + + file = OpenFile( filename_stringtable, "write" ); + if ( file == -1 ) + { + iprintlnbold( "Error: can't open " + dir_scriptdata + filename_stringtable + " for writing" ); + Println( "Error: can't open " + dir_scriptdata + filename_stringtable + " for writing" ); + return; + } + + FPrintLn( file, "// Extra Small Props" ); + FPrintPropsOfSizeGSH( file, "xsmall" ); + FPrintLn( file, "" ); + + FPrintLn( file, "// Small Props" ); + FPrintPropsOfSizeGSH( file, "small" ); + FPrintLn( file, "" ); + + FPrintLn( file, "// Medium Props" ); + FPrintPropsOfSizeGSH( file, "medium" ); + FPrintLn( file, "" ); + + FPrintLn( file, "// Large Props" ); + FPrintPropsOfSizeGSH( file, "large" ); + FPrintLn( file, "" ); + + FPrintLn( file, "// Extra Large Props" ); + FPrintPropsOfSizeGSH( file, "xlarge" ); + + iprintlnbold( "Wrote " + dir_scriptdata + filename_stringtable + "; move to " + dir_csv ); + PrintLn( "Wrote " + dir_scriptdata + filename_stringtable + "; move to " + dir_csv ); + + CloseFile( file ); +} + +function writeEmptyPlacementFiles() +{ + platform = "pc"; + if ( level.orbis ) + platform = "orbis"; + else if ( level.durango ) + platform = "durango"; + + filename_base = level.script + "_ph"; + filename_stringtable = filename_base + ".csv"; + filename_header = filename_base + ".gsh"; + filename_zpkg = filename_base + ".zpkg"; + + filename_zone = level.script + ".zone"; + filename_gsc = level.script + ".gsc"; + + dir_scriptdata = "game\\" + platform + "\\scriptdata\\"; + dir_zone = "\\game\\share\\zone_source\\"; + dir_gsc = "\\game\\share\\raw\\scripts\\mp\\"; + dir_csv = "\\game\\share\\raw\\gamedata\\tables\\mp\\"; + + // _ph.csv + file = OpenFile( filename_stringtable, "write" ); + if ( file == -1 ) + { + iprintlnbold( "Error: can't open " + dir_scriptdata + filename_stringtable + " for writing" ); + Println( "Error: can't open " + dir_scriptdata + filename_stringtable + " for writing" ); + return; + } + + writePlacementFileHeader( file, filename_base ); + + FPrintLn( file, "#Extra Small Props" ); + FPrintLn( file, "some_xsmall_model,xsmall" ); + FPrintLn( file, "some_xsmall_model,xsmall" ); + FPrintLn( file, "some_xsmall_model,xsmall" ); + FPrintLn( file, "" ); + + FPrintLn( file, "#Small Props" ); + FPrintLn( file, "some_small_model,small" ); + FPrintLn( file, "some_small_model,small" ); + FPrintLn( file, "some_small_model,small" ); + FPrintLn( file, "some_small_model,small" ); + FPrintLn( file, "some_small_model,small" ); + FPrintLn( file, "some_small_model,small" ); + FPrintLn( file, "" ); + + FPrintLn( file, "#Medium Props" ); + FPrintLn( file, "some_medium_model,medium" ); + FPrintLn( file, "some_medium_model,medium" ); + FPrintLn( file, "some_medium_model,medium" ); + FPrintLn( file, "some_medium_model,medium" ); + FPrintLn( file, "some_medium_model,medium" ); + FPrintLn( file, "some_medium_model,medium" ); + FPrintLn( file, "" ); + + FPrintLn( file, "#Large Props" ); + FPrintLn( file, "some_large_model,large" ); + FPrintLn( file, "some_large_model,large" ); + FPrintLn( file, "some_large_model,large" ); + FPrintLn( file, "some_large_model,large" ); + FPrintLn( file, "some_large_model,large" ); + FPrintLn( file, "some_large_model,large" ); + FPrintLn( file, "" ); + + FPrintLn( file, "#Extra Large Props" ); + FPrintLn( file, "some_extra_large_model,xlarge" ); + FPrintLn( file, "some_extra_large_model,xlarge" ); + FPrintLn( file, "some_extra_large_model,xlarge" ); + + CloseFile( file ); + + + // _ph.gsh + file = OpenFile( filename_header, "write" ); + if ( file == -1 ) + { + iprintlnbold( "Error: can't open " + dir_scriptdata + filename_header + " for writing" ); + Println( "Error: can't open " + dir_scriptdata + filename_header + " for writing" ); + return; + } + + FPrintLn( file, "// Extra Small Props" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "" ); + + FPrintLn( file, "// Small Props" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "" ); + + FPrintLn( file, "// Medium Props" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "" ); + + FPrintLn( file, "// Large Props" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "" ); + + FPrintLn( file, "// Extra Large Props" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + FPrintLn( file, "#precache( \"model\", \"\" );" ); + + CloseFile( file ); + + + // _ph.zpkg + file = OpenFile( filename_zpkg, "write" ); + if ( file == -1 ) + { + iprintlnbold( "Error: can't open " + dir_scriptdata + filename_zpkg + " for writing" ); + Println( "Error: can't open " + dir_scriptdata + filename_zpkg + " for writing" ); + return; + } + + FPrintLn( file, "stringtable,gamedata/tables/mp/" + filename_stringtable ); + FPrintLn( file, "rawfile,scripts/mp/" + filename_header ); + + CloseFile( file ); + + iprintlnbold( "Wrote empty files in: " + dir_scriptdata ); + PrintLn( "Wrote empty files in: " + dir_scriptdata ); +} + +function debugShotTest() +{ + player = level.players[0]; + if ( !IsDefined( player ) || !IsAlive( player ) || !( isdefined( player.hasSpawned ) && player.hasSpawned ) ) + return; + + if ( IsDefined( level.players[1] ) ) + enemyBot = level.players[1]; + else + enemyBot = bot::add_bot( util::getotherteam( player.team ) ); + + if ( !isdefined( enemyBot.pers["participation"] ) ) + enemyBot.pers["participation"] = 0; + + if ( !isdefined( enemyBot.hits ) ) + enemyBot.hits = 0; + + SetDvar( "bot_AllowAttack", 0 ); + SetDvar( "bot_AllowMovement", 0 ); + + player.health = player.maxhealth; + + weapon = GetWeapon( "smg_standard" ); + end = player.origin; + + dir = AnglesToForward( player.angles ); + start = end + ( dir * 100 ) + ( 0, 0, 30 ); + MagicBullet( weapon, start, end, enemyBot ); + + dirBack = -1 * dir; + start = end + ( dirBack * 100 ) + ( 0, 0, 30 ); + MagicBullet( weapon, start, end, enemyBot ); + + dirRight = AnglesToRight( player.angles ); + start = end + ( dirRight * 100 ) + ( 0, 0, 30 ); + MagicBullet( weapon, start, end, enemyBot ); + + dirLeft = -1 * dirRight; + start = end + ( dirLeft * 100 ) + ( 0, 0, 30 ); + MagicBullet( weapon, start, end, enemyBot ); + + start = end + ( 0, 0, 100 ); + MagicBullet( weapon, start, end, enemyBot ); + + player util::waittill_notify_or_timeout( "damage", 0.3 ); + + wait 0.5; + + player.health = player.maxhealth; +} + +function showHealth() +{ + if ( !IsDefined( level.players ) ) + return; + + foreach ( player in level.players ) + { + if ( IsDefined( player ) && IsDefined( player.team ) && player.team == game[ "defenders" ] ) + Print3D( player.origin + ( 0, 0, 50 ), "" + player.health ); + } +} + +function showPlayerSpeed() +{ + if ( !IsDefined( level.players ) ) + return; + + foreach ( player in level.players ) + { + velocity = player GetVelocity(); + velocity2d = ( velocity[0], velocity[1], 0 ); + speed = Length( velocity2d ); + Print3D( player.origin + ( 0, 0, 50 ), "" + speed ); + } +} + +function debugGroundTrace() +{ + if ( !IsDefined( level.players ) ) + return; + + foreach ( player in level.players ) + { + if ( IsDefined( player ) && IsDefined( player.prop ) ) + player prop_controls::get_ground_normal( player.prop, true ); + } +} + +function createFakeModel( propInfo, origin, angles ) +{ + propEnt = Spawn( "script_model", origin ); + propEnt SetContents( 0 ); + propEnt NotSolid(); + propEnt SetPlayerCollision( false ); + + prop = Spawn("script_model", propEnt.origin); + prop.angles = angles; + prop setModel( propInfo.modelName ); + prop SetScale( propInfo.propScale, true ); + prop SetCanDamage( true ); + prop.xyzOffset = propInfo.xyzOffset; + prop.anglesOffset = propInfo.anglesOffset; + prop.health = 1; + prop SetPlayerCollision( false ); + + forward = AnglesToForward( angles ) * prop.xyzOffset[0]; + right = AnglesToRight( angles ) * prop.xyzOffset[1]; + up = AnglesToUp( angles ) * prop.xyzOffset[2]; + prop.origin += forward; + prop.origin += right; + prop.origin += up; + prop.angles += prop.anglesOffset; + + prop LinkTo( propEnt ); + + propEnt.prop = prop; + propEnt.propInfo = propInfo; + + return propEnt; +} + +function showModels() +{ + player = level.players[0]; + angles = player.angles; + dir = AnglesToForward( angles ); + origin = player.origin + ( 0, 0, 100 ); + + if ( !isdefined( level.propDebugModels ) ) + { + level.propDebugModels = []; + + foreach ( category in level.propList ) + { + foreach ( propInfo in category ) + { + level.propDebugModels[level.propDebugModels.size] = createFakeModel( propInfo, origin, angles ); + origin = origin + dir * 60; + } + } + } + else + { + foreach ( propEnt in level.propDebugModels ) + { + propEnt.origin = origin; + origin = origin + dir * 60; + } + } +} + +function debugDrawLastLock() +{ + if ( !isdefined( level.phLockDebug ) ) + return; + + color = ( 0, 1, 0 ); + if ( !level.phLockDebug.success ) + color = ( 1, 0, 0 ); + + Print3D( level.phLockDebug.playerOrg + ( 0, 0, 50 ), level.phLockDebug.type ); + Box( level.phLockDebug.playerOrg, level.phLockDebug.playerMins, level.phLockDebug.playerMaxs, level.phLockDebug.playerAngles[1], color ); + + if ( isdefined( level.phLockDebug.origin1 ) ) + { + Sphere( level.phLockDebug.origin1, 5, color ); + Line( level.phLockDebug.playerOrg, level.phLockDebug.origin1 ); + if ( isdefined( level.phLockDebug.text1 ) ) + Print3D( level.phLockDebug.origin1 + ( 0, 0, -10 ), level.phLockDebug.text1 ); + } + + if ( isdefined( level.phLockDebug.origin2 ) ) + { + Sphere( level.phLockDebug.origin2, 5, color ); + Line( level.phLockDebug.playerOrg, level.phLockDebug.origin2 ); + if ( isdefined( level.phLockDebug.text2 ) ) + Print3D( level.phLockDebug.origin2 + ( 0, 0, 10 ), level.phLockDebug.text2 ); + } + + if ( isdefined( level.phLockDebug.origin3 ) ) + { + Sphere( level.phLockDebug.origin3, 5, color ); + Line( level.phLockDebug.playerOrg, level.phLockDebug.origin3 ); + if ( isdefined( level.phLockDebug.text3 ) ) + Print3D( level.phLockDebug.origin3 + ( 0, 0, 30 ), level.phLockDebug.text3 ); + } +} + +function makeFakeClone( propInfo, origin, angles, team ) +{ + newClone = Spawn( "script_model", origin ); + newClone.targetname = "propClone"; + newClone setModel( propInfo.modelname ); + newClone SetScale( propInfo.propScale, true ); + newClone.angles = angles; + newClone SetCanDamage( true ); + newClone.fakeHealth = 50; + newClone.health = 99999; + newClone.maxhealth = 99999; + newClone thread prop::entityDamageWatcher( &prop_controls::damageCloneWatch ); + newClone SetPlayerCollision( false ); + newClone MakeSentient(); + newClone NotSolidCapsule(); + newClone SetTeam( team ); +} + +function makeClones() +{ + player = level.players[0]; + angles = player.angles; + dir = AnglesToForward( angles ); + origin = player.origin + ( 0, 0, 100 ); + + if ( isdefined( level.propDebugClones ) ) + { + foreach ( clone in level.propDebugClones ) + { + clone prop_controls::deleteClone(); + } + } + + level.propDebugClones = []; + + foreach ( category in level.propList ) + { + foreach ( propInfo in category ) + { + level.propDebugClones[level.propDebugClones.size] = makeFakeClone( propInfo, origin, angles, util::getotherteam( player.team ) ); + origin = origin + dir * 60; + } + } +} + +function makeClone() +{ + player = level.players[0]; + angles = player.angles; + dir = AnglesToForward( angles ); + origin = player.origin + dir * ( 0, 0, 100 ); + + propInfo = prop::getNextProp( player ); + + if ( !isdefined( level.propDebugClones ) ) + level.propDebugClones = []; + + level.propDebugClones[level.propDebugClones.size] = makeFakeClone( propInfo, origin, angles, util::getotherteam( player.team ) ); +} + +function showPlayers() +{ + if ( !IsDefined( level.players ) ) + return; + + foreach ( player in level.players ) + { + Box( player.origin, player GetMins(), player GetMaxs(), player.angles[1], ( 1, 0, 1 ), 1, false, 1 ); + } +} + +function showTargets() +{ + if ( !isdefined( level.ph_minigame ) || !isdefined( level.ph_minigame.targets ) ) + return; + + for ( i = 0; i < level.ph_minigame.targets.size; i++ ) + { + target = level.ph_minigame.targets[i]; + if ( isdefined( target ) ) + Print3D( target.origin + ( 0, 0, 30 ), "" + target.fakehealth ); + } +} +#/ \ No newline at end of file diff --git a/mp/gametypes/_scoreboard.gsc b/mp/gametypes/_scoreboard.gsc new file mode 100644 index 0000000..d7492a9 --- /dev/null +++ b/mp/gametypes/_scoreboard.gsc @@ -0,0 +1,38 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; + + + +#namespace scoreboard; + +function autoexec __init__sytem__() { system::register("scoreboard",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); +} + +function init() +{ +// SetDvar("g_ScoresColor_Spectator", ".25 .25 .25"); +// SetDvar("g_ScoresColor_Free", ".76 .78 .10"); + +// SetDvar("g_teamColor_MyTeam", ".4 .7 .4" ); +// SetDvar("g_teamColor_EnemyTeam", "1 .315 0.35" ); +// SetDvar("g_teamColor_MyTeamAlt", ".35 1 1" ); //cyan +// SetDvar("g_teamColor_EnemyTeamAlt", "1 .5 0" ); //orange + + if ( SessionModeIsZombiesGame() ) + { + SetDvar( "g_TeamIcon_Axis", "faction_cia" ); + SetDvar( "g_TeamIcon_Allies", "faction_cdc" ); + } + else + { + SetDvar( "g_TeamIcon_Axis", game["icons"]["axis"] ); + SetDvar( "g_TeamIcon_Allies", game["icons"]["allies"] ); + // TODO MTEAM - setup the team icons for team3-8 + } +} diff --git a/mp/gametypes/_serversettings.gsc b/mp/gametypes/_serversettings.gsc new file mode 100644 index 0000000..94bfd13 --- /dev/null +++ b/mp/gametypes/_serversettings.gsc @@ -0,0 +1,204 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; + + + +#namespace serversettings; + +function autoexec __init__sytem__() { system::register("serversettings",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); +} + +function init() +{ + level.hostname = GetDvarString( "sv_hostname"); + if(level.hostname == "") + level.hostname = "CoDHost"; + SetDvar("sv_hostname", level.hostname); + SetDvar("ui_hostname", level.hostname); + //makeDvarServerInfo("ui_hostname", "CoDHost"); + + level.motd = GetDvarString( "scr_motd" ); + if(level.motd == "") + level.motd = ""; + SetDvar("scr_motd", level.motd); + SetDvar("ui_motd", level.motd); + //makeDvarServerInfo("ui_motd", ""); + + level.allowvote = GetDvarString( "g_allowvote"); + if(level.allowvote == "") + level.allowvote = "1"; + SetDvar("g_allowvote", level.allowvote); + SetDvar("ui_allowvote", level.allowvote); + //makeDvarServerInfo("ui_allowvote", "1"); + + level.allow_teamchange = "0"; + if( SessionModeIsPrivate() || !SessionModeIsOnlinegame() ) + { + level.allow_teamchange = "1"; + } + SetDvar("ui_allow_teamchange", level.allow_teamchange); + + + level.friendlyfire = GetGametypeSetting( "friendlyfiretype" ); + + SetDvar("ui_friendlyfire", level.friendlyfire); + //makeDvarServerInfo("ui_friendlyfire", "0"); + + if(GetDvarString( "scr_mapsize") == "") + SetDvar("scr_mapsize", "64"); + else if(GetDvarfloat( "scr_mapsize") >= 64) + SetDvar("scr_mapsize", "64"); + else if(GetDvarfloat( "scr_mapsize") >= 32) + SetDvar("scr_mapsize", "32"); + else if(GetDvarfloat( "scr_mapsize") >= 16) + SetDvar("scr_mapsize", "16"); + else + SetDvar("scr_mapsize", "8"); + level.mapsize = GetDvarfloat( "scr_mapsize"); + + constrain_gametype(GetDvarString( "g_gametype")); + constrain_map_size(level.mapsize); + + for(;;) + { + update(); + wait 5; + } +} + +function update() +{ + sv_hostname = GetDvarString( "sv_hostname"); + if(level.hostname != sv_hostname) + { + level.hostname = sv_hostname; + SetDvar("ui_hostname", level.hostname); + } + + scr_motd = GetDvarString( "scr_motd"); + if(level.motd != scr_motd) + { + level.motd = scr_motd; + SetDvar("ui_motd", level.motd); + } + + g_allowvote = GetDvarString( "g_allowvote"); + if(level.allowvote != g_allowvote) + { + level.allowvote = g_allowvote; + SetDvar("ui_allowvote", level.allowvote); + } + + scr_friendlyfire = GetGametypeSetting( "friendlyfiretype" ); + if(level.friendlyfire != scr_friendlyfire) + { + level.friendlyfire = scr_friendlyfire; + SetDvar("ui_friendlyfire", level.friendlyfire); + } +} + +function constrain_gametype(gametype) +{ + entities = getentarray(); + for(i = 0; i < entities.size; i++) + { + entity = entities[i]; + + if(gametype == "dm") + { + if(isdefined(entity.script_gametype_dm) && entity.script_gametype_dm != "1") + { + //iprintln("DELETED(GameType): ", entity.classname); + entity delete(); + } + } + else if(gametype == "tdm") + { + if(isdefined(entity.script_gametype_tdm) && entity.script_gametype_tdm != "1") + { + //iprintln("DELETED(GameType): ", entity.classname); + entity delete(); + } + } + else if(gametype == "ctf") + { + if(isdefined(entity.script_gametype_ctf) && entity.script_gametype_ctf != "1") + { + //iprintln("DELETED(GameType): ", entity.classname); + entity delete(); + } + } + else if(gametype == "hq") + { + if(isdefined(entity.script_gametype_hq) && entity.script_gametype_hq != "1") + { + //iprintln("DELETED(GameType): ", entity.classname); + entity delete(); + } + } + else if(gametype == "sd") + { + if(isdefined(entity.script_gametype_sd) && entity.script_gametype_sd != "1") + { + //iprintln("DELETED(GameType): ", entity.classname); + entity delete(); + } + } + else if(gametype == "koth") + { + if(isdefined(entity.script_gametype_koth) && entity.script_gametype_koth != "1") + { + //iprintln("DELETED(GameType): ", entity.classname); + entity delete(); + } + } + } +} + +function constrain_map_size(mapsize) +{ + entities = getentarray(); + for(i = 0; i < entities.size; i++) + { + entity = entities[i]; + + if(int(mapsize) == 8) + { + if(isdefined(entity.script_mapsize_08) && entity.script_mapsize_08 != "1") + { + //iprintln("DELETED(MapSize): ", entity.classname); + entity delete(); + } + } + else if(int(mapsize) == 16) + { + if(isdefined(entity.script_mapsize_16) && entity.script_mapsize_16 != "1") + { + //iprintln("DELETED(MapSize): ", entity.classname); + entity delete(); + } + } + else if(int(mapsize) == 32) + { + if(isdefined(entity.script_mapsize_32) && entity.script_mapsize_32 != "1") + { + //iprintln("DELETED(MapSize): ", entity.classname); + entity delete(); + } + } + else if(int(mapsize) == 64) + { + if(isdefined(entity.script_mapsize_64) && entity.script_mapsize_64 != "1") + { + //iprintln("DELETED(MapSize): ", entity.classname); + entity delete(); + } + } + } +} \ No newline at end of file diff --git a/mp/gametypes/_shellshock.gsc b/mp/gametypes/_shellshock.gsc new file mode 100644 index 0000000..264a006 --- /dev/null +++ b/mp/gametypes/_shellshock.gsc @@ -0,0 +1,99 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\util_shared; +#using scripts\shared\system_shared; + + + +#using scripts\mp\_util; + +#namespace shellshock; + +function autoexec __init__sytem__() { system::register("shellshock",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); + + level.shellshockOnPlayerDamage = &on_damage; +} + +function init() +{ +} + +function on_damage( cause, damage, weapon ) +{ + if ( self util::isFlashbanged() ) + { + return; // don't interrupt flashbang shellshock + + } + + if( self.health <= 0 ) + { + self clientfield::set_to_player("sndMelee", 0); + } + + if ( cause == "MOD_EXPLOSIVE" || + cause == "MOD_GRENADE" || + cause == "MOD_GRENADE_SPLASH" || + cause == "MOD_PROJECTILE" || + cause == "MOD_PROJECTILE_SPLASH" ) + { + // if we are using this as a impact damage only projectile then no shellshock + // we should probably add a bool to the weapon to indicate that it spawn protects + if ( weapon.explosionradius == 0 ) + return; + + time = 0; + + if(damage >= 90) + time = 4; + else if(damage >= 50) + time = 3; + else if(damage >= 25) + time = 2; + else if(damage > 10) + time = 2; + + if ( time ) + { + if ( self util::mayApplyScreenEffect() && self HasPerk( "specialty_flakjacket" ) == false ) + { + self shellshock("frag_grenade_mp", 0.5); + } + } + } +} + +function end_on_death() +{ + self waittill( "death" ); + waittillframeend; + self notify ( "end_explode" ); +} + +function end_on_timer( timer ) +{ + self endon( "disconnect" ); + + wait( timer ); + self notify( "end_on_timer" ); +} + +function rcbomb_earthquake(position) +{ + PlayRumbleOnPosition( "grenade_rumble", position ); + Earthquake( 0.5, 0.5, self.origin, 512 ); +} +function reset_meleeSnd() +{ + self endon ("death"); + + wait (6); + self clientfield::set_to_player("sndMelee", 0); + self notify ("snd_melee_end"); +} \ No newline at end of file diff --git a/mp/gametypes/_spawning.gsc b/mp/gametypes/_spawning.gsc new file mode 100644 index 0000000..a9d6704 --- /dev/null +++ b/mp/gametypes/_spawning.gsc @@ -0,0 +1,1166 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_tacticalinsertion; +#using scripts\shared\abilities\gadgets\_gadget_resurrect; + + + + +#using scripts\mp\gametypes\_spawnlogic; + + + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\gametypes\_dogtags; + +#namespace spawning; + +function autoexec __init__sytem__() { system::register("spawning",&__init__,undefined,undefined); } + +/* +_spawning.gsc +function Copyright (c) 2008 Certain Affinity, Inc. All rights reserved. +Friday April 25, 2008 3:59pm Stefan S. + +spawn_point[]: +-------------- +"origin" - worldspace origin +"score" - desirability for spawning; calculated by applying influencers plus a small amount of randomness + +spawn_influencer[]: +------------------- +"type" - one of: "static", "friend", "enemy", "enemy_weapon", "vehicle", "projectile", "airstrike", "dead_friend", "game_mode", "dog" +"shape" - one of: "sphere", "cylinder", "pill", "cone", "box" +"forward" - worldspace forward vector +"up" - worldspace up vector +"origin" - worldspace origin +"radius" - for sphere, cylinder, pill, cone +"axis_length" - for cylinder, pill, cone +"width", "height", "depth" - for box +"score" - base influencer score +"score_curve" - one of: "constant", "linear" (1->0), "inverse_linear" (0->1), "negative_to_positive" (-1 -> +1) + +level.recently_deceased[""][]: +-------------------------- +deceased.angles - angles of the deceased player at TOD +deceased.origin - origin of the deceased player at TOD +function deceased.timeOfDeathMillis - GetTime() value (in milliseconds) at TOD +*/ + +/*QUAKED mp_uspawn_influencer (1 0 0) (-16 -16 -16) (16 16 16) +Static influencer for unified spawning system +"script_shape" Shape of this influencer. Supported shapes for Radiant-placed static influencers are "cylinder" and "sphere". +"radius" Radius of the influencer ( whether cylinder or sphere ). +"height" Height of the cylinder ( ignored for sphere shapes ). +"script_score" The maximum influence this influencer can have. +"script_score_curve" The shape of the score falloff as a function of the distance from the primary cylinder axis. Options are: "constant", "linear", "inverse_linear", "negative_to_positive" +"script_team" Make this influencer team-specific by specifying "axis" or "allies" +"script_gameobjectname" Gametypes for which the influencer is active (space-separated values), can be "[all_modes]" or combination of "dm", "hq", "iwar", "war", "twar", "sd", "dom" +"script_twar_flag" Used to associate this influencer with a specific twar flag +default:"script_shape" "cylinder" +default:"radius" "400" +default:"height" "100" +default:"script_score" "25" +default:"script_score_curve" "constant" +default:"script_team" "neutral" +default:"script_gameobjectname" "[all_modes]" +default:"script_twar_flag" "NONE" +*/ + +/*QUAKED mp_mobile_spawn (0.0 0.0 1.0) (-16 -16 0) (16 16 72) +Mobile spawn point. For use with the mobile spawn killstreak.*/ + +/* ---------- includes */ + + + +/* ---------- initialization */ + +function __init__() +{ + level init_spawn_system(); + + level.influencers = []; + level.recently_deceased= []; + + foreach( team in level.teams ) + { + level.recently_deceased[ team ]= util::spawn_array_struct(); + } + + callback::on_connecting( &onPlayerConnect ); + + level.spawnProtectionTime = GetGametypeSetting( "spawnprotectiontime" ); + level.spawnProtectionTimeMS = int ( (isdefined(level.spawnProtectionTime)?level.spawnProtectionTime:0) * 1000 ); + level.spawnTrapTriggerTime = GetGametypeSetting( "spawntraptriggertime" ); + + /# + // this dvar stores the name for whom to display debug spawning information + SetDvar("scr_debug_spawn_player", ""); + SetDvar("scr_debug_render_spawn_data", "1"); + SetDvar("scr_debug_render_snapshotmode", "0"); + // for testing spawn point placements + SetDvar("scr_spawn_point_test_mode", "0"); + level.test_spawn_point_index= 0; + SetDvar("scr_debug_render_spawn_text", "1"); + #/ + + return; +} + +function init_spawn_system() +{ + level.spawnsystem = spawnstruct(); + spawnsystem = level.spawnsystem; + + if (!IsDefined( spawnsystem.sideSwitching )) + spawnsystem.sideSwitching = 1; + + spawnsystem.objective_facing_bonus = 0.0; + + spawnsystem.iSPAWN_TEAMMASK = []; + spawnsystem.iSPAWN_TEAMMASK_FREE = ( 1 << 0 ); + spawnsystem.iSPAWN_TEAMMASK["free"] = spawnsystem.iSPAWN_TEAMMASK_FREE; + all = spawnsystem.iSPAWN_TEAMMASK_FREE; + count = 1; + foreach( team in level.teams ) + { + spawnsystem.iSPAWN_TEAMMASK[team] = ( 1 << count ); + all = all | spawnsystem.iSPAWN_TEAMMASK[team]; + count++; + } + spawnsystem.iSPAWN_TEAMMASK["all"] = all; +} + + +/* +============= +onPlayerConnect + +============= +*/ +function onPlayerConnect() +{ + level endon ( "game_ended" ); + + self thread onPlayerSpawned(); + self thread onTeamChange(); + self thread onGrenadeThrow(); +} + + +/* +============= +onPlayerSpawned + +============= +*/ +function onPlayerSpawned() +{ + self endon( "disconnect" ); + level endon ( "game_ended" ); + + for(;;) + { + self waittill( "spawned_player" ); + + //println("SPAWN:onPlayerSpawned() - spawned player event"); + self airsupport::clearMonitoredSpeed(); + + self thread initialSpawnProtection(); + + // If radar permanently enabled for the player, enable it + if ( isdefined( self.pers["hasRadar"] ) && self.pers["hasRadar"] ) + { + self.hasSpyplane = true; + } + + self enable_player_influencers( true ); + self thread onDeath(); + } +} + +/* +============= +onDeath + +Drops any carried object when the player dies +============= +*/ +function onDeath() +{ + self endon( "disconnect" ); + level endon ( "game_ended" ); + + self waittill ( "death" ); + + self enable_player_influencers( false ); + + // creating on the level so the player does not clean it up + level create_friendly_influencer( "friend_dead", self.origin, self.team ); +} + +/* +============= +onTeamChange + +Changes influencer teams when player changes teams +============= +*/ +function onTeamChange() +{ + self endon( "disconnect" ); + level endon ( "game_ended" ); + + while(1) + { + self waittill ( "joined_team" ); + self.lastspawnpoint = undefined; + self player_influencers_set_team(); + {wait(.05);}; + } +} + + +/* +============= +onGrenadeThrow + +Creates an influencer on grenade +============= +*/ +function onGrenadeThrow() +{ + self endon( "disconnect" ); + level endon ( "game_ended" ); + + while(1) + { + self waittill ( "grenade_fire", grenade, weapon ); + level thread create_grenade_influencers( self.pers["team"], weapon, grenade ); + {wait(.05);}; + } +} + +function get_friendly_team_mask( team ) +{ + if ( level.teambased ) + { + team_mask = util::getTeamMask( team ); + } + else + { + team_mask = level.spawnsystem.iSPAWN_TEAMMASK_FREE; + } + + return team_mask; +} + +function get_enemy_team_mask( team ) +{ + if ( level.teambased ) + { + team_mask = util::getOtherTeamsMask( team ); + } + else + { + team_mask = level.spawnsystem.iSPAWN_TEAMMASK_FREE; + } + + return team_mask; +} + +function create_influencer( name, origin, team_mask ) +{ + self.influencers[name] = AddInfluencer( name, origin, team_mask ); +// /#println( "create_influencer: " + name + " id " + self.influencers[name] );#/ + + self thread watch_remove_influencer(); + + return self.influencers[name]; +} + +function create_friendly_influencer( name, origin, team ) +{ + team_mask = self get_friendly_team_mask( team ); + + self.influencersFriendly[name] = create_influencer( name, origin, team_mask );; + + return self.influencersFriendly[name]; +} + +function create_enemy_influencer( name, origin, team ) +{ + team_mask = self get_enemy_team_mask( team ); + + self.influencersEnemy[name] = create_influencer( name, origin, team_mask ); + + return self.influencersEnemy[name]; +} + +function create_entity_influencer( name, team_mask ) +{ + self.influencers[name] = AddEntityInfluencer( name, self, team_mask ); +// /#println( "create_entity_influencer: " + name + " id " + self.influencers[name] );#/ + + self thread watch_remove_influencer(); + + return self.influencers[name] ; +} + +function create_entity_friendly_influencer( name, team ) +{ + team_mask = self get_friendly_team_mask( team ); + + return self create_entity_masked_friendly_influencer( name, team_mask ); +} + +function create_entity_enemy_influencer( name, team ) +{ + team_mask = self get_enemy_team_mask( team ); + + return self create_entity_masked_enemy_influencer( name, team_mask ); +} + +function create_entity_masked_friendly_influencer( name, team_mask ) +{ + self.influencersFriendly[name] = self create_entity_influencer( name, team_mask ); + + return self.influencersFriendly[name]; +} + +function create_entity_masked_enemy_influencer( name, team_mask ) +{ + self.influencersEnemy[name] = self create_entity_influencer( name, team_mask ); + + return self.influencersEnemy[name]; +} + +function create_player_influencers() +{ + assert( !isdefined(self.influencers) ); + assert( !isdefined(self.influencers) ); + + if ( !level.teambased ) + { + team_mask = level.spawnsystem.iSPAWN_TEAMMASK_FREE; + enemy_teams_mask = level.spawnsystem.iSPAWN_TEAMMASK_FREE; + } + else if ( isdefined( self.pers["team"] ) ) + { + team = self.pers["team"]; + team_mask = util::getTeamMask( team ); + enemy_teams_mask = util::getOtherTeamsMask( team ); + } + else + { + team_mask = 0; + enemy_teams_mask = 0; + } + + angles = self.angles; + origin = self.origin; + up = (0,0,1); + forward = (1,0,0); + + self.influencers = []; + self.friendlyInfluencers = []; + self.enemyInfluencers = []; + + self create_entity_masked_enemy_influencer( "enemy", enemy_teams_mask ); + + if ( level.teambased ) + { + self create_entity_masked_friendly_influencer( "friend", team_mask ); + } + + if ( !isdefined(self.pers["team"]) || self.pers["team"] == "spectator" ) + { + self enable_influencers( false ); + } +} + +// influencers created for every spawn +function create_player_spawn_influencers( spawn_origin ) +{ + // create a negative influencer for the opposition here + level create_enemy_influencer( "enemy_spawn", spawn_origin, self.pers["team"] ); + level create_friendly_influencer( "friendly_spawn", spawn_origin, self.pers["team"] ); +} + +function remove_influencer( to_be_removed ) +{ +// /#println( "remove_influencer: id " + to_be_removed );#/ + foreach ( influencer in self.influencers ) + { + if ( influencer == to_be_removed ) + { + RemoveInfluencer( influencer ); + + ArrayRemoveValue( self.influencers, influencer ); + if ( IsDefined( self.influencersFriendly ) ) + { + ArrayRemoveValue( self.influencersFriendly, influencer ); + } + if ( IsDefined( self.influencersEnemy ) ) + { + ArrayRemoveValue( self.influencersEnemy, influencer ); + } + return; + } + } +} + +function remove_influencers() +{ + if ( isdefined( self.influencers ) ) + { + foreach ( influencer in self.influencers ) + { + RemoveInfluencer( influencer ); + } + } + + self.influencers = []; + if ( IsDefined( self.influencersFriendly ) ) + { + self.influencersFriendly = []; + } + if ( IsDefined( self.influencersEnemy ) ) + { + self.influencersEnemy = []; + } +} + +function watch_remove_influencer() +{ + self endon("death"); + + self notify("watch_remove_influencer" ); + self endon("watch_remove_influencer" ); + + self waittill("influencer_removed", index ); + + ArrayRemoveValue( self.influencers, index ); + if ( IsDefined( self.influencersFriendly ) ) + { + ArrayRemoveValue( self.influencersFriendly, index ); + } + if ( IsDefined( self.influencersEnemy ) ) + { + ArrayRemoveValue( self.influencersEnemy, index ); + } + + self thread watch_remove_influencer(); +} + + +function enable_influencers( enabled ) +{ + foreach ( influencer in self.influencers ) + { + EnableInfluencer( influencer, enabled ); + } +} + +function enable_player_influencers( enabled ) +{ + if (!isdefined(self.influencers)) + self create_player_influencers(); + + self enable_influencers( enabled ); +} + +function player_influencers_set_team() +{ + if ( !level.teambased ) + { + team_mask = level.spawnsystem.iSPAWN_TEAMMASK_FREE; + enemy_teams_mask = level.spawnsystem.iSPAWN_TEAMMASK_FREE; + } + else + { + // for the player influencers we need to pay attention to the switchedsides + team = self.pers["team"]; + + team_mask = util::getTeamMask( team ); + enemy_teams_mask = util::getOtherTeamsMask( team ); + } + + if ( isDefined( self.influencersFriendly ) ) + { + foreach ( influencer in self.influencersFriendly ) + { + SetInfluencerTeammask( influencer, team_mask ); + } + } + if ( isDefined( self.influencersEnemy ) ) + { + foreach ( influencer in self.influencersEnemy ) + { + SetInfluencerTeammask( influencer, enemy_teams_mask ); + } + } +} + +function create_grenade_influencers( parent_team, weapon, grenade ) +{ + pixbeginevent("create_grenade_influencers"); + + spawn_influencer = weapon.spawnInfluencer; + + if ( IsDefined( grenade.origin ) && spawn_influencer != "" ) + { + if ( !level.teambased ) + { + weapon_team_mask = level.spawnsystem.iSPAWN_TEAMMASK_FREE; + } + else + { + weapon_team_mask = util::getOtherTeamsMask( parent_team ); + + // if this is hardcore then we do not want to spawn infront of the weapon either + if ( level.friendlyfire ) + { + weapon_team_mask |= util::getTeamMask( parent_team ); + } + } + + grenade create_entity_masked_enemy_influencer( spawn_influencer, weapon_team_mask ); + } + + pixendevent();//"create_grenade_influencers" (moved from the bottom of the file - pix cannot extend over code with wait). +} + +function create_map_placed_influencers() +{ + staticInfluencerEnts = GetEntArray( "mp_uspawn_influencer", "classname" ); + + for ( i = 0; i < staticInfluencerEnts.size; i++ ) + { + staticInfluencerEnt = staticInfluencerEnts[ i ]; + + create_map_placed_influencer(staticInfluencerEnt); + } +} + +function create_map_placed_influencer( influencer_entity ) +{ + influencer_id = -1; + + if (isdefined(influencer_entity.script_noteworty)) + { + team_mask = util::getTeamMask(influencer_entity.script_team); + level create_enemy_influencer( influencer_entity.script_noteworty, influencer_entity.origin, team_mask ); + } + else + { + assertmsg( "Radiant-placed spawn influencers require the 'script_noteworty' parameter" ); + } + + return influencer_id; +} + +function updateAllSpawnPoints() +{ + // force spawn points to get precached + foreach( team in level.teams ) + { + gatherSpawnPoints( team ); + } + + clearSpawnPoints( 0 ); + + if ( level.teambased ) + { + foreach( team in level.teams ) + { + addSpawnPoints( team, level.player_spawn_points[ team ], 0 ); + EnableSpawnPointList( 0, util::getTeamMask( team ) ); + } + } + else + { + foreach( team in level.teams ) + { + addSpawnPoints( "free", level.player_spawn_points[ team ], 0 ); + EnableSpawnPointList( 0, util::getTeamMask( team ) ); + } + } + + level.spawnAllMins = level.spawnMins; + level.spawnAllMaxs = level.spawnMaxs; + + // this will remove all spawnpoints for the other gametypes which are not used + remove_unused_spawn_entities(); +} + +function update_fallback_spawnpoints() +{ + clearSpawnPoints( 1 ); + + if ( !IsDefined( level.player_fallback_points ) ) + return; + + if ( level.teambased ) + { + foreach( team in level.teams ) + { + addSpawnPoints( team, level.player_fallback_points[ team ], 1 ); + } + } + else + { + foreach( team in level.teams ) + { + addSpawnPoints( "free", level.player_fallback_points[ team ], 1 ); + } + } +} + +function add_fallback_spawnpoints( team, point_class ) +{ + if ( !IsDefined( level.player_fallback_points ) ) + { + level.player_fallback_points = []; + + foreach ( level_team in level.teams ) + { + level.player_fallback_points[level_team] = []; + } + } + + spawnlogic::add_spawn_point_classname( point_class ); + + spawnpoints = spawnlogic::get_spawnpoint_array( point_class ); + + if ( IsDefined( level.allowedGameObjects ) && level.convert_spawns_to_structs ) + { + for ( i = spawnpoints.size - 1; i >= 0; i-- ) + { + if (!gameobjects::entity_is_allowed(spawnpoints[i], level.allowedGameObjects)) + { + spawnpoints[i] = undefined; + } + } + ArrayRemoveValue( spawnpoints, undefined ); + } + + level.player_fallback_points[team] = spawnpoints; + DisableSpawnPointList( 1, util::getTeamMask( team ) ); +} + +function is_spawn_trapped( team ) +{ +/# + level.spawnTrapTriggerTime = GetGametypeSetting( "spawntraptriggertime" ); +#/ + + // this feature is only on for public match + if ( !level.rankedMatch ) + return false; + + if ( isDefined( level.aliveTimesAverage[team] ) && ( level.aliveTimesAverage[team] != 0 ) && ( level.aliveTimesAverage[team] < (level.spawnTrapTriggerTime * 1000) ) ) + return true; + + return false; +} + +function use_start_spawns(predictedSpawn) +{ + if ( level.alwaysUseStartSpawns ) + return true; + + if ( !level.useStartSpawns ) + return false; + + // if we have more players then start spawns dont use them + if( level.teambased ) + { + spawnteam = self.pers["team"]; + + if ( (level.alivePlayers[spawnteam].size + level.spawningPlayers[self.team].size) >= level.spawn_start[spawnteam].size ) + { + if ( !predictedSpawn ) + { + level.useStartSpawns = false; + } + return false; + } + } + else if ( (level.alivePlayers["free"].size + level.spawningPlayers["free"].size) >= level.spawn_start.size ) + { + if ( !predictedSpawn ) + { + level.useStartSpawns = false; + } + return false; + } + + return true; +} +/* ---------- spawn_point */ + +//void - self= player entity for the player who is spawning +function onSpawnPlayer(predictedSpawn) +{ + + if (!isdefined( predictedSpawn )) + predictedSpawn = false; + + /# + if (GetDvarint( "scr_spawn_point_test_mode")!=0) + { + spawn_point = get_debug_spawnpoint( self ); + self spawn( spawn_point.origin, spawn_point.angles ); + return; + } + #/ + + spawnOverride = self tacticalinsertion::overrideSpawn(predictedSpawn); + spawnResurrect = self resurrect::overrideSpawn(predictedSpawn); + +/# + if ( isdefined(self.devguiLockSpawn) && self.devguiLockSpawn ) + { + spawnResurrect = true; + } +#/ + + spawn_origin = undefined; + spawn_angles = undefined; + + if ( spawnResurrect ) + { + spawn_origin = self.resurrect_origin; + spawn_angles = self.resurrect_angles; + } + else if ( spawnOverride ) + { + if (predictedSpawn && isdefined( self.tacticalInsertion )) + { + // get the tactical insertion position + // send this point as a message to the client + self predictSpawnPoint( self.tacticalInsertion.origin, self.tacticalInsertion.angles ); + // println("predicted spawn [TI] - " + self.tacticalInsertion.origin ); + } + return; + } + else if ( self use_start_spawns( predictedSpawn ) ) + { + if ( !predictedSpawn ) + { + if( level.teambased ) + { + level.spawningPlayers[self.team][level.spawningPlayers[self.team].size] = self; + } + else + { + level.spawningPlayers["free"][level.spawningPlayers["free"].size] = self; + } + } + + if( level.teambased ) + { + spawnteam = self.pers["team"]; + if ( game["switchedsides"] ) + spawnteam = util::getOtherTeam( spawnteam ); + + spawnpoint = spawnlogic::get_spawnpoint_random(level.spawn_start[spawnteam], predictedSpawn); + } + else + { + spawnpoint = spawnlogic::get_spawnpoint_random(level.spawn_start, predictedSpawn); + } + + if (isdefined(spawnpoint)) + { + spawn_origin = spawnpoint.origin; + spawn_angles = spawnpoint.angles; + } + + if ( isdefined( level.finalizeStartSpawn ) ) + [[ level.finalizeStartSpawn ]]( spawnpoint, predictedSpawn ); + } + else + { + // find the most desireable spawn point, and spawn there + spawn_point = getSpawnPoint(self, predictedSpawn); + + if (isdefined(spawn_point)) + { + spawn_origin = spawn_point["origin"]; + spawn_angles = spawn_point["angles"]; + } + } + + if ( !isdefined(spawn_origin) ) + { + /# println("ERROR: unable to locate a usable spawn point for player"); #/ + callback::abort_level(); + } + + if ( predictedSpawn ) + { + self predictSpawnPoint( spawn_origin, spawn_angles ); + } + else + { + self spawn( spawn_origin, spawn_angles ); + + self.lastspawntime = gettime(); + self enable_player_influencers( true ); + + // on normal spawns create the spawned influencers + if ( !spawnResurrect && !spawnOverride ) + { + self create_player_spawn_influencers( spawn_origin ); + } + } + + if( ( isdefined( level.droppedTagRespawn ) && level.droppedTagRespawn ) ) + dogtags::on_spawn_player(); +} + +// spawnPoint: .origin, .angles +function getSpawnPoint( + player_entity, + predictedSpawn ) +{ + + if (!isdefined( predictedSpawn )) + predictedSpawn = false; + + if (level.teambased ) + { + point_team = player_entity.pers["team"]; + influencer_team = player_entity.pers["team"]; + } + else + { + point_team = "free"; + influencer_team = "free"; + } + + spawn_trapped = is_spawn_trapped(point_team); + + if (level.teambased && isdefined(game["switchedsides"]) && game["switchedsides"] && level.spawnsystem.sideSwitching) + { + point_team = util::getOtherTeam(point_team); + } + + if ( spawn_trapped ) + { + EnableSpawnPointList( 1, util::getTeamMask( point_team ) ); + } + else + { + DisableSpawnPointList( 1, util::getTeamMask( point_team ) ); + } + + best_spawn = get_best_spawnpoint( point_team, influencer_team, player_entity, predictedSpawn ); + + if (!predictedSpawn) + { + player_entity.last_spawn_origin = best_spawn["origin"]; + } + + return best_spawn; +} + +function get_debug_spawnpoint( player ) +{ + if (level.teambased ) + { + team = player.pers["team"]; + } + else + { + team = "free"; + } + + index = level.test_spawn_point_index; + level.test_spawn_point_index++; + + if ( team == "free" ) + { + spawn_counts = 0; + foreach( team in level.teams ) + { + spawn_counts += level.player_spawn_points[ team ].size; + } + + if (level.test_spawn_point_index >= spawn_counts) + { + level.test_spawn_point_index= 0; + } + + count = 0; + foreach( team in level.teams ) + { + size = level.player_spawn_points[ team ].size; + if (level.test_spawn_point_index < count + size) + return level.player_spawn_points[ team ][level.test_spawn_point_index - count]; + + count += size; + } + } + else + { + if (level.test_spawn_point_index >= level.player_spawn_points[ team ].size) + { + level.test_spawn_point_index= 0; + } + + return level.player_spawn_points[ team ][level.test_spawn_point_index]; + + } +} + +function get_best_spawnpoint( point_team, influencer_team, player, predictedSpawn ) +{ + if (level.teambased ) + { + vis_team_mask = util::getOtherTeamsMask( player.pers["team"] ); + } + else + { + vis_team_mask = level.spawnsystem.iSPAWN_TEAMMASK["all"]; + } + + spawn_point = GetBestSpawnPoint( point_team, influencer_team, vis_team_mask, player, predictedSpawn ); + + if( !predictedSpawn ) + { + // don't log predicted spawn points + bbPrint( "mpspawnpointsused", "reason %s x %d y %d z %d", "point used", spawn_point["origin"] ); + } + + return spawn_point; +} + +function gatherSpawnPoints( player_team ) +{ + // use cached spawn points when they are available + if ( !isdefined( level.player_spawn_points ) ) + { + level.player_spawn_points = []; + } + else if ( isdefined( level.player_spawn_points[ player_team ] ) ) + { + return; + } + + spawn_entities = spawnlogic::get_team_spawnpoints(player_team); + if ( !isdefined(spawn_entities)) + { + spawn_entities = []; + } + + level.player_spawn_points[player_team] = spawn_entities; +} + +/* ---------- private code */ + +/* ---------- spawn_influencer */ + +// These functions add the new influencers to the provided influencers array. + +function is_hardcore() +{ + return isdefined( level.hardcoreMode ) + && level.hardcoreMode; +} + +function teams_have_enmity( + team1, + team2) +{ + // If either is undefined, then we don't know; assume they are enemies. + // If the game is straight-up deathmatch, everyone is enemies (regardless of their assigned teams). + if ( !isdefined( team1 ) || !isdefined( team2 ) || (level.gameType=="dm")) + return true; + + return team1 != "neutral" + && team2 != "neutral" + && team1 != team2; +} + +// Really would like to get rid of these gametype specific references to spawn points +// from this file, however right now there is no way of getting a array of +// all spawnpoints in a level. They have to be asked for by classname. +function remove_unused_spawn_entities() +{ + if( level.convert_spawns_to_structs ) + return; + + spawn_entity_types = []; + + // TODO MTEAM - update this funcion for multiteam and just for all the new spawnpoints + + // dm + spawn_entity_types[ spawn_entity_types.size ] = "mp_dm_spawn"; + + // tdm + spawn_entity_types[ spawn_entity_types.size ] = "mp_tdm_spawn_allies_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_tdm_spawn_axis_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_tdm_spawn_team1_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_tdm_spawn_team2_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_tdm_spawn_team3_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_tdm_spawn_team4_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_tdm_spawn_team5_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_tdm_spawn_team6_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_tdm_spawn"; + + // ctf + spawn_entity_types[ spawn_entity_types.size ] = "mp_ctf_spawn_allies_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_ctf_spawn_axis_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_ctf_spawn_allies"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_ctf_spawn_axis"; + + // dom + spawn_entity_types[ spawn_entity_types.size ] = "mp_dom_spawn_allies_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dom_spawn_axis_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dom_spawn_flag_a"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dom_spawn_flag_b"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dom_spawn_flag_c"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dom_spawn"; + + // koth + // uses TDM spawns + + // sab + spawn_entity_types[ spawn_entity_types.size ] = "mp_sab_spawn_allies_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_sab_spawn_axis_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_sab_spawn_allies"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_sab_spawn_axis"; + + // sd + spawn_entity_types[ spawn_entity_types.size ] = "mp_sd_spawn_attacker"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_sd_spawn_defender"; + + // dem + spawn_entity_types[ spawn_entity_types.size ] = "mp_dem_spawn_attacker_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dem_spawn_defender_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dem_spawn_attackerOT_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dem_spawn_defenderOT_start"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dem_spawn_attacker"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dem_spawn_defender"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dem_spawn_defender_a"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dem_spawn_defender_b"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dem_spawn_attacker_remove_a"; + spawn_entity_types[ spawn_entity_types.size ] = "mp_dem_spawn_attacker_remove_b"; + + + for ( i = 0; i < spawn_entity_types.size; i++ ) + { + if ( spawn_point_class_name_being_used( spawn_entity_types[i] ) ) + continue; + + spawnpoints = spawnlogic::get_spawnpoint_array( spawn_entity_types[i] ); + + delete_all_spawns( spawnpoints ); + } +} + +function delete_all_spawns( spawnpoints ) +{ + for ( i = 0; i < spawnpoints.size; i++ ) + { + spawnpoints[i] delete(); + } +} + +function spawn_point_class_name_being_used( name ) +{ + if ( !isdefined( level.spawn_point_class_names ) ) + { + return false; + } + + for ( i = 0; i < level.spawn_point_class_names.size; i++ ) + { + if ( level.spawn_point_class_names[i] == name ) + { + return true; + } + } + + return false; +} + +// callback to process spawn point changes from Radiant Live Update +function CodeCallback_UpdateSpawnPoints() +{ + // rebuild the legacy spawn points + foreach( team in level.teams ) + { + spawnlogic::rebuild_spawn_points( team ); + } + + // make sure the list gets completely rebuilt + level.player_spawn_points = undefined; + updateAllSpawnPoints(); +} + +function initialSpawnProtection() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self thread airsupport::monitorSpeed( level.spawnProtectionTime ); + + if ( !isdefined( level.spawnProtectionTime ) || level.spawnProtectionTime == 0 ) + { + return; + } + + self.specialty_nottargetedbyairsupport = true; + self clientfield::set( "killstreak_spawn_protection", 1 ); + self.ignoreme = true; + wait level.spawnProtectionTime; + self clientfield::set( "killstreak_spawn_protection", 0 ); + self.specialty_nottargetedbyairsupport = undefined; + self.ignoreme = false; +} + +function getTeamStartSpawnName( team, spawnpointNameBase ) +{ + spawn_point_team_name = team; + + if ( !level.multiTeam && game["switchedsides"] ) + spawn_point_team_name = util::getOtherTeam( team ); + + // for multi-team using slightly different start spawns for axis and allies + if ( level.multiTeam ) + { + if ( team == "axis" ) + spawn_point_team_name = "team1"; + else if ( team == "allies" ) + spawn_point_team_name = "team2"; + + // for multi-round rotate the teams through all the start points + if ( !util::isOneRound() ) + { + // to zero based + number = int( getsubstr( spawn_point_team_name, 4, 5) ) - 1; + + // back to one based + number = (( number + game[ "roundsplayed" ]) % level.teams.size) + 1; + + spawn_point_team_name = "team" + number; + } + } + + return spawnpointNameBase + "_" + spawn_point_team_name + "_start" ; +} + +function getTDMStartSpawnName( team ) +{ + return getTeamStartSpawnName( team, "mp_tdm_spawn" ); +} diff --git a/mp/gametypes/_spawning.gsh b/mp/gametypes/_spawning.gsh new file mode 100644 index 0000000..139597f --- /dev/null +++ b/mp/gametypes/_spawning.gsh @@ -0,0 +1,2 @@ + + diff --git a/mp/gametypes/_spawnlogic.gsc b/mp/gametypes/_spawnlogic.gsc new file mode 100644 index 0000000..99e6d04 --- /dev/null +++ b/mp/gametypes/_spawnlogic.gsc @@ -0,0 +1,2657 @@ +#using scripts\codescripts\struct; +#using scripts\shared\callbacks_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; + + + +#using scripts\mp\gametypes\_spawnlogic; + + +#namespace spawnlogic; + +function autoexec __init__sytem__() { system::register("spawnlogic",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); +} + +// called once at start of game +function init() +{ + /# + if (GetDvarString( "scr_recordspawndata") == "") + { + SetDvar("scr_recordspawndata", 0); + } + level.storeSpawnData = GetDvarint( "scr_recordspawndata"); + + if (GetDvarString( "scr_killbots") == "") + { + SetDvar("scr_killbots", 0); + } + if (GetDvarString( "scr_killbottimer") == "") + { + SetDvar("scr_killbottimer", 0.25); + } + thread loop_bot_spawns(); + #/ + + // start keeping track of deaths + level.spawnlogic_deaths = []; + // DEBUG + level.spawnlogic_spawnkills = []; + level.players = []; + level.grenades = []; + level.pipebombs = []; + level.numPlayersWaitingToEnterKillcam = 0; + + level.convert_spawns_to_structs = GetDvarInt( "spawnsystem_convert_spawns_to_structs" ); + + /# + println( "[KILLCAM] level.numPlayersWaitingToEnterKillcam = 0" ); + #/ + + level.spawnMins = (0,0,0); + level.spawnMaxs = (0,0,0); + level.spawnMinsMaxsPrimed = false; + if ( isdefined( level.safespawns ) ) + { + for( i = 0; i < level.safespawns.size; i++ ) + { + level.safespawns[i] spawnpoint_init(); + } + } + + if ( GetDvarString( "scr_spawn_enemyavoiddist") == "" ) + { + SetDvar("scr_spawn_enemyavoiddist", "800"); + } + if ( GetDvarString( "scr_spawn_enemyavoidweight") == "" ) + { + SetDvar("scr_spawn_enemyavoidweight", "0"); + } + + // DEBUG + /# + if (GetDvarString( "scr_spawnsimple") == "") + { + SetDvar("scr_spawnsimple", "0"); + } + if (GetDvarString( "scr_spawnpointdebug") == "") + { + SetDvar("scr_spawnpointdebug", "0"); + } + if (GetDvarint( "scr_spawnpointdebug") > 0) + { + thread show_deaths_debug(); + thread update_death_info_debug(); + thread profile_debug(); + } + if (level.storeSpawnData) + { + thread allow_spawn_data_reading(); + } + if (GetDvarString( "scr_spawnprofile") == "") + { + SetDvar("scr_spawnprofile", "0"); + } + thread watch_spawn_profile(); + thread spawn_graph_check(); + #/ +} + +function add_spawn_points_internal( team, spawnpoints, list ) +{ + if ( !IsDefined( list ) ) + { + list = 0; + } + + oldSpawnPoints = []; + if ( level.teamSpawnPoints[team].size ) + { + oldSpawnPoints = level.teamSpawnPoints[team]; + } + + if ( IsDefined( level.allowedGameObjects ) && level.convert_spawns_to_structs ) + { + for ( i = spawnpoints.size - 1; i >= 0; i-- ) + { + if (!gameobjects::entity_is_allowed(spawnpoints[i], level.allowedGameObjects)) + { + spawnpoints[i] = undefined; + } + } + ArrayRemoveValue( spawnpoints, undefined ); + } + + level.teamSpawnPoints[team] = spawnpoints; + + if ( !isdefined( level.spawnpoints ) ) + { + level.spawnpoints = []; + } + + for ( index = 0; index < level.teamSpawnPoints[team].size; index++ ) + { + spawnpoint = level.teamSpawnPoints[team][index]; + + if ( !isdefined( spawnpoint.inited ) ) + { + spawnpoint spawnpoint_init(); + level.spawnpoints[ level.spawnpoints.size ] = spawnpoint; + } + } + + for ( index = 0; index < oldSpawnPoints.size; index++ ) + { + origin = oldSpawnPoints[index].origin; + + // are these 2 lines necessary? we already did it in spawnpoint_init + level.spawnMins = math::expand_mins( level.spawnMins, origin ); + level.spawnMaxs = math::expand_maxs( level.spawnMaxs, origin ); + + level.teamSpawnPoints[team][ level.teamSpawnPoints[team].size ] = oldSpawnPoints[index]; + } +} + +function clear_spawn_points() +{ + foreach( team in level.teams ) + { + level.teamSpawnPoints[team] = []; + } + level.spawnpoints = []; + level.player_spawn_points = undefined; +} + +function add_spawn_points( team, spawnPointName ) +{ + add_spawn_point_classname( spawnPointName ); + add_spawn_point_team_classname( team, spawnPointName ); + + add_spawn_points_internal( team, get_spawnpoint_array( spawnPointName ) ); + + if ( !level.teamSpawnPoints[team].size ) + { + /# + assert( level.teamSpawnPoints[team].size, "^1ERROR: No " + spawnPointName + " spawnpoints found in level!" ); + #/ + ///# println( "^1ERROR: No " + spawnPointName + " spawnpoints found in level!" ); #/ + //callback::abort_level(); + wait 1; // so we don't try to abort more than once before the frame ends + return; + } +} + +function rebuild_spawn_points( team ) +{ + level.teamSpawnPoints[team] = []; + + for ( index = 0; index < level.spawn_point_team_class_names[team].size; index++ ) + { + add_spawn_points_internal( team, level.spawn_point_team_class_names[team][index] ); + } +} + +function place_spawn_points( spawnPointName ) +{ + add_spawn_point_classname( spawnPointName ); + + spawnPoints = get_spawnpoint_array( spawnPointName ); + + /# + if ( !isdefined( level.extraspawnpointsused ) ) + { + level.extraspawnpointsused = []; + } + #/ + + if ( !spawnPoints.size ) + { + /# println( "^1No " + spawnPointName + " spawnpoints found in level!" ); #/ + assert( spawnPoints.size, "^1No " + spawnPointName + " spawnpoints found in level!" ); + callback::abort_level(); + wait( 1 ); // so we don't try to abort more than once before the frame ends + return; + } + + for( index = 0; index < spawnPoints.size; index++ ) + { + spawnPoints[index] spawnpoint_init(); + // don't add this spawnpoint to level.spawnpoints, + // because it's an unimportant one that we don't want to do sight traces to + + /# + spawnPoints[index].fakeclassname = spawnPointName; + level.extraspawnpointsused[ level.extraspawnpointsused.size ] = spawnPoints[index]; + #/ + } +} + +// just drops to ground +// needed for demolition because secondary attacker points do +// not get used until mid round and therefore dont get processed +// this was making the rendered debug boxes appear to be floating +// and causing confusion amongst the masses. Need the points to +// be debug friendly. Cant use the place_spawn_points function because +// that does somethings that we dont want to do until they are actually used +function drop_spawn_points( spawnPointName ) +{ + spawnPoints = get_spawnpoint_array( spawnPointName ); + if ( !spawnPoints.size ) + { + /# println( "^1No " + spawnPointName + " spawnpoints found in level!" ); #/ + return; + } + + for( index = 0; index < spawnPoints.size; index++ ) + { + PlaceSpawnPoint(spawnPoints[index]); + } +} + +function add_spawn_point_classname( spawnPointClassName ) +{ + if ( !isdefined( level.spawn_point_class_names ) ) + { + level.spawn_point_class_names = []; + } + + level.spawn_point_class_names[ level.spawn_point_class_names.size ] = spawnPointClassName; +} + +function add_spawn_point_team_classname( team, spawnPointClassName ) +{ + level.spawn_point_team_class_names[team][ level.spawn_point_team_class_names[team].size ] = spawnPointClassName; +} + +function _get_spawnpoint_array( spawnpoint_name ) +{ + if ( IsDefined(level.convert_spawns_to_structs) && level.convert_spawns_to_structs ) + { + return struct::get_array( spawnpoint_name, "targetname" ); + } + else + { + return getEntArray( spawnpoint_name, "classname" ); + } +} + +function get_spawnpoint_array( classname ) +{ + spawnPoints = _get_spawnpoint_array( classname ); + + if ( !isdefined( level.extraspawnpoints ) || !isdefined( level.extraspawnpoints[classname] ) ) + { + return spawnPoints; + } + + for ( i = 0; i < level.extraspawnpoints[classname].size; i++ ) + { + spawnPoints[ spawnPoints.size ] = level.extraspawnpoints[classname][i]; + } + + return spawnPoints; +} + +function spawnpoint_init() +{ + spawnpoint = self; + origin = spawnpoint.origin; + + // we need to properly prime the mins and maxs otherwise a level that is entirely + // on one side of the zero line for any axis will have an invalid map center + if ( !level.spawnMinsMaxsPrimed ) + { + level.spawnMins = origin; + level.spawnMaxs = origin; + level.spawnMinsMaxsPrimed = true; + } + else + { + level.spawnMins = math::expand_mins( level.spawnMins, origin ); + level.spawnMaxs = math::expand_maxs( level.spawnMaxs, origin ); + } + + PlaceSpawnPoint(spawnpoint); + spawnpoint.forward = anglesToForward( spawnpoint.angles ); + spawnpoint.sightTracePoint = spawnpoint.origin + (0,0,50); + + /*skyHeight = 500; + spawnpoint.outside = true; + if ( !bullettracepassed( spawnpoint.sightTracePoint, spawnpoint.sightTracePoint + (0,0,skyHeight), false, undefined) ) + { + startpoint = spawnpoint.sightTracePoint + spawnpoint.forward * 100; + if ( !bullettracepassed( startpoint, startpoint + (0,0,skyHeight), false, undefined) ) + { + spawnpoint.outside = false; + } + }*/ + + spawnpoint.inited = true; +} + +function get_team_spawnpoints( team ) +{ + return level.teamSpawnPoints[team]; +} + +// selects a spawnpoint, preferring ones with heigher weights (or toward the beginning of the array if no weights). +// also does final things like setting self.lastspawnpoint to the one chosen. +// this takes care of avoiding telefragging, so it doesn't have to be considered by any other function. +function get_spawnpoint_final( spawnpoints, useweights, predictedSpawn, isIntermmissionSpawn = false ) +{ + + bestspawnpoint = undefined; + + if ( !isdefined( spawnpoints ) || spawnpoints.size == 0 ) + { + return undefined; + } + + if ( !isdefined( useweights ) ) + { + useweights = true; + } + + if ( !isdefined( predictedSpawn ) ) + { + predictedSpawn = false; + } + + if ( useweights ) + { + // choose spawnpoint with best weight + // (if a tie, choose randomly from the best) + bestspawnpoint = get_best_weighted_spawnpoint( spawnpoints ); + thread spawn_weight_debug( spawnpoints ); + } + else + { + // try and use the last predicted spawn point if its ok + if ( isdefined( self.lastspawnpoint ) && self.lastspawnpoint.lastspawnpredicted && !predictedSpawn && !isIntermmissionSpawn ) + { + if ( !positionWouldTelefrag( self.lastspawnpoint.origin ) ) + { + bestspawnpoint = self.lastspawnpoint; + } + } + if ( !isdefined( bestspawnpoint ) ) + { + // (only place we actually get here from is get_spawnpoint_random() ) + // no weights. prefer spawnpoints toward beginning of array + for ( i = 0; i < spawnpoints.size; i++ ) + { + if( isdefined( self.lastspawnpoint ) && self.lastspawnpoint == spawnpoints[i] && !self.lastspawnpoint.lastspawnpredicted ) + { + continue; + } + + if ( positionWouldTelefrag( spawnpoints[i].origin ) ) + { + continue; + } + + if ( isdefined( level.checkSpawnPoint ) && ![[level.checkSpawnPoint]]( spawnpoints[i], predictedspawn ) ) + continue; + + bestspawnpoint = spawnpoints[i]; + break; + } + } + if ( !isdefined( bestspawnpoint ) ) + { + // Couldn't find a useable spawnpoint. All spawnpoints either telefragged or were our last spawnpoint + // Our only hope is our last spawnpoint - unless it too will telefrag... + if ( isdefined( self.lastspawnpoint ) && !positionWouldTelefrag( self.lastspawnpoint.origin ) ) + { + // (make sure our last spawnpoint is in the valid array of spawnpoints to use) + for ( i = 0; i < spawnpoints.size; i++ ) + { + if ( isdefined( level.checkSpawnPoint ) && ![[level.checkSpawnPoint]]( spawnpoints[i], predictedspawn ) ) + continue; + + if ( spawnpoints[i] == self.lastspawnpoint ) + { + bestspawnpoint = spawnpoints[i]; + break; + } + } + } + } + } + + if ( !isdefined( bestspawnpoint ) ) + { + // couldn't find a useable spawnpoint! all will telefrag. + if ( useweights ) + { + // at this point, forget about weights. just take a random one. + bestspawnpoint = spawnpoints[randomint(spawnpoints.size)]; + } + else + { + bestspawnpoint = spawnpoints[0]; + } + } + + self finalize_spawnpoint_choice( bestspawnpoint, predictedSpawn ); + + /# + self store_spawn_data( spawnpoints, useweights, bestspawnpoint ); + #/ + + return bestspawnpoint; +} + +function finalize_spawnpoint_choice( spawnpoint, predictedSpawn ) +{ + time = getTime(); + + self.lastspawnpoint = spawnpoint; + self.lastspawntime = time; + spawnpoint.lastspawnedplayer = self; + spawnpoint.lastspawntime = time; + spawnpoint.lastspawnpredicted = predictedSpawn; +} + +function get_best_weighted_spawnpoint( spawnpoints ) +{ + maxSightTracedSpawnpoints = 3; + for ( try = 0; try <= maxSightTracedSpawnpoints; try++ ) + { + bestspawnpoints = []; + bestweight = undefined; + bestspawnpoint = undefined; + for ( i = 0; i < spawnpoints.size; i++ ) + { + if ( !isdefined( bestweight ) || spawnpoints[i].weight > bestweight ) + { + if ( positionWouldTelefrag( spawnpoints[i].origin ) ) + { + continue; + } + + bestspawnpoints = []; + bestspawnpoints[0] = spawnpoints[i]; + bestweight = spawnpoints[i].weight; + } + else if ( spawnpoints[i].weight == bestweight ) + { + if ( positionWouldTelefrag( spawnpoints[i].origin ) ) + { + continue; + } + + bestspawnpoints[bestspawnpoints.size] = spawnpoints[i]; + } + } + if ( bestspawnpoints.size == 0 ) + { + return undefined; + } + + // pick randomly from the available spawnpoints with the best weight + bestspawnpoint = bestspawnpoints[randomint( bestspawnpoints.size )]; + + if ( try == maxSightTracedSpawnpoints ) + { + return bestspawnpoint; + } + + if ( isdefined( bestspawnpoint.lastSightTraceTime ) && bestspawnpoint.lastSightTraceTime == gettime() ) + { + return bestspawnpoint; + } + + if ( !last_minute_sight_traces( bestspawnpoint ) ) + { + return bestspawnpoint; + } + + penalty = get_los_penalty(); + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + bestspawnpoint.spawnData[bestspawnpoint.spawnData.size] = "Last minute sight trace: -" + penalty; + } + #/ + bestspawnpoint.weight -= penalty; + + bestspawnpoint.lastSightTraceTime = gettime(); + } +} + +/# +function check_bad( spawnpoint ) +{ + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + + if ( !isAlive( player ) || player.sessionstate != "playing" ) + { + continue; + } + if ( level.teambased && player.team == self.team ) + { + continue; + } + + losExists = bullettracepassed(player.origin + (0,0,50), spawnpoint.sightTracePoint, false, undefined); + if ( losExists ) + { + thread bad_spawn_line( spawnpoint.sightTracePoint, player.origin + (0,0,50), self.name, player.name ); + } + } +} + +function bad_spawn_line( start, end, name1, name2 ) +{ + dist = distance(start,end); + for ( i = 0; i < 20 * 10; i++ ) + { + line( start, end, (1,0,0) ); + print3d( start, "Bad spawn! " + name1 + ", dist = " + dist ); + print3d( end, name2 ); + + wait .05; + } +} +#/ + +/# +function store_spawn_data(spawnpoints, useweights, bestspawnpoint) +{ + if (!isdefined(level.storeSpawnData) || !level.storeSpawnData) + { + return; + } + + level.storeSpawnData = GetDvarint( "scr_recordspawndata"); + if (!level.storeSpawnData) + { + return; + } + + if (!isdefined(level.spawnID)) + { + level.spawnGameID = randomint(100); + level.spawnID = 0; + } + + if (bestspawnpoint.classname == "mp_global_intermission") + { + return; + } + + level.spawnID++; + + /* + Format: + spawnid, numspawnpoints, name + [for each spawnpoint + origin, was used, weight, num data, [for each data: data], num sight checks, [for each sight check: penalty, origin] + ] + num allies, num enemies, ally origins, enemy origins, + num other data, [for each other data: other data origin, other data text] + */ + + file = openfile("spawndata.txt", "append"); + fPrintFields(file, level.spawnGameID + "." + level.spawnID + "," + spawnpoints.size + "," + self.name); + + for (i = 0; i < spawnpoints.size; i++) + { + str = vec_to_str(spawnpoints[i].origin) + ","; + if (spawnpoints[i] == bestspawnpoint) + { + str = str + "1,"; + } + else + { + str = str + "0,"; + } + + if (!useweights) + { + str += "0,"; + } + else + { + str += spawnpoints[i].weight + ","; + } + + if (!isdefined(spawnpoints[i].spawnData)) + { + spawnpoints[i].spawnData = []; + } + if (!isdefined(spawnpoints[i].sightChecks)) + { + spawnpoints[i].sightChecks = []; + } + str += spawnpoints[i].spawnData.size + ","; + for (j = 0; j < spawnpoints[i].spawnData.size; j++) + { + str += spawnpoints[i].spawnData[j] + ","; + } + str += spawnpoints[i].sightChecks.size + ","; + for (j = 0; j < spawnpoints[i].sightChecks.size; j++) + { + str += spawnpoints[i].sightChecks[j].penalty + "," + vec_to_str(spawnpoints[i].origin) + ","; + } + + fPrintFields(file, str); + } + + obj = spawnstruct(); + get_all_allied_and_enemy_players(obj); + numallies = 0; + numenemies = 0; + str = ""; + for (i = 0; i < obj.allies.size; i++) + { + if ( obj.allies[i] == self ) + { + continue; + } + numallies++; + str += vec_to_str(obj.allies[i].origin) + ","; + } + for (i = 0; i < obj.enemies.size; i++) + { + numenemies++; + str += vec_to_str(obj.enemies[i].origin) + ","; + } + str = numallies + "," + numenemies + "," + str; + fPrintFields(file, str); + + otherdata = []; + if (isdefined(level.bombguy)) + { + index = otherdata.size; + otherdata[index] = spawnstruct(); + otherdata[index].origin = level.bombguy.origin + (0,0,20); + otherdata[index].text = "Bomb holder"; + } + else if (isdefined(level.bombpos)) + { + index = otherdata.size; + otherdata[index] = spawnstruct(); + otherdata[index].origin = level.bombpos; + otherdata[index].text = "Bomb"; + } + if (isdefined(level.flags)) + { + for (i = 0; i < level.flags.size; i++) + { + index = otherdata.size; + otherdata[index] = spawnstruct(); + otherdata[index].origin = level.flags[i].origin; + otherdata[index].text = level.flags[i].useObj gameobjects::get_owner_team() + " flag"; + } + } + str = otherdata.size + ","; + for (i = 0; i < otherdata.size; i++) + { + str += vec_to_str(otherdata[i].origin) + "," + otherdata[i].text + ","; + } + fPrintFields(file, str); + + closefile(file); + + thisspawnid = level.spawnGameID + "." + level.spawnID; + if (isdefined(self.thisspawnid)) + { +// self iprintln(&"MP_PREVIOUS_SPAWN_ID", self.thisspawnid); + } +// self iprintln(&"MP_THIS_SPAWN_ID", thisspawnid); + self.thisspawnid = thisspawnid; +} + +function read_spawn_data( desiredID, relativepos ) +{ + file = openfile("spawndata.txt", "read"); + if (file < 0) + { + return; + } + + oldspawndata = level.curspawndata; + level.curspawndata = undefined; + + prev = undefined; + prevThisPlayer = undefined; + lookingForNextThisPlayer = false; + lookingForNext = false; + + if ( isdefined( relativepos ) && !isdefined( oldspawndata ) ) + { + return; + } + + while(1) + { + if (freadln(file) <= 0) + { + break; + } + data = spawnstruct(); + + data.id = fgetarg(file, 0); + numspawns = int(fgetarg(file, 1)); + if (numspawns > 256) + { + break; + } + data.playername = fgetarg(file, 2); + + data.spawnpoints = []; + data.friends = []; + data.enemies = []; + data.otherdata = []; + + for (i = 0; i < numspawns; i++) + { + if (freadln(file) <= 0) + { + break; + } + + spawnpoint = spawnstruct(); + + spawnpoint.origin = str_to_vec(fgetarg(file, 0)); + spawnpoint.winner = int(fgetarg(file, 1)); + spawnpoint.weight = int(fgetarg(file, 2)); + spawnpoint.data = []; + spawnpoint.sightchecks = []; + + if (i == 0) + { + data.minweight = spawnpoint.weight; + data.maxweight = spawnpoint.weight; + } + else + { + if (spawnpoint.weight < data.minweight) + { + data.minweight = spawnpoint.weight; + } + if (spawnpoint.weight > data.maxweight) + { + data.maxweight = spawnpoint.weight; + } + } + + argnum = 4; + + numdata = int(fgetarg(file, 3)); + if (numdata > 256) + { + break; + } + for (j = 0; j < numdata; j++) + { + spawnpoint.data[spawnpoint.data.size] = fgetarg(file, argnum); + argnum++; + } + numsightchecks = int(fgetarg(file, argnum)); + argnum++; + if (numsightchecks > 256) + { + break; + } + for (j = 0; j < numsightchecks; j++) + { + index = spawnpoint.sightchecks.size; + spawnpoint.sightchecks[index] = spawnstruct(); + spawnpoint.sightchecks[index].penalty = int(fgetarg(file, argnum)); + argnum++; + spawnpoint.sightchecks[index].origin = str_to_vec(fgetarg(file, argnum)); + argnum++; + } + + data.spawnpoints[data.spawnpoints.size] = spawnpoint; + } + + if (!isdefined(data.minweight)) + { + data.minweight = -1; + data.maxweight = 0; + } + if (data.minweight == data.maxweight) + { + data.minweight = data.minweight - 1; + } + + if (freadln(file) <= 0) + { + break; + } + numfriends = int(fgetarg(file, 0)); + numenemies = int(fgetarg(file, 1)); + if (numfriends > 32 || numenemies > 32) + { + break; + } + argnum = 2; + for (i = 0; i < numfriends; i++) + { + data.friends[data.friends.size] = str_to_vec(fgetarg(file, argnum)); + argnum++; + } + for (i = 0; i < numenemies; i++) + { + data.enemies[data.enemies.size] = str_to_vec(fgetarg(file, argnum)); + argnum++; + } + + if (freadln(file) <= 0) + { + break; + } + numotherdata = int(fgetarg(file, 0)); + argnum = 1; + for (i = 0; i < numotherdata; i++) + { + otherdata = spawnstruct(); + otherdata.origin = str_to_vec(fgetarg(file, argnum)); + argnum++; + otherdata.text = fgetarg(file, argnum); + argnum++; + + data.otherdata[data.otherdata.size] = otherdata; + } + + if ( isdefined( relativepos ) ) + { + if ( relativepos == "prevthisplayer" ) + { + if ( data.id == oldspawndata.id ) + { + level.curspawndata = prevThisPlayer; + break; + } + } + else if ( relativepos == "prev" ) + { + if ( data.id == oldspawndata.id ) + { + level.curspawndata = prev; + break; + } + } + else if ( relativepos == "nextthisplayer" ) + { + if ( lookingForNextThisPlayer ) + { + level.curspawndata = data; + break; + } + else if ( data.id == oldspawndata.id ) + { + lookingForNextThisPlayer = true; + } + } + else if ( relativepos == "next" ) + { + if ( lookingForNext ) + { + level.curspawndata = data; + break; + } + else if ( data.id == oldspawndata.id ) + { + lookingForNext = true; + } + } + } + else + { + if ( data.id == desiredID ) + { + level.curspawndata = data; + break; + } + } + + prev = data; + if ( isdefined( oldspawndata ) && data.playername == oldspawndata.playername ) + { + prevThisPlayer = data; + } + } + closefile(file); +} +function draw_spawn_data() +{ + level notify("drawing_spawn_data"); + level endon("drawing_spawn_data"); + + textoffset = (0,0,-12); + while(1) + { + if (!isdefined(level.curspawndata)) + { + wait .5; + continue; + } + + for (i = 0; i < level.curspawndata.friends.size; i++) + { + print3d(level.curspawndata.friends[i], "=)", (.5,1,.5), 1, 5); + } + for (i = 0; i < level.curspawndata.enemies.size; i++) + { + print3d(level.curspawndata.enemies[i], "=(", (1,.5,.5), 1, 5); + } + + for (i = 0; i < level.curspawndata.otherdata.size; i++) + { + print3d(level.curspawndata.otherdata[i].origin, level.curspawndata.otherdata[i].text, (.5,.75,1), 1, 2); + } + + for (i = 0; i < level.curspawndata.spawnpoints.size; i++) + { + sp = level.curspawndata.spawnpoints[i]; + orig = sp.sightTracePoint; + if (sp.winner) + { + print3d(orig, level.curspawndata.playername + " spawned here", (.5,.5,1), 1, 2); + orig += textoffset; + } + amnt = (sp.weight - level.curspawndata.minweight) / (level.curspawndata.maxweight - level.curspawndata.minweight); + print3d(orig, "Weight: " + sp.weight, (1-amnt,amnt,.5)); + orig += textoffset; + for (j = 0; j < sp.data.size; j++) + { + print3d(orig, sp.data[j], (1,1,1)); + orig += textoffset; + } + for (j = 0; j < sp.sightchecks.size; j++) + { + //line(sp.origin, sp.sightchecks[j].origin, (1,0,0)); + print3d(orig, "Sightchecks: -" + sp.sightchecks[j].penalty, (1,.5,.5)); + orig += textoffset; + } + } + + wait .05; + } +} + +function vec_to_str(vec) +{ + return int(vec[0]) + "/" + int(vec[1]) + "/" + int(vec[2]); +} +function str_to_vec(str) +{ + parts = strtok(str, "/"); + if (parts.size != 3) + { + return (0,0,0); + } + return (int(parts[0]), int(parts[1]), int(parts[2])); +} +#/ + +function get_spawnpoint_random( spawnpoints, predictedSpawn, isIntermissionSpawn = false ) +{ +// level endon("game_ended"); + + // There are no valid spawnpoints in the map + if(!isdefined(spawnpoints)) + return undefined; + + // randomize order + for(i = 0; i < spawnpoints.size; i++) + { + j = randomInt(spawnpoints.size); + spawnpoint = spawnpoints[i]; + spawnpoints[i] = spawnpoints[j]; + spawnpoints[j] = spawnpoint; + } + + return get_spawnpoint_final( spawnpoints, false, predictedSpawn, isIntermissionSpawn ); +} + +function get_all_other_players() +{ + aliveplayers = []; + + // Make a list of fully connected, non-spectating, alive players + for(i = 0; i < level.players.size; i++) + { + if ( !isdefined( level.players[i] ) ) + { + continue; + } + player = level.players[i]; + + if ( player.sessionstate != "playing" || player == self ) + { + continue; + } + + aliveplayers[aliveplayers.size] = player; + } + return aliveplayers; +} + + +function get_all_allied_and_enemy_players( obj ) +{ + if ( level.teambased ) + { + assert( isdefined( level.teams[self.team] ) ); + obj.allies = level.alivePlayers[self.team]; + + obj.enemies = undefined; + foreach( team in level.teams ) + { + if ( team == self.team ) + { + continue; + } + + if ( !isdefined( obj.enemies ) ) + { + obj.enemies = level.alivePlayers[team]; + } + else + { + foreach( player in level.alivePlayers[team] ) + { + obj.enemies[obj.enemies.size] = player; + } + } + } + } + else + { + obj.allies = []; + obj.enemies = level.activePlayers; + } +} + +// weight array manipulation code +function init_weights(spawnpoints) +{ + for (i = 0; i < spawnpoints.size; i++) + { + spawnpoints[i].weight = 0; + } + + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + for (i = 0; i < spawnpoints.size; i++) + { + spawnpoints[i].spawnData = []; + spawnpoints[i].sightChecks = []; + } + } + #/ +} + +// ================================================ + + +function get_spawnpoint_near_team( spawnpoints, favoredspawnpoints ) +{ +// level endon("game_ended"); + + /*if ( self.wantSafeSpawn ) + { + return getSpawnpoint_SafeSpawn( spawnpoints ); + }*/ + + // There are no valid spawnpoints in the map + if(!isdefined(spawnpoints)) + { + return undefined; + } + + /# + if ( GetDvarString( "scr_spawn_randomly") == "" ) + { + SetDvar("scr_spawn_randomly", "0"); + } + if ( GetDvarString( "scr_spawn_randomly") == "1" ) + { + return get_spawnpoint_random( spawnpoints ); + } + #/ + + + if ( GetDvarint( "scr_spawnsimple") > 0 ) + { + return get_spawnpoint_random( spawnpoints ); + } + + begin(); + + k_favored_spawn_point_bonus= 25000; + + init_weights(spawnpoints); + + obj = spawnstruct(); + get_all_allied_and_enemy_players(obj); + + numplayers = obj.allies.size + obj.enemies.size; + + alliedDistanceWeight = 2; + + myTeam = self.team; + for (i = 0; i < spawnpoints.size; i++) + { + spawnpoint = spawnpoints[i]; + + if (!isdefined(spawnpoint.numPlayersAtLastUpdate)) + { + spawnpoint.numPlayersAtLastUpdate= 0; + } + + if ( spawnpoint.numPlayersAtLastUpdate > 0 ) + { + allyDistSum = spawnpoint.distSum[ myTeam ]; + enemyDistSum = spawnpoint.enemyDistSum[ myTeam ]; + + // high enemy distance is good, high ally distance is bad + spawnpoint.weight = (enemyDistSum - alliedDistanceWeight*allyDistSum) / spawnpoint.numPlayersAtLastUpdate; + + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + spawnpoint.spawnData[spawnpoint.spawnData.size] = "Base weight: " + int(spawnpoint.weight) + " = (" + int(enemyDistSum) + " - " + alliedDistanceWeight + "*" + int(allyDistSum) + ") / " + spawnpoint.numPlayersAtLastUpdate; + } + #/ + } + else + { + spawnpoint.weight = 0; + + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + spawnpoint.spawnData[spawnpoint.spawnData.size] = "Base weight: 0"; + } + #/ + } + } + + if (isdefined(favoredspawnpoints)) + { + for (i= 0; i < favoredspawnpoints.size; i++) + { + if (isdefined(favoredspawnpoints[i].weight)) + { + favoredspawnpoints[i].weight+= k_favored_spawn_point_bonus; + } + else + { + favoredspawnpoints[i].weight= k_favored_spawn_point_bonus; + } + } + } + + + + avoid_same_spawn(spawnpoints); + avoid_spawn_reuse(spawnpoints, true); + // not avoiding spawning near recent deaths for team-based modes. kills the fast pace. + //avoid_dangerous_spawns(spawnpoints, true); + avoid_weapon_damage(spawnpoints); + avoid_visible_enemies(spawnpoints, true); + + + result = get_spawnpoint_final(spawnpoints); + + /# + if ( GetDvarString( "scr_spawn_showbad") == "" ) + { + SetDvar("scr_spawn_showbad", "0"); + } + if ( GetDvarString( "scr_spawn_showbad") == "1" ) + { + check_bad( result ); + } + #/ + + return result; +} + +///////////////////////////////////////////////////////////////////////// + +function get_spawnpoint_dm(spawnpoints) +{ +// level endon("game_ended"); + + /*if ( self.wantSafeSpawn ) + { + return getSpawnpoint_SafeSpawn( spawnpoints ); + }*/ + + // There are no valid spawnpoints in the map + if(!isdefined(spawnpoints)) + { + return undefined; + } + + begin(); + + init_weights(spawnpoints); + + aliveplayers = get_all_other_players(); + + // new logic: we want most players near idealDist units away. + // players closer than badDist units will be considered negatively + idealDist = 1600; + badDist = 1200; + + if (aliveplayers.size > 0) + { + for (i = 0; i < spawnpoints.size; i++) + { + totalDistFromIdeal = 0; + nearbyBadAmount = 0; + for (j = 0; j < aliveplayers.size; j++) + { + dist = distance(spawnpoints[i].origin, aliveplayers[j].origin); + + if (dist < badDist) + { + nearbyBadAmount += (badDist - dist) / badDist; + } + + distfromideal = abs(dist - idealDist); + totalDistFromIdeal += distfromideal; + } + avgDistFromIdeal = totalDistFromIdeal / aliveplayers.size; + + wellDistancedAmount = (idealDist - avgDistFromIdeal) / idealDist; + // if (wellDistancedAmount < 0) wellDistancedAmount = 0; + + // wellDistancedAmount is between -inf and 1, 1 being best (likely around 0 to 1) + // nearbyBadAmount is between 0 and inf, + // and it is very important that we get a bad weight if we have a high nearbyBadAmount. + + spawnpoints[i].weight = wellDistancedAmount - nearbyBadAmount * 2 + randomfloat(.2); + } + } + + avoid_same_spawn(spawnpoints); + avoid_spawn_reuse(spawnpoints, false); + //avoid_dangerous_spawns(spawnpoints, false); + avoid_weapon_damage(spawnpoints); + avoid_visible_enemies(spawnpoints, false); + + return get_spawnpoint_final(spawnpoints); +} + +// ============================================= + +// called at the start of every spawn +function begin() +{ + //update_death_info(); + + /# + level.storeSpawnData = GetDvarint( "scr_recordspawndata"); + level.debugSpawning = (GetDvarint( "scr_spawnpointdebug") > 0); + #/ +} + +/# + +function watch_spawn_profile() +{ + while(1) + { + while(1) + { + if (GetDvarint( "scr_spawnprofile") > 0) + { + break; + } + wait .05; + } + + thread spawn_profile(); + + while(1) + { + if (GetDvarint( "scr_spawnprofile") <= 0) + { + break; + } + wait .05; + } + + level notify("stop_spawn_profile"); + } +} + +function spawn_profile() +{ + level endon("stop_spawn_profile"); + while(1) + { + if ( level.players.size > 0 && level.spawnpoints.size > 0 ) + { + playerNum = randomint(level.players.size); + player = level.players[playerNum]; + attempt = 1; + while ( !isdefined( player ) && attempt < level.players.size ) + { + playerNum = ( playerNum + 1 ) % level.players.size; + attempt++; + player = level.players[playerNum]; + } + + player get_spawnpoint_near_team(level.spawnpoints); + } + wait .05; + } +} + +function spawn_graph_check() +{ + while(1) + { + if ( GetDvarint( "scr_spawngraph") < 1 ) + { + wait 3; + continue; + } + thread spawn_graph(); + return; + } +} + +function spawn_graph() +{ + w = 20; + h = 20; + weightscale = .1; + fakespawnpoints = []; + + corners = getentarray("minimap_corner", "targetname"); + if ( corners.size != 2 ) + { + println("^1 can't spawn graph: no minimap corners"); + return; + } + min = corners[0].origin; + max = corners[0].origin; + if ( corners[1].origin[0] > max[0] ) + { + max = (corners[1].origin[0], max[1], max[2]); + } + else + { + min = (corners[1].origin[0], min[1], min[2]); + } + if ( corners[1].origin[1] > max[1] ) + { + max = (max[0], corners[1].origin[1], max[2]); + } + else + { + min = (min[0], corners[1].origin[1], min[2]); + } + + i = 0; + for ( y = 0; y < h; y++ ) + { + yamnt = y / (h - 1); + for ( x = 0; x < w; x++ ) + { + xamnt = x / (w - 1); + fakespawnpoints[i] = spawnstruct(); + fakespawnpoints[i].origin = (min[0] * xamnt + max[0] * (1-xamnt), min[1] * yamnt + max[1] * (1-yamnt), min[2]); + fakespawnpoints[i].angles = (0,0,0); + + fakespawnpoints[i].forward = anglesToForward( fakespawnpoints[i].angles ); + fakespawnpoints[i].sightTracePoint = fakespawnpoints[i].origin; + + i++; + } + } + + didweights = false; + + while(1) + { + spawni = 0; + numiters = 5; + for ( i = 0; i < numiters; i++ ) + { + if ( !level.players.size || !isdefined( level.players[0].team ) || level.players[0].team == "spectator" || !isdefined( level.players[0].curClass ) ) + { + break; + } + + endspawni = spawni + fakespawnpoints.size / numiters; + if ( i == numiters - 1 ) + { + endspawni = fakespawnpoints.size; + } + + for ( ; spawni < endspawni; spawni++ ) + { + spawnpoint_update( fakespawnpoints[spawni] ); + } + + if ( didweights ) + { + level.players[0] draw_spawn_graph( fakespawnpoints, w, h, weightscale ); + } + + wait .05; + } + + if ( !level.players.size || !isdefined( level.players[0].team ) || level.players[0].team == "spectator" || !isdefined( level.players[0].curClass ) ) + { + wait 1; + continue; + } + + level.players[0] get_spawnpoint_near_team( fakespawnpoints ); + + for ( i = 0; i < fakespawnpoints.size; i++ ) + { + setup_spawn_graph_point( fakespawnpoints[i], weightscale ); + } + + didweights = true; + + level.players[0] draw_spawn_graph( fakespawnpoints, w, h, weightscale ); + + wait .05; + } +} + +function draw_spawn_graph( fakespawnpoints, w, h, weightscale ) +{ + i = 0; + for ( y = 0; y < h; y++ ) + { + yamnt = y / (h - 1); + for ( x = 0; x < w; x++ ) + { + xamnt = x / (w - 1); + + if ( y > 0 ) + { + spawn_graph_line( fakespawnpoints[i], fakespawnpoints[i-w], weightscale ); + } + if ( x > 0 ) + { + spawn_graph_line( fakespawnpoints[i], fakespawnpoints[i-1], weightscale ); + } + i++; + } + } +} + +function setup_spawn_graph_point( s1, weightscale ) +{ + s1.visible = true; + if ( s1.weight < -1000/weightscale ) + { + s1.visible = false; + } +} + +function spawn_graph_line( s1, s2, weightscale ) +{ + if ( !s1.visible || !s2.visible ) + { + return; + } + + p1 = s1.origin + (0,0,s1.weight*weightscale + 100); + p2 = s2.origin + (0,0,s2.weight*weightscale + 100); + + line( p1, p2, (1,1,1) ); +} + +// DEBUG +function loop_bot_spawns() +{ + while(1) + { + if ( GetDvarint( "scr_killbots") < 1 ) + { + wait 3; + continue; + } + if ( !isdefined( level.players ) ) + { + wait .05; + continue; + } + + bots = []; + for (i = 0; i < level.players.size; i++) + { + if ( !isdefined( level.players[i] ) ) + { + continue; + } + + if ( level.players[i].sessionstate == "playing" && issubstr(level.players[i].name, "bot") ) + { + bots[bots.size] = level.players[i]; + } + } + if ( bots.size > 0 ) + { + if ( GetDvarint( "scr_killbots" ) == 1 ) + { + killer = bots[randomint(bots.size)]; + victim = bots[randomint(bots.size)]; + + victim thread [[level.callbackPlayerDamage]] ( + killer, // eInflictor The entity that causes the damage.(e.g. a turret) + killer, // eAttacker The entity that is attacking. + 1000, // iDamage Integer specifying the amount of damage done + 0, // iDFlags Integer specifying flags that are to be applied to the damage + "MOD_RIFLE_BULLET", // sMeansOfDeath Integer specifying the method of death + level.weaponNone, // weapon The weapon used to inflict the damage + (0,0,0), // vPoint The point the damage is from? + (0,0,0), // vDir The direction of the damage + "none", // sHitLoc The location of the hit + (0,0,0), // vDamageOrigin + 0, // psOffsetTime The time offset for the damage + 0, // boneIndex + (1,0,0) // vSurfaceNormal + ); + } + else + { + numKills = GetDvarint( "scr_killbots" ); + lastVictim = undefined; + for ( index = 0; index < numKills; index++ ) + { + killer = bots[randomint(bots.size)]; + victim = bots[randomint(bots.size)]; + + while ( isdefined( lastVictim ) && victim == lastVictim ) + { + victim = bots[randomint(bots.size)]; + } + + victim thread [[level.callbackPlayerDamage]] ( + killer, // eInflictor The entity that causes the damage.(e.g. a turret) + killer, // eAttacker The entity that is attacking. + 1000, // iDamage Integer specifying the amount of damage done + 0, // iDFlags Integer specifying flags that are to be applied to the damage + "MOD_RIFLE_BULLET", // sMeansOfDeath Integer specifying the method of death + "none", // sWeapon The weapon number of the weapon used to inflict the damage + (0,0,0), // vPoint The point the damage is from? + (0,0,0), // vDir The direction of the damage + "none", // sHitLoc + (0,0,0), // vDamageOrigin, + 0, // psOffsetTime The time offset for the damage + 0, // boneIndex + (1,0,0) // vSurfaceNormal + ); + + lastVictim = victim; + } + } + } + + if ( GetDvarString( "scr_killbottimer") != "" ) + { + wait GetDvarfloat( "scr_killbottimer"); + } + else + { + wait .05; + } + } +} + +// DEBUG +function allow_spawn_data_reading() +{ + SetDvar("scr_showspawnid", ""); + prevval = GetDvarString( "scr_showspawnid"); + + prevrelval = GetDvarString( "scr_spawnidcycle"); + + readthistime = false; + + while(1) + { + val = GetDvarString( "scr_showspawnid"); + relval = undefined; + if (!isdefined(val) || val == prevval) + { + relval = GetDvarString( "scr_spawnidcycle"); + if ( isdefined( relval ) && relval != "" ) + { + SetDvar("scr_spawnidcycle", ""); + } + else + { + wait(.5); + continue; + } + } + prevval = val; + + readthistime = false; + + read_spawn_data( val, relval ); + + if ( !isdefined( level.curspawndata ) ) + { + println( "No spawn data to draw." ); + } + else + { + println( "Drawing spawn ID " + level.curspawndata.id ); + } + + thread draw_spawn_data(); + } +} +#/ +// DEBUG +function show_deaths_debug() +{ + /# + while(1) + { + if (GetDvarString( "scr_spawnpointdebug") == "0") + { + wait(3); + continue; + } + + time = getTime(); + + for (i = 0; i < level.spawnlogic_deaths.size; i++) + { + if (isdefined(level.spawnlogic_deaths[i].los)) + { + line(level.spawnlogic_deaths[i].org, level.spawnlogic_deaths[i].killOrg, (1,0,0)); // line-of-sights are shown in red + } + else + { + line(level.spawnlogic_deaths[i].org, level.spawnlogic_deaths[i].killOrg, (1,1,1)); + } + killer = level.spawnlogic_deaths[i].killer; + if (isdefined(killer) && isalive(killer)) + { + line(level.spawnlogic_deaths[i].killOrg, killer.origin, (.4,.4,.8)); + } + } + + for (p = 0; p < level.players.size; p++) + { + if ( !isdefined( level.players[p] ) ) + { + continue; + } + if (isdefined(level.players[p].spawnlogic_killdist)) + { + print3d(level.players[p].origin + (0,0,64), level.players[p].spawnlogic_killdist, (1,1,1)); + } + } + + oldspawnkills = level.spawnlogic_spawnkills; + level.spawnlogic_spawnkills = []; + for (i = 0; i < oldspawnkills.size; i++) + { + spawnkill = oldspawnkills[i]; + + /*spawnkill.dierwasspawner = true; + spawnkill.dierorigin = dier.origin; + spawnkill.killerorigin = killer.origin; + spawnkill.spawnpointorigin = dier.lastspawnpoint.origin; + spawnkill.time = time;*/ + + if (spawnkill.dierwasspawner) + { + line(spawnkill.spawnpointorigin, spawnkill.dierorigin, (.4,.5,.4)); + line(spawnkill.dierorigin, spawnkill.killerorigin, (0,1,1)); + print3d(spawnkill.dierorigin + (0,0,32), "SPAWNKILLED!", (0,1,1)); + } + else + { + line(spawnkill.spawnpointorigin, spawnkill.killerorigin, (.4,.5,.4)); + line(spawnkill.killerorigin, spawnkill.dierorigin, (0,1,1)); + print3d(spawnkill.dierorigin + (0,0,32), "SPAWNDIED!", (0,1,1)); + } + + if (time - spawnkill.time < 60*1000) + { + level.spawnlogic_spawnkills[level.spawnlogic_spawnkills.size] = oldspawnkills[i]; + } + } + wait(.05); + } + #/ +} +// DEBUG +function update_death_info_debug() +{ + while(1) + { + if (GetDvarString( "scr_spawnpointdebug") == "0") + { + wait(3); + continue; + } + update_death_info(); + wait(3); + } +} +// DEBUG +function spawn_weight_debug(spawnpoints) +{ + level notify("stop_spawn_weight_debug"); + level endon("stop_spawn_weight_debug"); + /# + while(1) + { + if (GetDvarString( "scr_spawnpointdebug") == "0") + { + wait(3); + continue; + } + textoffset = (0,0,-12); + for (i = 0; i < spawnpoints.size; i++) + { + amnt = 1 * (1 - spawnpoints[i].weight / (-100000)); + if (amnt < 0) + { + amnt = 0; + } + if (amnt > 1) + { + amnt = 1; + } + + orig = spawnpoints[i].origin + (0,0,80); + + print3d(orig, int(spawnpoints[i].weight), (1,amnt,.5)); + orig += textoffset; + + if (isdefined(spawnpoints[i].spawnData)) + { + for (j = 0; j < spawnpoints[i].spawnData.size; j++) + { + print3d(orig, spawnpoints[i].spawnData[j], (.5,.5,.5)); + orig += textoffset; + } + } + if (isdefined(spawnpoints[i].sightChecks)) + { + for (j = 0; j < spawnpoints[i].sightChecks.size; j++) + { + if ( spawnpoints[i].sightChecks[j].penalty == 0 ) + { + continue; + } + print3d(orig, "Sight to enemy: -" + spawnpoints[i].sightChecks[j].penalty, (.5,.5,.5)); + orig += textoffset; + } + } + } + wait(.05); + } + #/ +} +// DEBUG +function profile_debug() +{ + while(1) + { + if (GetDvarString( "scr_spawnpointprofile") != "1") + { + wait(3); + continue; + } + + for (i = 0; i < level.spawnpoints.size; i++) + { + level.spawnpoints[i].weight = randomint(10000); + } + if (level.players.size > 0) + { + level.players[randomint(level.players.size)] get_spawnpoint_near_team(level.spawnpoints); + } + + wait(.05); + } +} +// DEBUG +function debug_nearby_players(players, origin) +{ + /# + if (GetDvarString( "scr_spawnpointdebug") == "0") + { + return; + } + starttime = gettime(); + while(1) + { + for (i = 0; i < players.size; i++) + { + line(players[i].origin, origin, (.5,1,.5)); + } + if (gettime() - starttime > 5000) + { + return; + } + wait .05; + } + #/ +} + +function death_occured(dier, killer) +{ + /*if (!isdefined(killer) || !isdefined(dier) || !isplayer(killer) || !isplayer(dier) || killer == dier) + return; + + time = getTime(); + + // DEBUG + // check if there was a spawn kill + if (time - dier.lastspawntime < 5*1000 && distance(dier.origin, dier.lastspawnpoint.origin) < 300) + { + spawnkill = spawnstruct(); + spawnkill.dierwasspawner = true; + spawnkill.dierorigin = dier.origin; + spawnkill.killerorigin = killer.origin; + spawnkill.spawnpointorigin = dier.lastspawnpoint.origin; + spawnkill.time = time; + level.spawnlogic_spawnkills[level.spawnlogic_spawnkills.size] = spawnkill; + } + else if (time - killer.lastspawntime < 5*1000 && distance(killer.origin, killer.lastspawnpoint.origin) < 300) + { + spawnkill = spawnstruct(); + spawnkill.dierwasspawner = false; + spawnkill.dierorigin = dier.origin; + spawnkill.killerorigin = killer.origin; + spawnkill.spawnpointorigin = killer.lastspawnpoint.origin; + spawnkill.time = time; + level.spawnlogic_spawnkills[level.spawnlogic_spawnkills.size] = spawnkill; + } + + // record kill information + deathInfo = spawnstruct(); + + deathInfo.time = time; + deathInfo.org = dier.origin; + deathInfo.killOrg = killer.origin; + deathInfo.killer = killer; + + check_for_similar_deaths(deathInfo); + level.spawnlogic_deaths[level.spawnlogic_deaths.size] = deathInfo; + + // keep track of the most dangerous players in terms of how far they have killed people recently + dist = distance(dier.origin, killer.origin); + if (!isdefined(killer.spawnlogic_killdist) || time - killer.spawnlogic_killtime > 1000*30 || dist > killer.spawnlogic_killdist) + { + killer.spawnlogic_killdist = dist; + killer.spawnlogic_killtime = time; + }*/ +} +function check_for_similar_deaths(deathInfo) +{ + // check if this is really similar to any old deaths, and if so, mark them for removal later + for (i = 0; i < level.spawnlogic_deaths.size; i++) + { + if (level.spawnlogic_deaths[i].killer == deathInfo.killer) + { + dist = distance(level.spawnlogic_deaths[i].org, deathInfo.org); + if (dist > 200) + { + continue; + } + dist = distance(level.spawnlogic_deaths[i].killOrg, deathInfo.killOrg); + if (dist > 200) + { + continue; + } + + level.spawnlogic_deaths[i].remove = true; + } + } +} + +function update_death_info() +{ + + time = getTime(); + for (i = 0; i < level.spawnlogic_deaths.size; i++) + { + // if the killer has walked away or enough time has passed, get rid of this death information + deathInfo = level.spawnlogic_deaths[i]; + + if (time - deathInfo.time > 1000*90 || // if 90 seconds have passed + !isdefined(deathInfo.killer) || + !isalive(deathInfo.killer) || + (!isdefined( level.teams[deathInfo.killer.team] )) || + distance(deathInfo.killer.origin, deathInfo.killOrg) > 400) + { + level.spawnlogic_deaths[i].remove = true; + } + } + + // remove all deaths with remove set + oldarray = level.spawnlogic_deaths; + level.spawnlogic_deaths = []; + + // never keep more than the 1024 most recent entries in the array + start = 0; + if (oldarray.size - 1024 > 0) + { + start = oldarray.size - 1024; + } + + for (i = start; i < oldarray.size; i++) + { + if (!isdefined(oldarray[i].remove)) + { + level.spawnlogic_deaths[level.spawnlogic_deaths.size] = oldarray[i]; + } + } + +} + +/* +// uses death information to reduce the weights of spawns that might cause spawn kills +function avoid_dangerous_spawns(spawnpoints, teambased) // (assign weights to the return value of this) +{ + // DEBUG + if (GetDvarString( "scr_spawnpointnewlogic") == "0") +{ + return; + } + + // DEBUG + + deathpenalty = 100000; + if (GetDvarString( "scr_spawnpointdeathpenalty") != "" && GetDvarString( "scr_spawnpointdeathpenalty") != "0") + { + deathpenalty = GetDvarfloat( "scr_spawnpointdeathpenalty"); + } + + maxDist = 200; + if (GetDvarString( "scr_spawnpointmaxdist") != "" && GetDvarString( "scr_spawnpointmaxdist") != "0") + { + maxdist = GetDvarfloat( "scr_spawnpointmaxdist"); + } + + maxDistSquared = maxDist*maxDist; + for (i = 0; i < spawnpoints.size; i++) + { + for (d = 0; d < level.spawnlogic_deaths.size; d++) + { + // (we've got a lotta checks to do, want to rule them out quickly) + distSqrd = distanceSquared(spawnpoints[i].origin, level.spawnlogic_deaths[d].org); + if (distSqrd > maxDistSquared) + { + continue; + } + + // make sure the killer in question is on the opposing team + player = level.spawnlogic_deaths[d].killer; + if (!isalive(player)) + { + continue; + } + if (player == self) + { + continue; + } + if (teambased && player.team == self.team) + { + continue; + } + + // (no sqrt, must recalculate distance) + dist = distance(spawnpoints[i].origin, level.spawnlogic_deaths[d].org); + spawnpoints[i].weight -= (1 - dist/maxDist) * deathpenalty; // possible spawn kills are *really* bad + } + } + + // DEBUG +} +*/ + + +// used by spawning; needs to be fast. +function is_point_vulnerable(playerorigin) +{ + pos = self.origin + level.bettymodelcenteroffset; + playerpos = playerorigin + (0,0,32); + distsqrd = distancesquared(pos, playerpos); + + forward = anglestoforward(self.angles); + + if (distsqrd < level.bettyDetectionRadius*level.bettyDetectionRadius) + { + playerdir = vectornormalize(playerpos - pos); + angle = acos(vectordot(playerdir, forward)); + if (angle < level.bettyDetectionConeAngle) + { + return true; + } + } + return false; +} + + +function avoid_weapon_damage(spawnpoints) +{ + if (GetDvarString( "scr_spawnpointnewlogic") == "0") + { + return; + } + + + weaponDamagePenalty = 100000; + if (GetDvarString( "scr_spawnpointweaponpenalty") != "" && GetDvarString( "scr_spawnpointweaponpenalty") != "0") + { + weaponDamagePenalty = GetDvarfloat( "scr_spawnpointweaponpenalty"); + } + + mingrenadedistsquared = 250*250; // (actual grenade radius is 220, 250 includes a safety area of 30 units) + + for (i = 0; i < spawnpoints.size; i++) + { + for (j = 0; j < level.grenades.size; j++) + { + if ( !isdefined( level.grenades[j] ) ) + { + continue; + } + + // could also do a sight check to see if it's really dangerous. + if (distancesquared(spawnpoints[i].origin, level.grenades[j].origin) < mingrenadedistsquared) + { + spawnpoints[i].weight -= weaponDamagePenalty; + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + spawnpoints[i].spawnData[spawnpoints[i].spawnData.size] = "Was near grenade: -" + int(weaponDamagePenalty); + } + #/ + } + } + } + +} + +function spawn_per_frame_update() +{ + spawnpointindex = 0; + + // each frame, do sight checks against a spawnpoint + + while(1) + { + wait .05; + + //time = gettime(); + + if ( !isdefined( level.spawnPoints ) ) + { + return; + } + + spawnpointindex = (spawnpointindex + 1) % level.spawnPoints.size; + spawnpoint = level.spawnPoints[spawnpointindex]; + + spawnpoint_update( spawnpoint ); + } +} + +function get_non_team_sum( skip_team, sums ) +{ + value = 0; + foreach( team in level.teams ) + { + if ( team == skip_team ) + { + continue; + } + + value += sums[team]; + } + + return value; +} + +function get_non_team_min_dist( skip_team, minDists ) +{ + dist = 9999999; + foreach( team in level.teams ) + { + if ( team == skip_team ) + { + continue; + } + + if ( dist > minDists[team] ) + { + dist = minDists[team]; + } + } + + return dist; +} + + +function spawnpoint_update( spawnpoint ) +{ + if ( level.teambased ) + { + sights = []; + foreach( team in level.teams ) + { + spawnpoint.enemySights[team] = 0; + sights[team] = 0; + spawnpoint.nearbyPlayers[team] = []; + } + } + else + { + spawnpoint.enemySights = 0; + spawnpoint.nearbyPlayers["all"] = []; + } + + spawnpointdir = spawnpoint.forward; + + debug = false; + /# + debug = (GetDvarint( "scr_spawnpointdebug") > 0); + #/ + + minDist = []; + distSum = []; + + if ( !level.teambased ) + { + minDist["all"] = 9999999; + } + + foreach( team in level.teams ) + { + spawnpoint.distSum[team] = 0; + spawnpoint.enemyDistSum[team] = 0; + spawnpoint.minEnemyDist[team] = 9999999; + minDist[team] = 9999999; + } + + spawnpoint.numPlayersAtLastUpdate = 0; + + for (i = 0; i < level.players.size; i++) + { + player = level.players[i]; + + if ( player.sessionstate != "playing" ) + { + continue; + } + + diff = player.origin - spawnpoint.origin; + diff = (diff[0], diff[1], 0); + dist = length( diff ); // needs to be actual distance for distSum value + + team = "all"; + if ( level.teambased ) + { + team = player.team; + } + + if ( dist < 1024 ) + { + spawnpoint.nearbyPlayers[team][spawnpoint.nearbyPlayers[team].size] = player; + } + + if ( dist < minDist[team] ) + { + minDist[team] = dist; + } + + distSum[ team ] += dist; + spawnpoint.numPlayersAtLastUpdate++; + + pdir = anglestoforward(player.angles); + if (vectordot(spawnpointdir, diff) < 0 && vectordot(pdir, diff) > 0) + { + continue; // player and spawnpoint are looking in opposite directions + } + + // do sight check + losExists = bullettracepassed(player.origin + (0,0,50), spawnpoint.sightTracePoint, false, undefined); + + spawnpoint.lastSightTraceTime = gettime(); + + if (losExists) + { + if ( level.teamBased ) + { + sights[player.team]++; + } + else + { + spawnpoint.enemySights++; + } + + // DEBUG + //println("Sight check succeeded!"); + + /* + death info stuff is disabled right now + // pretend this player killed a person at this spawnpoint, so we don't try to use it again any time soon. + deathInfo = spawnstruct(); + + deathInfo.time = time; + deathInfo.org = spawnpoint.origin; + deathInfo.killOrg = player.origin; + deathInfo.killer = player; + deathInfo.los = true; + + check_for_similar_deaths(deathInfo); + level.spawnlogic_deaths[level.spawnlogic_deaths.size] = deathInfo; + */ + + /# + if ( debug ) + { + line(player.origin + (0,0,50), spawnpoint.sightTracePoint, (.5,1,.5)); + } + #/ + } + //else + // line(player.origin + (0,0,50), spawnpoint.sightTracePoint, (1,.5,.5)); + } + + if ( level.teamBased ) + { + foreach( team in level.teams ) + { + spawnpoint.enemySights[team] = get_non_team_sum( team, sights ); + spawnpoint.minEnemyDist[team] = get_non_team_min_dist( team, minDist ); + spawnpoint.distSum[team] = distSum[team]; + spawnpoint.enemyDistSum[team] = get_non_team_sum( team, distSum); + } + } + else + { + spawnpoint.distSum["all"] = distSum["all"]; + spawnpoint.enemyDistSum["all"] = distSum["all"]; + spawnpoint.minEnemyDist["all"] = minDist["all"]; + } + +} + +function get_los_penalty() +{ + if (GetDvarString( "scr_spawnpointlospenalty") != "" && GetDvarString( "scr_spawnpointlospenalty") != "0") + { + return GetDvarfloat( "scr_spawnpointlospenalty"); + } + return 100000; +} + +function last_minute_sight_traces( spawnpoint ) +{ + if ( !isdefined( spawnpoint.nearbyPlayers ) ) + { + return false; + } + + closest = undefined; + closestDistsq = undefined; + secondClosest = undefined; + secondClosestDistsq = undefined; + + foreach( team in spawnpoint.nearbyPlayers ) + { + if ( team == self.team ) + { + continue; + } + + for ( i = 0; i < spawnpoint.nearbyPlayers[team].size; i++ ) + { + player = spawnpoint.nearbyPlayers[team][i]; + + if ( !isdefined( player ) ) + { + continue; + } + if ( player.sessionstate != "playing" ) + { + continue; + } + if ( player == self ) + { + continue; + } + + distsq = distanceSquared( spawnpoint.origin, player.origin ); + if ( !isdefined( closest ) || distsq < closestDistsq ) + { + secondClosest = closest; + secondClosestDistsq = closestDistsq; + + closest = player; + closestDistSq = distsq; + } + else if ( !isdefined( secondClosest ) || distsq < secondClosestDistSq ) + { + secondClosest = player; + secondClosestDistSq = distsq; + } + } + } + + if ( isdefined( closest ) ) + { + if ( bullettracepassed( closest.origin + (0,0,50), spawnpoint.sightTracePoint, false, undefined) ) + { + return true; + } + } + if ( isdefined( secondClosest ) ) + { + if ( bullettracepassed( secondClosest.origin + (0,0,50), spawnpoint.sightTracePoint, false, undefined) ) + { + return true; + } + } + + return false; +} + + + +function avoid_visible_enemies(spawnpoints, teambased) +{ + if (GetDvarString( "scr_spawnpointnewlogic") == "0") + { + return; + } + + // DEBUG + + lospenalty = get_los_penalty(); + + minDistTeam = self.team; + + if ( teambased ) + { + for ( i = 0; i < spawnpoints.size; i++ ) + { + if ( !isdefined(spawnpoints[i].enemySights) ) + { + continue; + } + + penalty = lospenalty * spawnpoints[i].enemySights[self.team]; + spawnpoints[i].weight -= penalty; + + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + index = spawnpoints[i].sightChecks.size; + spawnpoints[i].sightChecks[index] = spawnstruct(); + spawnpoints[i].sightChecks[index].penalty = penalty; + } + #/ + } + } + else + { + for ( i = 0; i < spawnpoints.size; i++ ) + { + if ( !isdefined(spawnpoints[i].enemySights) ) + { + continue; + } + + penalty = lospenalty * spawnpoints[i].enemySights; + spawnpoints[i].weight -= penalty; + + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + index = spawnpoints[i].sightChecks.size; + spawnpoints[i].sightChecks[index] = spawnstruct(); + spawnpoints[i].sightChecks[index].penalty = penalty; + } + #/ + } + + minDistTeam = "all"; + } + + avoidWeight = GetDvarfloat( "scr_spawn_enemyavoidweight"); + if ( avoidWeight != 0 ) + { + nearbyEnemyOuterRange = GetDvarfloat( "scr_spawn_enemyavoiddist"); + nearbyEnemyOuterRangeSq = nearbyEnemyOuterRange * nearbyEnemyOuterRange; + nearbyEnemyPenalty = 1500 * avoidWeight; // typical base weights tend to peak around 1500 or so. this is large enough to upset that while only locally dominating it. + nearbyEnemyMinorPenalty = 800 * avoidWeight; // additional negative weight for distances up to 2 * nearbyEnemyOuterRange + + lastAttackerOrigin = (-99999,-99999,-99999); + lastDeathPos = (-99999,-99999,-99999); + if ( isAlive( self.lastAttacker ) ) + { + lastAttackerOrigin = self.lastAttacker.origin; + } + if ( isdefined( self.lastDeathPos ) ) + { + lastDeathPos = self.lastDeathPos; + } + + for ( i = 0; i < spawnpoints.size; i++ ) + { + // penalty for nearby enemies + mindist = spawnpoints[i].minEnemyDist[minDistTeam]; + if ( mindist < nearbyEnemyOuterRange*2 ) + { + penalty = nearbyEnemyMinorPenalty * (1 - mindist / (nearbyEnemyOuterRange*2)); + if ( mindist < nearbyEnemyOuterRange ) + { + penalty += nearbyEnemyPenalty * (1 - mindist / nearbyEnemyOuterRange); + } + if ( penalty > 0 ) + { + spawnpoints[i].weight -= penalty; + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + spawnpoints[i].spawnData[spawnpoints[i].spawnData.size] = "Nearest enemy at " + int(spawnpoints[i].minEnemyDist[minDistTeam]) + " units: -" + int(penalty); + } + #/ + } + } + + /* + // additional penalty for being near the guy who just killed me + distSq = distanceSquared( lastAttackerOrigin, spawnpoints[i].origin ); + if ( distSq < nearbyEnemyOuterRangeSq ) + { + penalty = nearbyEnemyPenalty * (1 - sqrt( distSq ) / nearbyEnemyOuterRange); + assert( penalty > 0 ); + spawnpoints[i].weight -= penalty; + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + spawnpoints[i].spawnData[spawnpoints[i].spawnData.size] = "Nearby killer at " + int(sqrt( distSq )) + " units: -" + int(penalty); + } + #/ + } + */ + + /* + // penalty for being near where i just died + distSq = distanceSquared( lastDeathPos, spawnpoints[i].origin ); + if ( distSq < nearbyEnemyOuterRangeSq ) + { + penalty = nearbyEnemyPenalty * (1 - sqrt( distSq ) / nearbyEnemyOuterRange); + assert( penalty > 0 ); + spawnpoints[i].weight -= penalty; + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + spawnpoints[i].spawnData[spawnpoints[i].spawnData.size] = "Died nearby at " + int(sqrt( distSq )) + " units: -" + int(penalty); + } + #/ + } + */ + } + } + + // DEBUG +} + +function avoid_spawn_reuse(spawnpoints, teambased) +{ + // DEBUG + if (GetDvarString( "scr_spawnpointnewlogic") == "0") + { + return; + } + + + time = getTime(); + + maxtime = 10*1000; + maxdistSq = 1024 * 1024; + + for (i = 0; i < spawnpoints.size; i++) + { + spawnpoint = spawnpoints[i]; + + if (!isdefined(spawnpoint.lastspawnedplayer) || !isdefined(spawnpoint.lastspawntime) || + !isalive(spawnpoint.lastspawnedplayer)) + { + continue; + } + + if (spawnpoint.lastspawnedplayer == self) + { + continue; + } + if (teambased && spawnpoint.lastspawnedplayer.team == self.team) + { + continue; + } + + timepassed = time - spawnpoint.lastspawntime; + if (timepassed < maxtime) + { + distSq = distanceSquared(spawnpoint.lastspawnedplayer.origin, spawnpoint.origin); + if (distSq < maxdistSq) + { + worsen = 5000 * (1 - distSq/maxdistSq) * (1 - timepassed/maxtime); + spawnpoint.weight -= worsen; + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + spawnpoint.spawnData[spawnpoint.spawnData.size] = "Was recently used: -" + worsen; + } + #/ + } + else + { + spawnpoint.lastspawnedplayer = undefined; // don't worry any more about this spawnpoint + } + } + else + { + spawnpoint.lastspawnedplayer = undefined; // don't worry any more about this spawnpoint + } + } + +} + +function avoid_same_spawn(spawnpoints) +{ + // DEBUG + if (GetDvarString( "scr_spawnpointnewlogic") == "0") + { + return; + } + + + if (!isdefined(self.lastspawnpoint)) + { + return; + } + + for (i = 0; i < spawnpoints.size; i++) + { + if (spawnpoints[i] == self.lastspawnpoint) + { + spawnpoints[i].weight -= 50000; // (half as bad as a likely spawn kill) + /# + if ( level.storeSpawnData || level.debugSpawning ) + { + spawnpoints[i].spawnData[spawnpoints[i].spawnData.size] = "Was last spawnpoint: -50000"; + } + #/ + break; + } + } + +} + + + +function get_random_intermission_point() +{ + spawnpoints = _get_spawnpoint_array( "mp_global_intermission" ); + if ( !spawnpoints.size ) + { + spawnpoints = _get_spawnpoint_array( "info_player_start" ); + } + assert( spawnpoints.size ); + spawnpoint = spawnlogic::get_spawnpoint_random(spawnpoints, undefined, true ); + + return spawnpoint; +} + +function move_spawn_point( targetname, start_point, new_point, new_angles ) +{ + if ( GetDvarInt( "spawnsystem_convert_spawns_to_structs" ) ) + { + spawn_points = struct::get_array( targetname, "targetname" ); + } + else + { + spawn_points = getEntArray( targetname, "classname" ); + } + + for ( i = 0; i < spawn_points.size; i++ ) + { + if ( distancesquared( spawn_points[i].origin, start_point ) < 1 ) + { + spawn_points[i].origin = new_point; + + if ( isdefined( new_angles ) ) + { + spawn_points[i].angles = new_angles; + } + return; + } + } +} diff --git a/mp/gametypes/_spectating.gsc b/mp/gametypes/_spectating.gsc new file mode 100644 index 0000000..9460e25 --- /dev/null +++ b/mp/gametypes/_spectating.gsc @@ -0,0 +1,206 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; + + + +#namespace spectating; + +function autoexec __init__sytem__() { system::register("spectating",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); + callback::on_spawned( &set_permissions ); + callback::on_joined_team( &set_permissions_for_machine ); + callback::on_joined_spectate( &set_permissions_for_machine ); +} + +function init() +{ + foreach( team in level.teams ) + { + level.spectateOverride[team] = spawnstruct(); + } +} + +function update_settings() +{ + level endon ( "game_ended" ); + + for ( index = 0; index < level.players.size; index++ ) + level.players[index] set_permissions(); +} + + +function get_splitscreen_team() +{ + for ( index = 0; index < level.players.size; index++ ) + { + if ( !isdefined(level.players[index]) ) + continue; + + if ( level.players[index] == self ) + continue; + + if ( !(self IsPlayerOnSameMachine( level.players[index] )) ) + continue; + + team = level.players[index].sessionteam; + + // going to assume first non-spectator + if ( team != "spectator" ) + return team; + } + + return self.sessionteam; +} + +function other_local_player_still_alive() +{ + for ( index = 0 ; index < level.players.size ; index++ ) + { + if ( !isdefined( level.players[index] ) ) + continue; + + if ( level.players[index] == self ) + continue; + + if ( !( self IsPlayerOnSameMachine( level.players[index] ) ) ) + continue; + + if ( IsAlive( level.players[index] ) ) + return true; + } + + return false; +} + +function allow_all_teams( allow ) +{ + foreach( team in level.teams ) + { + self allowSpectateTeam( team, allow ); + } +} + +function allow_all_teams_except( skip_team, allow ) +{ + foreach( team in level.teams ) + { + if ( team == skip_team ) + continue; + self allowSpectateTeam( team, allow ); + } +} + +function set_permissions() +{ + team = self.sessionteam; + + if ( team == "spectator" ) + { + // in online splitscreen we are only going to allow spectators to + // spectate the team of the other player on splitscreen + if ( self IsSplitScreen() && !level.splitscreen ) + { + team = get_splitscreen_team(); + } + + if ( team == "spectator" ) + { + self allow_all_teams( true ); + self allowSpectateTeam( "freelook", false ); + self allowSpectateTeam( "none", true ); + self allowSpectateTeam( "localplayers", true ); + return; + } + } + + spectateType = level.spectateType; + + switch( spectateType ) + { + case 0: // disabled + self allow_all_teams( false ); + self allowSpectateTeam( "freelook", false ); + self allowSpectateTeam( "none", true ); + self allowSpectateTeam( "localplayers", false ); + break; + case 3: // team only - strict splitscreen + // player can only spectate other local players + if ( self IsSplitScreen() && self other_local_player_still_alive() ) + { + self allow_all_teams( false ); + self allowSpectateTeam( "none", false ); + self allowSpectateTeam( "freelook", false ); + self allowSpectateTeam( "localplayers", true ); + break; + } + // fall through + case 1: // team only + if ( !level.teamBased ) + { + self allow_all_teams( true ); + self allowSpectateTeam( "none", true ); + self allowSpectateTeam( "freelook", false ); + self allowSpectateTeam( "localplayers", true ); + } + else if ( isdefined( team ) && isdefined( level.teams[team] ) ) + { + self allowSpectateTeam( team, true ); + self allow_all_teams_except( team , false ); + self allowSpectateTeam( "freelook", false ); + self allowSpectateTeam( "none", false ); + self allowSpectateTeam( "localplayers", true ); + } + else + { + self allow_all_teams( false ); + self allowSpectateTeam( "freelook", false ); + self allowSpectateTeam( "none", false ); + self allowSpectateTeam( "localplayers", true ); + } + break; + case 2: // free + self allow_all_teams( true ); + self allowSpectateTeam( "freelook", true ); + self allowSpectateTeam( "none", true ); + self allowSpectateTeam( "localplayers", true ); + break; + } + + if ( isdefined( team ) && isdefined( level.teams[team] ) ) + { + if ( isdefined(level.spectateOverride[team].allowFreeSpectate) ) + self allowSpectateTeam( "freelook", true ); + + if (isdefined(level.spectateOverride[team].allowEnemySpectate)) + self allow_all_teams_except( team, true ); + } +} + +function set_permissions_for_machine() +{ +// error + + self set_permissions(); + + if ( !self IsSplitScreen() ) + return; + + for ( index = 0; index < level.players.size; index++ ) + { + if ( !isdefined(level.players[index]) ) + continue; + + if ( level.players[index] == self ) + continue; + + if ( !(self IsPlayerOnSameMachine( level.players[index] )) ) + continue; + + level.players[index] set_permissions(); + } +} \ No newline at end of file diff --git a/mp/gametypes/_wager.gsc b/mp/gametypes/_wager.gsc new file mode 100644 index 0000000..d092b00 --- /dev/null +++ b/mp/gametypes/_wager.gsc @@ -0,0 +1,1044 @@ +#using scripts\codescripts\struct; +#using scripts\shared\callbacks_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\persistence_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\system_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_loadout; + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\teams\_teams; + +#precache( "string", "MP_HEADS_UP" ); +#precache( "string", "MP_U2_ONLINE" ); +#precache( "string", "MP_BONUS_ACQUIRED" ); + +#namespace wager; + +function autoexec __init__sytem__() { system::register("wager",&__init__,undefined,undefined); } + +function __init__() +{ + callback::on_start_gametype( &init ); +} + +function init() +{ + if ( GameModeIsMode( 3 ) ) + { + level.wagerMatch = 1; + + if( !isdefined( game["wager_pot"] ) ) + { + game["wager_pot"] = 0; + game["wager_initial_pot"] = 0; + } + + game["dialog"]["wm_u2_online"] = "boost_gen_02"; + game["dialog"]["wm_in_the_money"] = "boost_gen_06"; + game["dialog"]["wm_oot_money"] = "boost_gen_07"; + + level.powerupList = []; + + callback::on_disconnect( &on_disconnect ); + callback::on_spawned( &init_player ); + level thread help_game_end(); + } + else + { + level.wagerMatch = 0; + } + + level.takeLivesOnDeath = true; +} + +function init_player() +{ + self endon( "disconnect" ); + + if( !isdefined( self.pers["wager"] ) ) + { + self.pers["wager"] = true; + self.pers["wager_sideBetWinnings"] = 0; + self.pers["wager_sideBetLosses"] = 0; + } + + if ( ( isdefined( level.inTheMoneyOnRadar ) && level.inTheMoneyOnRadar ) || ( isdefined( level.firstPlaceOnRadar ) && level.firstPlaceOnRadar ) ) + { + self.pers["hasRadar"] = true; + self.hasSpyplane = true; + } + else + { + self.pers["hasRadar"] = false; + self.hasSpyplane = false; + } + + self thread deduct_player_ante(); +} + + +function on_disconnect() +{ + level endon ( "game_ended" ); + self endon ( "player_eliminated" ); + + level notify( "player_eliminated" ); +} + + +function deduct_player_ante() +{ + if( isdefined( self.pers["hasPaidWagerAnte"] ) ) + { + return; + } + + waittillframeend; + + codPoints = self rank::getCodPointsStat(); + wagerBet = GetDvarint( "scr_wagerBet" ); + if( wagerBet > codPoints ) + { + // This should technically never happen since we boot any player with insufficient + // funds but it is here to make sure you can never get negative CodPoints. + wagerBet = codPoints; + } + codPoints -= wagerBet; + self rank::setCodPointsStat( codPoints ); + if ( !self IsLocalToHost() ) + { + self increment_escrow_for_player( wagerBet ); + } + game["wager_pot"] += wagerBet; + game["wager_initial_pot"] += wagerBet; + + self.pers["hasPaidWagerAnte"] = true; + + self AddPlayerStat( "LIFETIME_BUYIN", wagerBet ); + + self add_recent_earnings_to_stat( 0 - wagerBet ); + + if( isdefined( level.onWagerPlayerAnte ) ) + { + [[level.onWagerPlayerAnte]]( self, wagerBet ); + } + + self thread persistence::upload_stats_soon(); +} + +function increment_escrow_for_player( amount ) +{ + if ( !isdefined( self ) || !IsPlayer( self ) ) + { + return; + } + + if ( !isdefined( game["escrows"] ) ) + { + game["escrows"] = []; + } + + playerXUID = self GetXUID(); + if ( !isdefined( playerXUID ) ) + { + return; + } + + //IncrementEscrow( playerXUID, amount ); + + escrowStruct = SpawnStruct(); + escrowStruct.xuid = playerXUID; + escrowStruct.amount = amount; + game["escrows"][game["escrows"].size] = escrowStruct; +} + +function clear_escrows() +{ + if ( !isdefined( game["escrows"] ) ) + { + return; + } + + escrows = game["escrows"]; + numEscrows = escrows.size; + for ( i = 0 ; i < numEscrows ; i++ ) + { + escrowStruct = escrows[i]; + //IncrementEscrow( escrowStruct.xuid, 0 - escrowStruct.amount ); + } + game["escrows"] = []; +} + +function add_recent_earnings_to_stat( recentEarnings ) +{ + currEarnings = self persistence::get_recent_stat( true, 0, "score" ); + self persistence::set_recent_stat( true, 0, "score", currEarnings + recentEarnings ); +} + +function prematch_period() +{ + if ( !level.wagerMatch ) + { + return; + } +} + +function finalize_round() +{ + if ( level.wagerMatch == 0 ) + { + return; + } + + determine_winnings(); + if( isdefined( level.onWagerFinalizeRound ) ) + { + [[level.onWagerFinalizeRound]](); + } +} + +function determine_winnings() +{ + shouldCalculateWinnings = !isdefined( level.dontCalcWagerWinnings ) || !level.dontCalcWagerWinnings; + if( !shouldCalculateWinnings ) + { + return; + } + + if( !level.teamBased ) + { + calculate_free_for_all_payouts(); + } + else + { + calculate_team_payouts(); + } +} + +function calculate_free_for_all_payouts() +{ + playerRankings = level.placement["all"]; + payoutPercentages = array( 0.5, 0.3, 0.2 ); + if( playerRankings.size == 2 ) + { + payoutPercentages = array( 0.7, 0.3 ); + } + else if( playerRankings.size == 1 ) + { + payoutPercentages = array( 1.0 ); + } + + set_winnings_on_players( level.players, 0 ); + + // If host quit and host migration failed give everyone except the host their money back + if ( isdefined( level.hostForcedEnd ) && level.hostForcedEnd ) + { + wagerBet = GetDvarint( "scr_wagerBet" ); + for ( i = 0 ; i < playerRankings.size ; i++ ) + { + if ( !playerRankings[i] IsLocalToHost() ) + playerRankings[i].wagerWinnings = wagerBet; + } + } + // If host is the only one left in the game, no one gets anything (other players will get their money back later) + else if ( level.players.size == 1 ) + { + game["escrows"] = undefined; + return; + } + else + { + currentPayoutPercentage = 0; + cumulativePayoutPercentage = payoutPercentages[0]; + playerGroup = []; + playerGroup[playerGroup.size] = playerRankings[0]; + for ( i = 1 ; i < playerRankings.size ; i++ ) + { + if ( playerRankings[i].pers["score"] < playerGroup[0].pers["score"] ) + { + set_winnings_on_players( playerGroup, int( game["wager_pot"] * cumulativePayoutPercentage / playerGroup.size ) ); + playerGroup = []; + cumulativePayoutPercentage = 0; + } + playerGroup[playerGroup.size] = playerRankings[i]; + currentPayoutPercentage++; + if ( isdefined( payoutPercentages[currentPayoutPercentage] ) ) + { + cumulativePayoutPercentage += payoutPercentages[currentPayoutPercentage]; + } + } + set_winnings_on_players( playerGroup, int( game["wager_pot"] * cumulativePayoutPercentage / playerGroup.size ) ); + } +} + +function calculate_places_based_on_score() +{ + // Put each player in a bucket for 1st, 2nd, and 3rd based on their score. + // eg. if 3 players are tied for first, they will all go in the 0 index bucket. + level.playerPlaces = array( [], [], [] ); + + playerRankings = level.placement["all"]; + placementScores = array( playerRankings[ 0 ].pers["score"], -1, -1 ); + currentPlace = 0; + for ( index = 0 ; index < playerRankings.size && currentPlace < placementScores.size ; index++ ) + { + player = playerRankings[index]; + + if( player.pers["score"] < placementScores[ currentPlace ] ) + { + currentPlace++; + if( currentPlace >= level.playerPlaces.size ) + { + break; + } + + placementScores[ currentPlace ] = player.pers["score"]; + } + + level.playerPlaces[ currentPlace ][ level.playerPlaces[ currentPlace ].size ] = player; + } +} + +function calculate_team_payouts() +{ + winner = globallogic::determineTeamWinnerByGameStat( "teamScores" ); + if( winner == "tie" ) + { + calculate_free_for_all_payouts(); + return; + } + + playersOnWinningTeam = []; + for ( index = 0 ; index < level.players.size ; index++ ) + { + player = level.players[index]; + + player.wagerWinnings = 0; + if( player.pers["team"] == winner ) + { + playersOnWinningTeam[ playersOnWinningTeam.size ] = player; + } + } + if( playersOnWinningTeam.size == 0 ) + { + // The winning team has all been disconnected so just refund everyones buyin. + set_winnings_on_players( level.players, GetDvarint( "scr_wagerBet" ) ); + return; + } + winningsSplit = int( game["wager_pot"] / playersOnWinningTeam.size ); + set_winnings_on_players( playersOnWinningTeam, winningsSplit ); +} + +function set_winnings_on_players( players, amount ) +{ + for ( index = 0 ; index < players.size ; index++ ) + { + players[index].wagerWinnings = amount; + } +} + +function finalize_game() +{ + level.wagerGameFinalized = true; + if ( level.wagermatch == 0 ) + { + return; + } + + determine_winnings(); + determine_top_earners(); + + players = level.players; + wait 0.5; + playerRankings = level.wagerTopEarners; + + for ( index = 0 ; index < players.size ; index++ ) + { + player = players[index]; + + if ( isdefined( player.pers["wager_sideBetWinnings"] ) ) + pay_out_winnings( player, player.wagerWinnings + player.pers["wager_sideBetWinnings"] ); + else + pay_out_winnings( player, player.wagerWinnings ); + + if ( player.wagerWinnings > 0 ) + { + globallogic_score::updateWinStats( player ); + } + } + clear_escrows(); +} + +function pay_out_winnings( player, winnings ) +{ + if( winnings == 0 ) + { + return; + } + + codPoints = player rank::getCodPointsStat(); + player rank::setCodPointsStat( codPoints + winnings ); + + player AddPlayerStat( "LIFETIME_EARNINGS", winnings ); + + player add_recent_earnings_to_stat( winnings ); +} + +function determine_top_earners() +{ + topWinnings = array( -1, -1, -1 ); + level.wagerTopEarners = []; + for ( index = 0 ; index < level.players.size ; index++ ) + { + player = level.players[ index ]; + if( !isdefined( player.wagerWinnings ) ) + { + player.wagerWinnings = 0; + } + + if( player.wagerWinnings > topWinnings[ 0 ] ) + { + topWinnings[ 2 ] = topWinnings[ 1 ]; + topWinnings[ 1 ] = topWinnings[ 0 ]; + topWinnings[ 0 ] = player.wagerWinnings; + + level.wagerTopEarners[ 2 ] = level.wagerTopEarners[ 1 ]; + level.wagerTopEarners[ 1 ] = level.wagerTopEarners[ 0 ]; + level.wagerTopEarners[ 0 ] = player; + } + else if( player.wagerWinnings > topWinnings[ 1 ] ) + { + topWinnings[ 2 ] = topWinnings[ 1 ]; + topWinnings[ 1 ] = player.wagerWinnings; + + level.wagerTopEarners[ 2 ] = level.wagerTopEarners[ 1 ]; + level.wagerTopEarners[ 1 ] = player; + } + else if( player.wagerWinnings > topWinnings[ 2 ] ) + { + topWinnings[ 2 ] = player.wagerWinnings; + + level.wagerTopEarners[ 2 ] = player; + } + } +} + +function post_round_side_bet() +{ + if ( isdefined( level.sidebet ) && level.sidebet ) + { + level notify( "side_bet_begin" ); + level waittill( "side_bet_end" ); + } +} + +function side_bet_timer() +{ + level endon ( "side_bet_end" ); + + secondsToWait = ( level.sideBetEndTime - GetTime() ) / 1000.0; + if ( secondsToWait < 0 ) + { + secondsToWait = 0; + } + wait( secondsToWait ); + level notify ( "side_bet_end" ); +} + +function side_bet_all_bets_placed() +{ + secondsLeft = ( level.sideBetEndTime - GetTime() ) / 1000.0; + if( secondsLeft <= 3.0 ) + { + return; + } + + level.sideBetEndTime = GetTime() + 3000; + + wait 3; + level notify ( "side_bet_end" ); +} + + +function setup_blank_random_player( takeWeapons, chooseRandomBody, weapon ) +{ + if ( !isdefined( chooseRandomBody ) || chooseRandomBody ) + { + if ( !isdefined( self.pers["wagerBodyAssigned"] ) ) + { + self assign_random_body(); + self.pers["wagerBodyAssigned"] = true; + } + + self teams::set_player_model( self.team, weapon ); + } + + self clearPerks(); + self.killstreak = []; + self.pers["killstreaks"] = []; + self.pers["killstreak_has_been_used"] = []; + self.pers["killstreak_unique_id"] = []; + if ( !isdefined( takeWeapons ) || takeWeapons ) + { + self takeAllWeapons(); + } + + if ( isdefined( self.pers["hasRadar"] ) && self.pers["hasRadar"] ) + { + self.hasSpyplane = true; + } + + if ( isdefined( self.powerups ) && isdefined( self.powerups.size ) ) + { + for ( i = 0 ; i < self.powerups.size ; i++ ) + { + self apply_powerup( self.powerups[i] ); + } + } + + self set_radar_visibility(); +} + +function assign_random_body() +{ + //self.cac_body_type = array::random( GetArrayKeys( level.cac_functions[ "set_body_model" ] ) ); + //self.cac_head_type = self _armor::get_default_head(); + //self.cac_hat_type = "none"; +} + +function queue_popup( message, points, subMessage, announcement ) +{ + self endon("disconnect"); + + size = self.wagerNotifyQueue.size; + self.wagerNotifyQueue[size] = spawnstruct(); + self.wagerNotifyQueue[size].message = message; + self.wagerNotifyQueue[size].points = points; + self.wagerNotifyQueue[size].subMessage = subMessage; + self.wagerNotifyQueue[size].announcement = announcement; + + self notify( "received award" ); +} + +function help_game_end() +{ + level endon( "game_ended" ); + + for ( ;; ) + { + level waittill( "player_eliminated" ); + + if ( !isdefined( level.numLives ) || !level.numLives ) + { + continue; + } + + {wait(.05);}; // in case multiple players are eliminated simultaneously + + players = level.players; + playersLeft = 0; + for ( i = 0 ; i < players.size ; i++ ) + { + if ( isdefined( players[i].pers["lives"] ) && ( players[i].pers["lives"] > 0 ) ) + { + playersLeft++; + } + } + + if ( playersLeft == 2 ) + { + for ( i = 0 ; i < players.size ; i++ ) + { + players[i] queue_popup( &"MP_HEADS_UP", 0, &"MP_U2_ONLINE", "wm_u2_online" ); + players[i].pers["hasRadar"] = true; + players[i].hasSpyplane = true; + + if ( level.teambased ) + { + assert( isdefined( players[i].team ) ); + level.activePlayerUAVs[players[i].team]++; + } + else + { + level.activePlayerUAVs[players[i] getEntityNumber()]++; + } + + level.activePlayerUAVs[players[i] getEntityNumber()]++; + } + } + } +} + +function set_radar_visibility() +{ + prevScorePlace = self.prevScorePlace; + if ( !isdefined( prevScorePlace ) ) + { + prevScorePlace = 1; + } + + if ( isdefined( level.inTheMoneyOnRadar ) && level.inTheMoneyOnRadar ) + { + if ( prevScorePlace <= 3 && isdefined( self.score ) && ( self.score > 0 ) ) + { + self unsetPerk( "specialty_gpsjammer" ); + } + else + { + self setPerk( "specialty_gpsjammer" ); + } + } + else if ( isdefined( level.firstPlaceOnRadar ) && level.firstPlaceOnRadar ) + { + if ( prevScorePlace == 1 && isdefined( self.score ) && ( self.score > 0 ) ) + { + self unsetPerk( "specialty_gpsjammer" ); + } + else + { + self setPerk( "specialty_gpsjammer" ); + } + } +} + +function player_scored() +{ + self notify( "wager_player_scored" ); + self endon( "wager_player_scored" ); + + {wait(.05);}; // Let other simultaneous sounds play first + + globallogic::updatePlacement(); + for ( i = 0 ; i < level.placement["all"].size ; i++ ) + { + prevScorePlace = level.placement["all"][i].prevScorePlace; + if ( !isdefined( prevScorePlace ) ) + { + prevScorePlace = 1; + } + currentScorePlace = i + 1; + for ( j = i - 1 ; j >= 0 ; j-- ) + { + if ( level.placement["all"][i].score == level.placement["all"][j].score ) + { + currentScorePlace--; + } + } + wasInTheMoney = ( prevScorePlace <= 3 ); + isInTheMoney = ( currentScorePlace <= 3 ); + + if ( !wasInTheMoney && isInTheMoney ) + { + level.placement["all"][i] announcer( "wm_in_the_money" ); + } + else if ( wasInTheMoney && !isInTheMoney ) + { + level.placement["all"][i] announcer( "wm_oot_money" ); + } + level.placement["all"][i].prevScorePlace = currentScorePlace; + level.placement["all"][i] set_radar_visibility(); + } +} + +function announcer( dialog, group ) +{ + self globallogic_audio::leader_dialog_on_player( dialog, group ); +} + +function create_powerup( name, type, displayName, iconMaterial ) +{ + powerup = spawnstruct(); + powerup.name = []; + powerup.name[0] = name; + powerup.type = type; + powerup.displayName = displayName; + powerup.iconMaterial = iconMaterial; + + return powerup; +} + +function add_powerup( name, type, displayName, iconMaterial ) +{ + if ( !isdefined( level.powerupList ) ) + { + level.powerupList = []; + } + + for ( i = 0 ; i < level.powerupList.size ; i++ ) + { + if ( level.powerupList[i].displayName == displayName ) + { + level.powerupList[i].name[level.powerupList[i].name.size] = name; + return; + } + } + + powerup = create_powerup( name, type, displayName, iconMaterial ); + + level.powerupList[level.powerupList.size] = powerup; +} + +function copy_powerup( powerup ) +{ + return create_powerup( powerup.name[0], powerup.type, powerup.displayName, powerup.iconMaterial ); +} + +function apply_powerup( powerup ) +{ + weapon = level.weaponNone; + switch ( powerup.type ) + { + case "primary": + case "secondary": + case "equipment": + case "primary_grenade": + case "secondary_grenade": + weapon = GetWeapon( powerup.name[0] ); + break; + } + + switch ( powerup.type ) + { + case "primary": + self giveWeapon( weapon ); + self switchToWeapon( weapon ); + break; + + case "secondary": + self giveWeapon( weapon ); + break; + + case "equipment": + self GiveWeapon( weapon ); + self loadout::setWeaponAmmoOverall( weapon, 1 ); + self SetActionSlot( 1, "weapon", weapon ); + break; + + case "primary_grenade": + self setOffhandPrimaryClass( weapon ); + self giveWeapon( weapon ); + self setWeaponAmmoClip( weapon, 2 ); + break; + + case "secondary_grenade": + self setOffhandSecondaryClass( weapon ); + self giveWeapon( weapon ); + self setWeaponAmmoClip( weapon, 2 ); + break; + + case "perk": + for ( i = 0 ; i < powerup.name.size ; i++ ) + self setPerk( powerup.name[i] ); + break; + + case "killstreak": + self killstreaks::give( powerup.name[0] ); + break; + + case "score_multiplier": + self.scoreMultiplier = powerup.name[0]; + break; + } +} + +function give_powerup( powerup, doAnimation ) +{ + if ( !isdefined( self.powerups ) ) + { + self.powerups = []; + } + + powerupIndex = self.powerups.size; + self.powerups[powerupIndex] = copy_powerup( powerup ); + for ( i = 0 ; i < powerup.name.size ; i++ ) + { + self.powerups[powerupIndex].name[self.powerups[powerupIndex].name.size] = powerup.name[i]; + } + + self apply_powerup( self.powerups[powerupIndex] ); + + self thread show_powerup_message( powerupIndex, doAnimation ); +} + +function pulse_powerup_icon( powerupIndex ) +{ + if ( !isdefined( self ) || !isdefined( self.powerups ) || !isdefined( self.powerups[powerupIndex] ) || !isdefined( self.powerups[powerupIndex].hud_elem_icon ) ) + { + return; + } + + self endon( "disconnect" ); + self endon( "delete" ); + self endon( "clearing_powerups" ); + + pulsePercent = 1.5; + pulseTime = 0.5; + + hud_elem = self.powerups[powerupIndex].hud_elem_icon; + if ( isdefined( hud_elem.animating ) && hud_elem.animating ) + { + return; + } + + origX = hud_elem.x; + origY = hud_elem.y; + origWidth = hud_elem.width; + origHeight = hud_elem.height; + bigWidth = origWidth * pulsePercent; + bigHeight = origHeight * pulsePercent; + xOffset = ( bigWidth - origWidth ) / 2; + yOffset = ( bigHeight - origHeight ) / 2; + hud_elem ScaleOverTime( 0.05, int( bigWidth ), int( bigHeight ) ); + hud_elem MoveOverTime( 0.05 ); + hud_elem.x = origX - xOffset; + hud_elem.y = origY - yOffset; + + {wait(.05);}; + + hud_elem ScaleOverTime( pulseTime, origWidth, origHeight ); + hud_elem MoveOverTime( pulseTime ); + hud_elem.x = origX; + hud_elem.y = origY; +} + +function show_powerup_message( powerupIndex, doAnimation ) +{ + self endon( "disconnect" ); + self endon( "delete" ); + self endon( "clearing_powerups" ); + + if ( !isdefined( doAnimation ) ) + { + doAnimation = true; + } + + wasInPrematch = level.inPrematchPeriod; + + powerupStartY = 320; + powerupSpacing = 40; + if ( self IsSplitscreen() ) + { + powerupStartY = 120; + powerupSpacing = 35; + } + + // Text elem + if ( isdefined( self.powerups[powerupIndex].hud_elem ) ) + { + self.powerups[powerupIndex].hud_elem destroy(); + } + self.powerups[powerupIndex].hud_elem = NewClientHudElem( self ); + self.powerups[powerupIndex].hud_elem.fontScale = 1.5; + + self.powerups[powerupIndex].hud_elem.x = -125; + self.powerups[powerupIndex].hud_elem.y = powerupStartY - powerupSpacing * powerupIndex; + self.powerups[powerupIndex].hud_elem.alignX = "left"; + self.powerups[powerupIndex].hud_elem.alignY = "middle"; + self.powerups[powerupIndex].hud_elem.horzAlign = "user_right"; + self.powerups[powerupIndex].hud_elem.vertAlign = "user_top"; + self.powerups[powerupIndex].hud_elem.color = ( 1, 1, 1 ); + + self.powerups[powerupIndex].hud_elem.foreground = true; + self.powerups[powerupIndex].hud_elem.hidewhendead = false; + self.powerups[powerupIndex].hud_elem.hidewheninmenu = true; + self.powerups[powerupIndex].hud_elem.hidewheninkillcam = true; + self.powerups[powerupIndex].hud_elem.archived = false; + self.powerups[powerupIndex].hud_elem.alpha = 0.0; + self.powerups[powerupIndex].hud_elem SetText( self.powerups[powerupIndex].displayName ); + + // Icon elem + + bigIconSize = 40; + iconSize = 32; + if ( isdefined( self.powerups[powerupIndex].hud_elem_icon ) ) + { + self.powerups[powerupIndex].hud_elem_icon destroy(); + } + if ( doAnimation ) + { + self.powerups[powerupIndex].hud_elem_icon = self hud::createIcon( self.powerups[powerupIndex].iconMaterial, bigIconSize, bigIconSize ); + self.powerups[powerupIndex].hud_elem_icon.animating = true; + } + else + { + self.powerups[powerupIndex].hud_elem_icon = self hud::createIcon( self.powerups[powerupIndex].iconMaterial, iconSize, iconSize ); + } + + self.powerups[powerupIndex].hud_elem_icon.x = self.powerups[powerupIndex].hud_elem.x - 5 - iconSize / 2 - bigIconSize / 2; + self.powerups[powerupIndex].hud_elem_icon.y = powerupStartY - powerupSpacing * powerupIndex - bigIconSize / 2; + self.powerups[powerupIndex].hud_elem_icon.horzAlign = "user_right"; + self.powerups[powerupIndex].hud_elem_icon.vertAlign = "user_top"; + self.powerups[powerupIndex].hud_elem_icon.color = ( 1, 1, 1 ); + + self.powerups[powerupIndex].hud_elem_icon.foreground = true; + self.powerups[powerupIndex].hud_elem_icon.hidewhendead = false; + self.powerups[powerupIndex].hud_elem_icon.hidewheninmenu = true; + self.powerups[powerupIndex].hud_elem_icon.hidewheninkillcam = true; + self.powerups[powerupIndex].hud_elem_icon.archived = false; + self.powerups[powerupIndex].hud_elem_icon.alpha = 1.0; + + if ( !wasInPrematch && doAnimation ) + { + self thread queue_popup( self.powerups[powerupIndex].displayName, 0, &"MP_BONUS_ACQUIRED" ); + } + + pulseTime = 0.5; + if ( doAnimation ) + { + self.powerups[powerupIndex].hud_elem FadeOverTime( pulseTime ); + self.powerups[powerupIndex].hud_elem_icon ScaleOverTime( pulseTime, iconSize, iconSize ); + self.powerups[powerupIndex].hud_elem_icon.width = iconSize; + self.powerups[powerupIndex].hud_elem_icon.height = iconSize; + self.powerups[powerupIndex].hud_elem_icon MoveOverTime( pulseTime ); + } + + self.powerups[powerupIndex].hud_elem.alpha = 1.0; + self.powerups[powerupIndex].hud_elem_icon.x = self.powerups[powerupIndex].hud_elem.x - 5 - iconSize; + self.powerups[powerupIndex].hud_elem_icon.y = powerupStartY - powerupSpacing * powerupIndex - iconSize / 2; + + if ( doAnimation ) + { + wait( pulseTime ); + } + + if ( level.inPrematchPeriod ) + { + level waittill( "prematch_over" ); + } + else if ( doAnimation ) + { + wait ( pulseTime ); + } + + if ( wasInPrematch && doAnimation ) + { + self thread queue_popup( self.powerups[powerupIndex].displayName, 0, &"MP_BONUS_ACQUIRED" ); + } + + wait ( 1.5 ); + for ( i = 0 ; i <= powerupIndex ; i++ ) + { + self.powerups[i].hud_elem FadeOverTime( 0.25 ); + self.powerups[i].hud_elem.alpha = 0; + } + + wait ( 0.25 ); + for ( i = 0 ; i <= powerupIndex ; i++ ) + { + self.powerups[i].hud_elem_icon MoveOverTime( 0.25 ); + self.powerups[i].hud_elem_icon.x = 0 - iconSize; + self.powerups[i].hud_elem_icon.horzAlign = "user_right"; + } + + self.powerups[powerupIndex].hud_elem_icon.animating = false; +} + +function clear_powerups() +{ + self notify( "clearing_powerups" ); + if ( isdefined( self.powerups ) && isdefined( self.powerups.size ) ) + { + for ( i = 0 ; i < self.powerups.size ; i++ ) + { + if ( isdefined( self.powerups[i].hud_elem ) ) + { + self.powerups[i].hud_elem destroy(); + } + if ( isdefined( self.powerups[i].hud_elem_icon ) ) + { + self.powerups[i].hud_elem_icon destroy(); + } + } + } + self.powerups = []; +} + +function track_weapon_usage( name, incValue, statName ) +{ + if ( !isdefined( self.wagerWeaponUsage ) ) + { + self.wagerWeaponUsage = []; + } + + if ( !isdefined( self.wagerWeaponUsage[name] ) ) + { + self.wagerWeaponUsage[name] = []; + } + + if ( !isdefined( self.wagerWeaponUsage[name][statName] ) ) + { + self.wagerWeaponUsage[name][statName] = 0; + } + + self.wagerWeaponUsage[name][statName] += incValue; +} + +function get_highest_weapon_usage( statName ) +{ + if ( !isdefined( self.wagerWeaponUsage ) ) + { + return undefined; + } + + bestWeapon = undefined; + highestValue = 0; + + wagerWeaponsUsed = GetArrayKeys( self.wagerWeaponUsage ); + for ( i = 0 ; i < wagerWeaponsUsed.size ; i++ ) + { + weaponStats = self.wagerWeaponUsage[wagerWeaponsUsed[i]]; + if ( !isdefined( weaponStats[statName] ) || !GetBaseWeaponItemIndex( [[ level.get_base_weapon_param ]]( wagerWeaponsUsed[i] ) ) ) + { + continue; + } + else if ( !isdefined( bestWeapon ) || ( weaponStats[statName] > highestValue ) ) + { + bestWeapon = wagerWeaponsUsed[i]; + highestValue = weaponStats[statName]; + } + } + + return bestWeapon; +} + +function set_after_action_report_stats() +{ + topWeapon = self get_highest_weapon_usage( "kills" ); + topKills = 0; + if ( isdefined( topWeapon ) ) + { + topKills = self.wagerWeaponusage[topWeapon]["kills"]; + } + else + { + topWeapon = self get_highest_weapon_usage( "timeUsed" ); + } + + if ( !isdefined( topWeapon ) ) + { + topWeapon = ""; + } + + self persistence::set_after_action_report_stat( "topWeaponItemIndex", GetBaseWeaponItemIndex( [[ level.get_base_weapon_param ]]( topWeapon ) ) ); + self persistence::set_after_action_report_stat( "topWeaponKills", topKills ); + + if ( isdefined( level.onWagerAwards ) ) + { + self [[level.onWagerAwards]](); + } + else + { + for ( i = 0 ; i < 3 ; i++ ) + { + self persistence::set_after_action_report_stat( "wagerAwards", 0, i ); + } + } +} \ No newline at end of file diff --git a/mp/gametypes/_weapon_utils.gsc b/mp/gametypes/_weapon_utils.gsc new file mode 100644 index 0000000..2010d89 --- /dev/null +++ b/mp/gametypes/_weapon_utils.gsc @@ -0,0 +1,11 @@ +#using scripts\codescripts\struct; + + + +#namespace weapon_utils; + +function getBaseWeaponParam( weapon ) +{ + // figure out the weapon to pass as a parameter to GetBaseWeaponItemIndex() + return ( ( weapon.rootweapon.altweapon != level.weaponNone ) ? weapon.rootweapon.altweapon.rootweapon : weapon.rootweapon ); +} diff --git a/mp/gametypes/_weaponobjects.csc b/mp/gametypes/_weaponobjects.csc new file mode 100644 index 0000000..a9c44f5 --- /dev/null +++ b/mp/gametypes/_weaponobjects.csc @@ -0,0 +1,45 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weaponobjects; + + + + +#namespace weaponobjects; + +function autoexec __init__sytem__() { system::register("weaponobjects",&__init__,undefined,undefined); } + +function __init__() +{ + weaponobjects::init_shared(); + + level setupScriptMoverCompassIcons(); + level setupMissileCompassIcons(); +} + +function setupScriptMoverCompassIcons() +{ + if(!isdefined(level.scriptMoverCompassIcons))level.scriptMoverCompassIcons=[]; + + level.scriptMoverCompassIcons["wpn_t7_turret_emp_core"] = "compass_empcore_white"; + level.scriptMoverCompassIcons["t6_wpn_turret_ads_world"] = "compass_guardian_white"; + level.scriptMoverCompassIcons["veh_t7_drone_uav_enemy_vista"] = "compass_uav"; + level.scriptMoverCompassIcons["veh_t7_mil_vtol_fighter_mp"] = "compass_lightningstrike"; + level.scriptMoverCompassIcons["veh_t7_drone_rolling_thunder"] = "compass_lodestar"; + level.scriptMoverCompassIcons["veh_t7_drone_srv_blimp"] = "t7_hud_minimap_hatr"; +} + +function setupMissileCompassIcons() +{ + if(!isdefined(level.missileCompassIcons))level.missileCompassIcons=[]; + + if ( isdefined( getweapon("drone_strike") ) ) + { + level.missileCompassIcons[getweapon("drone_strike")] = "compass_lodestar"; + } +} \ No newline at end of file diff --git a/mp/gametypes/_weaponobjects.gsc b/mp/gametypes/_weaponobjects.gsc new file mode 100644 index 0000000..0520a46 --- /dev/null +++ b/mp/gametypes/_weaponobjects.gsc @@ -0,0 +1,37 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons_shared; +#using scripts\shared\weapons\_weaponobjects; + + + + +#using scripts\mp\_util; + +#namespace weaponobjects; + +function autoexec __init__sytem__() { system::register("weaponobjects",&__init__,undefined,undefined); } + +function __init__() +{ + weaponobjects::init_shared(); + + callback::on_start_gametype( &start_gametype ); +} + + +function start_gametype() +{ + callback::on_connect( &on_player_connect ); + callback::on_spawned( &on_player_spawned ); +} + +function on_player_spawned() // self == player +{ + self createSpikeLauncherWatcher( "hero_spike_launcher" ); +} \ No newline at end of file diff --git a/mp/gametypes/_weapons.gsc b/mp/gametypes/_weapons.gsc new file mode 100644 index 0000000..a5576d1 --- /dev/null +++ b/mp/gametypes/_weapons.gsc @@ -0,0 +1,219 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons_shared; +#using scripts\shared\weapons\_weapons; + + + +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_shellshock; +#using scripts\mp\gametypes\_weapon_utils; +#using scripts\mp\gametypes\_weaponobjects; + +#using scripts\mp\_challenges; +#using scripts\mp\_scoreevents; +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_dogs; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_killstreak_weapons; +#using scripts\mp\killstreaks\_supplydrop; + +#namespace weapons; + +function autoexec __init__sytem__() { system::register("weapons",&__init__,undefined,undefined); } + +function __init__() +{ + weapons::init_shared(); +} + + +function bestweapon_kill( weapon ) +{ +// self endon("disconnect"); +// +// if ( !weapon.isPrimary ) +// return; +// +// if ( weapon == level.weaponNone ) +// return; +// +// options = self GetWeaponOptions( weapon ); +// acvi = self GetWeaponAcvi( weapon ); +// +// if ( !IsDefined( acvi ) ) +// acvi = 0; +// +// wait( 0.33 ); // some random time +// +// weapon_key = bestweapon_find(weapon, options, acvi); +// +// if ( !IsDefined( weapon_key ) ) +// { +// weapon_key = bestweapon_init( weapon, options, acvi ); +// } +// +// self.pers["bestWeapon"][weapon.name][weapon_key]["kill_count"]++; +} + +function bestweapon_spawn( weapon, options, acvi ) +{ +// self endon("disconnect"); +// +// if ( weapon == level.weaponNone ) +// return; +// +// weapon_key = bestweapon_find(weapon, options, acvi); +// +// if ( !IsDefined( weapon_key ) ) +// { +// weapon_key = bestweapon_init( weapon, options, acvi ); +// } +// +// self.pers["bestWeapon"][weapon.name][weapon_key]["spawned_with"]++; +} + + +function bestweapon_init( weapon, options, acvi ) +{ + weapon_data = []; + weapon_data["weapon"] = weapon; + weapon_data["options"] = options; + weapon_data["acvi"] = acvi; + weapon_data["kill_count"] = 0; + weapon_data["spawned_with"] = 0; + key = self.pers["bestWeapon"][weapon.name].size; + self.pers["bestWeapon"][weapon.name][key] = weapon_data; + + return key; +} + +function bestweapon_find( weapon, options, acvi ) +{ + if ( !isDefined( self.pers["bestWeapon"] ) ) + { + self.pers["bestWeapon"] = []; + } + if ( !IsDefined( self.pers["bestWeapon"][weapon.name] ) ) + { + self.pers["bestWeapon"][weapon.name] = []; + } + + name = weapon.name; + size = self.pers["bestWeapon"][name].size; + for( index = 0; index < size; index++ ) + { + if ( self.pers["bestWeapon"][name][index]["weapon"] == weapon + && self.pers["bestWeapon"][name][index]["options"] == options && + self.pers["bestWeapon"][name][index]["acvi"] == acvi ) + { + return index; + } + } + + return undefined; +} + + +function bestweapon_get() +{ + most_kills = 0; + most_spawns = 0; + + if ( !IsDefined( self.pers["bestWeapon"] ) ) + { + return; + } + + best_key = 0; + best_index = 0; + weapon_keys = GetArrayKeys( self.pers["bestWeapon"] ); + for( key_index = 0; key_index < weapon_keys.size; key_index++ ) + { + key = weapon_keys[key_index]; + size = self.pers["bestWeapon"][key].size; + for( index = 0; index < size; index++ ) + { + kill_count = self.pers["bestWeapon"][key][index]["kill_count"]; + spawned_with = self.pers["bestWeapon"][key][index]["spawned_with"]; + + if ( kill_count > most_kills ) + { + best_index = index; + best_key = key; + most_kills = kill_count; + most_spawns = spawned_with; + } + else if ( kill_count == most_kills && spawned_with > most_spawns ) + { + best_index = index; + best_key = key; + most_kills = kill_count; + most_spawns = spawned_with; + } + } + } + + return self.pers["bestWeapon"][best_key][best_index]; +} + +function showcaseweapon_get() +{ + showcaseWeaponData = self GetPlayerShowcaseWeapon(); + + if ( !isdefined( showcaseWeaponData ) ) + { + return undefined; + } + + showcase_weapon = []; + + showcase_weapon["weapon"] = showcaseWeaponData.weapon; + + attachmentNames = []; + attachmentIndices = []; + tokenizedAttachmentInfo = strtok( showcaseWeaponData.attachmentInfo, "," ); + for ( index = 0; index + 1 < tokenizedAttachmentInfo.size; index += 2 ) + { + attachmentNames[ attachmentNames.size ] = tokenizedAttachmentInfo[ index ]; + attachmentIndices[ attachmentIndices.size ] = int( tokenizedAttachmentInfo[ index + 1 ] ); + } + for ( index = tokenizedAttachmentInfo.size; index + 1 < 16; index += 2 ) + { + attachmentNames[ attachmentNames.size ] = "none"; + attachmentIndices[ attachmentIndices.size ] = 0; + } + showcase_weapon["acvi"] = GetAttachmentCosmeticVariantIndexes( showcaseWeaponData.weapon, + attachmentNames[ 0 ], attachmentIndices[ 0 ], + attachmentNames[ 1 ], attachmentIndices[ 1 ], + attachmentNames[ 2 ], attachmentIndices[ 2 ], + attachmentNames[ 3 ], attachmentIndices[ 3 ], + attachmentNames[ 4 ], attachmentIndices[ 4 ], + attachmentNames[ 5 ], attachmentIndices[ 5 ], + attachmentNames[ 6 ], attachmentIndices[ 6 ], + attachmentNames[ 7 ], attachmentIndices[ 7 ] ); + + camoIndex = 0; + paintjobSlot = 15; + paintjobIndex = 15; + showPaintshop = false; + tokenizedWeaponRenderOptions = strtok( showcaseWeaponData.weaponRenderOptions, "," ); + if ( tokenizedWeaponRenderOptions.size > 2 ) + { + camoIndex = int( tokenizedWeaponRenderOptions[ 0 ] ); + paintjobSlot = int( tokenizedWeaponRenderOptions[ 1 ] ); + paintjobIndex = int( tokenizedWeaponRenderOptions[ 2 ] ); + showPaintshop = paintjobSlot != 15 && paintjobIndex != 15; + } + + showcase_weapon["options"] = self CalcWeaponOptions( camoIndex, 0, 0, false, false, showPaintshop, true ); + + return showcase_weapon; +} \ No newline at end of file diff --git a/mp/gametypes/ball.csc b/mp/gametypes/ball.csc new file mode 100644 index 0000000..36718ac --- /dev/null +++ b/mp/gametypes/ball.csc @@ -0,0 +1,323 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\duplicaterender_mgr; +#using scripts\shared\util_shared; + +#using scripts\mp\_shoutcaster; + + + + + + + + + + +#precache( "client_fx", "ui/fx_uplink_goal_marker" ); +#precache( "client_fx", "ui/fx_uplink_goal_marker_flash" ); +#precache( "client_fx", "ui/fx_uplink_goal_marker_white" ); +#precache( "client_fx", "ui/fx_uplink_goal_marker_white_flash" ); + +function main() +{ + clientfield::register( "allplayers", "ballcarrier", 1, 1, "int", &player_ballcarrier_changed, !true, true ); + clientfield::register( "allplayers", "passoption", 1, 1, "int", &player_passoption_changed, !true, !true ); + clientfield::register( "world", "ball_away" , 1, 1, "int", &world_ball_away_changed, !true, true ); + clientfield::register( "world", "ball_score_allies" , 1, 1, "int", &world_ball_score_allies, !true, true ); + clientfield::register( "world", "ball_score_axis" , 1, 1, "int", &world_ball_score_axis, !true, true ); + + callback::on_localclient_connect( &on_localclient_connect ); + callback::on_spawned( &on_player_spawned ); + + if ( !GetDvarInt("tu11_programaticallyColoredGameFX") ) + { + level.effect_scriptbundles = []; + level.effect_scriptbundles["goal"] = struct::get_script_bundle( "teamcolorfx", "teamcolorfx_uplink_goal" ); + level.effect_scriptbundles["goal_score"] = struct::get_script_bundle( "teamcolorfx", "teamcolorfx_uplink_goal_score" ); + } +} + +function on_localclient_connect( localClientNum ) +{ + objective_ids = []; + + while ( !isdefined( objective_ids["allies"] ) ) + { + objective_ids["allies"] = ServerObjective_GetObjective( localClientNum, "ball_goal_allies" ); + objective_ids["axis"] = ServerObjective_GetObjective( localClientNum, "ball_goal_axis" ); + wait(0.05); + } + + foreach( key, objective in objective_ids ) + { + level.goals[key] = SpawnStruct(); + level.goals[key].objectiveId = objective; + setup_goal( localClientNum, level.goals[key] ); + } + + setup_fx( localClientNum ); +} + +function on_player_spawned( localClientNum ) +{ + players = GetPlayers( localclientnum ); + foreach( player in players ) + { + if( player util::IsEnemyPlayer( self ) ) + { + player duplicate_render::update_dr_flag( localClientNum, "ballcarrier", 0 ); + } + } +} + +function setup_goal( localClientNum, goal ) +{ + goal.origin = ServerObjective_GetObjectiveOrigin( localClientNum, goal.objectiveId ); + goal_entity = ServerObjective_GetObjectiveEntity( localClientNum, goal.objectiveId ); + + if ( isdefined(goal_entity) ) + { + goal.origin = goal_entity.origin; + } + + goal.team = ServerObjective_GetObjectiveTeam( localClientNum, goal.objectiveId ); +} + +function setup_goal_fx( localClientNum, goal, effects ) +{ + if ( isdefined( goal.base_fx ) ) + { + StopFx( localClientNum, goal.base_fx ); + } + + goal.base_fx = PlayFx(localClientNum, effects[goal.team], goal.origin ); + SetFxTeam( localClientNum, goal.base_fx, goal.team ); +} + +function setup_fx( localClientNum ) +{ + effects = []; + + if ( shoutcaster::is_shoutcaster_using_team_identity(localClientNum) ) + { + if ( GetDvarInt("tu11_programaticallyColoredGameFX") ) + { + effects["allies"] = "ui/fx_uplink_goal_marker_white"; + effects["axis"] = "ui/fx_uplink_goal_marker_white"; + } + else + { + effects = shoutcaster::get_color_fx( localClientNum, level.effect_scriptbundles["goal"] ); + } + } + else + { + effects["allies"] = "ui/fx_uplink_goal_marker"; + effects["axis"] = "ui/fx_uplink_goal_marker"; + } + + foreach( goal in level.goals) + { + thread setup_goal_fx(localClientNum, goal, effects ); + thread resetOnDemoJump( localClientNum, goal, effects ); + } + + thread watch_for_team_change( localClientNum ); +} + +function play_score_fx( localClientNum, goal) +{ + effects = []; + + if ( shoutcaster::is_shoutcaster_using_team_identity(localClientNum) ) + { + if ( GetDvarInt("tu11_programaticallyColoredGameFX") ) + { + effects["allies"] = "ui/fx_uplink_goal_marker_white_flash"; + effects["axis"] = "ui/fx_uplink_goal_marker_white_flash"; + } + else + { + effects = shoutcaster::get_color_fx( localClientNum, level.effect_scriptbundles["goal_score"] ); + } + } + else + { + effects["allies"] = "ui/fx_uplink_goal_marker_flash"; + effects["axis"] = "ui/fx_uplink_goal_marker_flash"; + } + + fx_handle = PlayFx(localClientNum, effects[goal.team], goal.origin ); + SetFxTeam( localClientNum, fx_handle, goal.team ); +} + +function play_goal_score_fx( localClientNum, team, oldVal, newVal, bInitialSnap, bWasTimeJump ) +{ + if( ( newVal != oldVal ) && !bInitialSnap && !bWasTimeJump ) + { + play_score_fx( localClientNum, level.goals[team] ); + } +} + +function world_ball_score_allies( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + play_goal_score_fx( localClientNum, "allies", oldVal, newVal, bInitialSnap, bWasTimeJump ); +} + +function world_ball_score_axis( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + play_goal_score_fx( localClientNum, "axis", oldVal, newVal, bInitialSnap, bWasTimeJump ); +} + +function player_ballcarrier_changed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + localplayer = getlocalplayer( localClientNum ); + + if( localplayer == self ) + { + if( newVal ) + { + self._hasBall = true; + } + else + { + self._hasBall = false; + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.passOption" ), 0 ); + } + } + + if( ( localplayer != self ) && self isFriendly( localClientNum ) ) + { + self set_player_ball_carrier_dr( localClientNum, newVal ); + } + else + { + self set_player_ball_carrier_dr( localClientNum, false ); + } + +// if ( newVal == 1 ) +// { +// self set_hud(localClientNum); +// } +// else +// { +// self clear_hud( localClientNum ); +// } + + if ( isdefined( level.ball_carrier ) && level.ball_carrier != self ) + return; + + level notify( "watch_for_death" ); + + if ( newVal == 1 ) + { + // we need to track when the player dies because if they watch killcam we will not get flag updates + self thread watch_for_death( localClientNum ); + } +} + +function set_hud( localClientNum ) +{ + level.ball_carrier = self; + + if ( shoutcaster::is_shoutcaster( localClientNum ) ) + { + friendly = self shoutcaster::is_friendly( localclientnum ); + } + else + { + friendly = self isFriendly( localClientNum ); + } + + if( isdefined( self.name ) ) + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballStatusText" ), self.name ); + else + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballStatusText" ), "" ); + + if( isdefined( friendly ) ) + { + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballHeldByFriendly" ), friendly ); + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballHeldByEnemy" ), !friendly ); + } + else + { + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballHeldByFriendly" ), 0 ); + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballHeldByEnemy" ), 0 ); + } +} + +function clear_hud( localClientNum ) +{ + level.ball_carrier = undefined; + + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballHeldByEnemy" ), 0 ); + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballHeldByFriendly" ), 0 ); + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballStatusText" ), &"MPUI_BALL_AWAY" ); +} + +function watch_for_death( localClientNum ) +{ + level endon( "watch_for_death" ); + + self waittill( "entityshutdown" ); + + //self clear_hud(localClientNum); +} + +function player_passoption_changed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + localplayer = getlocalplayer( localClientNum ); + + if( ( localplayer != self ) && self isFriendly( localClientNum ) ) + { + if( ( isdefined( localplayer._hasBall ) && localplayer._hasBall ) ) + { + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.passOption" ), newVal ); + } + } +} + +function world_ball_away_changed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballAway" ), newVal ); + + //if( newVal == 0 || bInitialSnap ) + //{ + //SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballHeldByEnemy" ), 0 ); + //SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballHeldByFriendly" ), 0 ); + //SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "ballGametype.ballStatusText" ), &"MPUI_BALL_HOME" ); + //} +} + +function set_player_ball_carrier_dr( localClientNum, on_off ) +{ + self duplicate_render::update_dr_flag( localClientNum, "ballcarrier", on_off ); +} + +function set_player_pass_option_dr( localClientNum, on_off ) +{ + self duplicate_render::update_dr_flag( localClientNum, "passoption", on_off ); +} + +function resetOnDemoJump( localClientNum, goal, effects ) +{ + for (;;) + { + level waittill( "demo_jump" + localClientNum ); + + setup_goal_fx( localClientNum, goal, effects ); + } +} + +function watch_for_team_change( localClientNum ) +{ + level notify( "end_team_change_watch" ); + level endon( "end_team_change_watch" ); + + level waittill( "team_changed" ); + + thread setup_fx( localClientNum ); +} \ No newline at end of file diff --git a/mp/gametypes/ball.gsc b/mp/gametypes/ball.gsc new file mode 100644 index 0000000..f7345f5 --- /dev/null +++ b/mp/gametypes/ball.gsc @@ -0,0 +1,2762 @@ +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hud_message_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\array_shared; +#using scripts\shared\_oob; +#using scripts\shared\killstreaks_shared; + + + + + +#using scripts\shared\abilities\_ability_player; + +#using scripts\mp\_armor; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_defaults; +#using scripts\mp\gametypes\_globallogic_player; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_ui; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hud_message; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\teams\_teams; + + + + //"p7_mp_uplink_ball" + + + + + + + + + + + + + + + +#precache( "string", "OBJECTIVES_BALL" ); +//#precache( "string", "OBJECTIVES_BALL_SCORE" ); +#precache( "string", "OBJECTIVES_BALL_HINT" ); +#precache( "string", "MP_BALL_OVERTIME_ROUND_1" ); +#precache( "string", "MP_BALL_OVERTIME_ROUND_1" ); +#precache( "string", "MP_BALL_OVERTIME_ROUND_2_WINNER" ); +#precache( "string", "MP_BALL_OVERTIME_ROUND_2_LOSER" ); +#precache( "string", "MP_BALL_OVERTIME_ROUND_2_TIE" ); +#precache( "string", "MP_BALL_OVERTIME_ROUND_2_TIE" ); +#precache( "string", "MPUI_BALL_OVERTIME_FASTEST_CAP_TIME" ); +#precache( "string", "MPUI_BALL_OVERTIME_DEFEAT_TIMELIMIT" ); +#precache( "string", "MPUI_BALL_OVERTIME_DEFEAT_DID_NOT_DEFEND" ); + +#precache( "string", "MP_BALL_PICKED_UP" ); +#precache( "string", "MP_BALL_DROPPED" ); +#precache( "string", "MP_BALL_CAPTURE" ); + +#precache( "fx", "ui/fx_uplink_ball_trail" ); +#precache( "fx", "ui/fx_uplink_ball_vanish" ); + +#precache( "objective", "ball_ball" ); +#precache( "objective", "ball_goal_allies" ); +#precache( "objective", "ball_goal_axis" ); + +/* + BALL + + Level requirements + ------------------ + Allied Spawnpoints: + classname mp_sd_spawn_attacker + Allied players spawn from these. Place at least 16 of these relatively close together. + + Axis Spawnpoints: + classname mp_sd_spawn_defender + Axis players spawn from these. Place at least 16 of these relatively close together. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Goal: + Ball: +*/ + +function autoexec __init__sytem__() { system::register("ball",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "allplayers", "ballcarrier" , 1, 1, "int" ); + clientfield::register( "allplayers", "passoption" , 1, 1, "int" ); + clientfield::register( "world", "ball_away" , 1, 1, "int" ); + clientfield::register( "world", "ball_score_allies" , 1, 1, "int" ); + clientfield::register( "world", "ball_score_axis" , 1, 1, "int" ); +} + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerRoundSwitch( 0, 9 ); + util::registerNumLives( 0, 100 ); + util::registerRoundScoreLimit( 0, 5000 ); + util::registerScoreLimit( 0, 5000 ); + + level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false ); + + level.teamKillPenaltyMultiplier = GetGametypeSetting( "teamKillPenalty" ); + level.teamKillScoreMultiplier = GetGametypeSetting( "teamKillScore" ); + level.enemyObjectivePingTime = GetGametypeSetting( "objectivePingTime" ); + + if ( level.roundScoreLimit ) + { + level.carryScore = math::clamp( GetGametypeSetting( "carryScore" ), 0, level.roundScoreLimit ); + level.throwScore = math::clamp( GetGametypeSetting( "throwScore" ), 0, level.roundScoreLimit ); + } + else + { + level.carryScore = GetGametypeSetting( "carryScore" ); + level.throwScore = GetGametypeSetting( "throwScore" ); + } + + level.carryArmor = GetGametypeSetting( "carrierArmor" ); + + level.ballCount = GetGametypeSetting( "ballCount" ); + level.enemyCarrierVisible = GetGametypeSetting( "enemyCarrierVisible" ); + level.idleFlagReturnTime = GetGametypeSetting( "idleFlagResetTime" ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.teamBased = true; + level.overrideTeamScore = true; + level.clampScoreLimit = false; + level.doubleOvertime = true; + + level.onPrecacheGameType =&onPrecacheGameType; + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.onPlayerKilled =&onPlayerKilled; + level.onRoundSwitch =&onRoundSwitch; + level.onRoundScoreLimit = &onRoundScoreLimit; + level.onEndGame =&onEndGame; + level.onRoundEndGame =&onRoundEndGame; + level.getTeamKillPenalty =&ball_getTeamKillPenalty; + level.getTeamKillScore =&ball_getTeamKillScore; + level.setMatchScoreHUDElemForTeam =&setMatchScoreHUDElemForTeam; + level.shouldPlayOvertimeRound =&shouldPlayOvertimeRound; + level.onTimeLimit = &ball_onTimeLimit; + + gameobjects::register_allowed_gameobject( level.gameType ); + + globallogic_audio::set_leader_gametype_dialog ( "startUplink", "hcStartUplink", "uplOrders", "uplOrders" ); + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "carries", "throws", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "carries", "throws" ); +} + +function onPrecacheGameType() +{ + game["strings"]["score_limit_reached"] = &"MP_CAP_LIMIT_REACHED"; + +// game["ball_dropped_sound"] = "mp_war_objective_lost"; +// game["ball_recovered_sound"] = "mp_war_objective_taken"; +} + + +function onStartGameType() +{ + level.useStartSpawns = true; + level.ballWorldWeapon = GetWeapon( "ball_world" ); + level.passingBallWeapon = GetWeapon( "ball_world_pass" ); + + if ( !isdefined( game["switchedsides"] ) ) + { + game["switchedsides"] = false; + } + + setClientNameMode("auto_change"); + + if ( level.scoreRoundWinBased ) + { + globallogic_score::resetTeamScores(); + } + + util::setObjectiveText( "allies", &"OBJECTIVES_BALL" ); + util::setObjectiveText( "axis", &"OBJECTIVES_BALL" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_BALL" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_BALL" ); + } + else + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_BALL_SCORE" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_BALL_SCORE" ); + } + util::setObjectiveHintText( "allies", &"OBJECTIVES_BALL_HINT" ); + util::setObjectiveHintText( "axis", &"OBJECTIVES_BALL_HINT" ); + + if ( isdefined( game["overtime_round"] ) ) + { + // This is only necessary when cumulativeRoundScores is on so that the game doesn't immediately end due to scorelimit being set to 1 in OT + if ( !isdefined( game["ball_game_score"] ) ) + { + game["ball_game_score"] = []; + game["ball_game_score"]["allies"] = [[level._getTeamScore]]( "allies" ); + game["ball_game_score"]["axis"] = [[level._getTeamScore]]( "axis" ); + } + [[level._setTeamScore]]( "allies", 0 ); + [[level._setTeamScore]]( "axis", 0 ); + + if ( isdefined( game["ball_overtime_score_to_beat"] ) ) + { + util::registerScoreLimit( game["ball_overtime_score_to_beat"], game["ball_overtime_score_to_beat"] ); + } + else + { + util::registerScoreLimit( 1, 1 ); + } + + if ( isdefined( game["ball_overtime_time_to_beat"] ) ) + { + util::registerTimeLimit( game["ball_overtime_time_to_beat"] / 60000, game["ball_overtime_time_to_beat"] / 60000 ); + } + else + { + util::registerTimeLimit( 0, 1440 ); // Reset the time limit from the round_time_to_beat + } + + if ( game["overtime_round"] == 1 ) + { + util::setObjectiveHintText( "allies", &"MP_BALL_OVERTIME_ROUND_1" ); + util::setObjectiveHintText( "axis", &"MP_BALL_OVERTIME_ROUND_1" ); + } + else if ( isdefined( game["ball_overtime_first_winner"] ) ) + { + level.onTimeLimit = &ballOvertimeRound2_onTimeLimit; + game["teamSuddenDeath"][game["ball_overtime_first_winner"]] = true; + util::setObjectiveHintText( game["ball_overtime_first_winner"], &"MP_BALL_OVERTIME_ROUND_2_WINNER" ); + util::setObjectiveHintText( util::getOtherTeam( game["ball_overtime_first_winner"] ), &"MP_BALL_OVERTIME_ROUND_2_LOSER" ); + } + else + { + level.onTimeLimit = &ballOvertimeRound2_onTimeLimit; + util::setObjectiveHintText( "allies", &"MP_BALL_OVERTIME_ROUND_2_TIE" ); + util::setObjectiveHintText( "axis", &"MP_BALL_OVERTIME_ROUND_2_TIE" ); + } + } + else if ( isdefined( game["round_time_to_beat"] ) ) + { + util::registerTimeLimit( game["round_time_to_beat"] / 60000, game["round_time_to_beat"] / 60000 ); + } + + // Spawn Points + + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::place_spawn_points( "mp_ctf_spawn_allies_start" ); + spawnlogic::place_spawn_points( "mp_ctf_spawn_axis_start" ); + spawnlogic::add_spawn_points( "allies", "mp_ctf_spawn_allies" ); + spawnlogic::add_spawn_points( "axis", "mp_ctf_spawn_axis" ); + + spawning::add_fallback_spawnpoints( "allies", "mp_tdm_spawn" ); + spawning::add_fallback_spawnpoints( "axis", "mp_tdm_spawn" ); + + spawning::updateAllSpawnPoints(); + spawning::update_fallback_spawnpoints(); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + level.spawn_axis = spawnlogic::get_spawnpoint_array( "mp_ctf_spawn_axis" ); + level.spawn_allies = spawnlogic::get_spawnpoint_array( "mp_ctf_spawn_allies" ); + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array("mp_ctf_spawn_" + team + "_start"); + } + + level thread setup_objectives(); +} + +function anyBallsInTheAir() +{ + foreach( ball in level.balls ) + { + if ( isdefined( ball.carrier ) ) + continue; + if ( isdefined( ball.projectile ) ) + { + if ( !(ball.projectile IsOnGround()) ) + return ball; + } + } + + return; +} + +function waitForBallToComeToRest() +{ + self endon("reset"); + self endon("pickup_object"); + + if ( isdefined( self.projectile ) ) + { + if ( self.projectile IsOnGround() ) + return; + + self.projectile endon("death"); + self.projectile endon("stationary"); + self.projectile endon("grenade_bounce"); + while(1) + { + wait(1); + } + } +} + +function freezePlayersForRoundEnd() +{ + self endon("disconnect"); + + self globallogic_player::freezePlayerForRoundEnd(); + self thread globallogic::roundEndDoF( 4.0 ); + + // incase they were dead at the time + self waittill( "spawned" ); + + if ( self.sessionstate == "playing" ) + { + self globallogic_player::freezePlayerForRoundEnd(); + self thread globallogic::roundEndDoF( 4.0 ); + } +} + +function waitForAllBallsToComeToRest() +{ + // wait for the ball to hit the ground + ball = anyBallsInTheAir(); + + if ( isdefined( ball ) ) + { + //because we are waiting the check time limit keeps firing + //we dont want to rerun this or the endgame code + level.onTimeLimit = &ball_onTimeLimit_doNothing; + ball waitForBallToComeToRest(); + } +} + +function ball_onTimeLimit() +{ + waitForAllBallsToComeToRest(); + + globallogic_defaults::default_onTimeLimit(); +} + +function ball_onTimeLimit_doNothing() +{ +} + +function ballOvertimeRound2_onTimeLimit() +{ + waitForAllBallsToComeToRest(); + + winner = undefined; + + if ( level.teamBased ) + { + foreach( team in level.teams ) + { + if( game["teamSuddenDeath"][team] ) + { + winner = team; + break; + } + } + + if( !isDefined( winner ) ) + { + winner = globallogic::determineTeamWinnerByGameStat( "teamScores" ); + } + + globallogic_utils::logTeamWinString( "time limit", winner ); + } + else + { + winner = globallogic_score::getHighestScoringPlayer(); + + /# + if ( isdefined( winner ) ) + print( "time limit, win: " + winner.name ); + else + print( "time limit, tie" ); + #/ + } + + // i think these two lines are obsolete + //makeDvarServerInfo( "ui_text_endreason", game["strings"]["time_limit_reached"] ); + SetDvar( "ui_text_endreason", game["strings"]["time_limit_reached"] ); + + thread globallogic::endGame( winner, game["strings"]["time_limit_reached"] ); +} + + +function onSpawnPlayer(predictedSpawn) +{ + self.isBallCarrier = false; + self.ballCarried = undefined; + self clientfield::set( "ctf_flag_carrier", 0 ); + + self thread ballConsistencySwitchThread(); + + spawning::onSpawnPlayer(predictedSpawn); +} + +function ballConsistencySwitchThread() +{ + self endon( "death" ); + self endon( "delete" ); + // failsafe thread to watch if we have the ball but it is not primary + player = self; + ball = GetWeapon( "ball" ); + while( 1 ) + { + if( isdefined( ball ) && player HasWeapon( ball ) ) + { + curWeapon = player GetCurrentWeapon(); + if( isdefined( curWeapon ) && ( curWeapon != ball ) && !(player IsSwitchingWeapons()) ) + { + if( curWeapon.isHeroWeapon ) + { + slot = self GadgetGetSlot( curWeapon ); + if( !self ability_player::gadget_is_in_use( slot ) ) + { + {wait(.05);}; + continue; + } + } + /# + println( "player has ball" ); + #/ + player switchToWeapon( ball ); + player DisableWeaponCycling(); + player DisableOffhandWeapons(); + } + } + {wait(.05);}; + } +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( isdefined( self.carryObject ) ) + { + otherTeam = util::getOtherTeam( self.team ); + + self RecordGameEvent("return"); + + if ( isdefined( attacker ) && IsPlayer( attacker ) && attacker != self ) + { + attacker RecordGameEvent("kill_carrier"); + if( attacker.team != self.team ) + { + scoreevents::processScoreEvent( "kill_ball_carrier", attacker, undefined, weapon ); + attacker AddPlayerStat( "kill_carrier", 1 ); + } + + // TODO: Add ball # to objectiveId + globallogic_audio::leader_dialog( "uplWeDrop", self.team, undefined, "uplink_ball" ); + globallogic_audio::leader_dialog( "uplTheyDrop", otherTeam, undefined, "uplink_ball" ); + + globallogic_audio::play_2d_on_team( "mpl_balldrop_sting_friend", self.team ); + globallogic_audio::play_2d_on_team( "mpl_balldrop_sting_enemy", otherTeam ); + + level thread popups::DisplayTeamMessageToTeam( &"MP_BALL_DROPPED", self, self.team ); + level thread popups::DisplayTeamMessageToTeam( &"MP_BALL_DROPPED", self, otherTeam ); + } + } + else if ( isdefined( attacker.carryObject ) && ( attacker.team != self.team ) ) + { + scoreevents::processScoreEvent( "kill_enemy_while_carrying_ball", attacker, undefined, weapon ); + } + + foreach( ball in level.balls ) + { + ballCarrier = ball.carrier; + if ( isdefined( ballCarrier ) ) + { + ballOrigin = ball.carrier.origin; + isCarried = true; + } + else + { + ballOrigin = ball.curorigin; + isCarried = false; + } + + if ( isCarried && isdefined( attacker ) && isdefined( attacker.team ) && ( attacker != self ) && ( ballCarrier != attacker ) ) + { + if ( attacker.team == ball.carrier.team ) + { + dist = Distance2dSquared(self.origin, ballOrigin); + if ( dist < level.defaultOffenseRadiusSQ ) + { + attacker addplayerstat( "defend_carrier", 1 ); + break; + } + } + } + } + + victim = self; + + foreach( ball_goal in level.ball_goals ) + { + if ( isdefined( attacker ) && isdefined( attacker.team ) && ( attacker != victim ) && isdefined( victim.team ) && isPlayer( attacker ) ) + { + dist_to_goal = Distance2dSquared( attacker.origin, ball_goal.origin ); + victim_dist_to_goal = Distance2dSquared( victim.origin, ball_goal.origin ); + if ( dist_to_goal < level.defaultOffenseRadiusSQ || victim_dist_to_goal < level.defaultOffenseRadiusSQ ) + { + if ( victim.team == ball_goal.team ) + { + attacker thread challenges::killedBaseDefender( ball_goal.trigger ); + } + else + { + attacker thread challenges::killedBaseOffender( ball_goal.trigger, weapon ); + } + } + } + } +} + +function onRoundSwitch() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + level.halftimeType = "halftime"; + game["switchedsides"] = !game["switchedsides"]; +} + +function onRoundScoreLimit() +{ + if ( !isdefined( game["overtime_round"] ) ) + { + timeLimit = GetGametypeSetting( "timeLimit" ) * 60000; + timeToBeat = globallogic_utils::getTimePassed(); + if ( timeLimit > 0 && timeToBeat < timeLimit ) + { + game["round_time_to_beat"] = timeToBeat; + } + } + + return globallogic_defaults::default_onRoundScoreLimit(); +} + +function onEndGame( winningTeam ) +{ + if ( !isdefined( winningTeam ) || ( winningTeam == "tie" ) ) + { + return; + } + + if ( isdefined( game["overtime_round"] ) ) + { + if ( game["overtime_round"] == 1 ) + { + game["ball_overtime_first_winner"] = winningTeam; + game["ball_overtime_score_to_beat"] = GetTeamScore( winningTeam ); + game["ball_overtime_time_to_beat"] = globallogic_utils::getTimePassed(); + } + else + { + game["ball_overtime_second_winner"] = winningTeam; + game["ball_overtime_best_score"] = GetTeamScore( winningTeam ); + game["ball_overtime_best_time"] = globallogic_utils::getTimePassed(); + } + } +} + +function updateTeamScoreByRoundsWon() +{ + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, game["roundswon"][team] ); + } + } +} + + +function onRoundEndGame( winningTeam ) +{ + if ( isdefined( game["overtime_round"] ) ) + { + if ( isdefined( game["ball_overtime_first_winner"] ) ) + { + losing_team_score = 0; + if ( !isdefined( winningTeam ) || ( winningTeam == "tie" ) ) + { + winningTeam = game["ball_overtime_first_winner"]; + } + + if ( game["ball_overtime_first_winner"] == winningTeam ) + { + level.endVictoryReasonText = &"MPUI_BALL_OVERTIME_FASTEST_CAP_TIME"; + level.endDefeatReasonText = &"MPUI_BALL_OVERTIME_DEFEAT_TIMELIMIT"; + } + else + { + level.endVictoryReasonText = &"MPUI_BALL_OVERTIME_FASTEST_CAP_TIME"; + level.endDefeatReasonText = &"MPUI_BALL_OVERTIME_DEFEAT_DID_NOT_DEFEND"; + } + } + else if ( !isdefined( winningTeam ) || ( winningTeam == "tie" ) ) + { + updateTeamScoreByRoundsWon(); + return "tie"; + } + + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + score = game["roundswon"][team]; + if ( team === winningTeam ) + { + score++; + } + [[level._setTeamScore]]( team, score ); + } + } + else + { + if( isdefined( game["ball_overtime_score_to_beat"] ) && ( game["ball_overtime_score_to_beat"] > game["ball_overtime_best_score"] ) ) + { + added_score = game["ball_overtime_score_to_beat"]; + } + else + { + added_score = game["ball_overtime_best_score"]; + } + + foreach( team in level.teams ) + { + score = game["ball_game_score"][team]; + if ( team === winningTeam ) + { + score += added_score; + } + [[level._setTeamScore]]( team, score ); + } + } + return winningTeam; + } + + if ( level.scoreRoundWinBased ) + { + updateTeamScoreByRoundsWon(); + + winner = globallogic::determineTeamWinnerByGameStat( "roundswon" ); + } + else + { + winner = globallogic::determineTeamWinnerByTeamScore(); + } + + return winner; +} + + +function setMatchScoreHUDElemForTeam() +{ + self setText( &"" ); +} + +function shouldPlayOvertimeRound() +{ + if ( isdefined( game["overtime_round"] ) ) + { + if ( game["overtime_round"] == 1 || !level.gameEnded ) // If we've only played 1 round or we're in the middle of the 2nd keep going + { + return true; + } + + return false; + } + + if ( !level.scoreRoundWinBased ) + { + // Only go to overtime if both teams are tied and it's either the last round or both teams are one away from winning + if ( ( game["teamScores"]["allies"] == game["teamScores"]["axis"] ) && + ( util::hitRoundLimit() || ( game["teamScores"]["allies"] == level.scoreLimit-1 ) ) ) + { + return true; + } + } + else + { + // Only go to overtime if both teams are one round away from winning + alliesRoundsWon = util::getRoundsWon( "allies" ); + axisRoundsWon = util::getRoundsWon( "axis" ); + if ( ( level.roundWinLimit > 0 ) && ( axisRoundsWon == level.roundWinLimit-1 ) && ( alliesRoundsWon == level.roundWinLimit-1 ) ) + { + return true; + } + if ( util::hitRoundLimit() && ( alliesRoundsWon == axisRoundsWon ) ) + { + return true; + } + } + return false; +} + +function ball_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_penalty = globallogic_defaults::default_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ); + + if ( ( isdefined( self.isBallCarrier ) && self.isBallCarrier ) ) + { + teamkill_penalty = teamkill_penalty * level.teamKillPenaltyMultiplier; + } + + return teamkill_penalty; +} + +function ball_getTeamKillScore( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_score = rank::getScoreInfoValue( "kill" ); + + if ( ( isdefined( self.isBallCarrier ) && self.isBallCarrier ) ) + { + teamkill_score = teamkill_score * level.teamKillScoreMultiplier; + } + + return int(teamkill_score); +} + +////////////////////// + +function get_real_ball_location( startPos, startAngles, index, count, defaultDistance, rotation ) +{ + currentAngle = startAngles[1] + ( (( 360 / count ) * 0.5) + (( 360 / count ) * index) ); + cosCurrent = cos( currentAngle + rotation ); + sinCurrent = sin( currentAngle + rotation ); + + new_position = startPos + ( defaultDistance * cosCurrent, defaultDistance * sinCurrent, 0 ); + + clip_mask = (1 << 0) | (1 << 3); + trace = PhysicsTrace( startPos, new_position, (-5, -5, -5), (5, 5, 5), self, clip_mask ); + return trace[ "position" ]; +} + +function setup_objectives() +{ + level.ball_goals = []; + level.ball_starts = []; + level.balls = []; + + level.ball_starts = getEntArray( "ball_start" ,"targetname"); + + foreach( ball_start in level.ball_starts ) + { + level.balls[level.balls.size] = spawn_ball( ball_start ); + } + + if ( level.ballCount > level.ball_starts.size ) + { + width = 48; + height = 48; + count = level.ballCount - level.ball_starts.size; + + for ( index = 0; index < count; index++ ) + { + position = get_real_ball_location( level.ball_starts[0].origin, level.ball_starts[0].angles, index, count, width, 0 ); + trigger = spawn( "trigger_radius", position, 0, width, height ); + level.ball_starts[level.ball_starts.size] = trigger; + level.balls[level.balls.size] = spawn_ball( trigger ); + } + } + + foreach( team in level.teams ) + { + if( !game["switchedsides"] ) + { + trigger = GetEnt( "ball_goal_" + team, "targetname" ); + } + else + { + trigger = GetEnt( "ball_goal_" + util::getOtherTeam( team ), "targetname" ); + } + + level.ball_goals[team] = setup_goal( trigger, team ); + } +} + +// Goals +//======================================== + +function setup_goal( trigger, team ) +{ + // Goal Object + useObj = gameobjects::create_use_object( team, trigger, [], ( 0, 0, trigger.height * 0.5 ), istring("ball_goal_"+team) ); + useObj gameobjects::set_visible_team( "any" ); + useObj gameobjects::set_model_visibility( true ); + useObj gameobjects::allow_use( "enemy" ); + useObj gameobjects::set_use_time( 0 ); + + foreach(ball in level.balls) + { + useObj gameobjects::set_key_object( ball ); + } + + useObj.canUseObj = &can_use_goal; + useObj.onUse = &on_use_goal; + + useObj.ball_in_goal = false; + useObj.radiusSq = trigger.radius * trigger.radius; + useObj.center = trigger.origin + ( 0, 0, trigger.height * 0.5 ); + + // TODO Killcam + //useObj.killcamEnt = spawn( "script_model", goal.origin ); + + return useObj; +} + +function can_use_goal( player ) +{ + return !self.ball_in_goal; +} + +function on_use_goal(player) +{ + if ( !IsDefined(player) || !IsDefined(player.carryObject) ) + return; + + if ( isDefined( player.carryObject.scoreFrozenUntil ) && player.carryObject.scoreFrozenUntil > getTime() ) + return; + + self play_goal_score_fx(); + + player.carryObject.scoreFrozenUntil = getTime() + 10000; + + //player maps\mp\_events::touchdownEvent(score); TODO Score event + ball_check_assist( player, true ); + + team = self.team; + otherTeam = util::getOtherTeam( team ); + + // TODO: Ball ID + globallogic_audio::flush_objective_dialog( "uplink_ball" ); + globallogic_audio::leader_dialog( "uplWeUplink", otherTeam ); + globallogic_audio::leader_dialog( "uplTheyUplink", team ); + + globallogic_audio::play_2d_on_team( "mpl_ballcapture_sting_friend", otherTeam ); + globallogic_audio::play_2d_on_team( "mpl_ballcapture_sting_enemy", team ); + + level thread popups::DisplayTeamMessageToTeam( &"MP_BALL_CAPTURE", player, team ); + level thread popups::DisplayTeamMessageToTeam( &"MP_BALL_CAPTURE", player, otherTeam ); + + if( should_record_final_score_cam( otherTeam, level.carryScore ) ) + { +// killcamentity = self.goal.killcamEnt; +// killcamentityindex = killcamentity GetEntityNumber(); +// killcamentitystarttime = killcamentity.birthtime; +// if ( !IsDefined( killcamentitystarttime ) ) +// { +// killcamentitystarttime = 0; +// } +// player.deathTime = GetTime(); + //maps\mp\gametypes\_damage::recordFinalKillCam( 5.0, player, player, player GetEntityNumber(), killcamentityindex, killcamentitystarttime, "none", 0, 0, undefined, "score" ); TODO Killcam + } + + //ball_play_score_fx(self.goal); -- TODO FX + + if(IsDefined(player.shoot_charge_bar)) + { + player.shoot_charge_bar.inUse = false; + } + + ball = player.carryObject; + ball.lastCarrierScored = true; + + player gameobjects::take_carry_weapon( ball.carryWeapon ); + ball ball_set_dropped( true ); + ball thread upload_ball( self ); + + if( isdefined(player.pers["carries"]) ) + { + player.pers["carries"]++; + player.carries = player.pers["carries"]; + } + + bbPrint( "mpobjective", "gametime %d objtype %s team %s playerx %d playery %d playerz %d", gettime(), "ball_capture", team, player.origin ); + player RecordGameEvent("capture"); + + player challenges::capturedObjective( gettime(), self.trigger ); + player AddPlayerStatWithGameType( "CARRIES", 1 ); + player AddPlayerStatWithGameType( "captures", 1 ); // counts towards Destroyer challenge + scoreevents::processScoreEvent( "ball_capture_carry", player ); + ball_give_score( otherTeam, level.carryScore ); +} + +// Balls +//======================================== + +function spawn_ball( trigger ) +{ + visuals = []; + visuals[0] = spawn("script_model", trigger.origin ); + visuals[0] SetModel( "wpn_t7_uplink_ball_world" ); + visuals[0] notsolid(); + + trigger EnableLinkTo(); + trigger LinkTo( visuals[0] ); + trigger.no_moving_platfrom_unlink = true; + + ballObj = gameobjects::create_carry_object( "neutral", trigger, visuals, (0,0,0), istring("ball_ball"), "mpl_hit_alert_ballholder" ); + ballObj gameobjects::allow_carry( "any" ); + ballObj gameobjects::set_visible_team( "any" ); + ballObj gameobjects::set_drop_offset( 8 ); // the radius of the ball model so it looks like its on the ground + + ballObj.objectiveOnVisuals = true; + ballObj.allowWeapons = false; + ballObj.carryWeapon = GetWeapon( "ball" ); + ballObj.keepCarryWeapon = true; + ballObj.waterBadTrigger = false; + ballObj.disallowRemoteControl = true; + ballObj.disallowPlaceablePickup = true; + //ballObj.requiresLOS = true; I don't think this does anything + + ballObj gameobjects::update_objective(); + + ballObj.canUseObject = &can_use_ball; + ballObj.onPickup = &on_pickup_ball; + ballObj.setDropped = &ball_set_dropped; + ballObj.onReset = &on_reset_ball; + ballObj.pickupTimeoutOverride = &ball_physics_timeout; + ballObj.carryWeaponThink = &carry_think_ball; + + ballObj.in_goal = false; + ballObj.lastCarrierScored = false; + ballObj.lastCarrierTeam = "neutral"; + + if ( level.enemyCarrierVisible == 2 ) + { + ballObj.objIDPingFriendly = true; + } + + if ( level.idleFlagReturnTime > 0 ) + { + ballObj.autoResetTime = level.idleFlagReturnTime; + } + else + { + ballObj.autoResetTime = undefined; + } + + PlayFXOnTag( "ui/fx_uplink_ball_trail", ballObj.visuals[0], "tag_origin" ); + + return ballObj; +} + +// Ball Events +//======================================== + +function can_use_ball(player) +{ + if(!isDefined(player)) + return false; + + if ( !self gameobjects::can_interact_with( player ) ) + return false; + + if ( IsDefined(self.dropTime) && self.dropTime >= GetTime() ) + return false; + + //if ( player IsMeleeing() ) + //return false; + + if( isdefined( player.resurrect_weapon ) && ( player getcurrentweapon() == player.resurrect_weapon ) ) + return false; + + if ( player isCarryingTurret() ) + return false; + + currentWeapon = player GetCurrentWeapon(); + if(isDefined(currentWeapon)) + { + if( !valid_ball_pickup_weapon( currentWeapon ) ) + return false; + } + + nextWeapon = player.changingWeapon; + if(IsDefined(nextWeapon) && player IsSwitchingWeapons() ) + { + if( !valid_ball_pickup_weapon( nextWeapon ) ) + return false; + } + + if ( player player_no_pickup_time() ) + return false; + + ball = self.visuals[0]; + thresh = 15; + dist2 = Distance2DSquared( ball.origin, player.origin ); + if( dist2 < thresh * thresh ) + return true; + + start = player getEye(); + + end = ( self.curorigin[0], self.curorigin[1], self.curorigin[2] + 5 ); + if ( isdefined( ball ) ) + { + end = ( ball.origin[0], ball.origin[1], ball.origin[2] + 5 ); + } + + if ( isdefined( self.carrier ) && isPlayer( self.carrier ) ) + { + end = self.carrier getEye(); + } + + // in the case of a player to player pass + // if the ball hits the intended pass recipient the passed ball gets deleted and + // a new projectile gets launched to handle the bounce. This all happens before the + // gameobject trigger fires and tests the pickup. Ideally the bounce would never happen + // but its a timing issue with the engine and order of notify processing. Unfortunatly + // the old grenade might not be fully deleted when we do this test to see if the player + // can pick it up. So both the new missile and the old can block the test below. + first_skip_ent = ball; + second_skip_ent = ball; + if ( isdefined( self.projectile ) ) + { + first_skip_ent = self.projectile; + } + if ( isdefined( self.lastProjectile ) ) + { + second_skip_ent = self.lastProjectile; + } + + if ( !BulletTracePassed( end, start, false, first_skip_ent, second_skip_ent, false, false ) ) + { + player_origin = (player.origin[0], player.origin[1], player.origin[2] + 10 ); + if ( !BulletTracePassed( end, player_origin, false, first_skip_ent, second_skip_ent, false, false ) ) + { + return false; + } + } + + return true; +} + +function chief_mammal_reset() +{ + self.isResetting = true; + + self notify ( "reset" ); + + origin = self.curOrigin; + if( isdefined( self.projectile ) ) + origin = self.projectile.origin; + + foreach( visual in self.visuals ) + { + visual.origin = origin; + visual.angles = visual.baseAngles; + visual DontInterpolate(); + visual show(); + } + + if( isdefined( self.projectile ) ) + { + self.projectile Delete(); + self.lastProjectile = undefined; + } + + self gameobjects::clear_carrier(); + gameobjects::update_world_icons(); + gameobjects::update_compass_icons(); + gameobjects::update_objective(); + + self.isResetting = false; +} + +function on_pickup_ball( player ) +{ + self gameobjects::set_flags( 0 ); + + if( !isalive( player ) ) + { + self chief_mammal_reset(); + return; + } + + player DisableUsability(); + player DisableOffhandWeapons(); + + level.useStartSpawns = false; + + level clientfield::set( "ball_away", 1 ); + + //Physics objects get linked to entities if they come to rest on them + linkedParent = self.visuals[0] GetLinkedEnt(); + if(IsDefined(linkedParent)) + self.visuals[0] unlink(); + + player resetflashback(); + //self.current_start.in_use = false; + + pass = false; + ball_velocity = 0.0; + if(IsDefined(self.projectile)) + { + pass = true; + ball_velocity = self.projectile GetVelocity(); + self.projectile Delete(); + self.lastProjectile = undefined; + } + + if( pass ) + { + if( self.lastCarrierTeam == player.team ) + { + if ( self.lastCarrier != player ) + { + player.passTime = GetTime(); + player.passPlayer = self.lastcarrier; + + // TODO: Add ball # to objectiveId + globallogic_audio::leader_dialog( "uplTransferred", player.team, undefined, "uplink_ball" ); + } + } + else + { + if ( Length( ball_velocity ) > 0.1 ) + { + scoreevents::processScoreEvent( "ball_intercept", player ); + } + } + } + + otherTeam = util::getOtherTeam( player.team ); + + if( self.lastCarrierTeam != player.team ) + { + // TODO: Add ball # to objectiveId + globallogic_audio::leader_dialog( "uplWeTake", player.team, undefined, "uplink_ball" ); + globallogic_audio::leader_dialog( "uplTheyTake", otherTeam, undefined, "uplink_ball" ); + } + + globallogic_audio::play_2d_on_team( "mpl_ballget_sting_friend", player.team ); + globallogic_audio::play_2d_on_team( "mpl_ballget_sting_enemy", otherTeam ); + + level thread popups::DisplayTeamMessageToTeam( &"MP_BALL_PICKED_UP", player, player.team ); + level thread popups::DisplayTeamMessageToTeam( &"MP_BALL_PICKED_UP", player, otherTeam ); + + //self ball_fx_stop(); -- TODO FX + + self.lastCarrierScored = false; + self.lastcarrier = player; + self.lastCarrierTeam = player.team; + + self gameobjects::set_owner_team( player.team ); + + //player GiveWeapon( GetWeapon( CONST_BALL_WEAPON )); + + player.ballDropDelay = GetDvarInt( "scr_ball_water_drop_delay", 10 ); //server frames to wait while underwater before dropping the ball + player.objective = 1; + + player.hasPerkSprintFire = player HasPerk("specialty_sprintfire" ); + player setPerk("specialty_sprintfire" ); + + player clientfield::set( "ballcarrier", 1 ); + + if ( level.carryArmor > 0 ) + player thread armor::setLightArmor(level.carryArmor); + else + player thread armor::unsetLightArmor(); + + player thread player_update_pass_target(self); + + player RecordGameEvent("pickup"); +} + +function ball_carrier_cleanup( ) +{ + self gameobjects::set_owner_team( "neutral" ); + + if ( isdefined(self.carrier) ) + { + self.carrier clientfield::set( "ballcarrier", 0 ); + self.carrier.ballDropDelay = undefined; + self.carrier.noPickupTime = GetTime() + 500; + + self.carrier player_clear_pass_target(); + self.carrier notify("cancel_update_pass_target"); + + self.carrier thread armor::unsetLightArmor(); + + if ( !self.carrier.hasPerkSprintFire ) + { + self.carrier unsetPerk( "specialty_sprintfire" ); + } + + self.carrier EnableUsability(); + self.carrier EnableOffhandWeapons(); + + self.carrier SetBallPassAllowed(false); + self.carrier.objective = 0; + } +} + +function ball_set_dropped( skip_physics ) +{ + if(!isdefined(skip_physics))skip_physics=false; + + self.isResetting = true; + self.dropTime = GetTime(); + + self notify ( "dropped" ); + + dropAngles = (0,0,0); + + carrier = self.carrier; + if(IsDefined(carrier) && carrier.team != "spectator") + { + dropOrigin = carrier.origin; + dropAngles = carrier.angles; + } + else + { + dropOrigin = self.origin; + } + + if( !isdefined( dropOrigin ) ) + dropOrigin = self.safeorigin; + + dropOrigin += ( 0, 0, 40 ); + + if( isdefined( self.projectile ) ) + self.projectile Delete(); + + self ball_carrier_cleanup(); + self gameobjects::clear_carrier(); + self gameobjects::set_position( dropOrigin, dropAngles ); + self gameobjects::update_icons_and_objective(); + self thread gameobjects::pickup_timeout( dropOrigin[2], dropOrigin[2] - 40); + + self.isResetting = false; + + if(!skip_physics) + { + angles = ( 0, dropAngles[1], 0 ); + forward = AnglesToForward( angles ); + velocity = forward * 200 + (0,0,80); + ball_physics_launch(velocity); + } + + return true; +} + +function on_reset_ball( prev_origin ) +{ + if ( ( isdefined( level.gameEnded ) && level.gameEnded ) ) + { + return; + } + + //self ball_assign_random_start(); + visual = self.visuals[0]; + + //Physics objects get linked to entities if they come to rest on them + linkedParent = visual GetLinkedEnt(); + if( IsDefined(linkedParent) ) + { + visual unlink(); + } + + if( IsDefined( self.projectile ) ) + { + self.projectile Delete(); + } + + if( !self gameobjects::get_flags( 1 ) ) + { + PlayFx( "ui/fx_uplink_ball_vanish", prev_origin ); + self play_return_vo(); + } + self.lastCarrierTeam = "none"; + self thread download_ball(); + + if ( isdefined( self.killcamEnt ) && isEntity( self.killcamEnt ) ) + { + self.killcamEnt RecordGameEventNonPlayer( "ball_reset" ); + } +} + +// Ball Functions +//======================================== + +function reset_ball() +{ + self thread gameobjects::return_home(); +} + +function upload_ball( goal ) +{ + self notify( "score_event" ); + + self.in_goal = true; + goal.ball_in_goal = true; + + if(IsDefined(self.projectile)) + { + self.projectile Delete(); + } + + self gameobjects::allow_carry( "none" ); + + move_to_center_time = .4; + move_up_time = 1.2; + rotate_time = 1.0; + + in_enemyGoal_time = move_to_center_time + rotate_time; + total_time = in_enemyGoal_time + move_up_time; + + self gameobjects::set_flags( 1 ); + + visual = self.visuals[0]; + + visual MoveTo( goal.center, move_to_center_time, 0, move_to_center_time); + visual RotateVelocity( (1080,1080,0), total_time, total_time, 0); + + wait in_enemyGoal_time; + + goal.ball_in_goal = false; + + self.visibleTeam = "neutral"; + self gameobjects::update_world_icon( "friendly", false ); + self gameobjects::update_world_icon( "enemy", false ); + self gameobjects::update_objective(); + + visual MoveZ(4000, move_up_time, move_up_time*.1, 0); + + wait move_up_time; + + self thread gameobjects::return_home(); +} + +function download_ball() +{ + self endon ( "pickup_object" ); + + self gameobjects::allow_carry( "any" ); + self gameobjects::set_owner_team( "neutral" ); + self gameobjects::set_flags( 2 ); + + visual = self.visuals[0]; + + visual.origin = visual.baseOrigin + (0,0,4000); + visual DontInterpolate(); + + fall_time = 3; + visual MoveTo( visual.baseOrigin, fall_time, 0, fall_time); + + visual RotateVelocity( (0,720,0), fall_time, 0, fall_time); + + self.visibleTeam = "any"; + self gameobjects::update_world_icon( "friendly", true ); + self gameobjects::update_world_icon( "enemy", true ); + self gameobjects::update_objective(); + + wait( fall_time ); + + self gameobjects::set_flags( 0 ); + level clientfield::set( "ball_away", 0 ); + + //PlayFX( level._effect["ball_download_end"], self.current_start.ground_origin ); - TODO FX + + PlayFXOnTag( "ui/fx_uplink_ball_trail", visual, "tag_origin" ); + + self thread ball_download_fx(visual, fall_time); + + self.in_goal = false; +} + + +// Ball Carry Watch +//======================================== + +function carry_think_ball() +{ + self endon("disconnect"); + + self thread ball_pass_watch(); + self thread ball_shoot_watch(); + self thread ball_weapon_change_watch(); // change to hero weapons is allowed +} + +function ball_pass_watch() +{ + level endon ( "game_ended" ); + + self endon ( "disconnect" ); + self endon ( "death" ); + self endon ( "drop_object" ); + + while( 1 ) + { + self waittill( "ball_pass", weapon ); + + if( !isDefined(self.pass_target) ) + { + // self IPrintLnBold( "No Pass Target" ); + playerAngles = self GetPlayerAngles(); + playerAngles = ( math::Clamp( playerAngles[0], -85, 85 ), playerAngles[1], playerAngles[2] ); + dir = AnglesToForward( playerAngles ); + force = 90; + self.carryObject thread ball_physics_launch_drop( dir * force, self ); + return; + } + break; + } + + if( isDefined( self.carryObject ) ) + { + self thread ball_pass_or_throw_active(); + pass_target = self.pass_target; + last_target_origin = self.pass_target.origin; + wait .15; + + if( isdefined( self.pass_target ) ) + { // pass the ball + pass_target = self.pass_target; + self.carryObject thread ball_pass_projectile( self, pass_target, last_target_origin ); + } + else + { // drop the ball + playerAngles = self GetPlayerAngles(); + playerAngles = ( math::Clamp( playerAngles[0], -85, 85 ), playerAngles[1], playerAngles[2] ); + dir = AnglesToForward( playerAngles ); + force = 90; + self.carryObject thread ball_physics_launch_drop( dir * force, self ); + } + } +} + +function ball_shoot_watch() +{ + level endon ( "game_ended" ); + + self endon ( "disconnect" ); + self endon ( "death" ); + self endon ( "drop_object" ); + + extra_pitch = GetDvarFloat("scr_ball_shoot_extra_pitch", 0); + force = GetDvarFloat("scr_ball_shoot_force", 900 ); + + while(1) + { + self waittill("weapon_fired", weapon); + + if( weapon != GetWeapon( "ball" ) ) + { + continue; + } + + break; + } + + if ( IsDefined( self.carryObject ) ) + { + playerAngles = self GetPlayerAngles(); + playerAngles += (extra_pitch,0,0); + playerAngles = (math::Clamp(playerAngles[0], -85, 85), playerAngles[1], playerAngles[2]); + dir = AnglesToForward(playerAngles); + self thread ball_pass_or_throw_active(); + self thread ball_check_pass_kill_pickup( self.carryObject ); + self.carryObject ball_create_killcam_ent(); + self.carryObject thread ball_physics_launch_drop(dir*force, self, true ); + } +} + +function ball_weapon_change_watch() +{ + level endon ( "game_ended" ); + + self endon ( "disconnect" ); + self endon ( "death" ); + self endon ( "drop_object" ); + + ballWeapon = GetWeapon( "ball" ); + while( 1 ) + { + if( ballWeapon == self GetCurrentWeapon() ) + break; + self waittill ( "weapon_change" ); + } + + while( 1 ) + { + self waittill ( "weapon_change", weapon, lastWeapon ); + if( isdefined( weapon ) && ( weapon.gadget_type == 14 ) ) + break; + if( ( weapon === level.weaponNone ) && ( lastWeapon === ballWeapon ) ) // swtiching away from the ball for some reason - gravity spikes would be an example + break; + } + + playerAngles = self GetPlayerAngles(); + playerAngles = ( math::Clamp( playerAngles[0], -85, 85 ), AbsAngleClamp360( playerAngles[1] + 20 ), playerAngles[2] ); + dir = AnglesToForward( playerAngles ); + force = 90; + self.carryObject thread ball_physics_launch_drop( dir * force, self ); + + //thread gameobjects::gameObjects_dropped(); +} + +// Ball Pickup Helpers +//======================================== + +function valid_ball_pickup_weapon( weapon ) +{ + if( weapon == level.weaponNone ) + return false; + + if( weapon == GetWeapon( "ball" ) ) + return false; + + if( killstreaks::is_killstreak_weapon( weapon ) ) + return false; + + return true; +} + +function player_no_pickup_time() +{ + return isDefined(self.noPickupTime) && self.noPickupTime > GetTime(); +} + + +//self == player +function watchUnderwater( trigger ) +{ + self endon ("death" ); + self endon ("disconnect" ); + + while( 1 ) + { + if( self isplayerunderwater() ) + { + foreach(ball in level.balls) + { + if( isDefined(ball.carrier) && ball.carrier == self ) + { + ball gameobjects::set_dropped(); + return; + } + } + } + + self.ballDropDelay = undefined; + {wait(.05);}; + } +} + +function ball_physics_launch_drop( force, droppingPlayer, switchWeapon ) +{ + ball_set_dropped( true ); + ball_physics_launch( force, droppingPlayer ); + if( ( isdefined( switchWeapon ) && switchWeapon ) ) + droppingPlayer killstreaks::switch_to_last_non_killstreak_weapon( undefined, true ); +} + +function ball_check_pass_kill_pickup( carryObj ) +{ + self endon("death"); + self endon("disconnect"); + + carryObj endon("reset"); + + timer = spawnStruct(); + timer endon("timer_done"); + + timer thread timer_run(1.5); + carryObj waittill("pickup_object"); + timer timer_cancel(); + + if(!IsDefined(carryObj.carrier) || carryObj.carrier.team == self.team) + { + return; + } + + carryObj.carrier endon("disconnect"); + + timer thread timer_run(5); + carryObj.carrier waittill("death", attacker); + timer timer_cancel(); + + if(!IsDefined(attacker) || attacker != self) + { + return; + } + + timer thread timer_run(2); + carryObj waittill("pickup_object"); + timer timer_cancel(); + +// if( IsDefined(carryObj.carrier) && carryObj.carrier == self) TODO Event +// self maps\mp\_events::passKillPickupEvent(); +} + +function timer_run(time) +{ + self endon("cancel_timer"); + wait time; + self notify("timer_done"); +} + +function timer_cancel() +{ + self notify("cancel_timer"); +} + +function adjust_for_stance( ball ) +{ + target = self; + target endon("pass_end"); + + offs = 0; + while( isdefined( target ) && isdefined( ball ) ) + { + newoffs = 50; + switch( target GetStance() ) + { + case "crouch": + newoffs = 30; + break; + case "prone": + newoffs = 15; + break; + } + if( newoffs != offs ) + { + ball ballsettarget( target, ( 0, 0, newoffs ) ); + newoffs = offs; + } + {wait(.05);}; + } +} + +function ball_pass_projectile(passer, target, last_target_origin) +{ + ball_set_dropped(true); + + if(IsDefined(target)) + { + last_target_origin = target.origin; + } + + offset = ( 0, 0, 60 ); + + if ( target GetStance() == "prone" ) + { + offset = ( 0, 0, 15 ); + } + else if ( target GetStance() == "crouch" ) + { + offset = ( 0, 0, 30 ); + } + + playerAngles = passer GetPlayerAngles(); + playerAngles = (0, playerAngles[1], 0 ); + dir = AnglesToForward(playerAngles); + + delta = dir * 50; + origin = self.visuals[0].origin + delta; + + size = 5; + trace = physicstrace( self.visuals[0].origin, origin, ( -size, -size, -size ), ( size, size, size ), passer, (1 << 0) ); + + if( trace["fraction"] < 1 ) + { + t = 0.7 * trace["fraction"]; + self gameobjects::set_position( self.visuals[0].origin + delta * t, self.visuals[0].angles ); + } + else + { + self gameobjects::set_position( trace["position"], self.visuals[0].angles ); + } + + //self gameobjects::set_position( origin, playerAngles ); + + pass_dir = VectorNormalize((last_target_origin+offset) - self.visuals[0].origin); + //pass_dir = ( pass_dir[0], pass_dir[1], 0 ); + pass_vel = pass_dir * 850; + + self.lastProjectile = self.projectile; + self.projectile = passer MagicMissile( level.passingBallWeapon, self.visuals[0].origin, pass_vel ); + + target thread adjust_for_stance( self.projectile ); + + self.visuals[0] LinkTo(self.projectile); + self gameobjects::ghost_visuals(); + //self ball_dont_interpolate(); TODO FX + + self ball_create_killcam_ent(); + + self ball_clear_contents(); //Prevent magic grenade from hitting the ball visuals + + self thread ball_on_projectile_hit_client(passer); + self thread ball_on_projectile_death(); + self thread ball_watch_touch_enemy_goal(); + + passer killstreaks::switch_to_last_non_killstreak_weapon( undefined, true ); +} + +function ball_on_projectile_death() +{ + self.projectile waittill("death"); + ball = self.visuals[0]; + if(!IsDefined(self.carrier) && !self.in_goal) + { + // There's a bug where the ball will be reset during the same frame that the above trigger is notified, + // but the notification is processed after the reset. This turns physics back on, overrides the MoveTo, + // and causes the ball to bounce really hard in an arbitrary direction. + // This hack is a way to test whether the ball was just reset or not. If it was just reset, don't fake bounce. TODO THIS IS FROM AW - WTF DO WE NEED THIS? + if ( ball.origin != ball.baseOrigin + (0, 0, 4000) ) + { + self ball_physics_launch((0,0,10)); + } + } + self ball_restore_contents(); + + ball notify("pass_end"); +} + +function ball_restore_contents() +{ + if(IsDefined(self.visuals[0].old_contents)) + { + self.visuals[0] SetContents(self.visuals[0].old_contents); + self.visuals[0].old_contents = undefined; + } +} + +function ball_on_projectile_hit_client(passer) +{ + self endon("pass_end"); + self.projectile waittill( "projectile_impact_player", player ); + self.trigger notify( "trigger", player ); + self.projectile notify( "kill_ball_on_projectile_death"); + + if ( IsDefined( passer ) ) + { + passer RecordGameEvent("pass"); + } +} + +function ball_clear_contents() +{ + self.visuals[0].old_contents = self.visuals[0] SetContents(0); +} + +function ball_create_killcam_ent() +{ + if(IsDefined(self.killcamEnt)) + self.killcamEnt Delete(); + self.killcamEnt = spawn( "script_model", self.visuals[0].origin ); + self.killcamEnt linkTo(self.visuals[0]); + self.killcamEnt SetContents(0); + //self.killcamEnt SetScriptMoverKillCam( "explosive" ); TODO KILLCAM +} + +function ball_pass_or_throw_active() +{ + self endon("death"); + self endon("disconnect"); + + self.pass_or_throw_active = true; + + self AllowMelee( false ); + while( GetWeapon( "ball" ) == self GetCurrentWeapon() ) + { + {wait(.05);}; + } + + self AllowMelee( true ); + self.pass_or_throw_active = false; +} + +function ball_download_fx(ball_model, waitTime) +{ +// PlayFXOnTag( level._effect["ball_download"], ball_model, "tag_origin"); - TODO FX +// +// self waittill_notify_or_timeout("pickup_object", waitTime); +// +// StopFXOnTag( level._effect["ball_download"], ball_model, "tag_origin"); + + // bit of a hack, but this seems to be the most reliable place to put this... + self.scoreFrozenUntil = 0; +} + + +function ball_assign_random_start() +{ + new_start = undefined; + + rand_starts = array::randomize( level.ball_starts ); + foreach(start in rand_starts) + { + if(start.in_use) + continue; + + new_start = start; + break; + } + + if(!isDefined(new_start)) + return; + + ball_assign_start(new_start); +} + +function ball_assign_start(start) +{ + foreach(vis in self.visuals) + { + vis.baseOrigin = start.origin; + } + + self.trigger.baseOrigin = start.origin; + + self.current_start = start; + start.in_use = true; +} + +function ball_physics_launch(force, droppingPlayer) +{ + visuals = self.visuals[0]; + + visuals.origin_prev = undefined; + + origin = visuals.origin; + owner = visuals; + + if( isDefined( droppingPlayer ) ) + { + owner = droppingPlayer; + + origin = droppingPlayer getweaponmuzzlepoint(); + right = AnglesToRight( force ); + origin = origin + ( right[0], right[1], 0 ) * 7; + startPos = origin;// + ( 0, 0, 20 ); + delta = VectorNormalize(force) * 80; + + ///#sphere( startPos, 5, ( 0, 0, 1 ), 1, true, 10, 200 );#/ + + size = 5; + trace = physicstrace( startPos, startPos + delta, ( -size, -size, -size ), ( size, size, size ), droppingPlayer, (1 << 0) ); + + if( trace["fraction"] < 1 ) + { + t = 0.7 * trace["fraction"]; + self gameobjects::set_position( startPos + delta * t, visuals.angles ); + } + else + { + self gameobjects::set_position( trace["position"], visuals.angles ); + } + } + + //self ball_fx_start(); -- TODO FX + + grenade = owner MagicMissile( level.ballWorldWeapon, visuals.origin, force ); + visuals linkto( grenade ); + self gameobjects::ghost_visuals(); + self.lastProjectile = self.projectile; + self.projectile = grenade; + + ///#sphere( visuals.origin, 5, ( 1, 0, 0 ), 1, true, 10, 200 );#/ + visuals DontInterpolate(); // This triggers teleport to avoid interpolation from the previous position. + + self thread ball_physics_out_of_level(); + self thread ball_watch_touch_enemy_goal(); + self thread ball_physics_touch_cant_pickup_player(droppingPlayer); + self thread ball_check_oob(); + +} + +function ball_check_oob() +{ + self endon ( "reset" ); + self endon ( "pickup_object" ); + + visual = self.visuals[0]; + + while( 1 ) + { + skip_oob_check = ( isdefined( self.in_goal ) && self.in_goal ) || ( isdefined( self.isResetting ) && self.isResetting ); + if( !skip_oob_check ) + { + if( visual oob::IsTouchingAnyOOBTrigger() || visual is_touching_any_ball_return_trigger() || self gameobjects::should_be_reset( visual.origin[2], visual.origin[2] + 10, true ) ) + { + self reset_ball(); + return; + } + } + + {wait(.05);}; + } +} + + +function ball_physics_touch_cant_pickup_player(droppingPlayer) +{ + self endon ( "reset" ); + self endon ( "pickup_object" ); + + ball = self.visuals[0]; + trigger = self.trigger; + + while(1) + { + trigger waittill("trigger", player); + //Dont stop on the throwing player + if ( IsDefined(droppingPlayer) && droppingPlayer == player && player player_no_pickup_time() ) + { + continue; + } + + if ( self.dropTime >= GetTime() ) + { + continue; + } + + // There's a bug where the ball will be reset during the same frame that the above trigger is notified, + // but the notification is processed after the reset. This turns physics back on, overrides the MoveTo, + // and causes the ball to bounce really hard in an arbitrary direction. + // This hack is a way to test whether the ball was just reset or not. If it was just reset, don't fake bounce. -- TODO - WTF + if ( ball.origin == ball.baseOrigin + (0, 0, 4000) ) + { + continue; + } + +// if( !can_use_ball( player ) && ( self.dropTime + 200 < GetTime() ) ) +// { +// //self thread ball_physics_fake_bounce(); +// } + } +} + +function ball_physics_fake_bounce() +{ + ball = self.visuals[0]; //-- TODO Since we can use the physics, we need to kick the ball by doing another magic grenade, or apply new movement to existing projectile + // Test this existing projectile and then do stuff with it + vel = ball GetVelocity(); + bounceForce = Length(vel)/10; + bounceDir = -1*VectorNormalize(vel); + + //ball PhysicsStop(); + //ball PhysicsLaunch(ball.origin, bounceDir*bounceForce); +} + + +function ball_watch_touch_enemy_goal( ) +{ + self endon ( "reset" ); + self endon ( "pickup_object" ); + + enemyGoal = level.ball_goals[util::getotherteam( self.lastCarrierTeam )]; + + while(1) + { + if ( !enemyGoal can_use_goal() ) + { + {wait(.05);}; + continue; + } + + ballVisual = self.visuals[0]; + + distSq = DistanceSquared( ballVisual.origin, enemyGoal.center ); + if ( distSq <= enemyGoal.radiusSq ) + { + self thread ball_touched_goal( enemyGoal ); + return; + } + + if ( isdefined( ballVisual.origin_prev ) ) + { + result = line_intersect_sphere( ballVisual.origin_prev, ballVisual.origin, enemyGoal.center, enemyGoal.trigger.radius ); + if ( result ) + { + self thread ball_touched_goal( enemyGoal ); + return; + } + + } + + {wait(.05);}; + } +} + + +function line_intersect_sphere(line_start, line_end, sphere_center, sphere_radius) +{ + dir = VectorNormalize(line_end - line_start); + + a = VectorDot(dir,(line_start-sphere_center)); + a*=a; + b = (line_start-sphere_center); + b *= b; + c = sphere_radius*sphere_radius; + + return (a-b+c)>=0; +} + + +function ball_touched_goal(goal) +{ + //ball_play_score_fx(goal); -- TODO FX + + if ( isdefined( self.claimPlayer ) ) // We are about to give the ball to another player this frame + return; + + if ( isDefined( self.scoreFrozenUntil ) && self.scoreFrozenUntil > getTime() ) + return; + + self gameobjects::allow_carry( "none" ); + + goal play_goal_score_fx(); + + self.scoreFrozenUntil = getTime() + 10000; + + team = goal.team; + otherTeam = util::getOtherTeam( team ); + + // TODO: Ball ID + globallogic_audio::flush_objective_dialog( "uplink_ball" ); + globallogic_audio::leader_dialog( "uplWeUplinkRemote", otherTeam ); + globallogic_audio::leader_dialog( "uplTheyUplinkRemote", team ); + + globallogic_audio::play_2d_on_team( "mpl_ballcapture_sting_friend", otherTeam ); + globallogic_audio::play_2d_on_team( "mpl_ballcapture_sting_enemy", team ); + + if ( isDefined(self.lastCarrier) ) + { + level thread popups::DisplayTeamMessageToTeam( &"MP_BALL_CAPTURE", self.lastCarrier, team ); + level thread popups::DisplayTeamMessageToTeam( &"MP_BALL_CAPTURE", self.lastCarrier, otherTeam ); + + if( isdefined(self.lastCarrier.pers["throws"]) ) + { + self.lastCarrier.pers["throws"]++; + self.lastCarrier.throws = self.lastCarrier.pers["throws"]; + } + + bbPrint( "mpobjective", "gametime %d objtype %s team %s playerx %d playery %d playerz %d", gettime(), "ball_throw", team, self.lastCarrier.origin ); + self.lastCarrier RecordGameEvent("throw"); + + self.lastCarrier AddPlayerStatWithGameType( "THROWS", 1 ); + scoreevents::processScoreEvent( "ball_capture_throw", self.lastCarrier ); + self.lastCarrierScored = true; + ball_check_assist( self.lastcarrier, false ); + + if(IsDefined(self.killcamEnt) && should_record_final_score_cam( otherTeam, level.throwScore) ) + { +// killcamentity = self.killcamEnt; +// killcamentityindex = killcamentity GetEntityNumber(); +// killcamentitystarttime = killcamentity.birthtime; +// if ( !IsDefined( killcamentitystarttime ) ) +// killcamentitystarttime = 0; +// player = self.lastCarrier; +// goal.killcamEnt.deathTime = GetTime(); + //maps\mp\gametypes\_damage::recordFinalKillCam( 5.0, goal.killcamEnt, player, player GetEntityNumber(), killcamentityindex, killcamentitystarttime, "none", 0, 0, undefined, "score" ); -- TODO killcam + } + + self.lastCarrier challenges::capturedObjective( gettime(), self.trigger ); + self.lastCarrier AddPlayerStatWithGameType( "CAPTURES", 1 ); + } + + if(IsDefined(self.killcamEnt)) + { + self.killcamEnt Unlink(); + } + + self thread upload_ball( goal ); + + ball_give_score( otherTeam, level.throwScore ); +} + +function ball_give_score( team, score ) +{ + level globallogic_score::giveTeamScoreForObjective( team, score ); + + if ( isdefined( game["overtime_round"] ) ) + { + if( game["overtime_round"] == 1 ) + { + // level thread maps\mp\gametypes\_gamelogic::endGame( "overtime_halftime", game[ "end_reason" ][ "switching_sides" ] ); //-- TODO we have callback for endgame + } + else + { + if ( game["ball_overtime_first_winner"] === team ) + { + thread globallogic::endGame( team, game["strings"]["score_limit_reached"] ); + } + + team_score = [[level._getTeamScore]]( team ); + other_team_score = [[level._getTeamScore]]( util::getOtherTeam(team)); + + // if( team_score >= other_team_score) + // level thread maps\mp\gametypes\_gamelogic::endGame( toTeam, game[ "end_reason" ][ "score_limit_reached" ] ); //-- TODO we have callback for endgame + } + } +} + + +function should_record_final_score_cam(team, score_to_add) +{ + //Don't record kill cam if the team scoring would still be losing + team_score = [[level._getTeamScore]]( team ); + other_team_score = [[level._getTeamScore]]( util::getOtherTeam(team)); + + return (team_score + score_to_add) >= other_team_score; +} + +function ball_check_assist( player, wasDunk ) +{ + //Was the player passed to + if(!IsDefined(player.passTime) || !IsDefined(player.passPlayer)) + return; + + //Was it a recent pass + if(player.passTime+3000 < GetTime()) + return; + + scoreevents::processScoreEvent( "ball_capture_assist",player.passPlayer ); +} + +function watch_ball_physics_endons( projectile, timeout ) +{ + projectile endon( "stationary" ); + + ret = self util::waittill_any_timeout( timeout, "reset", "pickup_object", "score_event" ); + if ( ret != "timeout" && isdefined( projectile ) ) + { + projectile notify( "abort_ball_physics" ); + } +} + +function ball_physics_timeout( ) +{ + self endon( "reset" ); + self endon( "pickup_object" ); + self endon( "score_event" ); + + if ( isdefined( self.autoResetTime ) && self.autoResetTime > 15 ) + { + physicsTime = self.autoResetTime; + } + else + { + physicsTime = 15; + } + + if( isdefined( self.projectile ) ) + { + self.projectile endon( "abort_ball_physics" ); + self thread watch_ball_physics_endons( self.projectile, physicsTime ); + timeoutReason = self.projectile util::waittill_any_timeout( physicsTime, "stationary", "abort_ball_physics" ); + iF( !isdefined( timeoutReason ) ) + return; + + if ( timeoutReason == "stationary" ) + { + if ( isdefined( self.autoResetTime ) ) + { + wait self.autoResetTime; + } + } + } + + self reset_ball(); +} + +function ball_physics_out_of_level() +{ + self endon ( "reset" ); + self endon ( "pickup_object" ); + + ball = self.visuals[0]; + + self waittill ( "entity_oob" ); + + self reset_ball(); +} + +function player_update_pass_target(ballObj) +{ + self notify( "update_pass_target" ); + self endon( "update_pass_target" ); + self endon("disconnect"); + self endon("cancel_update_pass_target"); + + //self player_update_pass_target_hudoutline(); -- TODO outlines using the sitrep + //self childthread player_joined_update_pass_target_hudoutline(); + + test_dot = 0.8; + while(1) + { + new_target = undefined; + + if ( !self IsOnLadder() ) + { + playerDir = AnglesToForward( self GetPlayerAngles() ); + playerEye = self GetEye(); + + possible_pass_targets = []; + foreach(target in level.players) + { + if ( self == target ) + continue; + + if ( target.team != self.team ) + continue; + + if ( !isAlive( target ) ) + continue; + + if( !ballObj can_use_ball(target) ) + continue; + + targetEye = target GetEye(); + distSq = DistanceSquared( targetEye, playerEye ); + if ( distSq > ( 1000 * 1000 ) ) + continue; + + dirToTarget = VectorNormalize( targetEye - playerEye ); + dot = VectorDot( playerDir, dirToTarget ); + if ( dot > test_dot ) + { + target.pass_dot = dot; + target.pass_origin = targetEye; + possible_pass_targets[possible_pass_targets.size] = target; + } + } + + //possible_pass_targets = ArraySort(possible_pass_targets, self.origin ); + possible_pass_targets = array::quicksort( possible_pass_targets, &compare_player_pass_dot ); + + foreach(target in possible_pass_targets) + { + if ( SightTracePassed(playerEye, target.pass_origin, false, target ) ) + { + new_target = target; + break; + } + } + } + + self player_set_pass_target(new_target); + + {wait(.05);}; + } +} + +function play_return_vo() +{ + foreach( team in level.teams ) + { + globallogic_audio::play_2d_on_team( "mpl_ballreturn_sting", team ); + + // TODO: Add ball # to objectiveId + globallogic_audio::leader_dialog( "uplReset", team, undefined, "uplink_ball" ); + } +} + +function compare_player_pass_dot(left, right) +{ + return left.pass_dot>=right.pass_dot; +} + +function player_set_pass_target(new_target) +{ + //No Change + if ( IsDefined(self.pass_target) && IsDefined(new_target) && self.pass_target == new_target ) + return; + + if ( !IsDefined(self.pass_target) && !IsDefined(new_target) ) + return; + + self player_clear_pass_target(); + + if(IsDefined(new_target)) + { + offset = ( 0, 0, 80 ); + //self.pass_icon = new_target maps\mp\_entityheadIcons::setHeadIcon( self, "waypoint_ball_pass", offset, 10, 10, false, 0.05, false, true, false, false, "tag_origin" ); -- TODO HUD + + new_target clientfield::set( "passoption", 1 ); + self.pass_target = new_target; + + team_players = []; + foreach(player in level.players) + { + if(player.team == self.team && player != self && player != new_target) + team_players[team_players.size] = player; + } + + self SetBallPassAllowed(true); + } + + //self player_update_pass_target_hudoutline(); -- TODO siterepscan outlines +} + +function player_clear_pass_target() +{ + if(IsDefined(self.pass_icon)) + self.pass_icon Destroy(); + + team_players = []; + foreach(player in level.players) + { + if( player.team == self.team && player != self ) + team_players[team_players.size] = player; + } + + if( isDefined( self.pass_target ) ) + { + self.pass_target clientfield::set( "passoption", 0 ); + } + self.pass_target = undefined; + self SetBallPassAllowed(false); + + //self player_update_pass_target_hudoutline(); -- TODO siterepscan outlines +} + + +function ball_create_start( minStartingBalls ) +{ + ball_starts = getEntArray( "ball_start" ,"targetname"); + + ball_starts = array::randomize( ball_starts ); + foreach(new_start in ball_starts) + { + ballAddStart(new_start.origin); + } + + //Add a default start if none exist + default_ball_height = 30; + if( ball_starts.size==0 ) + { + origin = level.default_ball_origin; + if(!IsDefined(origin)) + { + origin = (0,0,0); + } + + ballAddStart(origin); + } + + //Add extra default starts to support multi ball + add_num = minStartingBalls - level.ball_starts.size; + if( add_num <= 0 ) + { + return; + } + + default_start = level.ball_starts[0].origin; + + near_nodes = GetNodesInRadius(default_start, 200, 20, 50); + near_nodes = array::randomize(near_nodes); + + for ( i = 0; i < add_num && i= 0) ) +// return dist; +// } +// +// // fail safe for bad pathing data +// return distance( from_origin, to_origin ); +//} +// + + +//ball_goal_fx() +//{ +// foreach(team, goal in level.ball_goals) +// { +// goal.score_fx["friendly"] = SpawnFx(getfx("ball_goal_activated_friendly"), goal.origin, (1,0,0)); +// goal.score_fx["enemy"] = SpawnFx(getfx("ball_goal_activated_enemy"), goal.origin, (1,0,0)); +// } +// +// level thread ball_play_fx_joined_team(); +// foreach(player in level.players) +// { +// ball_goal_fx_for_player(player); +// } +//} + + +// +//ball_play_score_fx(goal) +//{ +// //Update who can see what fx +// goal.score_fx["friendly"] Hide(); +// goal.score_fx["enemy"] Hide(); +// +// foreach(player in level.players) +// { +// team = ball_get_view_team(player); +// +// if( team == goal.team ) +// { +// goal.score_fx["friendly"] ShowToPlayer(player); +// } +// else +// { +// goal.score_fx["enemy"] ShowToPlayer(player); +// } +// } +// +// TriggerFx(goal.score_fx["friendly"]); +// TriggerFx(goal.score_fx["enemy"]); +//} +// +//ball_score_sound(scoring_team) +//{ +// ball_play_local_team_sound(scoring_team, "mp_obj_notify_pos_lrg", "mp_obj_notify_neg_lrg"); +//} +// +//ball_play_local_team_sound(team, teamSound, otherTeamSound) +//{ +// otherTeam = getOtherTeam(team); +// foreach(player in level.players) +// { +// if( player.team == team ) +// player PlayLocalSound( teamSound ); +// else if ( player.team == otherTeam ) +// player PlayLocalSound( otherTeamSound ); +// } +//} +// + + +// +//compare_script_index(left, right) +//{ +// return left.script_index<=right.script_index; +//} +// +//ball_on_connect() +//{ +// while ( true ) +// { +// level waittill( "connected", player ); +// +// player.ball_goal_fx = []; +// +// player thread player_on_disconnect(); +// } +//} +// +//player_on_disconnect() // self == player +//{ +// self waittill ("disconnect" ); +// +// player_delete_ball_goal_fx(); +//} +// +//ball_goal_fx_for_player(player) +//{ +// viewTeam = ball_get_view_team(player); +// +// player player_delete_ball_goal_fx(); +// +// foreach(team, goal in level.ball_goals) +// { +// fx_name = ter_op(team == viewTeam, "ball_goal_friendly", "ball_goal_enemy"); +// +// fx = SpawnFXForClient( getfx(fx_name), goal.origin, player, (1,0,0)); +// SetFXKillOnDelete( fx, true ); +// +// player.ball_goal_fx[fx_name] = fx; +// TriggerFX( fx ); +// } +//} +// +//ball_get_view_team(player) +//{ +// viewTeam = player.team; +// if( viewTeam != "allies" && viewTeam != "axis") +// viewTeam = "allies"; +// +// return viewTeam; +//} +// +//player_delete_ball_goal_fx() +//{ +// foreach (effect in self.ball_goal_fx) +// { +// if ( IsDefined( effect ) ) +// effect Delete(); +// } +//} +// +//ball_play_fx_joined_team() +//{ +// while(1) +// { +// level waittill ("joined_team", player ); +// ball_goal_fx_for_player(player); +// } +//} +// + + +///// FX + + +// +//function WatchPhysics() +//{ +// self endon("death"); +// +// while(1) +// { +// self waittill("physics_impact", position, normal, velocity, surface); +// +// //Print("Impact("+GetTime()+") p:" + position + " n:" + velocity + " v:" + velocity + " s:" + surface + "\n"); +// fxID = level._effect["ball_physics_impact"]; +// if(IsDefined(surface) && IsDefined(level._effect["ball_physics_impact_"+surface])) +// fxID = level._effect["ball_physics_impact_"+surface]; +// +// PlayFX(fxID, position, normal ); +// +// wait .3; +// } +//} + +// +//ball_fx_start() +//{ +// if (!self ball_fx_active()) +// { +// ball = self.visuals[0]; +// PlayFXOnTag(getfx("ball_trail"), ball, "tag_origin"); +// PlayFXOnTag(getfx("ball_idle"), ball, "tag_origin"); +// self.ball_fx_active = true; +// } +//} +// +//ball_fx_start_player(player) +//{ +// if(self ball_fx_active()) +// { +// ball = self.visuals[0]; +// PlayFXOnTagForClients(getfx("ball_trail"), ball, "tag_origin", player); +// PlayFXOnTagForClients(getfx("ball_idle"), ball, "tag_origin", player); +// } +//} +// +//ball_fx_stop() +//{ +// if ( self ball_fx_active() ) +// { +// ball = self.visuals[0]; +// StopFXOnTag(getfx("ball_trail"), ball, "tag_origin"); +// killFXOnTag(getfx("ball_idle"), ball, "tag_origin"); +// } +// self.ball_fx_active = false; +//} +// +//ball_fx_active() +//{ +// return IsDefined(self.ball_fx_active) && self.ball_fx_active; +//} + + +//function ball_dont_interpolate() +//{ +// self.visuals[0] DontInterpolate(); +// self.ball_fx_active = false; //DontInterpolate kill fx so need to pretent they were turned off. +//} + + +//// HUD + +// +//function player_update_pass_target_hudoutline() +//{ +// if(!IsDefined(self)) +// return; +// +// self HudOutlineDisableForClients( level.players ); +// foreach(player in level.players) +// { +// player HudOutlineDisableForClient(self); +// } +// +// team_players = []; +// other_team_players = []; +// other_team = getOtherTeam(self.team); +// +// foreach(player in level.players) +// { +// if( player == self ) +// continue; +// +// if( player.team == self.team ) +// team_players[team_players.size] = player; +// else if ( player.team == other_team ) +// other_team_players[other_team_players.size] = player; +// } +// +// if(IsDefined(self.carryObject)) +// { +// foreach(player in team_players) +// { +// isPassTarget = IsDefined(self.pass_target) && (self.pass_target == player); +// if(!isPassTarget) +// player HudOutlineEnableForClient(self, 4, false); +// } +// +// if(IsDefined(self.pass_target)) +// self.pass_target HudOutlineEnableForClient(self, 5, false); +// +// if(other_team_players.size>0) +// self HudOutlineEnableForClients( other_team_players, 0, true); +// +// if(team_players.size>0) +// self HudOutlineEnableForClients( team_players, 5, false ); +// } +//} + +// +//player_joined_update_pass_target_hudoutline() +//{ +// while(1) +// { +// level waittill( "joined_team", player ); +// self player_update_pass_target_hudoutline(); +// } +//} +// \ No newline at end of file diff --git a/mp/gametypes/bwars.csc b/mp/gametypes/bwars.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/bwars.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/bwars.gsc b/mp/gametypes/bwars.gsc new file mode 100644 index 0000000..39d767e --- /dev/null +++ b/mp/gametypes/bwars.gsc @@ -0,0 +1,1239 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\math_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_util; + +/* + Bot Wars + Objective: Capture all the flags by touching them + Map ends: When one team captures all the flags, or time limit is reached + Respawning: No wait / Near teammates + + Level requirements + ------------------ + Spawnpoints: + classname mp_tdm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of owned flags, teammates and + enemies at the time of spawn. Players generally spawn behind their teammates relative to the direction of enemies. + Optionally, give a spawnpoint a script_linkto to specify which flag it "belongs" to (see Flag Descriptors). + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Flags: + classname trigger_radius + targetname flag_primary or flag_secondary + Flags that need to be captured to win. Primary flags take time to capture; secondary flags are instant. + + Flag Descriptors: + classname script_origin + targetname flag_descriptor + Place one flag descriptor close to each flag. Use the script_linkname and script_linkto properties to say which flags + it can be considered "adjacent" to in the level. For instance, if players have a primary path from flag1 to flag2, and + from flag2 to flag3, flag2 would have a flag_descriptor with these properties: + script_linkname flag2 + script_linkto flag1 flag3 + + Set scr_domdebug to 1 to see flag connections and what spawnpoints are considered connected to each flag. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + This sets the nationalities of the teams. Allies can be american, british, or russian. Axis can be german. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["american_soldiertype"] = "normandy"; + game["german_soldiertype"] = "normandy"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + american_soldiertype normandy + british_soldiertype normandy, africa + russian_soldiertype coats, padded + german_soldiertype normandy, africa, winterlight, winterdark +*/ + +/*QUAKED mp_dom_spawn (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Players spawn near their flags at one of these positions.*/ + +/*QUAKED mp_dom_spawn_flag_a (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Players spawn near their flags at one of these positions.*/ + +/*QUAKED mp_dom_spawn_flag_b (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Players spawn near their flags at one of these positions.*/ + +/*QUAKED mp_dom_spawn_flag_c (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Players spawn near their flags at one of these positions.*/ + +/*QUAKED mp_dom_spawn_axis_start (1.0 0.0 1.0) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_dom_spawn_allies_start (0.0 1.0 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + + + + + +#precache( "material", "compass_waypoint_captureneutral" ); +#precache( "material", "compass_waypoint_capture" ); +#precache( "material", "compass_waypoint_defend" ); +#precache( "material", "compass_waypoint_captureneutral_a" ); +#precache( "material", "compass_waypoint_capture_a" ); +#precache( "material", "compass_waypoint_defend_a" ); +#precache( "material", "compass_waypoint_captureneutral_b" ); +#precache( "material", "compass_waypoint_capture_b" ); +#precache( "material", "compass_waypoint_defend_b" ); +#precache( "material", "compass_waypoint_captureneutral_c" ); +#precache( "material", "compass_waypoint_capture_c" ); +#precache( "material", "compass_waypoint_defend_c" ); + +#precache( "string", "OBJECTIVES_DOM" ); +#precache( "string", "OBJECTIVES_DOM_SCORE" ); +#precache( "string", "OBJECTIVES_DOM_HINT" ); +#precache( "string", "MP_CAPTURING_FLAG" ); +#precache( "string", "MP_LOSING_FLAG" ); +//#precache( "string", "MP_LOSING_LAST_FLAG" ); +#precache( "string", "MP_DOM_YOUR_FLAG_WAS_CAPTURED" ); +#precache( "string", "MP_DOM_ENEMY_FLAG_CAPTURED" ); +#precache( "string", "MP_DOM_NEUTRAL_FLAG_CAPTURED" ); + +#precache( "string", "MP_ENEMY_FLAG_CAPTURED_BY" ); +#precache( "string", "MP_NEUTRAL_FLAG_CAPTURED_BY" ); +#precache( "string", "MP_FRIENDLY_FLAG_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_A_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_B_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_C_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_D_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_E_CAPTURED_BY" ); +//#precache( "fx", "_t6/misc/fx_ui_flagbase_gold_t5" );//TODO T7 - contact FX team to get proper replacement + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 1000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerRoundSwitch( 0, 9 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false ); + level.teamBased = false; + level.overrideTeamScore = true; + level.overridePlayerScore = true; + level.onStartGameType =&onStartGameType; + level.onPlayerKilled =&onPlayerKilled; + level.onRoundSwitch =&onRoundSwitch; + level.onPrecacheGameType =&onPrecacheGameType; + level.onEndGame=&onEndGame; + level.onRoundEndGame =&onRoundEndGame; + + gameobjects::register_allowed_gameobject( "dom" ); + + game["dialog"]["gametype"] = "dom_start"; + game["dialog"]["gametype_hardcore"] = "hcdom_start"; + game["dialog"]["offense_obj"] = "cap_start"; + game["dialog"]["defense_obj"] = "cap_start"; + level.lastDialogTime = 0; + + // Sets the scoreboard columns and determines with data is sent across the network + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths" , "captures", "defends"); +} + + +function onPrecacheGameType() +{ +} + + +function onStartGameType() +{ + util::setObjectiveText( "allies", &"OBJECTIVES_DOM" ); + util::setObjectiveText( "axis", &"OBJECTIVES_DOM" ); + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_DOM" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_DOM" ); + } + else + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_DOM_SCORE" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_DOM_SCORE" ); + } + util::setObjectiveHintText( "allies", &"OBJECTIVES_DOM_HINT" ); + util::setObjectiveHintText( "axis", &"OBJECTIVES_DOM_HINT" ); + + level.flagBaseFXid = []; + level.flagBaseFXid[ "allies" ] = "_t6/misc/fx_ui_flagbase_gold_t5";//TODO T7 - contact FX team to get proper replacement + level.flagBaseFXid[ "axis" ] = "_t6/misc/fx_ui_flagbase_gold_t5";//TODO T7 - contact FX team to get proper replacement + + setClientNameMode("auto_change"); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::place_spawn_points( "mp_dom_spawn_allies_start" ); + spawnlogic::place_spawn_points( "mp_dom_spawn_axis_start" ); + + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + level.spawn_all = spawnlogic::get_spawnpoint_array( "mp_dom_spawn" ); + level.spawn_axis_start = spawnlogic::get_spawnpoint_array( "mp_dom_spawn_axis_start" ); + level.spawn_allies_start = spawnlogic::get_spawnpoint_array( "mp_dom_spawn_allies_start" ); + + flagSpawns = spawnlogic::get_spawnpoint_array( "mp_dom_spawn_flag_a" ); + //assert( flagSpawns.size > 0 ); + + level.startPos["allies"] = level.spawn_allies_start[0].origin; + level.startPos["axis"] = level.spawn_axis_start[0].origin; + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + if ( !util::isOneRound() && level.scoreRoundWinBased ) + { + globallogic_score::resetTeamScores(); + } + + updateGametypeDvars(); + bwars_init(); + level thread bwars_update_scores(); + bwars_spawns_update(); +} + +function onEndGame( winningTeam ) +{ +} + +function onRoundEndGame( roundWinner ) +{ + if ( level.scoreRoundWinBased ) + { + [[level._setTeamScore]]( "allies", game["roundswon"]["allies"] ); + [[level._setTeamScore]]( "axis", game["roundswon"]["axis"] ); + } + + axisScore = [[level._getTeamScore]]( "axis" ); + alliedScore = [[level._getTeamScore]]( "allies" ); + + if ( axisScore == alliedScore ) + { + winner = "tie"; + } + else if ( axisScore > alliedScore ) + { + winner = "axis"; + } + else + { + winner = "allies"; + } + + return winner; +} + +function updateGametypeDvars() +{ + level.flagCaptureTime = GetGametypeSetting( "captureTime" ); + level.flagCaptureLPM = math::clamp( GetDvarFloat( "maxFlagCapturePerMinute", 3 ), 0, 10 ); + level.playerCaptureLPM = math::clamp( GetDvarFloat( "maxPlayerCapturePerMinute", 2 ), 0, 10 ); + level.playerCaptureMax = math::clamp( GetDvarFloat( "maxPlayerCapture", 1000 ), 0, 1000 ); + level.playerOffensiveMax = math::clamp( GetDvarFloat( "maxPlayerOffensive", 16 ), 0, 1000 ); + level.playerDefensiveMax = math::clamp( GetDvarFloat( "maxPlayerDefensive", 16 ), 0, 1000 ); +} + +function bwars_init() +{ + level.lastStatus["allies"] = 0; + level.lastStatus["axis"] = 0; + + triggers = GetEntArray( "flag_primary", "targetname" ); + + if ( !triggers.size ) + { + printLn( "^1Not enough domination flags found in level!" ); + callback::abort_level(); + return; + } + + level.bwars_flags = []; + + foreach( trigger in triggers ) + { + visuals = trigger flag_model_init(); + + flag = gameobjects::create_use_object( "neutral", trigger, visuals, ( 0, 0, 100 ) ); + Objective_Delete( flag.objIDAllies ); + gameobjects::release_obj_id( flag.objIDAllies ); + + flag gameobjects::allow_use( "any" ); + flag gameobjects::set_use_time( level.flagCaptureTime ); + flag gameobjects::set_use_text( &"MP_CAPTURING_FLAG" ); + flag gameobjects::set_visible_team( "any" ); + + flag flag_compass_init(); + + flag.onUse =&onUse; + flag.onBeginUse =&onBeginUse; + flag.onUseUpdate =&onUseUpdate; + flag.onEndUse =&onEndUse; + + level.bwars_flags[ level.bwars_flags.size ] = flag; + } +} + +function player_hud_init() +{ + if ( isdefined( self.bwars_hud ) ) + { + return; + } + + self.bwars_hud = []; + + x = -40; + y = 300; + + for( i = 0; i < 4; i++ ) + { + hud = NewClientHudElem( self ); + + hud.alignX = "left"; + hud.alignY = "middle"; + hud.foreground = 1; + hud.fontScale = 1.5; + hud.alpha = .8; + hud.x = x; + hud.y = y; + hud.hidewhendead = 1; + hud.hidewheninkillcam = 1; + + hud.score = NewClientHudElem( self ); + + hud.score.alignX = "left"; + hud.score.alignY = "middle"; + hud.score.foreground = 1; + hud.score.fontScale = 1.5; + hud.score.alpha = .8; + hud.score.x = x + 125; + hud.score.y = y; + hud.score.hidewhendead = 1; + hud.score.hidewheninkillcam = 1; + + self.bwars_hud[ self.bwars_hud.size ] = hud; + + y += 15; + } + + level bwars_scoreboard_update(); +} + +function player_hud_update( names, scores ) +{ + if ( !isdefined( self.bwars_hud ) ) + { + return; + } + + for( i = 0; i < 4; i++ ) + { + self.bwars_hud[i] SetText( names[i] ); + + if ( names[i] == "" ) + { + self.bwars_hud[i].score SetText( "" ); + } + else + { + self.bwars_hud[i].score SetValue( scores[i] ); + } + + if ( names[i] == self.name ) + { + self.bwars_hud[i].color = ( 1, 0.84, 0 ); + self.bwars_hud[i].score.color = ( 1, 0.84, 0 ); + } + else + { + self.bwars_hud[i].color = ( 1, 1, 1 ); + self.bwars_hud[i].score.color = ( 1, 1, 1 ); + } + } +} + +function bubblesort_players() +{ + players = GetPlayers(); + + while( true ) + { + swapped = false; + + for ( i = 1; i < players.size; i++ ) + { + if ( players[i-1].score < players[i].score ) + { + t = players[i-1]; + players[i-1] = players[i]; + players[i] = t; + + swapped = true; + } + } + + if ( !swapped ) + { + break; + } + } + + return players; +} + +function bwars_scoreboard_update() +{ + names = []; + scores = []; + + players = bubblesort_players(); + + for( i = 0; i < 4; i++ ) + { + if ( players.size > i ) + { + names[i] = players[i].name; + scores[i] = players[i].score; + } + else + { + names[i] = ""; + scores[i] = -1; + } + } + + foreach( player in players ) + { + player player_hud_update( names, scores ); + } +} + +function flag_model_init() +{ + visuals = []; + + visuals[0] = spawn( "script_model", self.origin ); + visuals[0].angles = self.angles; + visuals[0] SetModel( "p7_mp_flag_neutral" ); + visuals[0] SetInvisibleToAll(); + + visuals[1] = spawn( "script_model", self.origin ); + visuals[1].angles = self.angles; + visuals[1] SetModel( "p7_mp_flag_neutral" ); + visuals[1] SetVisibleToAll(); + + return visuals; +} + +function flag_model_update() +{ + owner = self gameobjects::get_owner_team(); + + self.visuals[0] SetModel( "p7_mp_flag_allies" ); + self.visuals[0] SetInvisibleToAll(); + self.visuals[0] SetVisibleToPlayer( owner ); + + self.visuals[1] SetModel( "p7_mp_flag_axis" ); + self.visuals[1] SetVisibleToAll(); + self.visuals[1] SetInvisibleToPlayer( owner ); +} + +function flag_compass_init() +{ + self.compass_icons = []; + self.compass_icons[ 0 ] = gameobjects::get_next_obj_id(); + self.compass_icons[ 1 ] = gameobjects::get_next_obj_id(); + + label = self gameobjects::get_label(); + + Objective_Add( self.compass_icons[ 0 ], "active", self.curOrigin ); + Objective_Icon( self.compass_icons[ 0 ], "compass_waypoint_defend" + label ); + Objective_SetInvisibleToAll( self.compass_icons[ 0 ] ); + + Objective_Add( self.compass_icons[ 1 ], "active", self.curOrigin ); + Objective_Icon( self.compass_icons[ 1 ], "compass_waypoint_captureneutral" + label ); + Objective_SetVisibleToAll( self.compass_icons[ 1 ] ); +} + +function flag_compass_update() +{ + label = self gameobjects::get_label(); + owner = self gameobjects::get_owner_team(); + + Objective_Icon( self.compass_icons[ 0 ], "compass_waypoint_defend" + label ); + Objective_State( self.compass_icons[ 0 ], "active" ); + Objective_SetInvisibleToAll( self.compass_icons[ 0 ] ); + Objective_SetVisibleToPlayer( self.compass_icons[ 0 ], owner ); + + Objective_Icon( self.compass_icons[ 1 ], "compass_waypoint_capture" + label ); + Objective_State( self.compass_icons[ 1 ], "active" ); + Objective_SetVisibleToAll( self.compass_icons[ 1 ] ); + Objective_SetInvisibleToPlayer( self.compass_icons[ 1 ], owner ); +} + +function player_world_icon_init() +{ + if ( isdefined( self.bwars_icons ) ) + { + return; + } + + self.bwars_icons = []; + + foreach( flag in level.bwars_flags ) + { + icon = NewClientHudElem( self ); + + icon.flag = flag; + icon.x = flag.curOrigin[0]; + icon.y = flag.curOrigin[1]; + icon.z = flag.curOrigin[2] + 100; + icon.fadeWhenTargeted = true; + icon.archived = false; + icon.alpha = 1; + + self.bwars_icons[ self.bwars_icons.size ] = icon; + } + + self player_world_icon_update(); +} + +function player_world_icon_update() +{ + assert( isdefined( self.bwars_icons ) ); + + foreach( icon in self.bwars_icons ) + { + label = icon.flag gameobjects::get_label(); + owner = icon.flag gameobjects::get_owner_team(); + + if ( IsString( owner ) && owner == "neutral" ) + { + icon SetWaypoint( true, "waypoint_captureneutral" + label ); + } + else if ( owner == self ) + { + icon SetWaypoint( true, "waypoint_defend" + label ); + } + else + { + icon SetWaypoint( true, "waypoint_capture" + label ); + } + } +} + +function world_icon_update() +{ + players = GetPlayers(); + + foreach( player in players ) + { + player player_world_icon_update(); + } +} + +function getUnownedFlagNearestStart( team, excludeFlag ) +{ + best = undefined; + bestdistsq = undefined; + for ( i = 0; i < level.flags.size; i++ ) + { + flag = level.flags[i]; + + if ( flag getFlagTeam() != "neutral" ) + continue; + + distsq = distanceSquared( flag.origin, level.startPos[team] ); + if ( (!isdefined( excludeFlag ) || flag != excludeFlag) && (!isdefined( best ) || distsq < bestdistsq) ) + { + bestdistsq = distsq; + best = flag; + } + } + return best; +} + +function onBeginUse( player ) +{ +} + +function onUseUpdate( team, progress, change ) +{ +} + +function statusDialog( dialog, team ) +{ + time = getTime(); + if ( getTime() < level.lastStatus[team] + 6000 ) + return; + + thread delayedLeaderDialog( dialog, team ); + level.lastStatus[team] = getTime(); +} + +function statusDialogEnemies( dialog, friend_team ) +{ + foreach ( team in level.teams ) + { + if ( team == friend_team ) + continue; + + statusDialog( dialog, team ); + } +} + +function onEndUse( team, player, success ) +{ + +} + + +function resetFlagBaseEffect() +{ +/* + // once these get setup we never change them + if ( isdefined( self.baseeffect ) ) + return; + + team = self gameobjects::get_owner_team(); + + if ( team != "axis" && team != "allies" ) + return; + + fxid = level.flagBaseFXid[ team ]; + + self.baseeffect = spawnFx( fxid, self.baseeffectpos, self.baseeffectforward, self.baseeffectright ); + triggerFx( self.baseeffect ); +*/ +} + +function onUse( player ) +{ + self gameobjects::set_owner_team( player ); + self gameobjects::allow_use( "enemy" ); + + self flag_compass_update(); + self flag_model_update(); + level world_icon_update(); + level bwars_spawns_update(); + +// self resetFlagBaseEffect(); + + //level.useStartSpawns = false; +/* + string = &""; + switch ( label ) + { + case "_a": + string = &"MP_DOM_FLAG_A_CAPTURED_BY"; + break; + case "_b": + string = &"MP_DOM_FLAG_B_CAPTURED_BY"; + break; + case "_c": + string = &"MP_DOM_FLAG_C_CAPTURED_BY"; + break; + case "_d": + string = &"MP_DOM_FLAG_D_CAPTURED_BY"; + break; + case "_e": + string = &"MP_DOM_FLAG_E_CAPTURED_BY"; + break; + default: + break; + } + assert ( string != &"" ); + + // Copy touch list so there aren't any threading issues + touchList = []; + touchKeys = GetArrayKeys( self.touchList[team] ); + for ( i = 0 ; i < touchKeys.size ; i++ ) + touchList[touchKeys[i]] = self.touchList[team][touchKeys[i]]; + thread give_capture_credit( touchList, string ); + + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "dom_capture", label, team, player.origin ); + + if ( oldTeam == "neutral" ) + { + thread util::printAndSoundOnEveryone( team, undefined, &"", &"", "mp_war_objective_taken", undefined, "" ); + + thread sound::play_on_players( "mus_dom_captured"+"_"+level.teamPostfix[team] ); + if ( getTeamFlagCount( team ) == level.flags.size ) + { + + statusDialog( "secure_all", team ); + statusDialogEnemies( "lost_all", team ); + } + else + { + statusDialog( "secured"+self.label, team ); + statusDialogEnemies( "lost"+self.label, team ); + } + } + else + { + thread util::printAndSoundOnEveryone( team, oldTeam, &"", &"", "mp_war_objective_taken", "mp_war_objective_lost", "" ); + +// thread delayedLeaderDialogBothTeams( "obj_lost", oldTeam, "obj_taken", team ); + +// thread sound::play_on_players( "mus_dom_captured"+"_"+level.teamPostfix[team] ); + if ( getTeamFlagCount( team ) == level.flags.size ) + { + + statusDialog( "secure_all", team ); + statusDialog( "lost_all", oldTeam ); + } + else + { + statusDialog( "secured"+self.label, team ); + + statusDialog( "lost"+self.label, oldTeam ); + } + + level.bestSpawnFlag[ oldTeam ] = self.levelFlag; + } + + if ( dominated_challenge_check() ) + { + challenges::dominated( team ); + } + self update_spawn_influencers( team ); + level dom::change_dom_spawns(); +*/ +} + +function give_capture_credit( touchList, string ) +{ + wait .05; + util::WaitTillSlowProcessAllowed(); + + self updateCapsPerMinute(); + + players = getArrayKeys( touchList ); + for ( i = 0; i < players.size; i++ ) + { + player_from_touchlist = touchList[players[i]].player; + player_from_touchlist updateCapsPerMinute(); + if ( !isScoreBoosting( player_from_touchlist, self ) ) + { + //scoreevents::processScoreEvent( "position_secure", player_from_touchlist ); TFLAME 8/28 - We shouldn't need this event for bot wars + if( isdefined(player_from_touchlist.pers["captures"]) ) + { + player_from_touchlist.pers["captures"]++; + player_from_touchlist.captures = player_from_touchlist.pers["captures"]; + } + + demo::bookmark( "event", gettime(), player_from_touchlist ); + player_from_touchlist AddPlayerStatWithGameType( "CAPTURES", 1 ); + + } + + level thread popups::DisplayTeamMessageToAll( string, player_from_touchlist ); + } +} + +function delayedLeaderDialog( sound, team ) +{ + wait .1; + util::WaitTillSlowProcessAllowed(); + + globallogic_audio::leader_dialog( sound, team ); +} +function delayedLeaderDialogBothTeams( sound1, team1, sound2, team2 ) +{ + wait .1; + util::WaitTillSlowProcessAllowed(); + +// Eran: this function is missing. +// globallogic_audio::leaderdialogBothTeams( sound1, team1, sound2, team2 ); +} + + +function bwars_update_scores() +{ + while ( !level.gameEnded ) + { + foreach( flag in level.bwars_flags ) + { + owner = flag gameobjects::get_owner_team(); + + if ( IsPlayer( owner ) ) + { + owner.score += 1; + } + } + + level bwars_scoreboard_update(); + + players = GetPlayers(); + + foreach( player in players ) + { + player globallogic::checkScoreLimit(); + } + + wait ( 5.0 ); + hostmigration::waitTillHostMigrationDone(); + } +} + +function onRoundSwitch() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + game["switchedsides"] = !game["switchedsides"]; + + if ( level.cumulativeRoundScore == false ) + { + [[level._setTeamScore]]( "allies", game["roundswon"]["allies"] ); + [[level._setTeamScore]]( "axis", game["roundswon"]["axis"] ); + } +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ +} + +function getTeamFlagCount( team ) +{ + score = 0; + for (i = 0; i < level.flags.size; i++) + { + if ( level.domFlags[i] gameobjects::get_owner_team() == team ) + score++; + } + return score; +} + +function getFlagTeam() +{ + return self.useObj gameobjects::get_owner_team(); +} + +function getBoundaryFlags() +{ + // get all flags which are adjacent to flags that aren't owned by the same team + bflags = []; + for (i = 0; i < level.flags.size; i++) + { + for (j = 0; j < level.flags[i].adjflags.size; j++) + { + if (level.flags[i].useObj gameobjects::get_owner_team() != level.flags[i].adjflags[j].useObj gameobjects::get_owner_team() ) + { + bflags[bflags.size] = level.flags[i]; + break; + } + } + } + + return bflags; +} + +function getBoundaryFlagSpawns(team) +{ + spawns = []; + + bflags = getBoundaryFlags(); + for (i = 0; i < bflags.size; i++) + { + if (isdefined(team) && bflags[i] getFlagTeam() != team) + continue; + + for (j = 0; j < bflags[i].nearbyspawns.size; j++) + spawns[spawns.size] = bflags[i].nearbyspawns[j]; + } + + return spawns; +} + +function getSpawnsBoundingFlag( avoidflag ) +{ + spawns = []; + + for (i = 0; i < level.flags.size; i++) + { + flag = level.flags[i]; + if ( flag == avoidflag ) + continue; + + isbounding = false; + for (j = 0; j < flag.adjflags.size; j++) + { + if ( flag.adjflags[j] == avoidflag ) + { + isbounding = true; + break; + } + } + + if ( !isbounding ) + continue; + + for (j = 0; j < flag.nearbyspawns.size; j++) + spawns[spawns.size] = flag.nearbyspawns[j]; + } + + return spawns; +} + +// gets an array of all spawnpoints which are near flags that are +// owned by the given team, or that are adjacent to flags owned by the given team. +function getOwnedAndBoundingFlagSpawns(team) +{ + spawns = []; + + for (i = 0; i < level.flags.size; i++) + { + if ( level.flags[i] getFlagTeam() == team ) + { + // add spawns near this flag + for (s = 0; s < level.flags[i].nearbyspawns.size; s++) + spawns[spawns.size] = level.flags[i].nearbyspawns[s]; + } + else + { + for (j = 0; j < level.flags[i].adjflags.size; j++) + { + if ( level.flags[i].adjflags[j] getFlagTeam() == team ) + { + // add spawns near this flag + for (s = 0; s < level.flags[i].nearbyspawns.size; s++) + spawns[spawns.size] = level.flags[i].nearbyspawns[s]; + break; + } + } + } + } + + return spawns; +} + +// gets an array of all spawnpoints which are near flags that are +// owned by the given team +function getOwnedFlagSpawns(team) +{ + spawns = []; + + for (i = 0; i < level.flags.size; i++) + { + if ( level.flags[i] getFlagTeam() == team ) + { + // add spawns near this flag + for (s = 0; s < level.flags[i].nearbyspawns.size; s++) + spawns[spawns.size] = level.flags[i].nearbyspawns[s]; + } + } + + return spawns; +} + +function flagSetup() +{ + maperrors = []; + descriptorsByLinkname = []; + + // (find each flag_descriptor object) + descriptors = getentarray("flag_descriptor", "targetname"); + + flags = level.flags; + + for (i = 0; i < level.domFlags.size; i++) + { + closestdist = undefined; + closestdesc = undefined; + for (j = 0; j < descriptors.size; j++) + { + dist = distance(flags[i].origin, descriptors[j].origin); + if (!isdefined(closestdist) || dist < closestdist) { + closestdist = dist; + closestdesc = descriptors[j]; + } + } + + if (!isdefined(closestdesc)) { + maperrors[maperrors.size] = "there is no flag_descriptor in the map! see explanation in dom.gsc"; + break; + } + if (isdefined(closestdesc.flag)) { + maperrors[maperrors.size] = "flag_descriptor with script_linkname \"" + closestdesc.script_linkname + "\" is nearby more than one flag; is there a unique descriptor near each flag?"; + continue; + } + flags[i].descriptor = closestdesc; + closestdesc.flag = flags[i]; + descriptorsByLinkname[closestdesc.script_linkname] = closestdesc; + } + + if (maperrors.size == 0) + { + // find adjacent flags + for (i = 0; i < flags.size; i++) + { + if (isdefined(flags[i].descriptor.script_linkto)) + adjdescs = strtok(flags[i].descriptor.script_linkto, " "); + else + adjdescs = []; + for (j = 0; j < adjdescs.size; j++) + { + otherdesc = descriptorsByLinkname[adjdescs[j]]; + if (!isdefined(otherdesc) || otherdesc.targetname != "flag_descriptor") { + maperrors[maperrors.size] = "flag_descriptor with script_linkname \"" + flags[i].descriptor.script_linkname + "\" linked to \"" + adjdescs[j] + "\" which does not exist as a script_linkname of any other entity with a targetname of flag_descriptor (or, if it does, that flag_descriptor has not been assigned to a flag)"; + continue; + } + adjflag = otherdesc.flag; + if (adjflag == flags[i]) { + maperrors[maperrors.size] = "flag_descriptor with script_linkname \"" + flags[i].descriptor.script_linkname + "\" linked to itself"; + continue; + } + flags[i].adjflags[flags[i].adjflags.size] = adjflag; + } + } + } + + // assign each spawnpoint to nearest flag + spawnpoints = spawnlogic::get_spawnpoint_array( "mp_dom_spawn" ); + for (i = 0; i < spawnpoints.size; i++) + { + if (isdefined(spawnpoints[i].script_linkto)) { + desc = descriptorsByLinkname[spawnpoints[i].script_linkto]; + if (!isdefined(desc) || desc.targetname != "flag_descriptor") { + maperrors[maperrors.size] = "Spawnpoint at " + spawnpoints[i].origin + "\" linked to \"" + spawnpoints[i].script_linkto + "\" which does not exist as a script_linkname of any entity with a targetname of flag_descriptor (or, if it does, that flag_descriptor has not been assigned to a flag)"; + continue; + } + nearestflag = desc.flag; + } + else { + nearestflag = undefined; + nearestdist = undefined; + for (j = 0; j < flags.size; j++) + { + dist = distancesquared(flags[j].origin, spawnpoints[i].origin); + if (!isdefined(nearestflag) || dist < nearestdist) + { + nearestflag = flags[j]; + nearestdist = dist; + } + } + } + nearestflag.nearbyspawns[nearestflag.nearbyspawns.size] = spawnpoints[i]; + } + + if (maperrors.size > 0) + { + /# + println("^1------------ Map Errors ------------"); + for(i = 0; i < maperrors.size; i++) + println(maperrors[i]); + println("^1------------------------------------"); + + util::error("Map errors. See above"); + #/ + callback::abort_level(); + + return; + } +} + +function createFlagSpawnInfluencers() +{ + ss = level.spawnsystem; + + for (flag_index = 0; flag_index < level.flags.size; flag_index++) + { + if ( level.domFlags[flag_index] == self ) + break; + } + + // domination: owned flag influencers + self.owned_flag_influencer = self spawning::create_influencer( "dom_friendly", self.trigger.origin, 0 ); + + // domination: un-owned inner flag influencers + self.neutral_flag_influencer = self spawning::create_influencer( "dom_neutral", self.trigger.origin, 0 ); + + // domination: enemy flag influencers + self.enemy_flag_influencer = self spawning::create_influencer( "dom_enemy", self.trigger.origin, 0 ); + + // default it to neutral + self update_spawn_influencers("neutral"); +} + +function update_spawn_influencers( team ) +{ + assert(isdefined(self.neutral_flag_influencer)); + assert(isdefined(self.owned_flag_influencer)); + assert(isdefined(self.enemy_flag_influencer)); + + if ( team == "neutral" ) + { + EnableInfluencer(self.neutral_flag_influencer, true); + EnableInfluencer(self.owned_flag_influencer, false); + EnableInfluencer(self.enemy_flag_influencer, false); + } + else + { + EnableInfluencer(self.neutral_flag_influencer, false); + EnableInfluencer(self.owned_flag_influencer, true); + EnableInfluencer(self.enemy_flag_influencer, true); + + SetInfluencerTeammask(self.owned_flag_influencer, util::getTeamMask(team) ); + SetInfluencerTeammask(self.enemy_flag_influencer, util::getOtherTeamsMask(team) ); + } +} + +function bwars_spawns_update() +{ + spawnlogic::clear_spawn_points(); + spawnlogic::add_spawn_points( "axis", "mp_dom_spawn" ); + spawnlogic::add_spawn_points( "allies", "mp_dom_spawn" ); + + spawning::updateAllSpawnPoints(); +} + +function dominated_challenge_check() +{ + num_flags = level.flags.size; + allied_flags = 0; + axis_flags = 0; + + for ( i = 0 ; i < num_flags ; i++ ) + { + flag_team = level.flags[i] getFlagTeam(); + + if ( flag_team == "allies" ) + { + allied_flags++; + } + else if ( flag_team == "axis" ) + { + axis_flags++; + } + else + { + return false; + } + + if ( ( allied_flags > 0 ) && ( axis_flags > 0 ) ) + return false; + } + + return true; +} +//This function checks to see if one team owns all three flags +function dominated_check() +{ + num_flags = level.flags.size; + allied_flags = 0; + axis_flags = 0; + + for ( i = 0 ; i < num_flags ; i++ ) + { + flag_team = level.flags[i] getFlagTeam(); + + if ( flag_team == "allies" ) + { + allied_flags++; + } + else if ( flag_team == "axis" ) + { + axis_flags++; + } + + if ( ( allied_flags > 0 ) && ( axis_flags > 0 ) ) + return false; + } + + return true; +} + +function updateCapsPerMinute() +{ + if ( !isdefined( self.capsPerMinute ) ) + { + self.numCaps = 0; + self.capsPerMinute = 0; + } + + self.numCaps++; + + minutesPassed = globallogic_utils::getTimePassed() / ( 60 * 1000 ); + + // players use the actual time played + if ( IsPlayer( self ) && isdefined(self.timePlayed["total"]) ) + minutesPassed = self.timePlayed["total"] / 60; + + self.capsPerMinute = self.numCaps / minutesPassed; + if ( self.capsPerMinute > self.numCaps ) + self.capsPerMinute = self.numCaps; +} + +function isScoreBoosting( player, flag ) +{ + if ( !level.rankedMatch ) + return false; + + if ( player.capsPerMinute > level.playerCaptureLPM ) + return true; + + if ( flag.capsPerMinute > level.flagCaptureLPM ) + return true; + + if ( player.numCaps > level.playerCaptureMax ) + return true; + + return false; +} \ No newline at end of file diff --git a/mp/gametypes/clean.csc b/mp/gametypes/clean.csc new file mode 100644 index 0000000..4581c53 --- /dev/null +++ b/mp/gametypes/clean.csc @@ -0,0 +1,157 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\util_shared; + + + + + + + + + //0.15 + //0.15 + + +#precache( "client_fx", "ui/fx_stockpile_drop_marker" ); + + +#precache( "client_fx", "ui/fx_stockpile_player_marker" ); + +function main() +{ + clientfield::register( "scriptmover", "taco_flag", 12000, 2, "int", &taco_flag_changed, false, false ); + clientfield::register( "allplayers", "taco_carry", 12000, 1, "int", &taco_carry_changed, false, false ); + // TODO: If we add quantity with the carry indicator, remove the clientuimodel field and set it on the UI model from the carried value +} + +// Drop Effects +//======================================== + +function taco_flag_changed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self notify( "stopbounce" ); + + if ( isdefined( self.effectAnchor ) ) + { + self.effectAnchor Unlink(); + self.effectAnchor.origin = self.origin; + } + + self kill_drop_fx( localClientNum ); + + if ( newVal != 0 ) + { + if ( !isdefined( self.effectAnchor ) ) + { + self.effectAnchor = Spawn( localClientNum, self.origin, "script_model" ); + self.effectAnchor SetModel( "tag_origin" ); + + self thread drop_shutdown_cleanup( localClientNum ); + } + + self.tacoFx = PlayFXOnTag( localClientNum, "ui/fx_stockpile_drop_marker", self.effectAnchor, "tag_origin" ); + + SetFxTeam( localClientNum, self.tacoFx, self.team ); + } + + if ( newVal == 1 ) + { + self.effectAnchor LinkTo( self ); + } + else if ( newVal == 2 ) + { + self thread bounce_effect( localClientNum ); + } +} + +function drop_shutdown_cleanup( localClientNum ) +{ + self waittill( "entityshutdown" ); + + self kill_drop_fx( localClientNum ); + + self.effectAnchor Delete(); + self.effectAnchor = undefined; +} + +function kill_drop_fx( localClientNum ) +{ + if ( isdefined( self.tacoFx ) ) + { + KillFx( localClientNum, self.tacoFx ); + self.tacoFx = undefined; + } +} + +function bounce_effect( localClientNum ) +{ + self endon( "stopbounce" ); + self endon( "entityshutdown" ); + + topPos = self.origin + (0,0,12); + bottomPos = self.origin; + + while( true ) + { + self.effectAnchor MoveTo( topPos, 0.5, 0, 0 ); + + self.effectAnchor waittill( "movedone" ); + + self.effectAnchor MoveTo( bottomPos, 0.5, 0, 0 ); + + self.effectAnchor waittill( "movedone" ); + } +} + +// Carry Effects +//======================================== + +function taco_carry_changed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self kill_carry_fx( localClientNum ); + + // Don't play effects on the local player as the trail can get in their eyes + if ( newVal && GetLocalPlayer( localClientNum ) != self ) + { + if ( !isdefined( self.effectAnchor ) ) + { + self util::waittill_dobj( localClientNum ); + + neckPos = self GetTagOrigin( "j_neck" ); + neckAngles = self GetTagAngles( "j_neck" ); + + self.effectAnchor = Spawn( localClientNum, neckPos, "script_model" ); + self.effectAnchor SetModel( "tag_origin" ); + + self.effectAnchor.angles = neckAngles; + + self.effectAnchor LinkTo( self, "j_neck" ); + + self thread carry_shutdown_cleanup( localClientNum ); + } + + self.carryFx = PlayFxOnTag( localClientNum, "ui/fx_stockpile_player_marker", self.effectAnchor, "tag_origin" ); + SetFxTeam( localClientNum, self.carryFx, self.team ); + } +} + +function carry_shutdown_cleanup( localClientNum ) +{ + self waittill( "entityshutdown" ); + + self kill_carry_fx( localClientNum ); + + self.effectAnchor Delete(); + self.effectAnchor = undefined; +} + +function kill_carry_fx( localClientNum ) +{ + if ( isdefined( self.carryFx ) ) + { + KillFx( localClientNum, self.carryFx ); + self.carryFx = undefined; + } +} \ No newline at end of file diff --git a/mp/gametypes/clean.gsc b/mp/gametypes/clean.gsc new file mode 100644 index 0000000..3cf3fe4 --- /dev/null +++ b/mp/gametypes/clean.gsc @@ -0,0 +1,1214 @@ +#using scripts\shared\clientfield_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\lui_shared; +#using scripts\shared\math_shared; +#using scripts\shared\_oob; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\teams\_teams; +#using scripts\mp\_util; + +/* + Stockpile: Kill enemies and pick up objects. + Deposit objects at the objective + + PLEASE NOTE: + TACO stands for Totally Arbitrary Collectible Object + + Level requirementss + ------------------ + Spawnpoints: + + Spectator Spawnpoints: + classname mp_global_intermission + + Deposit Points: + classname trigger_multiple + --- Not Used --- + targetname clean_objective_scatter_trig + targetname clean_objective_center_trig + targetname clean_objective_base_trig + +*/ + +#precache( "string", "OBJECTIVES_CLEAN" ); +#precache( "string", "OBJECTIVES_CLEAN_SCORE" ); +#precache( "string", "OBJECTIVES_CLEAN_HINT" ); + +#precache( "string", "MP_CLEAN_DEPOSIT" ); + +#precache( "objective", "clean_deposit" ); +#precache( "objective", "clean_carry" ); + + +#precache( "fx", "ui/fx_stockpile_deposit_point" ); + + +#precache( "fx", "ui/fx_stockpile_deposit_point_end" ); + + +#precache( "fx", "ui/fx_stockpile_player_marker" ); + + + + + + + + + + + + + + + + +// 0 horizontal, negative angles go up + + + + + +#precache( "material", "t7_hud_waypoints_stockpile_dropped" ); + + + + + +// Sound Aliases + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function autoexec __init__sytem__() { system::register("clean",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "clientuimodel", "hudItems.cleanCarryCount", 12000, 4, "int" ); + clientfield::register( "clientuimodel", "hudItems.cleanCarryFull", 12000, 1, "int" ); + clientfield::register( "scriptmover", "taco_flag", 12000, 2, "int" ); + clientfield::register( "allplayers", "taco_carry", 12000, 1, "int" ); +} + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 50000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundSwitch( 0, 9 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.tacos = []; + + level.teamScorePerKill = GetGametypeSetting( "teamScorePerKill" ); + level.teamScorePerCleanDeposit = GetGametypeSetting( "teamScorePerCleanDeposit" ); + level.cleanDepositOfflineTime = 0; + level.cleanDepositOnlineTime = GetGametypeSetting( "cleanDepositOnlineTime" ); + level.cleanDepositRotation = GetGametypeSetting( "cleanDepositRotation" ); + + level.scoreRoundWinBased = true; + level.teamBased = true; + level.overrideTeamScore = true; + + level.onPrecacheGameType =&onPrecacheGameType; + level.onStartGameType =&onStartGameType; + + level.onSpawnPlayer =&onSpawnPlayer; + + level.onPlayerKilled =&onPlayerKilled; + + level.onRoundSwitch =&onRoundSwitch; + level.onRoundEndGame =&onRoundEndGame; + + level.cleanDropWeapon = GetWeapon( "clean_drop" ); + + // Sets the scoreboard columns and determines with data is sent across the network + + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "cleandeposits", "cleandenies", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "cleandeposits", "cleandenies" ); + + globallogic_audio::set_leader_gametype_dialog ( "startStockpile", "hcStartStockpile", "stockpileOrders", "stockpileOrders" ); + + gameobjects::register_allowed_gameobject( level.gameType ); +} + +function onPrecacheGameType() +{ + +} + + +function onStartGameType() +{ + if ( !isdefined( game["switchedsides"] ) ) + { + game["switchedsides"] = false; + } + + globallogic_score::resetTeamScores(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + foreach( team in level.teams ) + { + util::setObjectiveText( team, &"OBJECTIVES_CLEAN" ); + util::setObjectiveHintText( team, &"OBJECTIVES_CLEAN_HINT" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( team, &"OBJECTIVES_CLEAN" ); + } + else + { + util::setObjectiveScoreText( team, &"OBJECTIVES_CLEAN_SCORE" ); + } + + spawnlogic::add_spawn_points( team, "mp_tdm_spawn" ); + spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) ); + } + + spawning::updateAllSpawnPoints(); + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array( spawning::getTDMStartSpawnName(team) ); + } + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + // Create all the drops at once + for( i = 0; i < 60; i++ ) + { + level.tacos[i] = create_taco(); + } + + level setup_deposit_hubs(); + + level thread rotate_deposit_hubs(); + level thread game_ended_cleanup(); + +/# + level.activeDrops = 0; + level.mostActiveDrops = 0; + level.expiredDrops = 0; + level.precycledDrops = 0; + level.failedDrops = 0; + + //level thread debug_print(); +#/ +} + +function game_ended_cleanup() +{ + level waittill( "game_ended" ); + + foreach( taco in level.tacos ) + { + if ( taco clientfield::get( "taco_flag" ) > 0 ) + { + taco clientfield::set( "taco_flag", 0 ); + } + } + + foreach( depositHub in level.cleanDepositHubs ) + { + depositHub StopLoopSound( ); + + if ( isdefined( depositHub.baseEffect ) ) + { + depositHub.baseEffect delete(); + } + } + + foreach( player in level.players ) + { + player clientfield::set( "taco_carry", 0 ); + } +} + +/# +function debug_print() +{ + while( 1 ) + { + IPrintLn( "Active: " + level.activeDrops ); + IPrintLn( "Most Active: " + level.mostActiveDrops ); + IPrintLn( "Timed Out: " + level.expiredDrops ); + IPrintLn( "Precycled: " + level.precycledDrops ); + IPrintLn( "Failed: " + level.failedDrops ); + + wait 5; + } +} +#/ + +function onSpawnPlayer( predictedSpawn ) +{ + if ( level.useStartSpawns && !level.inGracePeriod ) + { + level.useStartSpawns = false; + } + + self.lastDepositTime = 0; + self.depositStreak = 0; + self.lastCarryFullTime = 0; + self.carriedTacos = 0; + + self clientfield::set_player_uimodel( "hudItems.cleanCarryCount", 0 ); + + spawning::onSpawnPlayer(predictedSpawn); + + self thread watch_camo_on(); + self thread watch_camo_off(); + + self thread watch_clone_on(); + self thread watch_clone_off(); + + //self thread watch_vehicle_interaction(); + + self.same_hub_deposit_count = 0; +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + player = self; + + if ( isPlayer( attacker ) && attacker.team != self.team ) + { + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + { + attacker globallogic_score::giveTeamScoreForObjective( attacker.team, level.teamScorePerKill ); + } + } + + if ( isdefined( attacker ) && IsPlayer( attacker ) && attacker.team != self.team ) + { + if ( isdefined( player ) && isdefined( player.carriedTacos ) ) + { + if ( player.carriedTacos >= 5 ) + { + scoreevents::processScoreEvent( "clean_kill_enemy_carrying_tacos", attacker ); + } + } + + taco = get_droppable_taco( ); + + if ( isdefined( taco ) ) + { + yawAngle = RandomInt( 360 ); + + taco drop_taco( self, attacker, undefined, yawAngle ); + } + + self thread drop_carried_tacos( attacker, yawAngle ); + } +} + +function watch_camo_on() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while ( 1 ) + { + self flagsys::wait_till( "camo_suit_on" ); + + self clientfield::set( "taco_carry", 0 ); + + self flagsys::wait_till_clear( "camo_suit_on" ); + } +} + +function watch_camo_off() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while ( 1 ) + { + self flagsys::wait_till( "camo_suit_on" ); + self flagsys::wait_till_clear( "camo_suit_on" ); + + self update_taco_carry(); + } +} + +function watch_clone_on() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while ( 1 ) + { + self flagsys::wait_till( "clone_activated" ); + + self clientfield::set( "taco_carry", 0 ); + + self flagsys::wait_till_clear( "clone_activated" ); + } +} + +function watch_clone_off() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while ( 1 ) + { + self flagsys::wait_till( "clone_activated" ); + self flagsys::wait_till_clear( "clone_activated" ); + + self update_taco_carry(); + } +} + +function watch_vehicle_interaction() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while( 1 ) + { + self util::waittill_any( "enter_vehicle", "exit_vehicle" ); + self update_taco_carry(); + } +} + +function update_taco_carry() +{ + if ( self.carriedTacos > 0 && + !self IsInVehicle() && + !self flagsys::get( "camo_suit_on" ) && + !self flagsys::get( "clone_activated" ) ) + { + self clientfield::set( "taco_carry", 1 ); + } + else + { + self clientfield::set( "taco_carry", 0 ); + } +} + +function get_droppable_taco() +{ + oldestTaco = undefined; + + foreach( taco in level.tacos ) + { + // Look for a dormant one + if ( taco.interactTeam == "none" ) + { + return taco; + } + + // Don't use ones still flying through the air + if ( isdefined( taco.physicsAnchor ) ) + { + continue; + } + + // Track the oldest + if ( !isdefined( oldestTaco ) || taco.dropTime < oldestTaco.dropTime ) + { + oldestTaco = taco; + } + } + + // Return the oldest taco if it wasn't dropped this frame + if ( isdefined( oldestTaco ) && oldestTaco.dropTime != GetTime() ) + { +/# + level.precycledDrops++; +#/ + oldestTaco reset_taco(); + return oldestTaco; + } + +/# + level.failedDrops++; +#/ + + return undefined; +} + +function create_taco( ) +{ + visuals = []; + + trigger = spawn( "trigger_radius", (0,0,0), 0, 32, 32 ); + + taco = gameobjects::create_use_object( "any", trigger, visuals, undefined, &"" ); + taco NotSolid(); + taco Ghost(); + + taco gameobjects::set_use_time( 0 ); + taco.onUse = &on_use_taco; + + Objective_Add( taco.objectiveId, "invisible", (0,0,0) ); + Objective_Icon( taco.objectiveId, "t7_hud_waypoints_stockpile_dropped" ); + + return taco; +} + +function drop_taco( victim, attacker, pos, yawAngle ) +{ +/# + level.activeDrops++; + level.mostActiveDrops = Max( level.mostActiveDrops, level.activeDrops ); + +#/ + if(!isdefined(yawAngle))yawAngle=RandomInt( 360 ); + if(!isdefined(pos))pos=victim.origin + (0,0,40); + + self.dropTime = GetTime(); + + self.team = victim.team; + + self.victim = victim; + self.victimTeam = victim.team; + self.attacker = attacker; + self.attackerTeam = attacker.team; + + self.trigger.origin = pos; + + self Show(); + self clientfield::set( "taco_flag", 1 ); + + self PlayLoopSound( "mpl_fracture_core_loop" ); + + self DontInterpolate(); + + // Show dropped objectives to the attacking player + Objective_SetInvisibleToAll( self.objectiveId ); + Objective_Team( self.objectiveId, attacker.team ); + Objective_State( self.objectiveId, "active" ); + Objective_SetColor( self.objectiveId, &"EnemyOrange" ); + Objective_SetVisibleToPlayer( self.objectiveId, attacker ); + + self gameobjects::allow_use( "any" ); + + if ( isdefined( self.physicsAnchor ) ) + { + self.physicsAnchor Delete(); + } + + dropAngles = ( -70, yawAngle, 0 ); + + force = AnglesToForward( dropAngles ) * RandomFloatRange( GetDvarFloat( "dropMin", 220 ), GetDvarFloat( "dropMax", 300 ) ); + + self.physicsAnchor = victim MagicMissile( level.cleanDropWeapon, pos, force ); + self.physicsAnchor Hide(); + self.physicsAnchor NotSolid(); + + self thread update_taco_position(); + self thread bounce_when_stationary(); + self thread timeout_wait(); +} + +function update_taco_position() +{ + level endon( "game_ended" ); + self endon( "reset" ); + self.physicsAnchor endon( "stationary" ); + + while(1) + { + if ( !isdefined( self ) ) + return; + + if ( !isdefined( self.physicsAnchor ) ) + return; + + if ( self.physicsAnchor oob::IsTouchingAnyOOBTrigger() || + self.physicsAnchor gameobjects::is_touching_any_trigger_key_value( "trigger_hurt", "classname", self.trigger.origin[2], self.trigger.origin[2] + 32 ) ) + { + self thread reset_taco(); + return; + } + + self.trigger.origin = self.physicsAnchor.origin; + + Objective_Position( self.objectiveId, self.trigger.origin ); + + {wait(.05);}; + } +} + +function bounce_when_stationary() +{ + level endon( "game_ended" ); + self endon( "reset" ); + self endon( "death" ); + + if ( !isdefined( self ) ) + return; + + if ( !isdefined( self.physicsAnchor ) ) + return; + + self.physicsAnchor waittill( "stationary" ); + + self.trigger.origin = self.physicsAnchor.origin; + Objective_Position( self.objectiveId, self.trigger.origin ); + + self PlaySound( "mpl_fracture_core_drop" ); + + self clientfield::set( "taco_flag", 2 ); + + self.physicsAnchor Delete(); + self.physicsAnchor = undefined; +} + +function timeout_wait() +{ + level endon( "game_ended" ); + self endon( "reset" ); + + wait 60; + +/# + level.expiredDrops++; +#/ + + self thread reset_taco(); +} + +function reset_taco() +{ +/# + level.activeDrops--; +#/ + self notify( "reset" ); + + self clientfield::set( "taco_flag", 0 ); + + self StopLoopSound(); + + self.trigger.origin = (0,0,1000); + self gameobjects::allow_use( "none" ); + + if ( isdefined( self.physicsAnchor ) ) + { + self.physicsAnchor Delete(); + self.physicsAnchor = undefined; + } + + Objective_State( self.objectiveId, "invisible" ); + + self Ghost(); +} + +function onRoundSwitch() +{ + game["switchedsides"] = !game["switchedsides"]; +} + +function onRoundEndGame( roundWinner ) +{ + return globallogic::determineTeamWinnerByGameStat( "roundswon" ); +} + +// Objectives +//======================================== + +function setup_deposit_hubs() +{ + globallogic::waitForPlayers(); + + remove_targetnames( "clean_objective_base_trig" ); + remove_targetnames( "clean_objective_center_trig" ); + remove_targetnames( "clean_objective_scatter_trig" ); + remove_flag_bases(); + + if ( !isdefined( level.cleanDepositPoints ) ) + { + /#util::error("level.cleanDepositPoints needs to be defined in level script");#/ + return; + } + + level.cleanDepositHubs = []; + + foreach( point in level.cleanDepositPoints ) + { + depositHub = create_deposit_hub( point ); + + level.cleanDepositHubs[level.cleanDepositHubs.size] = depositHub; + } +} + +function remove_targetnames( targetname ) +{ + ents = getEntArray( targetname, "targetname" ); + + foreach( ent in ents ) + { + ent Delete(); + } +} + +function remove_flag_bases() +{ + scriptModels = getEntArray( "script_model", "className" ); + + foreach( scriptModel in scriptModels ) + { + if ( scriptModel.model === "p7_mp_flag_base" ) + scriptModel Delete(); + } +} + +function create_deposit_hub( origin ) +{ + trigger = spawn( "trigger_radius", origin, 0, 80, 108 ); + + visuals[0] = spawn( "script_model", trigger.origin ); + + depositHub = gameobjects::create_use_object( "neutral", trigger, visuals, undefined, &"clean_deposit" ); + + depositHub gameobjects::set_use_time( 0 ); + depositHub gameobjects::allow_use( "none" ); + depositHub gameobjects::set_visible_team( "none" ); + + depositHub.onUse = &on_use_deposit_hub; + depositHub.canUseObject = &can_use_deposit_point; + + depositHub.effectOrigin = trigger.origin + ( 0, 0, 0 ); + + depositHub spawning::create_influencer( "clean_deposit_hub", depositHub.origin, 0 ); + depositHub spawning::enable_influencers(false); + + return depositHub; +} + +function trigger_base_effect() +{ + level endon( "game_ended" ); + self.baseEffectRotator = spawn( "script_model", self.effectOrigin ); + self.baseEffectRotator setModel( "tag_origin" ); + self.baseEffectRotator.angles = ( -90, 0, 0 ); + + self.baseEffect = spawnFx( "ui/fx_stockpile_deposit_point", self.effectOrigin ); + self.baseEffect.team = "none"; + + triggerFx( self.baseEffect, 0.001 ); + + time_left = 5; + wait_time = level.cleanDepositOnlineTime - time_left; + if ( wait_time < 0 ) + { + wait_time = level.cleanDepositOnlineTime * 0.05; + } + + wait (wait_time); + + if ( !isdefined(self.baseEffect) ) + return; + + self PlaySound( "mpl_fracture_deposit_leave" ); + self.baseEffect delete(); + self.baseEffect = PlayFxOnTag( "ui/fx_stockpile_deposit_point_end", self.baseEffectRotator, "tag_origin" ); + self.baseEffect.team = "none"; + + // dont want it to stop before being deleted + time_left += 0.25; + + num_rotations = 3; + angles = (self.baseEffectRotator.angles[0], self.baseEffectRotator.angles[1] - num_rotations * 360, self.baseEffectRotator.angles[2]); + + self.baseEffectRotator RotateTo( angles, time_left, time_left, 0, false ); +} + +function rotate_deposit_hubs() +{ + level endon( "game_ended" ); + + while ( level.inPrematchPeriod ) + { + {wait(.05);}; + } + + SetBombTimer( "A", 0 ); + setMatchFlag( "bomb_timer_a", 0 ); + + hubIndex = -1; + + while ( 1 ) + { + if ( level.cleanDepositOfflineTime > 0 ) + { + foreach( team in level.teams ) + { + setMatchFlag( "bomb_timer_a", 1 ); + SetBombTimer( "A", int( gettime() + 1000 + level.cleanDepositOfflineTime * 1000 ) ); + + if ( hubIndex >= 0 ) + { + globallogic_audio::leader_dialog( "hubOffline", team ); + globallogic_audio::play_2d_on_team( "mpl_fracture_sting_powerdown", team ); + } + } + + wait level.cleanDepositOfflineTime; + } + + nextHubIndex = get_next_hub_index( hubIndex ); + depositHub = level.cleanDepositHubs[nextHubIndex]; + + // Enable the point + depositHub gameobjects::allow_use( "any" ); + depositHub gameobjects::set_visible_team( "any" ); + depositHub thread trigger_base_effect(); + depositHub spawning::enable_influencers( true ); + depositHub PlayLoopSound( "mpl_fracture_location_lp" ); + + foreach( team in level.teams ) + { + setMatchFlag( "bomb_timer_a", 1 ); + SetBombTimer( "A", int( gettime() + 1000 + level.cleanDepositOnlineTime * 1000 ) ); + + if ( level.cleanDepositOfflineTime > 0 ) + { + globallogic_audio::leader_dialog( "hubOnline", team ); + } + else if ( hubIndex >= 0 ) + { + globallogic_audio::leader_dialog( "hubMoved", team ); + } + + if ( hubIndex >= 0 ) + { + globallogic_audio::play_2d_on_team( "mpl_fracture_sting_moved", team ); + } + } + + hubIndex = nextHubIndex; + //depositHub thread shutdown_warning(); + wait level.cleanDepositOnlineTime; + + // Disable the point + depositHub gameobjects::allow_use( "none" ); + depositHub gameobjects::set_visible_team( "none" ); + depositHub spawning::enable_influencers(false); + depositHub StopLoopSound( ); + + depositHub.baseEffect delete(); + + level notify( "clean_deposit_hub_moved" ); + } +} + +function get_next_hub_index( hubIndex ) +{ + if(!isdefined(hubIndex))hubIndex=-1; + + switch( level.cleanDepositRotation ) + { + case 0: // Linear + return ( hubIndex + 1 ) % level.cleanDepositHubs.size; + case 1: // Fixed start random + return get_fixed_start_hub_index( hubIndex, &get_random_cycle_hub_index ); + } + + //case 2: // Random Cycle + return get_random_cycle_hub_index( hubIndex ); +} + +// Start with hub index 0 +function get_fixed_start_hub_index( hubIndex, randomFunc ) +{ + if ( hubIndex < 0 ) + { + return 0; + } + + return [[randomFunc]]( hubIndex ); +} + + +// Get a new index without repeating any until all hubs have come online +function get_random_cycle_hub_index( hubIndex ) +{ + if(!isdefined(level.unusedDepositHubIndexes))level.unusedDepositHubIndexes=[]; + + if ( level.unusedDepositHubIndexes.size == 0 ) + { + for( i = 0; i < level.cleanDepositHubs.size; i++ ) + { + if ( i != hubIndex ) + { + level.unusedDepositHubIndexes[level.unusedDepositHubIndexes.size] = i; + } + } + } + + unusedIndex = RandomInt( level.unusedDepositHubIndexes.size ); + nextIndex = level.unusedDepositHubIndexes[unusedIndex]; + ArrayRemoveIndex( level.unusedDepositHubIndexes, unusedIndex ); + + return nextIndex; +} + +// Random index without repeating the last +function get_random_hub_index( hubIndex ) +{ + if ( hubIndex < 0 ) + { + return RandomInt( level.cleanDepositHubs.size ); + } + + nextIndex = RandomInt( level.cleanDepositHubs.size - 1 ); + + if ( nextIndex >= hubIndex ) + { + nextIndex++; + } + + return nextIndex; +} + +function hideTimerDisplayOnGameEnd() +{ + level waittill("game_ended"); + setMatchFlag( "bomb_timer_a", 0 ); +} + +function on_use_deposit_hub( player ) +{ + time = GetTime(); + + if ( time - player.lastDepositTime > ( .05 + 0.25 ) * 1000 ) + { + player.depositStreak = 0; + } + + player.lastDepositTime = time; + + if( isdefined(player.pers["cleandeposits"]) ) + { + player.pers["cleandeposits"] = player.pers["cleandeposits"] + 1; + player.cleandeposits = player.pers["cleandeposits"]; + score_event_deposit( player, self ); + } + + player AddPlayerStatWithGameType( "CLEANDEPOSITS", 1 ); + + if ( level.teamScorePerCleanDeposit > 0 ) + { + depositTeamScore = level.teamScorePerCleanDeposit; + + player globallogic_score::giveTeamScoreForObjective( player.team, depositTeamScore ); + } + + enemyTeam = util::getOtherTeam( player.team ); + level thread popups::DisplayTeamMessageToTeam( &"MP_CLEAN_DEPOSIT", player, player.team ); + level thread popups::DisplayTeamMessageToTeam( &"MP_CLEAN_DEPOSIT", player, enemyTeam ); + + //globallogic_audio::play_2d_on_team( ENEMY_DEPOSIT_SOUND, enemyTeam ); + globallogic_audio::play_2d_on_team( "mpl_flagcapture_sting_friendly", player.team ); + + switch( player.depositStreak ) + { + case 0: + player playLocalSound( "mpl_fracture_deposit_1" ); + break; + case 1: + player playLocalSound( "mpl_fracture_deposit_2" ); + break; + case 2: + player playLocalSound( "mpl_fracture_deposit_3" ); + break; + case 3: + player playLocalSound( "mpl_fracture_deposit_4" ); + break; + default: + player playLocalSound( "mpl_fracture_deposit_5" ); + } + + player.depositStreak++; + + scoreevents::processScoreEvent( "clean_enemy_deposit", player ); + + player.carriedTacos--; + player clientfield::set_player_uimodel( "hudItems.cleanCarryCount", player.carriedTacos ); + + player update_taco_carry(); +} + +function score_event_deposit( player, deposit_hub ) +{ + if ( !isdefined( player ) ) + return; + + time = GetTime(); // in MS + + player.same_hub_deposit_count++; + + if ( player.same_hub_deposit_count == 1 ) + { + player thread watch_leave_hub( deposit_hub ); + } + else if ( player.same_hub_deposit_count == 5 ) + { + scoreevents::processScoreEvent( "clean_multi_deposit_normal", player ); + } + else if ( player.same_hub_deposit_count == 10 ) + { + scoreevents::processScoreEvent( "clean_multi_deposit_big", player ); + player.same_hub_deposit_count = 0; + } +} + +function watch_leave_hub( deposit_hub ) +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "clean_deposit_hub_moved" ); + + self thread watch_leave_hub_on_move(); + + wait 0.1; + + while( 1 ) + { + // strange that this doesn't work. (will use an endon instead) + // + // if ( !deposit_hub.trigger IsTriggerEnabled() ) + // break; + // + + if ( Distance2DSquared( self.origin, deposit_hub.origin ) > ( (80 + 10) * (80 + 10) ) ) + break; + + wait 0.1; + } + + self.same_hub_deposit_count = 0; +} + +function watch_leave_hub_on_move() +{ + self endon( "death" ); + self endon( "disconnect" ); + + level waittill( "clean_deposit_hub_moved" ); + + self.same_hub_deposit_count = 0; +} + +function score_event_deny( player, victim ) +{ + if ( !isdefined( player ) ) + return; + + if ( victim === player ) + { + scoreevents::processScoreEvent( "clean_own_collect", player ); + } + else + { + scoreevents::processScoreEvent( "clean_friendly_collect", player ); + } + + time = GetTime(); // in MS + + if(!isdefined(player.clean_deny_times))player.clean_deny_times=[]; + deny_time_threshold = time - ( 3000 + 250 ); + prune_time_ms_array( player.clean_deny_times, deny_time_threshold ); + + if ( player.clean_deny_times.size >= ( 5 - 1 ) ) + { + scoreevents::processScoreEvent( "clean_multi_deny_tacos", player ); + player.clean_deny_times = undefined; + } + else + { + if ( !isdefined( player.clean_deny_times ) ) player.clean_deny_times = []; else if ( !IsArray( player.clean_deny_times ) ) player.clean_deny_times = array( player.clean_deny_times ); player.clean_deny_times[player.clean_deny_times.size]=time;; + } +} + +function prune_time_ms_array( ×, time_threshold ) +{ + for( i = 0; i < times.size; i++ ) + { + if ( times[ i ] < time_threshold ) + times[ i ] = 0; + } + ArrayRemoveValue( times, 0 ); +} + +function can_use_deposit_point( player ) +{ + if ( player.carriedTacos <= 0 ) + { + // Reset the centered/colored/pulsing objective + Objective_ClearPlayerUsing( self.objectiveID, player ); + return false; + } + + if ( !player.lastDepositTime ) + return true; + + return player.lastDepositTime + ( 0.25 * 1000 ) < GetTime(); +} + + +// Tag Functions +//======================================== + +function on_use_taco( player ) +{ + if ( self.victimTeam == player.team ) + { + PlaySoundAtPosition( "mpl_fracture_enemy_pickup_m", self.origin ); + + if( isdefined(player.pers["cleandenies"]) ) + { + player.pers["cleandenies"] = player.pers["cleandenies"] + 1; + player.cleandenies = player.pers["cleandenies"]; + } + + player AddPlayerStatWithGameType( "CLEANDENIES", 1 ); + + score_event_deny( player, self.victim ); + } + else if ( player.carriedTacos >= 10 ) + { + // Done here instead of in can_use since touching multiple triggers in the same frame will let you pick them all up + + time = GetTime(); + if ( time - player.lastCarryFullTime > 500 ) + { + player PlayLocalSound( "mpl_fracture_enemy_pickup_nope" ); + if(!isdefined(player.carryFull))player.carryFull=0; + player clientfield::set_player_uimodel( "hudItems.cleanCarryFull", player.carryFull ); + player.carryFull = ( !player.carryFull ? 1 : 0 ); + } + + player.lastCarryFullTime = time; + + return; + } + else + { + player.carriedTacos++; + player clientfield::set_player_uimodel( "hudItems.cleanCarryCount", player.carriedTacos ); + player update_taco_carry(); + + if ( player.carriedTacos < 4 ) + { + PlaySoundAtPosition( "mpl_fracture_enemy_pickup_s", self.origin ); + } + else if ( player.carriedTacos < 7 ) + { + PlaySoundAtPosition( "mpl_fracture_enemy_pickup_m", self.origin ); + } + else + { + PlaySoundAtPosition( "mpl_fracture_enemy_pickup_l", self.origin ); + } + + scoreevents::processScoreEvent( "clean_enemy_collect", player ); + + if ( self.attackerTeam == player.team && + isdefined( self.attacker ) && + self.attacker != player ) + { + scoreevents::processScoreEvent( "clean_assist_collect", self.attacker ); + } + } + + self reset_taco(); +} + +function drop_carried_tacos( attacker, yawAngle ) +{ + dropCount = self.carriedTacos; + + self.carriedTacos = 0; + self clientfield::set_player_uimodel( "hudItems.cleanCarryCount", self.carriedTacos ); + self update_taco_carry(); + +/# + dropCount += GetDvarInt( "extraTacos", 0 ); +#/ + + yawInterval = 360 / ( dropCount + 1 ); + + for ( i = 0; i < dropCount; i++ ) + { + taco = get_droppable_taco( ); + + if ( !isdefined( taco ) ) + { + return; + } + + yawAngle += yawInterval; + randomYaw = 0.8 * yawInterval; + randomYaw = RandomFloatRange( -randomYaw, randomYaw ); + taco drop_taco( self, attacker, undefined, yawAngle + randomYaw ); + } +} \ No newline at end of file diff --git a/mp/gametypes/clean.gsh b/mp/gametypes/clean.gsh new file mode 100644 index 0000000..15658c6 --- /dev/null +++ b/mp/gametypes/clean.gsh @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/mp/gametypes/conf.csc b/mp/gametypes/conf.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/conf.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/conf.gsc b/mp/gametypes/conf.gsc new file mode 100644 index 0000000..502fa17 --- /dev/null +++ b/mp/gametypes/conf.gsc @@ -0,0 +1,266 @@ +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\math_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_dogtags; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\killstreaks\_killstreaks; + +#using scripts\mp\_util; + +/* + Confirmed Kill + Objective: Score points for your team by eliminating players on the opposing team. + Score bonus points for picking up dogtags from downed enemies. + Map ends: When one team reaches the score limit, or time limit is reached + Respawning: No wait / Near teammates + + Level requirementss + ------------------ + Spawnpoints: + classname mp_tdm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of teammates and enemies + at the time of spawn. Players generally spawn behind their teammates relative to the direction of enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. +*/ + +#precache( "material", "waypoint_dogtags" ); +#precache( "string", "OBJECTIVES_CONF" ); +#precache( "string", "OBJECTIVES_CONF_SCORE" ); +#precache( "string", "OBJECTIVES_CONF_HINT" ); +#precache( "string", "MP_KILL_DENIED" ); + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 50000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundSwitch( 0, 9 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.scoreRoundWinBased = true; + level.teamBased = true; + level.onPrecacheGameType =&onPrecacheGameType; + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.onRoundEndGame =&onRoundEndGame; + level.onPlayerKilled =&onPlayerKilled; + level.onRoundSwitch =&onRoundSwitch; + level.determineWinner =&determineWinner; + level.overrideTeamScore = true; + level.teamScorePerKill = GetGametypeSetting( "teamScorePerKill" ); + level.teamScorePerKillConfirmed = GetGametypeSetting( "teamScorePerKillConfirmed" ); + level.teamScorePerKillDenied = GetGametypeSetting( "teamScorePerKillDenied" ); + + gameobjects::register_allowed_gameobject( level.gameType ); + +// if ( level.matchRules_damageMultiplier || level.matchRules_vampirism ) +// level.modifyPlayerDamage = _damage::gamemodeModifyPlayerDamage; + + globallogic_audio::set_leader_gametype_dialog ( "startKillConfirmed", "hcCtartKillConfirmed", "gameBoost", "gameBoost" ); + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "killsconfirmed", "killsdenied", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "killsconfirmed", "killsdenied" ); +} + + +function onPrecacheGameType() +{ + +} + + +function onStartGameType() +{ + setClientNameMode("auto_change"); + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + foreach( team in level.teams ) + { + util::setObjectiveText( team, &"OBJECTIVES_CONF" ); + util::setObjectiveHintText( team, &"OBJECTIVES_CONF_HINT" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( team, &"OBJECTIVES_CONF" ); + } + else + { + util::setObjectiveScoreText( team, &"OBJECTIVES_CONF_SCORE" ); + } + + spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) ); + spawnlogic::add_spawn_points( team, "mp_tdm_spawn" ); + } + + spawning::updateAllSpawnPoints(); + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array(spawning::getTDMStartSpawnName(team)); + } + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + dogtags::init(); + + if ( !util::isOneRound() ) + { + level.displayRoundEndText = true; + if( level.scoreRoundWinBased ) + { + globallogic_score::resetTeamScores(); + } + } + +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( !isPlayer( attacker ) || attacker.team == self.team ) + return; + + //score = rank::getScoreInfoValue( "kill" ); + //assert( isdefined( score ) ); + + level thread dogtags::spawn_dog_tag( self, attacker, &onUse, true ); + + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + attacker globallogic_score::giveTeamScoreForObjective( attacker.team, level.teamScorePerKill ); +} + +function onUse( player ) +{ + tacInsertBoost = false; + + // friendly pickup + if ( player.team != self.attackerTeam ) + { + tacInsertBoost = self.tacInsert; + + if ( isdefined ( self.attacker ) && self.attacker.team == self.attackerTeam ) + { + self.attacker LUINotifyEvent( &"player_callout", 2, &"MP_KILL_DENIED", player.entnum ); + // TODO: need audio cue + //self.attacker PlayLocalSound( game["dialog"]["kc_denied"] ); + } + + if ( !tacInsertBoost ) + { + // TODO: Need audio cue + //player globallogic_audio::leader_dialog_on_player( "kc_deny" ); + + player globallogic_score::giveTeamScoreForObjective( player.team, level.teamScorePerKillDenied ); + } + } + // enemy pickup + else + { +/# + assert( isdefined( player.lastKillConfirmedTime ) ); + assert( isdefined( player.lastKillConfirmedCount ) ); +#/ + // TODO: Need audio cue + //player globallogic_audio::leader_dialog_on_player( "kc_start" ); + + player.pers["killsconfirmed"]++; + player.killsconfirmed = player.pers["killsconfirmed"]; + player globallogic_score::giveTeamScoreForObjective( player.team, level.teamScorePerKillConfirmed ); + + } + + if ( !tacInsertBoost ) + { + currentTime = getTime(); + + if ( player.lastKillConfirmedTime + 1000 > currentTime ) + { + player.lastKillConfirmedCount++; + if ( player.lastKillConfirmedCount >= 3 ) + { + scoreevents::processScoreEvent( "kill_confirmed_multi", player ); + player.lastKillConfirmedCount = 0; + } + } + else + { + player.lastKillConfirmedCount = 1; + } + + player.lastKillConfirmedTime = currentTime; + } +} + + +function onSpawnPlayer(predictedSpawn) +{ + self.usingObj = undefined; + + if ( level.useStartSpawns && !level.inGracePeriod ) + { + level.useStartSpawns = false; + } + + self.lastKillConfirmedTime = 0; + self.lastKillConfirmedCount = 0; + + spawning::onSpawnPlayer(predictedSpawn); + + dogtags::on_spawn_player(); +} + +function onRoundSwitch() +{ + game["switchedsides"] = !game["switchedsides"]; +} + +function determineWinner() +{ + return globallogic::determineTeamWinnerByGameStat( "roundswon" ); +} + +function onRoundEndGame( roundWinner ) +{ + return globallogic::determineTeamWinnerByGameStat( "roundswon" ); +} diff --git a/mp/gametypes/ctf.csc b/mp/gametypes/ctf.csc new file mode 100644 index 0000000..f90ad70 --- /dev/null +++ b/mp/gametypes/ctf.csc @@ -0,0 +1,109 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\util_shared; + +#using scripts\mp\_shoutcaster; + + + + +#precache( "client_fx", "ui/fx_ctf_flag_base_team" ); +#precache( "client_fx", "ui/fx_ctf_flag_base_white" ); + +function main() +{ + callback::on_localclient_connect( &on_localclient_connect ); + +// level.effect_scriptbundle = struct::get_script_bundle( "teamcolorfx", "teamcolorfx_ctf_flag_base" ); +} + +function on_localclient_connect( localClientNum ) +{ + objective_ids = []; + + while ( !isdefined( objective_ids["allies_base"] ) ) + { + objective_ids["allies_base"] = ServerObjective_GetObjective( localClientNum, "allies_base" ); + objective_ids["axis_base"] = ServerObjective_GetObjective( localClientNum, "axis_base" ); + wait(0.05); + } + + foreach( key, objective in objective_ids ) + { + level.ctfFlags[key] = SpawnStruct(); + level.ctfFlags[key].objectiveId = objective; + setup_flag( localClientNum, level.ctfFlags[key] ); + } + + setup_fx( localClientNum ); +} + +function setup_flag( localClientNum, flag ) +{ + flag.origin = ServerObjective_GetObjectiveOrigin( localClientNum, flag.objectiveId ); + flag_entity = ServerObjective_GetObjectiveEntity( localClientNum, flag.objectiveId ); + flag.angles = (0,0,0); + + if ( isdefined(flag_entity) ) + { + flag.origin = flag_entity.origin; + flag.angles = flag_entity.angles; + } + + flag.team = ServerObjective_GetObjectiveTeam( localClientNum, flag.objectiveId ); +} + +function setup_flag_fx( localClientNum, flag, effects ) +{ + if ( isdefined( flag.base_fx ) ) + { + StopFx( localClientNum, flag.base_fx ); + } + + up = anglesToUp(flag.angles); + forward = anglesToForward(flag.angles); + flag.base_fx = PlayFx(localClientNum, effects[flag.team], flag.origin, up, forward ); + SetFxTeam( localClientNum, flag.base_fx, flag.team ); + + thread watch_for_team_change( localClientNum ); +} + +function setup_fx( localClientNum ) +{ + effects = []; + + if ( shoutcaster::is_shoutcaster_using_team_identity(localClientNum) ) + { + if ( GetDvarInt("tu11_programaticallyColoredGameFX") ) + { + effects["allies"] = "ui/fx_ctf_flag_base_white"; + effects["axis"] = "ui/fx_ctf_flag_base_white"; + } + else + { + effects = shoutcaster::get_color_fx( localClientNum, level.effect_scriptbundle ); + } + } + else + { + effects["allies"] = "ui/fx_ctf_flag_base_team"; + effects["axis"] = "ui/fx_ctf_flag_base_team"; + } + + foreach( flag in level.ctfFlags) + { + thread setup_flag_fx(localClientNum, flag, effects ); + } +} + +function watch_for_team_change( localClientNum ) +{ + level notify( "end_team_change_watch" ); + level endon( "end_team_change_watch" ); + + level waittill( "team_changed" ); + + thread setup_fx( localClientNum ); +} \ No newline at end of file diff --git a/mp/gametypes/ctf.gsc b/mp/gametypes/ctf.gsc new file mode 100644 index 0000000..5e506d7 --- /dev/null +++ b/mp/gametypes/ctf.gsc @@ -0,0 +1,1398 @@ +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hud_message_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_defaults; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hud_message; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\teams\_teams; + + + + + + +/* + CTF + + Level requirements + ------------------ + Allied Spawnpoints: + classname mp_sd_spawn_attacker + Allied players spawn from these. Place at least 16 of these relatively close together. + + Axis Spawnpoints: + classname mp_sd_spawn_defender + Axis players spawn from these. Place at least 16 of these relatively close together. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Flag: + classname trigger_multiple + targetname flagtrigger + script_gameobjectname ctf + script_label Set to name of flag. This sets the letter shown on the compass in original mode. + script_team Set to allies or axis. This is used to set which team a flag is used by. + This should be a 16x16 unit trigger with an origin brush placed so that it's center lies on the bottom plane of the trigger. + Must be in the level somewhere. This is the trigger that is used to represent a flag. + It gets moved to the position of the planted bomb model. +*/ + +/*QUAKED mp_ctf_spawn_axis (0.75 0.0 0.5) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near their team at one of these positions.*/ + +/*QUAKED mp_ctf_spawn_allies (0.0 0.75 0.5) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions.*/ + +/*QUAKED mp_ctf_spawn_axis_start (1.0 0.0 0.5) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_ctf_spawn_allies_start (0.0 1.0 0.5) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + + + + + +#precache( "string", "OBJECTIVES_CTF" ); +#precache( "string", "OBJECTIVES_CTF_SCORE" ); +#precache( "string", "OBJECTIVES_CTF_HINT" ); +#precache( "string", "MP_CTF_OVERTIME_ROUND_1" ); +#precache( "string", "MP_CTF_OVERTIME_ROUND_1" ); +#precache( "string", "MP_CTF_OVERTIME_ROUND_2_WINNER" ); +#precache( "string", "MP_CTF_OVERTIME_ROUND_2_LOSER" ); +#precache( "string", "MP_CTF_OVERTIME_ROUND_2_TIE" ); +#precache( "string", "MP_CTF_OVERTIME_ROUND_2_TIE" ); +#precache( "string", "MP_FLAG_TAKEN_BY"); +#precache( "string", "MP_ENEMY_FLAG_TAKEN"); +#precache( "string", "MP_FRIENDLY_FLAG_TAKEN"); +#precache( "string", "MP_FLAG_CAPTURED_BY"); +#precache( "string", "MP_ENEMY_FLAG_CAPTURED_BY"); +#precache( "string", "MP_FLAG_RETURNED_BY"); +#precache( "string", "MP_FLAG_RETURNED"); +#precache( "string", "MP_ENEMY_FLAG_RETURNED"); +#precache( "string", "MP_FRIENDLY_FLAG_RETURNED"); +#precache( "string", "MP_YOUR_FLAG_RETURNING_IN"); +#precache( "string", "MP_ENEMY_FLAG_RETURNING_IN"); +#precache( "string", "MP_FRIENDLY_FLAG_DROPPED_BY"); +#precache( "string", "MP_FRIENDLY_FLAG_DROPPED"); +#precache( "string", "MP_ENEMY_FLAG_DROPPED"); +#precache( "string", "MP_SUDDEN_DEATH"); +#precache( "string", "MP_CAP_LIMIT_REACHED"); +#precache( "string", "MP_CTF_CANT_CAPTURE_FLAG" ); +#precache( "triggerstring", "MP_CTF_CANT_CAPTURE_FLAG" ); +#precache( "string", "MP_CTF_OVERTIME_WIN" ); +#precache( "string", "MP_CTF_OVERTIME_ROUND_1" ); +#precache( "string", "MP_CTF_OVERTIME_ROUND_2_WINNER" ); +#precache( "string", "MP_CTF_OVERTIME_ROUND_2_LOSER" ); +#precache( "string", "MP_CTF_OVERTIME_ROUND_2_TIE" ); +#precache( "string", "MPUI_CTF_OVERTIME_FASTEST_CAP_TIME" ); +#precache( "string", "MPUI_CTF_OVERTIME_DEFEAT_TIMELIMIT" ); +#precache( "string", "MPUI_CTF_OVERTIME_DEFEAT_DID_NOT_DEFEND" ); +#precache( "objective", "allies_base" ); +#precache( "objective", "axis_base" ); +#precache( "objective", "allies_flag" ); +#precache( "objective", "axis_flag" ); +#precache( "fx", "ui/fx_ctf_flag_base_team" ); + +function autoexec __init__sytem__() { system::register("ctf",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "scriptmover", "ctf_flag_away", 1, 1, "int" ); +} + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerRoundSwitch( 0, 9 ); + util::registerNumLives( 0, 100 ); + util::registerScoreLimit( 0, 5000 ); + + level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false ); + level.flagCaptureCondition = GetGametypeSetting( "flagCaptureCondition" ); + level.doubleOvertime = true; + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + if ( GetDvarString( "scr_ctf_spawnPointFacingAngle") == "" ) + SetDvar("scr_ctf_spawnPointFacingAngle", "0"); + + level.teamBased = true; + level.overrideTeamScore = true; + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.onPrecacheGameType =&onPrecacheGameType; + level.onPlayerKilled =&onPlayerKilled; + level.onRoundSwitch =&onRoundSwitch; + level.onEndGame =&onEndGame; + level.onRoundEndGame =&onRoundEndGame; + level.getTeamKillPenalty =&ctf_getTeamKillPenalty; + level.getTeamKillScore =&ctf_getTeamKillScore; + level.setMatchScoreHUDElemForTeam =&setMatchScoreHUDElemForTeam; + level.shouldPlayOvertimeRound =&shouldPlayOvertimeRound; + + gameobjects::register_allowed_gameobject( level.gameType ); + + if ( !isdefined( game["ctf_teamscore_cache"] ) ) + { + game["ctf_teamscore_cache"]["allies"] = 0; // ctf team score cacge is the total flags earned before the score is reset for OT, used by cumulative-round win rule + game["ctf_teamscore_cache"]["axis"] = 0; + } + + globallogic_audio::set_leader_gametype_dialog ( "startCtf", "hcStartCtf", "objCapture", "objCapture" ); + + level.lastDialogTime = getTime(); + + level thread ctf_icon_hide(); + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "captures", "returns", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "captures", "returns" ); +} + +function onPrecacheGameType() +{ + game["flag_dropped_sound"] = "mp_war_objective_lost"; + game["flag_recovered_sound"] = "mp_war_objective_taken"; + + game["strings"]["score_limit_reached"] = &"MP_CAP_LIMIT_REACHED"; + +} + +function onStartGameType() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + /# + setdebugsideswitch(game["switchedsides"]); + #/ + + setClientNameMode("auto_change"); + + globallogic_score::resetTeamScores(); + + util::setObjectiveText( "allies", &"OBJECTIVES_CTF" ); + util::setObjectiveText( "axis", &"OBJECTIVES_CTF" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_CTF" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_CTF" ); + } + else + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_CTF_SCORE" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_CTF_SCORE" ); + } + util::setObjectiveHintText( "allies", &"OBJECTIVES_CTF_HINT" ); + util::setObjectiveHintText( "axis", &"OBJECTIVES_CTF_HINT" ); + + if ( isdefined( game["overtime_round"] ) ) + { + // This is only necessary when cumulativeRoundScores is on so that the game doesn't immediately end due to scorelimit being set to 1 in OT + game["ctf_teamscore_cache"]["allies"] += [[level._getTeamScore]]( "allies" ); + game["ctf_teamscore_cache"]["axis"] += [[level._getTeamScore]]( "axis" ); + + [[level._setTeamScore]]( "allies", 0 ); + [[level._setTeamScore]]( "axis", 0 ); + + // One flag wins the round + util::registerScoreLimit( 1, 1 ); + if ( isdefined( game["ctf_overtime_time_to_beat"] ) ) + { + util::registerTimeLimit( game["ctf_overtime_time_to_beat"] / 60000, game["ctf_overtime_time_to_beat"] / 60000 ); + } + + if ( game["overtime_round"] == 1 ) + { + util::setObjectiveHintText( "allies", &"MP_CTF_OVERTIME_ROUND_1" ); + util::setObjectiveHintText( "axis", &"MP_CTF_OVERTIME_ROUND_1" ); + } + else if ( isdefined( game["ctf_overtime_first_winner"] ) ) + { + util::setObjectiveHintText( game["ctf_overtime_first_winner"], &"MP_CTF_OVERTIME_ROUND_2_WINNER" ); + util::setObjectiveHintText( util::getOtherTeam( game["ctf_overtime_first_winner"] ), &"MP_CTF_OVERTIME_ROUND_2_LOSER" ); + } + else + { + util::setObjectiveHintText( "allies", &"MP_CTF_OVERTIME_ROUND_2_TIE" ); + util::setObjectiveHintText( "axis", &"MP_CTF_OVERTIME_ROUND_2_TIE" ); + } + } + + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::place_spawn_points( "mp_ctf_spawn_allies_start" ); + spawnlogic::place_spawn_points( "mp_ctf_spawn_axis_start" ); + spawnlogic::add_spawn_points( "allies", "mp_ctf_spawn_allies" ); + spawnlogic::add_spawn_points( "axis", "mp_ctf_spawn_axis" ); + + spawning::add_fallback_spawnpoints( "allies", "mp_tdm_spawn" ); + spawning::add_fallback_spawnpoints( "axis", "mp_tdm_spawn" ); + + spawning::updateAllSpawnPoints(); + spawning::update_fallback_spawnpoints(); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + level.spawn_axis = spawnlogic::get_spawnpoint_array( "mp_ctf_spawn_axis" ); + level.spawn_allies = spawnlogic::get_spawnpoint_array( "mp_ctf_spawn_allies" ); + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array("mp_ctf_spawn_" + team + "_start"); + } + + thread updateGametypeDvars(); + + thread ctf(); +} + +function shouldPlayOvertimeRound() +{ + // Play 2 rounds of overtime + if ( isdefined( game["overtime_round"] ) ) + { + if ( game["overtime_round"] == 1 || !level.gameEnded ) // If we've only played 1 round or we're in the middle of the 2nd keep going + { + return true; + } + + return false; + } + + if ( !level.scoreRoundWinBased ) + { + // Only go to overtime if both teams are tied and it's either the last round or both teams are one away from winning + if ( ( game["teamScores"]["allies"] == game["teamScores"]["axis"] ) && + ( util::hitRoundLimit() || ( game["teamScores"]["allies"] == level.scoreLimit-1 ) ) ) + { + return true; + } + } + else + { + // Only go to overtime if both teams are one round away from winning + alliesRoundsWon = util::getRoundsWon( "allies" ); + axisRoundsWon = util::getRoundsWon( "axis" ); + if ( ( level.roundWinLimit > 0 ) && ( axisRoundsWon == level.roundWinLimit-1 ) && ( alliesRoundsWon == level.roundWinLimit-1 ) ) + { + return true; + } + if ( util::hitRoundLimit() && ( alliesRoundsWon == axisRoundsWon ) ) + { + return true; + } + } + + return false; +} + +function minutesAndSecondsString( milliseconds ) +{ + minutes = floor( milliseconds / 60000 ); + milliseconds -= minutes * 60000; + seconds = floor( milliseconds / 1000 ); + if ( seconds < 10 ) + { + return minutes+":0"+seconds; + } + else + { + return minutes+":"+seconds; + } +} + +function setMatchScoreHUDElemForTeam( team ) +{ + if ( !isdefined( game["overtime_round"] ) ) + { + self hud_message::setMatchScoreHUDElemForTeam( team ); + } + else if ( isdefined( game["ctf_overtime_second_winner"] ) && ( game["ctf_overtime_second_winner"] == team ) ) + { + self setText( minutesAndSecondsString( game["ctf_overtime_best_time"] ) ); + } + else if ( isdefined( game["ctf_overtime_first_winner"] ) && ( game["ctf_overtime_first_winner"] == team ) ) + { + self setText( minutesAndSecondsString( game["ctf_overtime_time_to_beat"] ) ); + } + else + { + self setText( &"" ); + } +} + +function onRoundSwitch() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + level.halftimeType = "halftime"; + game["switchedsides"] = !game["switchedsides"]; +} + +function onEndGame( winningTeam ) +{ + if ( isdefined( game["overtime_round"] ) ) + { + if ( game["overtime_round"] == 1 ) + { + if ( isdefined( winningTeam ) && ( winningTeam != "tie" ) ) + { + game["ctf_overtime_first_winner"] = winningTeam; + game["ctf_overtime_time_to_beat"] = globallogic_utils::getTimePassed(); + } + } + else + { + game["ctf_overtime_second_winner"] = winningTeam; + game["ctf_overtime_best_time"] = globallogic_utils::getTimePassed(); + } + } +} + +function updateTeamScoreByRoundsWon() +{ + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, game["roundswon"][team] ); + } + } +} + +function updateTeamScoreByFlagsCaptured() +{ + if ( level.scoreRoundWinBased ) + return; + + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, [[level._getTeamScore]]( team ) + game["ctf_teamscore_cache"][team] ); + } +} + +function onRoundEndGame( winningTeam ) +{ + if ( isdefined( game["overtime_round"] ) ) + { + if ( isdefined( game["ctf_overtime_first_winner"] ) ) + { + if ( !isdefined( winningTeam ) || ( winningTeam == "tie" ) ) + { + winningTeam = game["ctf_overtime_first_winner"]; + } + + if ( game["ctf_overtime_first_winner"] == winningTeam ) + { + level.endVictoryReasonText = &"MPUI_CTF_OVERTIME_FASTEST_CAP_TIME"; + level.endDefeatReasonText = &"MPUI_CTF_OVERTIME_DEFEAT_TIMELIMIT"; + } + else + { + level.endVictoryReasonText = &"MPUI_CTF_OVERTIME_FASTEST_CAP_TIME"; + level.endDefeatReasonText = &"MPUI_CTF_OVERTIME_DEFEAT_DID_NOT_DEFEND"; + } + } + else if ( !isdefined( winningTeam ) || ( winningTeam == "tie" ) ) + { + if ( level.scoreRoundWinBased ) + { + updateTeamScoreByRoundsWon(); + } + else + { + updateTeamScoreByFlagsCaptured(); + } + + return "tie"; + } + + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + score = game["roundswon"][team]; + if ( team === winningTeam ) + { + score++; + } + [[level._setTeamScore]]( team, score ); + } + } + else + { + updateTeamScoreByFlagsCaptured(); + } + return winningTeam; + } + + if ( level.scoreRoundWinBased ) + { + updateTeamScoreByRoundsWon(); + + winner = globallogic::determineTeamWinnerByGameStat( "roundswon" ); + } + else + { + winner = globallogic::determineTeamWinnerByTeamScore(); + } + + return winner; +} + +function onSpawnPlayer(predictedSpawn) +{ + self.isFlagCarrier = false; + self.flagCarried = undefined; + self clientfield::set( "ctf_flag_carrier", 0 ); + + spawning::onSpawnPlayer(predictedSpawn); +} + +function updateGametypeDvars() +{ + level.flagCaptureTime = GetGametypeSetting( "captureTime" ); + level.flagTouchReturnTime = GetGametypeSetting( "defuseTime" ); // using defuseTime for touch return + level.idleFlagReturnTime = GetGametypeSetting( "idleFlagResetTime" ); + level.flagRespawnTime = GetGametypeSetting( "flagRespawnTime" ); + level.enemyCarrierVisible = GetGametypeSetting( "enemyCarrierVisible" ); + level.roundLimit = GetGametypeSetting( "roundLimit" ); + level.cumulativeRoundScores = GetGametypeSetting( "cumulativeRoundScores" ); + + level.teamKillPenaltyMultiplier = GetGametypeSetting( "teamKillPenalty" ); + level.teamKillScoreMultiplier = GetGametypeSetting( "teamKillScore" ); + + if ( level.flagTouchReturnTime >= 0 && level.flagTouchReturnTime != 63) + { + level.touchReturn = true; + } + else + { + level.touchReturn = false; + } +} + +function createFlag( trigger ) +{ + if ( isdefined( trigger.target ) ) + { + visuals[0] = getEnt( trigger.target, "targetname" ); + } + else + { + visuals[0] = spawn( "script_model", trigger.origin ); + visuals[0].angles = trigger.angles; + } + + entityTeam = trigger.script_team; + // TODO MTEAM - switched sides + if ( game["switchedsides"] ) + entityTeam = util::getOtherTeam( entityTeam ); + + visuals[0] setModel( teams::get_flag_model( entityTeam ) ); + visuals[0] SetTeam( entityTeam ); + + flag = gameobjects::create_carry_object( entityTeam, trigger, visuals, (0,0,100), istring(entityTeam+"_flag") ); + flag gameobjects::set_team_use_time( "friendly", level.flagTouchReturnTime ); + flag gameobjects::set_team_use_time( "enemy", level.flagCaptureTime ); + flag gameobjects::allow_carry( "enemy" ); + flag gameobjects::set_visible_team( "any" ); + flag gameobjects::set_visible_carrier_model( teams::get_flag_carry_model( entityTeam ) ); + flag gameobjects::set_2d_icon( "friendly", level.iconDefend2D ); + flag gameobjects::set_3d_icon( "friendly", level.iconDefend3D ); + flag gameobjects::set_2d_icon( "enemy", level.iconCapture2D ); + flag gameobjects::set_3d_icon( "enemy", level.iconCapture3D ); + + if ( level.enemyCarrierVisible == 2 ) + { + flag.objIDPingFriendly = true; + } + flag.allowWeapons = true; + flag.onPickup =&onPickup; + flag.onPickupFailed =&onPickup; + flag.onDrop =&onDrop; + flag.onReset =&onReset; + + if ( level.idleFlagReturnTime > 0 ) + { + flag.autoResetTime = level.idleFlagReturnTime; + } + else + { + flag.autoResetTime = undefined; + } + + return flag; +} + +function createFlagZone( trigger ) +{ + visuals = []; + + entityTeam = trigger.script_team; + // TODO MTEAM - switched sides + if ( game["switchedsides"] ) + entityTeam = util::getOtherTeam( entityTeam ); + + flagZone = gameobjects::create_use_object( entityTeam, trigger, visuals, (0,0,0), istring(entityTeam+"_base") ); + flagZone gameobjects::allow_use( "friendly" ); + flagZone gameobjects::set_use_time( 0 ); + flagZone gameobjects::set_use_text( &"MP_CAPTURING_FLAG" ); + flagZone gameobjects::set_visible_team( "friendly" ); + + enemyTeam = util::getOtherTeam( entityTeam ); + flagZone gameobjects::set_key_object( level.teamFlags[enemyTeam] ); + flagZone.onUse =&onCapture; + + flag = level.teamFlags[entityTeam]; + flag.flagBase = flagZone; + flagZone.flag = flag; + + flagZone createFlagSpawnInfluencer( entityTeam ); + + return flagZone; +} + +function createFlagHint( team, origin ) +{ + radius = 128; + height = 64; + + trigger = spawn("trigger_radius", origin, 0, radius, height); + trigger setHintString( &"MP_CTF_CANT_CAPTURE_FLAG" ); + trigger setcursorhint("HINT_NOICON"); + trigger.original_origin = origin; + + trigger turn_off(); + + return trigger; +} + +function ctf() +{ + level.flags = []; + level.teamFlags = []; + level.flagZones = []; + level.teamFlagZones = []; + + flag_triggers = getEntArray( "ctf_flag_pickup_trig", "targetname" ); + if ( !isdefined( flag_triggers ) || flag_triggers.size != 2) + { + /#util::error("Not enough ctf_flag_pickup_trig triggers found in map. Need two.");#/ + return; + } + + for ( index = 0; index < flag_triggers.size; index++ ) + { + trigger = flag_triggers[index]; + + flag = createFlag( trigger ); + + team = flag gameobjects::get_owner_team(); + level.flags[level.flags.size] = flag; + level.teamFlags[team] = flag; + + } + + flag_zones = getEntArray( "ctf_flag_zone_trig", "targetname" ); + if ( !isdefined( flag_zones ) || flag_zones.size != 2) + { + /#util::error("Not enough ctf_flag_zone_trig triggers found in map. Need two.");#/ + return; + } + + for ( index = 0; index < flag_zones.size; index++ ) + { + trigger = flag_zones[index]; + + flagZone = createFlagZone( trigger ); + + team = flagZone gameobjects::get_owner_team(); + level.flagZones[level.flagZones.size] = flagZone; + level.teamFlagZones[team] = flagZone; + + level.flagHints[team] = createFlagHint( team, trigger.origin ); + + facing_angle = GetDvarint( "scr_ctf_spawnPointFacingAngle"); + + // the opposite team will want to face this point + setspawnpointsbaseweight( util::getOtherTeamsMask(team), trigger.origin, facing_angle, level.spawnsystem.objective_facing_bonus); + } + + // once all the flags have been registered with the game, + // give each spawn point a baseline score for each objective flag, + // based on whether or not player will be looking in the direction of that flag upon spawning + //generate_baseline_spawn_point_scores(); + + createReturnMessageElems(); +} + +//Runs each round, as function as restarted at the start of every round. +//Hides the flag status icons and the 2D and 3D icons from the player's view +function ctf_icon_hide() +{ + level waittill ( "game_ended" ); + + level.teamFlags["allies"] gameobjects::set_visible_team( "none" ); + level.teamFlags["axis"] gameobjects::set_visible_team( "none" ); +} + +function removeInfluencers() +{ + if ( isdefined( self.spawn_influencer_enemy_carrier ) ) + { + // self == player + self spawning::remove_influencer( self.spawn_influencer_enemy_carrier ); + self.spawn_influencer_enemy_carrier = undefined; + } + if ( isdefined( self.spawn_influencer_friendly_carrier ) ) + { + // self == player + self spawning::remove_influencer( self.spawn_influencer_friendly_carrier ); + self.spawn_influencer_friendly_carrier = undefined; + } + if ( isdefined( self.spawn_influencer_dropped ) ) + { + // self == flag + self.trigger spawning::remove_influencer( self.spawn_influencer_dropped ); + self.spawn_influencer_dropped = undefined; + } +} + +function onDrop( player ) +{ + origin = (0,0,0); + if ( isdefined( player ) ) + { + player clientfield::set( "ctf_flag_carrier", 0 ); + origin = player.origin; + } + + team = self gameobjects::get_owner_team(); + otherTeam = util::getOtherTeam( team ); + + bbPrint( "mpobjective", "gametime %d objtype %s team %s playerx %d playery %d playerz %d", gettime(), "ctf_flagdropped", team, origin ); + + self.visuals[0] clientfield::set( "ctf_flag_away", 1 ); + + if ( level.touchReturn ) + { + self gameobjects::allow_carry( "any" ); + level.flagHints[otherTeam] turn_off(); + } + + if ( isdefined( player ) ) + { + util::printAndSoundOnEveryone( team, undefined, &"", undefined, "mp_war_objective_lost" ); + + level thread popups::DisplayTeamMessageToTeam( &"MP_FRIENDLY_FLAG_DROPPED", player, team ); + level thread popups::DisplayTeamMessageToTeam( &"MP_ENEMY_FLAG_DROPPED", player, otherTeam ); + } + else + { + util::printAndSoundOnEveryone( team, undefined, &"", undefined, "mp_war_objective_lost" ); + } + + globallogic_audio::leader_dialog( "ctfFriendlyFlagDropped", team, undefined, "ctf_flag" ); + globallogic_audio::leader_dialog( "ctfEnemyFlagDropped", otherTeam, undefined, "ctf_flag_enemy" ); + /# + if ( isdefined( player ) ) + print( team + " flag dropped" ); + else + print( team + " flag dropped" ); + #/ + + if ( isdefined( player ) ) + { + player playLocalSound("mpl_flag_drop_plr"); + } + + + globallogic_audio::play_2d_on_team( "mpl_flagdrop_sting_friend", otherTeam ); + globallogic_audio::play_2d_on_team( "mpl_flagdrop_sting_enemy", team ); + + if ( level.touchReturn ) + { + self gameobjects::set_3d_icon( "friendly", level.iconReturn3D ); + self gameobjects::set_2d_icon( "friendly", level.iconReturn2D ); + } + else + { + self gameobjects::set_3d_icon( "friendly", level.iconDropped3D ); + self gameobjects::set_2d_icon( "friendly", level.iconDropped2D ); + } + self gameobjects::set_visible_team( "any" ); + self gameobjects::set_3d_icon( "enemy", level.iconCapture3D ); + self gameobjects::set_2d_icon( "enemy", level.iconCapture2D ); + + thread sound::play_on_players( game["flag_dropped_sound"], game["attackers"] ); + + self thread returnFlagAfterTimeMsg( level.idleFlagReturnTime ); + + // remove carrier influencers + if ( isdefined( player ) ) + { + player removeInfluencers(); + } + + // create new influencers on the flag + ss = level.spawnsystem; + player_team_mask = util::getTeamMask( otherTeam ); // this is the player that has the flag's team + enemy_team_mask = util::getTeamMask( team ); // and his enemies + + if ( isdefined( player ) ) + flag_origin = player.origin; + else + flag_origin = self.curorigin; + + self.spawn_influencer_dropped = self.trigger spawning::create_entity_influencer( "ctf_flag_dropped", player_team_mask|enemy_team_mask ); + SetInfluencerTimeOut( self.spawn_influencer_dropped, level.idleFlagReturnTime ); + +} + + +function onPickup( player ) +{ + carrierKilledBy = self.carrierKilledBy; + self.carrierKilledBy = undefined; + if ( isdefined( self.spawn_influencer_dropped ) ) + { + self.trigger spawning::remove_influencer( self.spawn_influencer_dropped ); + self.spawn_influencer_dropped = undefined; + } + + player AddPlayerStatWithGameType( "PICKUPS", 1 ); + + //scoreevents::processScoreEvent( "flag_grab", player ); + + if ( level.touchReturn ) + { + self gameobjects::allow_carry( "enemy" ); + } + + // always clear influencers. we'll create new ones if it's been picked up by an enemy. + self removeInfluencers(); + + team = self gameobjects::get_owner_team(); + otherTeam = util::getOtherTeam( team ); + + self clearReturnFlagHudElems(); + + if ( isdefined( player ) && player.pers["team"] == team ) + { + self notify("picked_up"); + + util::printAndSoundOnEveryone( team, undefined, &"", undefined, "mp_obj_returned" ); + + if( isdefined(player.pers["returns"]) ) + { + player.pers["returns"]++; + player.returns = player.pers["returns"]; + } + if ( isdefined(carrierKilledBy) && carrierKilledBy == player ) + { + scoreevents::processScoreEvent( "flag_carrier_kill_return_close", player ); + } + else if (distancesquared(self.trigger.baseOrigin, player.origin) > 300*300) + { + scoreevents::processScoreEvent( "flag_return", player ); + } + demo::bookmark( "event", gettime(), player ); + + player AddPlayerStatWithGameType( "RETURNS", 1 ); + + level thread popups::DisplayTeamMessageToTeam( &"MP_FRIENDLY_FLAG_RETURNED", player, team ); + level thread popups::DisplayTeamMessageToTeam( &"MP_ENEMY_FLAG_RETURNED", player, otherTeam ); + + self.visuals[0] clientfield::set( "ctf_flag_away", 0 ); + self gameobjects::set_flags( 0 ); + + bbPrint( "mpobjective", "gametime %d objtype %s team %s playerx %d playery %d playerz %d", gettime(), "ctf_flagreturn", team, player.origin ); + player RecordGameEvent("return"); + + // want to return the flag here + self returnFlag(); + /# + if ( isdefined( player ) ) + print( team + " flag returned" ); + else + print( team + " flag returned" ); + #/ + + return; + } + else + { + bbPrint( "mpobjective", "gametime %d objtype %s team %s playerx %d playery %d playerz %d", gettime(), "ctf_flagpickup", team, player.origin ); + player RecordGameEvent("pickup"); + + scoreevents::processScoreEvent( "flag_grab", player ); + demo::bookmark( "event", gettime(), player ); + + util::printAndSoundOnEveryone( otherteam, undefined, &"", undefined, "mp_obj_taken", "mp_enemy_obj_taken" ); + + level thread popups::DisplayTeamMessageToTeam( &"MP_FRIENDLY_FLAG_TAKEN", player, team ); + level thread popups::DisplayTeamMessageToTeam( &"MP_ENEMY_FLAG_TAKEN", player, otherTeam ); + + globallogic_audio::leader_dialog( "ctfFriendlyFlagTaken", team, undefined, "ctf_flag" ); + globallogic_audio::leader_dialog( "ctfEnemyFlagTaken", otherTeam, undefined, "ctf_flag_enemy" ); + + player.isFlagCarrier = true; + player.flagCarried = self; + player playLocalSound("mpl_flag_pickup_plr"); + player clientfield::set( "ctf_flag_carrier", 1 ); + self gameobjects::set_flags( 1 ); + + globallogic_audio::play_2d_on_team( "mpl_flagget_sting_friend", otherTeam ); + globallogic_audio::play_2d_on_team( "mpl_flagget_sting_enemy", team ); + + if ( level.enemyCarrierVisible ) + { + self gameobjects::set_visible_team( "any" ); + } + else + { + self gameobjects::set_visible_team( "enemy" ); + } + + self gameobjects::set_2d_icon( "friendly", level.iconKill2D ); + self gameobjects::set_3d_icon( "friendly", level.iconKill3D ); + self gameobjects::set_2d_icon( "enemy", level.iconEscort2D ); + self gameobjects::set_3d_icon( "enemy", level.iconEscort3D ); + + player thread claim_trigger( level.flagHints[otherTeam] ); + + update_hints(); + + + //Reset flashback here + player resetflashback(); + + /#print( team + " flag taken" );#/ + + ss = level.spawnsystem; + player_team_mask = util::getTeamMask( otherTeam ); // this is the player that has the flag's team + enemy_team_mask = util::getTeamMask( team ); // and his enemies + + player.spawn_influencer_friendly_carrier = player spawning::create_entity_masked_friendly_influencer( "ctf_carrier_friendly", player_team_mask ); + player.spawn_influencer_enemy_carrier = player spawning::create_entity_masked_enemy_influencer( "ctf_carrier_enemy", enemy_team_mask ); + } + +} +function OnPickupMusicState ( player ) +{ + self endon( "disconnect" ); + self endon( "death" ); + + // wait 6 seconds and see if the player still has the flag. + wait (6); + if (player.isFlagCarrier) + { + //Was the SUSPENSE state changes to ACTION - removed state CDC - 6/19/12 + } +} +function isHome() +{ + if ( isdefined( self.carrier ) ) + return false; + + if ( self.curOrigin != self.trigger.baseOrigin ) + return false; + + return true; +} + +function returnFlag() +{ + team = self gameobjects::get_owner_team(); + otherTeam = util::getOtherTeam(team); + + globallogic_audio::play_2d_on_team( "mpl_flagreturn_sting", team ); + globallogic_audio::play_2d_on_team( "mpl_flagreturn_sting", otherTeam ); + + level.teamFlagZones[otherTeam] gameobjects::allow_use( "friendly" ); + level.teamFlagZones[otherTeam] gameobjects::set_visible_team( "friendly" ); + + update_hints(); + + if ( level.touchReturn ) + { + self gameobjects::allow_carry( "enemy" ); + } + self gameobjects::return_home(); + self gameobjects::set_visible_team( "any" ); + //TODO: Add 2D Icons + self gameobjects::set_3d_icon( "friendly", level.iconDefend3D ); + self gameobjects::set_2d_icon( "friendly", level.iconDefend2D ); + self gameobjects::set_3d_icon( "enemy", level.iconCapture3D ); + self gameobjects::set_2d_icon( "enemy", level.iconCapture2D ); + + globallogic_audio::leader_dialog( "ctfFriendlyFlagReturned", team, undefined, "ctf_flag" ); + globallogic_audio::leader_dialog( "ctfEnemyFlagReturned", otherTeam, undefined, "ctf_flag_enemy" ); +} + + +function onCapture( player ) +{ + team = player.pers["team"]; + enemyTeam = util::getOtherTeam( team ); + time = gettime(); + + playerTeamsFlag = level.teamFlags[team]; + + if ( (level.flagCaptureCondition == 1) && playerTeamsFlag gameobjects::is_object_away_from_home() ) + { + return; + } + + if ( !isdefined( player.carryObject ) ) + { + return; // the carryObject can be undefined if the player dies a frame before (and possibly other edges cases) + } + + util::printAndSoundOnEveryone( team, undefined, &"", undefined, "mp_obj_captured", "mp_enemy_obj_captured" ); + bbPrint( "mpobjective", "gametime %d objtype %s team %s playerx %d playery %d playerz %d", time, "ctf_flagcapture", enemyTeam, player.origin ); // flag BELONGS to enemyTeam + + game["challenge"][team]["capturedFlag"] = true; + + if( isdefined(player.pers["captures"]) ) + { + player.pers["captures"]++; + player.captures = player.pers["captures"]; + } + + demo::bookmark( "event", gettime(), player ); + player AddPlayerStatWithGameType( "CAPTURES", 1 ); + + level thread popups::DisplayTeamMessageToTeam( &"MP_ENEMY_FLAG_CAPTURED", player, team ); + level thread popups::DisplayTeamMessageToTeam( &"MP_FRIENDLY_FLAG_CAPTURED", player, enemyTeam ); + + globallogic_audio::play_2d_on_team( "mpl_flagcapture_sting_enemy", enemyTeam ); + globallogic_audio::play_2d_on_team( "mpl_flagcapture_sting_friend", team ); + + player giveFlagCaptureXP( player ); + + /#print( enemyTeam + " flag captured" );#/ + + flag = player.carryObject; + + player challenges::capturedObjective( time, flag.trigger ); + + flag.dontAnnounceReturn = true; + flag gameobjects::return_home(); + flag.dontAnnounceReturn = undefined; + + otherTeam = util::getOtherTeam(team); + level.teamFlags[otherTeam] gameobjects::allow_carry( "enemy" ); + level.teamFlags[otherTeam] gameobjects::set_visible_team( "any" ); + level.teamFlags[otherTeam] gameobjects::return_home(); + level.teamFlagZones[otherTeam] gameobjects::allow_use( "friendly" ); + + player.isFlagCarrier = false; + player.flagCarried = undefined; + player clientfield::set( "ctf_flag_carrier", 0 ); + + // execution will stop on this line on last flag cap of a level + globallogic_score::giveTeamScoreForObjective( team, 1 ); + + // NOTE: team is the team of the capturing player, unlike in other flag events + globallogic_audio::leader_dialog( "ctfEnemyFlagCaptured", team, undefined, "ctf_flag_enemy" ); + globallogic_audio::leader_dialog( "ctfFriendlyFlagCaptured", enemyTeam, undefined, "ctf_flag" ); + + flag removeInfluencers(); + player removeInfluencers(); +} + +function giveFlagCaptureXP( player ) +{ + scoreevents::processScoreEvent( "flag_capture", player ); + player RecordGameEvent("capture"); +} + +function onReset() +{ + update_hints(); + + team = self gameobjects::get_owner_team(); + + self gameobjects::set_3d_icon( "friendly", level.iconDefend3D ); + self gameobjects::set_2d_icon( "friendly", level.iconDefend2D ); + self gameobjects::set_3d_icon( "enemy", level.iconCapture3D ); + self gameobjects::set_2d_icon( "enemy", level.iconCapture2D ); + + if ( level.touchReturn ) + { + self gameobjects::allow_carry( "enemy" ); + } + + level.teamFlagZones[team] gameobjects::set_visible_team( "friendly" ); + level.teamFlagZones[team] gameobjects::allow_use( "friendly" ); + + self.visuals[0] clientfield::set( "ctf_flag_away", 0 ); + self gameobjects::set_flags( 0 ); + self clearReturnFlagHudElems(); + self removeInfluencers(); +} + +function getOtherFlag( flag ) +{ + if ( flag == level.flags[0] ) + return level.flags[1]; + + return level.flags[0]; +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( isdefined( attacker ) && isplayer( attacker ) ) + { + for ( index = 0; index < level.flags.size; index++ ) + { + flagTeam = "invalidTeam"; + inFlagRadius = false; + defendedFlag = false; + offendedFlag = false; + + flagCarrier = level.flags[index].carrier; + if ( isdefined( flagCarrier ) ) + { + flagOrigin = level.flags[index].carrier.origin; + isCarried = true; + + if ( isPlayer( attacker ) && ( attacker.pers["team"] != self.pers["team"] ) ) + { + if ( isdefined( level.flags[index].carrier.attackerData ) ) + { + if ( level.flags[index].carrier != attacker ) + { + if ( isdefined( level.flags[index].carrier.attackerData[self.clientid] ) ) + { + scoreevents::processScoreEvent( "rescue_flag_carrier", attacker, undefined, weapon ); + } + } + } + } + } + else + { + flagOrigin = level.flags[index].curorigin; + isCarried = false; + } + + dist = Distance2dSquared(self.origin, flagOrigin); + if ( dist < level.defaultOffenseRadiusSQ ) + { + inFlagRadius = true; + if ( level.flags[index].ownerteam == attacker.pers["team"] ) + defendedFlag = true; + else + offendedFlag = true; + } + dist = Distance2dSquared(attacker.origin, flagOrigin); + if ( dist < level.defaultOffenseRadiusSQ ) + { + inFlagRadius = true; + if ( level.flags[index].ownerteam == attacker.pers["team"] ) + defendedFlag = true; + else + offendedFlag = true; + } + + + if ( inFlagRadius && isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] ) + { + if ( defendedFlag ) + { + if ( isdefined( self.isFlagCarrier ) && self.isFlagCarrier ) + { + scoreevents::processScoreEvent( "kill_flag_carrier", attacker, undefined, weapon ); + attacker AddPlayerStat( "kill_carrier", 1 ); + } + else + { + scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon ); + } + + self RecordKillModifier("assaulting"); + } + if ( offendedFlag ) + { + if ( isCarried == true ) + { + if ( isdefined ( flagCarrier ) && attacker == flagCarrier ) + { + scoreevents::processScoreEvent( "killed_enemy_while_carrying_flag", attacker, undefined, weapon ); + } + else + { + scoreevents::processScoreEvent( "defend_flag_carrier", attacker, undefined, weapon ); + attacker addplayerstat( "defend_carrier", 1 ); + } + } + else + { + scoreevents::processScoreEvent( "killed_defender", attacker, undefined, weapon ); + } + self RecordKillModifier("defending"); + } + } + } + + victim = self; + + foreach( flag_zone in level.flagZones ) + { + if ( isdefined( attacker.team ) && ( attacker != victim ) && isdefined( victim.team ) ) + { + dist_to_zone_origin = Distance2dSquared( attacker.origin, flag_zone.origin ); + victim_dist_to_zone_origin = Distance2dSquared( victim.origin, flag_zone.origin ); + if ( victim_dist_to_zone_origin < level.defaultOffenseRadiusSQ || dist_to_zone_origin < level.defaultOffenseRadiusSQ ) + { + if ( victim.team == flag_zone.team ) + { + attacker thread challenges::killedBaseDefender( flag_zone.trigger ); + } + else + { + attacker thread challenges::killedBaseOffender( flag_zone.trigger, weapon ); + } + } + } + } + } + + if ( !isdefined( self.isFlagCarrier ) || !self.isFlagCarrier ) + return; + + if ( isdefined( attacker ) && isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] ) + { + if ( isdefined ( self.flagCarried ) ) + { + for ( index = 0; index < level.flags.size; index++ ) + { + currentFlag = level.flags[index]; + if ( currentFlag.ownerteam == self.team ) + { + if ( currentFlag.curOrigin == currentFlag.trigger.baseOrigin ) + { + dist = Distance2dSquared(self.origin, currentFlag.curOrigin ); + if ( dist < level.defaultOffenseRadiusSQ ) + { + self.flagCarried.carrierKilledBy = attacker; + break; + } + } + } + } + } + + attacker RecordGameEvent("kill_carrier"); + self RecordKillModifier("carrying"); + } +} + +function createReturnMessageElems() +{ + level.ReturnMessageElems = []; + + level.ReturnMessageElems["allies"]["axis"] = hud::createServerTimer( "objective", 1.4, "allies" ); + level.ReturnMessageElems["allies"]["axis"] hud::setPoint( "TOPRIGHT", "TOPRIGHT", 0, 0 ); + level.ReturnMessageElems["allies"]["axis"].label = &"MP_ENEMY_FLAG_RETURNING_IN"; + level.ReturnMessageElems["allies"]["axis"].alpha = 0; + level.ReturnMessageElems["allies"]["axis"].archived = false; + level.ReturnMessageElems["allies"]["allies"] = hud::createServerTimer( "objective", 1.4, "allies" ); + level.ReturnMessageElems["allies"]["allies"] hud::setPoint( "TOPRIGHT", "TOPRIGHT", 0, 20 ); + level.ReturnMessageElems["allies"]["allies"].label = &"MP_YOUR_FLAG_RETURNING_IN"; + level.ReturnMessageElems["allies"]["allies"].alpha = 0; + level.ReturnMessageElems["allies"]["allies"].archived = false; + + level.ReturnMessageElems["axis"]["allies"] = hud::createServerTimer( "objective", 1.4, "axis" ); + level.ReturnMessageElems["axis"]["allies"] hud::setPoint( "TOPRIGHT", "TOPRIGHT", 0, 0 ); + level.ReturnMessageElems["axis"]["allies"].label = &"MP_ENEMY_FLAG_RETURNING_IN"; + level.ReturnMessageElems["axis"]["allies"].alpha = 0; + level.ReturnMessageElems["axis"]["allies"].archived = false; + level.ReturnMessageElems["axis"]["axis"] = hud::createServerTimer( "objective", 1.4, "axis" ); + level.ReturnMessageElems["axis"]["axis"] hud::setPoint( "TOPRIGHT", "TOPRIGHT", 0, 20 ); + level.ReturnMessageElems["axis"]["axis"].label = &"MP_YOUR_FLAG_RETURNING_IN"; + level.ReturnMessageElems["axis"]["axis"].alpha = 0; + level.ReturnMessageElems["axis"]["axis"].archived = false; +} + +function returnFlagAfterTimeMsg( time ) +{ + if ( level.touchReturn || level.idleFlagReturnTime == 0 ) + return; + + self notify("returnFlagAfterTimeMsg"); + self endon("returnFlagAfterTimeMsg"); + + result = returnFlagHudElems( time ); + + self removeInfluencers(); + self clearReturnFlagHudElems(); + + if ( !isdefined( result ) ) // returnFlagHudElems hit an endon + return; + +// self returnFlag(); +} + +function returnFlagHudElems( time ) +{ + self endon("picked_up"); + level endon("game_ended"); + + ownerteam = self gameobjects::get_owner_team(); + + assert( !level.ReturnMessageElems["axis"][ownerteam].alpha ); + level.ReturnMessageElems["axis"][ownerteam].alpha = 1; + level.ReturnMessageElems["axis"][ownerteam] setTimer( time ); + + assert( !level.ReturnMessageElems["allies"][ownerteam].alpha ); + level.ReturnMessageElems["allies"][ownerteam].alpha = 1; + level.ReturnMessageElems["allies"][ownerteam] setTimer( time ); + + if( time <= 0 ) + return false; + else + wait time; + + return true; +} + +function clearReturnFlagHudElems() +{ + ownerteam = self gameobjects::get_owner_team(); + + level.ReturnMessageElems["allies"][ownerteam].alpha = 0; + level.ReturnMessageElems["axis"][ownerteam].alpha = 0; +} + +function turn_on() +{ + if ( level.hardcoreMode ) + return; + + self.origin = self.original_origin; +} + +function turn_off() +{ + self.origin = ( self.original_origin[0], self.original_origin[1], self.original_origin[2] - 10000); +} + +function update_hints() +{ + allied_flag = level.teamFlags["allies"]; + axis_flag = level.teamFlags["axis"]; + + if ( !level.touchReturn ) + return; + + if ( isdefined(allied_flag.carrier) && axis_flag gameobjects::is_object_away_from_home() ) + { + level.flagHints["axis"] turn_on(); + } + else + { + level.flagHints["axis"] turn_off(); + } + + if ( isdefined(axis_flag.carrier) && allied_flag gameobjects::is_object_away_from_home() ) + { + level.flagHints["allies"] turn_on(); + } + else + { + level.flagHints["allies"] turn_off(); + } +} + +function claim_trigger( trigger ) +{ + self endon("disconnect"); + self ClientClaimTrigger( trigger ); + + self waittill("drop_object"); + self ClientReleaseTrigger( trigger ); +} + +function createFlagSpawnInfluencer( entityTeam ) +{ + otherteam = util::getOtherTeam(entityTeam); + team_mask = util::getTeamMask( entityTeam ); + other_team_mask = util::getTeamMask( otherteam ); + + self.spawn_influencer_friendly = self spawning::create_influencer( "ctf_base_friendly", self.trigger.origin, team_mask ); + self.spawn_influencer_enemy = self spawning::create_influencer( "ctf_base_friendly", self.trigger.origin, other_team_mask ); +} + +function ctf_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_penalty = globallogic_defaults::default_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ); + + if ( ( isdefined( self.isFlagCarrier ) && self.isFlagCarrier ) ) + { + teamkill_penalty = teamkill_penalty * level.teamKillPenaltyMultiplier; + } + + return teamkill_penalty; +} + +function ctf_getTeamKillScore( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_score = rank::getScoreInfoValue( "kill" ); + + if ( ( isdefined( self.isFlagCarrier ) && self.isFlagCarrier ) ) + { + teamkill_score = teamkill_score * level.teamKillScoreMultiplier; + } + + return int(teamkill_score); +} \ No newline at end of file diff --git a/mp/gametypes/dem.csc b/mp/gametypes/dem.csc new file mode 100644 index 0000000..c8a48d9 --- /dev/null +++ b/mp/gametypes/dem.csc @@ -0,0 +1,27 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; + +#using scripts\mp\gametypes\_globallogic; + + + +function main() +{ + callback::on_spawned( &on_player_spawned ); + if( GetGametypeSetting( "silentPlant" ) != 0 ) + setsoundcontext( "bomb_plant", "silent" ); +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} + +function on_player_spawned( localClientNum ) +{ + self thread globallogic::watch_plant_sound( localClientNum ); +} \ No newline at end of file diff --git a/mp/gametypes/dem.gsc b/mp/gametypes/dem.gsc new file mode 100644 index 0000000..cc80312 --- /dev/null +++ b/mp/gametypes/dem.gsc @@ -0,0 +1,1480 @@ +#using scripts\shared\challenges_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\exploder_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\medals_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; +#using scripts\mp\gametypes\_dogtags; +#using scripts\mp\gametypes\_globallogic_spawn; + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_defaults; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\_spectating; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; + +// Rallypoints should be destroyed on leaving your team/getting killed +// Compass icons need to be looked at +// Doesn't seem to be setting angle on spawn so that you are facing your rallypoint + +/* + Demolition + Attackers objective: Bomb 2 positions + Defenders objective: Defend these 2 positions / Defuse planted bombs + Round ends: When both bomb positions are exploded, or roundlength time is reached + Map ends: When one team reaches the score limit, or time limit or round limit is reached + Respawning: Players respawn upon death + + Level requirements + ------------------ + Allied Spawnpoints: + classname mp_dem_spawn_attacker_start + Allied players spawn from these. Place at least 16 of these relatively close together. + + Axis Spawnpoints: + classname mp_dem_spawn_defender_start + Axis players spawn from these. Place at least 16 of these relatively close together. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Bombzones: + classname trigger_multiple + targetname bombzone_dem + script_gameobjectname bombzone_dem + script_bombmode_original + script_bombmode_single + script_bombmode_dual + script_team Set to allies or axis. This is used to set which team a bombzone is used by in dual bomb mode. + script_label Set to A or B. This sets the letter shown on the compass in original mode. + This is a volume of space in which the bomb can planted. Must contain an origin brush. + + Bomb: + classname trigger_lookat + targetname bombtrigger + script_gameobjectname bombzone + This should be a 16x16 unit trigger with an origin brush placed so that it's center lies on the bottom plane of the trigger. + Must be in the level somewhere. This is the trigger that is used when defusing a bomb. + It gets moved to the position of the planted bomb model. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + This sets the nationalities of the teams. Allies can be american, british, or russian. Axis can be german. + + game["attackers"] = "allies"; + game["defenders"] = "axis"; + This sets which team is attacking and which team is defending. Attackers plant the bombs. Defenders protect the targets. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals + + Exploder Effects: + Setting script_noteworthy on a bombzone trigger to an exploder group can be used to trigger additional effects. +*/ + +/*QUAKED mp_dem_spawn_attacker_start (0.0 1.0 0.0) (-16 -16 0) (16 16 72) +Attacking players spawn randomly at one of these positions at the beginning of a round.*/ + +/*QUAKED mp_dem_spawn_defender_start (1.0 0.0 0.0) (-16 -16 0) (16 16 72) +Defending players spawn randomly at one of these positions at the beginning of a round.*/ + +/*QUAKED mp_dem_spawn_attackerot_start (0.0 1.0 0.0) (-16 -16 0) (16 16 72) +Attacking players spawn randomly at one of these positions at the beginning of a round.*/ + +/*QUAKED mp_dem_spawn_defenderot_start (1.0 0.0 0.0) (-16 -16 0) (16 16 72) +Defending players spawn randomly at one of these positions at the beginning of a round.*/ + +/*QUAKED mp_dem_spawn_attacker (0.0 1.0 0.0) (-16 -16 0) (16 16 72) +Attacking players may spawn randomly at one of these positions after death.*/ + +/*QUAKED mp_dem_spawn_attacker_a (0.0 1.0 0.0) (-16 -16 0) (16 16 72) +Attacking players may spawn randomly at one of these positions after death if site A has been destroyed.*/ + +/*QUAKED mp_dem_spawn_attacker_b (0.0 1.0 0.0) (-16 -16 0) (16 16 72) +Attacking players may spawn randomly at one of these positions after death if site B has been destroyed.*/ + +/*QUAKED mp_dem_spawn_defender (1.0 0.0 0.0) (-16 -16 0) (16 16 72) +Defending players may spawn randomly at one of these positions after death.*/ + +/*QUAKED mp_dem_spawn_defender_a (1.0 0.0 0.0) (-16 -16 0) (16 16 72) +Defending players may spawn randomly at one of these positions after death if site A is still intact.*/ + +/*QUAKED mp_dem_spawn_defender_b (1.0 0.0 0.0) (-16 -16 0) (16 16 72) +Defending players may spawn randomly at one of these positions after death if site B is still intact.*/ + + + + + + + +#precache( "fx", "explosions/fx_exp_bomb_demo_mp" ); +#precache( "material", "compass_waypoint_target" ); +#precache( "material", "compass_waypoint_target_a" ); +#precache( "material", "compass_waypoint_target_b" ); +#precache( "material", "compass_waypoint_defend" ); +#precache( "material", "compass_waypoint_defend_a" ); +#precache( "material", "compass_waypoint_defend_b" ); +#precache( "material", "compass_waypoint_defuse" ); +#precache( "material", "compass_waypoint_defuse_a" ); +#precache( "material", "compass_waypoint_defuse_b" ); +#precache( "model", "p7_mp_suitcase_bomb" ); +#precache( "objective", "dem_a" ); +#precache( "objective", "dem_b" ); +#precache( "objective", "dem_overtime" ); +#precache( "string", "OBJECTIVES_DEM_ATTACKER" ); +#precache( "string", "OBJECTIVES_SD_DEFENDER" ); +#precache( "string", "OBJECTIVES_DEM_ATTACKER_SCORE" ); +#precache( "string", "OBJECTIVES_SD_DEFENDER_SCORE" ); +#precache( "string", "OBJECTIVES_DEM_ATTACKER_HINT" ); +#precache( "string", "OBJECTIVES_DEM_ATTACKER_OVERTIME_HINT" ); +#precache( "string", "OBJECTIVES_SD_DEFENDER_HINT" ); +#precache( "string", "MP_EXPLOSIVES_RECOVERED_BY" ); +#precache( "string", "MP_EXPLOSIVES_DROPPED_BY" ); +#precache( "string", "MP_EXPLOSIVES_PLANTED_BY" ); +#precache( "string", "MP_EXPLOSIVES_DEFUSED_BY" ); +#precache( "string", "PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); +#precache( "string", "PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); +#precache( "string", "MP_PLANTING_EXPLOSIVE" ); +#precache( "string", "MP_DEFUSING_EXPLOSIVE" ); +#precache( "string", "MP_TIME_EXTENDED" ); +#precache( "string", "MP_TARGET_DESTROYED" ); +#precache( "string", "MP_BOMB_DEFUSED" ); +#precache( "triggerstring", "PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); +#precache( "triggerstring", "PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); + +function main() +{ + globallogic::init(); + + util::registerRoundSwitch( 0, 9 ); + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 500 ); + util::registerRoundLimit( 0, 12 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.teamBased = true; + level.overrideTeamScore = true; + level.onPrecacheGameType =&onPrecacheGameType; + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.playerSpawnedCB =&dem_playerSpawnedCB; + level.onPlayerKilled =&onPlayerKilled; + level.onDeadEvent =&onDeadEvent; + level.onOneLeftEvent =&onOneLeftEvent; + level.onTimeLimit =&onTimeLimit; + level.onRoundSwitch =&onRoundSwitch; + level.getTeamKillPenalty =&dem_getTeamKillPenalty; + level.getTeamKillScore =&dem_getTeamKillScore; + level.getTimeLimit =&getTimeLimit; + level.shouldPlayOvertimeRound =&shouldPlayOvertimeRound; + level.lastBombExplodeTime = undefined; + level.lastBombExplodeByTeam = undefined; + level.ddBombModel = []; + + level.endGameOnScoreLimit = false; + + level.demBombzoneName = "bombzone_dem"; + + gameobjects::register_allowed_gameobject( level.gameType ); + gameobjects::register_allowed_gameobject( "sd" ); + gameobjects::register_allowed_gameobject( "blocker" ); + gameobjects::register_allowed_gameobject( level.demBombzoneName ); + + globallogic_audio::set_leader_gametype_dialog ( "startDemolition", "hcStartDemolition", "objDestroy", "objDefend" ); + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "plants", "defuses", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "plants", "defuses" ); +} + +function onPrecacheGameType() +{ + game["bombmodelname"] = "t5_weapon_briefcase_bomb_world"; + game["bombmodelnameobj"] = "t5_weapon_briefcase_bomb_world"; + game["bomb_dropped_sound"] = "fly_bomb_drop_plr"; + game["bomb_recovered_sound"] = "fly_bomb_pickup_plr"; +} + +function dem_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_penalty = globallogic_defaults::default_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ); + + if ( ( isdefined( self.isDefusing ) && self.isDefusing ) || ( isdefined( self.isPlanting ) && self.isPlanting ) ) + { + teamkill_penalty = teamkill_penalty * level.teamKillPenaltyMultiplier; + } + + return teamkill_penalty; +} + +function dem_getTeamKillScore( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_score = rank::getScoreInfoValue( "team_kill" ); + + if ( ( isdefined( self.isDefusing ) && self.isDefusing ) || ( isdefined( self.isPlanting ) && self.isPlanting ) ) + { + teamkill_score = teamkill_score * level.teamKillScoreMultiplier; + } + + return int(teamkill_score); +} + + +function onRoundSwitch() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["teamScores"]["allies"] == level.scorelimit - 1 && game["teamScores"]["axis"] == level.scorelimit - 1 ) + { + // overtime! team that's ahead in kills gets to defend. + aheadTeam = getBetterTeam(); + if ( aheadTeam != game["defenders"] ) + { + game["switchedsides"] = !game["switchedsides"]; + } + level.halftimeType = "overtime"; + + if( isdefined( level.bombZones[1] ) ) + level.bombZones[1] gameobjects::disable_object(); + } + else + { + level.halftimeType = "halftime"; + game["switchedsides"] = !game["switchedsides"]; + } +} + +function getBetterTeam() +{ + kills["allies"] = 0; + kills["axis"] = 0; + deaths["allies"] = 0; + deaths["axis"] = 0; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + team = player.pers["team"]; + if ( isdefined( team ) && (team == "allies" || team == "axis") ) + { + kills[ team ] += player.kills; + deaths[ team ] += player.deaths; + } + } + + if ( kills["allies"] > kills["axis"] ) + return "allies"; + else if ( kills["axis"] > kills["allies"] ) + return "axis"; + + // same number of kills + + if ( deaths["allies"] < deaths["axis"] ) + return "allies"; + else if ( deaths["axis"] < deaths["allies"] ) + return "axis"; + + // same number of deaths + + if ( randomint(2) == 0 ) + return "allies"; + return "axis"; +} + +function onStartGameType() +{ + SetBombTimer( "A", 0 ); + setMatchFlag( "bomb_timer_a", 0 ); + SetBombTimer( "B", 0 ); + setMatchFlag( "bomb_timer_b", 0 ); + + level.usingExtraTime = false; + + // we'll handle the sideswitching ourselves + level.spawnsystem.sideSwitching = 0; + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + setClientNameMode( "manual_change" ); + + game["strings"]["target_destroyed"] = &"MP_TARGET_DESTROYED"; + game["strings"]["bomb_defused"] = &"MP_BOMB_DEFUSED"; + + level._effect["bombexplosion"] = "explosions/fx_exp_bomb_demo_mp"; + + if ( isdefined( game["overtime_round"] ) ) + { + util::setObjectiveText( game["attackers"], &"OBJECTIVES_DEM_ATTACKER" ); + util::setObjectiveText( game["defenders"], &"OBJECTIVES_DEM_ATTACKER" ); + if ( level.splitscreen ) + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_DEM_ATTACKER" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_DEM_ATTACKER" ); + } + else + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_DEM_ATTACKER_SCORE" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_DEM_ATTACKER_SCORE" ); + } + + util::setObjectiveHintText( game["attackers"], &"OBJECTIVES_DEM_ATTACKER_OVERTIME_HINT" ); + util::setObjectiveHintText( game["defenders"], &"OBJECTIVES_DEM_ATTACKER_OVERTIME_HINT" ); + } + else + { + util::setObjectiveText( game["attackers"], &"OBJECTIVES_DEM_ATTACKER" ); + util::setObjectiveText( game["defenders"], &"OBJECTIVES_SD_DEFENDER" ); + if ( level.splitscreen ) + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_DEM_ATTACKER" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_SD_DEFENDER" ); + } + else + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_DEM_ATTACKER_SCORE" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_SD_DEFENDER_SCORE" ); + } + util::setObjectiveHintText( game["attackers"], &"OBJECTIVES_DEM_ATTACKER_HINT" ); + util::setObjectiveHintText( game["defenders"], &"OBJECTIVES_SD_DEFENDER_HINT" ); + } + + bombZones = getEntArray( level.demBombzoneName, "targetname" ); + if ( bombZones.size == 0 ) + level.demBombzoneName = "bombzone"; + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::drop_spawn_points( "mp_dem_spawn_attacker_a" ); + spawnlogic::drop_spawn_points( "mp_dem_spawn_attacker_b" ); + spawnlogic::drop_spawn_points( "mp_dem_spawn_defender_a" ); + spawnlogic::drop_spawn_points( "mp_dem_spawn_defender_b" ); + if ( !isdefined( game["overtime_round"] ) ) + { + spawnlogic::place_spawn_points( "mp_dem_spawn_defender_start" ); + spawnlogic::place_spawn_points( "mp_dem_spawn_attacker_start" ); + } + else + { + spawnlogic::place_spawn_points( "mp_dem_spawn_attackerot_start" ); + spawnlogic::place_spawn_points( "mp_dem_spawn_defenderot_start" ); + } + spawnlogic::add_spawn_points( game["attackers"], "mp_dem_spawn_attacker" ); + spawnlogic::add_spawn_points( game["defenders"], "mp_dem_spawn_defender" ); + if ( !isdefined( game["overtime_round"] ) ) + { + spawnlogic::add_spawn_points( game["defenders"], "mp_dem_spawn_defender_a" ); + spawnlogic::add_spawn_points( game["defenders"], "mp_dem_spawn_defender_b" ); + spawnlogic::add_spawn_points( game["attackers"], "mp_dem_spawn_attacker_remove_a" ); + spawnlogic::add_spawn_points( game["attackers"], "mp_dem_spawn_attacker_remove_b" ); + } + + spawning::add_fallback_spawnpoints( game["attackers"], "mp_tdm_spawn" ); + spawning::add_fallback_spawnpoints( game["defenders"], "mp_tdm_spawn" ); + + spawning::updateAllSpawnPoints(); + spawning::update_fallback_spawnpoints(); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + + level.spawn_start = []; + + if ( isdefined( game["overtime_round"] ) ) + { + level.spawn_start["axis"] = spawnlogic::get_spawnpoint_array( "mp_dem_spawn_defenderot_start" ); + level.spawn_start["allies"] = spawnlogic::get_spawnpoint_array( "mp_dem_spawn_attackerot_start" ); + } + else + { + level.spawn_start["axis"] = spawnlogic::get_spawnpoint_array( "mp_dem_spawn_defender_start" ); + level.spawn_start["allies"] = spawnlogic::get_spawnpoint_array( "mp_dem_spawn_attacker_start" ); + } + thread updateGametypeDvars(); + + thread bombs(); + + if( ( isdefined( level.droppedTagRespawn ) && level.droppedTagRespawn ) ) + level.numLives = 1; +} + + +function onSpawnPlayer(predictedSpawn) +{ + self.isPlanting = false; + self.isDefusing = false; + self.isBombCarrier = false; + + spawning::onSpawnPlayer(predictedSpawn); +} + +function dem_playerSpawnedCB() +{ + level notify ( "spawned_player" ); +} + +function onPlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration) +{ + thread checkAllowSpectating(); + + if( ( isdefined( level.droppedTagRespawn ) && level.droppedTagRespawn ) ) + { + should_spawn_tags = self dogtags::should_spawn_tags(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + + // we should spawn tags if one the previous statements were true and we may not spawn + should_spawn_tags = should_spawn_tags && !globallogic_spawn::maySpawn(); + + if( should_spawn_tags ) + level thread dogtags::spawn_dog_tag( self, attacker, &dogtags::onUseDogTag, false ); + } + + bombZone = undefined; + + for ( index = 0; index < level.bombZones.size; index++ ) + { + if ( !isdefined( level.bombZones[index].bombExploded ) || !level.bombZones[index].bombExploded ) + { + dist = Distance2dSquared(self.origin, level.bombZones[index].curorigin); + if ( dist < level.defaultOffenseRadiusSQ ) + { + bombZone = level.bombZones[index]; + break; + } + dist = Distance2dSquared(attacker.origin, level.bombZones[index].curorigin); + if ( dist < level.defaultOffenseRadiusSQ ) + { + inBombZone = true; + break; + } + } + } + + + if ( isdefined( bombZone ) && isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] ) + { + if ( bombZone gameobjects::get_owner_team() != attacker.team ) + { + if ( !isdefined( attacker.dem_offends ) ) + attacker.dem_offends = 0; + + attacker.dem_offends++; + + if ( level.playerOffensiveMax >= attacker.dem_offends ) + { + attacker medals::offenseGlobalCount(); + attacker thread challenges::killedBaseDefender( bombZone.trigger ); + self RecordKillModifier("defending"); + scoreevents::processScoreEvent( "killed_defender", attacker, self, weapon ); + } + else + { + /# + attacker IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU OFFENSIVE CREDIT AS BOOSTING PREVENTION" ); + #/ + } + } + else + { + if ( !isdefined( attacker.dem_defends ) ) + attacker.dem_defends = 0; + + attacker.dem_defends++; + + if ( level.playerDefensiveMax >= attacker.dem_defends ) + { + if( isdefined(attacker.pers["defends"]) ) + { + attacker.pers["defends"]++; + attacker.defends = attacker.pers["defends"]; + } + + attacker medals::defenseGlobalCount(); + attacker thread challenges::killedBaseOffender( bombZone.trigger, weapon ); + self RecordKillModifier("assaulting"); + scoreevents::processScoreEvent( "killed_attacker", attacker, self, weapon ); + } + else + { + /# + attacker IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU DEFENSIVE CREDIT AS BOOSTING PREVENTION" ); + #/ + } + } + } + + if( self.isPlanting == true ) + self RecordKillModifier("planting"); + + if( self.isDefusing == true ) + self RecordKillModifier("defusing"); +} + + +function checkAllowSpectating() +{ + self endon("disconnect"); + + {wait(.05);}; + + update = false; + + livesLeft = !(level.numLives && !self.pers["lives"]); + + if ( !level.aliveCount[ game["attackers"] ] && !livesLeft ) + { + level.spectateOverride[game["attackers"]].allowEnemySpectate = 1; + update = true; + } + if ( !level.aliveCount[ game["defenders"] ] && !livesLeft ) + { + level.spectateOverride[game["defenders"]].allowEnemySpectate = 1; + update = true; + } + if ( update ) + spectating::update_settings(); +} + + +function dem_endGame( winningTeam, endReasonText ) +{ + // set all bombs to non-visible so the hud gets correctly reset for the next round + // going into overtime the hud might still have a bomb b otherwise + foreach( bombZone in level.bombZones ) + { + bombZone gameobjects::set_visible_team( "none" ); + } + + if ( isdefined( winningTeam ) && ( winningTeam != "tie" ) ) + globallogic_score::giveTeamScoreForObjective( winningTeam, 1 ); + + thread globallogic::endGame( winningTeam, endReasonText ); +} + +function onDeadEvent( team ) +{ + if ( level.bombExploded || level.bombDefused ) + return; + + if ( team == "all" ) + { + if ( level.bombPlanted ) + dem_endGame( game["attackers"], game["strings"][game["defenders"]+"_eliminated"] ); + else + dem_endGame( game["defenders"], game["strings"][game["attackers"]+"_eliminated"] ); + } + else if ( team == game["attackers"] ) + { + if ( level.bombPlanted ) + return; + + dem_endGame( game["defenders"], game["strings"][game["attackers"]+"_eliminated"] ); + } + else if ( team == game["defenders"] ) + { + dem_endGame( game["attackers"], game["strings"][game["defenders"]+"_eliminated"] ); + } +} + + +function onOneLeftEvent( team ) +{ + if ( level.bombExploded || level.bombDefused ) + return; + + //if ( team == game["attackers"] ) + warnLastPlayer( team ); +} + + +function onTimeLimit() +{ + if ( isdefined( game["overtime_round"] ) ) + { + dem_endGame( "tie", game["strings"]["time_limit_reached"] ); + } + else + { + if ( level.teamBased ) + { + bombZonesLeft = 0; + + for ( index = 0; index < level.bombZones.size; index++ ) + { + if ( !isdefined( level.bombZones[index].bombExploded ) || !level.bombZones[index].bombExploded ) + bombZonesLeft++; + } + if ( bombZonesLeft == 0 ) + { + dem_endGame( game["attackers"], game["strings"]["target_destroyed"] ); + } + else + { + dem_endGame( game["defenders"], game["strings"]["time_limit_reached"] ); + } + } + else + dem_endGame( "tie", game["strings"]["time_limit_reached"] ); + } +} + + +function warnLastPlayer( team ) +{ + if ( !isdefined( level.warnedLastPlayer ) ) + level.warnedLastPlayer = []; + + if ( isdefined( level.warnedLastPlayer[team] ) ) + return; + + level.warnedLastPlayer[team] = true; + + players = level.players; + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if ( isdefined( player.pers["team"] ) && player.pers["team"] == team && isdefined( player.pers["class"] ) ) + { + if ( player.sessionstate == "playing" && !player.afk ) + break; + } + } + + if ( i == players.size ) + return; + + players[i] thread giveLastAttackerWarning(); +} + + +function giveLastAttackerWarning() +{ + self endon("death"); + self endon("disconnect"); + + fullHealthTime = 0; + interval = .05; + + while(1) + { + if ( self.health != self.maxhealth ) + fullHealthTime = 0; + else + fullHealthTime += interval; + + wait interval; + + if (self.health == self.maxhealth && fullHealthTime >= 3) + break; + } + + //self iprintlnbold(&"MP_YOU_ARE_THE_ONLY_REMAINING_PLAYER"); + self globallogic_audio::leader_dialog_on_player( "roundSuddenDeath" ); +} + + +function updateGametypeDvars() +{ + level.plantTime = GetGametypeSetting( "plantTime" ); + level.defuseTime = GetGametypeSetting( "defuseTime" ); + level.bombTimer = GetGametypeSetting( "bombTimer" ); + level.extraTime = GetGametypeSetting( "extraTime" ); + level.overtimeTimeLimit = GetGametypeSetting( "OvertimetimeLimit" ); + + level.teamKillPenaltyMultiplier = GetGametypeSetting( "teamKillPenalty" ); + level.teamKillScoreMultiplier = GetGametypeSetting( "teamKillScore" ); + level.playerEventsLPM = GetGametypeSetting( "maxPlayerEventsPerMinute" ); + level.bombEventsLPM = GetGametypeSetting( "maxObjectiveEventsPerMinute" ); + level.playerOffensiveMax = GetGametypeSetting( "maxPlayerOffensive" ); + level.playerDefensiveMax = GetGametypeSetting( "maxPlayerDefensive" ); +} + +function resetBombZone() +{ + if ( isdefined( game["overtime_round"] ) ) + { + self gameobjects::set_owner_team( "neutral" ); + self gameobjects::allow_use( "any" ); + } + else + { + self gameobjects::allow_use( "enemy" ); + } + self gameobjects::set_use_time( level.plantTime ); + self gameobjects::set_use_text( &"MP_PLANTING_EXPLOSIVE" ); + self gameobjects::set_use_hint_text( &"PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); + self gameobjects::set_key_object( level.ddBomb ); + self gameobjects::set_2d_icon( "friendly", "waypoint_defend" + self.label ); + self gameobjects::set_3d_icon( "friendly", "waypoint_defend" + self.label ); + self gameobjects::set_2d_icon( "enemy", "waypoint_target" + self.label ); + self gameobjects::set_3d_icon( "enemy", "waypoint_target" + self.label ); + self gameobjects::set_visible_team( "any" ); + self.useWeapon = GetWeapon( "briefcase_bomb" ); +} + +function setUpForDefusing() +{ + self gameobjects::allow_use( "friendly" ); + self gameobjects::set_use_time( level.defuseTime ); + self gameobjects::set_use_text( &"MP_DEFUSING_EXPLOSIVE" ); + self gameobjects::set_use_hint_text( &"PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); + self gameobjects::set_key_object( undefined ); + self gameobjects::set_2d_icon( "friendly", "compass_waypoint_defuse" + self.label ); + self gameobjects::set_3d_icon( "friendly", "waypoint_defuse" + self.label ); + self gameobjects::set_2d_icon( "enemy", "compass_waypoint_defend" + self.label ); + self gameobjects::set_3d_icon( "enemy", "waypoint_defend" + self.label ); + self gameobjects::set_visible_team( "any" ); +} + + +function bombs() +{ + level.bombAPlanted = false; + level.bombBPlanted = false; + level.bombPlanted = false; + level.bombDefused = false; + level.bombExploded = false; + + sdBomb = getEnt( "sd_bomb", "targetname" ); + if ( isdefined( sdBomb ) ) + sdBomb delete(); + + level.bombZones = []; + + bombZones = getEntArray( level.demBombzoneName, "targetname" ); + + for ( index = 0; index < bombZones.size; index++ ) + { + trigger = bombZones[index]; + scriptLabel = trigger.script_label; + visuals = getEntArray( bombZones[index].target, "targetname" ); + clipBrushes = getEntArray( "bombzone_clip"+scriptLabel, "targetname" ); + defuseTrig = getent( visuals[0].target, "targetname" ); + + bombSiteTeamOwner = game["defenders"]; + bombSiteAllowUse = "enemy"; + if ( isdefined( game["overtime_round"] ) ) + { + if ( scriptLabel != "_overtime" ) + { + trigger delete(); + defuseTrig delete(); + visuals[0] delete(); + foreach ( clip in clipBrushes ) + { + clip delete(); + } + continue; + } + bombSiteTeamOwner = "neutral"; + bombSiteAllowUse = "any"; + scriptLabel = "_a"; + } + else if ( scriptLabel == "_overtime" ) + { + trigger delete(); + defuseTrig delete(); + visuals[0] delete(); + foreach ( clip in clipBrushes ) + { + clip delete(); + } + continue; + } + + name = istring("dem" + scriptLabel); + + bombZone = gameobjects::create_use_object( bombSiteTeamOwner, trigger, visuals, (0,0,0), name, true, true ); + bombZone gameobjects::allow_use( bombSiteAllowUse ); + bombZone gameobjects::set_use_time( level.plantTime ); + bombZone gameobjects::set_use_text( &"MP_PLANTING_EXPLOSIVE" ); + bombZone gameobjects::set_use_hint_text( &"PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); + bombZone gameobjects::set_key_object( level.ddBomb ); + + bombZone.label = scriptLabel; + bombZone.index = index; + bombZone gameobjects::set_2d_icon( "friendly", "compass_waypoint_defend" + scriptLabel ); + bombZone gameobjects::set_3d_icon( "friendly", "waypoint_defend" + scriptLabel ); + bombZone gameobjects::set_2d_icon( "enemy", "compass_waypoint_target" + scriptLabel ); + bombZone gameobjects::set_3d_icon( "enemy", "waypoint_target" + scriptLabel ); + bombZone gameobjects::set_visible_team( "any" ); + bombZone.onBeginUse =&onBeginUse; + bombZone.onEndUse =&onEndUse; + bombZone.onUse =&onUseObject; + bombZone.onCantUse =&onCantUse; + bombZone.useWeapon = GetWeapon( "briefcase_bomb" ); + bombZone.visuals[0].killCamEnt = spawn( "script_model", bombZone.visuals[0].origin + (0,0,128) ); + + if ( isdefined( level.bomb_zone_fixup ) ) + [[ level.bomb_zone_fixup ]]( bombZone ); + + for ( i = 0; i < visuals.size; i++ ) + { + if ( isdefined( visuals[i].script_exploder ) ) + { + bombZone.exploderIndex = visuals[i].script_exploder; + break; + } + } + + foreach( visual in bombZone.visuals ) + visual.team = "free"; // for preventing red reticles when pointing at bomb zones + + level.bombZones[level.bombZones.size] = bombZone; + + bombZone.bombDefuseTrig = defuseTrig; + assert( isdefined( bombZone.bombDefuseTrig ) ); + bombZone.bombDefuseTrig.origin += (0,0,-10000); + bombZone.bombDefuseTrig.label = scriptLabel; + + // Add spawn influencer + team_mask = util::getTeamMask( game["attackers"] ); + bombZone.spawnInfluencer = bombZone spawning::create_influencer( "dem_enemy_base", trigger.origin, team_mask ); + } + + for ( index = 0; index < level.bombZones.size; index++ ) + { + array = []; + for ( otherindex = 0; otherindex < level.bombZones.size; otherindex++ ) + { + if ( otherindex != index ) + array[ array.size ] = level.bombZones[otherindex]; + } + level.bombZones[index].otherBombZones = array; + } +} + +function setBombOverheatingAfterWeaponChange( useObject, overheated, heat ) // self == player +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "joined_team"); + self endon ( "joined_spectators"); + + self waittill( "weapon_change", weapon ); + + if ( weapon == useObject.useWeapon ) + { + self SetWeaponOverheating( overheated, heat, weapon ); // resetting overheating allows for quick drop anim to be played + } +} + +function onBeginUse( player ) +{ + timeRemaining = globallogic_utils::getTimeRemaining(); + if (timeRemaining <= level.plantTime * 1000 ) + { + //setGameEndTime( 0 ); + globallogic_utils::pauseTimer(); + level.hasPausedTimer = true; + } + + + if ( self gameobjects::is_friendly_team( player.pers["team"] ) ) + { + player playSound( "mpl_sd_bomb_defuse" ); + player.isDefusing = true; + player thread setBombOverheatingAfterWeaponChange( self, false, 0 ); // overheated specific use weapons play "drop" instead of "quick drop" anims + player thread battlechatter::gametype_specific_battle_chatter( "sd_enemyplant", player.pers["team"] ); + + bestDistance = 9000000; + closestBomb = undefined; + + if ( isdefined( level.ddBombModel ) ) + { + keys = GetArrayKeys( level.ddBombModel ); + for ( bombLabel = 0; bombLabel < keys.size; bombLabel++ ) + { + bomb = level.ddBombModel[ keys[bombLabel] ]; + + if ( !isdefined( bomb ) ) + continue; + + dist = distanceSquared( player.origin, bomb.origin ); + + if ( dist < bestDistance ) + { + bestDistance = dist; + closestBomb = bomb; + } + } + + assert( isdefined(closestBomb) ); + player.defusing = closestBomb; + closestBomb hide(); + } + } + else + { + player.isPlanting = true; + player thread setBombOverheatingAfterWeaponChange( self, false, 0 ); // overheated specific use weapons play "drop" instead of "quick drop" anims + player thread battlechatter::gametype_specific_battle_chatter( "sd_friendlyplant", player.pers["team"] ); + } + + player playSound( "fly_bomb_raise_plr" ); +} + +function onEndUse( team, player, result ) +{ + if ( !isdefined( player ) ) + return; + + if ( !level.bombAPlanted && !level.bombBPlanted ) + { + globallogic_utils::resumeTimer(); + level.hasPausedTimer = false; + } + + player.isDefusing = false; + player.isPlanting = false; + player notify( "event_ended" ); + + if ( self gameobjects::is_friendly_team( player.pers["team"] ) ) + { + if ( isdefined( player.defusing ) && !result ) + { + player.defusing show(); + } + } +} + +function onCantUse( player ) +{ + player iPrintLnBold( &"MP_CANT_PLANT_WITHOUT_BOMB" ); +} + +function onUseObject( player ) +{ + team = player.team; + enemyTeam = util::getOtherTeam( team ); + + self updateEventsPerMinute(); + player updateEventsPerMinute(); + + // planted the bomb + if ( !self gameobjects::is_friendly_team( team ) ) + { + self gameobjects::set_flags( 1 ); + + level thread bombPlanted( self, player ); + /#print( "bomb planted: " + self.label );#/ + + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "dem_bombplant", self.label, team, player.origin ); + +// removed plant audio until finalization of assest TODO : new plant sounds when assests are online +// player playSound( "mpl_sd_bomb_plant" ); + player notify ( "bomb_planted" ); + + thread globallogic_audio::set_music_on_team( "DEM_WE_PLANT", team, 5 ); + thread globallogic_audio::set_music_on_team( "DEM_THEY_PLANT", enemyTeam, 5 ); + + if( isdefined(player.pers["plants"]) ) + { + player.pers["plants"]++; + player.plants = player.pers["plants"]; + } + + if ( !isScoreBoosting( player, self ) ) + { + demo::bookmark( "event", gettime(), player ); + player AddPlayerStatWithGameType( "PLANTS", 1 ); + + scoreevents::processScoreEvent( "planted_bomb", player ); + player RecordGameEvent("plant"); + } + else + { + /# + player IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU PLANT CREDIT AS BOOSTING PREVENTION" ); + #/ + } + + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_PLANTED_BY", player ); + globallogic_audio::leader_dialog( "bombPlanted" ); + } + else + { + self gameobjects::set_flags( 0 ); + + player notify ( "bomb_defused" ); + /#print( "bomb defused: " + self.label );#/ + self thread bombDefused( player ); + self resetBombzone(); + + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "dem_bombdefused", self.label, team, player.origin ); + + if( isdefined(player.pers["defuses"]) ) + { + player.pers["defuses"]++; + player.defuses = player.pers["defuses"]; + } + + if ( !isScoreBoosting( player, self ) ) + { + demo::bookmark( "event", gettime(), player ); + player AddPlayerStatWithGameType( "DEFUSES", 1 ); + + scoreevents::processScoreEvent( "defused_bomb", player ); + player RecordGameEvent("defuse"); + } + else + { + /# + player IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU DEFUSE CREDIT AS BOOSTING PREVENTION" ); + #/ + } + + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_DEFUSED_BY", player ); + + thread globallogic_audio::set_music_on_team( "DEM_WE_DEFUSE", team, 5 ); + thread globallogic_audio::set_music_on_team( "DEM_THEY_DEFUSE", enemyTeam, 5 ); + + globallogic_audio::leader_dialog( "bombDefused" ); + } +} + +function onDrop( player ) +{ + if ( !level.bombPlanted ) + { + globallogic_audio::leader_dialog( "bombFriendlyDropped", player.pers["team"] ); + /# + if ( isdefined( player ) ) + print( "bomb dropped" ); + else + print( "bomb dropped" ); + #/ + } + + player notify( "event_ended" ); + + self gameobjects::set_3d_icon( "friendly", "waypoint_bomb" ); + + sound::play_on_players( game["bomb_dropped_sound"], game["attackers"] ); +} + + +function onPickup( player ) +{ + player.isBombCarrier = true; + + self gameobjects::set_3d_icon( "friendly", "waypoint_defend" ); + + if ( !level.bombDefused ) + { + thread sound::play_on_players( "mus_sd_pickup"+"_"+level.teamPostfix[player.pers["team"]], player.pers["team"] ); + + globallogic_audio::leader_dialog( "bombFriendlyTaken", player.pers["team"] ); + /#print( "bomb taken" );#/ + } + sound::play_on_players( game["bomb_recovered_sound"], game["attackers"] ); +} + + +function onReset() +{ +} + +function bombReset( label, reason ) +{ + if ( label == "_a" ) + { + level.bombAPlanted = false; + SetBombTimer( "A", 0 ); + } + else + { + level.bombBPlanted = false; + SetBombTimer( "B", 0 ); + } + + setMatchFlag( "bomb_timer" + label, 0 ); + + if ( !level.bombAPlanted && !level.bombBPlanted ) + globallogic_utils::resumeTimer(); + + self.visuals[0] globallogic_utils::stopTickingSound(); +} + +function dropBombModel( player, site ) +{ + trace = bulletTrace( player.origin + (0,0,20), player.origin - (0,0,2000), false, player ); + + tempAngle = randomfloat( 360 ); + forward = (cos( tempAngle ), sin( tempAngle ), 0); + forward = vectornormalize( forward - VectorScale( trace["normal"], vectordot( forward, trace["normal"] ) ) ); + dropAngles = vectortoangles( forward ); + + if ( IsDefined( trace[ "surfacetype" ] ) && trace[ "surfacetype" ] == "water" ) + { + phystrace = playerPhysicsTrace( player.origin + (0,0,20), player.origin - (0,0,2000) ); + + if ( IsDefined( phystrace ) ) + { + trace["position"] = phystrace; + } + } + + level.ddBombModel[ site ] = spawn( "script_model", trace["position"] ); + level.ddBombModel[ site ].angles = dropAngles; + level.ddBombModel[ site ] setModel( "p7_mp_suitcase_bomb" ); +} + + +function bombPlanted( destroyedObj, player ) +{ + level endon( "game_ended" ); + destroyedObj endon( "bomb_defused" ); + team = player.team; + game["challenge"][team]["plantedBomb"] = true; + + globallogic_utils::pauseTimer(); + destroyedObj.bombPlanted = true; + player SetWeaponOverheating( true, 100, destroyedObj.useWeapon ); // overheating allows for non-quick drop anim to be played + player PlayBombPlant(); + + destroyedObj.visuals[0] thread globallogic_utils::playTickingSound( "mpl_sab_ui_suitcasebomb_timer" ); + destroyedObj.tickingObject = destroyedObj.visuals[0]; + + label = destroyedObj.label; + + detonateTime = int( gettime() + (level.bombTimer * 1000) ); + updateBombTimers(label, detonateTime); + destroyedObj.detonateTime = detonateTime; + + trace = bulletTrace( player.origin + (0,0,20), player.origin - (0,0,2000), false, player ); + + self dropBombModel( player, destroyedObj.label ); + destroyedObj gameobjects::allow_use( "none" ); + destroyedObj gameobjects::set_visible_team( "none" ); + if ( isdefined( game["overtime_round"] ) ) + { + destroyedObj gameobjects::set_owner_team( util::getOtherTeam( player.team ) ); + } + destroyedObj setUpForDefusing(); + + player.isBombCarrier = false; + game["challenge"][team]["plantedBomb"] = true; + + destroyedObj waitLongDurationWithBombTimeUpdate( label, level.bombTimer ); + destroyedObj bombReset( label, "bomb_exploded" ); + + if ( level.gameEnded ) + { + return; + } + + origin = (0,0,0); + if ( isdefined( player ) ) + { + origin = player.origin; + } + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "dem_bombexplode", label, team, origin ); + + destroyedObj.bombExploded = true; + game["challenge"][team]["destroyedBombSite"] = true; + explosionOrigin = destroyedObj.curorigin; + + level.ddBombModel[ destroyedObj.label ] Delete(); + clips = getEntArray( "bombzone_clip"+destroyedObj.label, "targetname" ); + foreach( clip in clips ) + { + clip delete(); + } + + if ( isdefined( player ) ) + { + destroyedObj.visuals[0] radiusDamage( explosionOrigin, 512, 200, 20, player, "MOD_EXPLOSIVE", GetWeapon( "briefcase_bomb" ) ); + + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_BLOWUP_BY", player ); + + if ( player.team == team ) + { + player AddPlayerStatWithGameType( "DESTRUCTIONS", 1 ); + player AddPlayerStatWithGameType( "captures", 1 ); // counts towards Destroyer challenge + // give points for being the bomb destroyer for extra game mode incentive + scoreevents::processScoreEvent( "bomb_detonated", player ); + } + + player RecordGameEvent("destroy"); + } + else + { + destroyedObj.visuals[0] radiusDamage( explosionOrigin, 512, 200, 20, undefined, "MOD_EXPLOSIVE", GetWeapon( "briefcase_bomb" ) ); + } + + currentTime = getTime(); + + if ( isdefined( level.lastBombExplodeTime ) && level.lastBombExplodeByTeam == team ) + { + if ( level.lastBombExplodeTime + 10000 > currentTime ) + { + for ( i = 0; i < level.players.size; i++ ) + { + if ( level.players[i].team == team ) + { + level.players[i] challenges::bothBombsDetonateWithinTime(); + } + } + } + } + level.lastBombExplodeTime = currentTime; + level.lastBombExplodeByTeam = team; + + rot = randomfloat(360); + explosionEffect = spawnFx( level._effect["bombexplosion"], explosionOrigin + (0,0,50), (0,0,1), (cos(rot),sin(rot),0) ); + triggerFx( explosionEffect ); + + thread sound::play_in_space( "mpl_sd_exp_suitcase_bomb_main", explosionOrigin ); + + if ( isdefined( destroyedObj.exploderIndex ) ) + exploder::exploder( destroyedObj.exploderIndex ); + + bombZonesLeft = 0; + + for ( index = 0; index < level.bombZones.size; index++ ) + { + if ( !isdefined( level.bombZones[index].bombExploded ) || !level.bombZones[index].bombExploded ) + bombZonesLeft++; + } + + destroyedObj gameobjects::disable_object(); + + if ( bombZonesLeft == 0 ) + { + + globallogic_utils::pauseTimer(); + level.hasPausedTimer = true; + setGameEndTime( 0 ); + wait 3; + dem_endGame( team, game["strings"]["target_destroyed"] ); + } + else + { + enemyTeam = util::getOtherTeam( team ); + + thread globallogic_audio::set_music_on_team( "DEM_WE_SCORE", team, 5 ); + thread globallogic_audio::set_music_on_team( "DEM_THEY_SCORE", enemyTeam, 5 ); + + //level thread play_one_left_underscore( team, enemyTeam ); + + if( [[level.getTimeLimit]]() > 0 ) + { + level.usingExtraTime = true; + } + + // remove the influencer on this object + destroyedObj spawning::remove_influencer( destroyedObj.spawnInfluencer ); + destroyedObj.spawnInfluencer = undefined; + + spawnlogic::clear_spawn_points(); + spawnlogic::add_spawn_points( game["attackers"], "mp_dem_spawn_attacker" ); + spawnlogic::add_spawn_points( game["defenders"], "mp_dem_spawn_defender" ); + if ( label == "_a" ) + { + spawnlogic::add_spawn_points( game["attackers"], "mp_dem_spawn_attacker_remove_b" ); + spawnlogic::add_spawn_points( game["attackers"], "mp_dem_spawn_attacker_a" ); + spawnlogic::add_spawn_points( game["defenders"], "mp_dem_spawn_defender_b" ); + } + else + { + spawnlogic::add_spawn_points( game["attackers"], "mp_dem_spawn_attacker_remove_a" ); + spawnlogic::add_spawn_points( game["attackers"], "mp_dem_spawn_attacker_b" ); + spawnlogic::add_spawn_points( game["defenders"], "mp_dem_spawn_defender_a" ); + } + spawning::updateAllSpawnPoints(); + } +} + +function getTimeLimit() +{ + timeLimit = globallogic_defaults::default_getTimeLimit(); + if ( isdefined( game["overtime_round"] ) ) + { + timeLimit = level.overtimeTimeLimit; + } + if ( level.usingExtraTime ) + return timeLimit + level.extraTime; + return timeLimit; + +} + +function shouldPlayOvertimeRound() +{ + if ( isdefined( game["overtime_round"] ) ) + { + return false; + } + + if ( game["teamScores"]["allies"] == level.scorelimit - 1 && game["teamScores"]["axis"] == level.scorelimit - 1 ) + { + return true; + } + + return false; +} + +function waitLongDurationWithBombTimeUpdate( whichBomb, duration ) +{ + if ( duration == 0 ) + return; + assert( duration > 0 ); + + starttime = gettime(); + + endtime = gettime() + duration * 1000; + + while ( gettime() < endtime ) + { + hostmigration::waitTillHostMigrationStarts( (endtime - gettime()) / 1000 ); + + while ( isdefined( level.hostMigrationTimer ) ) + { + endTime += 250; + updateBombTimers(whichBomb, endTime); + wait 0.25; + } + } + /# + if( gettime() != endtime ) + println("SCRIPT WARNING: gettime() = " + gettime() + " NOT EQUAL TO endtime = " + endtime); + #/ + while ( isdefined( level.hostMigrationTimer ) ) + { + endTime += 250; + updateBombTimers(whichBomb, endTime); + wait 0.250; + } + + return gettime() - starttime; +} + +function updateBombTimers(whichBomb, detonateTime) +{ + if ( whichBomb == "_a" ) + { + level.bombAPlanted = true; + SetBombTimer( "A", int(detonateTime) ); + } + else + { + level.bombBPlanted = true; + SetBombTimer( "B", int(detonateTime) ); + } + + setMatchFlag( "bomb_timer" + whichBomb, int(detonateTime) ); +} + +function bombDefused( player ) +{ + self.tickingObject globallogic_utils::stopTickingSound(); + self gameobjects::allow_use( "none" ); + self gameobjects::set_visible_team( "none" ); + self.bombDefused = true; + self notify( "bomb_defused" ); + self.bombPlanted = false; + self bombReset( self.label, "bomb_defused" ); + player SetWeaponOverheating( true, 100, self.useWeapon ); // overheating allows for non-quick drop anim to be played + player PlayBombDefuse(); +} + +function play_one_left_underscore( team, enemyTeam ) +{ + wait(3); + + if( (!isdefined(team)) || (!isdefined(enemyTeam)) ) + { + return; + } + + thread globallogic_audio::set_music_on_team( "DEM_ONE_LEFT_UNDERSCORE", team ); + thread globallogic_audio::set_music_on_team( "DEM_ONE_LEFT_UNDERSCORE", enemyTeam ); +} + +function updateEventsPerMinute() +{ + if ( !isdefined( self.eventsPerMinute ) ) + { + self.numBombEvents = 0; + self.eventsPerMinute = 0; + } + + self.numBombEvents++; + + minutesPassed = globallogic_utils::getTimePassed() / ( 60 * 1000 ); + + // players use the actual time played + if ( IsPlayer( self ) && isdefined(self.timePlayed["total"]) ) + minutesPassed = self.timePlayed["total"] / 60; + + self.eventsPerMinute = self.numBombEvents / minutesPassed; + if ( self.eventsPerMinute > self.numBombEvents ) + self.eventsPerMinute = self.numBombEvents; +} + +function isScoreBoosting( player, flag ) +{ + if ( !level.rankedMatch ) + return false; + + if ( player.eventsPerMinute > level.playerEventsLPM ) + return true; + + if ( flag.eventsPerMinute > level.bombEventsLPM ) + return true; + + return false; +} diff --git a/mp/gametypes/dm.csc b/mp/gametypes/dm.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/dm.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/dm.gsc b/mp/gametypes/dm.gsc new file mode 100644 index 0000000..b430d8b --- /dev/null +++ b/mp/gametypes/dm.gsc @@ -0,0 +1,197 @@ +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\killstreaks\_killstreaks; + +#using scripts\mp\_util; + +/* + Deathmatch + Objective: Score points by eliminating other players + Map ends: When one player reaches the score limit, or time limit is reached + Respawning: No wait / Away from other players + + Level requirements + ------------------ + Spawnpoints: + classname mp_dm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of enemies at the time of spawn. + Players generally spawn away from enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + Because Deathmatch doesn't have teams with regard to gameplay or scoring, this effectively sets the available weapons. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals +*/ + +/*QUAKED mp_dm_spawn (1.0 0.5 0.0) (-16 -16 0) (16 16 72) +Players spawn away from enemies at one of these positions.*/ + +#precache( "string", "OBJECTIVES_DM" ); +#precache( "string", "OBJECTIVES_DM_SCORE" ); +#precache( "string", "OBJECTIVES_DM_HINT" ); + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 50000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 0, 0, 1440 ); + + level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false ); + level.teamScorePerKill = GetGametypeSetting( "teamScorePerKill" ); + level.teamScorePerDeath = GetGametypeSetting( "teamScorePerDeath" ); + level.teamScorePerHeadshot = GetGametypeSetting( "teamScorePerHeadshot" ); + level.killstreaksGiveGameScore = GetGametypeSetting( "killstreaksGiveGameScore" ); + + level.onStartGameType =&onStartGameType; + level.onPlayerKilled =&onPlayerKilled; + level.onSpawnPlayer =&onSpawnPlayer; + + gameobjects::register_allowed_gameobject( level.gameType ); + + globallogic_audio::set_leader_gametype_dialog ( "startFreeForAll", "hcStartFreeForAll", "gameBoost", "gameBoost" ); + + // Sets the scoreboard columns and determines with data is sent across the network + globallogic::setvisiblescoreboardcolumns( "pointstowin", "kills", "deaths", "kdratio", "score" ); +} + + +function setupTeam( team ) +{ + util::setObjectiveText( team, &"OBJECTIVES_DM" ); + if ( level.splitscreen ) + { + util::setObjectiveScoreText( team, &"OBJECTIVES_DM" ); + } + else + { + util::setObjectiveScoreText( team, &"OBJECTIVES_DM_SCORE" ); + } + util::setObjectiveHintText( team, &"OBJECTIVES_DM_HINT" ); + + spawnlogic::add_spawn_points( team, "mp_dm_spawn" ); + spawnlogic::place_spawn_points( "mp_dm_spawn_start" ); + + level.spawn_start = spawnlogic::get_spawnpoint_array( "mp_dm_spawn_start" ); + +} + +function onStartGameType() +{ + setClientNameMode("auto_change"); + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + foreach( team in level.teams ) + { + setupTeam( team ); + } + + spawning::updateAllSpawnPoints(); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + level.displayRoundEndText = false; + + level thread onScoreCloseMusic(); + + if ( !util::isOneRound() ) + { + level.displayRoundEndText = true; + } +} + +function onEndGame( winningPlayer ) +{ + if ( isdefined( winningPlayer ) && isPlayer( winningPlayer ) ) + [[level._setPlayerScore]]( winningPlayer, winningPlayer [[level._getPlayerScore]]() + 1 ); +} + +function onScoreCloseMusic() +{ + while( !level.gameEnded ) + { + scoreLimit = level.scoreLimit; + scoreThreshold = scoreLimit * .9; + + for(i=0;i= scoreThreshold ) + { +// thread globallogic_audio::set_music_on_team( "timeOut" ); //Cutting the music call, because it was never working properly + return; + } + } + + wait(.5); + } +} + +function onSpawnPlayer(predictedSpawn) +{ + if( !level.inPrematchPeriod ) + { + level.useStartSpawns = false; + } + + spawning::onSpawnPlayer(predictedSpawn); +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( !isPlayer( attacker ) || ( self == attacker ) ) + return; + + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + { + attacker globallogic_score::givePointsToWin( level.teamScorePerKill ); + self globallogic_score::givePointsToWin( level.teamScorePerDeath * -1 ); + if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + { + attacker globallogic_score::givePointsToWin( level.teamScorePerHeadshot ); + } + } +} diff --git a/mp/gametypes/dom.csc b/mp/gametypes/dom.csc new file mode 100644 index 0000000..fee7df3 --- /dev/null +++ b/mp/gametypes/dom.csc @@ -0,0 +1,184 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\util_shared; + + + + +#precache( "client_fx", "ui/fx_dom_cap_indicator_team" ); +#precache( "client_fx", "ui/fx_dom_cap_indicator_neutral" ); +#precache( "client_fx", "ui/fx_dom_marker_team" ); +#precache( "client_fx", "ui/fx_dom_marker_neutral" ); + +function main() +{ + callback::on_localclient_connect( &on_localclient_connect ); + if( GetGametypeSetting( "silentPlant" ) != 0 ) + setsoundcontext( "bomb_plant", "silent" ); +} + +function on_localclient_connect( localClientNum ) +{ + self.domFlags = []; + + while ( !isdefined( level.domFlags["a"] ) ) + { + self.domFlags["a"] = ServerObjective_GetObjective( localClientNum, "dom_a" ); + self.domFlags["b"] = ServerObjective_GetObjective( localClientNum, "dom_b" ); + self.domFlags["c"] = ServerObjective_GetObjective( localClientNum, "dom_c" ); + wait (0.05); + } + + foreach( key, flag_objective in self.domFlags) + { + self thread monitor_flag_fx(localClientNum, flag_objective, key); + } +} + +function monitor_flag_fx(localClientNum, flag_objective, flag_name) +{ + if ( !IsDefined( flag_objective ) ) + return; + + flag = SpawnStruct(); + + flag.name = flag_name; + flag.objectiveId = flag_objective; + flag.origin = ServerObjective_GetObjectiveOrigin( localClientNum, flag_objective ); + flag.angles = (0,0,0); + + flag_entity = ServerObjective_GetObjectiveEntity( localClientNum, flag_objective ); + + if ( isdefined(flag_entity) ) + { + flag.origin = flag_entity.origin; + flag.angles = flag_entity.angles; + } + + fx_name = get_base_fx( flag, "neutral" ); + play_base_fx( localClientNum, flag, fx_name, "neutral" ); + flag.last_progress = 0; + while(1) + { + + team = ServerObjective_GetObjectiveTeam( localClientNum, flag_objective ); + if ( team != flag.last_team ) + { + flag update_base_fx( localClientNum, flag, team ); + } + + progress = (ServerObjective_GetObjectiveProgress( localClientNum, flag_objective ) > 0); + if ( progress != flag.last_progress ) + { + flag update_cap_fx( localClientNum, flag, team, progress ); + } + + wait(0.05); + } +} + +function play_base_fx( localClientNum, flag, fx_name, team ) +{ + if ( isdefined( flag.base_fx ) ) + { + StopFx( localClientNum, flag.base_fx ); + } + + up = anglesToUp(flag.angles); + forward = anglesToForward(flag.angles); + flag.base_fx = PlayFx(localClientNum, fx_name, flag.origin, up, forward ); + SetFxTeam( localClientNum, flag.base_fx, team ); + + flag.last_team = team; +} + +function update_base_fx( localClientNum, flag, team ) +{ + fx_name = get_base_fx( flag, team ); + + if ( team == "neutral" ) + { + play_base_fx( localClientNum, flag, fx_name, team ); + } + else if ( flag.last_team == "neutral" ) + { + play_base_fx( localClientNum, flag, fx_name, team ); + } + else + { + SetFxTeam( localClientNum, flag.base_fx, team ); + flag.last_team = team; + } +} + +function play_cap_fx( localClientNum, flag, fx_name, team ) +{ + if ( isdefined( flag.cap_fx ) ) + { + KillFx( localClientNum, flag.cap_fx ); + } + + up = anglesToUp(flag.angles); + forward = anglesToForward(flag.angles); + flag.cap_fx = PlayFx(localClientNum, fx_name, flag.origin, up, forward ); + SetFxTeam( localClientNum, flag.cap_fx, team ); +} + +function update_cap_fx( localClientNum, flag, team, progress ) +{ + if ( progress == 0 ) + { + if ( isdefined( flag.cap_fx ) ) + { + KillFx( localClientNum, flag.cap_fx ); + } + flag.last_progress = progress; + return; + } + + fx_name = get_cap_fx( flag, team ); + + play_cap_fx( localClientNum, flag, fx_name, team ); + + flag.last_progress = progress; +} + +function get_base_fx( flag, team ) +{ + if ( isdefined( level.domFlagBaseFxOverride ) ) + { + fx = [[level.domFlagBaseFxOverride]]( flag, team ); + if ( isdefined( fx ) ) + return fx; + } + + if ( team == "neutral" ) + { + return "ui/fx_dom_marker_neutral"; + } + else + { + return "ui/fx_dom_marker_team"; + } +} + +function get_cap_fx( flag, team ) +{ + if ( isdefined( level.domFlagCapFxOverride ) ) + { + fx = [[level.domFlagCapFxOverride]]( flag, team ); + if ( isdefined( fx ) ) + return fx; + } + + if ( team == "neutral" ) + { + return "ui/fx_dom_cap_indicator_neutral"; + } + else + { + return "ui/fx_dom_cap_indicator_team"; + } +} \ No newline at end of file diff --git a/mp/gametypes/dom.gsc b/mp/gametypes/dom.gsc new file mode 100644 index 0000000..f9a9586 --- /dev/null +++ b/mp/gametypes/dom.gsc @@ -0,0 +1,1793 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\math_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\util_shared; + + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\teams\_teams; + +/* + Domination + Objective: Capture all the flags by touching them + Map ends: When one team captures all the flags, or time limit is reached + Respawning: No wait / Near teammates + + Level requirements + ------------------ + Spawnpoints: + classname mp_tdm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of owned flags, teammates and + enemies at the time of spawn. Players generally spawn behind their teammates relative to the direction of enemies. + Optionally, give a spawnpoint a script_linkto to specify which flag it "belongs" to (see Flag Descriptors). + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Flags: + classname trigger_radius + targetname flag_primary or flag_secondary + Flags that need to be captured to win. Primary flags take time to capture; secondary flags are instant. + + Flag Descriptors: + classname script_origin + targetname flag_descriptor + Place one flag descriptor close to each flag. Use the script_linkname and script_linkto properties to say which flags + it can be considered "adjacent" to in the level. For instance, if players have a primary path from flag1 to flag2, and + from flag2 to flag3, flag2 would have a flag_descriptor with these properties: + script_linkname flag2 + script_linkto flag1 flag3 + + Set scr_domdebug to 1 to see flag connections and what spawnpoints are considered connected to each flag. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + This sets the nationalities of the teams. Allies can be american, british, or russian. Axis can be german. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals +*/ + +/*QUAKED mp_dom_spawn (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Players spawn near their flags at one of these positions.*/ + +/*QUAKED mp_dom_spawn_flag_a (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Players spawn near their flags at one of these positions.*/ + +/*QUAKED mp_dom_spawn_flag_b (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Players spawn near their flags at one of these positions.*/ + +/*QUAKED mp_dom_spawn_flag_c (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Players spawn near their flags at one of these positions.*/ + +/*QUAKED mp_dom_spawn_axis_start (1.0 0.0 1.0) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_dom_spawn_allies_start (0.0 1.0 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +#precache( "string", "OBJECTIVES_DOM" ); +#precache( "string", "OBJECTIVES_DOM_SCORE" ); +#precache( "string", "OBJECTIVES_DOM_HINT" ); +#precache( "string", "MP_CAPTURING_FLAG" ); +#precache( "string", "MP_LOSING_FLAG" ); +#precache( "string", "MP_DOM_YOUR_FLAG_WAS_CAPTURED" ); +#precache( "string", "MP_DOM_ENEMY_FLAG_CAPTURED" ); +#precache( "string", "MP_DOM_NEUTRAL_FLAG_CAPTURED" ); +#precache( "string", "MP_ENEMY_FLAG_CAPTURED_BY" ); +#precache( "string", "MP_NEUTRAL_FLAG_CAPTURED_BY" ); +#precache( "string", "MP_FRIENDLY_FLAG_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_A_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_B_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_C_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_D_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_E_CAPTURED_BY" ); +#precache( "fx", "ui/fx_ui_flagbase_blue" ); +#precache( "fx", "ui/fx_ui_flagbase_orng" ); +#precache( "fx", "ui/fx_ui_flagbase_wht" ); +#precache( "objective", "dom_a" ); +#precache( "objective", "dom_b" ); +#precache( "objective", "dom_c" ); + + + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 2000 ); + util::registerRoundScoreLimit( 0, 2000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerRoundSwitch( 0, 9 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false ); + level.teamBased = true; + level.overrideTeamScore = true; + level.onStartGameType =&onStartGameType; + level.onPlayerKilled =&onPlayerKilled; + level.onRoundSwitch =&onRoundSwitch; + level.onPrecacheGameType =&onPrecacheGameType; + level.onEndGame=&onEndGame; + level.onRoundEndGame =&onRoundEndGame; + + gameobjects::register_allowed_gameobject( level.gameType ); + + globallogic_audio::set_leader_gametype_dialog ( "startDomination", "hcStartDomination", "objCapture", "objCapture" ); + + game["dialog"]["securing_a"] = "domFriendlySecuringA"; + game["dialog"]["securing_b"] = "domFriendlySecuringB"; + game["dialog"]["securing_c"] = "domFriendlySecuringC"; + + game["dialog"]["secured_a"] = "domFriendlySecuredA"; + game["dialog"]["secured_b"] = "domFriendlySecuredB"; + game["dialog"]["secured_c"] = "domFriendlySecuredC"; + + game["dialog"]["secured_all"] = "domFriendlySecuredAll"; + + game["dialog"]["losing_a"] = "domEnemySecuringA"; + game["dialog"]["losing_b"] = "domEnemySecuringB"; + game["dialog"]["losing_c"] = "domEnemySecuringC"; + + game["dialog"]["lost_a"] = "domEnemySecuredA"; + game["dialog"]["lost_b"] = "domEnemySecuredB"; + game["dialog"]["lost_c"] = "domEnemySecuredC"; + + game["dialog"]["lost_all"] = "domEnemySecuredAll"; + + game["dialog"]["enemy_a"] = "domEnemyHasA"; + game["dialog"]["enemy_b"] = "domEnemyHasB"; + game["dialog"]["enemy_c"] = "domEnemyHasC"; + + game["dialogTime"] = []; + game["dialogTime"]["securing_a"] = 0; + game["dialogTime"]["securing_b"] = 0; + game["dialogTime"]["securing_c"] = 0; + game["dialogTime"]["losing_a"] = 0; + game["dialogTime"]["losing_b"] = 0; + game["dialogTime"]["losing_c"] = 0; + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "captures", "defends", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths" , "captures", "defends"); +} + + +function onPrecacheGameType() +{ +} + + +function onStartGameType() +{ + util::setObjectiveText( "allies", &"OBJECTIVES_DOM" ); + util::setObjectiveText( "axis", &"OBJECTIVES_DOM" ); + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_DOM" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_DOM" ); + } + else + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_DOM_SCORE" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_DOM_SCORE" ); + } + util::setObjectiveHintText( "allies", &"OBJECTIVES_DOM_HINT" ); + util::setObjectiveHintText( "axis", &"OBJECTIVES_DOM_HINT" ); + + level.flagBaseFXid = []; + +// level.flagBaseFXid[ "friendly" ] = "ui/fx_ui_flagbase_blue"; +// level.flagBaseFXid[ "enemy" ] = "ui/fx_ui_flagbase_orng"; +// level.flagBaseFXid[ "neutral" ] = "ui/fx_ui_flagbase_wht"; + + setClientNameMode("auto_change"); + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::place_spawn_points( "mp_dom_spawn_allies_start" ); + spawnlogic::place_spawn_points( "mp_dom_spawn_axis_start" ); + + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + level.spawn_all = spawnlogic::get_spawnpoint_array( "mp_dom_spawn" ); + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array("mp_dom_spawn_" + team + "_start"); + } + + flagSpawns = spawnlogic::get_spawnpoint_array( "mp_dom_spawn_flag_a" ); + //assert( flagSpawns.size > 0 ); + + level.startPos["allies"] = level.spawn_start[ "allies" ][0].origin; + level.startPos["axis"] = level.spawn_start[ "axis" ][0].origin; + + + if ( !util::isOneRound() && level.scoreRoundWinBased ) + { + globallogic_score::resetTeamScores(); + } + + level.spawnsystem.sideSwitching = 0; + + level thread watchForBFlagCap(); + updateGametypeDvars(); + thread domFlags(); + thread updateDomScores(); + level change_dom_spawns(); +} + +function onEndGame( winningTeam ) +{ + for ( i = 0; i < level.domFlags.size; i++ ) + { + domFlag = level.domFlags[i]; + domFlag gameobjects::allow_use( "none" ); + if ( isdefined( domFlag.singleOwner ) && domFlag.singleOwner == true ) + { + team = domFlag gameobjects::get_owner_team(); + label = domFlag gameobjects::get_label(); + challenges::holdFlagEntireMatch( team, label ); + } + } +} + +function onRoundEndGame( roundWinner ) +{ + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, game["roundswon"][team] ); + } + } + + return [[level.determineWinner]](); +} + +function updateGametypeDvars() +{ + level.flagCaptureTime = GetGametypeSetting( "captureTime" ); + level.playerCaptureLPM = GetGametypeSetting( "maxPlayerEventsPerMinute" ); + level.flagCaptureLPM = GetGametypeSetting( "maxObjectiveEventsPerMinute" ); + level.playerOffensiveMax = GetGametypeSetting( "maxPlayerOffensive" ); + level.playerDefensiveMax = GetGametypeSetting( "maxPlayerDefensive" ); + level.flagCanBeNeutralized = GetGametypeSetting( "flagCanBeNeutralized" ); +} + +function domFlags() +{ + level.lastStatus["allies"] = 0; + level.lastStatus["axis"] = 0; + + level.flagModel["allies"] = "tag_origin"; // teams::get_flag_model( "allies" ); + level.flagModel["axis"] = "tag_origin"; // teams::get_flag_model( "axis" ); + level.flagModel["neutral"] = "tag_origin"; // teams::get_flag_model( "neutral" ); + + primaryFlags = getEntArray( "flag_primary", "targetname" ); + + if ( (primaryFlags.size) < 2 ) + { + /# printLn( "^1Not enough domination flags found in level!" ); #/ + callback::abort_level(); + return; + } + + level.flags = []; + foreach( dom_flag in primaryFlags ) + { + if ( isdefined( dom_flag.target ) ) + { + trigger = getEnt( dom_flag.target, "targetname" ); + + if ( isDefined( trigger ) ) + { + trigger.visual = dom_flag; + trigger.script_label = dom_flag.script_label; + } + else + { + /# + util::error("Trigger not available for flag " + dom_flag.script_label + " with targetname " + dom_flag.target); + #/ + } + } + else + { + /# + util::error("Trigger not available for flag " + dom_flag.script_label); + #/ + } + + level.flags[level.flags.size] = trigger; + } + + level.domFlags = []; + foreach( trigger in level.flags ) + { + trigger.visual setModel( level.flagModel["neutral"] ); + + name = istring("dom" + trigger.visual.script_label); + + visuals = []; + visuals[0] = trigger.visual; + + domFlag = gameobjects::create_use_object( "neutral", trigger, visuals, (0,0,0), name ); + domFlag gameobjects::allow_use( "enemy" ); + if( level.flagCanBeNeutralized ) + { + domFlag gameobjects::set_use_time( level.flagCaptureTime / 2 ); + } + else + { + domFlag gameobjects::set_use_time( level.flagCaptureTime ); + } + domFlag gameobjects::set_use_text( &"MP_CAPTURING_FLAG" ); + label = domFlag gameobjects::get_label(); + domFlag.label = label; + domFlag.flagIndex = trigger.visual.script_index; + domFlag gameobjects::set_visible_team( "any" ); + domFlag.onUse =&onUse; + domFlag.onBeginUse =&onBeginUse; + domFlag.onUseUpdate =&onUseUpdate; + domFlag.onEndUse =&onEndUse; + domFlag.onUpdateUseRate =&onUpdateUseRate; + domFlag.hasBeenCaptured = false; + + domFlag gameobjects::set_objective_entity( visuals[ 0 ] ); + domFlag gameobjects::set_owner_team( "neutral" ); + + traceStart = visuals[0].origin + (0,0,32); + traceEnd = visuals[0].origin + (0,0,-32); + trace = bulletTrace( traceStart, traceEnd, false, undefined ); + + upangles = vectorToAngles( trace["normal"] ); + domFlag.baseeffectforward = anglesToForward( upangles ); + domFlag.baseeffectright = anglesToRight( upangles ); + + domFlag.baseeffectpos = trace["position"]; + + // legacy spawn code support + trigger.useObj = domFlag; + trigger.adjflags = []; + trigger.nearbyspawns = []; + + domFlag.levelFlag = trigger; + + level.domFlags[level.domFlags.size] = domFlag; + } + + // level.bestSpawnFlag is used as a last resort when the enemy holds all flags. + level.bestSpawnFlag = []; + level.bestSpawnFlag[ "allies" ] = getUnownedFlagNearestStart( "allies", undefined ); + level.bestSpawnFlag[ "axis" ] = getUnownedFlagNearestStart( "axis", level.bestSpawnFlag[ "allies" ] ); + + for ( index = 0; index < level.domFlags.size; index++ ) + { + level.domFlags[index] createFlagSpawnInfluencers(); + } + + flagSetup(); + + /# + thread domDebug(); + #/ +} + +function getUnownedFlagNearestStart( team, excludeFlag ) +{ + best = undefined; + bestdistsq = undefined; + for ( i = 0; i < level.flags.size; i++ ) + { + flag = level.flags[i]; + + if ( flag getFlagTeam() != "neutral" ) + continue; + + distsq = distanceSquared( flag.origin, level.startPos[team] ); + if ( (!isdefined( excludeFlag ) || flag != excludeFlag) && (!isdefined( best ) || distsq < bestdistsq) ) + { + bestdistsq = distsq; + best = flag; + } + } + return best; +} + +/# +function domDebug() +{ + while(1) + { + if (GetDvarString( "scr_domdebug") != "1") { + wait 2; + continue; + } + + while(1) + { + if (GetDvarString( "scr_domdebug") != "1") + break; + // show flag connections and each flag's spawnpoints + for (i = 0; i < level.flags.size; i++) { + for (j = 0; j < level.flags[i].adjflags.size; j++) { + line(level.flags[i].origin, level.flags[i].adjflags[j].origin, (1,1,1)); + } + + for (j = 0; j < level.flags[i].nearbyspawns.size; j++) { + line(level.flags[i].origin, level.flags[i].nearbyspawns[j].origin, (.2,.2,.6)); + } + + if ( level.flags[i] == level.bestSpawnFlag["allies"] ) + print3d( level.flags[i].origin, "allies best spawn flag" ); + if ( level.flags[i] == level.bestSpawnFlag["axis"] ) + print3d( level.flags[i].origin, "axis best spawn flag" ); + } + wait .05; + } + } +} +#/ + +function onBeginUse( player ) +{ + ownerTeam = self gameobjects::get_owner_team(); +// SetDvar( "scr_obj" + self gameobjects::get_label() + "_flash", 1 ); + self.didStatusNotify = false; + if ( ownerTeam == "allies" ) + otherTeam = "axis"; + else + otherTeam = "allies"; + + if ( ownerTeam == "neutral" ) + { + otherTeam = util::getOtherTeam( player.pers["team"] ); + statusDialog( "securing"+self.label, player.pers["team"], "objective" + self.label ); +// self.objPoints[player.pers["team"]] thread objpoints::start_flashing(); + return; + } + +// self.objPoints["allies"] thread objpoints::start_flashing(); +// self.objPoints["axis"] thread objpoints::start_flashing(); +} + +function onUseUpdate( team, progress, change ) +{ + if ( progress > 0.05 && change && !self.didStatusNotify ) + { + ownerTeam = self gameobjects::get_owner_team(); + if ( ownerTeam == "neutral" ) + { + otherTeam = util::getOtherTeam( team ); + statusDialog( "securing"+self.label, team, "objective" + self.label ); + //statusDialog( "losing"+self.label, otherTeam, "objective" + self.label ); + } + else + { + statusDialog( "losing"+self.label, ownerTeam, "objective" + self.label ); + statusDialog( "securing"+self.label, team, "objective" + self.label ); + globallogic_audio::flush_objective_dialog( "objective_all" ); + } + + self.didStatusNotify = true; + } +} + +function flushObjectiveFlagDialog() +{ + globallogic_audio::flush_objective_dialog( "objective_a" ); + globallogic_audio::flush_objective_dialog( "objective_b" ); + globallogic_audio::flush_objective_dialog( "objective_c" ); +} + +function statusDialog( dialog, team, objectiveKey ) +{ + // Don't play some dialog over and over + dialogTime = game["dialogTime"][dialog]; + if ( isdefined( dialogTime) ) + { + time = GetTime(); + + if ( dialogTime > time ) + { + return; + } + + game["dialogTime"][dialog] = time + 10000; + } + + dialogKey = game["dialog"][dialog]; + + if( isdefined( objectiveKey ) ) + { + if( objectiveKey != "objective_all" ) + { + dialogBufferKey = "domPointDialogBuffer"; + } + } + + globallogic_audio::leader_dialog( dialogKey, team, undefined, objectiveKey, undefined, dialogBufferKey ); +} + + +function onEndUse( team, player, success ) +{ + if ( !success ) + { + globallogic_audio::flush_objective_dialog( "objective" + self.label ); + } +} + +function flagCapturedFromNeutral( team ) +{ + self.singleOwner = true; + otherTeam = util::getOtherTeam( team ); + thread util::printAndSoundOnEveryone( team, undefined, &"", undefined, "mp_war_objective_taken" ); + + thread sound::play_on_players( "mus_dom_captured"+"_"+level.teamPostfix[team] ); + if ( getTeamFlagCount( team ) == level.flags.size ) + { + statusDialog( "secured_all", team, "objective_all" ); + statusDialog( "lost_all", otherTeam, "objective_all" ); + flushObjectiveFlagDialog(); + } + else + { + statusDialog( "secured"+self.label, team, "objective" + self.label ); + statusDialog( "enemy"+self.label, otherTeam, "objective" + self.label ); + globallogic_audio::flush_objective_dialog( "objective_all" ); + globallogic_audio::play_2d_on_team( "mpl_flagcapture_sting_enemy", otherTeam ); + globallogic_audio::play_2d_on_team( "mpl_flagcapture_sting_friend", team ); + } +} + +function flagCapturedFromTeam( team, oldTeam ) +{ + self.singleOwner = false; + thread util::printAndSoundOnEveryone( team, oldTeam, &"", &"", "mp_war_objective_taken", "mp_war_objective_lost", "" ); + + if ( getTeamFlagCount( team ) == level.flags.size ) + { + statusDialog( "secured_all", team, "objective_all" ); + statusDialog( "lost_all", oldTeam, "objective_all" ); + flushObjectiveFlagDialog(); + } + else + { + statusDialog( "secured"+self.label, team, "objective" + self.label ); + if( RandomInt(2) ) + { + statusDialog( "lost"+self.label, oldTeam, "objective" + self.label ); + } + else + { + statusDialog( "enemy"+self.label, oldTeam, "objective" + self.label ); + } + globallogic_audio::flush_objective_dialog( "objective_all" ); + globallogic_audio::play_2d_on_team( "mpl_flagcapture_sting_enemy", oldTeam ); + globallogic_audio::play_2d_on_team( "mpl_flagcapture_sting_friend", team ); + } + + level.bestSpawnFlag[ oldTeam ] = self.levelFlag; +} + +function flagNeutralized( team, oldTeam ) +{ + self.singleOwner = true; + thread util::printAndSoundOnEveryone( "neutral", oldTeam, &"", &"", "mp_war_objective_neutralized", "mp_war_objective_lost", "" ); + + if ( getTeamFlagCount( team ) == level.flags.size ) + { + statusDialog( "lost_all", oldTeam, "objective_all" ); + flushObjectiveFlagDialog(); + } + else + { + statusDialog( "lost"+self.label, oldTeam, "objective" + self.label ); + + globallogic_audio::flush_objective_dialog( "objective_all" ); + globallogic_audio::play_2d_on_team( "mpl_flagcapture_sting_enemy", oldTeam ); + } +} + +function GetDomFlagUseString( label, neutralized ) +{ + string = &""; + if( neutralized ) + { + switch ( label ) + { + case "_a": + string = &"MP_DOM_FLAG_A_NEUTRALIZED_BY"; + break; + case "_b": + string = &"MP_DOM_FLAG_B_NEUTRALIZED_BY"; + break; + case "_c": + string = &"MP_DOM_FLAG_C_NEUTRALIZED_BY"; + break; + case "_d": + string = &"MP_DOM_FLAG_D_NEUTRALIZED_BY"; + break; + case "_e": + string = &"MP_DOM_FLAG_E_NEUTRALIZED_BY"; + break; + default: + break; + } + } + else + { + switch ( label ) + { + case "_a": + string = &"MP_DOM_FLAG_A_CAPTURED_BY"; + break; + case "_b": + string = &"MP_DOM_FLAG_B_CAPTURED_BY"; + break; + case "_c": + string = &"MP_DOM_FLAG_C_CAPTURED_BY"; + break; + case "_d": + string = &"MP_DOM_FLAG_D_CAPTURED_BY"; + break; + case "_e": + string = &"MP_DOM_FLAG_E_CAPTURED_BY"; + break; + default: + break; + } + } + + return string; +} + +function onUseWithNeutralizingFlag( player ) +{ + team = player.pers["team"]; + oldTeam = self gameobjects::get_owner_team(); + label = self gameobjects::get_label(); + + /#print( "flag captured: " + self.label );#/ + + level.useStartSpawns = false; + + assert( team != "neutral" ); + + string = &""; + if ( oldTeam == "neutral" ) + { + level notify( "flag_captured" ); + string = GetDomFlagUseString( label, false ); + level.bestSpawnFlag[ oldTeam ] = self.levelFlag; + + self gameobjects::set_owner_team( team ); + self.visuals[0] setModel( level.flagModel[team] ); + SetDvar( "scr_obj" + self gameobjects::get_label(), team ); + self update_spawn_influencers( team ); + self flagCapturedFromNeutral( team ); + } + else + { + level notify( "flag_neutralized" ); + string = GetDomFlagUseString( label, true ); + + self gameobjects::set_owner_team( "neutral" ); + self.visuals[0] setModel( level.flagModel["neutral"] ); + SetDvar( "scr_obj" + self gameobjects::get_label(), "neutral" ); + self update_spawn_influencers( "neutral" ); + self flagNeutralized( team, oldTeam ); + } + assert ( string != &"" ); + + // Copy touch list so there aren't any threading issues + touchList = []; + touchKeys = GetArrayKeys( self.touchList[team] ); + for ( i = 0 ; i < touchKeys.size ; i++ ) + touchList[touchKeys[i]] = self.touchList[team][touchKeys[i]]; + + isBFlag = false; + if( label == "_b" ) + { + isBFlag = true; + } + + if( oldTeam == "neutral" ) + { + thread give_capture_credit( touchList, string, oldTeam, isBFlag, true ); + } + else + { + thread give_neutralized_credit( touchList, string, oldTeam, isBFlag ); + } + + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "dom_capture", label, team, player.origin ); + + if ( dominated_challenge_check() ) + { + level thread totalDomination( team ); + } +} + +function onUseWithoutNeutralizingFlag( player ) +{ + level notify( "flag_captured" ); + team = player.pers["team"]; + oldTeam = self gameobjects::get_owner_team(); + label = self gameobjects::get_label(); + + /#print( "flag captured: " + self.label );#/ + + self gameobjects::set_owner_team( team ); + self.visuals[0] setModel( level.flagModel[team] ); + SetDvar( "scr_obj" + self gameobjects::get_label(), team ); + + level.useStartSpawns = false; + + assert( team != "neutral" ); + + isBFlag = false; + if( label == "_b" ) + { + isBFlag = true; + } + string = GetDomFlagUseString( label, false ); + assert ( string != &"" ); + + // Copy touch list so there aren't any threading issues + touchList = []; + touchKeys = GetArrayKeys( self.touchList[team] ); + for ( i = 0 ; i < touchKeys.size ; i++ ) + touchList[touchKeys[i]] = self.touchList[team][touchKeys[i]]; + + thread give_capture_credit( touchList, string, oldTeam, isBFlag, false ); + + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "dom_capture", label, team, player.origin ); + + if ( oldTeam == "neutral" ) + { + self flagCapturedFromNeutral( team ); + } + else + { + self flagCapturedFromTeam( team, oldTeam ); + } + + if ( dominated_challenge_check() ) + { + level thread totalDomination( team ); + } + self update_spawn_influencers( team ); +} + +function onUse( player ) +{ + if( level.flagCanBeNeutralized ) + { + self onUseWithNeutralizingFlag( player ); + } + else + { + self onUseWithoutNeutralizingFlag( player ); + } + level change_dom_spawns(); +} + +function totalDomination( team ) +{ + level endon( "flag_captured" ); + level endon( "game_ended" ); + + wait ( 180 ); + + challenges::totalDomination( team ); +} + + +function watchForBFlagCap() +{ + level endon( "game_ended" ); + level endon( "endWatchForBFlagCapAfterTime" ); + + level thread endWatchForBFlagCapAfterTime( 60 ); + for (;;) + { + level waittill( "b_flag_captured", player ); + player challenges::capturedBFirstMinute(); + } +} + +function endWatchForBFlagCapAfterTime( time ) +{ + level endon( "game_ended" ); + wait( 60 ); + level notify( "endWatchForBFlagCapAfterTime" ); +} + +function give_capture_credit( touchList, string, lastOwnerTeam, isBFlag, neutralizing ) +{ + time = getTime(); + wait .05; + util::WaitTillSlowProcessAllowed(); + + self updateCapsPerMinute(lastOwnerTeam); + + players = getArrayKeys( touchList ); + for ( i = 0; i < players.size; i++ ) + { + player_from_touchlist = touchList[players[i]].player; + player_from_touchlist updateCapsPerMinute(lastOwnerTeam); + if ( !isScoreBoosting( player_from_touchlist, self ) ) + { + player_from_touchlist challenges::capturedObjective( time, self.levelflag ); + if (lastOwnerTeam == "neutral" && neutralizing && ( isdefined( self.hasBeenCaptured ) && self.hasBeenCaptured ) ) + { + scoreevents::processScoreEvent( "dom_point_secured_neutralizing", player_from_touchlist ); + } + else if ( lastOwnerTeam == "neutral" ) + { + if ( isBFlag ) + { + scoreevents::processScoreEvent( "dom_point_neutral_b_secured", player_from_touchlist ); + } + else + { + scoreevents::processScoreEvent( "dom_point_neutral_secured", player_from_touchlist ); + } + } + else + { + scoreevents::processScoreEvent( "dom_point_secured", player_from_touchlist ); + } + + self.hasBeenCaptured = true; + + player_from_touchlist RecordGameEvent("capture"); + if ( isBFlag ) + { + level notify( "b_flag_captured", player_from_touchlist ); + } + if( isdefined(player_from_touchlist.pers["captures"]) ) + { + player_from_touchlist.pers["captures"]++; + player_from_touchlist.captures = player_from_touchlist.pers["captures"]; + } + demo::bookmark( "event", gettime(), player_from_touchlist ); + player_from_touchlist AddPlayerStatWithGameType( "CAPTURES", 1 ); + } + else + { + /# + player_from_touchlist IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU CAPTURE CREDIT AS BOOSTING PREVENTION" ); + #/ + } + + level thread popups::DisplayTeamMessageToAll( string, player_from_touchlist ); + } +} + +function give_neutralized_credit( touchList, string, lastOwnerTeam, isBFlag ) +{ + time = getTime(); + wait .05; + util::WaitTillSlowProcessAllowed(); + + players = getArrayKeys( touchList ); + for ( i = 0; i < players.size; i++ ) + { + player_from_touchlist = touchList[players[i]].player; + player_from_touchlist updateCapsPerMinute(lastOwnerTeam); + if ( !isScoreBoosting( player_from_touchlist, self ) ) + { + scoreevents::processScoreEvent( "dom_point_neutralized_neutralizing", player_from_touchlist ); + + player_from_touchlist RecordGameEvent("neutralized"); + if( isdefined(player_from_touchlist.pers["neutralizes"]) ) + { + player_from_touchlist.pers["neutralizes"]++; + player_from_touchlist.captures = player_from_touchlist.pers["neutralizes"]; + } + demo::bookmark( "event", gettime(), player_from_touchlist ); + } + else + { + /# + player_from_touchlist IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU CAPTURE CREDIT AS BOOSTING PREVENTION" ); + #/ + } + + level thread popups::DisplayTeamMessageToAll( string, player_from_touchlist ); + } +} + +function updateDomScores() +{ + //level.playingActionMusic = false; + + if ( level.roundScoreLimit && !level.timeLimit ) + { + // Average time: 30 seconds, 6 intervals with 2 flags is 12 points + warningScore = Max( 0, level.roundScoreLimit - 12 ); + } + else + { + warningScore = 0; + } + + playedNearEndVo = false; + + alliesRoundStartScore = [[level._getTeamScore]]( "allies" ); + axisRoundStartScore = [[level._getTeamScore]]( "axis" ); + + while ( !level.gameEnded ) + { + numOwnedFlags = 0; + + scoring_teams = []; + + round_score_limit = util::get_current_round_score_limit(); + totalFlags = getTeamFlagCount( "allies" ) + getTeamFlagCount( "axis" ); + if( ( totalFlags == 3 ) && ( game["teamScores"]["allies"] == round_score_limit - 1 ) && ( game["teamScores"]["axis"] == round_score_limit - 1 ) ) + level.clampScoreLimit = false; // clear the clamp so we dont end up tie in the situation wehre the score is 199 vs 199 and one team has 2 flags and the other one has 1 + + numFlags = getTeamFlagCount( "allies" ); + numOwnedFlags += numFlags; + if ( numFlags ) + { + scoring_teams[scoring_teams.size] = "allies"; + globallogic_score::giveTeamScoreForObjective_DelayPostProcessing( "allies", numFlags ); + } + + numFlags = getTeamFlagCount( "axis" ); + numOwnedFlags += numFlags; + if ( numFlags ) + { + scoring_teams[scoring_teams.size] = "axis"; + globallogic_score::giveTeamScoreForObjective_DelayPostProcessing( "axis", numFlags ); + } + + if ( numOwnedFlags ) + globallogic_score::postProcessTeamScores( scoring_teams ); + + if ( warningScore && !playedNearEndVo ) + { + winningTeam = undefined; + + alliesRoundScore = [[level._getTeamScore]]( "allies" ) - alliesRoundStartScore; + axisRoundScore = [[level._getTeamScore]]( "axis" ) - axisRoundStartScore; + + if ( alliesRoundScore >= warningScore ) + { + winningTeam = "allies"; + } + else if ( axisRoundScore >= warningScore ) + { + winningTeam = "axis"; + } + + if ( isdefined( winningTeam ) ) + { + nearWinning = "nearWinning"; + nearLosing = "nearLosing"; + + if ( util::isOneRound() || util::isLastRound() ) + { + nearWinning = "nearWinningFinal"; + nearLosing = "nearLosingFinal"; + } + else + { + if ( RandomInt( 4 ) < 3 ) // 1 in winning, 3 in winningFinal + { + nearWinning = "nearWinningFinal"; + } + + if ( RandomInt( 4 ) < 1 ) // 3 in losing, 1 in losingFinal + { + nearLosing = "nearLosingFinal"; + } + } + + globallogic_audio::leader_dialog( nearWinning, winningTeam ); + globallogic_audio::leader_dialog_for_other_teams( nearLosing, winningTeam ); + playedNearEndVo = true; + } + } + + onScoreCloseMusic(); + + // end the game if people aren't playing + timePassed = globallogic_utils::getTimePassed(); + if ( (((timePassed / 1000) > 120 && numOwnedFlags < 2) || ((timePassed / 1000) > 300 && numOwnedFlags < 3)) && ( GameModeIsMode( 0 ) ) ) + { + thread globallogic::endGame( "tie", game["strings"]["time_limit_reached"] ); + return; + } + + wait ( 5.0 ); + hostmigration::waitTillHostMigrationDone(); + } +} + +function onScoreCloseMusic () +{ + axisScore = [[level._getTeamScore]]( "axis" ); + alliedScore = [[level._getTeamScore]]( "allies" ); + scoreLimit = level.scoreLimit; + scoreThreshold = scoreLimit * .1; + scoreDif = abs(axisScore - alliedScore); + scoreThresholdStart = abs(scoreLimit - scoreThreshold); + scoreLimitCheck = scoreLimit - 10; + + if( !isdefined( level.playingActionMusic ) ) + level.playingActionMusic = false; + + if( !isdefined( level.sndHalfway ) ) + level.sndHalfway = false; + + if (alliedScore > axisScore) + { + currentScore = alliedScore; + } + else + { + currentScore = axisScore; + } + /# + if( GetDvarint( "debug_music" ) > 0 ) + { + println ("Music System Domination - scoreDif " + scoreDif); + println ("Music System Domination - axisScore " + axisScore); + println ("Music System Domination - alliedScore " + alliedScore); + println ("Music System Domination - scoreLimit " + scoreLimit); + println ("Music System Domination - currentScore " + currentScore); + println ("Music System Domination - scoreThreshold " + scoreThreshold); + println ("Music System Domination - scoreDif " + scoreDif); + println ("Music System Domination - scoreThresholdStart " + scoreThresholdStart); + } + #/ + if ( scoreDif <= scoreThreshold && scoreThresholdStart <= currentScore && (level.playingActionMusic != true)) + { + //play some action music +// thread globallogic_audio::set_music_on_team( "timeOut" ); + } + + halfwayScore = scoreLimit*.5; + if( isdefined( level.roundScoreLimit ) ) + { + halfwayScore = level.roundScoreLimit*.5; + if( game[ "roundsplayed" ] == 1 ) + { + halfwayScore += level.roundScoreLimit; + } + } + + if( ((axisScore >= halfwayScore) || (alliedScore >= halfwayScore)) && !level.sndHalfway ) + { + + level notify( "sndMusicHalfway" ); + level.sndHalfway = true; + } + +} + +function onRoundSwitch() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + game["switchedsides"] = !game["switchedsides"]; + + if ( level.scoreRoundWinBased ) + { + [[level._setTeamScore]]( "allies", game["roundswon"]["allies"] ); + [[level._setTeamScore]]( "axis", game["roundswon"]["axis"] ); + } +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( isdefined( attacker ) && isplayer( attacker ) ) + { + scoreEventProcessed = false; + if ( attacker.touchTriggers.size && attacker.pers["team"] != self.pers["team"] ) + { + triggerIds = getArrayKeys( attacker.touchTriggers ); + ownerTeam = attacker.touchTriggers[triggerIds[0]].useObj.ownerTeam; + team = attacker.pers["team"]; + if ( team != ownerTeam ) + { + scoreevents::processScoreEvent( "kill_enemy_while_capping_dom", attacker, undefined, weapon ); + scoreEventProcessed = true; + } + } + + for ( index = 0; index < level.flags.size; index++ ) + { + flagTeam = "invalidTeam"; + inFlagZone = false; + defendedFlag = false; + offendedFlag = false; + + flagOrigin = level.flags[index].origin; + + offenseRadiusSQ = 300 * 300; + dist = Distance2dSquared(self.origin, flagOrigin); + if ( dist < offenseRadiusSQ ) + { + inFlagZone = true; + if ( level.flags[index] getFlagTeam() == attacker.pers["team"] || level.flags[index] getFlagTeam() == "neutral" ) + defendedFlag = true; + else + offendedFlag = true; + } + dist = Distance2dSquared(attacker.origin, flagOrigin); + if ( dist < offenseRadiusSQ ) + { + inFlagZone = true; + if ( level.flags[index] getFlagTeam() == attacker.pers["team"] || level.flags[index] getFlagTeam() == "neutral" ) + defendedFlag = true; + else + offendedFlag = true; + } + + if ( inFlagZone && isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] ) + { + if ( offendedFlag ) + { + if ( !isdefined( attacker.dom_defends ) ) + attacker.dom_defends = 0; + + attacker.dom_defends++; + if ( level.playerDefensiveMax >= attacker.dom_defends ) + { + attacker thread challenges::killedBaseDefender( level.flags[index] ); + if (!scoreeventprocessed) + { + scoreevents::processScoreEvent( "killed_defender", attacker, undefined, weapon ); + } + self RecordKillModifier("defending"); + break; + } + else + { + /# + attacker IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU DEFENSIVE CREDIT AS BOOSTING PREVENTION" ); + #/ + } + + + } + if ( defendedFlag ) + { + if ( !isdefined( attacker.dom_offends ) ) + attacker.dom_offends = 0; + + attacker thread updateattackermultikills(); + + attacker.dom_offends++; + if ( level.playerOffensiveMax >= attacker.dom_offends ) + { + attacker.pers["defends"]++; + attacker.defends = attacker.pers["defends"]; + attacker thread challenges::killedBaseOffender( level.flags[index], weapon ); + attacker RecordGameEvent("return"); + attacker challenges::killedZoneAttacker( weapon ); + if (!scoreeventprocessed) + { + scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon ); + } + self RecordKillModifier("assaulting"); + break; + } + else + { + /# + attacker IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU OFFENSIVE CREDIT AS BOOSTING PREVENTION" ); + #/ + } + } + } + } + + if ( self.touchTriggers.size && attacker.pers["team"] != self.pers["team"] ) + { + triggerIds = getArrayKeys( self.touchTriggers ); + ownerTeam = self.touchTriggers[triggerIds[0]].useObj.ownerTeam; + team = self.pers["team"]; + if ( team != ownerTeam ) + { + flag = self.touchTriggers[triggerIds[0]].useObj; + if ( isdefined( flag.contested ) && flag.contested == true) + { + attacker killWhileContesting( flag ); + } + } + } + } +} + +function killWhileContesting( flag ) +{ + self notify( "killWhileContesting" ); + self endon( "killWhileContesting" ); + self endon( "disconnect" ); + + killTime = getTime(); + playerteam = self.pers["team"]; + if ( !isdefined ( self.clearEnemyCount ) ) + { + self.clearEnemyCount = 0; + } + + self.clearEnemyCount++; + + flag waittill( "contest_over" ); + + + if (playerteam != self.pers["team"] || ( isdefined( self.spawntime ) && ( killTime < self.spawntime ) ) ) + { + self.clearEnemyCount = 0; + return; + } + if ( flag.ownerTeam != playerteam && flag.ownerTeam != "neutral" ) + { + self.clearEnemyCount = 0; + return; + } + + if ( self.clearEnemyCount >= 2 && killTime + 200 > getTime() ) + { + scoreevents::processScoreEvent( "clear_2_attackers", self ); + } + self.clearEnemyCount = 0; +} + + +function updateattackermultikills() +{ + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + self notify ( "updateDomRecentKills" ); + self endon ( "updateDomRecentKills" ); + if ( !isdefined (self.recentDomAttackerKillCount) ) + { + self.recentDomAttackerKillCount = 0; + } + self.recentDomAttackerKillCount++; + + wait ( 4.0 ); + + + if ( self.recentDomAttackerKillCount > 1 ) + self challenges::domAttackerMultiKill( self.recentDomAttackerKillCount ); + + self.recentDomAttackerKillCount = 0; +} + + + +function getTeamFlagCount( team ) +{ + score = 0; + for (i = 0; i < level.flags.size; i++) + { + if ( level.domFlags[i] gameobjects::get_owner_team() == team ) + score++; + } + return score; +} + +function getFlagTeam() +{ + return self.useObj gameobjects::get_owner_team(); +} + +function getBoundaryFlags() +{ + // get all flags which are adjacent to flags that aren't owned by the same team + bflags = []; + for (i = 0; i < level.flags.size; i++) + { + for (j = 0; j < level.flags[i].adjflags.size; j++) + { + if (level.flags[i].useObj gameobjects::get_owner_team() != level.flags[i].adjflags[j].useObj gameobjects::get_owner_team() ) + { + bflags[bflags.size] = level.flags[i]; + break; + } + } + } + + return bflags; +} + +function getBoundaryFlagSpawns(team) +{ + spawns = []; + + bflags = getBoundaryFlags(); + for (i = 0; i < bflags.size; i++) + { + if (isdefined(team) && bflags[i] getFlagTeam() != team) + continue; + + for (j = 0; j < bflags[i].nearbyspawns.size; j++) + spawns[spawns.size] = bflags[i].nearbyspawns[j]; + } + + return spawns; +} + +function getSpawnsBoundingFlag( avoidflag ) +{ + spawns = []; + + for (i = 0; i < level.flags.size; i++) + { + flag = level.flags[i]; + if ( flag == avoidflag ) + continue; + + isbounding = false; + for (j = 0; j < flag.adjflags.size; j++) + { + if ( flag.adjflags[j] == avoidflag ) + { + isbounding = true; + break; + } + } + + if ( !isbounding ) + continue; + + for (j = 0; j < flag.nearbyspawns.size; j++) + spawns[spawns.size] = flag.nearbyspawns[j]; + } + + return spawns; +} + +// gets an array of all spawnpoints which are near flags that are +// owned by the given team, or that are adjacent to flags owned by the given team. +function getOwnedAndBoundingFlagSpawns(team) +{ + spawns = []; + + for (i = 0; i < level.flags.size; i++) + { + if ( level.flags[i] getFlagTeam() == team ) + { + // add spawns near this flag + for (s = 0; s < level.flags[i].nearbyspawns.size; s++) + spawns[spawns.size] = level.flags[i].nearbyspawns[s]; + } + else + { + for (j = 0; j < level.flags[i].adjflags.size; j++) + { + if ( level.flags[i].adjflags[j] getFlagTeam() == team ) + { + // add spawns near this flag + for (s = 0; s < level.flags[i].nearbyspawns.size; s++) + spawns[spawns.size] = level.flags[i].nearbyspawns[s]; + break; + } + } + } + } + + return spawns; +} + +// gets an array of all spawnpoints which are near flags that are +// owned by the given team +function getOwnedFlagSpawns(team) +{ + spawns = []; + + for (i = 0; i < level.flags.size; i++) + { + if ( level.flags[i] getFlagTeam() == team ) + { + // add spawns near this flag + for (s = 0; s < level.flags[i].nearbyspawns.size; s++) + spawns[spawns.size] = level.flags[i].nearbyspawns[s]; + } + } + + return spawns; +} + +function flagSetup() +{ + maperrors = []; + descriptorsByLinkname = []; + + // (find each flag_descriptor object) + descriptors = getentarray("flag_descriptor", "targetname"); + + flags = level.flags; + + for (i = 0; i < level.domFlags.size; i++) + { + closestdist = undefined; + closestdesc = undefined; + for (j = 0; j < descriptors.size; j++) + { + dist = distance(flags[i].origin, descriptors[j].origin); + if (!isdefined(closestdist) || dist < closestdist) { + closestdist = dist; + closestdesc = descriptors[j]; + } + } + + if (!isdefined(closestdesc)) { + maperrors[maperrors.size] = "there is no flag_descriptor in the map! see explanation in dom.gsc"; + break; + } + if (isdefined(closestdesc.flag)) { + maperrors[maperrors.size] = "flag_descriptor with script_linkname \"" + closestdesc.script_linkname + "\" is nearby more than one flag; is there a unique descriptor near each flag?"; + continue; + } + flags[i].descriptor = closestdesc; + closestdesc.flag = flags[i]; + descriptorsByLinkname[closestdesc.script_linkname] = closestdesc; + } + + if (maperrors.size == 0) + { + // find adjacent flags + for (i = 0; i < flags.size; i++) + { + if (isdefined(flags[i].descriptor.script_linkto)) + adjdescs = strtok(flags[i].descriptor.script_linkto, " "); + else + adjdescs = []; + for (j = 0; j < adjdescs.size; j++) + { + otherdesc = descriptorsByLinkname[adjdescs[j]]; + if (!isdefined(otherdesc) || otherdesc.targetname != "flag_descriptor") { + maperrors[maperrors.size] = "flag_descriptor with script_linkname \"" + flags[i].descriptor.script_linkname + "\" linked to \"" + adjdescs[j] + "\" which does not exist as a script_linkname of any other entity with a targetname of flag_descriptor (or, if it does, that flag_descriptor has not been assigned to a flag)"; + continue; + } + adjflag = otherdesc.flag; + if (adjflag == flags[i]) { + maperrors[maperrors.size] = "flag_descriptor with script_linkname \"" + flags[i].descriptor.script_linkname + "\" linked to itself"; + continue; + } + flags[i].adjflags[flags[i].adjflags.size] = adjflag; + } + } + } + + // assign each spawnpoint to nearest flag + spawnpoints = spawnlogic::get_spawnpoint_array( "mp_dom_spawn" ); + for (i = 0; i < spawnpoints.size; i++) + { + if (isdefined(spawnpoints[i].script_linkto)) { + desc = descriptorsByLinkname[spawnpoints[i].script_linkto]; + if (!isdefined(desc) || desc.targetname != "flag_descriptor") { + maperrors[maperrors.size] = "Spawnpoint at " + spawnpoints[i].origin + "\" linked to \"" + spawnpoints[i].script_linkto + "\" which does not exist as a script_linkname of any entity with a targetname of flag_descriptor (or, if it does, that flag_descriptor has not been assigned to a flag)"; + continue; + } + nearestflag = desc.flag; + } + else { + nearestflag = undefined; + nearestdist = undefined; + for (j = 0; j < flags.size; j++) + { + dist = distancesquared(flags[j].origin, spawnpoints[i].origin); + if (!isdefined(nearestflag) || dist < nearestdist) + { + nearestflag = flags[j]; + nearestdist = dist; + } + } + } + nearestflag.nearbyspawns[nearestflag.nearbyspawns.size] = spawnpoints[i]; + } + + if (maperrors.size > 0) + { + /# + println("^1------------ Map Errors ------------"); + for(i = 0; i < maperrors.size; i++) + println(maperrors[i]); + println("^1------------------------------------"); + util::error("Map errors. See above"); + #/ + callback::abort_level(); + + return; + } +} + +function createFlagSpawnInfluencers() +{ + ss = level.spawnsystem; + + for (flag_index = 0; flag_index < level.flags.size; flag_index++) + { + if ( level.domFlags[flag_index] == self ) + break; + } + + // domination: owned flag influencers + self.owned_flag_influencer = self spawning::create_influencer( "dom_friendly", self.trigger.origin, 0 ); + + // domination: un-owned inner flag influencers + self.neutral_flag_influencer = self spawning::create_influencer( "dom_neutral", self.trigger.origin, 0 ); + + // domination: enemy flag influencers + self.enemy_flag_influencer = self spawning::create_influencer( "dom_enemy", self.trigger.origin, 0 ); + + // default it to neutral + self update_spawn_influencers("neutral"); +} + +function update_spawn_influencers( team ) +{ + assert(isdefined(self.neutral_flag_influencer)); + assert(isdefined(self.owned_flag_influencer)); + assert(isdefined(self.enemy_flag_influencer)); + + if ( team == "neutral" ) + { + EnableInfluencer(self.neutral_flag_influencer, true); + EnableInfluencer(self.owned_flag_influencer, false); + EnableInfluencer(self.enemy_flag_influencer, false); + } + else + { + EnableInfluencer(self.neutral_flag_influencer, false); + EnableInfluencer(self.owned_flag_influencer, true); + EnableInfluencer(self.enemy_flag_influencer, true); + + SetInfluencerTeammask(self.owned_flag_influencer, util::getTeamMask(team) ); + SetInfluencerTeammask(self.enemy_flag_influencer, util::getOtherTeamsMask(team) ); + } +} + +function addSpawnPointsForFlag( team, flag_team, flagSpawnName ) +{ + // flag_team is the team that owns the flag + // team is the team we are looking to add spawns for + // we should add spawns for team only if the flag isn't owned by the enemy team (it may be neutral or owned by us) + + if ( game["switchedsides"] ) + team = util::getOtherTeam( team ); + + otherTeam = util::getOtherTeam( team ); + + if ( flag_team != otherTeam ) + { + spawnlogic::add_spawn_points( team, flagSpawnName ); + } +} + +//Changes what spawns are available to a team based on what Domination point they own +function change_dom_spawns() +{ + + spawnlogic::clear_spawn_points(); + spawnlogic::add_spawn_points( "allies", "mp_dom_spawn" ); + spawnlogic::add_spawn_points( "axis", "mp_dom_spawn" ); + + //If one team owns all flags, we want to allow both teams to spawn anywhere + flag_number = level.flags.size; + if( dominated_check() ) + { + for ( i = 0 ; i < flag_number ; i++ ) + { + label = level.flags[i].useobj gameobjects::get_label(); + flagSpawnName = "mp_dom_spawn_flag" + label; + spawnlogic::add_spawn_points( "allies", flagSpawnName ); + spawnlogic::add_spawn_points( "axis", flagSpawnName ); + } + } + else + { + for ( i = 0; i < flag_number; i++ ) + { + //'getlabel' gives us the appropriate "_a" or "_b" + label = level.flags[i].useobj gameobjects::get_label(); + flagSpawnName = "mp_dom_spawn_flag" + label; + flag_team = level.flags[i] getFlagTeam(); + + addSpawnPointsForFlag( "allies", flag_team, flagSpawnName ); + addSpawnPointsForFlag( "axis", flag_team, flagSpawnName ); + } + } + + spawning::updateAllSpawnPoints(); + +} + +function dominated_challenge_check() +{ + num_flags = level.flags.size; + allied_flags = 0; + axis_flags = 0; + + for ( i = 0 ; i < num_flags ; i++ ) + { + flag_team = level.flags[i] getFlagTeam(); + + if ( flag_team == "allies" ) + { + allied_flags++; + } + else if ( flag_team == "axis" ) + { + axis_flags++; + } + else + { + return false; + } + + if ( ( allied_flags > 0 ) && ( axis_flags > 0 ) ) + return false; + } + + return true; +} +//This function checks to see if one team owns all three flags +function dominated_check() +{ + num_flags = level.flags.size; + allied_flags = 0; + axis_flags = 0; + + for ( i = 0 ; i < num_flags ; i++ ) + { + flag_team = level.flags[i] getFlagTeam(); + + if ( flag_team == "allies" ) + { + allied_flags++; + } + else if ( flag_team == "axis" ) + { + axis_flags++; + } + + if ( ( allied_flags > 0 ) && ( axis_flags > 0 ) ) + return false; + } + + return true; +} + +function updateCapsPerMinute(lastOwnerTeam) +{ + if ( !isdefined( self.capsPerMinute ) ) + { + self.numCaps = 0; + self.capsPerMinute = 0; + } + + // not including neutral flags as part of the boosting prevention + // to help with false positives at the start + if ( lastOwnerTeam == "neutral" ) + return; + + self.numCaps++; + + minutesPassed = globallogic_utils::getTimePassed() / ( 60 * 1000 ); + + // players use the actual time played + if ( IsPlayer( self ) && isdefined(self.timePlayed["total"]) ) + minutesPassed = self.timePlayed["total"] / 60; + + self.capsPerMinute = self.numCaps / minutesPassed; + if ( self.capsPerMinute > self.numCaps ) + self.capsPerMinute = self.numCaps; +} + +function isScoreBoosting( player, flag ) +{ + if ( !level.rankedMatch ) + return false; + + if ( player.capsPerMinute > level.playerCaptureLPM ) + return true; + + if ( flag.capsPerMinute > level.flagCaptureLPM ) + return true; + + return false; +} + + +function onUpdateUseRate() +{ + if ( !isdefined( self.contested ) ) + { + self.contested = false; + } + + numOther = gameobjects::get_num_touching_except_team( self.ownerTeam ); + numOwners = self.numTouching[self.claimTeam]; + + previousState = self.contested; + if ( numOther > 0 && numOwners > 0 ) + { + self.contested = true; + } + else + { + if ( previousState == true ) + { + self notify( "contest_over" ); + } + self.contested = false; + } +} \ No newline at end of file diff --git a/mp/gametypes/escort.csc b/mp/gametypes/escort.csc new file mode 100644 index 0000000..32fe278 --- /dev/null +++ b/mp/gametypes/escort.csc @@ -0,0 +1,150 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\duplicaterender_mgr; +#using scripts\shared\util_shared; + +#using scripts\mp\_shoutcaster; + + + + + + + + + + + + + +#precache( "client_tagfxset", "escort_robot_burn" ); + +function main() +{ + clientfield::register( "actor", "robot_state" , 1, 2, "int", &robot_state_changed, !true, true ); + clientfield::register( "actor", "escort_robot_burn" , 1, 1, "int", &robot_burn, !true, !true ); + + callback::on_localclient_connect( &on_localclient_connect ); +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} + +function on_localclient_connect( localClientNum ) +{ + // Initialize the ui model values + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "escortGametype.robotStatusText" ), &"MPUI_ESCORT_ROBOT_MOVING" ); + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "escortGametype.robotStatusVisible" ), 0 ); + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "escortGametype.enemyRobot" ), 0 ); + + level wait_team_changed( localClientNum ); +} + +// Clientfield Callbacks +//======================================== + +function robot_burn( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if( newVal ) + { + self endon( "entityshutdown" ); + self util::waittill_dobj( localClientNum ); + + fxHandles = PlayTagFXSet( localClientNum, "escort_robot_burn", self ); + self thread watch_fx_shutdown( localClientNum, fxHandles ); + } +} + +function watch_fx_shutdown( localClientNum, fxHandles ) +{ + wait( 3 ); + + foreach( fx in fxHandles ) + { + StopFX( localclientnum, fx ); + } +} + +function robot_state_changed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( bNewEnt ) + { + if ( !isdefined( level.escortRobots ) ) level.escortRobots = []; else if ( !IsArray( level.escortRobots ) ) level.escortRobots = array( level.escortRobots ); level.escortRobots[level.escortRobots.size]=self;; + + self thread update_robot_team( localClientNum ); + } + + if ( newVal == 1 ) + { + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "escortGametype.robotStatusVisible" ), 1 ); + } + else + { + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "escortGametype.robotStatusVisible" ), 0 ); + } +} + + +// HUD Updates +//======================================== + +function wait_team_changed( localClientNum ) +{ + while( 1 ) + { + level waittill( "team_changed" ); + + // the local player might not be valid yet and will cause the team detection functionality not to work + while ( !isdefined( GetNonPredictedLocalPlayer( localClientNum ) ) ) + { + wait( .05 ); + } + + if ( !isdefined( level.escortRobots ) ) + { + continue; + } + + foreach ( robot in level.escortRobots ) + { + robot thread update_robot_team( localClientNum ); + } + } +} + +function update_robot_team( localClientNum ) +{ + localPlayerTeam = GetLocalPlayerTeam( localClientNum ); + + if ( shoutcaster::is_shoutcaster( localClientNum ) ) + { + friend = self shoutcaster::is_friendly( localclientnum ); + } + else + { + friend = self.team == localPlayerTeam; + } + + if ( friend ) + { + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "escortGametype.enemyRobot" ), 0 ); + } + else + { + SetUIModelValue( CreateUIModel( GetUIModelForController( localClientNum ), "escortGametype.enemyRobot" ), 1 ); + } + + // TODO: FOR SHIP - Get this working when a player switches teams mid game + // Update the robot friend/enemy material + self duplicate_render::set_dr_flag( "enemyvehicle_fb", !friend ); + + localPlayer = GetLocalPlayer( localClientNum ); + localPlayer duplicate_render::update_dr_filters(localClientNum); +} \ No newline at end of file diff --git a/mp/gametypes/escort.gsc b/mp/gametypes/escort.gsc new file mode 100644 index 0000000..d0ca3f1 --- /dev/null +++ b/mp/gametypes/escort.gsc @@ -0,0 +1,2108 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\lui_shared; +#using scripts\shared\math_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\ai_shared; +#using scripts\shared\ai\archetype_robot; +#using scripts\shared\ai\archetype_utility; +#using scripts\shared\ai\systems\blackboard; +#using scripts\shared\ai\systems\gib; +#using scripts\shared\weapons\_heatseekingmissile; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\vehicleriders_shared; +#using scripts\shared\flagsys_shared; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_supplydrop; + + + + + + + +#using scripts\mp\gametypes\ctf; + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\teams\_teams; +#using scripts\mp\_util; + + + +/* + Safeguard: Get Chippy to the enemy goal + + Level requirements + ------------------ + Spawnpoints: + Attacker Start Spawnpoints: + classname mp_escort_spawn_attacker_start + Attackers spawn from these at start of match. + + Attacker Respawn Spawnpoints: + classname mp_escort_spawn_attacker + Attackers respawn from these. Place on the attacking side of the map. + + Defender Start Spawnpoints: + classname mp_escort_spawn_defender_start + Defenders spawn from these at start of match. + + Defender Respawn Spawnpoints: + classname mp_escort_spawn_defender_start + Defenders respawn from these. Place on the defending side of the map. + + Spectator Spawnpoints: + + Triggers: + escort_robot_move_trig - The robot will spawn in the middle of this trigger and will move when friendly players are inside it + escort_robot_goal_trig - The robot reaching the trigger ends the round + + Use Triggers: + escort_robot_reboot_trig - Not used + + Pathnodes: + escort_robot_path_start - This is the first node the robot will move to +*/ + + + + + + + + + + + + + + + + + + + + + +// Tweaks to how the combat robot's body is thrown after exploding + // Scales the initial velocity + + + + + + + + + + + + + + + + + + + + + +#precache( "string", "OBJECTIVES_ESCORT_ATTACKER" ); +#precache( "string", "OBJECTIVES_ESCORT_DEFENDER" ); +#precache( "string", "OBJECTIVES_ESCORT_ATTACKER_SCORE" ); +#precache( "string", "OBJECTIVES_ESCORT_DEFENDER_SCORE" ); +#precache( "string", "OBJECTIVES_ESCORT_ATTACKER_HINT" ); +#precache( "string", "OBJECTIVES_ESCORT_DEFENDER_HINT" ); + +//#precache( "string", "MPUI_ESCORT_ROBOT_IDLE" ); +#precache( "string", "MPUI_ESCORT_ROBOT_MOVING" ); +//#precache( "string", "MPUI_ESCORT_ROBOT_REBOOTING" ); + +#precache( "string", "MP_ESCORT_OVERTIME_ROUND_1_ATTACKERS" ); +#precache( "string", "MP_ESCORT_OVERTIME_ROUND_1_DEFENDERS" ); +#precache( "string", "MP_ESCORT_OVERTIME_ROUND_2_ATTACKERS" ); +#precache( "string", "MP_ESCORT_OVERTIME_ROUND_2_DEFENDERS" ); +#precache( "string", "MP_ESCORT_OVERTIME_ROUND_2_TIE_ATTACKERS" ); +#precache( "string", "MP_ESCORT_OVERTIME_ROUND_2_TIE_DEFENDERS" ); + +#precache( "string", "MP_ESCORT_ROBOT_DISABLED" ); + +#precache( "triggerstring", "PLATFORM_HOLD_TO_REBOOT_ROBOT" ); +#precache( "string", "MP_REBOOTING_ROBOT" ); + +#precache( "fx", "ui/fx_dom_marker_team_r120" ); +#precache( "fx", "weapon/fx_c4_exp_metal" ); + +#precache( "objective", "escort_goal" ); +#precache( "objective", "escort_robot" ); + + +function autoexec __init__sytem__() { system::register("escort",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "actor", "robot_state" , 1, 2, "int" ); + clientfield::register( "actor", "escort_robot_burn" , 1, 1, "int" ); + + callback::on_spawned( &on_player_spawned ); +} + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerRoundScoreLimit( 0, 2000 ); + util::registerScoreLimit( 0, 5000 ); + util::registerRoundLimit( 0, 12 ); + util::registerRoundSwitch( 0, 9 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + level.bootTime = GetGametypeSetting( "bootTime" ); // Initial time for the robot to boot when the game starts + level.rebootTime = GetGametypeSetting( "rebootTime" ); // Time it takes for the robot to reboot + level.rebootPlayers = GetGametypeSetting( "rebootPlayers" ); // 0 auto rebot, 1 players in radius, 2 players in radius with bonus + level.movePlayers = GetGametypeSetting( "movePlayers" ); // 0 auto move, 1 players in radius + + level.robotShield = GetGametypeSetting( "robotShield" ); // 0 no riot shield, 1 riot shield + + level.robotSpeed = "run"; + + switch( GetGametypeSetting( "shutdownDamage" ) ) + { + case 1: // Low + level.escortRobotKillstreakBundle = "escort_robot_low"; + break; + case 2: // Normal + level.escortRobotKillstreakBundle = "escort_robot"; + break; + case 3: // High + level.escortRobotKillstreakBundle = "escort_robot_high"; + case 0: // Invulnerable + default: + level.shutdownDamage = 0; + } + + if ( isdefined( level.escortRobotKillstreakBundle ) ) + { + killstreak_bundles::register_killstreak_bundle( level.escortRobotKillstreakBundle ); + level.shutdownDamage = killstreak_bundles::get_max_health( level.escortRobotKillstreakBundle ); + } + +/# + switch ( GetDvarInt( "robotSpeed", 1 ) ) + { + case 1: + level.robotSpeed = "run"; + break; + case 2: + level.robotSpeed = "sprint"; + break; + case 0: + default: + level.robotSpeed = "walk"; + } +#/ + globallogic_audio::set_leader_gametype_dialog ( "startSafeguard", "hcStartSafeguard", "sfgStartAttack", "sfgStartDefend" ); + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "escorts", "disables", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "escorts", "disables" ); + + level.teamBased = true; + level.overrideTeamScore = true; + level.scoreRoundWinBased = true; + level.doubleOvertime = true; + + level.onPrecacheGameType =&onPrecacheGameType; + level.onStartGameType =&onStartGameType; + + level.onSpawnPlayer =&onSpawnPlayer; + level.onPlayerKilled =&onPlayerKilled; + + level.onTimeLimit =&onTimeLimit; + level.onRoundSwitch =&onRoundSwitch; + level.onEndGame =&onEndGame; + level.shouldPlayOvertimeRound =&shouldPlayOvertimeRound; + + level.onRoundEndGame =&onRoundEndGame; + + gameobjects::register_allowed_gameobject( level.gameType ); + + killstreak_bundles::register_killstreak_bundle( "escort_robot" ); +} + +function onPrecacheGameType() +{ + +} + +function onStartGameType() +{ + level.useStartSpawns = true; + + if ( !isdefined( game["switchedsides"] ) ) + { + game["switchedsides"] = false; + } + + setClientNameMode("auto_change"); + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + util::setObjectiveText( game["attackers"], &"OBJECTIVES_ESCORT_ATTACKER" ); + util::setObjectiveText( game["defenders"], &"OBJECTIVES_ESCORT_DEFENDER" ); + + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_ESCORT_ATTACKER_SCORE" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_ESCORT_DEFENDER_SCORE" ); + + util::setObjectiveHintText( game["attackers"], &"OBJECTIVES_ESCORT_ATTACKER_HINT" ); + util::setObjectiveHintText( game["defenders"], &"OBJECTIVES_ESCORT_DEFENDER_HINT" ); + + if ( isdefined( game["overtime_round"] ) ) + { + [[level._setTeamScore]]( "allies", 0 ); + [[level._setTeamScore]]( "axis", 0 ); + + if ( isdefined( game["escort_overtime_time_to_beat"] ) ) + { + // Round down to the last second to prevent losses where the displayed resolutions of the time match + timeS = game["escort_overtime_time_to_beat"] / 1000; + timeM = Int( timeS ) / 60; + util::registerTimeLimit( timeM, timeM ); + } + + if ( game["overtime_round"] == 1 ) + { + level.onTimeLimit = &onTimeLimit_Overtime1; + util::setObjectiveHintText( game["attackers"], &"MP_ESCORT_OVERTIME_ROUND_1_ATTACKERS" ); + util::setObjectiveHintText( game["defenders"], &"MP_ESCORT_OVERTIME_ROUND_1_DEFENDERS" ); + } + else + { + level.onTimeLimit = &onTimeLimit_Overtime2; + util::setObjectiveHintText( game["attackers"], &"MP_ESCORT_OVERTIME_ROUND_2_TIE_ATTACKERS" ); + util::setObjectiveHintText( game["defenders"], &"MP_ESCORT_OVERTIME_ROUND_2_TIE_DEFENDERS" ); + } + } + + // Set up Spawn points + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + spawnlogic::place_spawn_points( "mp_escort_spawn_attacker_start" ); + spawnlogic::place_spawn_points( "mp_escort_spawn_defender_start" ); + + level.spawn_start = []; + level.spawn_start["allies"] = spawnlogic::get_spawnpoint_array( "mp_escort_spawn_attacker_start" ); + level.spawn_start["axis"] = spawnlogic::get_spawnpoint_array( "mp_escort_spawn_defender_start" ); + + spawnlogic::add_spawn_points( "allies", "mp_escort_spawn_attacker" ); + spawnlogic::add_spawn_points( "axis", "mp_escort_spawn_defender" ); + + spawning::add_fallback_spawnpoints( "allies", "mp_tdm_spawn" ); + spawning::add_fallback_spawnpoints( "axis", "mp_tdm_spawn" ); + + spawning::updateAllSpawnPoints(); + spawning::update_fallback_spawnpoints(); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + // Spawn Robot + level thread drop_robot(); +} + +function onSpawnPlayer( predictedSpawn ) +{ + spawning::onSpawnPlayer(predictedSpawn); +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( !isdefined( attacker ) || attacker == self || !IsPlayer( attacker ) || attacker.team == self.team ) + { + return; + } + + if ( self.team == game["defenders"] && ( isdefined( attacker.escortingRobot ) && attacker.escortingRobot ) ) + { + attacker RecordGameEvent("attacking"); + scoreevents::processScoreEvent( "killed_defender", attacker ); + } + else if ( self.team == game["attackers"] && ( isdefined( self.escortingRobot ) && self.escortingRobot ) ) + { + attacker RecordGameEvent("defending"); + scoreevents::processScoreEvent( "killed_attacker", attacker ); + } +} + +function onTimeLimit() +{ + winner = game["defenders"]; + globallogic_score::giveTeamScoreForObjective_DelayPostProcessing( winner, 1 ); + level thread globallogic::endGame( winner, game["strings"]["time_limit_reached"] ); +} + +function onTimeLimit_Overtime1() +{ + winner = game["defenders"]; + level thread globallogic::endGame( winner, game["strings"]["time_limit_reached"] ); +} + +function onTimeLimit_Overtime2() +{ + winner = game["defenders"]; + prevWinner = game["escort_overtime_first_winner"]; + + if( winner == prevWinner ) + { + level thread globallogic::endGame( winner, game["strings"]["time_limit_reached"] ); + } + else + { + level thread globallogic::endGame( "tie", game["strings"]["time_limit_reached"] ); + } +} + +function onRoundSwitch() +{ + game["switchedsides"] = !game["switchedsides"]; +} + +function onEndGame( winningTeam ) +{ + if ( isdefined( game["overtime_round"] ) ) + { + if ( game["overtime_round"] == 1 ) + { + game["escort_overtime_first_winner"] = winningTeam; + if( winningTeam == game["defenders"] ) + game["escort_overtime_time_to_beat"] = undefined; + else + game["escort_overtime_time_to_beat"] = globallogic_utils::getTimePassed(); + } + else + { + game["escort_overtime_second_winner"] = winningTeam; + if( isdefined( winningTeam ) && winningTeam != "tie" ) + game["escort_overtime_best_time"] = globallogic_utils::getTimePassed(); + } + } + + level.robot thread delete_on_endgame_sequence(); +} + +function shouldPlayOvertimeRound() +{ + if ( isdefined( game["overtime_round"] ) ) + { + if ( game["overtime_round"] == 1 || !level.gameEnded ) // If we've only played 1 round or we're in the middle of the 2nd keep going + { + return true; + } + + return false; + } + + alliesRoundsWon = util::getRoundsWon( "allies" ); + axisRoundsWon = util::getRoundsWon( "axis" ); + + if ( util::hitRoundLimit() && ( alliesRoundsWon == axisRoundsWon ) ) + { + return true; + } + + return false; +} + +function onRoundEndGame( winningTeam ) +{ + if ( isdefined( game["overtime_round"] ) ) + { + foreach( team in level.teams ) + { + score = game["roundswon"][team]; + [[level._setTeamScore]]( team, score ); + } + + return winningTeam; + } + + return globallogic::determineTeamWinnerByTeamScore(); +} + +// Callbacks +//======================================== + +function on_player_spawned() +{ + self.escortingRobot = undefined; +} + +function drop_robot() +{ + globallogic::waitForPlayers(); + + moveTrigger = GetEnt( "escort_robot_move_trig", "targetname" ); + pathArray = get_robot_path_array(); + startDir = pathArray[0] - moveTrigger.origin; + startAngles = VectorToAngles( startDir ); + + //level.robot = spawn_robot( moveTrigger.origin, ( 0, startAngles[1], 0 ) ); + + drop_origin = moveTrigger.origin; + drop_height = (isdefined(level.escort_drop_height)?level.escort_drop_height:supplydrop::getDropHeight( drop_origin )); + heli_drop_goal = ( drop_origin[0], drop_origin[1], drop_height ); + + goalPath = undefined; + + dropOffset = ( 0, -120, 0 ); + + goalPath = supplydrop::supplyDropHeliStartPath_v2_setup( heli_drop_goal, dropOffset ); + supplydrop::supplyDropHeliStartPath_v2_part2_local( heli_drop_goal, goalPath, dropOffset ); + + drop_direction = VectorToAngles( ( heli_drop_goal[0], heli_drop_goal[1], 0 ) - ( goalPath.start[0], goalPath.start[1], 0 ) ); + + //chopper = spawnHelicopter( getplayers()[0], goalPath.start, drop_direction, "combat_escort_robot_dropship", "" ); + chopper = spawnHelicopter( getplayers()[0], heli_drop_goal, ( 0, 0, 0 ), "combat_escort_robot_dropship", "" ); + + ///#sphere( drop_origin, 10, (0,1,0), 1, true, 10, 1000 );#/ + + chopper.maxhealth = 999999; + chopper.health = 999999; + chopper.spawnTime = GetTime(); + supplydropSpeed = (isdefined(level.escort_drop_speed)?level.escort_drop_speed:GetDvarInt ( "scr_supplydropSpeedStarting" , 1000 )) ; // 250); + supplydropAccel = (isdefined(level.escort_drop_accel)?level.escort_drop_accel:GetDvarInt ( "scr_supplydropAccelStarting" , 1000 )); //175); + chopper SetSpeed( supplydropSpeed, supplydropAccel ); + maxPitch = GetDvarInt( "scr_supplydropMaxPitch", 25); + maxRoll = GetDvarInt( "scr_supplydropMaxRoll", 45 ); // 85); + chopper SetMaxPitchRoll( 0, maxRoll ); + + spawnPosition = ( 0,0,0 ); + spawnAngles = ( 0,0,0 ); + + level.robot = spawn_robot( spawnPosition, spawnAngles ); + level.robot.onground = undefined; + level.robot.team = game["attackers"]; + level.robot SetForceNoCull(); + level.robot.vehicle = chopper; + level.robot.vehicle.ignore_seat_check = true; + level.robot vehicle::get_in( chopper , "driver", true ); + level.robot.DropUnderVehicleOriginOverride = true; + + //chopper thread airsupport::followPath( goalPath.path, "drop_goal", true ); + + //chopper thread supplydrop::speedRegulator( heli_drop_goal ); + + //chopper waittill( "drop_goal" ); + + level.robot.targetAngles = startAngles; + + chopper vehicle::unload( "all" ); + //iprintlnbold ("LANDED"); + level.robot playsound ("evt_safeguard_robot_land"); + + chopper thread drop_heli_leave(); + + while( level.robot flagsys::get( "in_vehicle" ) ) + { + wait 1; + } + + level.robot.pathArray = pathArray; + level.robot.pathIndex = 0; + level.robot.victimSoundMod = "safeguard_robot"; + level.robot.goalJustBlocked = false; + + level.robot thread update_stop_position(); + level.robot thread watch_robot_damaged(); + level.robot thread wait_robot_moving(); + level.robot thread wait_robot_stopped(); + + level.robot.spawn_influencer_friendly = level.robot spawning::create_entity_friendly_influencer( "escort_robot_attackers", game["attackers"] ); + +/# + debug_draw_robot_path(); + level thread debug_reset_robot_to_start(); +#/ + + // Triggers + level.moveObject = setup_move_object( level.robot, "escort_robot_move_trig" ); + level.goalObject = setup_goal_object( level.robot, "escort_robot_goal_trig" ); + + // Deprecated, just deletes the trigger + setup_reboot_object( level.robot, "escort_robot_reboot_trig" ); + + // Start the robot shutdown + if ( level.bootTime ) + { + level.robot clientfield::set( "robot_state", 2 ); + level.moveObject gameobjects::set_flags( 2 ); + + Blackboard::SetBlackBoardAttribute( level.robot, "_stance", "crouch" ); + level.robot ai::set_behavior_attribute( "rogue_control_speed", level.robotSpeed ); // Reset blackboard value after AnimScripted + level.robot shutdown_robot(); + } + else + { + Objective_SetProgress( level.moveObject.objectiveID, 1 ); + level.moveObject gameobjects::allow_use( "friendly" ); + } + + level.robot thread wait_robot_shutdown(); + level.robot thread wait_robot_reboot(); + + while ( level.inPrematchPeriod ) + { + {wait(.05);}; + } + + level.robot.onground = 1; + + + // Start the robot boot or move once prematch is over + if ( level.bootTime ) + { + level.robot thread auto_reboot_robot( level.bootTime ); + } + else if ( level.movePlayers == 0 ) + { + level.robot move_robot(); + } +} + +function drop_heli_leave() +{ + chopper = self; + + wait ( 1 ); + + supplydropSpeed = GetDvarInt( "scr_supplydropSpeedLeaving", 250 ); + supplydropAccel = GetDvarInt( "scr_supplydropAccelLeaving", 60 ); + + chopper setspeed( supplydropSpeed, supplydropAccel ); + goalPath = supplydrop::supplyDropHeliEndPath_v2( chopper.origin ); + chopper airsupport::followPath( goalPath.path, undefined, false ); + + chopper Delete(); +} + +// Safeguard Robot +//======================================== + +/# + +function debug_draw_robot_path() +{ + // early out if we do not need to debug robot path + if ( (isdefined(GetDvarInt( "scr_escort_debug_robot_path" ))?GetDvarInt( "scr_escort_debug_robot_path" ):0) == 0 ) + return; + + // now debug robot path + debug_duration = 999999999; + pathNodes = level.robot.pathArray; + for( i = 0; i < pathNodes.size - 1; i++ ) + { + currNode = pathNodes[ i ]; + nextNode = pathNodes[ i + 1 ]; + + util::debug_line( currNode, nextNode, ( 0, 0.9, 0 ), 0.9, 0, debug_duration ); + } + + foreach( path in pathNodes ) + { + util::debug_sphere( path, 6, ( 0, 0, 0.9 ), 0.9, debug_duration ); + } +} + +function debug_draw_approximate_robot_path_to_goal( &goalPathArray ) +{ + // early out if we do not need to debug robot path + if ( (isdefined(GetDvarInt( "scr_escort_debug_robot_path" ))?GetDvarInt( "scr_escort_debug_robot_path" ):0) == 0 ) + return; + + // now debug robot path + debug_duration = 60; + pathNodes = goalPathArray; + for( i = 0; i < pathNodes.size - 1; i++ ) + { + currNode = pathNodes[ i ]; + nextNode = pathNodes[ i + 1 ]; + + util::debug_line( currNode, nextNode, ( 0.9, 0.9, 0 ), 0.9, 0, debug_duration ); + } + + foreach( path in pathNodes ) + { + util::debug_sphere( path, 3, ( 0, 0.5, 0.5 ), 0.9, debug_duration ); + } +} + +function debug_draw_current_robot_goal( goal ) +{ + // early out if we do not need to debug robot path + if ( (isdefined(GetDvarInt( "scr_escort_debug_robot_path" ))?GetDvarInt( "scr_escort_debug_robot_path" ):0) == 0 ) + return; + + if ( isdefined( goal ) ) + { + debug_duration = 60; + util::debug_sphere( goal, 8, ( 0, 0.9, 0 ), 0.9, debug_duration ); + } +} + +function debug_draw_find_immediate_goal( pathGoal ) +{ + // early out if we do not need to debug robot path + if ( (isdefined(GetDvarInt( "scr_escort_debug_robot_path" ))?GetDvarInt( "scr_escort_debug_robot_path" ):0) == 0 ) + return; + + if ( isdefined( pathGoal ) ) + { + debug_duration = 60; + util::debug_sphere( pathGoal + ( 0, 0, 18 ), 6, ( 0.9, 0, 0 ), 0.9, debug_duration ); + } +} + +function debug_draw_find_immediate_goal_override( immediateGoal ) +{ + // early out if we do not need to debug robot path + if ( (isdefined(GetDvarInt( "scr_escort_debug_robot_path" ))?GetDvarInt( "scr_escort_debug_robot_path" ):0) == 0 ) + return; + + if ( isdefined( immediateGoal ) ) + { + debug_duration = 60; + util::debug_sphere( immediateGoal + ( 0, 0, 18 ), 6, ( 0.9, 0, 0.9 ), 0.9, debug_duration ); + } +} + +function debug_draw_blocked_path_kill_radius( center, radius ) +{ + if ( (isdefined(GetDvarInt( "scr_escort_debug_robot_path" ))?GetDvarInt( "scr_escort_debug_robot_path" ):0) == 0 ) + return; + + if ( isdefined( center ) ) + { + debug_duration = 200; + circle( center + ( 0, 0, 2 ), radius, ( 0.9, 0, 0.0 ), true, true, debug_duration ); + circle( center + ( 0, 0, 4 ), radius, ( 0.9, 0, 0.0 ), true, true, debug_duration ); + } +} + +#/ + +function wait_robot_moving() +{ + level endon( "game_ended" ); + + while(1) + { + self waittill( "robot_moving" ); + + self RecordGameEventNonPlayer( "robot_start" ); + + self clientfield::set( "robot_state", 1 ); + level.moveObject gameobjects::set_flags( 1 ); + } +} + +function wait_robot_stopped() +{ + level endon( "game_ended" ); + + while(1) + { + self waittill( "robot_stopped" ); + + if ( self.active ) + { + self RecordGameEventNonPlayer( "robot_stop" ); + self clientfield::set( "robot_state", 0 ); + level.moveObject gameobjects::set_flags( 0 ); + } + } +} + +function wait_robot_shutdown() +{ + level endon( "game_ended" ); + + while(1) + { + self waittill( "robot_shutdown" ); + + level.moveObject gameobjects::allow_use( "none" ); + + Objective_SetProgress( level.moveObject.objectiveID, -0.05 ); + + self clientfield::set( "robot_state", 2 ); + level.moveObject gameobjects::set_flags( 2 ); + + otherTeam = util::getOtherTeam( self.team ); + + globallogic_audio::leader_dialog( "sfgRobotDisabledAttacker", self.team, undefined, "robot" ); + globallogic_audio::leader_dialog( "sfgRobotDisabledDefender", otherTeam, undefined, "robot" ); + + globallogic_audio::play_2d_on_team( "mpl_safeguard_disabled_sting_friend", self.team ); + globallogic_audio::play_2d_on_team( "mpl_safeguard_disabled_sting_enemy", otherTeam ); + + self thread auto_reboot_robot( level.rebootTime ); + } +} + +function wait_robot_reboot() +{ + level endon( "game_ended" ); + + while(1) + { + self waittill( "robot_reboot" ); + + self RecordGameEventNonPlayer( "robot_repair_complete" ); + + level.moveObject gameobjects::allow_use( "friendly" ); + + otherTeam = util::getOtherTeam( self.team ); + + globallogic_audio::leader_dialog( "sfgRobotRebootedAttacker", self.team, undefined, "robot" ); + globallogic_audio::leader_dialog( "sfgRobotRebootedDefender", otherTeam, undefined, "robot" ); + + globallogic_audio::play_2d_on_team( "mpl_safeguard_reboot_sting_friend", self.team ); + globallogic_audio::play_2d_on_team( "mpl_safeguard_reboot_sting_enemy", otherTeam ); + + Objective_SetProgress( level.moveObject.objectiveID, 1 ); + + if ( level.movePlayers == 0 ) + { + self move_robot(); + } + else if ( level.moveObject.numTouching[level.moveObject.ownerTeam] == 0 ) + { + self clientfield::set( "robot_state", 0 ); + level.moveObject gameobjects::set_flags( 0 ); + } + } +} + +function auto_reboot_robot( time ) +{ + self endon( "robot_reboot" ); + self endon( "game_ended" ); + + shutdownTime = 0; + + while( shutdownTime < time ) + { + rate = 0; + friendlyCount = level.moveObject.numTouching[level.moveObject.ownerTeam]; + + if ( !level.rebootPlayers ) + { + rate = .05; + } + else if ( friendlyCount > 0 ) + { + rate = .05; // Base rate for interactive reboot + + if ( friendlyCount > 1 ) // Add in multiple player bonus if any + { + bonusRate = ( friendlyCount - 1 ) * .05 * 0; + + rate += bonusRate; + } + } + + if ( rate > 0 ) + { + shutdownTime += rate; + percent = Min( 1, shutdownTime / time ); + Objective_SetProgress( level.moveObject.objectiveID, percent ); + } + + {wait(.05);}; + } + + if ( level.rebootPlayers > 0 ) + { + foreach( struct in level.moveObject.touchList[game["attackers"]] ) + { + scoreevents::processScoreEvent( "escort_robot_reboot", struct.player ); + } + } + + self thread reboot_robot(); +} + +function watch_robot_damaged() +{ + level endon( "game_ended" ); + + while (1) + { + self waittill( "robot_damaged" ); + + percent = Min( 1, ( self.shutdownDamage / level.shutdownDamage ) ); + Objective_SetProgress( level.moveObject.objectiveID, 1 - percent ); + + health = level.shutdownDamage - self.shutdownDamage; + + lowHealth = killstreak_bundles::get_low_health( level.escortRobotKillstreakBundle ); + + if ( !( isdefined( self.playedDamage ) && self.playedDamage ) && health <= lowHealth ) + { + globallogic_audio::leader_dialog( "sfgRobotUnderFire", self.team, undefined, "robot" ); + self.playedDamage = true; + } + else if ( health > lowHealth ) + { + self.playedDamage = false; + } + } +} + +function delete_on_endgame_sequence() +{ + self endon( "death" ); + level waittill( "endgame_sequence" ); + + self Delete(); +} + +function get_robot_path_array() +{ + if ( isdefined( level.escortRobotPath ) ) + { +/# + PrintLn( "Using script level.escortRobotPath" ); +#/ + return level.escortRobotPath; + } + +/# + PrintLn( "Using bsp pathnodes" ); +#/ + pathArray = []; + + currNode = GetNode( "escort_robot_path_start", "targetname" ); + pathArray[pathArray.size] = currNode.origin; + + while( isdefined( currNode.target ) ) + { + currNode = GetNode( currNode.target, "targetname" ); + + pathArray[pathArray.size] = currNode.origin; + } + + if ( isdefined( level.update_escort_robot_path ) ) + { + [[ level.update_escort_robot_path ]] ( pathArray ); + } + + return pathArray; +} + +/# +function calc_robot_path_length( robotOrigin, pathArray ) +{ + distance = 0; + + lastPoint = robotOrigin; + + for( i = 0; i < pathArray.size; i++ ) + { + distance += Distance( lastPoint, pathArray[i] ); + lastPoint = pathArray[i]; + } + + PrintLn( "Escort Path Length: " + distance ); +} +#/ + + +// Robot +//======================================== + +function spawn_robot( position, angles ) +{ + robot = SpawnActor( "spawner_bo3_robot_grunt_assault_mp_escort", + position, + angles, + "", + true ); + + robot ai::set_behavior_attribute( "rogue_allow_pregib", false ); + robot ai::set_behavior_attribute( "rogue_allow_predestruct", false ); + + robot ai::set_behavior_attribute( "rogue_control", "forced_level_2" ); // Lights + robot ai::set_behavior_attribute( "rogue_control_speed", level.robotSpeed ); + + robot ai::set_ignoreall( true ); + robot.allowdeath = false; + //robot.magic_bullet_shield = true; + + robot ai::set_behavior_attribute( "can_become_crawler", false ); + robot ai::set_behavior_attribute( "can_be_meleed", false ); + robot ai::set_behavior_attribute( "can_initiateaivsaimelee", false ); + robot ai::set_behavior_attribute( "traversals", "procedural" ); + + AiUtility::ClearAIOverrideDamageCallbacks( robot ); + + robot.active = true; + robot.canWalk = true; + robot.moving = false; + robot.shutdownDamage = 0; + robot.properName = ""; // Chippy - Never Forget + robot.ignoreTriggerDamage = true; + robot.allowPain = false; + + robot clientfield::set( "robot_mind_control", 0 ); + robot ai::set_behavior_attribute( "robot_lights", 3 ); + + robot.pushable = false; + robot PushActors( true ); + robot PushPlayer( true ); + robot SetAvoidanceMask( "avoid none" ); + robot DisableAimAssist(); + + robot SetSteeringMode( "slow steering" ); + Blackboard::SetBlackBoardAttribute( robot, "_robot_locomotion_type", "alt1" ); + + if ( level.robotShield ) + { + aiutility::attachRiotshield( robot, GetWeapon( "riotshield" ), "wpn_t7_shield_riot_world_lh", "tag_stowed_back" ); + } + + robot ASMSetAnimationRate( 1.1 ); + + if ( ( isdefined( level.shutdownDamage ) && level.shutdownDamage ) ) + { + Target_Set( robot, ( 0, 0, 50 ) ); + } + + robot.overrideActorDamage = &robot_damage; + + robot thread robot_move_chatter(); + + robot.missileTargetMissDistance = 64; + robot thread heatseekingmissile::MissileTarget_ProximityDetonateIncomingMissile(); + + return robot; +} + +function robot_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex, modelIndex ) +{ + if( !( isdefined( self.onground ) && self.onground ) ) + return 0; + + if ( level.shutdownDamage <= 0 || + !self.active || + eAttacker.team == game["attackers"] ) + { + return 0; + } + + level.useStartSpawns = false; + + weapon_damage = killstreak_bundles::get_weapon_damage( level.escortRobotKillstreakBundle, level.shutdownDamage, eAttacker, weapon, sMeansOfDeath, iDamage, iDFlags, undefined ); + + if(!isdefined(weapon_damage))weapon_damage=iDamage; + + if ( !weapon_damage ) + { + return 0; + } + + self.shutdownDamage += weapon_damage; + + self notify( "robot_damaged" ); + + if(!isdefined(eAttacker.damageRobot))eAttacker.damageRobot=0; + + eAttacker.damageRobot += weapon_damage; + + if ( self.shutdownDamage >= level.shutdownDamage ) + { + origin = (0,0,0); + + if ( IsPlayer( eAttacker ) ) + { + level thread popups::DisplayTeamMessageToAll( &"MP_ESCORT_ROBOT_DISABLED", eAttacker ); + + level.robot RecordGameEventNonPlayer( "robot_disabled" ); + + if ( Distance2DSquared( self.origin, level.goalObject.trigger.origin ) < ( (level.goalObject.trigger.radius + 50) * (level.goalObject.trigger.radius + 50) ) ) + { + scoreevents::processScoreEvent( "escort_robot_disable_near_goal", eAttacker ); + } + else + { + scoreevents::processScoreEvent( "escort_robot_disable", eAttacker ); + } + + if( isdefined( eAttacker.pers["disables"]) ) + { + eAttacker.pers["disables"]++; + eAttacker.disables = eAttacker.pers["disables"]; + } + + eAttacker AddPlayerStatWithGameType( "DISABLES", 1 ); + eAttacker RecordGameEvent("return"); + origin = eAttacker.origin; + } + + foreach( player in level.players ) + { + if ( player == eAttacker || + player.team == self.team || + !isdefined( player.damageRobot ) ) + { + continue; + } + + damagePercent = player.damageRobot / level.shutdownDamage; + + if ( damagePercent >= 0.5 ) + { + scoreevents::processScoreEvent( "escort_robot_disable_assist_50", player ); + } + else if ( damagePercent >= 0.25 ) + { + scoreevents::processScoreEvent( "escort_robot_disable_assist_25", player ); + } + + player.damageRobot = undefined; + } + + bbPrint( "mpobjective", "gametime %d objtype %s team %s playerx %d playery %d playerz %d", gettime(), "escort_shutdown", game["defenders"], origin ); + + self shutdown_robot(); + + // Add to the player killed scorestreak BDA tally + // HACK: Copied from mp/globallogic_player:updateAttacker + if( isdefined( eAttacker ) && eAttacker != self && isdefined( weapon ) ) + { + if ( weapon.name == "planemortar" ) + { + if(!isdefined(eAttacker.planeMortarBda))eAttacker.planeMortarBda=0; + eAttacker.planeMortarBda++; + } + else if( weapon.name == "dart" || + weapon.name == "dart_turret" ) + { + if(!isdefined(eAttacker.dartBda))eAttacker.dartBda=0; + eAttacker.dartBda++; + } + else if( weapon.name == "straferun_rockets" || weapon.name == "straferun_gun") + { + if( isdefined( eAttacker.strafeRunbda ) ) + { + eAttacker.strafeRunbda++; + } + } + else if ( weapon.name == "remote_missile_missile" || weapon.name == "remote_missile_bomblet" ) + { + if(!isdefined(eAttacker.remotemissileBda))eAttacker.remotemissileBda=0; + eAttacker.remotemissileBda++; + } + } + } + + self.health += 1; + return 1; +} + +function robot_damage_none( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex, modelIndex ) +{ + return 0; +} + +function shutdown_robot() +{ + self.active = false; + self ai::set_ignoreme( true ); + self.canWalk = false; + + self stop_robot(); + + self notify( "robot_shutdown" ); + + if ( Target_IsTarget( self ) ) + { + Target_Remove( self ); + } + + if ( isdefined( self.riotshield ) ) + { + self AsmChangeAnimMappingTable( 1 ); + self Detach( self.riotshield.model, self.riotshield.tag ); + aiutility::attachRiotshield( self, GetWeapon( "riotshield" ), "wpn_t7_shield_riot_world_lh", "tag_weapon_left" ); + } + + self ai::set_behavior_attribute( "shutdown", true ); +} + +function reboot_robot() +{ + self endon( "robot_shutdown" ); + level endon( "game_ended" ); + + self.active = true; + self.shutdownDamage = 0; + self ai::set_ignoreme( false ); + + self notify( "robot_reboot" ); + + if ( ( isdefined( level.shutdownDamage ) && level.shutdownDamage ) ) + { + Target_Set( self, ( 0, 0, 50 ) ); + } + + if ( isdefined( self.riotshield ) ) + { + self AsmChangeAnimMappingTable( 0 ); + + self Detach( self.riotshield.model, self.riotshield.tag ); + aiutility::attachRiotshield( self, GetWeapon( "riotshield" ), "wpn_t7_shield_riot_world_lh", "tag_stowed_back" ); + } + + self ai::set_behavior_attribute( "shutdown", false ); + + wait ( GetAnimLength( "ai_robot_rogue_ctrl_crc_shutdown_2_alert" ) ); + + self.canWalk = true; +} + +function move_robot() +{ + if ( self.active == false || + self.moving || + !isdefined( self.pathIndex ) ) + { + return; + } + + if ( self check_blocked_goal_and_kill() ) + return; + + if ( GetTime() < (isdefined(self.blocked_wait_end_time)?self.blocked_wait_end_time:0) ) + return; + + self notify ( "robot_moving" ); + + self.moving = true; + self set_goal_to_point_on_path(); + + self thread robot_wait_next_point(); +} + +function get_current_goal() +{ + return ( isdefined( self.immediateGoalOverride ) ? self.immediateGoalOverride : self.pathArray[ self.pathIndex ] ); +} + +function reached_closest_nav_mesh_goal_but_still_too_far_and_blocked( goalOnNavMesh ) +{ + if ( isdefined( self.immediateGoalOverride ) ) + return false; + + distSqr = DistanceSquared( goalOnNavMesh, self.origin ); + robotReachedClosestGoalOnNavMesh = ( distSqr <= ( (24) * (24) ) ); + if ( robotReachedClosestGoalOnNavMesh ) + { + closestGoalOnNavMeshTooFarFromPathGoal = ( DistanceSquared( goalOnNavMesh, self.pathArray[ self.pathIndex ] ) > ( (1.0) * (1.0) ) ); + if ( closestGoalOnNavMeshTooFarFromPathGoal ) + { + robotIsBlockedFromGettingToPathGoal = self check_if_goal_is_blocked( self.origin, self.pathArray[ self.pathIndex ] ); + if ( robotIsBlockedFromGettingToPathGoal ) + return true; + } + } + + return false; +} + +function check_blocked_goal_and_kill() +{ + // don't check until the reboot anim finishes + if ( !self.canWalk ) + { + return false; + } + + // wait until we kill again + if ( GetTime() < (isdefined(self.blocked_wait_end_time)?self.blocked_wait_end_time:0) ) + { + wait ( ( self.blocked_wait_end_time - GetTime() ) / 1000 ); + } + + goalOnNavMesh = self get_closest_point_on_nav_mesh_for_current_goal(); + previousGoal = ( ( self.pathIndex > 0 && !isdefined( self.immediateGoalOverride ) ) ? self.pathArray[ self.pathIndex - 1 ] : self.origin ); + + if ( self.goalJustBlocked || self reached_closest_nav_mesh_goal_but_still_too_far_and_blocked( goalOnNavMesh ) || self check_if_goal_is_blocked( previousGoal, goalOnNavMesh ) ) + { + // we are blocked now, try to kill something to get unblocked + + self.goalJustBlocked = false; + stillBlocked = true; + + killedSomething = self kill_anything_blocking_goal( goalOnNavMesh ); + + if ( killedSomething ) + { + stillBlocked = self check_if_goal_is_blocked( previousGoal, goalOnNavMesh ); + if ( stillBlocked ) + { + self.blocked_wait_end_time = GetTime() + 200; + self stop_robot(); + } + } + else + { + self find_immediate_goal(); + } + + return stillBlocked; + } + + return false; +} + +function find_immediate_goal() +{ + // cannot get to goal and cannot kill anything, try to find nearby immediate goal along the line-of-sight path + // this does assume that the path from one node to the other has all points on navmesh. (No corners or walls are in the way.) + + pathGoal = self.pathArray[ self.pathIndex ]; + currPos = self.origin; + + /# debug_draw_find_immediate_goal( pathGoal ); #/ + + immediateGoal = get_closest_point_on_nav_mesh( VectorLerp( currPos, pathGoal, 0.5 ) ); // get closest point + while( self check_if_goal_is_blocked( currPos, immediateGoal ) ) + { + immediateGoal = get_closest_point_on_nav_mesh( VectorLerp( currPos, immediateGoal, 0.5 ) ); + } + + self.immediateGoalOverride = immediateGoal; + + /# debug_draw_find_immediate_goal_override( self.immediateGoalOverride ); #/ +} + +function check_if_goal_is_blocked( previousGoal, goal ) +{ + // check path distance; note: a zero-length array does not mean there isn't a path, using robot origin to detect that + approxPathArray = self CalcApproximatePathToPosition( goal ); + distanceToNextGoal = min( Distance( self.origin, goal ), Distance( previousGoal, Goal ) ); + approxPathTooLong = is_path_distance_to_goal_too_long( approxPathArray, distanceToNextGoal * 2.5 ); + + return approxPathTooLong; +} + +function watch_goal_becoming_blocked( goal ) +{ + self notify( "end_watch_goal_becoming_blocked_singleton" ); + self endon( "end_watch_goal_becoming_blocked_singleton" ); + + self endon( "robot_stopped" ); + self endon( "goal" ); + level endon( "game_ended" ); + + distToGoalSqr = 999999999.0; + + while ( 1 ) + { + wait 0.1; + + // don't check distances when taking traversals + if ( isdefined( self.traverseStartNode ) ) + { + self waittill( "traverse_end" ); + continue; + } + + // don't check distances when performing a turn (ASM transition decorator running) + if ( self AsmIsTransDecRunning() ) + { + continue; + } + + // don't check until the reboot anim finishes + if ( !self.canWalk ) + { + continue; + } + + newDistToGoalSqr = DistanceSquared( self.origin, goal ); + if ( newDistToGoalSqr < distToGoalSqr ) + { + distToGoalSqr = newDistToGoalSqr; + } + else + { + self.goalJustBlocked = true; + self notify( "goal_blocked" ); + // self stop_robot(); + } + } +} + +function watch_becoming_blocked_at_goal() +{ + self notify( "end_watch_becoming_blocked_at_goal" ); + self endon( "end_watch_becoming_blocked_at_goal" ); + + self endon( "robot_stop" ); + //self endon( "goal" ); // IMPORTANT: never setup an endon( "goal" for this function. This is why it was created in the first place. + level endon( "game_ended" ); + + // don't check distances when taking traversals + while ( isdefined( self.traverseStartNode ) ) + { + self waittill( "traverse_end" ); + } + + self.watch_becoming_blocked_at_goal_established = true; + + startPos = self.origin; + atSamePosCount = 0; + iterationCount = 0; + + while ( self.moving ) + { + wait 0.1; + + if ( DistanceSquared( startPos, self.origin ) < 1.0 ) + { + atSamePosCount++; + } + + if ( atSamePosCount >= 2 ) + { + self.goalJustBlocked = true; + self notify( "goal_blocked" ); + } + + iterationCount++; + + if ( iterationCount >= 3 ) + break; + } + + self.watch_becoming_blocked_at_goal_established = false; +} + +function stop_robot() +{ + if ( !self.moving ) + { + return; + } + + if ( isdefined( self.traverseStartNode ) ) + { + self thread check_robot_on_travesal_end(); + return; + } + + self.moving = false; + self.mostRecentClosestPathPointGoal = undefined; + self.watch_becoming_blocked_at_goal_established = false; + + // robot sometimes overshoots its position and turns around, so we give it a stop position slightly ahead of itself + // note: there are cases in sector where turning around is still an issue when robot is sprinting + velocity = self GetVelocity(); + deltaPos = ( velocity * 0.05 ); + stopGoal = (isdefined(GetClosestPointOnNavMesh( self.origin + deltaPos, 48, 15 ))?GetClosestPointOnNavMesh( self.origin + deltaPos, 48, 15 ):self.origin); + + self SetGoal( stopGoal, false ); + + self notify ( "robot_stopped" ); +} + +function check_robot_on_travesal_end() +{ + self notify( "check_robot_on_travesal_end_singleton" ); + self endon( "check_robot_on_travesal_end_singleton" ); + self endon( "death" ); + + self waittill( "traverse_end" ); + + numOwners = (isdefined(level.moveObject.numTouching[ level.moveObject.ownerTeam ])?level.moveObject.numTouching[ level.moveObject.ownerTeam ]:0); + if ( numOwners < level.movePlayers ) + { + self stop_robot(); + } + else + { + self move_robot(); + } +} + +function update_stop_position() +{ + self endon( "death" ); + level endon( "game_ended" ); + + while ( true ) + { + self waittill( "traverse_end" ); + + // Make the robot update it's goal position after taking a traversal. + // Prevents oscillating across a traversal edge. + if ( !self.moving ) + { + self SetGoal( self.origin, true ); + } + } +} + +function robot_wait_next_point() +{ + self endon( "robot_stopped" ); + self endon( "death" ); + level endon( "game_ended" ); + + while( 1 ) + { + self util::waittill_any( "goal", "goal_blocked" ); + + if ( !isdefined( self.watch_becoming_blocked_at_goal_established ) || self.watch_becoming_blocked_at_goal_established == false ) + { + self thread watch_becoming_blocked_at_goal(); + } + + if ( DistanceSquared( self.origin, get_current_goal() ) < ( (24) * (24) ) ) + { + self.pathIndex += ( isdefined( self.immediateGoalOverride ) ? 0 : 1 ); // only increment when not pathing to immediate goal override + self.immediateGoalOverride = undefined; + } + + while ( ( self.pathIndex < self.pathArray.size ) && DistanceSquared( self.origin, self.pathArray[ self.pathIndex ] ) < ( (( ( 24 * 2 ) + 1 )) * (( ( 24 * 2 ) + 1 )) ) ) + { + self.pathIndex++; + } + + if ( self.pathIndex >= self.pathArray.size ) + { + self.pathIndex = undefined; + self stop_robot(); + return; + } + + if ( ( self.pathIndex + 1 ) >= self.pathArray.size ) + { + otherTeam = util::getOtherTeam( self.team ); + + globallogic_audio::leader_dialog( "sfgRobotCloseAttacker", self.team, undefined, "robot" ); + globallogic_audio::leader_dialog( "sfgRobotCloseDefender", otherTeam, undefined, "robot" ); + } + + if ( self check_blocked_goal_and_kill() ) + { + self stop_robot(); + } + + set_goal_to_point_on_path(); + } +} + +function get_closest_point_on_nav_mesh_for_current_goal() +{ + immediateGoal = get_current_goal(); + + closestPathPoint = GetClosestPointOnNavMesh( immediateGoal, 48, 15 ); + if ( !isdefined( closestPathPoint ) ) + closestPathPoint = GetClosestPointOnNavMesh( immediateGoal, 96, 15 ); + + return (isdefined(closestPathPoint)?closestPathPoint:immediateGoal); +} + +function get_closest_point_on_nav_mesh( point ) +{ + closestPathPoint = GetClosestPointOnNavMesh( point, 48, 15 ); + if ( !isdefined( closestPathPoint ) ) + closestPathPoint = GetClosestPointOnNavMesh( point, 96, 15 ); + + // during large traversals, the intermediate points can be in mid air like the big window in Metro + if ( !isdefined( closestPathPoint ) ) + { + iterCount = 0; + lowerPoint = point - ( 0, 0, 36 ); + // keep trying lower points until we find one on the mesh + while ( !isdefined( closestPathPoint ) && iterCount < 5 ) + { + closestPathPoint = GetClosestPointOnNavMesh( lowerPoint, 48, 15 ); + lowerPoint = lowerPoint - ( 0, 0, 36 ); + iterCount++; + } + } + + return (isdefined(closestPathPoint)?closestPathPoint:point); +} + +function set_goal_to_point_on_path( recursionCount = 0 ) +{ + self.goalJustBlocked = false; + + closestPathPoint = self get_closest_point_on_nav_mesh_for_current_goal(); + + if ( isdefined( closestPathPoint ) ) + { + if ( !isdefined( self.mostRecentClosestPathPointGoal ) || ( DistanceSquared( closestPathPoint, self.mostRecentClosestPathPointGoal ) > 1.0 ) ) + { + self SetGoal( closestPathPoint, false, 24 ); // use nav mesh goal radius as it is smaller of the two + self thread watch_goal_becoming_blocked( closestPathPoint ); + self.mostRecentClosestPathPointGoal = closestPathPoint; + } + } + else if ( recursionCount < 3 ) + { + self find_immediate_goal(); + self set_goal_to_point_on_path( recursionCount + 1 ); + } + else + { + // this should never happen + self stop_robot(); + } + +/# + debug_draw_current_robot_goal( closestPathPoint ); +#/ +} + +function is_path_distance_to_goal_too_long( &pathArray, tooLongThreshold ) +{ + +/# + debug_draw_approximate_robot_path_to_goal( pathArray ); +#/ + + if ( tooLongThreshold < 20.0 ) + tooLongThreshold = 20.0; + + goalDistance = 0; + + lastIndexToCheck = pathArray.size - 1; + for( i = 0; i < lastIndexToCheck; i++ ) + { + goalDistance += Distance( pathArray[ i ], pathArray[ i + 1 ] ); + if (goalDistance >= tooLongThreshold ) + return true; + } + + return false; +} + +/# +function debug_reset_robot_to_start() +{ + level endon( "game_ended" ); + + while( 1 ) + { + if ( (isdefined(GetDvarInt( "scr_escort_robot_reset_path" ))?GetDvarInt( "scr_escort_robot_reset_path" ):0) > 0 ) + { + if( isdefined( level.robot ) ) + { + pathIndex = (isdefined(GetDvarInt( "scr_escort_robot_reset_path" ))?GetDvarInt( "scr_escort_robot_reset_path" ):0) - 1; + pathPoint = level.robot.pathArray[ pathIndex ]; + robotAngles = ( 0, 0, 0 ); + if ( pathIndex < level.robot.pathArray.size - 1 ) + { + nextPoint = level.robot.pathArray[ pathIndex + 1 ]; + robotAngles = VectorToAngles( nextPoint - pathPoint ); + } + level.robot forceTeleport( pathPoint, robotAngles ); + level.robot.pathIndex = pathIndex; + level.robot.immediateGoalOverride = undefined; + + while ( isdefined( self.traverseStartNode ) ) + { + {wait(.05);}; + } + + level.robot stop_robot(); + level.robot SetGoal( level.robot.origin, false ); // set goal to self again as stop_robot may not have + } + SetDvar( "scr_escort_robot_reset_path", 0 ); + } + + wait 0.5; + } +} +#/ + +function explode_robot( ) +{ + self clientfield::set( "escort_robot_burn", 1 ); + clientfield::set( + "robot_mind_control_explosion", 1 ); + self thread wait_robot_corpse(); + + if ( RandomInt( 100 ) >= 50 ) + GibServerUtils::GibLeftArm( self ); + else + GibServerUtils::GibRightArm( self ); + + GibServerUtils::GibLegs( self ); + GibServerUtils::GibHead( self ); + + velocity = self GetVelocity() * ( 1 / 8 ); + + self StartRagdoll(); + self LaunchRagdoll( + ( velocity[0] + RandomFloatRange( -20, 20 ), + velocity[1] + RandomFloatRange( -20, 20 ), + RandomFloatRange( 60, 80 ) ), + "j_mainroot" ); + + PlayFXOnTag( "weapon/fx_c4_exp_metal", self, "tag_origin" ); + + if ( Target_IsTarget( self ) ) + { + Target_Remove( self ); + } + + PhysicsExplosionSphere( self.origin, + 200, + 1, + 1, + 1, + 1 ); + + RadiusDamage( self.origin, + 200, + 1, + 1, + undefined, + "MOD_EXPLOSIVE" ); + + PlayRumbleOnPosition( "grenade_rumble", self.origin ); +} + +function wait_robot_corpse() +{ + archetype = self.archetype; + self waittill("actor_corpse", corpse); + corpse clientfield::set( "escort_robot_burn", 1 ); +} + +function robot_move_chatter() +{ + level endon( "game_ended" ); + + while(1) + { + if ( self.moving ) + { + self PlaySoundOnTag( "vox_robot_chatter", "J_Head" ); + } + + wait( RandomFloatRange( 1.5, 2.5 ) ); + } +} + + +// Robot Move Trigger +//======================================== + +function setup_move_object( robot, triggerName ) +{ + trigger = GetEnt( triggerName, "targetname" ); + + useObj = gameobjects::create_use_object( game["attackers"], trigger, [], ( 0, 0 ,0 ), &"escort_robot" ); + useObj gameobjects::set_objective_entity( robot ); + useObj gameobjects::allow_use( "none" ); + useObj gameobjects::set_visible_team( "any" ); + useObj gameobjects::set_use_time( 0 ); + + trigger EnableLinkTo(); + trigger LinkTo( robot ); + + useObj.onUse = &on_use_robot_move; + useObj.onUpdateUseRate = &on_update_use_rate_robot_move; + useObj.robot = robot; + + if ( isdefined( level.levelescortDisable ) ) + { + if(!isdefined(useObj.exclusions))useObj.exclusions=[]; + + foreach( trigger in level.levelescortDisable ) + { + useObj.exclusions[useObj.exclusions.size] = trigger; + } + } + + return useObj; +} + +function on_use_robot_move( player ) +{ + level.useStartSpawns = false; + + if ( self.robot.moving || + !self.robot.active || + self.numTouching[self.ownerTeam] < level.movePlayers ) + { + return; + } + + self thread track_escorting_players(); + self.robot move_robot(); +} + +function on_update_use_rate_robot_move( team, progress, change ) +{ + numOwners = self.numTouching[self.ownerTeam]; + + if ( numOwners < level.movePlayers ) + { + self.robot stop_robot(); + } +} + +function track_escorting_players() +{ + level endon( "game_ended" ); + self.robot endon( "robot_stopped" ); + + while( 1 ) + { + foreach( touch in self.touchList[self.team] ) + { + if ( !( isdefined( touch.player.escortingRobot ) && touch.player.escortingRobot ) ) + self thread track_escort_time( touch.player ); + } + {wait(.05);}; + } +} + +function track_escort_time( player ) +{ + level endon( "game_ended" ); + player endon( "death" ); + player endon( "disconnect" ); + self.robot endon( "robot_shutdown" ); + + player.escortingRobot = true; + player RecordGameEvent( "player_escort_start" ); + + self thread wait_escort_death( player ); + self thread wait_escort_shutdown( player ); + + consecutiveEscorts = 0; + + while( 1 ) + { + wait 1; + + touching = false; + + foreach( touch in self.touchList[self.team] ) + { + if ( touch.player == player ) + { + touching = true; + break; + } + } + + if ( !touching ) + break; + + // Escorts stat every 1 second + if( isdefined( player.pers["escorts"] ) ) + { + player.pers["escorts"]++; + player.escorts = player.pers["escorts"]; + } + player AddPlayerStatWithGameType( "ESCORTS", 1 ); + + // Score event every 3 seconds + consecutiveEscorts++; + if( ( consecutiveEscorts % 3 ) == 0 ) + { + scoreevents::processScoreEvent( "escort_robot_escort", player ); + } + } + + player player_stop_escort(); +} + +function player_stop_escort() +{ + if ( !isdefined( self ) ) + return; + + self.escortingRobot = false; + self RecordGameEvent( "player_escort_stop" ); + + self notify( "escorting_stopped" ); +} + +function wait_escort_death( player ) +{ + level endon( "game_ended" ); + player endon( "escorting_stopped" ); + player endon( "disconnect" ); + + player waittill( "death" ); + + player thread player_stop_escort(); +} + +function wait_escort_shutdown( player ) +{ + level endon( "game_ended" ); + player endon( "escorting_stopped" ); + player endon( "disconnect" ); + + self.robot waittill( "robot_shutdown" ); + + player thread player_stop_escort(); +} + +// Robot Reboot Trigger +//======================================== + +function setup_reboot_object( robot, triggerName ) +{ + trigger = GetEnt( triggerName, "targetname" ); + + if ( isdefined( trigger ) ) + { + trigger Delete(); + } +} + + +// Goal +//======================================== + +function setup_goal_object( robot, triggerName ) +{ + trigger = GetEnt( triggername, "targetname" ); + + useObj = gameobjects::create_use_object( game["defenders"], trigger, [], ( 0, 0 ,0 ), &"escort_goal" ); + useObj gameobjects::set_visible_team( "any" ); + useObj gameobjects::allow_use( "none" ); + useObj gameobjects::set_use_time( 0 ); + + fwd = ( 0, 0, 1 ); + right = ( 0, -1, 0 ); + + useObj.fx = SpawnFx( "ui/fx_dom_marker_team_r120", trigger.origin, fwd, right ); + useObj.fx.team = game["defenders"]; + TriggerFx( useObj.fx, 0.001 ); + + useObj thread watch_robot_enter( robot ); + + return useObj; +} + +function watch_robot_enter( robot ) +{ + robot endon( "death" ); + level endon( "game_ended" ); + + radiusSq = self.trigger.radius * self.trigger.radius; + + while(1) + { + if ( robot.moving === true && + Distance2DSquared( self.trigger.origin, robot.origin ) < radiusSq ) + { + level.movePlayers = 0; // Keep moving + robot.overrideActorDamage = &robot_damage_none; // Don't actually take damage + + if ( Target_IsTarget( self ) ) + { + Target_Remove( self ); + } + + attackers = game["attackers"]; + self.fx.team = attackers; + + foreach( player in level.alivePlayers[attackers] ) + { + if ( ( isdefined( player.escortingRobot ) && player.escortingRobot ) ) + { + scoreevents::processScoreEvent( "escort_robot_escort_goal", player ); + } + } + + level.robot RecordGameEventNonPlayer( "robot_reached_objective" ); + + setGameEndTime( 0 ); + + robot ai::set_ignoreme( true ); + + robot thread explode_robot_after_wait( 1.0 ); + + globallogic_score::giveTeamScoreForObjective( attackers, 1 ); + level thread globallogic::endGame( attackers, game["strings"][attackers + "_mission_accomplished"] ); + + return; + } + + {wait(.05);}; + } +} + +function explode_robot_after_wait( wait_time ) +{ + robot = self; + + wait wait_time; + + if ( isdefined( robot ) ) + { + robot explode_robot(); + } +} + +function kill_anything_blocking_goal( goal ) +{ + self endon( "end_kill_anything" ); + self.disableFinalKillcam = true; + + dirToGoal = VectorNormalize( goal - self.origin ); + atLeastOneDestroyed = false; + bestCandidate = undefined; + bestCandidateDot = -999999999.0; + + /# debug_draw_blocked_path_kill_radius( self.origin, 108 ); #/ + + entities = GetDamageableEntArray( self.origin, 108 ); + foreach( entity in entities ) + { + if ( IsPlayer( entity ) ) + continue; + + if ( entity == self ) + continue; + + if ( entity.classname == "grenade" ) + continue; + + if ( !IsAlive( entity ) ) + continue; + + // isTouching does not work with some killstreaks like Guardian vs robot, not sure why + // if ( !entity IsTouching( self ) ) + // continue; + + entityDot = VectorDot( dirToGoal, entity.origin - self.origin ); + if ( entityDot > bestCandidateDot ) + { + bestCandidate = entity; + bestCandidateDot = entityDot; + } + } + + if ( isdefined( bestCandidate ) ) + { + entity = bestCandidate; + + if ( IsDefined( entity.targetname ) ) + { + if ( entity.targetname == "talon" ) + { + entity notify( "death" ); + return true; + } + } + + if ( IsDefined( entity.helitype ) && entity.helitype == "qrdrone" ) + { + watcher = entity.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + watcher thread weaponobjects::waitAndDetonate( entity, 0.0, undefined ); + return true; + } + + if ( entity.classname == "auto_turret" ) + { + if ( !IsDefined( entity.damagedToDeath ) || !entity.damagedToDeath ) + { + entity util::DoMaxDamage( self.origin + ( 0, 0, 1 ), self, self, 0, "MOD_CRUSH" ); + } + return true; + } + + if( IsVehicle( entity ) && ( !isdefined( entity.team ) || entity.team != "neutral" ) ) // vehicles are immune from MOD_CRUSH + { + entity kill(); + return true; + } + + entity DoDamage( entity.health * 2, self.origin + ( 0, 0, 1 ), self, self, 0, "MOD_CRUSH" ); + atLeastOneDestroyed = true; + } + + atLeastOneDestroyed = atLeastOneDestroyed || self destroy_supply_crate_blocking_goal( dirToGoal ); + + return atLeastOneDestroyed; +} + + +function destroy_supply_crate_blocking_goal( dirToGoal ) +{ + crates = GetEntArray( "care_package", "script_noteworthy" ); + bestCrate = undefined; + bestCrateeDot = -999999999.0; + + // find the best crate to destroy + foreach( crate in crates ) + { + if ( DistanceSquared( crate.origin, self.origin ) > ( (108) * (108) ) ) + continue; + + crateDot = VectorDot( dirToGoal, crate.origin - self.origin ); + if ( crateDot > bestCrateeDot ) + { + bestCrate = crate; + bestCrateeDot = crateDot; + } + } + + if( isdefined( bestCrate ) ) + { + PlayFX( level._supply_drop_explosion_fx, bestCrate.origin ); + PlaySoundAtPosition( "wpn_grenade_explode", bestCrate.origin ); + wait ( 0.1 ); + bestCrate supplydrop::crateDelete(); + return true; + } + + return false; +} \ No newline at end of file diff --git a/mp/gametypes/fr.csc b/mp/gametypes/fr.csc new file mode 100644 index 0000000..3a91034 --- /dev/null +++ b/mp/gametypes/fr.csc @@ -0,0 +1,117 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; + + + + + +function main() +{ + callback::on_localclient_connect( &on_player_connect ); + + clientfield::register( "world", "freerun_state", 1, 3, "int", &freerunStateChanged, !true, !true ); + clientfield::register( "world", "freerun_retries", 1, 16, "int", &freerunRetriesUpdated, !true, !true ); + clientfield::register( "world", "freerun_faults", 1, 16, "int", &freerunFaultsUpdated, !true, !true ); + clientfield::register( "world", "freerun_startTime", 1, 31, "int", &freerunStartTimeUpdated, !true, !true ); + clientfield::register( "world", "freerun_finishTime", 1, 31, "int", &freerunFinishTimeUpdated, !true, !true ); + clientfield::register( "world", "freerun_bestTime", 1, 31, "int", &freerunBestTimeUpdated, !true, !true ); + clientfield::register( "world", "freerun_timeAdjustment", 1, 31, "int", &freerunTimeAdjustmentUpdated, !true, !true ); + clientfield::register( "world", "freerun_timeAdjustmentNegative", 1, 1, "int", &freerunTimeAdjustmentSignUpdated, !true, !true ); + clientfield::register( "world", "freerun_bulletPenalty", 1, 16, "int", &freerunbulletPenaltyUpdated, !true, !true ); + clientfield::register( "world", "freerun_pausedTime", 1, 31, "int", &freerunPausedTimeUpdated, !true, !true ); + clientfield::register( "world", "freerun_checkpointIndex", 1, 7, "int", &freerunCheckPointUpdated, !true, !true ); +} + +function on_player_connect( localClientNum ) +{ + AllowActionSlotInput( localClientNum ); + AllowScoreboard( localClientNum, false ); +} + +function freerunStateChanged( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + stateModel = CreateUIModel( controllerModel, "FreeRun.runState" ); + SetUIModelValue( stateModel, newVal ); +} + +function freerunRetriesUpdated( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + retriesModel = CreateUIModel( controllerModel, "FreeRun.freeRunInfo.retries" ); + SetUIModelValue( retriesModel, newVal ); +} + +function freerunFaultsUpdated( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + faultsModel = CreateUIModel( controllerModel, "FreeRun.freeRunInfo.faults" ); + SetUIModelValue( faultsModel, newVal ); +} + +function freerunStartTimeUpdated( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + model = CreateUIModel( controllerModel, "FreeRun.startTime" ); + SetUIModelValue( model, newVal ); +} + +function freerunFinishTimeUpdated( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + model = CreateUIModel( controllerModel, "FreeRun.finishTime" ); + SetUIModelValue( model, newVal ); +} + +function freerunBestTimeUpdated( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + model = CreateUIModel( controllerModel, "FreeRun.freeRunInfo.bestTime" ); + SetUIModelValue( model, newVal ); +} + +function freerunTimeAdjustmentUpdated( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + model = CreateUIModel( controllerModel, "FreeRun.timer.timeAdjustment" ); + SetUIModelValue( model, newVal ); +} + +function freerunTimeAdjustmentSignUpdated( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + model = CreateUIModel( controllerModel, "FreeRun.timer.timeAdjustmentNegative" ); + SetUIModelValue( model, newVal ); +} + +function freerunbulletPenaltyUpdated( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + bulletPenaltyModel = CreateUIModel( controllerModel, "FreeRun.freeRunInfo.bulletPenalty" ); + SetUIModelValue( bulletPenaltyModel, newVal ); +} + +function freerunPausedTimeUpdated( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + model = CreateUIModel( controllerModel, "FreeRun.pausedTime" ); + SetUIModelValue( model, newVal ); +} + +function freerunCheckPointUpdated( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + controllerModel = GetUIModelForController( localClientNum ); + model = CreateUIModel( controllerModel, "FreeRun.freeRunInfo.activeCheckpoint" ); + SetUIModelValue( model, newVal ); +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} \ No newline at end of file diff --git a/mp/gametypes/fr.gsc b/mp/gametypes/fr.gsc new file mode 100644 index 0000000..69e2dfb --- /dev/null +++ b/mp/gametypes/fr.gsc @@ -0,0 +1,1686 @@ +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\lui_shared; + + + + + +#using scripts\shared\callbacks_shared; +#using scripts\shared\system_shared; +#using scripts\mp\_pickup_items; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_util; + +/* + Freerun + Objective: Make it to the goal the fastest + Map ends: When player quits + Respawning: Instant + + Level requirements + ------------------ + Spawnpoints: + classname mp_dm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of enemies at the time of spawn. + Players generally spawn away from enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. +*/ + +/*QUAKED mp_dm_spawn (1.0 0.5 0.0) (-16 -16 0) (16 16 72) +Players spawn away from enemies at one of these positions.*/ + +/* + - down is reset to active checkpoint + - left/right change courses + - up is to reset to start of course/reset timer +*/ + +/* TODO: + + - top 3 scores show on track end + - timer based missile swarm that penalizes being on foot for too long + - random missile swarm that chooses areas the player is running towards + - timer value counting up every frame and incremented 5s on fault (or just add the faults at the end maybe) + - combo/speed boost methods for stringing together special moves? + - drop the ctf spawn point FX on the start and end triggers + - UI NEEDS + - timer + - top 3 + - communication string billboard + - track/difficulty callout + - press X to start gate + + - BUGS + - find a way to unbind score board from script or CFG maybe + - has to happen after binds are loaded from HD +*/ + + + + + + + + + +#precache( "string", "OBJECTIVES_FR" ); +#precache( "string", "OBJECTIVES_FR_SCORE" ); +#precache( "string", "OBJECTIVES_FR_HINT" ); +#precache( "string", "OBJECTIVES_FR_NEW_RECORD" ); +#precache( "string", "OBJECTIVES_FR_CHECKPOINT" ); +#precache( "string", "OBJECTIVES_FR_FAULT" ); +#precache( "string", "OBJECTIVES_FR_FAULTS" ); +#precache( "string", "OBJECTIVES_FR_RETRY" ); +#precache( "string", "OBJECTIVES_FR_RETRIES" ); + +#precache( "string", "FREERUN_TUTORIAL_01" ); +#precache( "string", "FREERUN_TUTORIAL_02" ); +#precache( "string", "FREERUN_TUTORIAL_03" ); +#precache( "string", "FREERUN_TUTORIAL_04" ); +#precache( "string", "FREERUN_TUTORIAL_05" ); +#precache( "string", "FREERUN_TUTORIAL_06" ); +#precache( "string", "FREERUN_TUTORIAL_07" ); +#precache( "string", "FREERUN_TUTORIAL_08" ); +#precache( "string", "FREERUN_TUTORIAL_09" ); +#precache( "string", "FREERUN_TUTORIAL_10" ); +#precache( "string", "FREERUN_TUTORIAL_11" ); +#precache( "string", "FREERUN_TUTORIAL_12" ); +#precache( "string", "FREERUN_TUTORIAL_13" ); +#precache( "string", "FREERUN_TUTORIAL_14" ); +#precache( "string", "FREERUN_TUTORIAL_14a" ); +#precache( "string", "FREERUN_TUTORIAL_15" ); +#precache( "string", "FREERUN_TUTORIAL_16" ); +#precache( "string", "FREERUN_TUTORIAL_17" ); +#precache( "string", "FREERUN_TUTORIAL_18" ); +#precache( "string", "FREERUN_TUTORIAL_19" ); +#precache( "string", "FREERUN_TUTORIAL_20" ); +#precache( "string", "FREERUN_TUTORIAL_21" ); +#precache( "string", "FREERUN_TUTORIAL_22" ); +#precache( "string", "FREERUN_TUTORIAL_22a" ); +#precache( "string", "FREERUN_TUTORIAL_23" ); +#precache( "string", "FREERUN_TUTORIAL_24" ); +#precache( "string", "FREERUN_TUTORIAL_25" ); +#precache( "string", "FREERUN_TUTORIAL_26" ); +#precache( "string", "FREERUN_WELCOME" ); +#precache( "string", "FREERUN_CHECKPOINT" ); +#precache( "string", "FREERUN_BEST_TIME" ); +#precache( "string", "FREERUN_COMPLETE" ); +#precache( "string", "FREERUN_BEST_RUN" ); + +#precache( "fx", "ui/fx_fr_target_demat" ); +#precache( "fx", "ui/fx_fr_target_impact" ); + +#precache( "menu", "freerun_endgame_popup" ); + +function main() +{ + level.trackWeaponStats = false; + globallogic::init(); + + clientfield::register( "world", "freerun_state", 1, 3, "int" ); + clientfield::register( "world", "freerun_retries", 1, 16, "int" ); + clientfield::register( "world", "freerun_faults", 1, 16, "int" ); + clientfield::register( "world", "freerun_startTime", 1, 31, "int" ); + clientfield::register( "world", "freerun_finishTime", 1, 31, "int" ); + clientfield::register( "world", "freerun_bestTime", 1, 31, "int" ); + clientfield::register( "world", "freerun_timeAdjustment", 1, 31, "int" ); + clientfield::register( "world", "freerun_timeAdjustmentNegative", 1, 1, "int" ); + clientfield::register( "world", "freerun_bulletPenalty", 1, 16, "int" ); + clientfield::register( "world", "freerun_pausedTime", 1, 31, "int" ); + clientfield::register( "world", "freerun_checkpointIndex", 1, 7, "int" ); + + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 50000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 0, 0, 1440 ); + + level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false ); + level.teamScorePerKill = GetGametypeSetting( "teamScorePerKill" ); + level.teamScorePerDeath = GetGametypeSetting( "teamScorePerDeath" ); + level.teamScorePerHeadshot = GetGametypeSetting( "teamScorePerHeadshot" ); + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.giveCustomLoadout = &giveCustomLoadout; + + //level.skipGameEnd = 1; + level.postRoundTime = 0.5; + level.doEndgameScoreboard = false; + + callback::on_connect( &on_player_connect ); + + gameobjects::register_allowed_gameobject( "dm" ); + gameobjects::register_allowed_gameobject( level.gameType ); + + if ( !IsDefined( level.fr_target_impact_fx ) ) + { + level.fr_target_impact_fx = "ui/fx_fr_target_impact"; + } + if ( !IsDefined( level.fr_target_disable_fx ) ) + { + level.fr_target_disable_fx = "ui/fx_fr_target_demat"; + } + if ( !IsDefined( level.fr_target_disable_sound ) ) + { + level.fr_target_disable_sound = "wpn_grenade_explode_default"; + } + +// globallogic_audio::set_leader_gametype_dialog ( "startFreeRun", "hcStartFreeRun", "gameBoost", "gameBoost" ); + + level.FRGame = SpawnStruct(); + + level.FRGame.activeTrackIndex = 0; + level.FRGame.tracks = []; + + for ( i = 0; i < 1; i++ ) + { + level.FRGame.tracks[i] = SpawnStruct(); + + level.FRGame.tracks[i].startTrigger = GetEnt( "fr_start_0" + i, "targetname" ); + assert( IsDefined( level.FRGame.tracks[i].startTrigger )); + + level.FRGame.tracks[i].goalTrigger = GetEnt( "fr_end_0" + i, "targetname" ); + assert( IsDefined( level.FRGame.tracks[i].goalTrigger )); + + level.FRGame.tracks[i].highScores = []; + } + + level.FRGame.checkpointTriggers = GetEntArray( "fr_checkpoint", "targetname" ); + assert( level.FRGame.checkpointTriggers.size ); + + // Sets the scoreboard columns and determines with data is sent across the network + globallogic::setvisiblescoreboardcolumns( "pointstowin", "kills", "deaths", "headshots", "score" ); + +} + +function setupTeam( team ) +{ + util::setObjectiveText( team, &"OBJECTIVES_FR" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( team, &"OBJECTIVES_FR" ); + } + else + { + util::setObjectiveScoreText( team, &"OBJECTIVES_FR_SCORE" ); + } + util::setObjectiveHintText( team, &"OBJECTIVES_FR_SCORE" ); + + spawnlogic::add_spawn_points( team, "mp_dm_spawn" ); +} + +function onStartGameType() +{ + setClientNameMode("auto_change"); + + level.useXCamsForEndGame = false; + level.can_set_aar_stat = false; + level.disableBehaviorTracker = true; + level.disableStatTracking = true; + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + foreach( team in level.teams ) + { + setupTeam( team ); + } + + spawns = spawnlogic::get_spawnpoint_array( "mp_dm_spawn" ); + spawning::updateAllSpawnPoints(); + + foreach( index, trigger in level.FRGame.checkpointTriggers ) + { + level.FRGame.checkpointTimes[index] = 0; + trigger.checkPointIndex = index; + + trigger thread watchCheckpointTrigger(); + + closest = 99999999; + foreach( spawn in spawns ) + { + dist = DistanceSquared( spawn.origin, trigger.origin ); + if ( dist < closest ) + { + closest = dist; + trigger.spawnPoint = spawn; + } + } + + assert( IsDefined( trigger.spawnPoint )); + } + + player_starts = spawnlogic::_get_spawnpoint_array( "info_player_start" ); + assert( player_starts.size ); + + foreach( track in level.FRGame.tracks ) + { + closest = 99999999; + foreach( start in player_starts ) + { + dist = DistanceSquared( start.origin, track.startTrigger.origin ); + if ( dist < closest ) + { + closest = dist; + track.playerStart = start; + } + } + + assert( IsDefined( track.playerStart )); + } + + level.FRGame.deathTriggers = GetEntArray( "fr_die", "targetname" ); + assert( level.FRGame.deathTriggers.size ); + + foreach( trigger in level.FRGame.deathTriggers ) + { + trigger thread watchDeathTrigger(); + } + + setup_tutorial(); + + if(!IsDefined (level.freerun)) // this keeps suspense music from the global mp system from playing + { + level.freerun = true; + } + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + // use the new spawn logic from the start + level.useStartSpawns = false; + level.displayRoundEndText = false; + + if ( !util::isOneRound() ) + { + level.displayRoundEndText = true; + } + + foreach( item in level.pickup_items ) + { + closest = 99999999; + foreach( trigger in level.FRGame.checkpointTriggers ) + { + dist = DistanceSquared( item.origin, trigger.origin ); + if ( dist < closest ) + { + closest = dist; + item.checkPoint = trigger; + } + } + assert( IsDefined( item.checkPoint )); + + item.checkPoint.weapon = item.visuals[0].items[0].weapon; + item.checkPoint.weaponObject = item; + + item.checkPoint setup_weapon_targets(); + } + + thread watch_for_game_end(); + + level.FRGame.trackIndex = GetFreerunTrackIndex( ); + level.FRGame.mapUniqueId = GetMissionUniqueID( ); + level.FRGame.mapVersion = GetMissionVersion( ); +} + +function watch_for_game_end() +{ + level waittill("game_ended"); + + if ( !end_game_state() ) + { + level clientfield::set( "freerun_finishTime", 0 ); + } + self stop_tutorial_vo(); + level clientfield::set( "freerun_state", 4 ); +} + +function on_player_connect() +{ + self thread on_menu_response(); +} + +function on_menu_response() +{ + self endon("disconnect"); + + for(;;) + { + self waittill("menuresponse", menu, response); + if ( response == "fr_restart" ) + { + self playsoundtoplayer( "uin_freerun_reset", self ); + self thread freerunMusic(); + activateTrack( level.FRGame.activeTrackIndex ); + } + } +} + +function onSpawnPlayer(predictedSpawn) // self == player +{ + spawning::onSpawnPlayer(predictedSpawn); + + if ( predictedSpawn ) + return; + + // if we are here a second time its because the player died. + if ( IsDefined( self.FRInited ) ) + { + self.body hide(); + faultDeath(); + return; + } + + self.FRInited = true; + + self thread activate_tutorial_mode(); + self thread activateTrack( level.FRGame.activeTrackIndex ); + self thread watchTrackSwitch(); + self thread watchWeaponFire(); + self thread freerunMusic(); + + self thread trackPlayerOrigin(); + +// self.overridePlayerDamage = &on_player_damage; + + level.FRGame.lastPlayedFaultVOTime = 0; + + self DisableWeaponCycling(); +} + +function on_player_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, modelIndex, psOffsetTime ) +{ + if ( iDamage >= self.health ) + { + // damage function always applies at least 1 damage + self.health = self.maxHealth + 1; + faultDeath(); + return 0; + } + + return iDamage; +} + +function trackPlayerOrigin() +{ + self endon( "disconnect" ); + + while( 1 ) + { + self.prev_origin = self.origin; + self.prev_time = GetTime(); + {wait(.05);}; + waittillframeend; + } +} + +function readHighScores() // self == player +{ + get_top_scores_stats(); + + updateHighScores(); +} + +function updateHighScores() +{ + self FreerunSetHighScores( level.FRGame.activeTrack.highScores[0].time, level.FRGame.activeTrack.highScores[1].time, level.FRGame.activeTrack.highScores[2].time ); + level clientfield::set( "freerun_bestTime", level.FRGame.activeTrack.highScores[0].time ); +} + +function activateTrack( trackIndex ) // self == player +{ + level notify( "activate_track" ); + +/# + if ( level.FRGame.tracks.size > 1 ) + { + IPrintLn( "Track " + trackIndex ); + } +#/ + + if ( !isdefined( level.FRGame.tutorials ) || !level.FRGame.tutorials ) + { + // we are not doing tutorial mode so then play the main freerun vo + self playlocalsound( "vox_tuto_tutorial_sequence_27" ); + } + + level.FRGame.lastPlayedFaultVOCheckpoint = -1; + + level.FRGame.activeTrackIndex = trackIndex; + level.FRGame.activeTrack = level.FRGame.tracks[trackIndex]; + level.FRGame.activeSpawnPoint = level.FRGame.activeTrack.playerStart; + level.FRGame.activeSpawnLocation = level.FRGame.activeTrack.playerStart.origin; + level.FRGame.activeSpawnAngles = level.FRGame.activeTrack.playerStart.angles; + level.FRGame.activeTrack.goalTrigger thread watchGoalTrigger(); + level.FRGame.activeSpawnPoint.checkpointIndex = 0; + + level.FRGame.faults = 0; + level.FRGame.userSpawns = 0; + level.FRGame.checkpointTimes = []; + foreach( index, trigger in level.FRGame.checkpointTriggers ) + { + level.FRGame.checkpointTimes[index] = 0; + } + + level clientfield::set( "freerun_faults", 0 ); + level clientfield::set( "freerun_retries", 0 ); + level clientfield::set( "freerun_state", 0 ); + level clientfield::set( "freerun_bulletPenalty", 0 ); + level clientfield::set( "freerun_pausedTime", 0 ); + level clientfield::set( "freerun_checkpointIndex", 0 ); + + self readHighScores(); + + self giveCustomLoadout(); + self SetOrigin( level.FRGame.activeTrack.playerStart.origin ); + self SetPlayerAngles( level.FRGame.activeTrack.playerStart.angles ); + self SetVelocity( (0,0,0) ); + self RecordGameEvent( "start" ); + + ResetGlass(); + reset_all_targets(); + pickup_items::respawn_all_pickups(); + + self unfreeze(); + self.respawn_position = undefined; + + enable_all_tutorial_triggers(); + take_players_out_of_tutorial_mode(); + + level.FRGame.activeTrack.startTrigger thread watchStartRun( self ); +} + +function startRun() // self == player +{ + level.FRGame.totalPausedTime = 0; + level.FRGame.pausedAtTime = 0; + level.FRGame.bulletPenalty = 0; + level.FRGame.hasBeenPaused = false; + level.FRGame.trackStartTime = 0; + level.FRGame.trackStartTime = get_current_track_time( self ); + + level clientfield::set( "freerun_startTime", level.FRGame.trackStartTime ); + level clientfield::set( "freerun_state", 1 ); + + self playsoundtoplayer( "uin_freerun_start", self ); + + self thread watchUserRespawn(); +} + +function onCheckpointTrigger( player, endOnString ) // self == trigger +{ + self endon( endOnString ); + + level.FRGame.activeSpawnLocation = getGroundPointForOrigin( player.origin ); + level.FRGame.activeSpawnAngles = player.angles; + + if ( level.FRGame.activeSpawnPoint != self ) + { + level.FRGame.activeSpawnPoint = self; + + player take_all_player_weapons( false, false ); + + if ( IsDefined(self.weaponObject) ) + { + self.weaponObject reset_targets(); + self.weaponObject pickup_items::respawn_pickup(); + } + } +} + +function leaveCheckpointTrigger( player ) // self == trigger +{ + self thread watchCheckpointTrigger(); +} + +function get_current_track_time( player ) // self == checkpoint trigger +{ + curtime = GetTime(); + dt = curtime - player.prev_time; + + frac = getfirsttouchfraction( player, self, player.prev_origin, player.origin ); + + current_time = (curtime - level.FRGame.trackStartTime + (level.FRGame.bulletPenalty * 1000) + (level.FRGame.userSpawns * 5000) - level.FRGame.totalPausedTime); + + return int(current_time - dt * ( 1 - frac )); +} + +function watchCheckpointTrigger() // self == checkpoint trigger +{ + self waittill( "trigger", player ); + + if ( IsPlayer( player )) + { + if ( level.FRGame.activeSpawnPoint != self ) + { + checkpoint_index = self.checkpointIndex; + + current_time = get_current_track_time( player ); + + first_time = false; + + // make sure we dont double set this if they go backwards + if ( !IsDefined( level.FRGame.checkpointTimes[ checkpoint_index ] ) || level.FRGame.checkpointTimes[ checkpoint_index ] == 0 ) + { + level.FRGame.checkpointTimes[ checkpoint_index ] = current_time; + first_time = true; + } + + if ( first_time ) + { + if ( IsDefined( level.FRGame.activeTrack.fastestRunCheckpointTimes ) ) + { + if ( IsDefined( level.FRGame.activeTrack.fastestRunCheckpointTimes[checkpoint_index] ) && level.FRGame.activeTrack.fastestRunCheckpointTimes[checkpoint_index] ) + { + delta_time = current_time - level.FRGame.activeTrack.fastestRunCheckpointTimes[checkpoint_index]; + + if ( delta_time < 0 ) + { + delta_time = -delta_time; + sign = 1; + } + else + { + sign = 0; + } + + level clientfield::set( "freerun_timeAdjustment", delta_time ); + level clientfield::set( "freerun_timeAdjustmentNegative", sign ); + } + + + } + + //Set the checkpoint index to one above the actual checkpoint index in order to update on the first checkpoint ( 0 ), as + //Clientfields can't handle negative numbers. + level clientfield::set( "freerun_checkpointIndex", checkpoint_index + 1 ); + player playsoundtoplayer( "uin_freerun_checkpoint", player ); + } + } + self thread util::trigger_thread( player, &onCheckpointTrigger, &leaveCheckpointTrigger ); + } +} + +function watchDeathTrigger() // self == death trigger +{ + while( true ) + { + self waittill( "trigger", player ); + + if ( IsPlayer( player )) + { + player faultDeath(); + } + } +} + +function add_current_run_to_high_scores(player) +{ + active_track = level.FRGame.activeTrack; + + run_data = create_high_score_struct( get_current_track_time( player ), level.FRGame.faults, level.FRGame.userSpawns, level.FRGame.bulletPenalty ); + + push_score = true; + new_record = false; + + if ( active_track.highScores.size > 0 ) + { + for ( i = 0; i < active_track.highScores.size; i++ ) + { + if ( (run_data.time < active_track.highScores[i].time) || (active_track.highScores[i].time == 0) ) + { + push_score = false; + + ArrayInsert( active_track.highScores, run_data, i ); + + if ( i == 0 ) + { + new_record = true; + } + + if ( i < 3 ) + { + player write_high_scores_stats(i); + } + break; + } + } + } + else + { + new_record = true; + } + + if ( push_score ) + { + ArrayInsert( active_track.highScores, run_data, active_track.highScores.size ); + player write_high_scores_stats( active_track.highScores.size - 1 ); + } + + if ( new_record ) + { + player write_checkpoint_times(); + } + + return new_record; +} + +function watchGoalTrigger() // self == goal trigger +{ + level notify( "watch_goal_trigger" ); + level endon( "watch_goal_trigger" ); + + self waittill( "trigger", player ); + + if ( IsPlayer( player )) + { + player playsoundtoplayer( "uin_freerun_finish", player ); + + player take_all_player_weapons( true, false ); + + new_record = add_current_run_to_high_scores(player); + +// if ( new_record ) +// { +// if ( level.FRGame.activeTrack.highScores.size > 1 ) +// { +// level thread popups::DisplayTeamMessageToAll( &"OBJECTIVES_FR_NEW_RECORD", player ); +// } +// } + + tracksCompleted = player getDStat( "freerunTracksCompleted" ); + + if ( tracksCompleted < level.FRGame.trackIndex ) + { + player setDStat( "freerunTracksCompleted", level.FRGame.trackIndex ); + } + + player RecordGameEvent( "completion" ); + + player.respawn_position = self.origin; + player thread freeze(); + player thread freerunMusic(false); + player updateHighScores(); + level clientfield::set( "freerun_finishTime", get_current_track_time( player ) ); + level clientfield::set( "freerun_state", 2 ); + level notify( "finished_track" ); + + // this profile setting will not work if someone can play on a remote server + if ( player IsHost() ) + { + level notify("stop_tutorials"); + take_players_out_of_tutorial_mode(); + level.FRGame.tutorials = false; + SetLocalProfileVar( "com_firsttime_freerun", 1 ); + + highest_track = GetLocalProfileInt( "freerunHighestTrack" ); + + if ( highest_track < level.FRGame.trackIndex ) + { + SetLocalProfileVar( "freerunHighestTrack", level.FRGame.trackIndex ); + } + } + + /#DumpHighScores();#/ + + wait(1.5); + + UploadStats(); + player uploadleaderboards(); + + level clientfield::set( "freerun_state", 5 ); + } +} + +function freeze() +{ + self util::freeze_player_controls( true ); +} + +function unfreeze() +{ + self util::freeze_player_controls( false ); +} + +function setup_weapon_targets() // self == checkpoint +{ + target_name = self.weaponObject.visuals[0].target; + if ( !IsDefined(target_name) ) + return; + + self.weaponObject.targetShotTime = 0; + self.weaponObject.targets = []; + self.weaponObject.target_visuals = []; + + targets = GetEntArray( target_name, "targetname" ); + foreach( target in targets ) + { + if ( target.script_noteworthy == "fr_target" ) + { + self.weaponObject.targets[self.weaponObject.targets.size] = target; + } + if ( target.script_noteworthy == "fr_target_visual" ) + { + self.weaponObject.target_visuals[self.weaponObject.target_visuals.size] = target; + } + } + + foreach ( target in self.weaponObject.targets ) + { + foreach( visual in self.weaponObject.target_visuals ) + { + if ( target.origin == visual.origin ) + { + target.visual = visual; + } + } + } + + foreach ( target in self.weaponObject.targets ) + { + target.blocker = GetEnt( target.target, "targetname" ); + if ( IsDefined( target.blocker ) ) + { + if ( !isdefined( target.blocker.targetCount ) ) + { + target.blocker.targetCount = 0; + target.blocker.activeTargetCount = 0; + } + + target.blocker.targetCount++; + target.blocker.activeTargetCount++; + + target.checkPoint = self; + target.disabled = false; + target thread watch_target_trigger_thread(self.weaponObject); + } + } +} + +function watch_target_trigger_thread( weaponObject ) +{ + self endon( "death" ); + + while(1) + { + self waittill ( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, weapon, iDFlags ); + + if ( level.FRGame.activeSpawnPoint != self.checkPoint ) + continue; + + if ( weapon == level.weaponBaseMeleeHeld ) + continue; + + if ( self.disabled ) + continue; + + self turn_off_target(weapon); + + PlayFx( level.fr_target_impact_fx, point, direction_vec ); + weaponObject.targetShotTime = GetTime(); + } +} + +function turn_off_target(weapon) +{ + self.disabled = true; + self.visual ghost(); // this will still stop bullet collision with ghost + self.visual notsolid(); + + self.blocker blocker_disable(); + + Playfx( level.fr_target_disable_fx, self.origin ); + PlaySoundAtPosition( level.fr_target_disable_sound, self.origin ); +} + +function blocker_enable() +{ + self.activeTargetCount = self.targetCount; + + self.disabled = false; + self show(); + self solid(); +} + +function blocker_disable() +{ + self.activeTargetCount--; + + if ( self.activeTargetCount == 0 ) + { + self.disabled = true; + self ghost(); + self notsolid(); + } +} + +function reset_targets() +{ + foreach ( target in self.targets ) + { + target.blocker blocker_enable(); + target.visual show(); + target.visual solid(); + target.disabled = false; + } +} + +function reset_all_targets() +{ + foreach( trigger in level.FRGame.checkpointTriggers ) + { + if ( IsDefined( trigger.weaponObject ) ) + { + trigger.weaponObject reset_targets(); + } + } +} + +/# +function DumpHighScores() +{ + for ( i = 0; i < level.FRGame.activeTrack.highScores.size; i++ ) + { + println( ( i + 1 ) + ": " + level.FRGame.activeTrack.highScores[i].time ); + + if ( i == 0 ) + { + for ( j = 0; j < level.FRGame.activeTrack.fastestRunCheckpointTimes.size; j++ ) + { + println( "CP" + j + ": " + level.FRGame.activeTrack.fastestRunCheckpointTimes[ j ] ); + } + } + } +} +#/ + +function play_fault_VO() +{ + current_time = GetTime(); + fault_vo_interval = 20000; + + if ( current_time - level.FRGame.lastPlayedFaultVOTime < fault_vo_interval ) + return; + + if ( isdefined( self.lastTutorialVOPlayed ) ) + return; + + if ( level.FRGame.lastPlayedFaultVOCheckpoint == level.FRGame.activeSpawnPoint.checkpointIndex ) + return; + + level.FRGame.lastPlayedFaultVOCheckpoint = level.FRGame.activeSpawnPoint.checkpointIndex; + level.FRGame.lastPlayedFaultVOTime = current_time; + self playlocalsound( "vox_tuto_tutorial_fail" ); +} + +function faultDeath() // self == player +{ + self play_fault_VO(); + + // do the fall deaths increase time in trials? + level.FRGame.faults++; + self RecordGameEvent( "fault" ); + level clientfield::set( "freerun_faults", level.FRGame.faults ); + self playsoundtoplayer( "uin_freerun_reset", self ); + self respawnAtActiveCheckpoint(); +} + +function dpad_up_pressed() +{ + return self ActionSlotOneButtonPressed(); +} + +function dpad_down_pressed() +{ + return self ActionSlotTwoButtonPressed(); +} + +function dpad_right_pressed() +{ + return self ActionSlotFourButtonPressed(); +} + +function dpad_left_pressed() +{ + return self ActionSlotThreeButtonPressed(); +} + +function end_game_state() +{ + state = level clientfield::get( "freerun_state" ); + if ( state == 2 || state == 4 || state == 5 ) + { + return true; + } + + return false; +} + +function watchTrackSwitch() // self == player +{ + track_count = level.FRGame.tracks.size; + + while( true ) + { + wait .05; + + switch_track = false; + + if ( end_game_state() ) + { + continue; + } + +/# + if ( self dpad_right_pressed() && track_count > 1 ) + { + switch_track = true; + curr_track_index = level.FRGame.activeTrackIndex; + curr_track_index++; + } + else if ( self dpad_left_pressed() && track_count > 1 ) + { + switch_track = true; + curr_track_index = level.FRGame.activeTrackIndex; + curr_track_index--; + } +#/ + if ( !switch_track && self dpad_up_pressed()) + { + switch_track = true; + curr_track_index = level.FRGame.activeTrackIndex; + self thread freerunMusic(); + } + + if ( switch_track ) + { + if ( curr_track_index == 1 ) + { + curr_track_index = 0; + } + else if ( curr_track_index < 0 ) + { + curr_track_index = 1 - 1; + } + + self playsoundtoplayer( "uin_freerun_reset", self ); + activateTrack( curr_track_index ); + + while ( true ) + { + wait .05; + + if (!( self dpad_right_pressed() || self dpad_left_pressed() || self dpad_up_pressed() )) + { + break; + } + } + } + } +} + +function watchUserRespawn() // self == player +{ + level endon( "activate_track" ); + level endon( "finished_track" ); + +/# + wasinnoclip = false; +#/ + while( true ) + { + wait .05; + + if ( end_game_state() ) + { + continue; + } + +/# + if ( self IsInMoveMode( "noclip" )) + { + wasinnoclip = true; + continue; + } + if ( wasinnoclip && self dpad_down_pressed() ) + { + continue; + } + wasinnoclip = false; +#/ + + if ( self dpad_down_pressed() ) + { + level.FRGame.userSpawns++; + self RecordGameEvent( "retry" ); + level clientfield::set( "freerun_retries", level.FRGame.userSpawns ); + self playsoundtoplayer( "uin_freerun_reset", self ); + self respawnAtActiveCheckpoint(); + + while ( true ) + { + wait .05; + + if (!( self dpad_down_pressed() )) + { + break; + } + } + } + } +} + +function ignoreBulletsFired(weapon) +{ + if ( !IsDefined(level.FRGame.activespawnpoint) ) + return false; + + if ( !IsDefined(level.FRGame.activespawnpoint.weaponobject) ) + return false; + + grace_period = (weapon.fireTime * 4) * 1000; + + if ( (level.FRGame.activespawnpoint.weaponobject.targetShotTime + grace_period) >= GetTime() ) + return true; + + foreach( target in level.FRGame.activespawnpoint.weaponobject.targets ) + { + if ( !target.disabled ) + { + return false; + } + } + + return true; +} + +function watchWeaponFire() // self == player +{ + self endon("disconnect"); + + while(1) + { + self waittill( "weapon_fired", weapon ); + + if ( weapon == level.weaponBaseMeleeHeld ) + continue; + + if ( ignoreBulletsFired(weapon) ) + continue; + + level.FRGame.bulletPenalty++; + level clientfield::set( "freerun_bulletPenalty", level.FRGame.bulletPenalty ); + } +} + +function getGroundPointForOrigin( position ) +{ + trace = BulletTrace( position + (0,0,10), position - (0,0,1000), false, undefined ); + return trace["position"]; +} + +function watchStartRun( player ) // self == start trigger +{ + level endon( "activate_track" ); + + self waittill( "trigger", trigger_ent ); + + if ( trigger_ent == player ) + { + player startRun(); + } +} + +function respawnAtActiveCheckpoint() // self == player +{ + ResetGlass(); + reset_all_targets(); + pickup_items::respawn_all_pickups(); + take_players_out_of_tutorial_mode(); + + self playsoundtoplayer( "evt_freerun_respawn", self ); + + if ( IsDefined( self.respawn_position ) ) + { + self SetOrigin( self.respawn_position ); + self SetVelocity( (0,0,0) ); + } + else if ( IsDefined( level.FRGame.activeSpawnPoint.spawnPoint )) + { + self SetOrigin( level.FRGame.activeSpawnPoint.spawnPoint.origin ); + self SetPlayerAngles( level.FRGame.activeSpawnPoint.spawnPoint.angles ); + self SetVelocity( (0,0,0) ); + } + else + { + // no spawn point for the track start triggers + spawn_origin = level.FRGame.activeSpawnLocation; + spawn_origin += ( 0,0, 5.0 ); + + self SetOrigin( spawn_origin ); + self SetPlayerAngles( level.FRGame.activeSpawnAngles ); + self SetVelocity( (0,0,0) ); + } + + self setdoublejumpenergy( 1.0 ); + self take_all_player_weapons( true, true ); +} + +function giveCustomLoadout() +{ + self TakeAllWeapons(); + self clearPerks(); + + self SetPerk( "specialty_fallheight" ); + + self GiveWeapon( level.weaponBaseMeleeHeld ); + self setSpawnWeapon( level.weaponBaseMeleeHeld ); + + return level.weaponBaseMeleeHeld; +} + +function set_high_score_stat( trackIndex, slot, stat, value ) +{ + self setDStat( "freerunTrackTimes", "track", trackIndex, "topTimes", slot, stat, value ); +} + +function write_high_scores_stats(start_index) +{ + active_track = level.FRGame.activeTrack; + + self setDStat( "freerunTrackTimes", "track", level.FRGame.trackIndex, "mapUniqueId", level.FRGame.mapUniqueId ); + self setDStat( "freerunTrackTimes", "track", level.FRGame.trackIndex, "mapVersion", level.FRGame.mapVersion ); + + for ( slot = start_index; slot < 3; slot++ ) + { + set_high_score_stat( level.FRGame.trackIndex, slot, "time", active_track.highScores[slot].time ); + set_high_score_stat( level.FRGame.trackIndex, slot, "faults", active_track.highScores[slot].faults ); + set_high_score_stat( level.FRGame.trackIndex, slot, "retries", active_track.highScores[slot].retries ); + set_high_score_stat( level.FRGame.trackIndex, slot, "bulletPenalty", active_track.highScores[slot].bulletPenalty ); + } +} + +function write_checkpoint_times() +{ + level.FRGame.activeTrack.fastestRunCheckpointTimes = level.FRGame.checkpointTimes; + + for ( i = 0; i < level.FRGame.checkpointTriggers.size; i++ ) + { + self setDStat( "freerunTrackTimes", "track", level.FRGame.trackIndex, "checkPointTimes", "time", i, level.FRGame.checkpointTimes[i] ); + } +} + +function get_high_score_stat( trackIndex, slot, stat ) +{ + return (self getDStat( "freerunTrackTimes", "track", trackIndex, "topTimes", slot, stat )); +} + +function create_high_score_struct( time, faults, retries, bulletPenalty ) // self == player +{ + score_set = SpawnStruct(); + + score_set.time = time; + score_set.faults = faults; + score_set.retries = retries; + score_set.bulletPenalty = bulletPenalty; + + return score_set; +} + +function get_stats_for_track( trackIndex, slot ) // self == player +{ + time = self get_high_score_stat( trackIndex, slot, "time" ); + faults = self get_high_score_stat( trackIndex, slot, "faults" ); + retries = self get_high_score_stat( trackIndex, slot, "retries" ); + bulletPenalty = self get_high_score_stat( trackIndex, slot, "bulletPenalty" ); + + return create_high_score_struct( time, faults, retries, bulletPenalty); +} + +function get_checkpoint_times_for_track( trackIndex ) // self == player +{ + for ( i = 0; i < level.FRGame.checkpointTriggers.size; i++ ) + { + level.FRGame.activeTrack.fastestRunCheckpointTimes[i] = self getDStat( "freerunTrackTimes", "track", trackIndex, "checkPointTimes", "time", i ); + } +} + +function get_top_scores_stats() +{ + if ( isdefined(level.FRGame.activeTrack.statsRead) ) + return; + + mapId = self getDStat( "freerunTrackTimes", "track", level.FRGame.trackIndex, "mapUniqueId" ); + mapVersion = self getDStat( "freerunTrackTimes", "track", level.FRGame.trackIndex, "mapVersion" ); + + if ( level.FRGame.mapUniqueId != mapId || level.FRGame.mapVersion != mapVersion ) + { + for( i = 0; i < 3; i++ ) + { + level.FRGame.activeTrack.highScores[i] = create_high_score_struct( 0,0,0,0 ); + } + for ( i = 0; i < level.FRGame.checkpointTriggers.size; i++ ) + { + level.FRGame.activeTrack.fastestRunCheckpointTimes[i] = 0; + } + } + else + { + for( i = 0; i < 3; i++ ) + { + level.FRGame.activeTrack.highScores[i] = get_stats_for_track( level.FRGame.trackIndex, i ); + } + get_checkpoint_times_for_track(level.FRGame.trackIndex); + } + + level.FRGame.activeTrack.statsRead = true; +} + +function take_all_player_weapons( only_default, immediate ) // self == player +{ + self endon("disconnect"); + self endon("death"); + + keep_weapon = level.weaponNone; + if ( isDefined(level.FRGame.activeSpawnPoint.weapon) && !only_default ) + { + keep_weapon = level.FRGame.activeSpawnPoint.weapon; + } + + if ( immediate ) + { + self switchtoweaponimmediate(level.weaponBaseMeleeHeld); + } + else + { + while ( self isswitchingweapons() ) + { + wait(0.05); + } + + current_weapon = self GetCurrentWeapon(); + + if ( current_weapon != level.weaponBaseMeleeHeld && keep_weapon != current_weapon ) + { + self SwitchToWeapon(level.weaponBaseMeleeHeld); + while( self GetCurrentWeapon() != level.weaponBaseMeleeHeld ) + { + wait(0.05); + } + } + } + + weaponsList = self GetWeaponsList(); + foreach( weapon in weaponsList ) + { + if ( weapon != level.weaponBaseMeleeHeld && keep_weapon != weapon ) + self TakeWeapon( weapon ); + } +} + +function freerunMusic(start=true) +{ + player = self; + + if( start && !( isdefined( player.musicStart ) && player.musicStart ) ) + { + mapname = GetDvarString( "mapname" ); + player globallogic_audio::set_music_on_player( mapname ); + player.musicStart = true; + } + else if( !start ) + { + player globallogic_audio::set_music_on_player( "mp_freerun_finish" ); + player.musicStart = false; + } +} + +function _tutorial_mode( b_tutorial_mode ) +{ +// self SetLowReady( b_tutorial_mode ); +// self AllowDoubleJump( !b_tutorial_mode ); +// self AllowJump( !b_tutorial_mode ); +// self AllowSprint( !b_tutorial_mode ); +} + +function take_players_out_of_tutorial_mode() +{ + if ( level.FRGame.tutorials ) + { + if ( ( level clientfield::get( "freerun_state" ) ) == 3 ) + { +// level.FRGame.totalPausedTime += GetTime() - level.FRGame.pausedAtTime; +// level.FRGame.pausedAtTime = 0; +// level clientfield::set( "freerun_pausedTime", level.FRGame.totalPausedTime ); +// level clientfield::set( "freerun_state", FR_STATE_RUNNING ); + } + + foreach( player in level.players ) + { + player _tutorial_mode( false ); + } + } +} + +function put_players_in_tutorial_mode() +{ + if ( level.FRGame.tutorials ) + { + if ( ( level clientfield::get( "freerun_state" ) ) == 1 ) + { +// level.FRGame.hasBeenPaused = true; +// level.FRGame.pausedAtTime = GetTime(); +// level clientfield::set( "freerun_state", FR_STATE_PAUSED ); + } + + foreach( player in level.players ) + { + player _tutorial_mode( true ); + } + } +} + +function enable_all_tutorial_triggers() +{ + if ( level.FRGame.tutorials ) + { + foreach( trigger in level.FRGame.tutorialTriggers ) + { + trigger TriggerEnable( true ); + } + } +} + +function activate_tutorial_mode() +{ + // profile var will be 0 if never run before + // this profile setting will not work if someone can play on a remote server + if ( (!(self IsHost()) || GetLocalProfileInt( "com_firsttime_freerun" )) && !GetDvarInt( "freerun_tutorial" ) ) + { + return; + } + + level.FRGame.tutorials = true; + + wait(1); + + foreach( trigger in level.FRGame.tutorialTriggers ) + { + trigger thread watchTutorialTrigger(); + } + +} + +function setup_tutorial() +{ + level.FRGame.tutorials = false; + + level.FRGame.tutorialTriggers = GetEntArray( "fr_tutorial", "targetname" ); + + level.FRGame.tutorialFunctions = []; + + register_tutorials(); +} + +function watchTutorialTrigger() +{ + level endon("stop_tutorials"); + + while( true ) + { + self waittill( "trigger", player ); + + if ( IsPlayer( player )) + { + player thread start_tutorial(self.script_noteworthy); + self TriggerEnable( false ); + } + } +} + +function stop_tutorial_when_restarting_track() +{ + self notify("stop_tutorial_when_restarting_track"); + self waittill("stop_tutorial_when_restarting_track"); + + level waittill( "activate_track" ); + + take_players_out_of_tutorial_mode(); + self util::hide_hint_text(false); + + self stop_tutorial_vo(); + self stopsounds(); +} + +function start_tutorial( tutorial ) +{ + self endon("death"); + self endon("disconnect"); + level endon( "game_ended" ); + level endon( "activate_track" ); + if (!isdefined(level.FRGame.tutorialFunctions[tutorial])) + return; + + level notify( "playing_tutorial" ); + level endon( "playing_tutorial" ); + + self thread stop_tutorial_when_restarting_track(); + + put_players_in_tutorial_mode(); + wait( 0.5 ); + [[level.FRGame.tutorialFunctions[tutorial]]](); + take_players_out_of_tutorial_mode(); +} + +function stop_tutorial_vo() +{ + if ( isdefined( self.lastTutorialVOPlayed ) ) + { + self stopsound(self.lastTutorialVOPlayed); + self.lastTutorialVOPlayed = undefined; + } +} + +function play_tutorial_vo( aliasstring ) +{ + self stop_tutorial_vo(); + + self.lastTutorialVOPlayed = aliasstring; + self playsoundwithnotify( aliasstring, "sounddone" ); + self waittill( "sounddone"); + wait( 1.0 ); +} + +function play_tutorial_vo_with_hint( aliasstring, text ) +{ + self stop_tutorial_vo(); + + self thread _show_tutorial_hint_with_vo( text ); + + self.lastTutorialVOPlayed = aliasstring; + self playsoundwithnotify( aliasstring, "sounddone" ); + self waittill( "sounddone"); + wait( 1.0 ); +} + +function _show_tutorial_hint_with_vo( text, time, unlock_player ) +{ + wait (0.5); + show_tutorial_hint( text, time, unlock_player ); +} + +function show_tutorial_hint( text, time, unlock_player ) +{ + if ( isdefined( unlock_player ) ) + { + take_players_out_of_tutorial_mode(); + } + + if (!isdefined(time) ) + { + time = 4.0; + } + self util::show_hint_text( text, false, "activate_track", 4.0); + wait( 4.0 + 0.5); +} + +function show_tutorial_hint_with_full_movement( text, time ) +{ + show_tutorial_hint( text, time, true ); +} + +function register_tutorials() +{ + level.FRGame.tutorialFunctions["tutorial_01"] = &tutorial_01; + level.FRGame.tutorialFunctions["tutorial_02"] = &tutorial_02; + level.FRGame.tutorialFunctions["tutorial_03"] = &tutorial_03; + //level.FRGame.tutorialFunctions["tutorial_03a"] = &tutorial_03a; + //level.FRGame.tutorialFunctions["tutorial_04"] = &tutorial_04; + //level.FRGame.tutorialFunctions["tutorial_05"] = &tutorial_05; + level.FRGame.tutorialFunctions["tutorial_06"] = &tutorial_06; + //level.FRGame.tutorialFunctions["tutorial_07"] = &tutorial_07; + level.FRGame.tutorialFunctions["tutorial_08"] = &tutorial_08; + level.FRGame.tutorialFunctions["tutorial_09"] = &tutorial_09; + level.FRGame.tutorialFunctions["tutorial_10"] = &tutorial_10; + level.FRGame.tutorialFunctions["tutorial_10a"] = &tutorial_10a; + level.FRGame.tutorialFunctions["tutorial_12"] = &tutorial_12; + level.FRGame.tutorialFunctions["tutorial_12a"] = &tutorial_12a; + level.FRGame.tutorialFunctions["tutorial_13"] = &tutorial_13; + //level.FRGame.tutorialFunctions["tutorial_13a"] = &tutorial_13a; + level.FRGame.tutorialFunctions["tutorial_14"] = &tutorial_14; + level.FRGame.tutorialFunctions["tutorial_15"] = &tutorial_15; + level.FRGame.tutorialFunctions["tutorial_16"] = &tutorial_16; + level.FRGame.tutorialFunctions["tutorial_17"] = &tutorial_17; + level.FRGame.tutorialFunctions["tutorial_17a"] = &tutorial_17a; + level.FRGame.tutorialFunctions["tutorial_18"] = &tutorial_18; + level.FRGame.tutorialFunctions["tutorial_19"] = &tutorial_19; + level.FRGame.tutorialFunctions["tutorial_20"] = &tutorial_20; + //level.FRGame.tutorialFunctions["tutorial_21"] = &tutorial_21; + //level.FRGame.tutorialFunctions["tutorial_22"] = &tutorial_22; +} + +function tutorial_01() +{ + self play_tutorial_vo( "vox_tuto_tutorial_sequence_1" ); + self play_tutorial_vo( "vox_tuto_tutorial_sequence_2" ); + self play_tutorial_vo( "vox_tuto_tutorial_sequence_6" ); +} + +function tutorial_02() +{ + self show_tutorial_hint_with_full_movement( &"FREERUN_TUTORIAL_02" ); +} + +function tutorial_03() +{ + self show_tutorial_hint_with_full_movement( &"FREERUN_TUTORIAL_03" ); +} + +function tutorial_06() +{ + self thread play_tutorial_vo( "vox_tuto_tutorial_sequence_11" ); + self show_tutorial_hint_with_full_movement( &"FREERUN_TUTORIAL_09" ); +} + +function tutorial_08() +{ + self show_tutorial_hint_with_full_movement( &"FREERUN_TUTORIAL_11" ); +} + +function tutorial_09() +{ + self play_tutorial_vo_with_hint( "vox_tuto_tutorial_sequence_28", &"FREERUN_TUTORIAL_12" ); +} + +function tutorial_10() +{ + self play_tutorial_vo( "vox_tuto_tutorial_sequence_10" ); +} + +function tutorial_10a() +{ + self show_tutorial_hint_with_full_movement( &"FREERUN_TUTORIAL_13" ); +} + +function tutorial_12() +{ + self play_tutorial_vo( "vox_tuto_tutorial_sequence_16" ); +} + +function tutorial_12a() +{ + self show_tutorial_hint_with_full_movement( &"FREERUN_TUTORIAL_14" ); +} + +function tutorial_13() +{ + self play_tutorial_vo_with_hint( "vox_tuto_tutorial_sequence_17", &"FREERUN_TUTORIAL_14a" ); + self play_tutorial_vo( "vox_tuto_tutorial_sequence_18" ); + self show_tutorial_hint_with_full_movement( &"FREERUN_TUTORIAL_16" ); +} + +function tutorial_14() +{ + self play_tutorial_vo( "vox_tuto_tutorial_sequence_19" ); + self show_tutorial_hint_with_full_movement( &"FREERUN_TUTORIAL_18" ); +} + +function tutorial_15() +{ + self play_tutorial_vo( "vox_tuto_tutorial_sequence_20" ); +} + +function tutorial_16() +{ + self play_tutorial_vo( "vox_tuto_tutorial_sequence_29" ); +} + +function tutorial_17() +{ + self play_tutorial_vo( "vox_tuto_tutorial_sequence_21" ); +} + +function tutorial_17a() +{ + self show_tutorial_hint_with_full_movement( &"FREERUN_TUTORIAL_22" ); +} + + +function tutorial_18() +{ + self play_tutorial_vo_with_hint( "vox_tuto_tutorial_sequence_23", &"FREERUN_TUTORIAL_23" ); + self show_tutorial_hint_with_full_movement( &"FREERUN_TUTORIAL_22a" ); +} + +function tutorial_19() +{ + self play_tutorial_vo( "vox_tuto_tutorial_sequence_25" ); +} + +function tutorial_20() +{ + self play_tutorial_vo( "vox_tuto_tutorial_sequence_26" ); +} \ No newline at end of file diff --git a/mp/gametypes/fr.gsh b/mp/gametypes/fr.gsh new file mode 100644 index 0000000..fbb30a4 --- /dev/null +++ b/mp/gametypes/fr.gsh @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/mp/gametypes/gun.csc b/mp/gametypes/gun.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/gun.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/gun.gsc b/mp/gametypes/gun.gsc new file mode 100644 index 0000000..ebbfddc --- /dev/null +++ b/mp/gametypes/gun.gsc @@ -0,0 +1,539 @@ +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\persistence_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\weapons\_weapon_utils; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_util; + +/* + Deathmatch + Objective: Score points by eliminating other players + Map ends: When one player reaches the score limit, or time limit is reached + Respawning: No wait / Away from other players + + Level requirements + ------------------ + Spawnpoints: + classname mp_dm_spawn_start + classname mp_dm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of enemies at the time of spawn. + Players generally spawn away from enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Level script requirements + ------------------------- + Team Definitions: + + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ +*/ + +/*QUAKED mp_wager_spawn (1.0 0.5 0.0) (-16 -16 0) (16 16 72) +Players spawn away from enemies at one of these positions.*/ + +#precache( "string", "OBJECTIVES_GUN" ); +#precache( "string", "OBJECTIVES_GUN_SCORE" ); +#precache( "string", "OBJECTIVES_GUN_HINT" ); +#precache( "string", "MPUI_PLAYER_KILLED" ); +#precache( "string", "MP_GUN_NEXT_LEVEL" ); +#precache( "string", "MP_GUN_PREV_LEVEL" ); +#precache( "string", "MP_GUN_PREV_LEVEL_OTHER" ); +#precache( "string", "MP_HUMILIATION" ); +#precache( "string", "MP_HUMILIATED" ); + +function main() +{ + globallogic::init(); + + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.onPlayerKilled =&onPlayerKilled; + level.onEndGame = &onEndGame; + + globallogic_audio::set_leader_gametype_dialog ( "startGunGame", "hcSstartGunGame", "", "" ); + + level.giveCustomLoadout =&giveCustomLoadout; + + level.setBacksPerDemotion = GetGametypeSetting( "setbacks" ); + level.inactivityKick = 60; // GetGametypeSetting( "inactivityKick" ); + + gameobjects::register_allowed_gameobject( level.gameType ); + + level.weapon_lmg_infinite = GetWeapon( "lmg_infinite" ); + + populateGunList(); + + util::registerTimeLimit( 0, 1440 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "pointstowin", "kills", "stabs", "humiliated", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "pointstowin", "kills", "deaths", "stabs", "humiliated" ); +} + +function populateBlackMarketGunList() +{ + //Black Market List + addGunToProgression( "pistol_m1911" ); + addGunToProgression( "pistol_shotgun_dw" ); + addGunToProgression( "shotgun_olympia", "reddot" ); + addGunToProgression( "shotgun_energy", "reflex" ); + addGunToProgression( "smg_ak74u", "grip", "extbarrel" ); + addGunToProgression( "smg_ppsh", "reflex", "steadyaim" ); + addGunToProgression( "smg_msmc", "fastreload", "reddot" ); + addGunToProgression( "ar_an94", "rf", "fastreload" ); + addGunToProgression( "ar_m16", "reddot", "damage" ); + addGunToProgression( "ar_peacekeeper", "reddot", "quickdraw" ); + addGunToProgression( "ar_galil", "reflex", "extbarrel" ); + addGunToProgression( "ar_garand", "acog" ); + addGunToProgression( "ar_pulse", "fastreload", "steadyaim" ); + addGunToProgression( "lmg_infinite", "rf" ); + addGunToProgression( "sniper_quickscope", "swayreduc" ); + addGunToProgression( "sniper_double", "acog", "swayreduc" ); + addGunToProgression( "launcher_ex41" ); + addGunToProgression( "special_discgun" ); + addGunToProgression( "special_crossbow" ); + addGunToProgression( "knife_ballistic" ); +} + +function populateNormalGunList() +{ + //Normal List + addGunToProgression( "pistol_standard", "fastreload", "steadyaim" ); + addGunToProgression( "pistol_burst_dw" ); + addGunToProgression( "pistol_fullauto", "reflex" ); + addGunToProgression( "shotgun_pump", "extbarrel", "suppressed" ); + addGunToProgression( "shotgun_precision", "qucikdraw", "holo" ); + addGunToProgression( "shotgun_fullauto", "extclip" ); + addGunToProgression( "smg_versatile", "grip", "extbarrel" ); + addGunToProgression( "smg_burst", "suppressed" ); + addGunToProgression( "smg_longrange", "reflex" ); + addGunToProgression( "ar_cqb", "rf", "fastreload" ); + addGunToProgression( "ar_standard", "fmj", "damage" ); + addGunToProgression( "ar_longburst", "acog" ); + addGunToProgression( "ar_marksman", "ir" ); + addGunToProgression( "lmg_slowfire", "extclip" ); + addGunToProgression( "lmg_cqb", "quickdraw", "steadyaim" ); + addGunToProgression( "sniper_fastbolt", "swayreduc" ); + addGunToProgression( "sniper_powerbolt", "reddot" ); + addGunToProgression( "launcher_standard" ); + addGunToProgression( "knife_loadout" ); + addGunToProgression( "bare_hands" ); +} + +function populateGunListNormalSetting() +{ + if ( GetDvarInt( "black_market_gun_game", 0 ) > 0 ) + { + populateBlackMarketGunList(); + } + else + { + populateNormalGunList(); + } +} + +function populateGunList() +{ + level.gunProgression = []; + + //use this variable for assigning cusom game lists + gunList = GetGametypeSetting( "gunSelection" ); + if ( gunList == 3 ) + { + gunList = RandomIntRange( 0, 3 ); + } + + switch ( gunList ) + { + case 0: + populateGunListNormalSetting(); + break; + + case 1: + //Close Quarters + addGunToProgression( "pistol_standard", "reddot" ); + addGunToProgression( "pistol_fullauto_dw", "reflex" ); + addGunToProgression( "pistol_standard_dw" ); + addGunToProgression( "pistol_burst_dw" ); + addGunToProgression( "pistol_fullauto_dw" ); + addGunToProgression( "shotgun_semiauto", "steadyaim" ); + addGunToProgression( "shotgun_fullauto", "suppressed" ); + addGunToProgression( "shotgun_pump", "quickdraw" ); + addGunToProgression( "shotgun_precision", "reddot" ); + addGunToProgression( "smg_fastfire", "holo" ); + addGunToProgression( "smg_standard", "quickdraw" ); + addGunToProgression( "smg_versatile", "suppressed" ); + addGunToProgression( "smg_capacity", "stalker" ); + addGunToProgression( "smg_longrange", "rf" ); + addGunToProgression( "smg_burst", "reddot" ); + addGunToProgression( "lmg_cqb", "quickdraw" ); + addGunToProgression( "launcher_standard" ); + addGunToProgression( "sniper_powerbolt", "reddot" ); + addGunToProgression( "knife_loadout" ); + addGunToProgression( "bare_hands" ); + break; + + case 2: + //Marksman + addGunToProgression( "smg_capacity", "holo", "quickdraw" ); + addGunToProgression( "smg_longrange", "acog", "extclip" ); + addGunToProgression( "smg_burst", "acog", "extbarrel" ); + addGunToProgression( "ar_cqb", "acog" ); + addGunToProgression( "ar_standard", "reflex" ); + addGunToProgression( "ar_longburst", "extbarrel" ); + addGunToProgression( "ar_fastburst", "holo" ); + addGunToProgression( "ar_marksman", "acog" ); + addGunToProgression( "ar_damage", "reddot" ); + addGunToProgression( "ar_accurate", "ir", "extbarrel" ); + addGunToProgression( "lmg_light", "ir" ); + addGunToProgression( "lmg_cqb", "reflex" ); + addGunToProgression( "lmg_heavy", "acog" ); + addGunToProgression( "lmg_slowfire", "ir", "extclip" ); + addGunToProgression( "sniper_fastsemi", "swayreduc", "stalker" ); + addGunToProgression( "sniper_fastbolt", "ir", "rf" ); + addGunToProgression( "sniper_powerbolt", "swayreduc" ); + addGunToProgression( "launcher_standard" ); + addGunToProgression( "knife_loadout" ); + addGunToProgression( "bare_hands" ); + break; + } +} + + +// Game Events +//======================================== + +function onStartGameType() +{ + level.gunGameKillScore = rank::getScoreInfoValue( "kill_gun" ); + util::registerScoreLimit( level.gunProgression.size * level.gunGameKillScore, level.gunProgression.size * level.gunGameKillScore ); + + SetDvar( "ui_weapon_tiers", level.gunProgression.size ); + + setClientNameMode("auto_change"); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + foreach( team in level.teams ) + { + setupTeam( team ); + } + + level.useStartSpawns = true; + level.spawn_start = spawnlogic::get_spawnpoint_array( "mp_dm_spawn_start" ); + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + spawning::updateAllSpawnPoints(); + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + level.displayRoundEndText = false; +} + +function inactivityKick() +{ + self endon("disconnect"); + self endon("death"); + + if ( SessionModeIsPrivate() ) + return; + + if ( level.inactivityKick == 0 ) + return; + + while ( level.inactivityKick > self.timePlayed["total"] ) + { + wait( 1 ); + } + + if ( self.pers["participation"] == 0 && self.pers["time_played_moving"] < 1 ) + { + globallogic::gameHistoryPlayerKicked(); + + kick( self getEntityNumber(), "GAME_DROPPEDFORINACTIVITY" ); + } + + if ( self.pers["participation"] == 0 && self.timePlayed["total"] > 60 ) + { + globallogic::gameHistoryPlayerKicked(); + + kick( self getEntityNumber(), "GAME_DROPPEDFORINACTIVITY" ); + } +} + +function onSpawnPlayer(predictedSpawn) +{ + if( !level.inPrematchPeriod ) + { + level.useStartSpawns = false; + } + + spawning::onSpawnPlayer(predictedSpawn); + self thread infiniteAmmo(); + + self thread inactivityKick(); +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + level.useStartSpawns = false; + + if ( ( sMeansOfDeath == "MOD_SUICIDE" ) || ( sMeansOfDeath == "MOD_TRIGGER_HURT" ) ) + { + self thread demotePlayer(); + return; + } + + if ( isdefined( attacker ) && IsPlayer( attacker ) ) + { + if ( attacker == self ) + { + self thread demotePlayer(attacker); + return; + } + + if ( isdefined( attacker.lastPromotionTime ) && attacker.lastPromotionTime + 3000 > getTime() ) + { + scoreevents::processScoreEvent( "kill_in_3_seconds_gun", attacker, self, weapon ); + } + + if ( weapon_utils::isMeleeMOD( sMeansOfDeath ) ) + { + scoreevents::processScoreEvent( "humiliation_gun", attacker, self, weapon ); + if ( globallogic_score::getHighestScoringPlayer() === self ) + { + scoreevents::processScoreEvent( "melee_leader_gun", attacker, self, weapon ); + } + self thread demotePlayer(attacker); + } + + if ( ( !weapon.isBallisticKnife && sMeansOfDeath != "MOD_MELEE_WEAPON_BUTT" ) || ( weapon.isBallisticKnife && sMeansOfDeath != "MOD_MELEE" ) ) + { + attacker thread promotePlayer( weapon ); + } + } +} + +function onEndGame( winningPlayer ) +{ + if ( isDefined( winningPlayer ) && isPlayer( winningPlayer ) ) + [[level._setPlayerScore]]( winningPlayer, [[level._getPlayerScore]]( winningPlayer ) + level.gunGameKillScore ); +} + +// Game Setup +//======================================== + +function addGunToProgression( weaponName, attachment1, attachment2, attachment3, attachment4, attachment5, attachment6, attachment7, attachment8 ) +{ + attachments = []; + + if ( isdefined( attachment1 ) ) + attachments[attachments.size] = attachment1; + if ( isdefined( attachment2 ) ) + attachments[attachments.size] = attachment2; + if ( isdefined( attachment3 ) ) + attachments[attachments.size] = attachment3; + if ( isdefined( attachment4 ) ) + attachments[attachments.size] = attachment4; + if ( isdefined( attachment5 ) ) + attachments[attachments.size] = attachment5; + if ( isdefined( attachment6 ) ) + attachments[attachments.size] = attachment6; + if ( isdefined( attachment7 ) ) + attachments[attachments.size] = attachment7; + if ( isdefined( attachment8 ) ) + attachments[attachments.size] = attachment8; + + weapon = GetWeapon( weaponName, attachments ); + level.gunProgression[level.gunProgression.size] = weapon; +} + +function setupTeam( team ) +{ + util::setObjectiveText( team, &"OBJECTIVES_GUN" ); + if ( level.splitscreen ) + { + util::setObjectiveScoreText( team, &"OBJECTIVES_GUN" ); + } + else + { + util::setObjectiveScoreText( team, &"OBJECTIVES_GUN_SCORE" ); + } + util::setObjectiveHintText( team, &"OBJECTIVES_GUN_HINT" ); + + spawnlogic::add_spawn_points( team, "mp_dm_spawn" ); + spawnlogic::place_spawn_points( "mp_dm_spawn_start" ); +} + +// Gun Game +//======================================== + +function takeOldWeapon( oldWeapon ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + wait( 1 ); + + self takeweapon( oldWeapon ); +} + +function giveCustomLoadout( takeOldWeapon ) +{ + if(!isdefined(takeOldWeapon))takeOldWeapon=false; + + self loadout::giveLoadout_init( !takeOldWeapon ); + + self loadout::setClassNum( "CLASS_ASSAULT" ); + + if ( takeOldWeapon ) + { + oldWeapon = self GetCurrentWeapon(); + + weapons = self GetWeaponsList(); + foreach ( weapon in weapons ) + { + if( weapon != oldWeapon ) + { + self takeweapon( weapon ); + } + } + + self thread takeOldWeapon( oldWeapon ); + } + + if(!isdefined(self.gunProgress))self.gunProgress=0; + currentWeapon = level.gunProgression[self.gunProgress]; + + self giveWeapon( currentWeapon ); + self switchToWeapon( currentWeapon ); + + self disableweaponcycling(); + + if ( self.firstSpawn !== false ) + { + self setSpawnWeapon( currentWeapon ); + } + + return currentWeapon; +} + +function promotePlayer( weaponUsed ) +{ + self endon( "disconnect" ); + self endon( "cancel_promotion" ); + level endon( "game_ended" ); + + {wait(.05);}; // If you suicide simultaneously, you shouldn't get the promotion + + if ( weaponUsed.rootweapon == level.gunProgression[self.gunProgress].rootweapon || ( isdefined( level.gunProgression[self.gunProgress].dualwieldweapon ) && level.gunProgression[self.gunProgress].dualwieldweapon.rootweapon == weaponUsed.rootweapon ) ) + { + if ( self.gunProgress < level.gunProgression.size-1 ) + { + self.gunProgress++; + if ( IsAlive( self ) ) // Player may have died during the wait through no fault of their own + { + self thread giveCustomLoadout( true ); + } + } + pointsToWin = self.pers["pointstowin"]; + if ( pointsToWin < level.scorelimit ) + { + self globallogic_score::givePointsToWin( level.gunGameKillScore ); + scoreevents::processScoreEvent( "kill_gun", self ); + } + self.lastPromotionTime = getTime(); + } +} + +function demotePlayer( attacker ) +{ + self endon( "disconnect" ); + self notify( "cancel_promotion" ); + + currentGunProgress = self.gunProgress; + + for ( i = 0 ; i < level.setBacksPerDemotion ; i++ ) + { + if ( self.gunProgress <= 0 ) + { + break; + } + self globallogic_score::givePointsToWin( level.gunGameKillScore * -1 ); + self.gunProgress--; + } + + if ( currentGunProgress != self.gunProgress && IsAlive( self ) ) + { + self thread giveCustomLoadout( true ); + } + + if ( isdefined( attacker ) ) + { + self AddPlayerStatWithGameType( "HUMILIATE_ATTACKER", 1 ); + + // record attacker's melee event + attacker RecordGameEvent("capture"); + } + + self AddPlayerStatWithGameType( "HUMILIATE_VICTIM", 1 ); + + self.pers["humiliated"]++; + self.humiliated = self.pers["humiliated"]; + + // record victim's demote event + self RecordGameEvent("return"); + + self PlayLocalSound( "mpl_wager_humiliate" ); + self globallogic_audio::leader_dialog_on_player( "humiliated" ); +} + +function infiniteAmmo() +{ + self endon( "death" ); + self endon( "disconnect" ); + + while( 1 ) + { + wait( 0.1 ); + + weapon = self GetCurrentWeapon(); + + if ( weapon.rootWeapon == level.weapon_lmg_infinite ) + continue; + + self GiveMaxAmmo( weapon ); + } +} diff --git a/mp/gametypes/hq.csc b/mp/gametypes/hq.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/hq.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/hq.gsc b/mp/gametypes/hq.gsc new file mode 100644 index 0000000..68bc920 --- /dev/null +++ b/mp/gametypes/hq.gsc @@ -0,0 +1,1421 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\medals_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weapons; + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_rcbomb; +#using scripts\mp\killstreaks\_supplydrop; + + + + + + + + +#precache( "string", "OBJECTIVES_KOTH" ); +#precache( "string", "OBJECTIVES_HQ" ); +#precache( "string", "OBJECTIVES_HQ_SCORE" ); +#precache( "string", "MP_WAITING_FOR_HQ" ); +#precache( "string", "MP_HQ_CAPTURED_BY" ); +#precache( "string", "MP_CONTROL_HQ" ); +#precache( "string", "MP_CAPTURE_HQ" ); +#precache( "string", "MP_DESTROY_HQ" ); +#precache( "string", "MP_DEFEND_HQ" ); +#precache( "string", "MP_HQ_AVAILABLE_IN" ); +#precache( "string", "MP_HQ_DESPAWN_IN" ); +#precache( "string", "MP_HQ_REINFORCEMENTS_IN" ); +#precache( "string", "MP_CAPTURING_HQ" ); +#precache( "string", "MP_DESTROYING_HQ" ); +#precache( "eventstring", "objective" ); + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 1000 ); + util::registerNumLives( 0, 100 ); + util::registerRoundSwitch( 0, 9 ); + util::registerRoundWinLimit( 0, 10 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + + level.teamBased = true; + level.doPrematch = true; + level.overrideTeamScore = true; + level.scoreRoundWinBased = true; + level.onStartGameType =&onStartGameType; + level.playerSpawnedCB =&koth_playerSpawnedCB; + level.onRoundSwitch =&onRoundSwitch; + level.onPlayerKilled =&onPlayerKilled; + level.onEndGame=&onEndGame; + + level.hqAutoDestroyTime = GetGametypeSetting( "autoDestroyTime" ); + level.hqSpawnTime = GetGametypeSetting( "objectiveSpawnTime" ); + level.kothMode = GetGametypeSetting( "kothMode" ); + level.captureTime = GetGametypeSetting( "captureTime" ); + level.destroyTime = GetGametypeSetting( "destroyTime" ); + level.delayPlayer = GetGametypeSetting( "delayPlayer" ); + level.randomHQSpawn = GetGametypeSetting( "randomObjectiveLocations" ); + + level.maxRespawnDelay = GetGametypeSetting( "timeLimit" ) * 60; + + level.iconoffset = (0,0,32); + + level.onRespawnDelay =&getRespawnDelay; + + gameobjects::register_allowed_gameobject( level.gameType ); + + game["dialog"]["gametype"] = "hq_start"; + game["dialog"]["gametype_hardcore"] = "hchq_start"; + game["dialog"]["offense_obj"] = "cap_start"; + game["dialog"]["defense_obj"] = "cap_start"; + + level.lastDialogTime = 0; + level.radioSpawnQueue = []; + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "captures", "defends", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "captures", "defends" ); +} + +function updateObjectiveHintMessages( defenderTeam, defendMessage, attackMessage ) +{ + foreach( team in level.teams ) + { + if ( defenderTeam == team ) + { + game["strings"]["objective_hint_" + team] = defendMessage; + } + else + { + game["strings"]["objective_hint_" + team] = attackMessage; + } + } +} + +function updateObjectiveHintMessage( message ) +{ + foreach( team in level.teams ) + { + game["strings"]["objective_hint_" + team] = message; + } +} + +function getRespawnDelay() +{ + self.lowerMessageOverride = undefined; + + if ( !isdefined( level.radio.gameobject ) ) + return undefined; + + hqOwningTeam = level.radio.gameobject gameobjects::get_owner_team(); + if ( self.pers["team"] == hqOwningTeam ) + { + if ( !isdefined( level.hqDestroyTime ) ) + timeRemaining = level.maxRespawnDelay; + else + timeRemaining = (level.hqDestroyTime - gettime()) / 1000; + + if (!level.playerObjectiveHeldRespawnDelay ) + return undefined; + + if ( level.playerObjectiveHeldRespawnDelay >= level.hqAutoDestroyTime ) + self.lowerMessageOverride = &"MP_WAITING_FOR_HQ"; + + if ( level.delayPlayer ) + { + return min( level.spawnDelay, timeRemaining ); + } + else + { + return ceil(timeRemaining); + } + } +} + +function onStartGameType() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + + globallogic_score::resetTeamScores(); + + foreach( team in level.teams ) + { + util::setObjectiveText( team, &"OBJECTIVES_KOTH" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( team, &"OBJECTIVES_HQ" ); + } + else + { + util::setObjectiveScoreText( team, &"OBJECTIVES_HQ_SCORE" ); + } + } + + level.objectiveHintPrepareHQ = &"MP_CONTROL_HQ"; + level.objectiveHintCaptureHQ = &"MP_CAPTURE_HQ"; + level.objectiveHintDestroyHQ = &"MP_DESTROY_HQ"; + level.objectiveHintDefendHQ = &"MP_DEFEND_HQ"; + + if ( level.kothMode ) + level.objectiveHintDestroyHQ = level.objectiveHintCaptureHQ; + + if ( level.hqSpawnTime ) + updateObjectiveHintMessage( level.objectiveHintPrepareHQ ); + else + updateObjectiveHintMessage( level.objectiveHintCaptureHQ ); + + setClientNameMode("auto_change"); + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + // TODO: HQ spawnpoints + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + foreach( team in level.teams ) + { + spawnlogic::add_spawn_points( team, "mp_tdm_spawn" ); + + spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) ); + } + + spawning::updateAllSpawnPoints(); + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array( spawning::getTDMStartSpawnName(team) ); + } + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + level.spawn_all = spawnlogic::get_spawnpoint_array( "mp_tdm_spawn" ); + if ( !level.spawn_all.size ) + { +/# + println("^1No mp_tdm_spawn spawnpoints in level!"); +#/ + callback::abort_level(); + return; + } + + + thread SetupRadios(); + + thread HQMainLoop(); +} + +function spawn_first_radio(delay) +{ + // pick next HQ object + if ( level.randomHQSpawn == 1 ) + { + level.radio = GetNextRadioFromQueue(); + } + else + { + level.radio = GetFirstRadio(); + } + + /#print("radio spawned: ("+level.radio.trigOrigin[0]+","+level.radio.trigOrigin[1]+","+level.radio.trigOrigin[2]+")");#/ + + level.radio spawning::enable_influencers(true); + + return; +} + +function spawn_next_radio() +{ + // pick next HQ object + if ( level.randomHQSpawn != 0 ) + { + level.radio = GetNextRadioFromQueue(); + } + else + { + level.radio = GetNextRadio(); + } + + /#print("radio spawned: ("+level.radio.trigOrigin[0]+","+level.radio.trigOrigin[1]+","+level.radio.trigOrigin[2]+")");#/ + + level.radio spawning::enable_influencers(true); + + return; +} + +function HQMainLoop() +{ + level endon("game_ended"); + + level.hqRevealTime = -100000; + + hqSpawningInStr = &"MP_HQ_AVAILABLE_IN"; + if ( level.kothMode ) + { + hqDestroyedInFriendlyStr = &"MP_HQ_DESPAWN_IN"; + hqDestroyedInEnemyStr = &"MP_HQ_DESPAWN_IN"; + } + else + { + hqDestroyedInFriendlyStr = &"MP_HQ_REINFORCEMENTS_IN"; + hqDestroyedInEnemyStr = &"MP_HQ_DESPAWN_IN"; + } + + spawn_first_radio(); + + objective_name = istring("objective"); + + while ( level.inPrematchPeriod ) + {wait(.05);}; + + wait 5; + + timerDisplay = []; + foreach ( team in level.teams ) + { + timerDisplay[team] = hud::createServerTimer( "objective", 1.4, team ); + timerDisplay[team] hud::setGamemodeInfoPoint(); + timerDisplay[team].label = hqSpawningInStr; + timerDisplay[team].font = "small"; + timerDisplay[team].alpha = 0; + timerDisplay[team].archived = false; + timerDisplay[team].hideWhenInMenu = true; + timerDisplay[team].hideWhenInKillcam = true; + timerDisplay[team].showplayerteamhudelemtospectator = true; + + thread hideTimerDisplayOnGameEnd( timerDisplay[team] ); + } + +// locationObjID = gameobjects::get_next_obj_id(); + +// objective_add( locationObjID, "invisible", (0,0,0) ); + + while( 1 ) + { + iPrintLn( &"MP_HQ_REVEALED" ); + sound::play_on_players( "mp_suitcase_pickup" ); + globallogic_audio::leader_dialog( "hq_located" ); +// globallogic_audio::leader_dialog( "move_to_new" ); + + level.radio.gameobject gameobjects::set_model_visibility( true ); + + level.hqRevealTime = gettime(); + + rcbombs = GetEntArray( "rcbomb","targetname" ); + radius = 75; + for( index = 0; index < rcbombs.size; index ++ ) + { + if( DistanceSquared( rcbombs[index], level.radio.origin ) < radius * radius ) + { + rcbombs[index] notify( "rcbomb_shutdown" ); + } + } + + if ( level.hqSpawnTime ) + { +// nextObjPoint = objpoints::create( "objpoint_next_hq", level.radio.origin + level.iconoffset, "all", "waypoint_targetneutral" ); +// nextObjPoint setWayPoint( true, "waypoint_targetneutral" ); +// objective_position( locationObjID, level.radio.trigorigin ); +// objective_icon( locationObjID, "waypoint_targetneutral" ); +// objective_state( locationObjID, "active" ); + + level.radio.gameobject gameobjects::set_visible_team( "any" ); + level.radio.gameobject gameobjects::set_flags( 1 ); + + updateObjectiveHintMessage( level.objectiveHintPrepareHQ ); + + foreach( team in level.teams ) + { + timerDisplay[team].label = hqSpawningInStr; + timerDisplay[team] setTimer( level.hqSpawnTime ); + timerDisplay[team].alpha = 1; + } + + wait level.hqSpawnTime; + +// objpoints::delete( nextObjPoint ); +// objective_state( locationObjID, "invisible" ); + level.radio.gameobject gameobjects::set_flags( 0 ); + globallogic_audio::leader_dialog( "hq_online" ); + } + + foreach( team in level.teams ) + { + timerDisplay[team].alpha = 0; + } + + waittillframeend; + + globallogic_audio::leader_dialog( "obj_capture" ); + updateObjectiveHintMessage( level.objectiveHintCaptureHQ ); + sound::play_on_players( "mpl_hq_cap_us" ); + + level.radio.gameobject gameobjects::enable_object(); + level.radio.gameobject.onUpdateUseRate =&onUpdateUseRate; + + level.radio.gameobject gameobjects::allow_use( "any" ); + level.radio.gameobject gameobjects::set_use_time( level.captureTime ); + level.radio.gameobject gameobjects::set_use_text( &"MP_CAPTURING_HQ" ); + + //objective_icon( locationObjID, "compass_waypoint_captureneutral" ); +// level.radio.gameobject gameobjects::set_2d_icon( "enemy", "compass_waypoint_captureneutral" ); +// level.radio.gameobject gameobjects::set_3d_icon( "enemy", "waypoint_captureneutral" ); + level.radio.gameobject gameobjects::set_visible_team( "any" ); + level.radio.gameobject gameobjects::set_model_visibility( true ); + + level.radio.gameobject.onUse =&onRadioCapture; + level.radio.gameobject.onBeginUse =&onBeginUse; + level.radio.gameobject.onEndUse =&onEndUse; + + level waittill( "hq_captured" ); + + ownerTeam = level.radio.gameobject gameobjects::get_owner_team(); + + if ( level.hqAutoDestroyTime ) + { + thread DestroyHQAfterTime( level.hqAutoDestroyTime, ownerTeam ); + foreach( team in level.teams ) + { + timerDisplay[team] setTimer( level.hqAutoDestroyTime ); + } + } + else + { + level.hqDestroyedByTimer = false; + } + + while( 1 ) + { + ownerTeam = level.radio.gameobject gameobjects::get_owner_team(); + + foreach( team in level.teams ) + { + updateObjectiveHintMessages( ownerTeam, level.objectiveHintDefendHQ, level.objectiveHintDestroyHQ ); + } + + level.radio.gameobject gameobjects::allow_use( "enemy" ); +// level.radio.gameobject gameobjects::set_2d_icon( "friendly", "compass_waypoint_defend" ); +// level.radio.gameobject gameobjects::set_3d_icon( "friendly", "waypoint_defend" ); +// level.radio.gameobject gameobjects::set_2d_icon( "enemy", "compass_waypoint_capture" ); +// level.radio.gameobject gameobjects::set_3d_icon( "enemy", "waypoint_capture" ); + + if ( !level.kothMode ) + level.radio.gameobject gameobjects::set_use_text( &"MP_DESTROYING_HQ" ); + + level.radio.gameobject.onUse =&onRadioDestroy; + + if ( level.hqAutoDestroyTime ) + { + foreach( team in level.teams ) + { + if ( team == ownerTeam ) + timerDisplay[team].label = hqDestroyedInFriendlyStr; + else + timerDisplay[team].label = hqDestroyedInEnemyStr; + + timerDisplay[team].alpha = 1; + } + } + + level thread dropAllAroundHQ(); + + level waittill( "hq_destroyed", destroy_team ); + + level.radio spawning::enable_influencers(false); + + if ( !level.kothMode || level.hqDestroyedByTimer ) + break; + + thread forceSpawnTeam( ownerTeam ); + + if ( isdefined( destroy_team ) ) + { + level.radio.gameobject gameobjects::set_owner_team( destroy_team ); + } + } + + level.radio.gameobject gameobjects::disable_object(); + level.radio.gameobject gameobjects::allow_use( "none" ); + level.radio.gameobject gameobjects::set_owner_team( "neutral" ); + level.radio.gameobject gameobjects::set_model_visibility( false ); + + level notify("hq_reset"); + + foreach( team in level.teams ) + { + timerDisplay[team].alpha = 0; + } + + spawn_next_radio(); + + wait .05; + + thread forceSpawnTeam( ownerTeam ); + + wait 3.0; + } +} + + +function hideTimerDisplayOnGameEnd( timerDisplay ) +{ + level waittill("game_ended"); + timerDisplay.alpha = 0; +} + + +function forceSpawnTeam( team ) +{ + players = level.players; + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + if ( !isdefined( player ) ) + continue; + + if ( player.pers["team"] == team ) + { + player notify( "force_spawn" ); + wait .1; + } + } +} + + +function onBeginUse( player ) +{ + ownerTeam = self gameobjects::get_owner_team(); + + if ( ownerTeam == "neutral" ) + { +// self.objPoints[player.pers["team"]] thread objpoints::start_flashing(); + player thread battlechatter::gametype_specific_battle_chatter( "hq_protect", player.pers["team"] ); + } + else + { +// foreach( team in level.teams ) +// { +// self.objPoints[team] thread objpoints::start_flashing(); +// } + player thread battlechatter::gametype_specific_battle_chatter( "hq_attack", player.pers["team"] ); + } +} + + +function onEndUse( team, player, success ) +{ +// foreach( team in level.teams ) +// { +// self.objPoints[team] thread objpoints::stop_flashing(); +// } + player notify( "event_ended" ); +} + + +function onRadioCapture( player ) +{ + capture_team = player.pers["team"]; + + /#print( "radio captured" );#/ + + string = &"MP_HQ_CAPTURED_BY"; + + level.useStartSpawns = false; + + thread give_capture_credit( self.touchList[capture_team], string ); + + oldTeam = gameobjects::get_owner_team(); + self gameobjects::set_owner_team( capture_team ); + if ( !level.kothMode ) + self gameobjects::set_use_time( level.destroyTime ); + + foreach( team in level.teams ) + { + if ( team == capture_team ) + { + thread util::printOnTeamArg( &"MP_HQ_CAPTURED_BY", team, player ); + globallogic_audio::leader_dialog( "hq_secured", team ); + thread sound::play_on_players( "mp_war_objective_taken", team ); + } + else + { + thread util::printOnTeam( &"MP_HQ_CAPTURED_BY_ENEMY", team ); + globallogic_audio::leader_dialog( "hq_enemy_captured", team ); + thread sound::play_on_players( "mp_war_objective_lost", team ); + } + } + + level thread awardHQPoints( capture_team ); + + level notify( "hq_captured" ); + player notify( "event_ended" ); +} + +function give_capture_credit( touchList, string ) +{ + time = getTime(); + wait .05; + util::WaitTillSlowProcessAllowed(); + + players = getArrayKeys( touchList ); + for ( i = 0; i < players.size; i++ ) + { + player_from_touchlist = touchList[players[i]].player; + player_from_touchlist challenges::capturedObjective( time, self ); // TODO: fix this when this game mode can be played, need to pass in the "trigger" not self which is the useObj + scoreevents::processScoreEvent( "hq_secure", player_from_touchlist ); + player_from_touchlist RecordGameEvent("capture"); + + level thread popups::DisplayTeamMessageToAll( string, player_from_touchlist ); + + if( isdefined(player_from_touchlist.pers["captures"]) ) + { + player_from_touchlist.pers["captures"]++; + player_from_touchlist.captures = player_from_touchlist.pers["captures"]; + } + + demo::bookmark( "event", gettime(), player_from_touchlist ); + player_from_touchlist AddPlayerStatWithGameType( "CAPTURES", 1 ); + } +} + +function dropAllToGround( origin, radius, stickyObjectRadius ) +{ + PhysicsExplosionSphere( origin, radius, radius, 0 ); + {wait(.05);}; + weapons::drop_all_to_ground( origin, radius ); + // grenades are now done in code when an entity they were on gets deleted +// weapons::drop_grenades_to_ground( origin, radius ); + supplydrop::dropCratesToGround( origin, radius ); + level notify( "drop_objects_to_ground", origin, stickyObjectRadius ); +} + +function dropAllAroundHQ( radio ) +{ + origin = level.radio.origin; + level waittill( "hq_reset" ); + dropAllToGround( origin, 100, 50 ); +} + +function onRadioDestroy( firstPlayer ) +{ + destroyed_team = firstPlayer.pers["team"]; + + touchList = self.touchList[destroyed_team]; + + touchListKeys = getArrayKeys( touchList ); + foreach( index in touchListKeys ) + { + player = touchList[index].player; + /#print( "radio destroyed" );#/ + + scoreevents::processScoreEvent( "hq_destroyed", player ); + player RecordGameEvent("destroy"); + player AddPlayerStatWithGameType( "DESTRUCTIONS", 1 ); + + if( isdefined(player.pers["destructions"]) ) + { + player.pers["destructions"]++; + player.destructions = player.pers["destructions"]; + } + } + + destroyTeamMessage = &"MP_HQ_DESTROYED_BY"; + otherTeamMessage = &"MP_HQ_DESTROYED_BY_ENEMY"; + + if ( level.kothMode ) + { + destroyTeamMessage = &"MP_HQ_CAPTURED_BY"; + otherTeamMessage = &"MP_HQ_CAPTURED_BY_ENEMY"; + } + + level thread popups::DisplayTeamMessageToAll( destroyTeamMessage, player ); + + foreach( team in level.teams ) + { + if ( team == destroyed_team ) + { + thread util::printOnTeamArg( destroyTeamMessage, team, player ); + globallogic_audio::leader_dialog( "hq_secured", team ); + } + else + { + thread util::printOnTeam( otherTeamMessage, team ); + globallogic_audio::leader_dialog( "hq_enemy_destroyed", team ); + } + } + + level notify( "hq_destroyed", destroyed_team ); + + if ( level.kothMode ) + level thread awardHQPoints( destroyed_team ); + + player notify( "event_ended" ); +} + + +function DestroyHQAfterTime( time, ownerTeam ) +{ + level endon( "game_ended" ); + level endon( "hq_reset" ); + + level.hqDestroyTime = gettime() + time * 1000; + level.hqDestroyedByTimer = false; + + wait time; + + globallogic_audio::leader_dialog( "hq_offline" ); + + level.hqDestroyedByTimer = true; + checkPlayerCount( ownerTeam ); + + level notify( "hq_destroyed" ); +} + +function checkPlayerCount( ownerTeam ) +{ + lastPlayerAlive = undefined; + players = level.players; + + for ( i = 0 ; i < players.size ; i++ ) + { + if ( players[i].team != ownerTeam ) + continue; + + if ( IsAlive( players[i] ) ) + { + if ( isdefined( lastPlayerAlive ) ) + { + return; // more than one player alive + } + lastPlayerAlive = players[i]; + } + } + if ( isdefined ( lastPlayerAlive ) ) + { + scoreevents::processScoreEvent( "defend_hq_last_man_alive", lastPlayerAlive ); + } +} + +function awardHQPoints( team ) +{ + level endon( "game_ended" ); + level endon( "hq_destroyed" ); + + level notify("awardHQPointsRunning"); + level endon("awardHQPointsRunning"); + + seconds = 5; + + while ( !level.gameEnded ) + { + globallogic_score::giveTeamScoreForObjective( team, seconds ); + for ( index = 0; index < level.players.size; index++ ) + { + player = level.players[index]; + + if ( player.pers["team"] == team ) + { + //scoreevents::processScoreEvent( "defend", player ); + } + } + + wait seconds; + } +} + +function koth_playerSpawnedCB() +{ + self.lowerMessageOverride = undefined; +} + +function CompareRadioIndexes( radio_a, radio_b ) +{ + script_index_a = radio_a.script_index; + script_index_b = radio_b.script_index; + + if( !isdefined(script_index_a) && !isdefined(script_index_b) ) + { + return false; + } + + if( !isdefined(script_index_a) && isdefined(script_index_b) ) + { +/# + println( "KOTH: Missing script_index on radio at " + radio_a.origin ); +#/ + return true; + } + + if( isdefined(script_index_a) && !isdefined(script_index_b) ) + { +/# + println( "KOTH: Missing script_index on radio at " + radio_b.origin ); +#/ + return false; + } + + if( script_index_a > script_index_b ) + { + return true; + } + + return false; +} + + +function getRadioArray() +{ + radios = getentarray( "hq_hardpoint", "targetname" ); + + if( !isdefined( radios ) ) + { + return undefined; + } + + swapped = true; + n = radios.size; + while ( swapped ) + { + swapped = false; + for( i = 0 ; i < n-1 ; i++ ) + { + if( CompareRadioIndexes(radios[i], radios[i+1]) ) + { + temp = radios[i]; + radios[i] = radios[i+1]; + radios[i+1] = temp; + swapped = true; + } + } + n--; + } + return radios; +} + + +function SetupRadios() +{ + maperrors = []; + + radios = getRadioArray(); + + if ( radios.size < 2 ) + { + maperrors[maperrors.size] = "There are not at least 2 entities with targetname \"radio\""; + } + + trigs = getentarray("radiotrigger", "targetname"); + for ( i = 0; i < radios.size; i++ ) + { + errored = false; + + radio = radios[i]; + radio.trig = undefined; + for ( j = 0; j < trigs.size; j++ ) + { + if ( radio istouching( trigs[j] ) ) + { + if ( isdefined( radio.trig ) ) + { + maperrors[maperrors.size] = "Radio at " + radio.origin + " is touching more than one \"radiotrigger\" trigger"; + errored = true; + break; + } + radio.trig = trigs[j]; + break; + } + } + + if ( !isdefined( radio.trig ) ) + { + if ( !errored ) + { + maperrors[maperrors.size] = "Radio at " + radio.origin + " is not inside any \"radiotrigger\" trigger"; + continue; + } + + // possible fallback (has been tested) + //radio.trig = spawn( "trigger_radius", radio.origin, 0, 128, 128 ); + //errored = false; + } + + assert( !errored ); + + radio.trigorigin = radio.trig.origin; + + visuals = []; + visuals[0] = radio; + + otherVisuals = getEntArray( radio.target, "targetname" ); + for ( j = 0; j < otherVisuals.size; j++ ) + { + visuals[visuals.size] = otherVisuals[j]; + } + + objective_name = istring("objective"); + + radio setUpNodes(); + + radio.gameObject = gameobjects::create_use_object( "neutral", radio.trig, visuals, (radio.origin - radio.trigorigin), objective_name ); + radio.gameObject gameobjects::disable_object(); + radio.gameObject gameobjects::set_model_visibility( false ); + radio.trig.useObj = radio.gameObject; + + radio setUpNearbySpawns(); + radio createRadioSpawnInfluencer(); + } + + if (maperrors.size > 0) + { + /# + println("^1------------ Map Errors ------------"); + for(i = 0; i < maperrors.size; i++) + println(maperrors[i]); + println("^1------------------------------------"); + + util::error("Map errors. See above"); + #/ + callback::abort_level(); + + return; + } + + level.radios = radios; + + level.prevradio = undefined; + level.prevradio2 = undefined; + + return true; +} + +function setUpNearbySpawns() +{ + spawns = level.spawn_all; + + for ( i = 0; i < spawns.size; i++ ) + { + spawns[i].distsq = distanceSquared( spawns[i].origin, self.origin ); + } + + // sort by distsq + for ( i = 1; i < spawns.size; i++ ) + { + thespawn = spawns[i]; + for ( j = i - 1; j >= 0 && thespawn.distsq < spawns[j].distsq; j-- ) + spawns[j + 1] = spawns[j]; + spawns[j + 1] = thespawn; + } + + first = []; + second = []; + third = []; + outer = []; + + thirdSize = spawns.size / 3; + for ( i = 0; i <= thirdSize; i++ ) + { + first[ first.size ] = spawns[i]; + } + for ( ; i < spawns.size; i++ ) + { + outer[ outer.size ] = spawns[i]; + if ( i <= (thirdSize*2) ) + second[ second.size ] = spawns[i]; + else + third[ third.size ] = spawns[i]; + } + + self.gameObject.nearSpawns = first; + self.gameObject.midSpawns = second; + self.gameObject.farSpawns = third; + self.gameObject.outerSpawns = outer; +} + +function setUpNodes() +{ + self.points = []; + temp = spawn( "script_model", ( 0, 0, 0 ) ); + + maxs = self.trig GetPointInBounds( 1, 1, 1 ); + self.node_radius = Distance( self.trig.origin, maxs ); + + points = util::PositionQuery_PointArray( self.trig.origin, 0, self.node_radius, 70, 128 ); + + foreach( point in points ) + { + temp.origin = point; + + if ( temp IsTouching( self.trig ) ) + { + self.points[ self.points.size ] = point; + } + } + + assert( self.points.size ); + temp delete(); +} + +function GetFirstRadio() +{ + radio = level.radios[ 0 ]; + + // old linear and "random" systems + level.prevradio2 = level.prevradio; + level.prevradio = radio; + level.prevRadioIndex = 0; + + // new shuffled system + ShuffleRadios(); + ArrayRemoveValue( level.radioSpawnQueue, radio ); + + return radio; +} + +function GetNextRadio() +{ + nextRadioIndex = (level.prevRadioIndex + 1) % level.radios.size; + radio = level.radios[ nextRadioIndex ]; + level.prevradio2 = level.prevradio; + level.prevradio = radio; + level.prevRadioIndex = nextRadioIndex; + + return radio; +} + +function PickRandomRadioToSpawn() +{ + level.prevRadioIndex = randomint( level.radios.size); + radio = level.radios[ level.prevRadioIndex ]; + level.prevradio2 = level.prevradio; + level.prevradio = radio; + + return radio; +} + +function ShuffleRadios() +{ + level.radioSpawnQueue = []; + + spawnQueue = ArrayCopy(level.radios); + total_left = spawnQueue.size; + + while( total_left > 0 ) + { + index = randomint( total_left ); + + valid_radios = 0; + for( radio = 0; radio < level.radios.size; radio++ ) + { + if ( !isdefined(spawnQueue[radio]) ) + continue; + + if ( valid_radios == index ) + { + // dont allow the last radio from the previous shuffle to be put first in the next + if ( level.radioSpawnQueue.size == 0 && isdefined( level.radio ) && level.radio == spawnQueue[radio] ) + continue; + + level.radioSpawnQueue[level.radioSpawnQueue.size] = spawnQueue[radio]; + spawnQueue[radio] = undefined; + break; + } + + valid_radios++; + } + + total_left--; + } +} + +// shuffled picking +function GetNextRadioFromQueue() +{ + if ( level.radioSpawnQueue.size == 0 ) + ShuffleRadios(); + + assert( level.radioSpawnQueue.size > 0 ); + + next_radio = level.radioSpawnQueue[0]; + ArrayRemoveIndex( level.radioSpawnQueue, 0 ); + + return next_radio; +} + +function GetCountOfTeamsWithPlayers(num) +{ + has_players = 0; + + foreach( team in level.teams ) + { + if ( num[team] > 0 ) + has_players++; + } + + return has_players; +} + +function GetPointCost( avgpos, origin ) +{ + avg_distance = 0; + total_error = 0; + distances = []; + + foreach( team, position in avgpos ) + { + distances[team] = Distance(origin, avgpos[team]); + avg_distance += distances[team]; + } + + avg_distance = avg_distance / distances.size; + + foreach( team, dist in distances ) + { + err = (distances[team] - avg_distance); + total_error += err * err; + } + + return total_error; +} + +function PickRadioToSpawn() +{ + // find average of positions of each team + // (medians would be better, to get rid of outliers...) + // and find the radio which has the least difference in distance from those two averages + + foreach( team in level.teams ) + { + avgpos[team] = (0,0,0); + num[team] = 0; + } + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if ( isalive( player ) ) + { + avgpos[ player.pers["team"] ] += player.origin; + num[ player.pers["team"] ]++; + } + } + + if ( GetCountOfTeamsWithPlayers(num) <= 1 ) + { + radio = level.radios[ randomint( level.radios.size) ]; + while ( isdefined( level.prevradio ) && radio == level.prevradio ) // so lazy + radio = level.radios[ randomint( level.radios.size) ]; + + level.prevradio2 = level.prevradio; + level.prevradio = radio; + + return radio; + } + + foreach( team in level.teams ) + { + if ( num[team] == 0 ) + { + avgpos[team] = undefined; + } + else + { + avgpos[team] = avgpos[team] / num[team]; + } + } + + bestradio = undefined; + lowestcost = undefined; + for ( i = 0; i < level.radios.size; i++ ) + { + radio = level.radios[i]; + + // (purposefully using distance instead of distanceSquared) + cost = GetPointCost( avgpos, radio.origin ); + + if ( isdefined( level.prevradio ) && radio == level.prevradio ) + { + continue; + } + if ( isdefined( level.prevradio2 ) && radio == level.prevradio2 ) + { + if ( level.radios.size > 2 ) + continue; + else + cost += 512 * 512; + } + + if ( !isdefined( lowestcost ) || cost < lowestcost ) + { + lowestcost = cost; + bestradio = radio; + } + } + assert( isdefined( bestradio ) ); + + level.prevradio2 = level.prevradio; + level.prevradio = bestradio; + + return bestradio; +} + +function onRoundSwitch() +{ + game["switchedsides"] = !game["switchedsides"]; +} + + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( !isPlayer( attacker ) || (!self.touchTriggers.size && !attacker.touchTriggers.size) || attacker.pers["team"] == self.pers["team"] ) + return; + + medalGiven = false; + scoreEventProcessed = false; + + if ( attacker.touchTriggers.size ) + { + triggerIds = getArrayKeys( attacker.touchTriggers ); + ownerTeam = attacker.touchTriggers[triggerIds[0]].useObj.ownerTeam; + + team = attacker.pers["team"]; + if ( team == ownerTeam || ownerTeam == "neutral" ) + { + if ( !medalGiven ) + { + if( isdefined(attacker.pers["defends"]) ) + { + attacker.pers["defends"]++; + attacker.defends = attacker.pers["defends"]; + } + + attacker medals::defenseGlobalCount(); + medalGiven = true; + attacker thread challenges::killedBaseDefender( attacker.touchTriggers[triggerIds[0]] ); + attacker RecordGameEvent("return"); + } + + attacker challenges::killedZoneAttacker( weapon ); + if (team != ownerTeam ) + { + scoreevents::processScoreEvent( "kill_enemy_while_capping_hq", attacker, undefined, weapon ); + } + else + { + scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon ); + } + self RecordKillModifier("assaulting"); + scoreEventProcessed = true; + } + else + { + if ( !medalGiven ) + { + attacker medals::offenseGlobalCount(); + medalGiven = true; + attacker thread challenges::killedBaseOffender( attacker.touchTriggers[triggerIds[0]], weapon ); + } + scoreevents::processScoreEvent( "kill_enemy_while_capping_hq", attacker, undefined, weapon ); + self RecordKillModifier("defending"); + scoreEventProcessed = true; + } + } + + if ( self.touchTriggers.size ) + { + triggerIds = getArrayKeys( self.touchTriggers ); + ownerTeam = self.touchTriggers[triggerIds[0]].useObj.ownerTeam; + + team = self.pers["team"]; + if ( team == ownerTeam ) + { + if ( !medalGiven ) + { + attacker medals::offenseGlobalCount(); + attacker thread challenges::killedBaseOffender( self.touchTriggers[triggerIds[0]], weapon ); + medalGiven = true; + } + if ( !scoreEventProcessed ) + { + scoreevents::processScoreEvent( "killed_defender", attacker, undefined, weapon ); + self RecordKillModifier("defending"); + scoreEventProcessed = true; + } + } + else + { + if ( !medalGiven ) + { + if( isdefined(attacker.pers["defends"]) ) + { + attacker.pers["defends"]++; + attacker.defends = attacker.pers["defends"]; + } + + attacker medals::defenseGlobalCount(); + medalGiven = true; + attacker thread challenges::killedBaseDefender( self.touchTriggers[triggerIds[0]] ); + attacker RecordGameEvent("return"); + } + + if ( !scoreEventProcessed ) + { + attacker challenges::killedZoneAttacker( weapon ); + scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon ); + self RecordKillModifier("assaulting"); + scoreEventProcessed = true; + } + } + + if ( scoreEventProcessed == true ) + { + attacker killWhileContesting( self.touchTriggers[triggerIds[0]].useObj ); + } + } + + +} + +function killWhileContesting( radio ) +{ + self notify( "killWhileContesting" ); + self endon( "killWhileContesting" ); + self endon( "disconnect" ); + + killTime = getTime(); + playerteam = self.pers["team"]; + if ( !isdefined ( self.clearEnemyCount ) ) + { + self.clearEnemyCount = 0; + } + + self.clearEnemyCount++; + + radio waittill( "state_change" ); + + if (playerteam != self.pers["team"] || ( isdefined( self.spawntime ) && ( killTime < self.spawntime ) ) ) + { + self.clearEnemyCount = 0; + return; + } + if ( radio.ownerTeam != playerteam && radio.ownerTeam != "neutral" ) + { + self.clearEnemyCount = 0; + return; + } + + if ( self.clearEnemyCount >= 2 && killTime + 200 > getTime() ) + { + scoreevents::processScoreEvent( "clear_2_attackers", self ); + } + self.clearEnemyCount = 0; +} + + +function onEndGame( winningTeam ) +{ + for ( i = 0; i < level.radios.size; i++ ) + { + level.radios[i].gameobject gameobjects::allow_use( "none" ); + } +} + +function createRadioSpawnInfluencer() +{ + // this affects both teams + self spawning::create_influencer( "hq_large", self.gameobject.curOrigin, 0 ); + self spawning::create_influencer( "hq_small", self.gameobject.curOrigin, 0 ); + + // turn it off for now + self spawning::enable_influencers(false); +} + +function onUpdateUseRate() +{ + if ( !isdefined( self.currentContenderCount ) ) + { + self.currentContenderCount = 0; + } + numOthers = gameobjects::get_num_touching_except_team( self.ownerteam ); // 0 + numOwners = self.numTouching[self.ownerteam]; // 1 + previousState = self.currentContenderCount; + + if ( numOthers == 0 && numOwners == 0 ) + { + self.currentContenderCount = 0; + } + else + { + if ( self.ownerteam == "neutral" ) + { + numOtherClaim = gameobjects::get_num_touching_except_team( self.claimteam ); //1 + + if ( numOtherClaim > 0 ) + { + self.currentContenderCount = 2; + } + else + { + self.currentContenderCount = 1; + } + } + else + { + if ( numOthers > 0 ) + { + self.currentContenderCount = 1; + } + else + { + self.currentContenderCount = 0; + } + } + } + + if ( self.currentContenderCount != previousState ) + { + self notify( "state_change" ); + } +} \ No newline at end of file diff --git a/mp/gametypes/hun.csc b/mp/gametypes/hun.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/hun.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/hun.gsc b/mp/gametypes/hun.gsc new file mode 100644 index 0000000..69d0f27 --- /dev/null +++ b/mp/gametypes/hun.gsc @@ -0,0 +1,425 @@ +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_supplydrop; + +/* + Hunted + Objective: Score points by eliminating other players. Packages are dropped randomly + which contain weapons and ammo. + Map ends: When one player reaches the score limit, or time limit is reached + Respawning: No wait / Away from other players + + Level requirements + ------------------ + Spawnpoints: + classname mp_dm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of enemies at the time of spawn. + Players generally spawn away from enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + Because Deathmatch doesn't have teams with regard to gameplay or scoring, this effectively sets the available weapons. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals +*/ + +/*QUAKED mp_dm_spawn (1.0 0.5 0.0) (-16 -16 0) (16 16 72) +Players spawn away from enemies at one of these positions.*/ + +#precache( "string", "OBJECTIVES_DM" ); +#precache( "string", "OBJECTIVES_DM_SCORE" ); +#precache( "string", "OBJECTIVES_DM_HINT" ); + +// smg +#precache( "string", "WEAPON_SMG_STANDARD" ); +//assault +#precache( "string", "WEAPON_AR_STANDARD" ); +// LMG +#precache( "string", "WEAPON_LMG_LIGHT" ); +// shotgun +#precache( "string", "WEAPON_SHOTGUN_PUMP" ); +// pistol +#precache( "string", "WEAPON_PISTOL_STANDARD" ); + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 50000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 0, 0, 1440 ); + + level.scoreRoundWinBased = true; + level.resetPlayerScoreEveryRound = true; + level.onStartGameType =&onStartGameType; + level.giveCustomLoadout =&giveCustomLoadout; + + level.heliTime = GetGametypeSetting( "objectiveSpawnTime" ); + + gameobjects::register_allowed_gameobject( "dm" ); + + game["dialog"]["gametype"] = "ffa_start"; + game["dialog"]["gametype_hardcore"] = "hcffa_start"; + game["dialog"]["offense_obj"] = "generic_boost"; + game["dialog"]["defense_obj"] = "generic_boost"; + + // Sets the scoreboard columns and determines with data is sent across the network + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "kdratio", "headshots" ); + +} + + +function onStartGameType() +{ + setClientNameMode("auto_change"); + + util::setObjectiveText( "allies", &"OBJECTIVES_DM" ); + util::setObjectiveText( "axis", &"OBJECTIVES_DM" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_DM" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_DM" ); + } + else + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_DM_SCORE" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_DM_SCORE" ); + } + util::setObjectiveHintText( "allies", &"OBJECTIVES_DM_HINT" ); + util::setObjectiveHintText( "axis", &"OBJECTIVES_DM_HINT" ); + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::add_spawn_points( "allies", "mp_dm_spawn" ); + spawnlogic::add_spawn_points( "axis", "mp_dm_spawn" ); + spawning::updateAllSpawnPoints(); + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + // use the new spawn logic from the start + level.useStartSpawns = false; + + level.displayRoundEndText = false; + + level thread onScoreCloseMusic(); + + if ( !util::isOneRound() ) + { + level.displayRoundEndText = true; + } + + level.heliOwner = spawn( "script_origin", (0,0,0) ); + initDropLocations(); + registerCrates(); + thread crateDropper(); +} + + +function onEndGame( winningPlayer ) +{ + if ( isdefined( winningPlayer ) && isPlayer( winningPlayer ) ) + [[level._setPlayerScore]]( winningPlayer, winningPlayer [[level._getPlayerScore]]() + 1 ); +} + +// ******************************************************************* +// Custom Loadout +// ******************************************************************* +function giveCustomLoadout() +{ + self takeAllWeapons(); + self ClearPerks(); + + weapon = GetWeapon( "pistol_standard" ); + + self.primaryWeapon = weapon; + self.lethalGrenade = GetWeapon( "hatchet" ); + self.tacticalGrenade = undefined; + + self GiveWeapon( weapon ); + self GiveWeapon( level.weaponBaseMelee ); + self GiveWeapon( level.weaponBaseMeleeHeld ); + self GiveWeapon( self.lethalGrenade ); + self SetWeaponAmmoStock( weapon, 0 ); + self SetWeaponAmmoClip( weapon, 5 ); + self switchToWeapon( weapon ); + + self setSpawnWeapon( weapon ); + + return weapon; +} + +function onScoreCloseMusic() +{ + while( !level.gameEnded ) + { + scoreLimit = level.scoreLimit; + scoreThreshold = scoreLimit * .9; + + for(i=0;i= scoreThreshold ) + { + thread globallogic_audio::set_music_on_team( "timeOut" ); + return; + } + } + + wait(.5); + } +} + +function inside_bounds( node_origin, bounds ) +{ + mins = level.mapCenter - bounds; + maxs = level.mapCenter + bounds; + + if ( node_origin[0] > maxs[0] ) + return false; + if ( node_origin[0] < mins[0] ) + return false; + if ( node_origin[1] > maxs[1] ) + return false; + if ( node_origin[1] < mins[1] ) + return false; + + return true; +} + +function initDropLocations() +{ + scalar = 0.8; + + bound = ( level.spawnMaxs - level.mapCenter ) * scalar ; + +// all_nodes = getallnodes(); + +// center = (0,0,0); + +// foreach( node in all_nodes ) +// { +// center = center + node.origin; +// } +// +// center = center / all_nodes.size; + +///# +// mins = level.mapCenter - bound; +// maxs = level.mapCenter + bound; +// +// time = 10000; +// box( level.mapCenter, -1 * bound, bound, 0, (1,1,0), 1, 1, time ); +//// sphere( center, 20, (1,1,0), 1, true, 10, time ); +// sphere( level.mapCenter, 20, (1,1,1), 1, true, 10, time ); +// sphere( mins, 20, (0,1,0), 1, true, 10, time ); +// sphere( maxs, 20, (1,0,0), 1, true, 10, time ); +// sphere( level.spawnMins, 20, (0,1,0), 1, true, 10, time ); +// sphere( level.spawnMaxs, 20, (1,0,0), 1, true, 10, time ); +//#/ + + // get path nodes using the radius function to help trim out the nodes around the bounds +// possible_nodes = GetNodesInRadius( level.mapCenter, Length( bound ), 0 ,level.spawnMaxs[2], "Path" ); + possible_nodes = getallnodes(); + nodes = []; + + count = 0; + + foreach( node in possible_nodes ) + { + if ( inside_bounds( node.origin, bound ) ) + { + nodes[nodes.size] = node; + +// debugstar( node.origin, time, ( 1, 0, 1 ) ); + count++; + } + } + + level.dropNodes = nodes; +} + +function registerCrates() +{ + // weapons + + // smg + + supplydrop::registerCrateType("hunted", "weapon", "smg_standard", 1, &"WEAPON_SMG_STANDARD", undefined,&giveHuntedWeapon,&huntedCrateLandOverride ); + + // assault + supplydrop::registerCrateType("hunted", "weapon", "ar_standard", 1, &"WEAPON_AR_STANDARD", undefined,&giveHuntedWeapon,&huntedCrateLandOverride ); + + // LMG + supplydrop::registerCrateType("hunted", "weapon", "lmg_light", 1, &"WEAPON_LMG_LIGHT", undefined,&giveHuntedWeapon,&huntedCrateLandOverride ); + + // shotgun + supplydrop::registerCrateType("hunted", "weapon", "shotgun_pump", 1, &"WEAPON_SHOTGUN_PUMP", undefined,&giveHuntedWeapon,&huntedCrateLandOverride ); + + + // pistol + supplydrop::registerCrateType("hunted", "weapon", "pistol_standard", 1, &"WEAPON_PISTOL_STANDARD", undefined,&giveHuntedWeapon,&huntedCrateLandOverride ); + + + // killstreaks +/* + // NOTE: precache strings in the top of the file + supplydrop::registerCrateType("hunted", "killstreak", "radar", 4, &"KILLSTREAK_RADAR_CRATE",&giveCrateKillstreak,&huntedCrateLandOverride ); + supplydrop::registerCrateType("hunted", "killstreak", "autoturret", 3, &"KILLSTREAK_AUTO_TURRET_CRATE",&giveCrateKillstreak,&huntedCrateLandOverride ); + supplydrop::registerCrateType("hunted", "killstreak", "remote_missile", 4, &"KILLSTREAK_REMOTE_MISSILE_CRATE",&giveCrateKillstreak,&huntedCrateLandOverride ); + supplydrop::registerCrateType("hunted", "killstreak", "planemortar", 4, &"KILLSTREAK_PLANE_MORTAR_CRATE",&giveCrateKillstreak,&huntedCrateLandOverride ); + supplydrop::registerCrateType("hunted", "killstreak", "rcbomb", 4, &"KILLSTREAK_RCBOMB_CRATE",&giveCrateKillstreak,&huntedCrateLandOverride ); + supplydrop::registerCrateType("hunted", "killstreak", "radardirection", 2, &"KILLSTREAK_SATELLITE_CRATE",,&giveCrateKillstreak,&huntedCrateLandOverride ); + supplydrop::registerCrateType("hunted", "killstreak", "inventory_ai_tank_drop", 1, &"KILLSTREAK_AI_TANK_CRATE",&giveCrateKillstreak,&huntedCrateLandOverride ); +*/ + + supplydrop::setCategoryTypeWeight( "hunted", "weapon", 4 ); + supplydrop::setCategoryTypeWeight( "hunted", "killstreak", 1 ); + + + supplydrop::advancedFinalizeCrateCategory( "hunted" ); +} + +function giveHuntedWeapon( weapon ) +{ + if ( isdefined( self.primaryWeapon ) ) + { + self TakeWeapon(self.primaryWeapon); + } + + self.primaryWeapon = weapon; + self GiveWeapon(weapon); + self switchToWeapon(weapon); + self SetWeaponAmmoStock( Weapon, 0 ); +} + +function giveHuntedLethalGrenade( weapon ) +{ + if ( self.lethalGrenade == weapon ) + { + currStock = self GetAmmoCount( weapon ); + + self setweaponammostock( weapon, currStock + 1 ); + return; + } + + if ( isdefined( self.lethalGrenade ) ) + { + self takeweapon(self.lethalGrenade); + } + + self.lethalGrenade = weapon; + self GiveWeapon(weapon); + self setOffhandPrimaryClass( weapon ); +} + +function giveHuntedTacticalGrenade( weapon ) +{ + if ( self.tacticalGrenade == weapon ) + { + currStock = self GetAmmoCount( weapon ); + + self setweaponammostock( weapon, currStock + 1 ); + return; + } + + if ( isdefined( self.tacticalGrenade ) ) + { + self takeweapon(self.tacticalGrenade); + } + + self.tacticalGrenade = weapon; + self GiveWeapon(weapon); + self setOffhandSecondaryClass( weapon ); +} + +function huntedCrateLandOverride( crate, category, owner, team ) +{ + crate.visibleToAll = true; + crate supplydrop::crateActivate(); + + crate thread supplydrop::crateUseThink(); + crate thread supplydrop::crateUseThinkOwner(); + + supplydrop::default_land_function( crate, category, owner, team ); +} + +function getCrateDropOrigin() +{ + node = undefined; + time = 10000; + + while ( !isdefined(node) ) + { + random_index = RandomInt( level.dropNodes.size ); + + if ( !isdefined(level.dropNodes[random_index]) ) + continue; + + node_origin = level.dropNodes[random_index].origin; + + if ( !bulletTracePassed( node_origin + (0,0,1000), node_origin, false, undefined ) ) + { + level.dropNodes[random_index] = undefined; +// debugstar( node_origin, time, ( 1, 0, 0 ) ); + continue; + } + + node = level.dropNodes[random_index]; + break; + } + + return node.origin; +} + +function crateDropper() +{ + wait_time = level.heliTime; + time = 10000; + while (1) + { + wait( wait_time ); + origin = getCrateDropOrigin(); +// debugstar( origin, time, ( 1, 0, 1 ) ); + self thread supplydrop::heliDeliverCrate( origin, "hunted", level.heliOwner, "free", 0, 0 ); + } +} \ No newline at end of file diff --git a/mp/gametypes/infect.csc b/mp/gametypes/infect.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/infect.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/infect.gsc b/mp/gametypes/infect.gsc new file mode 100644 index 0000000..e481441 --- /dev/null +++ b/mp/gametypes/infect.gsc @@ -0,0 +1,1442 @@ +#using scripts\shared\flag_shared; +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\util_shared; +#using scripts\shared\abilities\_ability_util; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_globallogic_ui; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\_spectating; +#using scripts\mp\gametypes\_wager; + +#using scripts\mp\bots\_bot; + +#using scripts\mp\_teamops; +#using scripts\mp\_util; +#using scripts\mp\teams\_teams; + +/* + Infect - Infected + Attackers objective: Infect all Defenders + Defenders objective: Kill / Avoid Attackers and survive until time limit is reached + Respawning: No wait / Near teammates for Attackers, Defenders immediately respawned as Attackers when killed +*/ + +#precache( "string", "OBJECTIVES_INFECT" ); +#precache( "string", "OBJECTIVES_INFECT_HINT" ); +#precache( "string", "MP_DRAFT_STARTS_IN" ); +#precache( "string", "MP_GOT_INFECTED" ); +#precache( "string", "MPUI_INFECTED" ); +#precache( "string", "MPUI_SURVIVORS" ); +#precache( "string", "MP_SURVIVORS_ELIMINATED" ); +#precache( "string", "MP_INFECTED_ELIMINATED" ); +#precache( "string", "MP_INFECTED_TIME_EXTENDED" ); + +function main() +{ + globallogic::init(); + + level.isInfectMode = true; + + level.weapon_FIRST_INFECTED_PRIMARY_WEAPON = GetWeapon( "pistol_standard" ); + level.weapon_INFECTED_PRIMARY_WEAPON = GetWeapon( "melee_boneglass" ); + level.weapon_INFECTED_PRIMARY_GRENADE_WEAPON = GetWeapon( "hatchet" ); + + util::registerRoundSwitch( 0, 9 ); + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 50000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false ); + level.teamScorePerKill = GetGametypeSetting( "teamScorePerKill" ); + level.teamScorePerDeath = GetGametypeSetting( "teamScorePerDeath" ); + level.teamScorePerHeadshot = GetGametypeSetting( "teamScorePerHeadshot" ); + level.teamBased = true; + level.overrideTeamScore = true; + level.onStartGameType =&onStartGameType; + level.onEndGame = &onEndGame; + level.onSpawnPlayer =&onSpawnPlayer; + level.onRoundEndGame =&onRoundEndGame; + level.onRoundSwitch =&onRoundSwitch; + level.onPlayerKilled =&onPlayerKilled; + level.playerGetWeaponScavengeAmmo = &playerGetWeaponScavengeAmmo; + level.getTimeLimit = &getTimeLimit; + level.gameModeWeaponDropped = &weaponDropped; +// level.gametypeRoundEndScoreHud =&gametypeRoundEndScoreHud; +// level.onCloneSelectWeapon = &onCloneSelectWeapon; + + callback::on_connect( &onPlayerConnect ); + callback::on_disconnect( &onPlayerDisconnect ); + callback::on_joined_team( &onPlayerJoinedTeam ); + callback::on_joined_spectate( &onPlayerJoinedSpectate ); + + gameobjects::register_allowed_gameobject( level.gameType ); + +// globallogic_audio::set_leader_gametype_dialog ( "startTeamDeathmatch", "hcStartTeamDeathmatch", "gameBoost", "gameBoost" ); + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "infects", "deaths", "assists" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "infects", "assists" ); + + level.giveCustomLoadout =&giveCustomLoadout; + level.getAutoAssignTeamName = &getAutoAssignTeamName; + level.onTimeLimit =&onTimeLimit; + level.mayDropWeapon = &mayDropWeapon; + + infect_perks = []; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_longersprint";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_quieter";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_loudenemies";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_movefaster";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_jetnoradar";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_jetquiet";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_fallheight";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_fastladderclimb";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_fastmantle";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_fastreload";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_detectexplosive";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_bulletaccuracy";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_stalker";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_jetcharger";; + if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_overcharge";; + level.infect_perks = infect_perks; + + infect_gadgets = []; + if ( !isdefined( infect_gadgets ) ) infect_gadgets = []; else if ( !IsArray( infect_gadgets ) ) infect_gadgets = array( infect_gadgets ); infect_gadgets[infect_gadgets.size]="gadget_camo";; + if ( !isdefined( infect_gadgets ) ) infect_gadgets = []; else if ( !IsArray( infect_gadgets ) ) infect_gadgets = array( infect_gadgets ); infect_gadgets[infect_gadgets.size]="gadget_clone";; + if ( !isdefined( infect_gadgets ) ) infect_gadgets = []; else if ( !IsArray( infect_gadgets ) ) infect_gadgets = array( infect_gadgets ); infect_gadgets[infect_gadgets.size]="gadget_armor";; + level.infect_gadgets = infect_gadgets; +} + +function onStartGameType() +{ + setClientNameMode("auto_change"); + + game["defenders"] = "allies"; + game["attackers"] = "axis"; + + if ( !IsDefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + +// setCustomTeamNames(); + + level.displayRoundEndText = false; + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + foreach ( team in level.teams ) + { + util::setObjectiveText( team, &"OBJECTIVES_INFECT" ); + util::setObjectiveHintText( team, &"OBJECTIVES_INFECT_HINT" ); + util::setObjectiveScoreText( team, &"OBJECTIVES_INFECT" ); + + spawnlogic::add_spawn_points( team, "mp_tdm_spawn" ); + spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) ); + } + + spawning::updateAllSpawnPoints(); + + level.spawn_start = []; + + foreach ( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array( spawning::getTDMStartSpawnName(team) ); + } + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + if ( !util::isOneRound() ) + { + level.displayRoundEndText = true; + if( level.scoreRoundWinBased ) + { + globallogic_score::resetTeamScores(); + } + } + + level.infect_choseFirstInfected = false; + level.infect_choosingFirstInfected = false; + level.infect_allowSuicide = false; + level.infect_awardedFinalSurvivor = false; + + level.infect_players = []; + + initHud(); + + /# level thread devgui_infect(); #/ + + // gametype is user-facing as FFA so we need to use FFA bot-population logic + maxFree = GetDvarInt( "bot_maxFree", 0 ); + level thread bot::monitor_bot_population( maxFree ); + + level thread abortedCountdownCleanup(); + level thread timeExtendedCleanup(); +} + +function setCustomTeamNames() +{ + setTeamCustomName( game["attackers"], "MPUI_INFECTED" ); + setTeamCustomName( game["defenders"], "MPUI_SURVIVORS" ); +} + +function setTeamCustomName( team, name ) +{ + customTeamDvar = "g_customTeamName_" + team; + if ( GetDvarString( customTeamDvar ) == "" ) + { + SetDvar( customTeamDvar, name ); + } +} + +function onEndGame( winningTeam ) +{ + if ( !util::isOneRound() && !util::isLastRound() ) + { + // give a win/loss for each round played + infectUpdateWinLossStats( winningTeam ); + } +} + +function infectUpdateWinLossStats( winningTeam ) +{ + players = level.players; + + for ( i = 0; i < players.size; i++ ) + { + if ( !isdefined( players[i].pers["team"] ) ) + continue; + + if ( level.hostForcedEnd && players[i] IsHost() ) + continue; + + if ( winningTeam == "tie" ) + globallogic_score::updateTieStats( players[i] ); + else if ( players[i].pers["team"] == winningTeam ) + globallogic_score::updateWinStats( players[i] ); + else + { + // need to add condition for arena late join loss prevention + if ( level.rankedMatch && !level.leagueMatch && ( players[i].pers["lateJoin"] === true ) ) + { + globallogic_score::updateLossLateJoinStats( players[i] ); + } + + if ( !level.disableStatTracking ) + { + players[i] SetDStat( "playerstatslist", "cur_win_streak", "StatValue", 0 ); + } + } + } +} + +function initHud() +{ + level.infect_timerDisplay = hud::createServerTimer( "objective", 1.5 ); + level.infect_timerDisplay hud::setPoint( "CENTER", undefined, 0, 50 ); + level.infect_timerDisplay.label = &"MP_DRAFT_STARTS_IN"; + level.infect_timerDisplay.alpha = 0; + level.infect_timerDisplay.archived = false; + level.infect_timerDisplay.hideWhenInMenu = true; + + level.infect_timeExtendedDisplay = hud::createServerFontString( "objective", 1.5 ); + level.infect_timeExtendedDisplay hud::setPoint( "CENTER", undefined, 0, 50 ); + level.infect_timeExtendedDisplay.label = &"MP_INFECTED_TIME_EXTENDED"; + level.infect_timeExtendedDisplay.alpha = 0; + level.infect_timeExtendedDisplay.archived = false; + level.infect_timeExtendedDisplay.hideWhenInMenu = true; +} + +function onPlayerConnect() +{ + self.infect_firstSpawn = true; + self.infect_joinedAtStart = level.inPrematchPeriod; + + if ( self.sessionteam != "spectator" ) + { + self.pers["needteam"] = 1; + } + + playerXuid = self GetXUID(); + if ( IsDefined( level.infect_players[playerXuid] ) ) + { + self.infect_rejoined = true; + } + + self.noDrowning = true; +} + +function onPlayerJoinedTeam() +{ + if ( self.team == game["attackers"] ) + { + self.disableClassSelection = true; + } + else + { + self.disableClassSelection = undefined; + } +} + +function getAutoAssignTeamName( player, comingFromMenu ) +{ + if ( !comingFromMenu && player.sessionteam == "spectator" ) + { + teamName = "spectator"; + } + else if ( ( isdefined( level.infect_forceAssignDefender ) && level.infect_forceAssignDefender ) ) + { + level.infect_forceAssignDefender = undefined; + teamName = game["defenders"]; + level thread delayedForceSpawnDefenders(); + } + else if ( ( isdefined( player.infect_rejoined ) && player.infect_rejoined ) || ( isdefined( level.infect_choseFirstInfected ) && level.infect_choseFirstInfected ) ) + { + teamName = game["attackers"]; + } + else + { + teamName = game["defenders"]; + } + + return teamName; +} + +function playerWaitForStreamer() +{ + started_waiting = GetTime(); + + // try to let them finish loading the level + while ( ( !self isStreamerReady( -1, true ) ) && started_waiting + 90000 > GetTime() ) + { + {wait(.05);}; + } +} + +function onSpawnPlayer(predictedSpawn) +{ + if ( level.useStartSpawns && !level.inGracePeriod && !level.playerQueuedRespawn ) + { + level.useStartSpawns = false; + } + + updateTeamScores(); + + if ( self.team == game["attackers"] ) + { + sanitizeInfectedLoadouts(); + } + + // let the first spawned player kick this off + if ( !level.infect_choosingFirstInfected ) + { + level.infect_choosingFirstInfected = true; + level thread chooseFirstInfected(); + } + + spawning::onSpawnPlayer(predictedSpawn); +} + +function onRoundSwitch() +{ + game["switchedsides"] = !game["switchedsides"]; +} + +function onRoundEndGame( roundWinner ) +{ + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, game["roundswon"][team] ); + } + } + + return [[level.determineWinner]](); +} + +function playerHasParticipated() +{ + return ( self.pers["time_played_moving"] > 0 ); +} + +function teamCallout( team, calloutMessage, calloutPlayer ) +{ + players = getPlayersOnTeam( team ); + foreach ( player in players ) + { + player LUINotifyEvent( &"player_callout", 2, calloutMessage, calloutPlayer ); + } +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + processKill = false; + wasSuicide = false; + + if ( self.team == game["defenders"] && IsDefined( attacker ) ) + { + if ( level.friendlyfire > 0 && isdefined( attacker.team ) && attacker.team == self.team ) + { + processkill = false; + } + else if ( IsPlayer( attacker ) && attacker != self ) + { + processKill = true; + } + else if ( level.infect_allowSuicide && ( attacker == self || !isPlayer( attacker ) ) ) + { + processKill = true; + wasSuicide = true; + } + } + + if ( !processKill ) + { + return; + } + + if ( !wasSuicide ) + { + // regular attacker reward + scoreevents::processScoreEvent( "infected_survivor", attacker, self, weapon ); + + if ( isdefined( attacker.pers["infects"] ) ) + { + attacker.pers["infects"] = attacker.pers["infects"] + 1; + attacker.infects = attacker.pers["infects"]; + } + attacker AddPlayerStatWithGameType( "INFECTS", 1 ); + } + + level thread handlePlayerDeath( self, wasSuicide ); +} + +function handlePlayerDeath( victim, wasSuicide ) +{ + level endon( "game_ended" ); + + waittillframeend; // give laststand a chance to kick in + + if ( isdefined( victim.laststand ) ) + { + result = victim playerWaitForLastStand(); + if ( result === "player_input_revive" ) + { + return; + } + } + + {wait(.05);}; // allows obituary to have expected team colors (i.e. Infected as enemy, Survivor as friendly) + + if ( IsDefined( victim ) ) + { + level thread displayTimeExtended(); + switchPlayerToInfectedTeam( victim ); + sanitizeInfectedLoadouts(); + } + + // generic messages/sounds, and reward survivors + totalDefenders = [[level._getTeamScore]]( game["defenders"] ); + if ( totalDefenders > 1 && IsDefined( victim ) ) + { + sound::play_on_players( "mpl_flagget_sting_enemy", game["defenders"] ); + sound::play_on_players( "mpl_flagget_sting_friend", game["attackers"] ); + + teamCallout( game["defenders"], &"MP_GOT_INFECTED", victim.entnum ); + + if ( !wasSuicide ) + { + teamCallout( game["attackers"], &"SCORE_INFECTED_SURVIVOR", victim.entnum ); + + survivors = getPlayersOnTeam( game["defenders"] ); + foreach ( survivor in survivors ) + { + if ( survivor != victim && survivor playerHasParticipated() ) + { + survivor scoreevents::processScoreEvent( "survivor_still_alive", survivor ); + } + } + } + } + // inform/reward last + else if ( totalDefenders == 1 ) + { + onFinalSurvivor(); + } + // infected win + else if ( totalDefenders == 0 ) + { + onSurvivorsEliminated(); + } +} + +function playerWaitForLastStand() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + return self util::waittill_any_return( "player_input_revive", "death" ); +} + +function onFinalSurvivor() +{ + if ( ( isdefined( level.infect_announcedFinalSurvivor ) && level.infect_announcedFinalSurvivor ) ) + { + return; + } + + sound::play_on_players( "mpl_ballreturn_sting" ); + + finalSurvivor = getPlayersOnTeam( game["defenders"] )[0]; + + if ( !level.infect_awardedFinalSurvivor ) + { + finalSurvivor playerFullyChargeHeroWeapon(); + + if ( finalSurvivor.infect_joinedAtStart && finalSurvivor playerHasParticipated() ) + { + finalSurvivor scoreevents::processScoreEvent( "final_survivor", finalSurvivor ); + } + level.infect_awardedFinalSurvivor = true; + } + + LUINotifyEvent( &"player_callout", 2, &"SCORE_FINAL_SURVIVOR", finalSurvivor.entnum ); + + doFinalSurvivorUAV = GetDvarInt( "scr_infect_finaluav" ); + if ( doFinalSurvivorUAV ) + { + level thread finalSurvivorUAV( finalSurvivor ); + } + + level.infect_announcedFinalSurvivor = true; +} + +function playerFullyChargeHeroWeapon() +{ + if ( !IsDefined( self.heroWeapon ) ) + { + return; + } + + heroWeaponSlot = self GadgetGetSlot( self.heroWeapon ); + if ( self GadgetIsReady( heroWeaponSlot ) ) + { + return; + } + + self GadgetPowerSet( heroWeaponSlot, 100.0 ); +} + +function finalSurvivorUAV( finalSurvivor ) +{ + level endon( "game_ended" ); + finalSurvivor endon( "disconnect" ); + finalSurvivor endon( "death" ); + level endon( "infect_lateJoiner" ); + level thread endUAVonLateJoiner( finalSurvivor ); + + SetTeamSpyplane( game["attackers"], 1 ); + util::set_team_radar( game["attackers"], 1 ); + removeUAV = false; + + while ( true ) + { + prevPos = finalSurvivor.origin; + + wait( 4.0 ); + + if ( removeUAV ) + { + SetTeamSpyplane( game["attackers"], 0 ); + util::set_team_radar( game["attackers"], 0 ); + removeUAV = false; + } + + wait( 6.0 ); + + if ( DistanceSquared( prevPos, finalSurvivor.origin ) < ( (200) * (200) ) ) + { + SetTeamSpyplane( game["attackers"], 1 ); + util::set_team_radar( game["attackers"], 1 ); + removeUAV = true; + + foreach ( player in level.players ) + { + sound::play_on_players( "fly_hunter_raise_plr" ); + } + } + } +} + +function endUAVonLateJoiner( finalSurvivor ) +{ + level endon( "game_ended" ); + finalSurvivor endon( "disconnect" ); + finalSurvivor endon( "death" ); + + while( true ) + { + totalDefenders = [[level._getTeamScore]]( game["defenders"] ); + if ( totalDefenders > 1 ) + { + level notify( "infect_lateJoiner" ); + {wait(.05);}; + SetTeamSpyplane( game["attackers"], 1 ); + util::set_team_radar( game["attackers"], 1 ); + break; + } + {wait(.05);}; + } +} + +function onTimeLimit() +{ + winner = game["defenders"]; + level thread endGame( winner, game["strings"]["time_limit_reached"] ); +} + +function onSurvivorsEliminated() +{ + updateTeamScores(); + + winner = game["attackers"]; + loser = util::getOtherTeam( winner ); + level thread endGame( winner, game["strings"][loser + "_eliminated"] ); +} + +function endGame( winner, endReasonText ) +{ + if ( ( isdefined( level.infect_gameEnded ) && level.infect_gameEnded ) ) + { + return; + } + + level.infect_gameEnded = true; + + util::wait_network_frame(); // give final player a chance to switch teams in native + + globallogic::endGame( winner, endReasonText ); +} + +function onPlayerJoinedSpectate() +{ + checkTeamStates(); +} + +function onPlayerDisconnect() +{ + checkTeamStates(); +} + +function checkTeamStates() +{ + if ( ( isdefined( level.gameEnded ) && level.gameEnded ) ) + { + return; + } + + updateTeamScores(); + + totalAttackers = [[level._getTeamScore]]( game["attackers"] ); + totalDefenders = [[level._getTeamScore]]( game["defenders"] ); + + // team actions or win condition necessary? + if ( isDefined( self.infect_isBeingChosen ) || level.infect_choseFirstInfected ) + { + if ( totalAttackers > 0 && totalDefenders > 0 ) + { + if ( totalDefenders == 1 ) + { + // final survivor was abandoned: inform, reward, call uav + onFinalSurvivor(); + } + } + else if ( totalDefenders == 0 ) + { + // no more survivors, infected win + onSurvivorsEliminated(); + } + else if ( totalAttackers == 0 ) + { + if ( totalDefenders == 1 ) + { + // last survivor wins + winner = game["defenders"]; + loser = util::getOtherTeam( winner ); + level thread endGame( winner, game["strings"][loser + "_eliminated"] ); + } + else if ( totalDefenders > 1 ) + { + // pick a new infected and keep the game going + level.infect_choseFirstInfected = false; + level thread chooseFirstInfected(); + } + } + } + else + { + activeDefenders = getActivePlayersOnTeam( game["defenders"] ); + if ( activeDefenders.size < 1 ) + { + level notify( "infect_stopCountdown" ); + } + } +} + +function giveCustomLoadout( takeAllWeapons, alreadySpawned ) +{ + if ( self.team == game["attackers"] ) + { + self giveInfectedLoadout(); + self.movementSpeedModifier = 1.2; + } + else if ( self.team == game["defenders"] ) + { + self giveWeaponClassLoadout(); + self.movementSpeedModifier = undefined; + } + + return self.spawnWeapon; +} + +// direct copy of non-giveCustomLoadout block from loadout::giveLoadout to avoid invasive changes to _loadout.gsc +function giveWeaponClassLoadout() +{ + loadout::giveLoadout_init( true ); + + loadout::setClassNum( self.curClass ); + + self SetActionSlot( 3, "altMode" ); + self SetActionSlot( 4, "" ); + + allocationSpent = self GetLoadoutAllocation( self.class_num ); + overAllocation = ( allocationSpent > level.maxAllocation ); + + if ( !overAllocation ) + { + //Perks must come first in case other give-functions check for perks + givePerks(); + + loadout::giveWeapons(); + loadout::givePrimaryOffhand(); + } + else + { + loadout::giveBaseWeapon(); + } + + loadout::giveSecondaryOffhand(); + + if ( GetDvarint( "tu11_enableClassicMode") == 0 ) + { + loadout::giveSpecialOffhand(); + loadout::giveHeroWeapon(); + } + + loadout::giveKillStreaks(); +} + +function givePerks() +{ + self.specialty = self GetLoadoutPerks( self.class_num ); + + if ( level.leagueMatch ) + { + for ( i = 0; i < self.specialty.size; i++ ) + { + if ( loadout::isLeagueItemRestricted( self.specialty[i] ) ) + { + ArrayRemoveIndex( self.specialty, i ); + i--; + } + } + } + + // re-registering perks to code since perks are cleared after respawn in case if players switch classes + self loadout::register_perks(); + + // now that perks are re-registered... + // update player momentum taking into account anteup perk; any score less than the initial value is boosted to that value + anteup_bonus = GetDvarInt( "perk_killstreakAnteUpResetValue" ); + momentum_at_spawn_or_game_end = (isdefined(self.pers["momentum_at_spawn_or_game_end"])?self.pers["momentum_at_spawn_or_game_end"]:0); + hasNotDoneCombat = !( self.hasDoneCombat === true ); // fixes dev gui bot spawning in grace period + if ( ( level.inPreMatchPeriod || ( level.inGracePeriod && hasNotDoneCombat ) ) && momentum_at_spawn_or_game_end < anteup_bonus ) + { + new_momentum = ( self HasPerk( "specialty_anteup" ) ? anteup_bonus : momentum_at_spawn_or_game_end ); + globallogic_score::_setPlayerMomentum( self, new_momentum, false ); + } +} + + +function giveInfectedLoadout() +{ + self loadout::giveLoadout_init( true ); + + loadout::setClassNum( self.curClass ); + + self ClearPerks(); + foreach ( perkName in level.infect_perks ) + { + if ( !self HasPerk( perkName ) ) + { + self SetPerk( perkName ); + } + } + + totalAttackers = [[level._getTeamScore]]( game["attackers"] ); + if ( totalAttackers == 1 ) + { + defaultWeapon = level.weapon_FIRST_INFECTED_PRIMARY_WEAPON; + } + else + { + defaultWeapon = level.weapon_INFECTED_PRIMARY_WEAPON; + } + self playerInfectedGivePrimaryWeapon( defaultWeapon ); + self SwitchToWeapon( defaultWeapon ); + self SetSpawnWeapon( defaultWeapon ); + self.spawnWeapon = defaultWeapon; + + primaryOffhand = level.weapon_INFECTED_PRIMARY_GRENADE_WEAPON; + primaryOffhandCount = 1; + self GiveWeapon( primaryOffhand ); + self SetWeaponAmmoStock( primaryOffhand, primaryOffhandCount ); + self SwitchToOffhand( primaryOffhand ); + self.grenadeTypePrimary = primaryOffhand; + self.grenadeTypePrimaryCount = primaryOffhandCount; + + secondaryOffhand = GetWeapon( "null_offhand_secondary" ); + secondaryOffhandCount = 0; + self GiveWeapon( secondaryOffhand ); + self SetWeaponAmmoClip( secondaryOffhand, secondaryOffhandCount ); + self SwitchToOffhand( secondaryOffhand ); + self.grenadeTypeSecondary = secondaryOffhand; + self.grenadeTypeSecondaryCount = secondaryOffhandCount; + + self GiveWeapon( level.weaponBaseMelee ); + + giveRandomGadget(); + + self.heroWeapon = undefined; +} + +function giveRandomGadget() +{ + specialOffhand = self.infect_randomGadget; + resetCharge = false; + + if ( !isdefined( specialOffhand ) ) + { + randomGadget = array::random( level.infect_gadgets ); + specialOffhand = GetWeapon( randomGadget ); + resetCharge = true; + } + + specialOffhandCount = specialOffhand.startammo; + self GiveWeapon( specialOffhand ); + + self SetWeaponAmmoClip( specialOffhand, specialOffhandCount ); + self SwitchToOffhand( specialOffhand ); + self.grenadeTypeSpecial = specialOffhand; + self.grenadeTypeSpecialCount = specialOffhandCount; + + if ( ( isdefined( resetCharge ) && resetCharge ) ) + { + slot = self GadgetGetSlot( specialOffhand ); + self GadgetPowerReset( slot ); + } + + self.infect_randomGadget = specialOffhand; +} + +function chooseFirstInfected() +{ + level endon( "game_ended" ); + level endon( "infect_stopCountdown" ); + + // no suicides + level.infect_allowSuicide = false; + + level.lastInfectionTime = undefined; + + if ( level.inPrematchPeriod ) + { + level waittill( "prematch_over" ); + } + + level.infect_timerDisplay.label = &"MP_DRAFT_STARTS_IN"; + level.infect_timerDisplay setTimer( 8 ); + level.infect_timerDisplay.alpha = 1; + + hostmigration::waitLongDurationWithHostMigrationPause( 8.0 ); + + level.infect_timerDisplay.alpha = 0; + + activeDefenders = getActivePlayersOnTeam( game["defenders"] ); + if ( activeDefenders.size > 0 ) + { + activeDefenders[GetArrayKeys(activeDefenders)[RandomInt(GetArrayKeys(activeDefenders).size)]] setFirstInfected(); + } + else + { + level.infect_choosingFirstInfected = false; + } +} + +function abortedCountdownCleanup() +{ + while ( true ) + { + event = level util::waittill_any_return( "game_ended", "infect_stopCountdown" ); + + if ( isdefined( level.infect_timerDisplay ) ) + { + level.infect_timerDisplay.alpha = 0; + } + + if ( event == "game_ended" ) + { + return; + } + + level.infect_choosingFirstInfected = false; + } +} + +function displayTimeExtended() +{ + level notify( "timeExtended" ); + + level endon( "game_ended" ); + level endon( "infect_stopTimeExtended" ); + level endon( "timeExtended" ); + + timeout = 0; + + while( isDefined( level.infect_timerDisplay ) && level.infect_timerDisplay.alpha > 0 ) + { + hostmigration::waitLongDurationWithHostMigrationPause( 0.5 ); + timeout++; + + if( timeout == 20 ) + return; + } + + level.infect_timeExtendedDisplay.alpha = 1; + + hostmigration::waitLongDurationWithHostMigrationPause( 1.0 ); + + level.infect_timeExtendedDisplay FadeOverTime( 2 ); + + level.infect_timeExtendedDisplay.alpha = 0; +} + +function timeExtendedCleanup() +{ + while ( true ) + { + event = level util::waittill_any_return( "game_ended", "infect_stopTimeExtended" ); + + if ( isdefined( level.infect_timeExtendedDisplay ) ) + { + level.infect_timeExtendedDisplay.alpha = 0; + } + + if ( event == "game_ended" ) + { + return; + } + } +} + +function getActivePlayersOnTeam( team ) +{ + activePlayers = []; + + teamPlayers = getPlayersOnTeam( team ); + foreach ( player in teamPlayers ) + { + if ( player.sessionstate == "spectator" ) + { + continue; + } + + if ( !isdefined( activePlayers ) ) activePlayers = []; else if ( !IsArray( activePlayers ) ) activePlayers = array( activePlayers ); activePlayers[activePlayers.size]=player;; + } + + return activePlayers; +} + +function setFirstInfected() +{ + self endon( "disconnect" ); + + self.infect_isBeingChosen = true; + + // wait alive + while( !IsAlive( self ) || self util::isUsingRemote() ) + {wait(.05);}; + + // remove placement item if carrying + if ( ( isdefined( self.isCarrying ) && self.isCarrying ) ) + { + self notify( "force_cancel_placement" ); + {wait(.05);}; + } + + // not while mantling + while ( self IsMantling() ) + {wait(.05);}; + + // not while in air + while ( !self IsOnGround() && !self IsOnLadder() ) + {wait(.05);}; + + switchPlayerToInfectedTeam( self ); + self.switching_teams = undefined; // otherwise first death will be ignored by stats system + + if ( self IsUsingOffHand() ) + { + self ForceOffHandEnd(); + } + self DisableOffhandSpecial(); // circumvents a weapon-flag issue if drafted while cooking an offhand weapon + self thread playerEnableOffhandAfterWeaponSwitch(); + + loadout::giveLoadout( self.team, self.curClass ); + + totalDefenders = [[level._getTeamScore]]( game["defenders"] ); + if ( totalDefenders < 1 ) + { + level.infect_forceAssignDefender = true; + } + else + { + forceSpawnTeam( game["defenders"] ); + } + + // tell the world! + LUINotifyEvent( &"player_callout", 2, &"SCORE_FIRST_INFECTED", self.entnum ); + scoreevents::processScoreEvent( "first_infected", self ); + sound::play_on_players( "mpl_flagget_sting_enemy" ); + + level.infect_allowSuicide = true; + level.infect_choseFirstInfected = true; + self.infect_isBeingChosen = undefined; +} + +function forceSpawnTeam( team ) +{ + players = getPlayersOnTeam( team ); + foreach ( player in players ) + { + player thread playerForceSpawn(); + } +} + +function playerForceSpawn() +{ + level endon( "game_ended" ); + self endon( "death" ); + self endon( "disconnect" ); + self endon( "spawned" ); + + if ( self.hasSpawned ) + { + return; + } + + if ( self.pers["team"] == "spectator" ) + { + return; + } + + self playerWaitForStreamer(); + + self.pers["class"] = level.defaultClass; + self.curClass = level.defaultClass; + + self globallogic_ui::closeMenus(); + self CloseMenu( "ChooseClass_InGame" ); + self thread [[level.spawnClient]](); +} + +function switchPlayerToInfectedTeam( player ) +{ + storeInfectedPlayer( player ); + destroyPlayerEquipment( player ); + player changeTeam( game["attackers"] ); + updateTeamScores(); + resetTimeLimit(); +} + +function playerEnableOffhandAfterWeaponSwitch() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + self endon( "death" ); + + self waittill( "weapon_change" ); + + self EnableOffhandSpecial(); +} + +function storeInfectedPlayer( player ) +{ + playerXuid = player GetXUID(); + level.infect_players[playerXuid] = true; +} + +function changeTeam( team ) +{ + if ( self.sessionstate != "dead" ) + { + // Set a flag on the player to they aren't robbed points for dying - the callback will remove the flag + self.switching_teams = true; + self.switchedTeamsResetGadgets = true; + self.joining_team = team; + self.leaving_team = self.pers["team"]; + } + + self.pers["team"] = team; + self.team = team; + self.pers["weapon"] = undefined; + self.pers["spawnweapon"] = undefined; + self.pers["savedmodel"] = undefined; + self.pers["teamTime"] = undefined; + self.sessionteam = self.pers["team"]; + + self globallogic_ui::updateObjectiveText(); + + // update spectator permissions immediately on change of team + self spectating::set_permissions(); + + self notify("end_respawn"); +} + +function getPlayersOnTeam( team ) +{ + playersOnTeam = []; + + foreach ( player in level.players ) + { + if ( !isdefined( player ) ) + { + continue; + } + + playerTeam = player.pers[ "team" ]; + if ( IsDefined( playerTeam ) && IsDefined( level.teams[ playerTeam ] ) && playerTeam == team ) + { + if ( !isdefined( playersOnTeam ) ) playersOnTeam = []; else if ( !IsArray( playersOnTeam ) ) playersOnTeam = array( playersOnTeam ); playersOnTeam[playersOnTeam.size]=player;; + } + } + + return playersOnTeam; +} + +function countPlayersOnTeam( team ) +{ + playersOnTeam = getPlayersOnTeam( team ); + return playersOnTeam.size; +} + +function updateTeamScores() +{ + updateTeamScore( game["attackers"] ); + updateTeamScore( game["defenders"] ); +} + +function updateTeamScore( team ) +{ + score = countPlayersOnTeam( team ); + game["teamScores"][team] = score; + globallogic_score::updateTeamScores( team ); +} + +function mayDropWeapon( weapon ) +{ + if ( self.team === game["attackers"] ) + { + return false; + } + + return true; +} + +function playerInfectedGivePrimaryWeapon( weapon ) +{ + self GiveWeapon( weapon ); + self GiveStartAmmo( weapon ); + self SetBlockWeaponPickup( weapon, true ); + self.infect_primaryWeapon = weapon; +} + +function sanitizeInfectedLoadouts() +{ + attackers = getPlayersOnTeam( game["attackers"] ); + if ( attackers.size < 2 ) + { + return; + } + + foreach ( player in attackers ) + { + if ( !IsAlive( player ) ) + { + continue; + } + + if ( player.infect_primaryWeapon !== level.weapon_INFECTED_PRIMARY_WEAPON ) + { + if ( isdefined( player.infect_primaryWeapon ) ) + { + player TakeWeapon( player.infect_primaryWeapon ); + } + + newWeapon = level.weapon_INFECTED_PRIMARY_WEAPON; + player playerInfectedGivePrimaryWeapon( newWeapon ); + player SwitchToWeapon( newWeapon ); + } + } +} + +function playerGetWeaponScavengeAmmo( weapon, defaultAmmo ) +{ + if ( weapon == self.grenadeTypePrimary || weapon == self.grenadeTypeSecondary ) + { + return 0; + } + + return defaultAmmo; +} + +function resetTimeLimit() +{ + level.lastInfectionTime = GetTime(); +} + +function getTimeLimit() +{ + defaultTimeLimit = GetGametypeSetting( "timeLimit" ); + if ( defaultTimeLimit == 0 ) + { + return 0; + } + + if ( !isdefined( level.lastInfectionTime ) ) + { + return 0; + } + + minutesElapsed = ( ( level.lastInfectionTime - level.startTime + 1000 ) / 60000 ); + timeLimit = minutesElapsed + defaultTimeLimit; + return timeLimit; +} + +function destroyPlayerEquipment( player ) +{ + for ( i = 0; i < level.missileEntities.size; i++ ) + { + item = level.missileEntities[i]; + + if ( !isdefined( item ) ) + { + continue; + } + + if ( !isdefined( item.weapon ) ) + { + continue; + } + + if ( item.owner !== player && item.originalowner !== player ) + { + continue; + } + + item notify( "detonating" ); + if ( isdefined( item ) ) + item Delete(); + } +} + +function gametypeRoundEndScoreHud( winner, endType, endReasonText, outcomeText, team, winnerEnum, notifyRoundEndToUI, matchbonus ) +{ + if ( endType == "roundend" ) + { + if ( winner == "tie" ) + { + outcomeText = game["strings"]["draw"]; + } + else if ( isdefined( self.pers["team"] ) && winner == team ) + { + outcomeText = game["strings"]["victory"]; + overrideSpectator = true; + } + else + { + outcomeText = game["strings"]["defeat"]; + + if ( ( level.rankedMatch || level.leagueMatch ) && ( self.pers[ "lateJoin" ] === true ) ) + { + endReasonText = game["strings"]["join_in_progress_loss"]; + } + + overrideSpectator = true; + + } + notifyRoundEndToUI = false; + + // Codcaster Specific Outcome Override + if ( team == "spectator" && overrideSpectator ) + { + // Special case handling of end reason text + foreach ( team in level.teams ) + { + if ( endreasontext == game["strings"][team + "_eliminated"] ) + { + endReasonText = game["strings"]["cod_caster_team_eliminated"]; + break; + } + } + + outcomeText = game["strings"]["cod_caster_team_wins"]; + } + + self LUINotifyEvent( &"show_outcome", 5, outcomeText, endReasonText, int( matchBonus ), winnerEnum, notifyRoundEndToUI ); + return true; + } + + return false; +} + +function onCloneSelectWeapon( player ) +{ + if ( player.team === game["attackers"] ) + { + playerWeapon = player GetCurrentWeapon(); + if ( isdefined( playerWeapon.worldModel ) ) + { + return playerWeapon; + } + } + + return undefined; // use default logic +} + +function delayedForceSpawnDefenders() +{ + level endon( "game_ended" ); + + level notify( "infect_forcespawn_defenders" ); + level endon( "infect_forcespawn_defenders" ); + + wait( 30 ); + + forceSpawnTeam( game["defenders"] ); +} + +function weaponDropped( weaponItem ) +{ + weaponItem HideFromTeam( game["attackers"] ); +} + +/# +function runDvarHandler( dvarName, commandHandlerFunc ) +{ + SetDvar( dvarName, "" ); + + while ( true ) + { + {wait(.05);}; + + dvarValue = GetDvarString( dvarName ); + if ( dvarValue == "" ) + { + continue; + } + + SetDvar( dvarName, "" ); + + tokens = StrTok( dvarValue, " " ); + if ( !IsDefined( tokens ) || tokens.size < 1 ) + { + continue; + } + + command = tokens[0]; + ArrayRemoveIndex( tokens, 1 ); + + [[ commandHandlerFunc ]]( command, tokens ); + } +} + + + +function handleDevCommand( command, args ) +{ + switch ( command ) + { + case "killall_bots": + foreach ( player in level.players ) + { + if ( player util::is_bot() ) + { + player Kill(); + } + } + break; + + default: + break; + } +} + +function devgui_infect() +{ + level thread runDvarHandler( "scr_infect_devcmd", &handleDevCommand ); + + level flag::wait_till("all_players_spawned" ); + + devguiRoot = "devgui_cmd \"Infected/"; + + AddDebugCommand( devguiRoot + "Kill All Bots" + "\" \"set " + "scr_infect_devcmd" + " killall_bots\" \n"); +} +#/ diff --git a/mp/gametypes/koth.csc b/mp/gametypes/koth.csc new file mode 100644 index 0000000..4077991 --- /dev/null +++ b/mp/gametypes/koth.csc @@ -0,0 +1,229 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\util_shared; + +#using scripts\mp\_shoutcaster; + + + + +#precache( "client_fx", "ui/fx_koth_marker_blue" ); +#precache( "client_fx", "ui/fx_koth_marker_orng" ); +#precache( "client_fx", "ui/fx_koth_marker_neutral" ); +#precache( "client_fx", "ui/fx_koth_marker_contested" ); +#precache( "client_fx", "ui/fx_koth_marker_blue_window" ); +#precache( "client_fx", "ui/fx_koth_marker_orng_window" ); +#precache( "client_fx", "ui/fx_koth_marker_neutral_window" ); +#precache( "client_fx", "ui/fx_koth_marker_contested_window" ); +#precache( "client_fx", "ui/fx_koth_marker_white" ); +#precache( "client_fx", "ui/fx_koth_marker_white_window" ); + + + + + + + + + +function main() +{ + level.current_zone = []; + level.current_state = []; + for( i = 0; i < 4; i++ ) + { + level.current_zone[i] = 0; + level.current_state[i] = 0; + } + + level.hardPoints = []; + level.visuals = []; + level.hardPointFX = []; + + clientfield::register( "world", "hardpoint", 1, 5, "int",&hardpoint, !true, !true ); + clientfield::register( "world", "hardpointteam", 1, 5, "int",&hardpoint_state, !true, !true ); + + level.effect_scriptbundles = []; + + level.effect_scriptbundles["zoneEdgeMarker"] = struct::get_script_bundle( "teamcolorfx", "teamcolorfx_koth_edge_marker" ); + level.effect_scriptbundles["zoneEdgeMarkerWndw"] = struct::get_script_bundle( "teamcolorfx", "teamcolorfx_koth_edge_marker_window" ); + + level._effect["zoneEdgeMarker"] = []; + level._effect["zoneEdgeMarker"][0] = "ui/fx_koth_marker_neutral"; + level._effect["zoneEdgeMarker"][1] = "ui/fx_koth_marker_blue"; + level._effect["zoneEdgeMarker"][2] = "ui/fx_koth_marker_orng"; + level._effect["zoneEdgeMarker"][3] = "ui/fx_koth_marker_contested"; + + level._effect["zoneEdgeMarkerWndw"] = []; + level._effect["zoneEdgeMarkerWndw"][0] = "ui/fx_koth_marker_neutral_window"; + level._effect["zoneEdgeMarkerWndw"][1] = "ui/fx_koth_marker_blue_window"; + level._effect["zoneEdgeMarkerWndw"][2] = "ui/fx_koth_marker_orng_window"; + level._effect["zoneEdgeMarkerWndw"][3] = "ui/fx_koth_marker_contested_window"; +} + +function get_shoutcaster_fx(local_client_num) +{ + effects = []; + effects["zoneEdgeMarker"][0] = level._effect["zoneEdgeMarker"][0]; + effects["zoneEdgeMarker"][3] = level._effect["zoneEdgeMarker"][3]; + effects["zoneEdgeMarkerWndw"][0] = level._effect["zoneEdgeMarkerWndw"][0]; + effects["zoneEdgeMarkerWndw"][3] = level._effect["zoneEdgeMarkerWndw"][3]; + + if ( GetDvarInt("tu11_programaticallyColoredGameFX") ) + { + effects["zoneEdgeMarker"][1] = "ui/fx_koth_marker_white"; + effects["zoneEdgeMarker"][2] = "ui/fx_koth_marker_white"; + effects["zoneEdgeMarkerWndw"][1] = "ui/fx_koth_marker_white_window"; + effects["zoneEdgeMarkerWndw"][2] = "ui/fx_koth_marker_white_window"; + } + else + { + caster_effects = []; + caster_effects["zoneEdgeMarker"] = shoutcaster::get_color_fx( local_client_num, level.effect_scriptbundles["zoneEdgeMarker"] ); + caster_effects["zoneEdgeMarkerWndw"] = shoutcaster::get_color_fx( local_client_num, level.effect_scriptbundles["zoneEdgeMarkerWndw"] ); + + effects["zoneEdgeMarker"][1] = caster_effects["zoneEdgeMarker"]["allies"]; + effects["zoneEdgeMarker"][2] = caster_effects["zoneEdgeMarker"]["axis"]; + effects["zoneEdgeMarkerWndw"][1] = caster_effects["zoneEdgeMarkerWndw"]["allies"]; + effects["zoneEdgeMarkerWndw"][2] = caster_effects["zoneEdgeMarkerWndw"]["axis"]; + } + + return effects; +} + +function get_fx_state( local_client_num, state, is_shoutcaster ) +{ + if ( is_shoutcaster ) + return state; + + if ( state == 1 ) + { + if ( util::friend_not_foe_team( local_client_num, "allies" ) ) + return 1; + else + return 2; + } + else if ( state == 2 ) + { + if ( util::friend_not_foe_team( local_client_num, "axis" ) ) + return 1; + else + return 2; + } + + return state; +} + +function get_fx( fx_name, fx_state, effects ) +{ + return effects[fx_name][fx_state]; +} + +function setup_hardpoint_fx( local_client_num, zone_index, state ) +{ + effects = []; + + if ( shoutcaster::is_shoutcaster_using_team_identity(local_client_num) ) + { + effects = get_shoutcaster_fx(local_client_num); + } + else + { + effects["zoneEdgeMarker"] = level._effect["zoneEdgeMarker"]; + effects["zoneEdgeMarkerWndw"] = level._effect["zoneEdgeMarkerWndw"]; + } + + if ( isdefined( level.hardPointFX[local_client_num] ) ) + { + foreach ( fx in level.hardPointFX[local_client_num] ) + { + StopFx( local_client_num, fx ); + } + } + level.hardPointFX[local_client_num] = []; + + if ( zone_index ) + { + if ( isdefined( level.visuals[zone_index] ) ) + { + fx_state = get_fx_state( local_client_num, state, shoutcaster::is_shoutcaster(local_client_num) ); + + foreach ( visual in level.visuals[zone_index] ) + { + if ( !isdefined(visual.script_fxid ) ) + continue; + + fxid = get_fx( visual.script_fxid, fx_state, effects ); + + if ( isdefined(visual.angles) ) + forward = AnglesToForward( visual.angles ); + else + forward = ( 0,0,0 ); + + fxHandle = PlayFX( local_client_num, fxid, visual.origin, forward ); + level.hardPointFX[local_client_num][level.hardPointFX[local_client_num].size] = fxHandle; + if ( isdefined( fxHandle ) ) + { + if ( state == 1 ) + { + SetFxTeam( local_client_num, fxHandle, "allies" ); + } + else if ( state == 2 ) + { + SetFxTeam( local_client_num, fxHandle, "axis" ); + } + else + { + SetFxTeam( local_client_num, fxHandle, "free" ); + } + } + } + } + } + + thread watch_for_team_change( local_client_num ); +} + +function hardpoint(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + if ( level.hardPoints.size == 0 ) + { + hardpoints = struct::get_array( "koth_zone_center", "targetname" ); + foreach( point in hardpoints ) + { + level.hardPoints[point.script_index] = point; + } + + foreach( point in level.hardPoints ) + { + level.visuals[point.script_index] = struct::get_array( point.target, "targetname" ); + } + } + + level.current_zone[localClientNum] = newVal; + level.current_state[localClientNum] = 0; + + setup_hardpoint_fx( localClientNum, level.current_zone[localClientNum], level.current_state[localClientNum] ); +} + +function hardpoint_state(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + if ( newVal != level.current_state[localClientNum] ) + { + level.current_state[localClientNum] = newVal; + setup_hardpoint_fx( localClientNum, level.current_zone[localClientNum], level.current_state[localClientNum] ); + } +} + +function watch_for_team_change( localClientNum ) +{ + level notify( "end_team_change_watch" ); + level endon( "end_team_change_watch" ); + + level waittill( "team_changed" ); + + wait(0.05); + + thread setup_hardpoint_fx( localClientNum, level.current_zone[localClientNum], level.current_state[localClientNum] ); +} \ No newline at end of file diff --git a/mp/gametypes/koth.gsc b/mp/gametypes/koth.gsc new file mode 100644 index 0000000..4d12ead --- /dev/null +++ b/mp/gametypes/koth.gsc @@ -0,0 +1,1654 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\medals_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\util_shared; + + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; + + + + + + + + +/*QUAKED mp_multi_team_spawn (1.0 0.0 0.0) (-16 -16 0) (16 16 72) +Spawns used for use in some multi team game modes to open up other portions of the map for multi team scenarios.*/ + +#precache( "string", "OBJECTIVES_KOTH" ); +#precache( "string", "OBJECTIVES_KOTH_SCORE" ); +#precache( "string", "MP_WAITING_FOR_HQ" ); +#precache( "string", "MP_KOTH_CAPTURED_BY" ); +#precache( "string", "MP_KOTH_CAPTURED_BY_ENEMY" ); +#precache( "string", "MP_KOTH_MOVING_IN" ); +#precache( "string", "MP_CAPTURING_OBJECTIVE" ); +#precache( "string", "MP_KOTH_CONTESTED_BY_ENEMY" ); +#precache( "string", "MP_KOTH_AVAILABLE_IN" ); +#precache( "string", "MP_CONTROL_KOTH" ); +#precache( "string", "MP_CAPTURE_KOTH" ); +#precache( "string", "MP_DEFEND_KOTH" ); +#precache( "string", "MP_KOTH_AVAILABLE_IN" ); +#precache( "string", "MP_HQ_DESPAWN_IN" ); +#precache( "string", "MP_HQ_REINFORCEMENTS_IN" ); +#precache( "string", "MP_CAPTURING_HQ" ); +#precache( "string", "MP_DESTROYING_HQ" ); +#precache( "fx", "ui/fx_koth_marker_blue" ); +#precache( "fx", "ui/fx_koth_marker_orng" ); +#precache( "fx", "ui/fx_koth_marker_neutral" ); +#precache( "fx", "ui/fx_koth_marker_contested" ); +#precache( "fx", "ui/fx_koth_marker_blue_window" ); +#precache( "fx", "ui/fx_koth_marker_orng_window" ); +#precache( "fx", "ui/fx_koth_marker_neutral_window" ); +#precache( "fx", "ui/fx_koth_marker_contested_window" ); +#precache( "objective", "hardpoint" ); + +function main() +{ + globallogic::init(); + + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 1000 ); + util::registerNumLives( 0, 100 ); + util::registerRoundSwitch( 0, 9 ); + util::registerRoundWinLimit( 0, 10 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.teamBased = true; + level.doPrematch = true; + level.overrideTeamScore = true; + level.scoreRoundWinBased = true; + level.kothStartTime = 0; + level.onStartGameType =&onStartGameType; + level.playerSpawnedCB =&koth_playerSpawnedCB; + level.onRoundSwitch =&onRoundSwitch; + level.onPlayerKilled =&onPlayerKilled; + level.onEndGame=&onEndGame; + + clientfield::register( "world", "hardpoint", 1, 5, "int" ); + clientfield::register( "world", "hardpointteam", 1, 5, "int" ); + + level.zoneAutoMoveTime = GetGametypeSetting( "autoDestroyTime" ); + level.zoneSpawnTime = GetGametypeSetting( "objectiveSpawnTime" ); + level.kothMode = GetGametypeSetting( "kothMode" ); + level.captureTime = GetGametypeSetting( "captureTime" ); + level.destroyTime = GetGametypeSetting( "destroyTime" ); + level.delayPlayer = GetGametypeSetting( "delayPlayer" ); + level.randomZoneSpawn = GetGametypeSetting( "randomObjectiveLocations" ); + level.scorePerPlayer = GetGametypeSetting( "scorePerPlayer" ); + level.timePausesWhenInZone = GetGametypeSetting( "timePausesWhenInZone" ); + + level.iconoffset = (0,0,32); + + level.onRespawnDelay =&getRespawnDelay; + + gameobjects::register_allowed_gameobject( level.gameType ); + + globallogic_audio::set_leader_gametype_dialog ( "startHardPoint", "hcStartHardPoint", "objCapture", "objCapture" ); + + game["objective_gained_sound"] = "mpl_flagcapture_sting_friend"; + game["objective_lost_sound"] = "mpl_flagcapture_sting_enemy"; + game["objective_contested_sound"] = "mpl_flagreturn_sting"; + + level.lastDialogTime = 0; + level.zoneSpawnQueue = []; + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "objtime", "defends", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "objtime", "defends" ); + + /# + // HQ radio triggers are not scoped to exclude koth right now + // going to delete them just so if we render triggers we dont see all these + // TODO in the future is to get them into the automatic game type delete system + trigs = getentarray("radiotrigger", "targetname"); + foreach( trig in trigs ) + { + trig delete(); + } + #/ +} + + +function updateObjectiveHintMessages( defenderTeam, defendMessage, attackMessage ) +{ + foreach( team in level.teams ) + { + if ( defenderTeam == team ) + { + game["strings"]["objective_hint_" + team] = defendMessage; + } + else + { + game["strings"]["objective_hint_" + team] = attackMessage; + } + } +} + +function updateObjectiveHintMessage( message ) +{ + foreach( team in level.teams ) + { + game["strings"]["objective_hint_" + team] = message; + } +} + +function getRespawnDelay() +{ + self.lowerMessageOverride = undefined; + + if ( !isdefined( level.zone.gameobject ) ) + return undefined; + + zoneOwningTeam = level.zone.gameobject gameobjects::get_owner_team(); + if ( self.pers["team"] == zoneOwningTeam ) + { + if ( !isdefined( level.zoneMoveTime ) ) + return undefined; + + timeRemaining = (level.zoneMoveTime - gettime()) / 1000; + + if (!level.playerObjectiveHeldRespawnDelay ) + return undefined; + + if ( level.playerObjectiveHeldRespawnDelay >= level.zoneAutoMoveTime ) + self.lowerMessageOverride = &"MP_WAITING_FOR_HQ"; + + if ( level.delayPlayer ) + { + return min( level.spawnDelay, timeRemaining ); + } + else + { + return ceil(timeRemaining); + } + } +} + +function onStartGameType() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + + globallogic_score::resetTeamScores(); + + foreach( team in level.teams ) + { + util::setObjectiveText( team, &"OBJECTIVES_KOTH" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( team, &"OBJECTIVES_KOTH" ); + } + else + { + util::setObjectiveScoreText( team, &"OBJECTIVES_KOTH_SCORE" ); + } + } + + level.kothTotalSecondsInZone = 0; + + level.objectiveHintPrepareZone = &"MP_CONTROL_KOTH"; + level.objectiveHintCaptureZone = &"MP_CAPTURE_KOTH"; + level.objectiveHintDefendHQ = &"MP_DEFEND_KOTH"; + + if ( level.zoneSpawnTime ) + updateObjectiveHintMessage( level.objectiveHintPrepareZone ); + else + updateObjectiveHintMessage( level.objectiveHintCaptureZone ); + + setClientNameMode("auto_change"); + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + // TODO: HQ spawnpoints + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + foreach( team in level.teams ) + { + spawnlogic::add_spawn_points( team, "mp_tdm_spawn" ); + spawnlogic::add_spawn_points( team, "mp_multi_team_spawn" ); + + spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) ); + } + + spawning::updateAllSpawnPoints(); + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array( spawning::getTDMStartSpawnName(team) ); + } + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + level.spawn_all = spawnlogic::get_spawnpoint_array( "mp_tdm_spawn" ); + if ( !level.spawn_all.size ) + { +/# + println("^1No mp_tdm_spawn spawnpoints in level!"); +#/ + callback::abort_level(); + return; + } + + + thread SetupZones(); + + updateGametypeDvars(); + + thread KothMainLoop(); +} + +function pause_time() +{ + if ( level.timePausesWhenInZone ) + { + globallogic_utils::pauseTimer(); + level.timerPaused = true; + } +} + +function resume_time() +{ + if ( level.timePausesWhenInZone ) + { + globallogic_utils::resumeTimerDiscardOverride( level.kothTotalSecondsInZone * 1000 ); + level.timerPaused = false; + } +} + +function updateGametypeDvars() +{ + level.playerCaptureLPM = GetGametypeSetting( "maxPlayerEventsPerMinute" ); + level.timePausesWhenInZone = GetGametypeSetting( "timePausesWhenInZone" ); +} + +function spawn_first_zone(delay) +{ + // pick next Zone object + if ( level.randomZoneSpawn == 1 ) + { + level.zone = GetNextZoneFromQueue(); + } + else + { + level.zone = GetFirstZone(); + } + + if ( isdefined( level.zone ) ) + { + /#print("zone spawned: ("+level.zone.trigOrigin[0]+","+level.zone.trigOrigin[1]+","+level.zone.trigOrigin[2]+")");#/ + + level.zone spawning::enable_influencers(true); + } + + level.zone.gameobject.trigger AllowTacticalInsertion( false ); + + return; +} + +function spawn_next_zone() +{ + level.zone.gameobject.trigger AllowTacticalInsertion( true ); + + // pick next Zone object + if ( level.randomZoneSpawn != 0 ) + { + level.zone = GetNextZoneFromQueue(); + } + else + { + level.zone = GetNextZone(); + } + + if ( isdefined( level.zone ) ) + { + /#print("zone spawned: ("+level.zone.trigOrigin[0]+","+level.zone.trigOrigin[1]+","+level.zone.trigOrigin[2]+")");#/ + + level.zone spawning::enable_influencers(true); + } + + level.zone.gameobject.trigger AllowTacticalInsertion( false ); + + return; +} + +function getNumTouching( ) +{ + numTouching = 0; + foreach( team in level.teams ) + { + numTouching += self.numTouching[team]; + } + + return numTouching; +} + +function toggleZoneEffects( enabled ) +{ + index = 0; + + if ( enabled ) + { + index = self.script_index; + } + + level clientfield::set( "hardpoint", index ); + level clientfield::set( "hardpointteam", 0 ); +} + +function KothCaptureLoop() +{ + level endon("game_ended"); + level endon("zone_moved"); + level.kothStartTime = gettime(); + + while( 1 ) + { + level.zone.gameobject gameobjects::allow_use( "any" ); + level.zone.gameobject gameobjects::set_use_time( level.captureTime ); + level.zone.gameobject gameobjects::set_use_text( &"MP_CAPTURING_OBJECTIVE" ); + + numTouching = level.zone.gameobject getNumTouching( ); + + level.zone.gameobject gameobjects::set_visible_team( "any" ); + level.zone.gameobject gameobjects::set_model_visibility( true ); + level.zone.gameobject gameobjects::must_maintain_claim( false ); + level.zone.gameobject gameobjects::can_contest_claim( true ); + + level.zone.gameobject.onUse =&onZoneCapture; + level.zone.gameobject.onBeginUse =&onBeginUse; + level.zone.gameobject.onEndUse =&onEndUse; + + level.zone toggleZoneEffects( true ); + + msg = level util::waittill_any_return( "zone_captured", "zone_destroyed", "game_ended", "zone_moved" ); + + // this happens if it goes from contested to neutral + if ( msg == "zone_destroyed" ) + continue; + + ownerTeam = level.zone.gameobject gameobjects::get_owner_team(); + + foreach( team in level.teams ) + { + updateObjectiveHintMessages( ownerTeam, level.objectiveHintDefendHQ, level.objectiveHintCaptureZone ); + } + + level.zone.gameobject gameobjects::allow_use( "none" ); + + level.zone.gameobject.onUse = undefined; + level.zone.gameobject.onUnoccupied =&onZoneUnoccupied; + level.zone.gameobject.onContested =&onZoneContested; + level.zone.gameobject.onUncontested =&onZoneUncontested; + + level waittill( "zone_destroyed", destroy_team ); + + if ( !level.kothMode || level.zoneDestroyedByTimer ) + break; + + thread forceSpawnTeam( ownerTeam ); + + if ( isdefined( destroy_team ) ) + { + level.zone.gameobject gameobjects::set_owner_team( destroy_team ); + } + else + { + level.zone.gameobject gameobjects::set_owner_team( "none" ); + } + } +} + +function KothMainLoop() +{ + level endon("game_ended"); + + level.zoneRevealTime = -100000; + + zoneSpawningInStr = &"MP_KOTH_AVAILABLE_IN"; + if ( level.kothMode ) + { + zoneDestroyedInFriendlyStr = &"MP_HQ_DESPAWN_IN"; + zoneDestroyedInEnemyStr = &"MP_KOTH_MOVING_IN"; + } + else + { + zoneDestroyedInFriendlyStr = &"MP_HQ_REINFORCEMENTS_IN"; + zoneDestroyedInEnemyStr = &"MP_HQ_DESPAWN_IN"; + } + + spawn_first_zone(); + + while ( level.inPrematchPeriod ) + {wait(.05);}; + + pause_time(); + + wait 5; + + SetBombTimer( "A", 0 ); + setMatchFlag( "bomb_timer_a", 0 ); + + thread hideTimerDisplayOnGameEnd(); + + while( 1 ) + { + resume_time(); + + sound::play_on_players( "mp_suitcase_pickup" ); + + globallogic_audio::leader_dialog( "kothLocated", undefined, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" ); + + level.zone.gameobject gameobjects::set_model_visibility( true ); + + level.zoneRevealTime = gettime(); + + if ( level.zoneSpawnTime ) + { + level.zone.gameobject gameobjects::set_visible_team( "any" ); + level.zone.gameobject gameobjects::set_flags( 1 ); + + updateObjectiveHintMessage( level.objectiveHintPrepareZone ); + setMatchFlag( "bomb_timer_a", 1 ); + SetBombTimer( "A", int( gettime() + 1000 + level.zoneSpawnTime * 1000 ) ); + + wait level.zoneSpawnTime; + + level.zone.gameobject gameobjects::set_flags( 0 ); + globallogic_audio::leader_dialog( "kothOnline", undefined, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" ); + } + + setMatchFlag( "bomb_timer_a", 0 ); + + waittillframeend; + + updateObjectiveHintMessage( level.objectiveHintCaptureZone ); + sound::play_on_players( "mpl_hq_cap_us" ); + + level.zone.gameobject gameobjects::enable_object(); + // Attach the objective to the visuals since it is at the right position and we can't attch it to the koth_zone_center script_origin + Objective_OnEntity( level.zone.gameobject.objectiveID, level.zone.objectiveAnchor ); + + level.zone.gameobject.captureCount = 0; + + if ( level.zoneAutoMoveTime ) + { + thread MoveZoneAfterTime( level.zoneAutoMoveTime ); + setMatchFlag( "bomb_timer_a", 1 ); + SetBombTimer( "A", int( gettime() + 1000 + level.zoneAutoMoveTime * 1000 ) ); + } + else + { + level.zoneDestroyedByTimer = false; + } + + KothCaptureLoop(); + + ownerTeam = level.zone.gameobject gameobjects::get_owner_team(); + + if ( level.zone.gameobject.captureCount == 1 ) + { + // Copy touch list so there aren't any threading issues + touchList = []; + touchKeys = GetArrayKeys( level.zone.gameobject.touchList[ownerTeam] ); + for ( i = 0 ; i < touchKeys.size ; i++ ) + touchList[touchKeys[i]] = level.zone.gameobject.touchList[ownerTeam][touchKeys[i]]; + thread give_held_credit( touchList ); + } + + pause_time(); + + level.zone spawning::enable_influencers(false); + level.zone.gameobject.lastCaptureTeam = undefined; + level.zone.gameobject gameobjects::disable_object(); + level.zone.gameobject gameobjects::allow_use( "none" ); + level.zone.gameobject gameobjects::set_owner_team( "neutral" ); + level.zone.gameobject gameobjects::set_model_visibility( false ); + level.zone.gameobject gameobjects::must_maintain_claim( false ); + level.zone toggleZoneEffects( false ); + + level notify("zone_reset"); + + setMatchFlag( "bomb_timer_a", 0 ); + spawn_next_zone(); + + wait 0.5; + + thread forceSpawnTeam( ownerTeam ); + + wait 0.5; + } +} + + +function hideTimerDisplayOnGameEnd() +{ + level waittill("game_ended"); + setMatchFlag( "bomb_timer_a", 0 ); +} + + +function forceSpawnTeam( team ) +{ + players = level.players; + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + if ( !isdefined( player ) ) + continue; + + if ( player.pers["team"] == team ) + { + player notify( "force_spawn" ); + wait .1; + } + } +} + +function updateTeamClientField() +{ + ownerTeam = self gameobjects::get_owner_team(); + + if ( ( isdefined( self.isContested ) && self.isContested ) ) + { + level clientfield::set( "hardpointteam", 3 ); + } + else if ( ownerTeam == "neutral" ) + { + level clientfield::set( "hardpointteam", 0 ); + } + else + { + if ( ownerTeam == "allies" ) + level clientfield::set( "hardpointteam", 1 ); + else + level clientfield::set( "hardpointteam", 2 ); + } +} + +function onBeginUse( player ) +{ + ownerTeam = self gameobjects::get_owner_team(); + + if ( ownerTeam == "neutral" ) + { + player thread battlechatter::gametype_specific_battle_chatter( "hq_protect", player.pers["team"] ); + } + else + { + player thread battlechatter::gametype_specific_battle_chatter( "hq_attack", player.pers["team"] ); + } +} + + +function onEndUse( team, player, success ) +{ + player notify( "event_ended" ); +} + + +function onZoneCapture( player ) +{ + capture_team = player.pers["team"]; + captureTime = getTime(); + + /#print( "zone captured" );#/ + + pause_time(); + + string = &"MP_KOTH_CAPTURED_BY"; + + level.zone.gameobject.isContested = false; + level.useStartSpawns = false; + + if ( !isdefined( self.lastCaptureTeam ) || self.lastCaptureTeam != capture_team ) + { + // Copy touch list so there aren't any threading issues + touchList = []; + touchKeys = GetArrayKeys( self.touchList[capture_team] ); + for ( i = 0 ; i < touchKeys.size ; i++ ) + touchList[touchKeys[i]] = self.touchList[capture_team][touchKeys[i]]; + thread give_capture_credit( touchList, string, captureTime, capture_team, self.lastCaptureTeam ); + } + + level.kothCapTeam = capture_team; + + self gameobjects::set_owner_team( capture_team ); + if ( !level.kothMode ) + self gameobjects::set_use_time( level.destroyTime ); + + foreach( team in level.teams ) + { + if ( team == capture_team ) + { + if ( !isdefined( self.lastCaptureTeam ) || ( self.lastCaptureTeam != team ) ) // If retaking this point after being contested, don't play VO again + { + globallogic_audio::leader_dialog( "kothSecured", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" ); + for ( index = 0; index < level.players.size; index++ ) + { + player = level.players[index]; + + if ( player.pers["team"] == team ) + { + if ( player.lastKilltime + 500 > getTime() ) + { + player challenges::killedLastContester(); + } + } + } + } + thread sound::play_on_players( game["objective_gained_sound"], team ); + } + else + { + if ( !isdefined( self.lastCaptureTeam ) ) + { + globallogic_audio::leader_dialog( "kothCaptured", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" ); + } + else if ( self.lastCaptureTeam == team ) + { + globallogic_audio::leader_dialog( "kothLost", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" ); + } + thread sound::play_on_players( game["objective_lost_sound"], team ); + } + } + + self thread awardCapturePoints( capture_team, self.lastCaptureTeam ); + self.captureCount++; + self.lastCaptureTeam = capture_team; + + self gameobjects::must_maintain_claim( true ); + + self updateTeamClientField(); + + player RecordGameEvent( "hardpoint_captured" ); + + level notify( "zone_captured" ); + level notify( "zone_captured" + capture_team ); + player notify( "event_ended" ); +} + +function track_capture_time() +{ +} + +function give_capture_credit( touchList, string, captureTime, capture_team, lastCaptureTeam ) +{ + wait .05; + util::WaitTillSlowProcessAllowed(); + + players = getArrayKeys( touchList ); + for ( i = 0; i < players.size; i++ ) + { + player = touchList[players[i]].player; + + player updateCapsPerMinute( lastCaptureTeam ); + + if ( !isScoreBoosting( player ) ) + { + player challenges::capturedObjective( captureTime, self.trigger ); + if ( level.kothStartTime + 3000 > captureTime && level.kothCapTeam == capture_team ) + { + scoreevents::processScoreEvent( "quickly_secure_point", player ); + } + + scoreevents::processScoreEvent( "koth_secure", player ); + player RecordGameEvent("capture"); + + level thread popups::DisplayTeamMessageToAll( string, player ); + + if( isdefined(player.pers["captures"]) ) + { + player.pers["captures"]++; + player.captures = player.pers["captures"]; + } + + if ( level.kothStartTime + 500 > captureTime ) + { + player challenges::immediateCapture(); + } + + demo::bookmark( "event", gettime(), player ); + player AddPlayerStatWithGameType( "CAPTURES", 1 ); + } + else + { + /# + player IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU CAPTURE CREDIT AS BOOSTING PREVENTION" ); + #/ + } + } +} + +function give_held_credit( touchList, team ) +{ + wait .05; + util::WaitTillSlowProcessAllowed(); + + players = getArrayKeys( touchList ); + for ( i = 0; i < players.size; i++ ) + { + player = touchList[players[i]].player; + + //scoreevents::processScoreEvent( "koth_held", player ); + + // do not know if we want the following + //player RecordGameEvent("held"); + + } +} + +function onZoneDestroy( player ) +{ + destroyed_team = player.pers["team"]; + + /#print( "zone destroyed" );#/ + scoreevents::processScoreEvent( "zone_destroyed", player ); + player RecordGameEvent("destroy"); + player AddPlayerStatWithGameType( "DESTRUCTIONS", 1 ); + + if( isdefined(player.pers["destructions"]) ) + { + player.pers["destructions"]++; + player.destructions = player.pers["destructions"]; + } + + destroyTeamMessage = &"MP_HQ_DESTROYED_BY"; + otherTeamMessage = &"MP_HQ_DESTROYED_BY_ENEMY"; + + if ( level.kothMode ) + { + destroyTeamMessage = &"MP_KOTH_CAPTURED_BY"; + otherTeamMessage = &"MP_KOTH_CAPTURED_BY_ENEMY"; + } + + level thread popups::DisplayTeamMessageToAll( destroyTeamMessage, player ); + + foreach( team in level.teams ) + { + if ( team == destroyed_team ) + { + globallogic_audio::leader_dialog( "koth_secured", team, undefined, "gamemode_objective" ); + } + else + { + globallogic_audio::leader_dialog( "koth_destroyed", team, undefined, "gamemode_objective" ); + } + } + + level notify( "zone_destroyed", destroyed_team ); + + if ( level.kothMode ) + level thread awardCapturePoints( destroyed_team ); + + player notify( "event_ended" ); +} + +function onZoneUnoccupied() +{ + level notify( "zone_destroyed" ); + level.kothCapTeam = "neutral"; + level.zone.gameobject.wasLeftUnoccupied = true; + level.zone.gameobject.isContested = false; + + level.zone.gameobject RecordGameEventNonPlayer( "hardpoint_empty" ); + + resume_time(); + + self updateTeamClientField(); +} + +function onZoneContested() +{ + zoneOwningTeam = self gameobjects::get_owner_team(); + self.wasContested = true; + self.isContested = true; + + self updateTeamClientField(); + + self RecordGameEventNonPlayer( "hardpoint_contested" ); + + resume_time(); + + foreach( team in level.teams ) + { + if ( team == zoneOwningTeam ) + { + thread sound::play_on_players( game["objective_contested_sound"], team ); + globallogic_audio::leader_dialog( "kothContested", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" ); + } + } +} + +function onZoneUncontested( lastClaimTeam ) +{ + assert( lastClaimTeam == level.zone.gameobject gameobjects::get_owner_team() ); + + self.isContested = false; + + pause_time(); + + self gameobjects::set_claim_team( lastClaimTeam ); + + self updateTeamClientField(); + + self RecordGameEventNonPlayer( "hardpoint_uncontested" ); +} + +function MoveZoneAfterTime( time ) +{ + level endon( "game_ended" ); + level endon( "zone_reset" ); + + level.zoneMoveTime = gettime() + time * 1000; + level.zoneDestroyedByTimer = false; + + wait time; + + if ( !isdefined( level.zone.gameobject.wasContested ) || level.zone.gameobject.wasContested == false ) + { + if ( !isdefined( level.zone.gameobject.wasLeftUnoccupied ) || level.zone.gameobject.wasLeftUnoccupied == false ) + { + zoneOwningTeam = level.zone.gameobject gameobjects::get_owner_team(); + challenges::controlZoneEntirely( zoneOwningTeam ); + } + } + + level.zoneDestroyedByTimer = true; + + level.zone.gameobject RecordGameEventNonPlayer( "hardpoint_moved" ); + + level notify( "zone_moved" ); +} + + +function awardCapturePoints( team, lastCaptureTeam ) +{ + level endon( "game_ended" ); + level endon( "zone_destroyed" ); + level endon( "zone_reset" ); + level endon( "zone_moved" ); + + level notify("awardCapturePointsRunning"); + level endon("awardCapturePointsRunning"); + + seconds = 1; + score = 1; + + while ( !level.gameEnded ) + { + wait seconds; + + hostmigration::waitTillHostMigrationDone(); + + if ( !level.zone.gameobject.isContested ) + { + if ( level.scorePerPlayer ) + { + score = level.zone.gameobject.numTouching[team]; + } + + globallogic_score::giveTeamScoreForObjective( team, score ); + level.kothTotalSecondsInZone++; + + foreach( player in level.aliveplayers[team] ) + { + if ( !IsDefined( player.touchTriggers[self.entNum] ) ) + continue; + + if( isdefined(player.pers["objtime"]) ) + { + player.pers["objtime"]++; + player.objtime = player.pers["objtime"]; + } + player AddPlayerStatWithGameType( "OBJECTIVE_TIME", 1 ); + } + } + } +} + +function koth_playerSpawnedCB() +{ + self.lowerMessageOverride = undefined; +} + +function CompareZoneIndexes( zone_a, zone_b ) +{ + script_index_a = zone_a.script_index; + script_index_b = zone_b.script_index; + + if( !isdefined(script_index_a) && !isdefined(script_index_b) ) + { + return false; + } + + if( !isdefined(script_index_a) && isdefined(script_index_b) ) + { +/# + println( "KOTH: Missing script_index on zone at " + zone_a.origin ); +#/ + return true; + } + + if( isdefined(script_index_a) && !isdefined(script_index_b) ) + { +/# + println( "KOTH: Missing script_index on zone at " + zone_b.origin ); +#/ + return false; + } + + if( script_index_a > script_index_b ) + { + return true; + } + + return false; +} + + +function getZoneArray() +{ + zones = getentarray( "koth_zone_center", "targetname" ); + + if( !isdefined( zones ) ) + { + return undefined; + } + + swapped = true; + n = zones.size; + while ( swapped ) + { + swapped = false; + for( i = 0 ; i < n-1 ; i++ ) + { + if( CompareZoneIndexes(zones[i], zones[i+1]) ) + { + temp = zones[i]; + zones[i] = zones[i+1]; + zones[i+1] = temp; + swapped = true; + } + } + n--; + } + return zones; +} + + +function SetupZones() +{ + maperrors = []; + + zones = getZoneArray(); + +// if ( zones.size < 2 ) +// { +// maperrors[maperrors.size] = "There are not at least 2 entities with targetname \"zone\""; +// } + + trigs = getentarray("koth_zone_trigger", "targetname"); + for ( i = 0; i < zones.size; i++ ) + { + errored = false; + + zone = zones[i]; + zone.trig = undefined; + for ( j = 0; j < trigs.size; j++ ) + { + if ( zone istouching( trigs[j] ) ) + { + if ( isdefined( zone.trig ) ) + { + maperrors[maperrors.size] = "Zone at " + zone.origin + " is touching more than one \"zonetrigger\" trigger"; + errored = true; + break; + } + zone.trig = trigs[j]; + break; + } + } + + if ( !isdefined( zone.trig ) ) + { + if ( !errored ) + { + maperrors[maperrors.size] = "Zone at " + zone.origin + " is not inside any \"zonetrigger\" trigger"; + continue; + } + + // possible fallback (has been tested) + //zone.trig = spawn( "trigger_radius", zone.origin, 0, 128, 128 ); + //errored = false; + } + + assert( !errored ); + + zone.trigorigin = zone.trig.origin; + + zone.objectiveAnchor = Spawn( "script_model", zone.origin ); // We need a script_model to attach the objective to + + visuals = []; + visuals[0] = zone; + + if ( isdefined( zone.target ) ) + { + otherVisuals = getEntArray( zone.target, "targetname" ); + for ( j = 0; j < otherVisuals.size; j++ ) + { + visuals[visuals.size] = otherVisuals[j]; + } + } + + objective_name = istring("hardpoint"); + + zone.gameObject = gameobjects::create_use_object( "neutral", zone.trig, visuals, (0,0,0), objective_name ); + zone.gameObject gameobjects::set_objective_entity( zone ); + zone.gameObject gameobjects::disable_object(); + zone.gameObject gameobjects::set_model_visibility( false ); + zone.trig.useObj = zone.gameObject; + zone.trig.remote_control_player_can_trigger = true; + zone setUpNearbySpawns(); + zone createZoneSpawnInfluencer(); + } + + if (maperrors.size > 0) + { + /# + println("^1------------ Map Errors ------------"); + for(i = 0; i < maperrors.size; i++) + println(maperrors[i]); + println("^1------------------------------------"); + + util::error("Map errors. See above"); + #/ + callback::abort_level(); + + return; + } + + level.zones = zones; + + level.prevzone = undefined; + level.prevzone2 = undefined; + + setupZoneExclusions(); + + return true; +} + +function setupZoneExclusions() +{ + if ( !isdefined( level.levelkothDisable ) ) + return; + +/* foreach( nullZone in level.levelkothDisable ) + { + foreach( zone in level.zones ) + { +// if ( zone.gameObject.trigger istouching( nullZone ) ) + if ( zone.gameObject.trigger istouchingvolume( nullZone.origin, nullZone getmins(), nullZone getmaxs() ) ) + { + if ( !isdefined( zone.gameObject.exclusions ) ) + { + zone.gameObject.exclusions = []; + } + + zone.gameObject.exclusions[ zone.gameObject.exclusions.size ] = nullZone; + } + } + } +*/ + + foreach( nullZone in level.levelkothDisable ) + { + mindist = 10000000000; + foundZone = undefined; + + foreach( zone in level.zones ) + { + distance = DistanceSquared( nullZone.origin, zone.origin ); + + if ( distance < mindist ) + { + foundZone = zone; + mindist = distance; + } + } + + if ( isdefined( foundZone ) ) + { + if ( !isdefined( foundZone.gameObject.exclusions ) ) + { + foundZone.gameObject.exclusions = []; + } + foundZone.gameObject.exclusions[ foundZone.gameObject.exclusions.size ] = nullZone; + } + } + +} + +function setUpNearbySpawns() +{ + spawns = level.spawn_all; + + for ( i = 0; i < spawns.size; i++ ) + { + spawns[i].distsq = distanceSquared( spawns[i].origin, self.origin ); + } + + // sort by distsq + for ( i = 1; i < spawns.size; i++ ) + { + thespawn = spawns[i]; + for ( j = i - 1; j >= 0 && thespawn.distsq < spawns[j].distsq; j-- ) + spawns[j + 1] = spawns[j]; + spawns[j + 1] = thespawn; + } + + first = []; + second = []; + third = []; + outer = []; + + thirdSize = spawns.size / 3; + for ( i = 0; i <= thirdSize; i++ ) + { + first[ first.size ] = spawns[i]; + } + for ( ; i < spawns.size; i++ ) + { + outer[ outer.size ] = spawns[i]; + if ( i <= (thirdSize*2) ) + second[ second.size ] = spawns[i]; + else + third[ third.size ] = spawns[i]; + } + + self.gameObject.nearSpawns = first; + self.gameObject.midSpawns = second; + self.gameObject.farSpawns = third; + self.gameObject.outerSpawns = outer; +} + +function GetFirstZone() +{ + zone = level.zones[ 0 ]; + + // old linear and "random" systems + level.prevzone2 = level.prevzone; + level.prevzone = zone; + level.prevZoneIndex = 0; + + // new shuffled system + ShuffleZones(); + ArrayRemoveValue( level.zoneSpawnQueue, zone ); + + return zone; +} + +function GetNextZone() +{ + nextZoneIndex = (level.prevZoneIndex + 1) % level.zones.size; + zone = level.zones[ nextZoneIndex ]; + level.prevzone2 = level.prevzone; + level.prevzone = zone; + level.prevZoneIndex = nextZoneIndex; + + return zone; +} + +function PickRandomZoneToSpawn() +{ + level.prevZoneIndex = randomint( level.zones.size); + zone = level.zones[ level.prevZoneIndex ]; + level.prevzone2 = level.prevzone; + level.prevzone = zone; + + return zone; +} + +function ShuffleZones() +{ + level.zoneSpawnQueue = []; + + spawnQueue = ArrayCopy(level.zones); + total_left = spawnQueue.size; + + while( total_left > 0 ) + { + index = randomint( total_left ); + + valid_zones = 0; + for( zone = 0; zone < level.zones.size; zone++ ) + { + if ( !isdefined(spawnQueue[zone]) ) + continue; + + if ( valid_zones == index ) + { + // dont allow the last radio from the previous shuffle to be put first in the next + if ( level.zoneSpawnQueue.size == 0 && isdefined( level.zone ) && level.zone == spawnQueue[zone] ) + continue; + + level.zoneSpawnQueue[level.zoneSpawnQueue.size] = spawnQueue[zone]; + spawnQueue[zone] = undefined; + break; + } + + valid_zones++; + } + + total_left--; + } +} + +// shuffled picking +function GetNextZoneFromQueue() +{ + if ( level.zoneSpawnQueue.size == 0 ) + ShuffleZones(); + + assert( level.zoneSpawnQueue.size > 0 ); + + next_zone = level.zoneSpawnQueue[0]; + ArrayRemoveIndex( level.zoneSpawnQueue, 0 ); + + return next_zone; +} + +function GetCountOfTeamsWithPlayers(num) +{ + has_players = 0; + + foreach( team in level.teams ) + { + if ( num[team] > 0 ) + has_players++; + } + + return has_players; +} + +function GetPointCost( avgpos, origin ) +{ + avg_distance = 0; + total_error = 0; + distances = []; + + foreach( team, position in avgpos ) + { + distances[team] = Distance(origin, avgpos[team]); + avg_distance += distances[team]; + } + + avg_distance = avg_distance / distances.size; + + foreach( team, dist in distances ) + { + err = (distances[team] - avg_distance); + total_error += err * err; + } + + return total_error; +} + +function PickZoneToSpawn() +{ + // find average of positions of each team + // (medians would be better, to get rid of outliers...) + // and find the zone which has the least difference in distance from those two averages + + foreach( team in level.teams ) + { + avgpos[team] = (0,0,0); + num[team] = 0; + } + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + if ( isalive( player ) ) + { + avgpos[ player.pers["team"] ] += player.origin; + num[ player.pers["team"] ]++; + } + } + + if ( GetCountOfTeamsWithPlayers(num) <= 1 ) + { + zone = level.zones[ randomint( level.zones.size) ]; + while ( isdefined( level.prevzone ) && zone == level.prevzone ) // so lazy + zone = level.zones[ randomint( level.zones.size) ]; + + level.prevzone2 = level.prevzone; + level.prevzone = zone; + + return zone; + } + + foreach( team in level.teams ) + { + if ( num[team] == 0 ) + { + avgpos[team] = undefined; + } + else + { + avgpos[team] = avgpos[team] / num[team]; + } + } + + bestzone = undefined; + lowestcost = undefined; + for ( i = 0; i < level.zones.size; i++ ) + { + zone = level.zones[i]; + + // (purposefully using distance instead of distanceSquared) + cost = GetPointCost( avgpos, zone.origin ); + + if ( isdefined( level.prevzone ) && zone == level.prevzone ) + { + continue; + } + if ( isdefined( level.prevzone2 ) && zone == level.prevzone2 ) + { + if ( level.zones.size > 2 ) + continue; + else + cost += 512 * 512; + } + + if ( !isdefined( lowestcost ) || cost < lowestcost ) + { + lowestcost = cost; + bestzone = zone; + } + } + assert( isdefined( bestzone ) ); + + level.prevzone2 = level.prevzone; + level.prevzone = bestzone; + + return bestzone; +} + +function onRoundSwitch() +{ + game["switchedsides"] = !game["switchedsides"]; +} + + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( !isPlayer( attacker ) || (level.captureTime && !self.touchTriggers.size && !attacker.touchTriggers.size) || attacker.pers["team"] == self.pers["team"] ) + return; + + medalGiven = false; + scoreEventProcessed = false; + + ownerTeam = undefined; + + if ( level.captureTime == 0 ) + { + if ( !isdefined( level.zone ) ) + return; + + ownerTeam = level.zone.gameObject.ownerTeam ; + + if ( !isdefined( ownerTeam ) || ownerTeam == "neutral" ) + return; + } + + if ( self.touchTriggers.size || ( level.captureTime == 0 && self IsTouching( level.zone.trig ) ) ) + { + if ( level.captureTime > 0 ) + { + triggerIds = getArrayKeys( self.touchTriggers ); + ownerTeam = self.touchTriggers[triggerIds[0]].useObj.ownerTeam; + } + + if ( ownerTeam != "neutral" ) + { + attacker.lastKilltime = getTime(); + team = attacker.pers["team"]; + if ( team == ownerTeam ) + { + if ( !medalGiven ) + { + attacker medals::offenseGlobalCount(); + attacker thread challenges::killedBaseOffender(level.zone.trig, weapon); + + medalGiven = true; + } + //scoreevents::processScoreEvent( "killed_defender", attacker, undefined, weapon );// TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint + scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon ); + self RecordKillModifier("defending"); + scoreEventProcessed = true; + } + else + { + if ( !medalGiven ) + { + if( isdefined(attacker.pers["defends"]) ) + { + attacker.pers["defends"]++; + attacker.defends = attacker.pers["defends"]; + } + + attacker medals::defenseGlobalCount(); + medalGiven = true; + attacker thread challenges::killedBaseDefender(level.zone.trig); + attacker RecordGameEvent("defending"); + } + attacker challenges::killedZoneAttacker( weapon ); + //scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon ); // TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint + scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon ); + self RecordKillModifier("assaulting"); + scoreEventProcessed = true; + } + } + } + + if ( attacker.touchTriggers.size || ( level.captureTime == 0 && attacker IsTouching( level.zone.trig ) ) ) + { + if ( level.captureTime > 0 ) + { + triggerIds = getArrayKeys( attacker.touchTriggers ); + ownerTeam = attacker.touchTriggers[triggerIds[0]].useObj.ownerTeam; + } + + if ( ownerTeam != "neutral" ) + { + team = self.pers["team"]; + if ( team == ownerTeam ) + { + if ( !medalGiven ) + { + if( isdefined(attacker.pers["defends"]) ) + { + attacker.pers["defends"]++; + attacker.defends = attacker.pers["defends"]; + } + + attacker medals::defenseGlobalCount(); + medalGiven = true; + attacker thread challenges::killedBaseDefender(level.zone.trig); + attacker RecordGameEvent("defending"); + } + if ( scoreEventProcessed == false ) + { + attacker challenges::killedZoneAttacker( weapon ); + //scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon );// TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint + scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon ); + self RecordKillModifier("assaulting"); + } + } + else + { + if ( !medalGiven ) + { + attacker medals::offenseGlobalCount(); + medalGiven = true; + attacker thread challenges::killedBaseOffender(level.zone.trig, weapon); + } + if ( scoreEventProcessed == false ) + { + //scoreevents::processScoreEvent( "killed_defender", attacker, undefined, weapon );// TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint + scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon ); + self RecordKillModifier("defending"); + } + } + } + } + + if ( medalGiven == true ) + { + if ( ( isdefined( level.zone.gameobject.isContested ) && level.zone.gameobject.isContested ) ) + { + attacker thread killWhileContesting(); + } + } +} + +function watchKillWhileContesting( zone_captured_team ) +{ + level endon( zone_captured_team ); + level endon( "zone_destroyed" ); + level endon( "zone_captured" ); + level endon( "death" ); + + self util::waittill_any_return( "killWhileContesting", "disconnect" ); + level notify( "abortKillWhileContesting" ); +} + +function killWhileContesting() +{ + self notify( "killWhileContesting" ); + self endon( "killWhileContesting" ); + self endon( "disconnect" ); + + killTime = getTime(); + playerteam = self.pers["team"]; + if ( !isdefined ( self.clearEnemyCount ) ) + { + self.clearEnemyCount = 0; + } + + self.clearEnemyCount++; + + zone_captured_team = "zone_captured" + playerteam; + self thread watchKillWhileContesting( zone_captured_team ); + zoneReturn = level util::waittill_any_return( zone_captured_team, "zone_destroyed", "zone_captured", "death", "abortKillWhileContesting" ); + + if ( zoneReturn == "death" || playerteam != self.pers["team"] ) + { + self.clearEnemyCount = 0; + return; + } + + if ( self.clearEnemyCount >= 2 && killTime + 200 > getTime() ) + { + scoreevents::processScoreEvent( "clear_2_attackers", self ); + } + self.clearEnemyCount = 0; +} + +function onEndGame( winningTeam ) +{ + for ( i = 0; i < level.zones.size; i++ ) + { + level.zones[i].gameobject gameobjects::allow_use( "none" ); + } +} + +function createZoneSpawnInfluencer() +{ + // this affects both teams + self spawning::create_influencer( "koth_large", self.gameobject.curOrigin, 0 ); + self spawning::create_influencer( "koth_small", self.gameobject.curOrigin, 0 ); + + // turn it off for now + self spawning::enable_influencers(false); +} + +function updateCapsPerMinute(lastOwnerTeam) +{ + if ( !isdefined( self.capsPerMinute ) ) + { + self.numCaps = 0; + self.capsPerMinute = 0; + } + + // not including neutral flags as part of the boosting prevention + // to help with false positives at the start + if ( !isdefined ( lastOwnerTeam ) || lastOwnerTeam == "neutral" ) + return; + + self.numCaps++; + + minutesPassed = globallogic_utils::getTimePassed() / ( 60 * 1000 ); + + // players use the actual time played + if ( IsPlayer( self ) && isdefined(self.timePlayed["total"]) ) + minutesPassed = self.timePlayed["total"] / 60; + + self.capsPerMinute = ( minutesPassed ? self.numCaps / minutesPassed : 0 ); + if ( self.capsPerMinute > self.numCaps ) + self.capsPerMinute = self.numCaps; +} + +function isScoreBoosting( player ) +{ + if ( !level.rankedMatch ) + return false; + + if ( player.capsPerMinute > level.playerCaptureLPM ) + return true; + + return false; +} diff --git a/mp/gametypes/prop.csc b/mp/gametypes/prop.csc new file mode 100644 index 0000000..63c44bb --- /dev/null +++ b/mp/gametypes/prop.csc @@ -0,0 +1,293 @@ +#using scripts\codescripts\struct; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\duplicaterender_mgr; +#using scripts\shared\util_shared; +#using scripts\mp\_callbacks; + + + + + +function main() +{ + clientfield::register( "allplayers", "hideTeamPlayer", 27000, 2, "int", &hideShowPlayerFromTeam, false, false ); + clientfield::register( "allplayers", "pingHighlight", 27000, 1, "int", &highlightPlayer, false, false ); + + callback::on_localplayer_spawned( &onLocalplayerSpawned ); + + level.retrievableChangedFunc = &hideProp; + level.enemyEquipChangedFunc = &highlightProp; + + thread teamChangedReNotify(); +} + +function onLocalplayerSpawned( localClientNum ) +{ + level notify( "localPlayerSpectatingEnd" + localClientNum ); + + if ( IsSpectating( localClientNum, false ) ) + { + level thread localPlayerSpectating( localClientNum ); + } + + level thread setupPropPlayerNames( localClientNum ); +} + +function localPlayerSpectating( localClientNum ) +{ + level notify( "localPlayerSpectating" + localClientNum ); + level endon( "localPlayerSpectatingEnd" + localClientNum ); + + lastPlayerSpectated = PlayerBeingSpectated( localClientNum ); + while ( true ) + { + player = PlayerBeingSpectated( localClientNum ); + + if ( player != lastPlayerSpectated ) + { + level notify( "localPlayerSpectating" + localClientNum ); + } + + wait 0.1; + } +} + +function teamChangedReNotify() +{ + while ( true ) + { + level waittill( "team_changed", localClientNum ); + level notify( "team_changed" + localClientNum ); + } +} + +function entityIsPlayerALinkedParent( player ) +{ + parent = self GetLinkedEnt(); + while ( isdefined( parent ) ) + { + if ( parent == player ) + return true; + else + parent = parent GetLinkedEnt(); + } + + return false; +} + +function getPlayersProp( localClientNum, player ) +{ + if ( isdefined( player.prop ) ) + return player.prop; + + ents = GetEntArray( localClientNum ); + foreach ( ent in ents ) + { + if ( !ent IsPlayer() && isdefined( ent.owner ) && ent.owner == player && ent entityIsPlayerALinkedParent( player ) ) + { + return ent; + } + } +} + +function setupPropPlayerNames( localClientNum ) +{ + level notify( "setupPropPlayerNames" + localClientNum ); + level endon( "setupPropPlayerNames" + localClientNum ); + + while ( true ) + { + localPlayer = GetLocalPlayer( localClientNum ); + + spectating = IsSpectating( localClientNum, false ); + + players = GetPlayers( localClientNum ); + foreach ( player in players ) + { + if ( ( player != localPlayer || spectating ) && player IsHidden() && isdefined( player.team ) && ( player.team == localPlayer.team ) ) + { + player.prop = getPlayersProp( localClientNum, player ); + + if ( isdefined( player.prop ) ) + { + if ( !( isdefined( player.nameRendering ) && player.nameRendering ) ) + { + player.prop SetDrawOwnerName( true, spectating ); + player.nameRendering = true; + } + } + } + else if ( ( isdefined( player.nameRendering ) && player.nameRendering ) ) + { + player.prop = getPlayersProp( localClientNum, player ); + + if ( isdefined( player.prop ) ) + player.prop SetDrawOwnerName( false, spectating ); + player.nameRendering = false; + } + } + + wait 1; + } +} + +function highlightProp( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( newVal == 0 ) + { + self notify( "highlightPropTeamChange" ); + self duplicate_render::update_dr_flag( localClientNum, "prop_ally", false ); + self duplicate_render::update_dr_flag( localClientNum, "prop_clone", false ); + } + else + { + self thread highlightPropTeamChange( localClientNum, newVal ); + } +} + +function highlightPropTeamChange( localClientNum, highlightType ) +{ + self endon( "entityshutdown" ); + level endon( "disconnect" ); + self notify( "highlightPropTeamChange" ); + self endon( "highlightPropTeamChange" ); + + while ( 1 ) + { + localPlayer = GetLocalPlayer( localClientNum ); + + spectating = IsSpectating( localClientNum, false ) && !GetInKillcam( localClientNum ); + isHighlighted = ( ( !isdefined( self.owner ) || ( self.owner != localPlayer ) || spectating ) && isdefined( self.team ) && isdefined( localPlayer.team ) && self.team == localPlayer.team ); + + if ( highlightType == 1 ) + { + self duplicate_render::update_dr_flag( localClientNum, "prop_ally", isHighlighted ); + self duplicate_render::update_dr_flag( localClientNum, "prop_clone", false ); + } + else + { + self duplicate_render::update_dr_flag( localClientNum, "prop_clone", isHighlighted ); + self duplicate_render::update_dr_flag( localClientNum, "prop_ally", false ); + } + + self duplicate_render::update_dr_filters( localClientNum ); + + level util::waittill_any( "team_changed" + localClientNum, "localPlayerSpectating" + localClientNum ); + } +} + +function highlightPlayer( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( newVal == 0 ) + { + self notify( "highlightPropTeamChange" ); + self duplicate_render::update_dr_flag( localClientNum, "prop_clone", false ); + } + else + { + self thread highlightPlayerTeamChange( localClientNum, newVal ); + } +} + +function highlightPlayerTeamChange( localClientNum, highlightType ) +{ + self endon( "entityshutdown" ); + self notify( "highlightPlayerTeamChange" ); + self endon( "highlightPlayerTeamChange" ); + + while ( 1 ) + { + localPlayer = GetLocalPlayer( localClientNum ); + + isHighlighted = ( self != localPlayer && isdefined( self.team ) && isdefined( localPlayer.team ) && self.team == localPlayer.team ); + + self duplicate_render::update_dr_flag( localClientNum, "prop_clone", isHighlighted ); + + level util::waittill_any( "team_changed" + localClientNum, "localPlayerSpectating" + localClientNum ); + } +} + +function hideProp( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + localPlayer = GetLocalPlayer( localClientNum ); + + isHighlighted = ( newVal && isdefined( self.owner ) && self.owner == localPlayer ); + + if ( isHighlighted ) + { + self duplicate_render::update_dr_flag( localClientNum, "prop_look_through", true ); + self duplicate_render::set_dr_flag( "hide_model", true ); + self duplicate_render::set_dr_flag( "active_camo_reveal", false ); + self duplicate_render::set_dr_flag( "active_camo_on", true ); + self duplicate_render::update_dr_filters( localClientNum ); + } + else + { + self duplicate_render::update_dr_flag( localClientNum, "prop_look_through", false ); + self duplicate_render::set_dr_flag( "hide_model", false ); + self duplicate_render::set_dr_flag( "active_camo_reveal", false ); + self duplicate_render::set_dr_flag( "active_camo_on", false ); + self duplicate_render::update_dr_filters( localClientNum ); + } +} + +function hideShowPlayerFromTeam( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( newVal == 0 ) + { + self notify( "hideShowFromTeam" ); + if ( !self IsPlayerProp( localClientNum ) ) + self Show(); + } + else + { + self hideShowFromTeamChange( localClientNum, newVal ); + } +} + +function IsPlayerProp( localClientNum ) +{ + if ( isdefined( self.prop ) ) + return true; + + if ( self IsPlayer() ) + { + self.prop = getPlayersProp( localClientNum, self ); + return ( isdefined( self.prop ) ); + } + + return false; +} + +function hideShowFromTeamChange( localClientNum, teamInt ) +{ + self endon( "entityshutdown" ); + self notify( "hideShowFromTeam" ); + self endon( "hideShowFromTeam" ); + + assert( teamInt == 1 || teamInt == 2 ); + + team = "allies"; + if ( teamInt == 2 ) + team = "axis"; + + while ( 1 ) + { + localPlayer = GetLocalPlayer( localClientNum ); + + isHidden = ( isdefined( localPlayer.team ) && team == localPlayer.team && !IsSpectating( localClientNum ) ); + + if ( isHidden ) + { + self Hide(); + } + else + { + if ( !self IsPlayerProp( localClientNum ) ) + self Show(); + } + + level waittill( "team_changed" + localClientNum ); + } +} \ No newline at end of file diff --git a/mp/gametypes/prop.gsc b/mp/gametypes/prop.gsc new file mode 100644 index 0000000..3fccb43 --- /dev/null +++ b/mp/gametypes/prop.gsc @@ -0,0 +1,3659 @@ +#using scripts\mp\_teamops; +#using scripts\mp\_util; +#using scripts\mp\bots\_bot; +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_deathicons; +#using scripts\mp\gametypes\_dogtags; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_defaults; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_globallogic_ui; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_killcam; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_prop_controls; +#using scripts\mp\gametypes\_prop_dev; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\teams\_teams; +#using scripts\shared\ai_shared; +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\fx_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weapons; + + + + + +#namespace prop; + +#precache( "string", "OBJECTIVES_PH_ATTACKER" ); +#precache( "string", "OBJECTIVES_PH_DEFENDER" ); +#precache( "string", "OBJECTIVES_PH_ATTACKER_SCORE" ); +#precache( "string", "OBJECTIVES_PH_DEFENDER_SCORE" ); +#precache( "string", "OBJECTIVES_PH_ATTACKER_HINT" ); +#precache( "string", "OBJECTIVES_PH_DEFENDER_HINT" ); +#precache( "string", "MP_PH_STARTS_IN" ); +#precache( "string", "MP_PH_WHISTLE_IN" ); +#precache( "string", "MP_PH_WHISTLING" ); +#precache( "string", "MP_PH_ALIVE" ); +#precache( "string", "MP_PH_FINAL_WHISTLE" ); +#precache( "string", "MP_PH_SPIN" ); +#precache( "string", "MP_PH_SPIN_PC" ); +#precache( "string", "MP_PH_SLOPE" ); +#precache( "string", "MP_PH_SLOPE_PC" ); +#precache( "string", "MP_PH_CHANGE" ); +#precache( "string", "MP_PH_CLONE" ); +#precache( "string", "MP_PH_ZOOM" ); +#precache( "string", "MP_PH_SPECKEY" ); +#precache( "string", "MP_PH_SLOPED" ); +#precache( "string", "MP_PH_SLOPED_PC" ); +#precache( "string", "MP_PH_LOCK" ); +#precache( "string", "MP_PH_LOCKED" ); +#precache( "string", "MP_PH_SPECCOMMANDS" ); +#precache( "string", "MP_PH_FLASH" ); +#precache( "string", "MP_PH_HIDEPROP" ); +#precache( "string", "MP_PH_SHOWPROP" ); +#precache( "string", "MP_PH_TIEBREAKER" ); +#precache( "string", "SCORE_LAST_ALIVE" ); +#precache( "string", "MP_PH_TIEBREAKER_KILL" ); +#precache( "string", "MP_PH_TIEBREAKER_TIME" ); +#precache( "string", "MP_PH_MINIGAME_FIRST" ); +#precache( "string", "MP_PH_MINIGAME_SECOND" ); +#precache( "string", "MP_PH_MINIGAME_THIRD" ); +#precache( "string", "MP_PH_PREGAME_HUNT" ); +#precache( "string", "MP_PH_PREGAME_CHASE" ); +#precache( "string", "MP_PH_PING" ); +#precache( "string", "MP_PH_EMPTY" ); +#precache( "string", "SCORE_DECOY_KILLED" ); + +#precache( "material", "t7_hud_waypoints_safeguard_location" ); + +#precache( "fx", "explosions/fx_exp_grenade_concussion" ); +#precache( "fx", "explosions/fx_prop_exp" ); +#precache( "fx", "player/fx_plyr_clone_reaper_appear" ); + +/* + Prop Hunt +*/ + +function autoexec __init__sytem__() { system::register("prop",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "allplayers", "hideTeamPlayer" , 27000, 2, "int" ); + clientfield::register( "allplayers", "pingHighlight" , 27000, 1, "int" ); +} + +function main() +{ + globallogic::init(); + + util::registerRoundSwitch( 0, 9 ); + util::registerTimeLimit( 0, 4 ); + util::registerScoreLimit( 0, 0 ); + util::registerRoundLimit( 0, 4 ); + util::registerRoundWinLimit( 0, 3 ); + util::registerNumLives( 0, 1 ); + + level.phSettings = SpawnStruct(); + level.phSettings.propHideTime = 30; + level.phSettings.propWhistleTime = getMapMaxWhistleTime(); + level.phSettings.propChangeCount = 2; + level.phSettings.propNumFlashes = 1; + level.phSettings.propNumClones = 3; + level.phSettings.propSpeedScale = 1.4; + level.phSettings.hunterNumPings = 2; + level.phSettings.allowLoadouts = false; + level.phSettings.nuketownVariant = ( level.script == "mp_nuketown_x" ); + level.phSettings.altModeManyClones = level.phSettings.nuketownVariant; + + if ( level.phSettings.altModeManyClones ) + { + level.phSettings.propNumClones = 9; + } + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.isPropHunt = true; + level.allow_teamchange = "1"; + level.killstreaksEnabled = false; + level.teamBased = true; + level.overrideTeamScore = true; + level.alwaysUseStartSpawns = true; + level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false ); + level.teamScorePerKill = GetGametypeSetting( "teamScorePerKill" ); + level.teamScorePerDeath = GetGametypeSetting( "teamScorePerDeath" ); + level.teamScorePerHeadshot = GetGametypeSetting( "teamScorePerHeadshot" ); + level.killstreaksGiveGameScore = GetGametypeSetting( "killstreaksGiveGameScore" ); + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.onPlayerDisconnect =&onPlayerDisconnect; + level.onRoundEndGame =&onRoundEndGame; + level.onRoundSwitch =&onRoundSwitch; + level.gameModeAssistedSuicide =&gameModeAssistedSuicide; + level.onPlayerKilled =&onPlayerKilled; + level.onOneLeftEvent =&onOneLeftEvent; + level.onTimeLimit =&onTimeLimit; + level.onDeadEvent =&onDeadEvent; + level.customPlayPainSound =&playDamageSoundPH; + level.customPlayDeathSound =&playDeathSoundPH; + level.overridePlayerDamage =&gamemodeModifyPlayerDamage; + level.gametypeRoundEndScoreHud =&gametypeRoundEndScoreHud; + level.checkSpawnPoint =&checkSpawnPoint; + level.finalizeStartSpawn =&finalizeStartSpawn; + level.maySpawnGameMode =&maySpawnGameMode; + level.givecustomloadout =&giveCustomLoadout; + level.prematchPeriodSpawnFunc =&prematchPeriodSpawn; + level.playerAvoidDamageGameMode =&playerAvoidDamageGameMode; + level.customDamageShellshockAndRumble =&customDamageShellshockAndRumble; + level.determineWinner =&determineWinner; + + level.disableWeaponAccuracyTracking = true; + + gameobjects::register_allowed_gameobject( level.gameType ); + + // Sets the scoreboard columns and determines with data is sent across the network + globallogic::setvisiblescoreboardcolumns( "score", "objtime", "kills", "deaths", "assists" ); + + level.propList = []; + level.propIndex = []; + level.spawnPropList = []; + level.abilities = Array( "FLASH", "CLONE" ); + + populatePropList(); + + level.gracePeriod = Int( level.phSettings.propHideTime + 0.5 ); + + level thread onPlayerConnect(); + level thread delaySet(); + level thread monitorHostmigrationTime(); + + if ( level.phSettings.nuketownVariant ) + level thread manageMannequins(); + + ShatterAllGlass(); + util::set_dvar_int_if_unset( "scr_prop_minigame", 1 ); + + /# + level.alwaysAllowSpawn = false; + thread prop_dev::propDevGui(); + #/ +} + +function getMapMaxWhistleTime() +{ + if ( level.script == "mp_chinatown" ) + { + return 18; + } + else if ( level.script == "mp_redwood" ) + { + return 20; + } + else if ( level.script == "mp_nuketown_x" ) + { + return 0; + } + + return 30; +} + +function delaySet() +{ + {wait(.05);}; + + level.playStartConversation = false; + level.allowSpecialistDialog = false; +} + +function onEndGame( winningTeam ) +{ + if ( isdefined( winningTeam ) && isdefined( level.teams[winningTeam] ) ) + globallogic_score::giveTeamScoreForObjective( winningTeam, 1 ); +} + +function onRoundSwitch() +{ + game["switchedsides"] = !game["switchedsides"]; + + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, game["roundswon"][team] ); + } + } +} + +function onRoundEndGame( finalRoundWinner ) +{ + gameWinner = finalRoundWinner; + + // need to evaluate winner here for round based game modes + if ( level.gameEnded ) + { + gameWinner = determineWinnerInternal( gameWinner, true ); + } + + if ( ( gameWinner == "allies" ) || ( gameWinner == "axis" ) ) + ph_setFinalKillCamWinner( gameWinner ); + + return gameWinner; +} + +function determineWinner( roundWinner ) +{ + return determineWinnerInternal( roundWinner, false ); +} + +function determineWinnerInternal( roundWinner, adjustTieScore ) +{ + gameWinner = roundWinner; + + winEvent = "roundswon"; + + level.propTieBreaker = "none"; + if ( game[winEvent]["allies"] == game[winEvent]["axis"] ) + { + level.propTieBreaker = "kills"; + if ( game["propScore"]["axis"] == game["propScore"]["allies"] ) + { + level.propTieBreaker = "time"; + if ( game["hunterKillTime"]["axis"] == game["hunterKillTime"]["allies"] ) + { + level.propTieBreaker = "tie"; + gameWinner = "tie"; + } + else if ( game["hunterKillTime"]["axis"] < game["hunterKillTime"]["allies"] ) + { + gameWinner = "axis"; + } + else + { + gameWinner = "allies"; + } + } + else if ( game["propScore"]["axis"] > game["propScore"]["allies"] ) + { + gameWinner = "axis"; + } + else + { + gameWinner = "allies"; + } + + + // Give the tie-breaker winner one extra round win to showcase in the AAR (so it doesn't look like a tie) + if ( gameWinner != "tie" && adjustTieScore ) + level thread givePHTeamScore( gameWinner ); + } + else if ( game[winEvent]["axis"] > game[winEvent]["allies"] ) + { + gameWinner = "axis"; + } + else + { + gameWinner = "allies"; + } + + return gameWinner; +} + +function onScoreCloseMusic() +{ + teamScores = []; + + while( !level.gameEnded ) + { + scoreLimit = level.scoreLimit; + scoreThreshold = scoreLimit * .1; + scoreThresholdStart = abs(scoreLimit - scoreThreshold); + scoreLimitCheck = scoreLimit - 10; + + topScore = 0; + runnerUpScore = 0; + foreach( team in level.teams ) + { + score = [[level._getTeamScore]]( team ); + + if ( score > topScore ) + { + runnerUpScore = topScore; + topScore = score; + } + else if ( score > runnerUpScore ) + { + runnerUpScore = score; + } + } + + scoreDif = (topScore - runnerUpScore); + + if( topScore >= scoreLimit*.5) + { + level notify( "sndMusicHalfway" ); + return; + } + + wait(1); + } +} + +function onPlayerConnect() +{ + while ( true ) + { + level waittill( "connected", player ); + + player.noDrowning = true; + + if ( isdefined( level.allow_teamchange ) && level.allow_teamchange == "0" ) + player.hasDoneCombat = true; + + if ( !isdefined( player.pers["objtime"] ) ) // prop alive time + player.pers["objtime"] = 0; + } +} +function hideHUDIntermission() +{ + level waittill( "game_ended" ); + + if ( prop::usePropHudServer() ) + { + level.elim_hud.alpha = 0; + if ( level.phSettings.propWhistleTime > 0 ) + { + level.phWhistleTimer.alpha = 0; + level.whistling.alpha = 0; + } + } + + foreach( player in level.players ) + { + player prop_controls::propAbilityKeysVisible( false ); + } + +} + +function onStartGameType() +{ + setClientNameMode( "manual_change" ); + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + level.displayRoundEndText = false; + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + util::setObjectiveText( game["attackers"], &"OBJECTIVES_PH_ATTACKER" ); + util::setObjectiveText( game["defenders"], &"OBJECTIVES_PH_DEFENDER" ); + util::setObjectiveHintText( game["attackers"], &"OBJECTIVES_PH_ATTACKER_HINT" ); + util::setObjectiveHintText( game["defenders"], &"OBJECTIVES_PH_DEFENDER_HINT" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_PH_ATTACKER" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_PH_DEFENDER" ); + } + else + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_PH_ATTACKER_SCORE" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_PH_DEFENDER_SCORE" ); + } + + foreach( team in level.teams ) + { + spawnlogic::add_spawn_points( team, "mp_tdm_spawn" ); + spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) ); + } + + spawning::updateAllSpawnPoints(); + + + //Swap spawns so props start on the other side of the map after halftime (aka if 2 or 3 rounds have been played) + swapSpawnSides = (game["roundsplayed"]%4 == 2) || (game["roundsplayed"]%4 == 3); + if ( swapSpawnSides ) + game["switchedsides"] = !game["switchedsides"]; + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array( spawning::getTDMStartSpawnName(team) ); + } + + if ( swapSpawnSides ) + game["switchedsides"] = !game["switchedsides"]; + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + + //removed action loop -CDC + level thread onScoreCloseMusic(); + + if ( !util::isOneRound() ) + { + level.displayRoundEndText = true; + if( level.scoreRoundWinBased ) + { + globallogic_score::resetTeamScores(); + } + } + + if( ( isdefined( level.droppedTagRespawn ) && level.droppedTagRespawn ) ) + level.numLives = 1; + + level._effect[ "propFlash" ] = "explosions/fx_exp_grenade_concussion"; + level._effect[ "propDeathFX" ] = "explosions/fx_prop_exp"; + + if ( !IsDefined( game["propScore"] ) ) + { + game["propScore"] = []; + game["propScore"]["allies"] = 0; + game["propScore"]["axis"] = 0; + } + + if ( !IsDefined( game["propSurvivalTime"] ) ) + { + game["propSurvivalTime"] = []; + game["propSurvivalTime"]["allies"] = 0; + game["propSurvivalTime"]["axis"] = 0; + } + + if ( !IsDefined( game["hunterKillTime"] ) ) + { + game["hunterKillTime"] = []; + game["hunterKillTime"]["allies"] = 0; + game["hunterKillTime"]["axis"] = 0; + } + + level flag::init( "props_hide_over", false ); + + level thread setupRoundStartHUD(); + if ( level.phSettings.propWhistleTime > 0 ) + level thread propWhistle(); + level thread hideHUDIntermission(); + level thread monitorTimers(); + level thread setPHTeamScores(); + level thread stillAliveXP(); + level thread deleteLevelTurrets(); + level thread trackTimeAlive(); +} + +function givePrimaryWeapon( weapon ) +{ + self.primaryWeapon = weapon; + self GiveWeapon( weapon ); + self switchToWeapon( weapon ); + self setSpawnWeapon( weapon ); + self.spawnWeapon = weapon; + self SetBlockWeaponPickup( weapon, true ); +} + +function giveSecondayWeapon( weapon ) +{ + self.secondaryWeapon = weapon; + self GiveWeapon( weapon ); + self SetBlockWeaponPickup( weapon, true ); +} + +function giveOffhandPrimary( primaryOffhand, primaryOffhandCount ) +{ + self GiveWeapon( primaryOffhand ); + self SetWeaponAmmoStock( primaryOffhand, primaryOffhandCount ); + self SwitchToOffhand( primaryOffhand ); + self.grenadeTypePrimary = primaryOffhand; + self.grenadeTypePrimaryCount = primaryOffhandCount; +} + +function giveOffhandSecondary( secondaryOffhand, secondaryOffhandCount ) +{ + self GiveWeapon( secondaryOffhand ); + self SetWeaponAmmoClip( secondaryOffhand, secondaryOffhandCount ); + self SwitchToOffhand( secondaryOffhand ); + self.grenadeTypeSecondary = secondaryOffhand; + self.grenadeTypeSecondaryCount = secondaryOffhandCount; +} + +function givePerk( perkName ) +{ + if ( !self HasPerk( perkName ) ) + { + self SetPerk( perkName ); + } +} + +function giveCustomLoadout() +{ + loadout::giveLoadout_init( true ); + loadout::setClassNum( self.curClass ); + self ClearPerks(); + + weapon = undefined; + if ( self util::isProp() ) + { + weapon = GetWeapon( "pistol_standard" ); + self givePrimaryWeapon( weapon ); + self giveOffhandPrimary( GetWeapon( "null_offhand_primary" ), 0 ); + self giveOffhandSecondary( GetWeapon( "null_offhand_secondary" ), 0 ); + + self givePerk( "specialty_quieter" ); + self givePerk( "specialty_fastladderclimb" ); + self givePerk( "specialty_fastmantle" ); + self givePerk( "specialty_jetcharger" ); + } + else + { + weapon = GetWeapon( "pistol_standard" ); + self giveSecondayWeapon( weapon ); + + attachments = Array(); + attachments[attachments.size] = "extclip"; + attachments[attachments.size] = "steadyaim"; + weapon = GetWeapon( "smg_standard", attachments ); + self givePrimaryWeapon( weapon ); + + if ( !propMiniGameActive() ) + self giveOffhandPrimary( GetWeapon( "concussion_grenade" ), 2 ); + else + self giveOffhandPrimary( GetWeapon( "null_offhand_primary" ), 0 ); + + self giveOffhandSecondary( GetWeapon( "null_offhand_secondary" ), 0 ); + self attackerInitAmmo(); + + self givePerk( "specialty_twogrenades" ); + self givePerk( "specialty_fastladderclimb" ); + self givePerk( "specialty_fastmantle" ); + self givePerk( "specialty_longersprint" ); + self givePerk( "specialty_sprintfire" ); + self givePerk( "specialty_jetcharger" ); + } + + self GiveWeapon( level.weaponBaseMelee ); + + self notify( "applyLoadout" ); + return weapon; +} + +function is_player_gamepad_enabled() +{ + return self GamepadUsedLast(); +} + +function whistleStartTimer( duration ) +{ + level notify( "whistle_start_timer_beginning" ); + + countTime = int( duration ); + + if ( countTime >= 0 ) + { + thread whistleStartTimer_Internal( countTime ); + } +} + +function whistleStartTimer_Internal( countTime ) +{ + level endon( "whistle_start_timer_beginning" ); // stop any existing whistle timer threads + + waittillframeend; // ensure we don't start in the middle of a frame + + while( ( countTime > 0 ) && !level.gameEnded ) + { + countTime--; + + wait( 1 ); + } +} + +function usePropHudServer() +{ + /# + if ( GetDvarInt( "scr_ph_usePropHudServer", 0 ) != 0 ) + return true; + #/ + + return true; +} + +function setupRoundStartHUD() +{ + //Initial Countdown Timer + level.phCountdownTimer = hud::createServerTimer( "default", 1.5 ); + level.phCountdownTimer hud::setPoint( "CENTER", undefined, 0, 50 ); + level.phCountdownTimer.label = &"MP_PH_STARTS_IN"; + level.phCountdownTimer.alpha = 0; + level.phCountdownTimer.archived = false; + level.phCountdownTimer.hideWhenInMenu = true; + level.phCountdownTimer.sort = 1; + + if ( usePropHudServer() ) + { + yUpperLeft = 110; + ySpace = 20; + if ( !level.console ) + { + yUpperLeft = 125; + ySpace = 15; + } + //Props Alive Counter + level.elim_hud = hud::createServerFontString( "default", 1.5 ); + level.elim_hud.label = &"MP_PH_ALIVE"; + level.elim_hud SetValue( 0 ); + level.elim_hud.x = 5; + level.elim_hud.y = yUpperLeft; + level.elim_hud.alignX = "left"; + level.elim_hud.alignY = "top"; + level.elim_hud.horzAlign = "left"; + level.elim_hud.vertAlign = "top"; + level.elim_hud.archived = true; + level.elim_hud.alpha = 0; + level.elim_hud.glowAlpha = 0; + level.elim_hud.hidewheninmenu = false; + level thread eliminatedHUDMonitor(); + + if ( level.phSettings.propWhistleTime > 0 ) + { + //Whistle Timer + level.phWhistleTimer = hud::createServerTimer( "default", 1.5 ); + level.phWhistleTimer.x = 5; + level.phWhistleTimer.y = yUpperLeft + ySpace; + level.phWhistleTimer.alignX = "left"; + level.phWhistleTimer.alignY = "top"; + level.phWhistleTimer.horzAlign = "left"; + level.phWhistleTimer.vertAlign = "top"; + level.phWhistleTimer.label = &"MP_PH_WHISTLE_IN"; + level.phWhistleTimer.alpha = 0; + level.phWhistleTimer.archived = true; + level.phWhistleTimer.hideWhenInMenu = false; + level.phWhistleTimer setTimer( 120 ); + + //Whistling + level.whistling = hud::createServerFontString( "default", 1.5 ); + level.whistling.label = &"MP_PH_WHISTLING"; + level.whistling.x = 5; + level.whistling.y = yUpperLeft + ySpace; + level.whistling.alignX = "left"; + level.whistling.alignY = "top"; + level.whistling.horzAlign = "left"; + level.whistling.vertAlign = "top"; + level.whistling.archived = true; + level.whistling.alpha = 0; + level.whistling.glowAlpha = 0.2; + level.whistling.hidewheninmenu = false; + } + } +} + +function eliminatedHUDMonitor() +{ + level endon ( "game_ended" ); + + while(1) + { + props = get_alive_nonspecating_players( game["defenders"] ); + level.elim_hud SetValue( props.size ); + level util::waittill_any( "player_spawned", "player_killed", "player_eliminated", "playerCountChanged", "propCountChanged", "playerDisconnected" ); + } +} + +function get_alive_nonspecating_players( team ) +{ + alive_nonspec_players = []; + + foreach ( player in level.players ) + { + if ( IsDefined( player ) && isalive( player ) && (!IsDefined( player.sessionstate ) || player.sessionstate == "playing" ) ) + { + if ( !IsDefined( team ) || player.team == team ) + alive_nonspec_players[alive_nonspec_players.size] = player; + } + } + + return alive_nonspec_players; +} + +function onPlayerDisconnect() +{ + level notify( "playerDisconnected" ); + + if ( propMiniGameActive() ) + thread propMiniGameUpdateScoreboard( 0.05 ); +} + +function waittillPrematchDone() +{ + while ( !( isdefined( level.prematch_over ) && level.prematch_over ) ) + {wait(.05);}; +} + +function onSpawnPlayer(predictedSpawn) +{ + self.breathingStopTime = 0; + + if ( self util::isProp() ) + { + self.pingsLeft = undefined; + + if ( !isdefined( self.abilityLeft ) ) + self.abilityLeft = 0; + + if ( !isdefined( self.clonesLeft ) ) + self.clonesLeft = 0; + + //remember ability selection between rounds + if( !isDefined( self.pers["ability"] ) ) + self.pers["ability"] = 0; + + self.currentAbility = level.abilities[ self.pers["ability"] ]; + + if ( usePropHudServer() ) + self thread prop_controls::propControlsHUD(); + + self.isAngleOffset = false; + + changeCount = Int( level.phSettings.propChangeCount ); + abilityCount = undefined; + cloneCount = undefined; + if ( isdefined( self.spawnedOnce ) && isdefined( self.changesLeft ) ) + { + changeCount = self.changesLeft; + abilityCount = self.abilityLeft; + cloneCount = self.clonesLeft; + } + + self prop_controls::propSetChangesLeft( changeCount ); + self prop_controls::setNewAbilityCount( self.currentAbility, abilityCount ); + self prop_controls::setNewAbilityCount( "CLONE", cloneCount ); + + self thread prop_controls::cleanupPropControlsHUDOnDeath(); + self thread handleProp(); + } + else + { + self.abilityLeft = undefined; + self.clonesLeft = undefined; + + if ( !isdefined( self.pingsLeft ) ) + self.pingsLeft = 0; + + if ( !IsDefined( self.thrownSpecialCount ) ) + self.thrownSpecialCount = 0; + + if ( usePropHudServer() ) + self thread prop_controls::hunterControlsHUD(); + + pingsLeft = level.phSettings.hunterNumPings; + if ( isdefined( self.spawnedOnce ) && isdefined( self.pingsLeft ) ) + { + pingsLeft = self.pingsLeft; + } + + self prop_controls::hunterSetPingsLeft( pingsLeft ); + + self thread prop_controls::cleanupHunterControlsHUDOnDeath(); + self thread handleNonProp(); + } + + self thread attackersWaitTime(); + + if ( level.useStartSpawns && !level.inGracePeriod && !level.playerQueuedRespawn ) + { + level.useStartSpawns = false; + } + + self.spawnedOnce = true; + + spawning::onSpawnPlayer( predictedSpawn ); +} + +function monitorTimers() +{ + level endon( "game_ended" ); + + waittillPrematchDone(); + + level.allow_teamchange = "0"; + foreach ( player in level.players ) + player.hasDoneCombat = true; + + level thread pausePHTimerForMigration(); + + if ( level.phSettings.propHideTime > 0 ) + { + level.phCountdownTimer setTimer( level.phSettings.propHideTime ); + level.phCountdownTimer.alpha = 1; + } + + if ( usePropHudServer() && level.phSettings.propWhistleTime > 0 ) + { + level.phWhistleTimer setTimer( level.phsettings.propWhistleTime + level.phSettings.propHideTime ); + } + + if ( level.phSettings.propHideTime > 0 || level.phSettings.propWhistleTime > 0 ) + whistleStartTimer( level.phsettings.propWhistleTime + level.phSettings.propHideTime ); + + if ( level.phSettings.propHideTime > 0 ) + waittillCountdownComplete( level.phSettings.propHideTime ); + + level flag::set( "props_hide_over" ); + + if ( usePropHudServer() ) + { + if ( level.phSettings.propWhistleTime > 0 ) + level.phWhistleTimer.alpha = 1; + level.elim_hud.alpha = 1; + } + + level.phCountdownTimer.alpha = 0; +} + +function pausePHTimerForMigration() +{ + level endon( "game_ended" ); + level endon( "props_hide_over" ); + + countdownEndTime = int( ( level.phSettings.propHideTime ) + ( GetTime() / 1000 ) ); + totalTimePassed = 0; + + while( 1 ) + { + level waittill( "host_migration_begin" ); + + level.phCountdownTimer.alpha = 0; + if ( usePropHudServer() && level.phSettings.propWhistleTime > 0 ) + level.phWhistleTimer.alpha = 0; + timePassed = int( hostmigration::waitTillHostMigrationDone() / 1000 ); + totalTimePassed += timePassed; + timePassed = totalTimePassed; + + intendedEnd = countdownEndTime + timePassed - int( gettime() / 1000 ); + level.phCountdownTimer setTimer( intendedEnd ); + if ( usePropHudServer() && level.phSettings.propWhistleTime > 0 ) + level.phWhistleTimer setTimer( level.phsettings.propWhistleTime + intendedEnd ); + whistleStartTimer( level.phsettings.propWhistleTime + intendedEnd ); + + level.phCountdownTimer.alpha = 1; + if ( usePropHudServer() && level.phSettings.propWhistleTime > 0 ) + level.phWhistleTimer.alpha = 1; + } +} + +function handleProp() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + self endon( "death" ); + + self waittill( "applyLoadout" ); + + self AllowProne( false ); + self AllowCrouch( false ); + self AllowSprint( false ); + self AllowSlide( false ); + self SetMoveSpeedScale( level.phSettings.propSpeedScale ); + self PlayerKnockback( false ); + self TakeAllWeapons(); + self AllowSpectateTeam( game["attackers"], true ); + self Ghost(); + self SetClientUIVisibilityFlag( "weapon_hud_visible", 0 ); + + self.healthRegenDisabled = true; + self.concussionImmune = undefined; + + Assert( !IsDefined( self.prop ) ); + self thread setupProp(); + self thread prop_controls::setupKeyBindings(); + self thread setupDamage(); + self thread prop_controls::propInputWatch(); + self thread propWatchDeath(); + self thread propWatchCleanupOnDisconnect(); + self thread propWatchCleanupOnRoundEnd(); + self thread propWatchPreMatchSettings(); +} + +function getThirdPersonRangeForSize( propSize ) +{ + switch ( propSize ) + { + case 50: + return 120; + case 100: + return 150; + case 250: + return 180; + case 450: + return 260; + case 550: + return 320; + default: + AssertMsg( "Unknown size: " + propSize ); + break; + } + + return 120; +} + +function getThirdPersonHeightOffsetForSize( propSize ) +{ + switch ( propSize ) + { + case 50: + return -30; + case 100: + return -20; + case 250: + return 0; + case 450: + return 20; + case 550: + return 40; + default: + AssertMsg( "Unknown size: " + propSize ); + break; + } + + return 0; +} + +function applyXYZOffset() +{ + if( !isDefined( self.prop.xyzOffset ) ) + return; + + self.prop.angles = self.angles; + + forward = AnglesToForward( self.prop.angles ) * self.prop.xyzOffset[0]; + right = AnglesToRight( self.prop.angles ) * self.prop.xyzOffset[1]; + up = AnglesToUp( self.prop.angles ) * self.prop.xyzOffset[2]; + + self.prop.origin += forward; + self.prop.origin += right; + self.prop.origin += up; +} + +function applyAnglesOffset() +{ + if( !isDefined( self.prop.anglesOffset ) ) + return; + + self.prop.angles = self.angles; + self.prop.angles += self.prop.anglesOffset; + self.isAngleOffset = true; +} + +function propWhistle() +{ + level endon( "game_ended" ); + + waittillPrematchDone(); + + time = GetTime(); + whistleTime = level.phSettings.propWhistleTime*1000; + whistleTimeMinTime = 20000; + whistleTimeFinal = whistleTimeMinTime; + whistleTimeDifference = 500; // start the whistling a bit earlier so that it matches with how we want to show the UI + //time in miliseconds when the game will not allow whistles + endGameGracePeriod = 5000; + + whistleCount = 0; + whistleSortOrigin = ( 0, 0, 0 ); + minimapCorners = getentarray("minimap_corner", "targetname"); + if ( minimapCorners.size > 0 ) + whistleSortOrigin = minimapCorners[0].origin; + + hostmigration::waitLongDurationWithHostMigrationPause( level.phSettings.propHideTime+level.phSettings.propWhistleTime ); + + while( 1 ) + { + if( ( time + whistleTime - whistleTimeDifference ) < GetTime() ) + { + whistleCount++; + + if ( usePropHudServer() ) + { + level.phWhistleTimer.alpha = 0; + level.whistling.alpha = 1; + level.whistling fadeOverTime( 0.75 ); + level.whistling.alpha = .6; + } + + sortedPlayers = ArraySortClosest(level.players, whistleSortOrigin); + foreach( player in sortedPlayers ) + { + if( !IsDefined( player ) ) + continue; + + + if ( player util::isProp() && isAlive( player ) ) + { + PlaySoundAtPosition( "mpl_phunt_char_whistle", player.origin + ( 0, 0, 60 ) ); + //due to this wait each player takes 1.5 seconds to whistle + hostmigration::waitLongDurationWithHostMigrationPause( 1.5 ); + + } + } + time = GetTime(); + + if( whistleCount % 2 == 0 ) + whistleTime = max( whistleTime - 5000, whistleTimeMinTime ); + + if( ( WhistleTimeFinal ) >= ( globallogic_utils::getTimeRemaining() - endGameGracePeriod ) ) + { + if ( usePropHudServer() ) + level.whistling.alpha = 0; + return; + } + else + { + if( ( WhistleTimeFinal * 2 ) + ( getTeamPlayersAlive( game["defenders"] ) * 2500 ) >= ( globallogic_utils::getTimeRemaining() - endGameGracePeriod ) ) + { + if ( usePropHudServer() ) + level.phWhistleTimer.label = &"MP_PH_FINAL_WHISTLE"; + whistleTimeFinal += getTeamPlayersAlive( game["defenders"] ) * 2500; + } + + if ( usePropHudServer() ) + level.phWhistleTimer setTimer( int( whistleTime/1000 ) ); + whistleStartTimer( int( whistleTime/1000 ) ); + + if ( usePropHudServer() ) + { + level.whistling.alpha = 0; + level.phWhistleTimer.alpha = 1; + } + } + } + hostmigration::waitLongDurationWithHostMigrationPause( 0.5 ); + } +} + +function getLivingPlayersOnTeam( team ) +{ + players = []; + foreach( player in level.players ) + { + if ( !IsDefined( player.team ) ) + continue; + + if ( IsAlive(player) && player.team == team ) + { + players[players.size] = player; + } + } + + return players; +} + +function setupDamage() +{ + level endon( "game_ended" ); + self endon( "death" ); + self endon( "disconnect" ); + + hostmigration::waitLongDurationWithHostMigrationPause( 0.5 ); + + self.prop.health = 99999; + self.prop.maxhealth = 99999; + self.prop thread entityDamageWatcher( &damageWatch ); +} + +function entityDamageWatcher( damageCallback ) +{ + level endon( "game_ended" ); + self endon( "death" ); + + while ( true ) + { + self waittill ( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags ); + + self thread [[ damageCallback ]]( damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags ); + } +} + +function damageWatch( damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags ) +{ + if ( !IsDefined( attacker ) ) + return; + + if ( !IsDefined( self.owner ) ) + return; + + if ( isPlayer( attacker ) ) + { + if ( attacker.pers["team"] == self.owner.pers["team"] ) + return; + + attacker thread damagefeedback::update(); + + if ( isdefined( weapon ) && weapon.rootWeapon.name == "concussion_grenade" && isdefined( meansOfDeath ) && meansOfDeath != "MOD_IMPACT" ) + prop_controls::registerConcussionEvent( attacker, undefined, meansOfDeath, damage, point, weapon ); + } + + self.owner DoDamage( damage, point, attacker, attacker, "none", meansOfDeath, iDFlags, weapon ); + + self.health = 99999; + self.maxhealth = 99999; +} + +function propCleanup() // self == player +{ + array = Array( self.prop, self.propAnchor, self.propEnt ); + self thread propCleanupDelayed( array ); +} + +function propCleanupDelayed( propEnts ) +{ + foreach( prop in propEnts ) + { + if ( IsDefined( prop ) ) + prop Unlink(); + } + + {wait(.05);}; + + foreach( prop in propEnts ) + { + if ( IsDefined( prop ) ) + prop Delete(); + } +} + +function propWatchDeath() // self == player +{ + level endon( "game_ended" ); + self endon("disconnect"); + + self waittill( "death" ); + + corpse = self.body; + + PlaySoundAtPosition( "wpn_flash_grenade_explode", self.prop.origin + ( 0, 0, 4 ) ); + PlayFX( fx::get( "propDeathFX" ), ( self.prop.origin + ( 0, 0, 4 ) ) ); + + if ( IsDefined( corpse ) ) + corpse Delete(); + + self propCleanup(); +} + +function propWatchCleanupOnDisconnect() // self == player +{ + self notify( "propWatchCleanupOnDisconnect" ); + self endon( "propWatchCleanupOnDisconnect" ); + level endon( "game_ended" ); + + self waittill( "disconnect" ); + + self propCleanup(); + self propCloneCleanup(); +} + +function propWatchCleanupOnRoundEnd() // self == player +{ + self notify( "propWatchDeleteRoundEnd" ); + self endon( "propWatchDeleteRoundEnd" ); + self endon( "disconnect" ); + + level waittill( "round_end_done" ); + + self propCleanup(); + self propCloneCleanup(); +} + +function propCloneCleanup() +{ + if ( IsDefined( self.propClones ) ) + { + foreach( clone in self.propClones ) + { + if ( isdefined( clone ) ) + clone Delete(); + } + } +} + +function propWatchPreMatchSettings() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "joined_team" ); + self endon( "joined_spectators" ); + + waittillPrematchDone(); + + // Any other settings that can be overwritten by the prematch freeze controls + self AllowProne( false ); + self AllowCrouch( false ); + self AllowSprint( false ); +} + +function organizePropList(inArray) +{ + return array::randomize(inArray); +} + +function randGetPropSizeToAllocate() +{ + WEIGHT_XSMALL = 10 * IsDefined(level.propList[50]); + WEIGHT_SMALL = 30 * IsDefined(level.propList[100]); + WEIGHT_MEDIUM = 40 * IsDefined(level.propList[250]); + WEIGHT_LARGE = 20 * IsDefined(level.propList[450]); + WEIGHT_XLARGE = 10 * IsDefined(level.propList[550]); + + randomRange = WEIGHT_XSMALL + WEIGHT_SMALL + WEIGHT_MEDIUM + WEIGHT_LARGE + WEIGHT_XLARGE; + + randomVal = RandomInt(randomRange); + + if ( randomVal < WEIGHT_XSMALL ) + return 50; + randomVal -= WEIGHT_XSMALL; + + if ( randomVal < WEIGHT_SMALL ) + return 100; + randomVal -= WEIGHT_SMALL; + + if ( randomVal < WEIGHT_MEDIUM ) + return 250; + randomVal -= WEIGHT_MEDIUM; + + if ( randomVal < WEIGHT_LARGE ) + return 450; + randomVal -= WEIGHT_LARGE; + + return 550; +} + +function getNextProp(inPlayer) +{ + firstChoicePropSize = randGetPropSizeToAllocate(); + allPropSizes = GetArrayKeys(level.propList); + allPropSizes = array::randomize(allPropSizes); + + propSizes = Array( firstChoicePropSize ); + foreach(size in allPropSizes) + { + if( size != firstChoicePropSize ) + propSizes[propSizes.size] = size; + } + + prop = undefined; + for(i=0; i= time ) + break; + } + + return; +} + +function giveLastOnTeamWarning() +{ + self endon("death"); + self endon("disconnect"); + level endon( "game_ended" ); + + self waitTillRecoveredHealth( 3 ); + + level thread playerCardSplash( &"SCORE_LAST_ALIVE", self ); + + if ( self util::isProp() ) + { + level notify ( "noPropsToSpectate" ); + level.noPropsSpectate = true; + } + + level notify ( "last_alive", self ); +} + +function playerCardSplash( calloutMessage, calloutPlayer ) +{ + LUINotifyEvent( &"player_callout", 2, calloutMessage, calloutPlayer.entnum ); +} + +function onTimeLimit() +{ + if ( !( isdefined( level.gameEnding ) && level.gameEnding ) ) + { + storeTimePassed(); + + chooseFinalKillCam(); + ph_endGame( game["defenders"], game["strings"]["time_limit_reached"] ); + } +} + +function storeTimePassed() +{ + timeLimitInMilliseconds = ( globallogic_defaults::default_getTimeLimit() * 60 * 1000 ); + timePassed = globallogic_utils::getTimePassed(); + + timePassedClamped = Int( min( timeLimitInMilliseconds, timePassed ) ); + + game["propSurvivalTime"][ game["defenders"] ] += timePassedClamped; + game["hunterKillTime"][ game["attackers"] ] += timePassedClamped; +} + +function chooseFinalKillCam() +{ + livingProps = getLivingPlayersOnTeam( game["defenders"] ); + if ( livingProps.size < 1 ) + { + return; + } + + livingHunters = getLivingPlayersOnTeam( game["attackers"] ); + if ( livingHunters.size < 1 ) + { + return; + } + + killCamProp = chooseBestPropForKillcam( livingProps, livingHunters ); + + if ( IsPlayer( killCamProp ) ) + { + attackerNum = killCamProp getEntityNumber(); + } + else + { + attackerNum = -1; + } + + victim = livingHunters[0]; + victim.deathtime = getTime() - 1000; + + weap = GetWeapon( "none" ); + killcam_entity_info = killcam::get_killcam_entity_info( killCamProp, killCamProp, weap ); + level thread killcam::record_settings( attackerNum, victim getEntityNumber(), weap, "MOD_UNKNOWN", victim.deathtime, 0, 0, killcam_entity_info, [], [], killCamProp ); +} + +function chooseBestPropForKillcam( livingProps, livingHunters ) +{ + bestProp = undefined; + shortestPathDistOverall = ( 1 << 30 ); + + foreach ( prop in livingProps ) + { + Assert( isAlive( prop ) ); + + closestHunter = undefined; + shortestPathDist = ( 1 << 30 ); + + foreach ( hunter in livingHunters ) + { + pathDist = PathDistance( prop.origin, hunter.origin ); + if ( !isdefined( pathDist ) ) + pathDist = Distance( prop.origin, hunter.origin ); + + if ( pathDist < shortestPathDist ) + { + shortestPathDist = pathDist; + closestHunter = hunter; + } + } + + if ( shortestPathDist < shortestPathDistOverall ) + { + shortestPathDistOverall = shortestPathDist; + bestProp = prop; + } + } + + if ( !IsDefined( bestProp ) ) + { + bestProp = array::random( livingProps ); + } + + return bestProp; +} + +function showPlayerToAll( setClientField ) +{ + self Show(); + self notify( "showPlayer" ); + if ( setClientField ) + self clientfield::set( "hideTeamPlayer", 0 ); +} + +function hidePlayerFromTeam( team, setClientField ) +{ + self Hide(); // turns off the rest: footstep/landing fx, gun tracers/whizbys, etc + if ( setClientField ) + self thread hidePlayerClientfield( team ); + + foreach ( player in level.players ) + self thread hidePlayerFromTeamOnPlayerSpawned( player, team ); + + self thread hidePlayerFromTeamOnPlayerConnect( team ); +} + +function hidePlayerClientfield( team ) +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + self endon( "showPlayer" ); + + {wait(.05);}; // need to give time for the player entity to be created before setting clientfields + + teamInt = 1; + if ( team == "axis" ) + teamInt = 2; + + self clientfield::set( "hideTeamPlayer", teamInt ); // makes the player invisible +} + +function hidePlayerFromTeamOnPlayerConnect( team ) +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + self endon( "showPlayer" ); + + while ( true ) + { + level waittill( "connected", player ); + + self thread hidePlayerFromTeamOnPlayerSpawned( player, team ); + } +} + +function hidePlayerFromTeamOnPlayerSpawned( player, team ) +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + self endon( "showPlayer" ); + player endon( "disconnect" ); + + while ( true ) + { + if ( ( isdefined( player.hasSpawned ) && player.hasSpawned ) && player.team != team ) + { + self ShowToPlayer( player ); + + // ShowToPlayer turns off the EF_NODRAW flag; turn it back on since props should never render the player body + if ( self util::IsProp() ) + self Ghost(); + } + + player waittill( "spawned" ); + } +} + +function handleNonProp() +{ + self.thirdPersonRange = undefined; + self SetClientThirdPerson( false, 0 ); + self AllowProne( true ); + self AllowSprint( true ); + self SetMoveSpeedScale( 1 ); + self PlayerKnockback( true ); + self Show(); + self SetClientUIVisibilityFlag( "weapon_hud_visible", 1 ); + + if ( propMiniGameActive() ) + self propMiniGameSetHunterVisibility( false ); + + self thread prop_controls::setupHunterKeyBindings(); + self thread prop_controls::hunterInputWatch(); + + self.concussionImmune = true; + + self.healthRegenDisabled = false; + + self thread attackerRegenAmmo(); +} + +function stillAliveXP() +{ + level endon( "game_ended" ); + + level.xpEventInfo["kill"]["value"] = 300; + + level waittill( "props_hide_over" ); + + while(1) + { + hostmigration::waitLongDurationWithHostMigrationPause( 10 ); + + /# + if ( GetGametypeSetting( "timelimit" ) == 0 ) + continue; + #/ + + foreach ( player in level.players ) + { + if ( !IsDefined(player.team) ) + continue; + + if ( player.team == game[ "attackers" ] ) + continue; + + if ( !isAlive( player ) ) + continue; + + if ( !isdefined( player.prop ) ) + continue; + + scoreevents::processScoreEvent( "still_alive", player ); + + switch( player.prop.info.propSize ) + { + case 250: + { + scoreevents::processScoreEvent( "still_alive_medium_bonus", player ); + break; + } + case 450: + { + scoreevents::processScoreEvent( "still_alive_large_bonus", player ); + break; + } + case 550: + { + scoreevents::processScoreEvent( "still_alive_extra_large_bonus", player ); + break; + } + default: + break; + } + } + } +} + +function trackTimeAlive() +{ + level endon( "game_ended" ); + + waittillPrematchDone(); + + while(1) + { + foreach ( player in level.players ) + { + if ( !IsDefined(player.team) ) + continue; + + if ( player.team == game[ "attackers" ] ) + continue; + + if ( !IsAlive( player ) ) + continue; + + player.pers["objtime"]++; // prop alive time on scoreboard + player.objtime = player.pers["objtime"]; + + player AddPlayerStatWithGameType( "OBJECTIVE_TIME", 1 ); + } + + hostmigration::waitLongDurationWithHostMigrationPause( 1 ); + } +} + +function gamemodeModifyPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) +{ + victim = self; + + if ( IsDefined( eAttacker ) && IsPlayer( eAttacker ) && isAlive( eAttacker ) ) + { + if( !isDefined( eAttacker.hasHitPlayer ) ) + eAttacker.hasHitPlayer = true; + } + + return iDamage; +} + +function playerAvoidDamageGameMode( iDFlags, sHitLoc, weapon, friendlyFire, attackerIsHittingSelf, sMeansOfDeath ) +{ + if ( IsDefined( sMeansOfDeath ) && sMeansOfDeath == "MOD_FALLING" ) + { + return true; + } + + if ( self isHunter() ) + { + if ( weapon.name == "concussion_grenade" ) + { + return true; + } + + if ( IsSubStr( weapon.name, "destructible" ) ) + { + return true; + } + } + + return false; +} + +function attackersWaitTime() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + waittillPrematchDone(); + + if ( self.team == game[ "defenders" ] ) + { + self notify( "cancelCountdown" ); + return; + } + + while ( !IsDefined( level.startTime ) ) + {wait(.05);}; + + while ( IsDefined( self.controlsFrozen ) && self.controlsFrozen ) + {wait(.05);}; + + gameSecondsElapsed = getGameSecondsElapsed(); + remainingTime = level.phSettings.propHideTime - gameSecondsElapsed; + + result = false; + if ( remainingTime > 0 ) + { + if ( !propMiniGameActive() ) + result = attackersWaitTimeNormal( gameSecondsElapsed, remainingTime ); + else + result = attackersWaitTimeMinigame( gameSecondsElapsed, remainingTime ); + } +} + +function attackersWaitTimeNormal( gameSecondsElapsed, remainingTime ) +{ + self freezeControls( true ); + + if ( Int( gameSecondsElapsed ) > 0 ) + fadeInTime = 0.0; + else + fadeInTime = 1.0; + + fadeOutTime = 1.0; + if ( fadeInTime + fadeOutTime > remainingTime ) + { + fadeInTime = 0.0; + fadeOutTime = 0.0; + } + self thread prop_controls::countdownFadeToBlackForXSec( remainingTime, fadeInTime, fadeOutTime ); + + result = self waittillCountdownComplete( remainingTime ); + + self freezeControls( false ); + + return result; +} + +function attackersWaitTimeMinigame( gameSecondsElapsed, remainingTime ) +{ + CONST_MINIGAME_TIME_TO_END_SEC = 3; + CONST_MINIGAME_FADE_TIME = 1; + + fadeInTime = 0.0; + result = false; + + timeToMinigame = remainingTime - CONST_MINIGAME_TIME_TO_END_SEC; + self thread propWaitMiniGameInit( timeToMinigame + CONST_MINIGAME_FADE_TIME ); + + waittillframeend; // make sure spawnPlayer() runs since it will unfreeze controls and make sure the player is not spectating before grabbing the origin + self.phSpawnOrigin = undefined; + self.phSpawnAngles = undefined; + if ( remainingTime > 8 ) + { + self.phSpawnOrigin = self.origin; + self.phSpawnAngles = self.angles; + + result = self waittillCountdownComplete( timeToMinigame ); + + fadeInTime = 1.0; + } + + gameSecondsElapsed = getGameSecondsElapsed(); + remainingTime = level.phSettings.propHideTime - gameSecondsElapsed; + + if ( remainingTime > 0 ) + { + self freezeControls( true ); + + fadeOutTime = 1.0; + if ( fadeInTime + fadeOutTime > remainingTime ) + { + fadeInTime = 0.0; + fadeOutTime = 0.0; + } + self thread prop_controls::countdownFadeToBlackForXSec( remainingTime, fadeInTime, fadeOutTime ); + + result = self waittillCountdownComplete( remainingTime ); + + self freezeControls( false ); + } + + return result; +} + +function getGameSecondsElapsed() +{ + return ( ( GetTime() - level.startTime - level.totalHostMigrationTime ) / 1000.0 ); +} + +function monitorHostmigrationTime() +{ + level.totalHostMigrationTime = 0; + + while ( true ) + { + level waittill( "host_migration_begin" ); + startTime = GetTime(); + level waittill( "host_migration_end" ); + passedTime = GetTime() - startTime; + + level.totalHostMigrationTime += passedTime; + } +} + +function waittillCountdownComplete( remainingTime ) +{ + result = waittillCountdownCompleteInternal( remainingTime ); + + /# + while ( GetDvarInt( "scr_ph_pauseCountdown", 0 ) != 0 ) + {wait(.05);}; + #/ + + return result; +} + +function waittillCountdownCompleteInternal( remainingTime ) +{ + self endon( "cancelCountdown" ); + + hostmigration::waitLongDurationWithHostMigrationPause( remainingTime ); + + return true; +} + +function prematchPeriodSpawn() +{ + if ( self.pers["team"] == game["attackers"] ) + self util::freeze_player_controls( true ); + else + self thread propPrematchLock(); + + team = self.pers["team"]; + + if( isdefined( self.pers["music"].spawn ) && self.pers["music"].spawn == false ) + { + + if (level.wagerMatch) + { + music = "SPAWN_WAGER"; + } + else + { + music = game["music"]["spawn_" + team]; + } + + if( game[ "roundsplayed" ] == 0 ) + { + self thread globallogic_spawn::sndDelayedMusicStart("spawnFull"); + } + else + { + self thread globallogic_spawn::sndDelayedMusicStart("spawnShort"); + } + + self.pers["music"].spawn = true; + } + + if ( level.splitscreen ) + { + if ( isdefined( level.playedStartingMusic ) ) + music = undefined; + else + level.playedStartingMusic = true; + } + + self thread globallogic_spawn::doInitialSpawnMessaging(); +} + +function propPrematchLock() +{ + self endon( "disconnect" ); + + self FreezeControlsAllowLook( true ); + + waittillPrematchDone(); + + self FreezeControlsAllowLook( false ); +} + +function attackerInitAmmo() +{ + primaryWeapons = self GetWeaponsListPrimaries(); + foreach ( weapon in primaryWeapons ) + { + self GiveMaxAmmo( weapon ); + self SetWeaponAmmoClip( weapon, 999 ); + } + + if ( !propMiniGameActive() ) + { + if ( !IsDefined( self.thrownSpecialCount ) ) + self.thrownSpecialCount = 0; + + // Handle tactical grenades that may have already been used + weapon = GetWeapon( "concussion_grenade" ); + tacticalCount = self GetWeaponAmmoStock( weapon ); + tacticalCount = tacticalCount - self.thrownSpecialCount; + tacticalCount = Int( max( tacticalCount, 0 ) ); + self SetWeaponAmmoStock( weapon, tacticalCount ); + + if ( tacticalCount > 0 ) + self thread prop_controls::watchSpecialGrenadeThrow(); + } +} + +function attackerRegenAmmo() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self notify( "attackerRegenAmmo" ); + self endon( "attackerRegenAmmo" ); + + level endon( "game_ended" ); + + while ( true ) + { + self waittill( "reload" ); + + primaryWeapon = self GetCurrentWeapon(); + self GiveMaxAmmo( primaryWeapon ); + } +} + +function checkKillRespawn() +{ + self endon( "disconnect" ); + level endon( "game_ended" ); + + hostmigration::waitLongDurationWithHostMigrationPause( 0.1 ); + + if( self.pers["lives"] == 1 ) + { + self.pers["lives"]--; + level.livesCount[self.team]--; + + globallogic::updateGameEvents(); + level notify( "propCountChanged" ); + return; + } +} + +function gameModeAssistedSuicide( attacker, sMeansOfDeath, weapon ) +{ + bestPlayer = undefined; + bestPlayerMeansOfDeath = undefined; + bestPlayerWeapon = undefined; + + if ( !level flag::get( "props_hide_over" ) ) + return; + + if ( ( !isdefined( attacker ) || attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" || ( isdefined( attacker.isMagicBullet ) && attacker.isMagicBullet == true ) || attacker == self ) ) + { + for ( i = 0; i < self.attackers.size; i++ ) + { + player = self.attackers[i]; + if ( !isdefined( player ) ) + continue; + + if ( !isdefined( self.attackerDamage[ player.clientId ] ) || ! isdefined( self.attackerDamage[ player.clientId ].damage ) ) + continue; + + if ( player == self || ( level.teamBased && player.team == self.team ) ) + continue; + + if ( self.attackerDamage[ player.clientId ].damage > 1 && !isdefined( bestPlayer ) ) + { + bestPlayer = player; + bestPlayerMeansOfDeath = self.attackerDamage[ player.clientId ].meansOfDeath; + bestPlayerWeapon = self.attackerDamage[ player.clientId ].weapon; + } + else if ( isdefined( bestPlayer ) && self.attackerDamage[ player.clientId ].lasttimedamaged > self.attackerDamage[ bestPlayer.clientId ].lasttimedamaged ) + { + bestPlayer = player; + bestPlayerMeansOfDeath = self.attackerDamage[ player.clientId ].meansOfDeath; + bestPlayerWeapon = self.attackerDamage[ player.clientId ].weapon; + } + } + + if ( !isdefined( bestPlayer ) && self util::IsProp() ) + { + bestDistSq = undefined; + foreach ( player in level.players ) + { + if ( IsAlive( player ) && player.team != self.team ) + { + distSq = DistanceSquared( player.origin, self.origin ); + if ( !isdefined( bestDistSq ) || distSq < bestDistSq ) + { + bestPlayer = player; + bestDistSq = distSq; + } + } + } + + if ( isdefined( bestPlayer ) ) + { + bestPlayerMeansOfDeath = "MOD_MELEE"; + bestPlayerWeapon = GetWeapon( "none" ); + } + } + } + + result = undefined; + if ( isdefined( bestPlayer ) ) + { + result = []; + result["bestPlayer"] = bestPlayer; + result["bestPlayerWeapon"] = bestPlayerWeapon; + result["bestMeansOfDeath"] = bestPlayerMeansOfDeath; + } + + return result; +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration, lifeId ) +{ + victim = self; + + killedByEnemy = false; + + level notify( "playerCountChanged" ); + + if ( victim.team == game[ "attackers" ] ) + { + self thread respawnPlayer(); + } + else + { + if ( !level flag::get( "props_hide_over" ) ) + { + self thread respawnPlayer(); + return; + } + else + { + thread deathicons::add( victim.body, victim, victim.team, 5.0 ); + } + } + + if ( ( IsDefined( attacker ) && IsPlayer( attacker ) && attacker != victim && victim.team != attacker.team ) ) + { + killedByEnemy = true; + } + + if ( killedByEnemy ) + { + scoreevents::processScoreEvent( "prop_finalblow", attacker, victim ); + + // Play local sound for players who did damage + foreach( assailant in victim.attackers ) + { + if ( assailant == attacker ) + { + assailant PlayHitMarker( "mpl_hit_alert" ); + } + else + { + assailant PlayHitMarker( "mpl_hit_alert_escort" ); + } + } + + } + + foreach ( player in level.players ) + { + if ( player != attacker && player util::isProp() && isalive( player ) && victim util::isProp() ) + { + scoreevents::processScoreEvent( "prop_survived", player ); + } + else if ( player != attacker && player isHunter() && victim.team == game["defenders"] ) + { + scoreevents::processScoreEvent( "prop_killed", player, victim ); + } + } + + if ( victim util::isProp() ) + { + attackerTeam = util::getOtherTeam( victim.team ); + game["propScore"][attackerTeam] += 1; + } +} + +function respawnPlayer() +{ + self thread waitTillCanSpawnClient(); +} + +// Adding the same fix below, that I did to Reinforce, to make sure this function ends correctly +// instead of continously running while a person is stuck in an incrementing wait loop, due to spawning in the same frame as another player +function waitTillCanSpawnClient() +{ + self endon ( "started_spawnPlayer" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + for (;;) + { + wait ( .05 ); + if ( isDefined( self ) && isdefined( self.curClass ) && ( self.sessionstate == "spectator" || !isAlive( self ) ) ) + { + self.pers["lives"] = 1; + self globallogic_spawn::spawnClient(); + + //we need to continue here because spawn client can fail for up to 3 server frames in this instance + continue; + } + + //player either disconnected or has spawned + return; + } +} + +function onDeadEvent( team ) +{ + if ( team == game[ "defenders" ] ) + { + /# + if ( isdefined( level.allow_teamchange ) && level.allow_teamchange == "1" ) + return; + #/ + level thread propKilledEnd(); + } +} + +function propKilledEnd() +{ + if ( ( isdefined( level.huntersWonEnding ) && level.huntersWonEnding ) ) + return; + + if ( ( isdefined( level.gameEnding ) && level.gameEnding ) ) + return; + + level.huntersWonEnding = true; + + storeTimePassed(); + + level.gameEnding = 1; + hostmigration::waitLongDurationWithHostMigrationPause( 3 ); + thread ph_endGame( game["attackers"], game["strings"][game["defenders"]+"_eliminated"] ); +} + +function playDamageSoundPH( sMeansOfDeath ) +{ + if ( self.team == game["attackers"] ) + self battlechatter::pain_vox( sMeansOfDeath ); +} + +function playDeathSoundPH( body, attacker, weapon, sMeansOfDeath ) +{ + if ( self.team == game["attackers"] && IsDefined( body ) ) + self battlechatter::play_death_vox( body, attacker, weapon, sMeansOfDeath ); +} + +function round( value ) +{ + value = int( value + 0.5 ); + return value; +} + +function gametypeRoundEndScoreHud( winner, endType, endReasonText, outcomeText, team, winnerEnum, notifyRoundEndToUI, matchBonus ) +{ + if ( endType == "gameend" && IsDefined( level.propTieBreaker ) ) + { + if ( !isdefined( team ) || team == "spectator" ) + { + if ( isdefined( self.team ) && self.team != "spectator" && isdefined( game[ "propScore" ][ self.team ] ) ) + team = self.team; + else if ( isdefined( self.sessionteam ) && self.sessionteam != "spectator" && isdefined( game[ "propScore" ][ self.sessionteam ] ) ) + team = self.sessionteam; + + if ( !isdefined( team ) ) + return true; + } + + otherTeam = util::getotherteam( team ); + + if( level.propTieBreaker == "kills" ) + { + winnerScore = game[ "propScore" ][ team ]; + loserScore = game[ "propScore" ][ otherTeam ]; + if ( winnerScore < loserScore ) + { + winnerScore = game[ "propScore" ][ otherTeam ]; + loserScore = game[ "propScore" ][ team ]; + } + + scorePacked = ( winnerScore << 8 ) + loserScore; + + self LUINotifyEvent( &"show_outcome", 6, outcomeText, &"MP_PH_TIEBREAKER_KILL", int( matchBonus ), winnerEnum, notifyRoundEndToUI, scorePacked ); + return true; + } + else if ( level.propTieBreaker == "time" ) + { + teamSeconds = game[ "hunterKillTime" ][ team ] / 1000; + otherTeam = util::getotherteam( team ); + otherTeamSeconds = game[ "hunterKillTime" ][ otherTeam ] / 1000; + + teamSecondsRounded = round(teamSeconds); + otherTeamSecondsRounded = round(otherTeamSeconds); + + if( teamSecondsRounded == otherTeamSecondsRounded ) + { + if( teamSeconds > otherTeamSeconds ) + teamSecondsRounded++; + else + otherTeamSecondsRounded++; + } + + winnerTime = teamSecondsRounded; + loserTime = otherTeamSecondsRounded; + if ( winnerTime < loserTime ) + { + winnerTime = otherTeamSecondsRounded; + loserTime = teamSecondsRounded; + } + + self LUINotifyEvent( &"show_outcome", 7, outcomeText, &"MP_PH_TIEBREAKER_TIME", int( matchBonus ), winnerEnum, notifyRoundEndToUI, winnerTime, loserTime ); + return true; + } + } + + return false; +} + +function finalizeStartSpawn( spawnPoint, predictedSpawn ) +{ + if ( !predictedspawn ) + { + self.startSpawn = spawnPoint; + } +} + +function checkSpawnPoint( spawnPoint, predictedspawn ) +{ + // Fail if any of the (start) spawns have been given to another player + foreach ( player in level.players ) + { + if ( IsDefined( player.pers["team"] ) && player.pers["team"] != "spectator" ) + { + if ( IsDefined( player.startSpawn ) && player.startSpawn == spawnPoint ) + return false; + } + } + + return true; +} + +// from maySpawn() +function gameHasStarted() +{ + if ( level.teamBased ) + return ( globallogic_spawn::allTeamsHaveExisted() ); + else + return (level.maxPlayerCount > 1) || ( !util::isOneRound() && !util::isFirstRound() ); +} + +function maySpawnGameMode() +{ + /# + if ( level.alwaysAllowSpawn ) + return true; + #/ + + if ( level.inOvertime ) + return false; + + if ( level.playerQueuedRespawn && !isdefined(self.allowQueueSpawn) && !level.inGracePeriod && !level.useStartSpawns ) + return false; + + if ( level.numLives || level.numTeamLives ) + { + gameHasStarted = gameHasStarted(); + + if ( gameHasStarted && ( level.numLives && !self.pers["lives"] ) || ( level.numTeamLives && !game[self.team+"_lives"] ) ) + { + return false; + } + else if ( gameHasStarted ) + { + // disallow spawning for late comers + if ( !level.inGracePeriod && !self.hasSpawned && !level.wagerMatch ) + return false; + } + + if ( self disableSpawningForPlayer() ) + return false; + } + return true; +} + +function disableSpawningForPlayer() +{ + if ( !gameHasStarted() ) + { + return false; + } + + if ( self isHunter() ) + { + return false; + } + else if ( self util::isProp() ) + { + return ( !level.inGracePeriod ); + } + + return false; +} + +function isHunter() +{ + return ( isdefined( self.team ) && self.team == game["attackers"] ); +} + +function deleteLevelTurrets() +{ + turrets = getEntArray( "misc_turret", "classname" ); + foreach ( turret in turrets ) + turret Delete(); +} + +function customDamageShellshockAndRumble( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage, vPoint ) +{ + self thread weaponsOnDamage( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage, vPoint ); + + if ( !self util::isUsingRemote() ) + { + self PlayRumbleOnEntity( "damage_heavy" ); + } +} + +function weaponsOnDamage( eAttacker, eInflictor, weapon, meansOfDeath, damage, point ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + if ( isdefined( level._custom_weapon_damage_func ) ) + { + is_weapon_registered = self [[level._custom_weapon_damage_func]]( eAttacker, eInflictor, weapon, meansOfDeath, damage ); + if( is_weapon_registered ) + { + return; + } + } + + switch ( weapon.rootWeapon.name ) + { + case "concussion_grenade": + if ( ( isdefined( self.concussionImmune ) && self.concussionImmune ) ) + return; + + radius = weapon.explosionRadius; + + if (self == eAttacker) // TFLAME 8/1/12 - reduce effects on attacker + radius *= 0.5; + + damageOrigin = eInflictor.origin; + if ( isdefined( point ) ) + damageOrigin = point; + + if ( self prop_controls::wasConcussedAtOrigin( damageOrigin ) ) + return; + + scale = 1 - (distance( self.origin, damageOrigin ) / radius); + + if ( scale < 0 ) + scale = 0; + + time = 0.25 + (4 * scale); + + {wait(.05);}; + + if ( meansOfDeath != "MOD_IMPACT" ) + { + if ( self HasPerk ( "specialty_stunprotection" ) ) + { + time *= 0.1; + } + else + { + if ( self util::mayApplyScreenEffect() ) + { + self shellShock( "concussion_grenade_mp", time, false ); + } + } + + self thread weapons::play_concussion_sound( time ); + self.concussionEndTime = getTime() + (time * 1000); + self.lastConcussedBy = eAttacker; + + if ( self util::IsProp() ) + { + if ( ( isdefined( self.lock ) && self.lock ) ) + self prop_controls::unlockProp(); + + self prop_controls::registerConcussionEvent( einflictor, self, meansOfDeath, damage, damageOrigin, weapon ); + } + } + break; + + default: + // shellshock will only be done if meansofdeath is an appropriate type and if there is enough damage. + if ( isdefined( level.shellshockOnPlayerDamage ) ) + { + [[level.shellshockOnPlayerDamage]]( meansOfDeath, damage, weapon ); + } + break; + } +} + +function manageMannequins() +{ + level endon( "game_ended" ); + + {wait(.05);}; + + while ( !isdefined( level.mannequins ) ) + {wait(.05);}; + + foreach ( mannequin in level.mannequins ) + { + mannequin NotSolid(); + } + + level waittill( "props_hide_over" ); + + foreach ( mannequin in level.mannequins ) + { + mannequin Solid(); + } +} + + + +// ------------------------------------------------------------------------------------------------------------- + + + + + + + +function propWaitMiniGameInit( time ) +{ + if ( !isdefined( level.ph_minigame ) ) + level.ph_minigame = SpawnStruct(); + + if ( !( isdefined( level.ph_minigame.started ) && level.ph_minigame.started ) ) + { + level.ph_minigame.started = true; + self thread propMiniGameGlobal( time ); + } + + self.ph_miniGameScore = 0; + self.ph_miniGameTime = 0; + + if ( level.ph_minigame.doChase && self IsHunter() && time > 8 ) + { + waittillframeend; + + if ( level.ph_minigame_clonerunners.size < 6 ) + self propMiniGameSpawnCloneRunner(); + } +} + +function propMiniGameGlobal( time ) +{ + if ( time <= 0 ) + { + level.ph_minigame.active = false; + return; + } + + thread propMiniGameStart(); + + waittillCountdownComplete( time ); + + level notify( "propMiniGameComplete" ); + + level.ph_minigame.active = false; + + foreach ( player in level.players ) + { + if ( isdefined( player.pers["team"] ) && player.pers["team"] == game["defenders"] ) + player playerDefenderPropMiniGameComplete(); + else if ( isdefined( player.pers["team"] ) && player.pers["team"] == game["attackers"] ) + player playerAttackerPropMiniGameComplete(); + } + + if ( isdefined( level.ph_minigame.hudPregame ) ) + level.ph_minigame.hudPregame Destroy(); + + thread propMiniGameUpdateShowWinner( level.ph_minigame.hudPlaces[0], -80, 2.0 ); + thread propMiniGameUpdateShowWinner( level.ph_minigame.hudPlaces[1], -50, 1.75 ); + thread propMiniGameUpdateShowWinner( level.ph_minigame.hudPlaces[2], -20, 1.5 ); + + if ( isdefined( level.ph_minigame.targets ) ) + { + foreach ( target in level.ph_minigame.targets ) + { + if ( isdefined( target ) ) + target Delete(); + } + } + + if ( isdefined( level.ph_minigame_clonerunners ) ) + { + foreach ( clone in level.ph_minigame_clonerunners ) + { + if ( isdefined( clone ) ) + { + if ( isdefined( clone.cloneTarget ) ) + clone.cloneTarget Delete(); + + if ( isdefined( clone.targetObjectiveId ) ) + gameobjects::release_obj_id( clone.targetObjectiveId ); + + clone Delete(); + } + } + } +} + +function propMiniGameUpdateShowWinner( hud, winYOffset, winFontScale ) +{ + hud endon("death"); + + moveTime = 0.5; + showTime = 2.5; + fadeTime = 0.5; + + hud hud::setPoint( "BOTTOM", "CENTER", 0, winYOffset, moveTime ); + hud changefontscaleovertime( moveTime ); + hud.fontScale = winFontScale; + + wait moveTime+showTime; + + hud.alpha = 0; + hud fadeovertime( fadeTime ); + + wait fadeTime; + + if ( isdefined( hud ) ) + hud Destroy(); +} + +function playerAttackerPropMiniGameComplete() +{ + self propMiniGameSetHunterVisibility( true ); + self TakeWeapon( GetWeapon( "null_offhand_primary" ) ); + self giveOffhandPrimary( GetWeapon( "concussion_grenade" ), 2 ); + self attackerInitAmmo(); +} + +function playerDefenderPropMiniGameComplete() +{ + self propMiniGameSetPropVisibility( true ); +} + +function propMiniGameActive() +{ + if ( !isdefined( level.ph_minigame ) ) + level.ph_minigame = SpawnStruct(); + + if ( !isdefined( level.ph_minigame.active ) ) + level.ph_minigame.active = GetDvarInt( "scr_prop_minigame", 0 ); + + return level.ph_minigame.active; +} + +function propMiniGameSetHunterVisibility( isVisible ) +{ + if ( isVisible ) + { + self Solid(); + self SolidCapsule(); + self showPlayerToAll( true ); + if ( isdefined( self.phSpawnOrigin ) ) + { + self SetOrigin( self.phSpawnOrigin ); + self SetPlayerAngles( self.phSpawnAngles ); + } + + if ( isdefined( self.phClone ) ) + self.phClone Delete(); + } + else + { + self NotSolid(); + self NotSolidCapsule(); + self thread hidePlayerFromTeam( game["defenders"], true ); + self thread propMiniGameHunterClone( self ); + } +} + +function propMiniGameHunterClone( player ) +{ + player endon( "disconnect" ); + level endon( "game_ended" ); + + if ( !isdefined( player.phClone ) ) + { + waittillPrematchDone(); + wait 0.1; + + clone = util::spawn_player_clone( player, "pb_stand_alert" ); + + weapon = player GetCurrentWeapon(); + if( IsDefined( weapon.worldModel ) ) + { + clone Attach( weapon.worldModel, "tag_weapon_right" ); + } + + clone NotSolid(); + clone NotSolidCapsule(); + clone HideFromTeam( player.pers["team"] ); + + player.phClone = clone; + + player thread propMiniGameCloneCleanup(player, player.phClone); + } +} + +function propMiniGameCloneCleanup( player, clone ) +{ + clone endon( "entityshutdown" ); + clone endon( "death" ); + + player waittill( "disconnect" ); + + if ( isdefined( clone ) ) + clone Delete(); +} + +function propMiniGameSetPropVisibility( isVisible ) +{ + if ( isVisible ) + { + if ( isdefined( self.prop ) ) + { + self.prop Show(); + self.prop Solid(); + } + self showPlayerToAll( false ); + self Ghost(); + + if ( isdefined( self.propClones ) ) + { + foreach ( clone in self.propClones ) + { + clone Show(); + clone Solid(); + } + } + } + else + { + if ( isdefined( self.prop ) ) + { + self.prop NotSolid(); + self.prop HideFromTeam( game["attackers"] ); + } + self thread hidePlayerFromTeam( game["attackers"], false ); + } +} + +function propMiniGameStart() +{ + level.ph_minigame.doChase = false; + label = &"MP_PH_PREGAME_HUNT"; + if ( RandomFloat( 1 ) < 0.5 ) + { + level.ph_minigame.doChase = true; + label = &"MP_PH_PREGAME_CHASE"; + } + + /# + if ( GetDvarInt( "scr_ph_debugDoMinigameType", 0 ) == 2 && level.ph_minigame.doChase ) + { + level.ph_minigame.doChase = false; + label = &"MP_PH_PREGAME_HUNT"; + } + else if ( GetDvarInt( "scr_ph_debugDoMinigameType", 0 ) == 1 && !level.ph_minigame.doChase ) + { + level.ph_minigame.doChase = true; + label = &"MP_PH_PREGAME_CHASE"; + } + #/ + + thread propMiniGameHud( label ); + + level.ph_minigame.targetLocations = propMiniGameGetTargetLocations(); + level.ph_minigame.targetLocations = array::randomize( level.ph_minigame.targetLocations ); + level.ph_minigame.nextIndex = 0; + + if ( !level.ph_minigame.doChase ) + thread propMiniGameSpawnTargets(); + else + level.ph_minigame_clonerunners = []; + + foreach ( player in level.players ) + { + if ( isdefined( player.pers["team"] ) && player.pers["team"] == game["attackers"] ) + player thread playerAttackerPropMiniGameStart( &"MP_PH_EMPTY" ); + } +} + +function propMiniGameGetTargetLocations() +{ + CONST_TARGET_DIST_SQ = 90000; + + targetLocations = []; + allLocations = spawnlogic::get_spawnpoint_array( "mp_tdm_spawn" ); + hunters = getLivingPlayersOnTeam( game["attackers"] ); + hunter = hunters[0]; + + foreach ( location in allLocations ) + { + distSq = DistanceSquared( location.origin, hunter.origin ); + if ( distSq > CONST_TARGET_DIST_SQ ) + targetLocations[targetLocations.size] = location; + } + + return targetLocations; +} + +function propMiniGameGetTargetModel() +{ + return "wpn_t7_uplink_ball_world"; +} + +function propMiniGameSpawnTargets() +{ + CONST_MAX_TARGETS = 40; + CONST_SPAWNS_PER_FRAME = 4; + + model = propMiniGameGetTargetModel(); + + numTargets = min( level.ph_minigame.targetLocations.size, CONST_MAX_TARGETS ); + + level.ph_minigame.targets = []; + + num = 0; + for ( i = 0; i < numTargets; i++ ) + { + origin = getNextTargetOrigin(); + target = propMiniGameSpawnTarget( origin, model ); + level.ph_minigame.targets[level.ph_minigame.targets.size] = target; + + num++; + if ( num >= CONST_SPAWNS_PER_FRAME ) + { + {wait(.05);}; + num = 0; + } + } +} + +function propMiniGameTargetFx( targetEnt ) +{ + {wait(.05);}; + + if ( isdefined( targetEnt ) ) + PlayFxOnTag( "ui/fx_uplink_ball_vanish", targetEnt, "tag_origin" ); +} + +function propMiniGameSpawnTarget( origin, model ) +{ + target = Spawn( "script_model", origin ); + target SetModel( model ); + target.targetname = "propTarget"; + target SetCanDamage( true ); + target.fakeHealth = 50; + target.health = 99999; + target.maxhealth = 99999; + target thread entityDamageWatcher( &propMiniGameDamageTargetWatch ); + target SetPlayerCollision( false ); + target MakeSentient(); + target NotSolidCapsule(); + target SetTeam( game["defenders"] ); + target HideFromTeam( game["defenders"] ); + target SetScale( 2, true ); + + thread propMiniGameTargetFx( target ); + + return target; +} + +function propMiniGameDamageTargetWatch( damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags ) +{ + if ( !IsDefined( attacker ) ) + return; + + if ( isPlayer( attacker ) ) + { + if ( ( isdefined( self.isDying ) && self.isDying ) ) + return; + + attacker thread damagefeedback::update(); + + self.lastattacker = attacker; + + self.fakeHealth -= damage; + if ( self.fakeHealth <= 0 ) + { + propMiniGamePlayerScored( attacker ); + self thread moveTarget(); + } + } + + self.health += damage; +} + +function moveTarget() +{ + self.isDying = true; + + {wait(.05);}; + + self.fakeHealth = 50; + + fxEnt = PlayFx( fx::get( "propDeathFX" ), ( self.origin + ( 0, 0, 4 ) ) ); + fxEnt Hide(); + foreach ( player in level.players ) + { + if ( player IsHunter() ) + fxEnt ShowToPlayer( player ); + } + fxEnt PlaySoundToTeam( "wpn_flash_grenade_explode", game["attackers"] ); + + self.origin = getNextTargetOrigin(); + self DontInterpolate(); + + self.isDying = false; +} + +function isLocationNearOtherTargets( location ) +{ + CONST_CLOSEST_DIST_SQ = 90000; + + foreach ( target in level.ph_minigame.targets ) + { + distSq = DistanceSquared( target.origin, location ); + if ( distSq < CONST_CLOSEST_DIST_SQ ) + return true; + } + + return false; +} + +function getNextTargetOrigin() +{ + if ( level.ph_minigame.nextIndex >= level.ph_minigame.targetLocations.size ) + level.ph_minigame.nextIndex = 0; + + location = level.ph_minigame.targetLocations[level.ph_minigame.nextIndex]; + if ( !isdefined( location.altOrigin ) ) + { + dir = ( level.mapCenter - location.origin ); + dist = Distance( level.mapCenter, location.origin ); + if ( dist > 0 ) + dir = ( dir[0] / dist, dir[1] / dist, dir[2] / dist ); + + attempts = 9; + newLocation = location.origin; + rand = RandomFloat( 1 ); + while ( attempts > 0 ) + { + randDist = dist * rand; + newLocation = location.origin + ( dir * randDist ); + + if ( !isLocationNearOtherTargets( newLocation ) ) + break; + + rand -= 0.1; + if ( rand < 0 ) + { + newLocation = location.origin; + break; + } + + attempts--; + } + newLocation = GetClosestPointOnNavMesh( newLocation, 100 ); + if ( !isdefined( newlocation ) ) + newLocation = location.origin; + location.altOrigin = newLocation; + } + + origin = location.altOrigin + ( 0, 0, 40 ); + level.ph_minigame.nextIndex++; + + return origin; +} + +function propMiniGameHudCreatePlace( x, y, label, color ) +{ + hudPlace = hud::createServerFontString( "default", 1.5, game["attackers"] ); + hudPlace.label = label; + hudPlace.x = x; + hudPlace.y = y; + hudPlace.alignX = "left"; + hudPlace.alignY = "top"; + hudPlace.horzAlign = "left"; + hudPlace.vertAlign = "top"; + hudPlace.color = color; + hudPlace.archived = true; + hudPlace.alpha = 0; + hudPlace.glowAlpha = 0; + hudPlace.hidewheninmenu = false; + hudPlace.sort = 1001; + + return hudPlace; +} + +function propMiniGameHud( titleLabel ) +{ + level.ph_minigame.hudPlaces = []; + + yUpperLeft = 110; + ySpace = 20; + if ( !level.console ) + { + yUpperLeft = 125; + ySpace = 15; + } + + x = 5; + y = yUpperLeft; + level.ph_minigame.hudPlaces[level.ph_minigame.hudPlaces.size] = propMiniGameHudCreatePlace( x, y, &"MP_PH_MINIGAME_FIRST", (1.000, 0.843, 0.000) ); + y += ySpace; + level.ph_minigame.hudPlaces[level.ph_minigame.hudPlaces.size] = propMiniGameHudCreatePlace( x, y, &"MP_PH_MINIGAME_SECOND", (0.300, 0.300, 0.300) ); + y += ySpace; + level.ph_minigame.hudPlaces[level.ph_minigame.hudPlaces.size] = propMiniGameHudCreatePlace( x, y, &"MP_PH_MINIGAME_THIRD", (0.804, 0.498, 0.196) ); + + level.ph_minigame.hudPregame = hud::createServerFontString( "default", 2.5, game["attackers"] ); + level.ph_minigame.hudPregame hud::setPoint( "CENTER", undefined, 0, -30 ); + level.ph_minigame.hudPregame.label = titleLabel; + level.ph_minigame.hudPregame.x = 0; + level.ph_minigame.hudPregame.archived = true; + level.ph_minigame.hudPregame.alpha = 1; + level.ph_minigame.hudPregame.glowAlpha = 0; + level.ph_minigame.hudPregame.hidewheninmenu = false; + + thread propMinigameDelayedHud(); +} + +function propMiniGameDelayedHud() +{ + level endon( "game_ended" ); + + wait 5.5; + + level.ph_minigame.hudPregame MoveOverTime( 1.0 ); + level.ph_minigame.hudPregame hud::setPoint( "CENTER", undefined, 0, -100 ); + + wait 1.0; + + level.ph_minigame.hudPregame FadeOverTime( 1.0 ); + level.ph_minigame.hudPregame.color = ( 0, 1, 0 ); + + wait 1.0; + + level.ph_minigame.hudPregame FadeOverTime( 1.0 ); + level.ph_minigame.hudPregame.color = ( 1, 1, 1 ); +} + +function propMiniGamePlayerScored( player ) +{ + gameMSElapsed = ( GetTime() - level.startTime - level.totalHostMigrationTime ); + + player.ph_miniGameScore++; + player.ph_miniGameTime += gameMSElapsed; + + player.phIndicator SetValue( player.ph_miniGameScore ); + + player thread propMiniGameFlashIndicator(); + + propMiniGameUpdateScoreboard(); +} + +function propMiniGameUpdateScoreboard( delayTime ) +{ + level endon( "game_ended" ); + + if ( isdefined( delayTime ) ) + wait delayTime; + + hunters = getLivingPlayersOnTeam( game["attackers"] ); + sortedHunters = array::quickSort( hunters, &propMiniGameScoreCompare ); + + for ( i = 0; i < 3; i++ ) + { + if ( isdefined( sortedHunters[i] ) && isdefined( sortedHunters[i].ph_miniGameScore ) && sortedHunters[i].ph_miniGameScore > 0 ) + { + level.ph_minigame.hudPlaces[i].alpha = 1; + level.ph_minigame.hudPlaces[i] SetPlayerNameString( sortedHunters[i] ); + } + else if ( isdefined( level.ph_minigame.hudPlaces ) && isdefined( level.ph_minigame.hudPlaces[i] ) && level.ph_minigame.hudPlaces[i].alpha > 0 ) + { + level.ph_minigame.hudPlaces[i].alpha = 0; + } + } +} + +function propMiniGameScoreCompare( p1, p2 ) +{ + if ( !isdefined( p1 ) || !isdefined( p1.ph_miniGameScore ) ) + return false; + + if ( !isdefined( p2 ) || !isdefined( p2.ph_miniGameScore ) ) + return true; + + if ( p1.ph_miniGameScore > p2.ph_miniGameScore ) + return true; + + return ( ( p1.ph_miniGameScore == p2.ph_miniGameScore ) && ( p1.ph_miniGameTime <= p2.ph_miniGameTime ) ); +} + +function propMiniGameIndicator( label ) +{ + self.phIndicator = hud::createFontString( "objective", 1 ); + self.phIndicator.label = label; + self.phIndicator.x = 0; + self.phIndicator.y = 20; + self.phIndicator.alignX = "center"; + self.phIndicator.alignY = "middle"; + self.phIndicator.horzAlign = "user_center"; + self.phIndicator.vertAlign = "middle"; + self.phIndicator.archived = true; + self.phIndicator.fontscale = 1; + self.phIndicator.alpha = 0; + self.phIndicator.glowAlpha = 0.5; + self.phIndicator.hidewheninmenu = false; +} + +function playerAttackerPropMiniGameStart( label ) +{ + self propMiniGameIndicator( label ); +} + +function propMiniGameFlashIndicator() +{ + self.phIndicator.alpha = 1; + self.phIndicator FadeOverTime( 3 ); + self.phIndicator.alpha = 0; +} + +//function propMiniGameSpawnCloneRunners() +//{ +// CONST_NUM_RUNNERS = 6; +// +// level.ph_minigame_clonerunners = []; +// +// foreach ( player in level.players ) +// { +// propMiniGameSpawnCloneRunner(); +// } +//} + +function propMiniGameSpawnCloneRunner() +{ + forward = anglestoforward( self getangles() ); + origin = self.origin + VectorScale( forward, 100 ); + origin = GetClosestPointOnNavMesh( origin, 600 ); + + clone = SpawnActor( "spawner_bo3_robot_grunt_assault_mp", origin, self.angles, "", true ); + + clone.cloneTarget = propMiniGameSpawnCloneTarget( origin + ( 0, 0, 40 ) ); + clone.cloneTarget LinkTo( clone ); + + level.ph_minigame_clonerunners[level.ph_minigame_clonerunners.size] = clone; + + propMiniGameConfigureClone( clone, self, forward ); +} + + + + + + + +function propMiniGameConfigureClone( clone, player, forward ) // self is player +{ + clone.isAiClone = true; + clone.properName = ""; + clone.ignoreTriggerDamage = true; + clone.minWalkDistance = 125; + clone.overrideActorDamage = &cloneDamageOverride; + clone.spawnTime = GetTime(); + clone.skipGibs = true; + clone setmaxhealth( 9999 ); + clone PushActors( true ); // Don't collide with other actors. + clone PushPlayer( true ); // Don't collide with players. + clone SetContents( (1 << 13) ); // Collide with bullets. + clone SetAvoidanceMask( "avoid none" ); // Disable all avoidance. + + clone.targetObjectiveId = gameobjects::get_next_obj_id(); + Objective_Add( clone.targetObjectiveId, "active" ); + Objective_Team( clone.targetObjectiveId, game["attackers"] ); + Objective_Position( clone.targetObjectiveId, clone.origin ); + Objective_Icon( clone.targetObjectiveId, "t7_hud_waypoints_safeguard_location" ); + Objective_SetColor( clone.targetObjectiveId, &"FriendlyBlue" ); + Objective_OnEntity( clone.targetObjectiveId, clone ); + + clone ASMSetAnimationRate( 1.20 ); + + clone setclone(); + clone._goal_center_point = getNextCloneGoalOrigin(); + + queryResult = undefined; + + if ( IsDefined( clone._goal_center_point ) && + clone FindPath( clone.origin, clone._goal_center_point, true, false ) ) + { + queryResult = PositionQuery_Source_Navigation( + clone._goal_center_point, + 0, + 450, + 450, + 100, + clone ); + } + else + { + queryResult = PositionQuery_Source_Navigation( + clone.origin, + 500, + 750, + 750, + 50, + clone ); + } + + if( queryResult.data.size > 0 ) + { + clone setgoalpos( queryResult.data[0].origin, true ); + clone._clone_goal = queryresult.data[0].origin; + clone._clone_goal_max_dist = 450; + } + else + { + clone._goal_center_point = clone.origin; + } + + clone thread _UpdateClonePathing(); + clone HideFromTeam( game["defenders"] ); + clone Ghost(); + _ConfigureCloneTeam( clone, player ); +} + +function cloneDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, timeOffset, boneIndex, modelIndex, surfaceType, surfaceNormal ) +{ + return 0; +} + +function _ConfigureCloneTeam( clone, player ) +{ + team = util::getOtherTeam( player.team ); + clone.ignoreall = true; + clone SetTeam( team ); + clone.team = team; +} + +function _GetCloseClones() +{ + CLONE_TOO_CLOSE_DIST_SQ = 10000; + + closeClones = []; + + foreach ( clone in level.ph_minigame_clonerunners ) + { + if ( self == clone ) + continue; + + distSq = DistanceSquared( clone.origin, self.origin ); + if ( distSq < CLONE_TOO_CLOSE_DIST_SQ ) + closeClones[closeClones.size] = clone; + } + + return closeClones; +} + +function _UpdateClonePathing() +{ + self endon( "death" ); + + CLONE_NOT_MOVING_DIST_SQ = 576; + CLONE_NOT_MOVING_POLL_TIME = 2000; + CLONE_TOO_CLOSE_POLL_TIME = 1500; + + if ( !isdefined( level.ph_minigame.lastPathFindTime ) ) + level.ph_minigame.lastPathFindTime = 0; + + while( true ) + { + if ( !IsDefined( self.lastKnownPos ) ) + { + self.lastKnownPos = self.origin; + self.lastKnownPosTime = GetTime(); + } + + if ( !isdefined( self.lastTooCloseTime ) ) + { + self.lastTooCloseTime = GetTime(); + } + + distance = 0; + if( isDefined( self._clone_goal ) ) + distance = DistanceSquared( self._clone_goal, self.origin ); + + findNewPath = false; + if ( distance < 120 * 120 ) + { + findNewPath = true; + } + else if ( !self HasPath() ) + { + findNewPath = true; + } + else if ( ( self.lastKnownPosTime + CLONE_NOT_MOVING_POLL_TIME ) <= GetTime() ) + { + if ( DistanceSquared( self.lastKnownPos, self.origin ) < CLONE_NOT_MOVING_DIST_SQ ) + findNewPath = true; + + self.lastKnownPos = self.origin; + self.lastKnownPosTime = GetTime(); + } + else if ( ( self.lastTooCloseTime + CLONE_TOO_CLOSE_POLL_TIME ) <= GetTime() && level.ph_minigame.lastPathFindTime != GetTime() ) // one path find per frame + { + clones = _GetCloseClones(); + + if ( clones.size > 0 ) + findNewPath = true; + + // give this clone a chance to separate + for ( i = 0; i < clones.size; i++ ) + { + clones[i].lastTooCloseTime = GetTime(); + } + + self.lastTooCloseTime = GetTime(); + } + + if ( findNewPath ) + { + level.ph_minigame.lastPathFindTime = GetTime(); + + self._goal_center_point = getNextCloneGoalOrigin(); + + queryResult = PositionQuery_Source_Navigation( + self._goal_center_point, + 500, + 750, + 750, + 100, + self ); + + if( queryResult.data.size == 0 ) + { + queryResult = PositionQuery_Source_Navigation( + self.origin, + 500, + 750, + 750, + 100, + self ); + } + + if( queryResult.data.size > 0 ) + { + randIndex = RandomIntRange( 0, queryResult.data.size ); + + self setgoalpos( queryResult.data[ randIndex ].origin, true ); + self._clone_goal = queryresult.data[ randIndex ].origin; + self._clone_goal_max_dist = 750; + } + } +// util::drawcylinder( self._goal_center_point, self._clone_goal_max_dist, 10, .5, "stop_notify_asdf" ); +// util::debug_sphere( self._clone_goal, 10, ( 1, 0, 1 ), 1, 1 ); + wait( .5 ); + } +} + +function getNextCloneGoalOrigin() +{ + if ( level.ph_minigame.nextIndex >= level.ph_minigame.targetLocations.size ) + level.ph_minigame.nextIndex = 0; + + location = level.ph_minigame.targetLocations[level.ph_minigame.nextIndex]; + level.ph_minigame.nextIndex++; + + return location.origin; +} + +function propMiniGameSpawnCloneTarget( origin ) +{ + model = propMiniGameGetTargetModel(); + target = Spawn( "script_model", origin ); + target SetModel( model ); + target.targetname = "propTarget"; + target SetCanDamage( true ); + target.fakeHealth = 50; + target.health = 99999; + target.maxhealth = 99999; + target thread entityDamageWatcher( &propMiniGameDamageCloneTargetWatch ); + target SetPlayerCollision( false ); + target MakeSentient(); + target NotSolidCapsule(); + target SetTeam( game["defenders"] ); + target HideFromTeam( game["defenders"] ); + target SetScale( 2, true ); + + thread propMiniGameTargetFx( target ); + + return target; +} + +function propMiniGameDamageCloneTargetWatch( damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags ) +{ + if ( !IsDefined( attacker ) ) + return; + + if ( isPlayer( attacker ) ) + { + attacker thread damagefeedback::update(); + + self.lastattacker = attacker; + + propMiniGamePlayerScored( attacker ); + } + + self.health += damage; +} diff --git a/mp/gametypes/prop.gsh b/mp/gametypes/prop.gsh new file mode 100644 index 0000000..fc57aef --- /dev/null +++ b/mp/gametypes/prop.gsh @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// defined in code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mp/gametypes/pur.csc b/mp/gametypes/pur.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/pur.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/pur.gsc b/mp/gametypes/pur.gsc new file mode 100644 index 0000000..cbf5877 --- /dev/null +++ b/mp/gametypes/pur.gsc @@ -0,0 +1,483 @@ +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_defaults; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_util; + +/* + PUR - Purgatory + Objective: Score points for your team by eliminating players on the opposing team + Map ends: When one team reaches the score limit, or time limit is reached + Respawning: No wait / Near teammates + + Level requirements + ------------------ + Spawnpoints: + classname mp_tdm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of teammates and enemies + at the time of spawn. Players generally spawn behind their teammates relative to the direction of enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + This sets the nationalities of the teams. Allies can be american, british, or russian. Axis can be german. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals +*/ + +/*QUAKED mp_tdm_spawn (0.0 0.0 1.0) (-16 -16 0) (16 16 72) +Players spawn away from enemies and near their team at one of these positions.*/ + +/*QUAKED mp_tdm_spawn_axis_start (0.5 0.0 1.0) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_tdm_spawn_allies_start (0.0 0.5 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +#precache( "string", "OBJECTIVES_TDM" ); +#precache( "string", "OBJECTIVES_TDM_SCORE" ); +#precache( "string", "OBJECTIVES_TDM_HINT" ); +#precache( "string", "MP_PURGATORY_QUEUE_POSITION" ); +#precache( "string", "MP_PURGATORY_NEXT_SPAWN" ); +#precache( "string", "MP_PURGATORY_TEAMMATE_COUNT" ); +#precache( "string", "MP_PURGATORY_ENEMY_COUNT" ); +#precache( "string", "MP_ALL_TEAMS_ELIMINATED" ); + +function main() +{ + globallogic::init(); + + util::registerRoundSwitch( 0, 9 ); + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 50000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 10 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.cumulativeRoundScores = GetGametypeSetting( "cumulativeRoundScores" ); + level.teamBased = true; + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.onRoundEndGame =&onRoundEndGame; + level.onRoundSwitch =&onRoundSwitch; + level.onDeadEvent =&onDeadEvent; + level.onLastTeamAliveEvent =&onLastTeamAliveEvent; + level.onAliveCountChange =&onAliveCountChange; + level.spawnMessage =&pur_spawnMessage; + level.onSpawnSpectator =&onSpawnSpectator; + level.onRespawnDelay =&getRespawnDelay; + + gameobjects::register_allowed_gameobject( "tdm" ); + + game["dialog"]["gametype"] = "tdm_start"; + game["dialog"]["gametype_hardcore"] = "hctdm_start"; + game["dialog"]["offense_obj"] = "generic_boost"; + game["dialog"]["defense_obj"] = "generic_boost"; + game["dialog"]["sudden_death"] = "generic_boost"; + + // Sets the scoreboard columns and determines with data is sent across the network + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "kdratio", "assists" ); +} + +function onStartGameType() +{ + setClientNameMode("auto_change"); + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + foreach( team in level.teams ) + { + util::setObjectiveText( team, &"OBJECTIVES_TDM" ); + util::setObjectiveHintText( team, &"OBJECTIVES_TDM_HINT" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( team, &"OBJECTIVES_TDM" ); + } + else + { + util::setObjectiveScoreText( team, &"OBJECTIVES_TDM_SCORE" ); + } + + spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) ); + spawnlogic::add_spawn_points( team, "mp_tdm_spawn" ); + } + + spawning::updateAllSpawnPoints(); + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array(spawning::getTDMStartSpawnName(team)); + } + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + + level.displayRoundEndText = false; + + //removed action loop -CDC + //level thread onScoreCloseMusic(); + + if ( !util::isOneRound() ) + { + level.displayRoundEndText = true; + if( level.scoreRoundWinBased ) + { + globallogic_score::resetTeamScores(); + } + } +} + +function waitThenSpawn() +{ + while( self.sessionstate == "dead" ) + { + {wait(.05);}; + } + +// spawntime = 3; +// util::setLowerMessage( game["strings"]["waiting_to_spawn"], spawntime ); + + //wait(spawntime); +} + +function onSpawnPlayer(predictedSpawn) +{ + self endon( "disconnect" ); + level endon( "end_game" ); + + self.usingObj = undefined; + + self initPlayerHud(); + + self waitThenSpawn(); + + self util::clearLowerMessage(); + spawning::onSpawnPlayer(predictedSpawn); +} + +function pur_endGameWithKillcam( winningTeam, endReasonText ) +{ + thread globallogic::endGame( winningTeam, endReasonText ); +} + +function onAliveCountChange( team ) +{ + level thread updateQueueMessage(team); +} + +function onLastTeamAliveEvent( team ) +{ + if (level.multiTeam ) + { + pur_endGameWithKillcam( team, &"MP_ALL_TEAMS_ELIMINATED" ); + } + else if ( team == game["attackers"] ) + { + pur_endGameWithKillcam( game["attackers"], game["strings"][game["defenders"]+"_eliminated"] ); + } + else if ( team == game["defenders"] ) + { + pur_endGameWithKillcam( game["defenders"], game["strings"][game["attackers"]+"_eliminated"] ); + } +} + +function onDeadEvent( team ) +{ + if ( team == "all" ) + { + pur_endGameWithKillcam( "tie", game["strings"]["round_draw"] ); + } +} + +function onEndGame( winningTeam ) +{ + if ( isdefined( winningTeam ) && isdefined( level.teams[winningTeam] ) ) + globallogic_score::giveTeamScoreForObjective( winningTeam, 1 ); +} + +function onRoundSwitch() +{ + game["switchedsides"] = !game["switchedsides"]; + + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, game["roundswon"][team] ); + } + } +} + +function onRoundEndGame( roundWinner ) +{ + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, game["roundswon"][team] ); + } + + winner = globallogic::determineTeamWinnerByGameStat( "roundswon" ); + } + else + { + winner = globallogic::determineTeamWinnerByTeamScore(); + } + + return winner; +} + +function onScoreCloseMusic() +{ + teamScores = []; + + while( !level.gameEnded ) + { + scoreLimit = level.scoreLimit; + scoreThreshold = scoreLimit * .1; + scoreThresholdStart = abs(scoreLimit - scoreThreshold); + scoreLimitCheck = scoreLimit - 10; + + topScore = 0; + runnerUpScore = 0; + foreach( team in level.teams ) + { + score = [[level._getTeamScore]]( team ); + + if ( score > topScore ) + { + runnerUpScore = topScore; + topScore = score; + } + else if ( score > runnerUpScore ) + { + runnerUpScore = score; + } + } + + scoreDif = (topScore - runnerUpScore); + + if ( scoreDif <= scoreThreshold && scoreThresholdStart <= topScore ) + { + //play some action music and break the loop + thread globallogic_audio::set_music_on_team( "timeOut" ); + return; + } + + wait(1); + } +} + +function initPurgatoryEnemyCountElem( team, y_pos ) +{ + self.purPurgatoryCountElem[team] = newclienthudelem( self ); + self.purPurgatoryCountElem[team].fontScale = 1.25; + self.purPurgatoryCountElem[team].x = 110; + self.purPurgatoryCountElem[team].y = y_pos; + self.purPurgatoryCountElem[team].alignX = "right"; + self.purPurgatoryCountElem[team].alignY = "top"; + self.purPurgatoryCountElem[team].horzAlign = "left"; + self.purPurgatoryCountElem[team].vertAlign = "top"; + self.purPurgatoryCountElem[team].foreground = true; + self.purPurgatoryCountElem[team].hidewhendead = false; + self.purPurgatoryCountElem[team].hidewheninmenu = true; + self.purPurgatoryCountElem[team].archived = false; + self.purPurgatoryCountElem[team].alpha = 1.0; + self.purPurgatoryCountElem[team].label = &"MP_PURGATORY_ENEMY_COUNT"; +} + +function initPlayerHud() +{ + if( isdefined( self.purPurgatoryCountElem ) ) + { + if ( self.pers["team"] == self.purHudTeam ) + return; + + foreach( elem in self.purPurgatoryCountElem ) + { + elem destroy(); + } + } + + self.purPurgatoryCountElem = []; + + y_pos = 115; + y_inc = 15; + + team = self.pers["team"]; + self.purHudTeam = team; + + self.purPurgatoryCountElem[team] = newclienthudelem( self ); + self.purPurgatoryCountElem[team].fontScale = 1.25; + self.purPurgatoryCountElem[team].x = 110; + self.purPurgatoryCountElem[team].y = y_pos; + self.purPurgatoryCountElem[team].alignX = "right"; + self.purPurgatoryCountElem[team].alignY = "top"; + self.purPurgatoryCountElem[team].horzAlign = "left"; + self.purPurgatoryCountElem[team].vertAlign = "top"; + self.purPurgatoryCountElem[team].foreground = true; + self.purPurgatoryCountElem[team].hidewhendead = false; + self.purPurgatoryCountElem[team].hidewheninmenu = true; + self.purPurgatoryCountElem[team].archived = false; + self.purPurgatoryCountElem[team].alpha = 1.0; + self.purPurgatoryCountElem[team].label = &"MP_PURGATORY_TEAMMATE_COUNT"; + + + foreach( team in level.teams ) + { + if ( team == self.team ) + continue; + + y_pos += y_inc; + + initPurgatoryEnemyCountElem(team, y_pos); + } + + self thread hidePlayerHudOnGameEnd(); + self thread updatePlayerHud(); +} + +function updatePlayerHud() +{ + self endon("disconnect"); + level endon("end_game"); + + while (true ) + { + if ( self.team != "spectator" ) + { + self.purPurgatoryCountElem[self.team] setvalue( level.deadPlayers[self.team].size ); + + foreach( team in level.teams ) + { + if ( self.team == team ) + continue; + + self.purPurgatoryCountElem[team] setvalue( level.aliveCount[team] ); + } + } + wait(0.25); + } +} + +function hidePlayerHudOnGameEnd( ) +{ + level waittill("game_ended"); + + foreach( elem in self.purPurgatoryCountElem ) + { + elem.alpha = 0; + } +} + +function displaySpawnMessage() +{ + if ( self.waitingToSpawn ) + return; + + if ( self.name == "TolucaLake" ) + { + shit = 0; + } + + //self util::setLowerMessage( &"MP_PURGATORY_QUEUE_POSITION", 100, true ); + if ( self.spawnQueueIndex != 0 ) + { + self util::setLowerMessageValue( &"MP_PURGATORY_QUEUE_POSITION", self.spawnQueueIndex + 1, true ); + } + else + { + self util::setLowerMessageValue( &"MP_PURGATORY_NEXT_SPAWN", undefined, false ); + } +} + +function pur_spawnMessage() +{ + util::WaitTillSlowProcessAllowed(); + + //self displaySpawnMessage(); +} + +function onSpawnSpectator( origin, angles ) +{ + self displaySpawnMessage(); + + globallogic_defaults::default_onSpawnSpectator( origin, angles ); +} + +function updateQueueMessage(team) +{ + self notify("updateQueueMessage"); + self endon("updateQueueMessage"); + + util::WaitTillSlowProcessAllowed(); + + players = level.deadPlayers[team]; + + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + if ( !player.waitingToSpawn && player.sessionstate != "dead" && !isdefined( player.killcam ) ) + { + player displaySpawnMessage(); + } + } +} + +function getRespawnDelay() +{ + self.lowerMessageOverride = undefined; + + return level.playerRespawnDelay; +} \ No newline at end of file diff --git a/mp/gametypes/res.csc b/mp/gametypes/res.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/res.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/res.gsc b/mp/gametypes/res.gsc new file mode 100644 index 0000000..1945c23 --- /dev/null +++ b/mp/gametypes/res.gsc @@ -0,0 +1,1157 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\medals_shared; +#using scripts\shared\objpoints_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_defaults; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\teams\_teams; + +/* + Resistance + Objective: Attacking team capture all the flags by touching them. + Map ends: When attacking team captures all the flags, or time limit is reached + Respawning: No wait / Near teammates + + Level requirements + ------------------ + Spawnpoints: + classname mp_tdm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of owned flags, teammates and + enemies at the time of spawn. Players generally spawn behind their teammates relative to the direction of enemies. + Optionally, give a spawnpoint a script_linkto to specify which flag it "belongs" to (see Flag Descriptors). + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Flags: + classname trigger_radius + targetname res_flag_primary or flag_secondary + Flags that need to be captured to win. Primary flags take time to capture; secondary flags are instant. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + This sets the nationalities of the teams. Allies can be american, british, or russian. Axis can be german. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals +*/ + +/*QUAKED mp_res_spawn_axis (1.0 0.0 1.0) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near their team at one of these positions.*/ + +/*QUAKED mp_res_spawn_axis_a (1.0 0.0 1.0) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near the A flag at one of these positions.*/ + +/*QUAKED mp_res_spawn_allies_a (1.0 0.0 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near the A flag at one of these positions.*/ + +/*QUAKED mp_res_spawn_allies_b (1.0 0.0 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near the B flag at one of these positions.*/ + +/*QUAKED mp_res_spawn_allies_a (1.0 0.0 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near the A flag at one of these positions.*/ + +/*QUAKED mp_res_spawn_allies (0.0 1.0 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions.*/ + +/*QUAKED mp_res_spawn_axis_start (1.0 0.0 1.0) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_res_spawn_allies_start (0.0 1.0 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +#precache( "material", "compass_waypoint_captureneutral" ); +#precache( "material", "compass_waypoint_capture" ); +#precache( "material", "compass_waypoint_defend" ); +#precache( "material", "compass_waypoint_captureneutral_a" ); +#precache( "material", "compass_waypoint_capture_a" ); +#precache( "material", "compass_waypoint_defend_a" ); +#precache( "material", "compass_waypoint_captureneutral_b" ); +#precache( "material", "compass_waypoint_capture_b" ); +#precache( "material", "compass_waypoint_defend_b" ); +#precache( "material", "compass_waypoint_captureneutral_c" ); +#precache( "material", "compass_waypoint_capture_c" ); +#precache( "material", "compass_waypoint_defend_c" ); + +#precache( "string", "OBJECTIVES_RES_ATTACKER" ); +#precache( "string", "OBJECTIVES_RES_DEFENDER" ); +#precache( "string", "OBJECTIVES_RES_ATTACKER_SCORE" ); +#precache( "string", "OBJECTIVES_RES_DEFENDER_SCORE" ); +#precache( "string", "OBJECTIVES_RES_ATTACKER_HINT" ); +#precache( "string", "OBJECTIVES_RES_DEFENDER_HINT" ); +#precache( "string", "MP_TARGET_DESTROYED" ); +#precache( "string", "MP_CONTROL_HQ" ); +#precache( "string", "MP_CAPTURE_HQ" ); +#precache( "string", "MP_DEFEND_HQ" ); +#precache( "string", "MP_TIME_EXTENDED" ); +#precache( "string", "MP_CAPTURING_FLAG" ); +#precache( "string", "MP_LOSING_FLAG" ); +#precache( "string", "MP_RES_YOUR_FLAG_WAS_CAPTURED" ); +#precache( "string", "MP_RES_ENEMY_FLAG_CAPTURED" ); +#precache( "string", "MP_RES_NEUTRAL_FLAG_CAPTURED" ); +#precache( "string", "MP_ENEMY_FLAG_CAPTURED_BY" ); +#precache( "string", "MP_NEUTRAL_FLAG_CAPTURED_BY" ); +#precache( "string", "MP_FRIENDLY_FLAG_CAPTURED_BY" ); +// purposefully using dom strings here as there is no need to duplicate +#precache( "string", "MP_DOM_FLAG_A_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_B_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_C_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_D_CAPTURED_BY" ); +#precache( "string", "MP_DOM_FLAG_E_CAPTURED_BY" ); +#precache( "string", "MP_HQ_AVAILABLE_IN" ); +//#precache( "fx", "_t6/misc/fx_ui_flagbase_marines" );//TODO T7 - contact FX team to get proper replacement +//#precache( "fx", "_t6/misc/fx_ui_flagbase_seals" ); +//#precache( "fx", "_t6/misc/fx_ui_flagbase_cdc" );//TODO T7 - contact FX team to get proper replacement +//#precache( "fx", "_t6/misc/fx_ui_flagbase_nva" );//TODO T7 - contact FX team to get proper replacement +//#precache( "fx", "_t6/misc/fx_ui_flagbase_pmc" ); +//#precache( "fx", "_t6/misc/fx_ui_flagbase_cia" );//TODO T7 - contact FX team to get proper replacement +#precache( "objective", "_a" ); +#precache( "objective", "_b" ); + +function main() +{ + globallogic::init(); + + util::registerRoundSwitch( 0, 9 ); + util::registerTimeLimit( 0, 2.5 ); + util::registerScoreLimit( 0, 1000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.teamBased = true; + level.overrideTeamScore = true; + level.onStartGameType =&onStartGameType; + level.onRoundSwitch =&onRoundSwitch; + level.onPlayerKilled =&onPlayerKilled; + level.onPrecacheGameType =&onPrecacheGameType; + level.onEndGame=&onEndGame; + level.onRoundEndGame =&onRoundEndGame; + level.onTimeLimit =&onTimeLimit; + level.getTimeLimit =&getTimeLimit; + + gameobjects::register_allowed_gameobject( level.gameType ); + + game["dialog"]["gametype"] = "res_start"; + game["dialog"]["gametype_hardcore"] = "hcres_start"; + game["dialog"]["offense_obj"] = "cap_start"; + game["dialog"]["defense_obj"] = "defend_start"; + level.lastDialogTime = 0; + + level.iconoffset = (0,0,100); + + // Sets the scoreboard columns and determines with data is sent across the network + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths" , "captures", "defends"); + +} + + +function onPrecacheGameType() +{ +} + + +function onRoundSwitch() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["teamScores"]["allies"] == level.scorelimit - 1 && game["teamScores"]["axis"] == level.scorelimit - 1 ) + { + // overtime! team that's ahead in kills gets to defend. + aheadTeam = getBetterTeam(); + + if ( aheadTeam != game["defenders"] ) + { + game["switchedsides"] = !game["switchedsides"]; + } + else + { + level.halftimeSubCaption = ""; + } + level.halftimeType = "overtime"; + } + else + { + level.halftimeType = "halftime"; + game["switchedsides"] = !game["switchedsides"]; + } +} + +function getBetterTeam() +{ + kills["allies"] = 0; + kills["axis"] = 0; + deaths["allies"] = 0; + deaths["axis"] = 0; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + team = player.pers["team"]; + if ( isdefined( team ) && (team == "allies" || team == "axis") ) + { + kills[ team ] += player.kills; + deaths[ team ] += player.deaths; + } + } + + if ( kills["allies"] > kills["axis"] ) + return "allies"; + else if ( kills["axis"] > kills["allies"] ) + return "axis"; + + if ( deaths["allies"] < deaths["axis"] ) + return "allies"; + else if ( deaths["axis"] < deaths["allies"] ) + return "axis"; + + if ( randomint(2) == 0 ) + return "allies"; + return "axis"; +} + +function onStartGameType() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + level.usingExtraTime = false; + + game["strings"]["flags_capped"] = &"MP_TARGET_DESTROYED"; + + util::setObjectiveText( game["attackers"], &"OBJECTIVES_RES_ATTACKER" ); + util::setObjectiveText( game["defenders"], &"OBJECTIVES_RES_DEFENDER" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_RES_ATTACKER" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_RES_DEFENDER" ); + } + else + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_RES_ATTACKER_SCORE" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_RES_DEFENDER_SCORE" ); + } + util::setObjectiveHintText( game["attackers"], &"OBJECTIVES_RES_ATTACKER_HINT" ); + util::setObjectiveHintText( game["defenders"], &"OBJECTIVES_RES_DEFENDER_HINT" ); + + level.objectiveHintPrepareHQ = &"MP_CONTROL_HQ"; + level.objectiveHintCaptureHQ = &"MP_CAPTURE_HQ"; + level.objectiveHintDefendHQ = &"MP_DEFEND_HQ"; + + level.flagBaseFXid = []; + level.flagBaseFXid[ "allies" ] = "_t6/misc/fx_ui_flagbase_" + game["allies"]; + level.flagBaseFXid[ "axis" ] = "_t6/misc/fx_ui_flagbase_" + game["axis"]; + + setClientNameMode("auto_change"); + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::place_spawn_points( "mp_res_spawn_allies_start" ); + spawnlogic::place_spawn_points( "mp_res_spawn_axis_start" ); + spawnlogic::add_spawn_points( "allies", "mp_res_spawn_allies" ); + spawnlogic::add_spawn_points( "axis", "mp_res_spawn_axis" ); + spawnlogic::add_spawn_points( "axis", "mp_res_spawn_axis_a" ); + spawnlogic::drop_spawn_points( "mp_res_spawn_allies_a" ); + spawning::updateAllSpawnPoints(); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array("mp_res_spawn_" + team + "_start"); + } + + hud_createFlagProgressBar(); + + updateGametypeDvars(); + thread createTimerDisplay(); + + thread resFlagsInit(); + + level.overtime = false; + overtime = false; + + if ( game["teamScores"]["allies"] == level.scorelimit - 1 && game["teamScores"]["axis"] == level.scorelimit - 1 ) + overtime = true; + + if ( overtime ) + spawnlogic::clear_spawn_points(); + spawnlogic::add_spawn_points( "allies", "mp_res_spawn_allies_a" ); + spawnlogic::add_spawn_points( "axis", "mp_res_spawn_axis" ); + spawning::updateAllSpawnPoints(); + + if ( overtime ) + setupNextFlag( int(level.resFlags.size / 3) ); + else + setupNextFlag(0); + + level.overtime = overtime; + + if ( level.flagActivateDelay ) + updateObjectiveHintMessages( level.objectiveHintPrepareHQ, level.objectiveHintPrepareHQ ); + else + updateObjectiveHintMessages( level.objectiveHintCaptureHQ, level.objectiveHintCaptureHQ ); +} + +function onEndGame( winningTeam ) +{ + for ( i = 0; i < level.resFlags.size; i++ ) + { + level.resFlags[i] gameobjects::allow_use( "none" ); + } +} + +function onRoundEndGame( roundWinner ) +{ + winner = globallogic::determineTeamWinnerByGameStat( "roundswon" ); + + return winner; +} + +function res_endGame( winningTeam, endReasonText ) +{ + if ( isdefined( winningTeam ) && winningTeam != "tie" ) + globallogic_score::giveTeamScoreForObjective( winningTeam, 1 ); + + thread globallogic::endGame( winningTeam, endReasonText ); +} + +function onTimeLimit() +{ + if ( level.overtime ) + { + if ( isdefined ( level.resProgressTeam ) ) + { + res_endGame( level.resProgressTeam, game["strings"]["time_limit_reached"] ); + } + else + { + res_endGame( "tie", game["strings"]["time_limit_reached"] ); + } + } + else + { + res_endGame( game["defenders"], game["strings"]["time_limit_reached"] ); + } + +} + +function getTimeLimit() +{ + timeLimit = globallogic_defaults::default_getTimeLimit(); + if ( level.usingExtraTime ) + { + flagCount = 0; + + if ( isdefined( level.currentFlag ) ) + { + flagCount += level.currentFlag.orderIndex; + } + return timeLimit + level.extraTime * flagCount; + } + return timeLimit; +} + +function updateGametypeDvars() +{ + level.flagCaptureTime = GetGametypeSetting( "captureTime" ); + level.flagDecayTime = GetGametypeSetting( "flagDecayTime" ); + level.flagActivateDelay = GetGametypeSetting( "objectiveSpawnTime" ); + level.flaginactiveresettime = GetGametypeSetting( "idleFlagResetTime" ); + level.flagInactiveDecay = GetGametypeSetting( "idleFlagDecay" ); + level.extraTime = GetGametypeSetting( "extraTime" ); + level.flagCaptureGracePeriod = GetGametypeSetting( "flagCaptureGracePeriod" ); + level.playerOffensiveMax = GetGametypeSetting( "maxPlayerOffensive" ); + level.playerDefensiveMax = GetGametypeSetting( "maxPlayerDefensive" ); +} + +function resFlagsInit() +{ + level.lastStatus["allies"] = 0; + level.lastStatus["axis"] = 0; + + level.flagModel["allies"] = teams::get_flag_model( "allies" ); + level.flagModel["axis"] = teams::get_flag_model( "axis" ); + level.flagModel["neutral"] = teams::get_flag_model( "neutral" ); + + primaryFlags = getEntArray( "res_flag_primary", "targetname" ); + + if ( (primaryFlags.size) < 2 ) + { + /# + printLn( "^1Not enough Resistance flags found in level!" ); + #/ + callback::abort_level(); + return; + } + + level.flags = []; + for ( index = 0; index < primaryFlags.size; index++ ) + level.flags[level.flags.size] = primaryFlags[index]; + + level.resFlags = []; + for ( index = 0; index < level.flags.size; index++ ) + { + trigger = level.flags[index]; + if ( isdefined( trigger.target ) ) + { + visuals[0] = getEnt( trigger.target, "targetname" ); + } + else + { + visuals[0] = spawn( "script_model", trigger.origin ); + visuals[0].angles = trigger.angles; + } + + visuals[0] setModel( level.flagModel[game["defenders"]] ); + + resFlag = gameobjects::create_use_object( game["defenders"], trigger, visuals, level.iconoffset ); + resFlag gameobjects::allow_use( "none" ); + resFlag gameobjects::set_use_time( level.flagCaptureTime ); + resFlag gameobjects::set_use_text( &"MP_CAPTURING_FLAG" ); + resFlag gameobjects::set_decay_time( level.flagDecayTime ); + label = resFlag gameobjects::get_label(); + resFlag.label = label; + resFlag gameobjects::set_model_visibility( false ); + resFlag.onUse =&onUse; + resFlag.onBeginUse =&onBeginUse; + resFlag.onUseUpdate =&onUseUpdate; + resFlag.onUseClear =&onUseClear; + resFlag.onEndUse =&onEndUse; + resFlag.claimGracePeriod = level.flagCaptureGracePeriod; + resFlag.decayProgress = level.flagInactiveDecay; + + traceStart = visuals[0].origin + (0,0,32); + traceEnd = visuals[0].origin + (0,0,-32); + trace = bulletTrace( traceStart, traceEnd, false, undefined ); + + upangles = vectorToAngles( trace["normal"] ); + resFlag.baseeffectforward = anglesToForward( upangles ); + resFlag.baseeffectright = anglesToRight( upangles ); + + resFlag.baseeffectpos = trace["position"]; + + // legacy spawn code support + level.flags[index].useObj = resFlag; + level.flags[index].nearbyspawns = []; + + resFlag.levelFlag = level.flags[index]; + + level.resFlags[level.resFlags.size] = resFlag; + } + + sortFlags(); + + // level.bestSpawnFlag is used as a last resort when the enemy holds all flags. + level.bestSpawnFlag = []; + level.bestSpawnFlag[ "allies" ] = getUnownedFlagNearestStart( "allies", undefined ); + level.bestSpawnFlag[ "axis" ] = getUnownedFlagNearestStart( "axis", level.bestSpawnFlag[ "allies" ] ); + + for ( index = 0; index < level.resFlags.size; index++ ) + { + level.resFlags[index] createFlagSpawnInfluencers(); + } + + +} + +function sortFlags() +{ + flagOrder["_a"] = 0; + flagOrder["_b"] = 1; + flagOrder["_c"] = 2; + flagOrder["_d"] = 3; + flagOrder["_e"] = 4; + + for ( i = 0; i < level.resFlags.size; i++ ) + { + level.resFlags[i].orderIndex = flagOrder[level.resFlags[i].label]; + assert(isdefined(level.resFlags[i].orderIndex)); + } + + for ( i = 1; i < level.resFlags.size; i++ ) + { + for ( j = 0; j level.resFlags[j+1].orderIndex ) + { + temp = level.resFlags[j]; + level.resFlags[j] = level.resFlags[j + 1]; + level.resFlags[j + 1] = temp; + } + } + } +} + +function setupNextFlag(flagIndex) +{ + prevFlagIndex = flagIndex - 1; + + if ( prevFlagIndex >= 0 ) + { + thread hideFlag( prevFlagIndex ); + } + + if ( flagIndex < level.resFlags.size && !level.overtime ) + { + thread showFlag( flagIndex ); + } + else + { + globallogic_utils::resumeTimer(); + hud_hideFlagProgressBar(); + } +} + +function createTimerDisplay() +{ + + flagSpawningInStr = &"MP_HQ_AVAILABLE_IN"; + + level.locationObjID = gameobjects::get_next_obj_id(); + level.timerDisplay = []; + level.timerDisplay["allies"] = hud::createServerTimer( "objective", 1.4, "allies" ); + level.timerDisplay["allies"] hud::setPoint( "TOPCENTER", "TOPCENTER", 0, 0 ); + level.timerDisplay["allies"].label = flagSpawningInStr; + level.timerDisplay["allies"].alpha = 0; + level.timerDisplay["allies"].archived = false; + level.timerDisplay["allies"].hideWhenInMenu = true; + + level.timerDisplay["axis" ] = hud::createServerTimer( "objective", 1.4, "axis" ); + level.timerDisplay["axis" ] hud::setPoint( "TOPCENTER", "TOPCENTER", 0, 0 ); + level.timerDisplay["axis" ].label = flagSpawningInStr; + level.timerDisplay["axis" ].alpha = 0; + level.timerDisplay["axis" ].archived = false; + level.timerDisplay["axis" ].hideWhenInMenu = true; + + thread hideTimerDisplayOnGameEnd( level.timerDisplay["allies"] ); + thread hideTimerDisplayOnGameEnd( level.timerDisplay["axis" ] ); +} + +function hideTimerDisplayOnGameEnd( timerDisplay ) +{ + level waittill("game_ended"); + timerDisplay.alpha = 0; +} + +function showFlag(flagIndex) +{ + assert( flagIndex < level.resFlags.size ); + resFlag = level.resFlags[flagIndex]; + label = resFlag.label; + + resFlag gameobjects::set_visible_team( "any" ); + resFlag gameobjects::set_model_visibility( true ); + + level.currentFlag = resFlag; + + if ( level.flagActivateDelay ) + { + hud_hideFlagProgressBar(); + + if ( level.PrematchPeriod > 0 && level.inPrematchPeriod == true ) + { + level waittill("prematch_over"); + } + + nextObjPoint = objpoints::create( "objpoint_next_hq", resFlag.curOrigin + level.iconoffset, "all", "waypoint_targetneutral" ); + nextObjPoint setWayPoint( true, "waypoint_targetneutral" ); + objective_position( level.locationObjID, resFlag.curOrigin ); + objective_icon( level.locationObjID, "waypoint_targetneutral" ); + objective_state( level.locationObjID, "active" ); + + updateObjectiveHintMessages( level.objectiveHintPrepareHQ, level.objectiveHintDefendHQ ); + + flagSpawningInStr = &"MP_HQ_AVAILABLE_IN"; + level.timerDisplay["allies"].label = flagSpawningInStr; + level.timerDisplay["allies"] setTimer( level.flagActivateDelay ); + level.timerDisplay["allies"].alpha = 1; + level.timerDisplay["axis" ].label = flagSpawningInStr; + level.timerDisplay["axis" ] setTimer( level.flagActivateDelay ); + level.timerDisplay["axis" ].alpha = 1; + + wait level.flagActivateDelay; + + objpoints::delete( nextObjPoint ); + objective_state( level.locationObjID, "invisible" ); + globallogic_audio::leader_dialog( "hq_online" ); + + hud_showFlagProgressBar(); + } + + level.timerDisplay["allies"].alpha = 0; + level.timerDisplay["axis" ].alpha = 0; + + resFlag gameobjects::set_2d_icon( "friendly", "compass_waypoint_defend" + label ); + resFlag gameobjects::set_3d_icon( "friendly", "waypoint_defend" + label ); + resFlag gameobjects::set_2d_icon( "enemy", "compass_waypoint_capture" + label ); + resFlag gameobjects::set_3d_icon( "enemy", "waypoint_capture" + label ); + + if ( level.overtime ) + { + resFlag gameobjects::allow_use( "enemy" ); + resFlag gameobjects::set_visible_team( "any" ); + resFlag gameobjects::set_owner_team( "neutral" ); + resFlag gameobjects::set_decay_time( level.flagCaptureTime ); + } + else + { + resFlag gameobjects::allow_use( "enemy" ); + } + + resFlag resetFlagBaseEffect(); + +} + +function hideFlag( flagIndex ) +{ + assert( flagIndex < level.resFlags.size ); + resFlag = level.resFlags[flagIndex]; + resFlag gameobjects::allow_use( "none" ); + resFlag gameobjects::set_visible_team( "none" ); + resFlag gameobjects::set_model_visibility( false ); + +} + +function getUnownedFlagNearestStart( team, excludeFlag ) +{ + best = undefined; + bestdistsq = undefined; + for ( i = 0; i < level.flags.size; i++ ) + { + flag = level.flags[i]; + + if ( flag getFlagTeam() != "neutral" ) + continue; + + distsq = distanceSquared( flag.origin, level.startPos[team] ); + if ( (!isdefined( excludeFlag ) || flag != excludeFlag) && (!isdefined( best ) || distsq < bestdistsq) ) + { + bestdistsq = distsq; + best = flag; + } + } + return best; +} + +function onBeginUse( player ) +{ + ownerTeam = self gameobjects::get_owner_team(); + SetDvar( "scr_obj" + self gameobjects::get_label() + "_flash", 1 ); + self.didStatusNotify = false; + + + if ( ownerTeam == "allies" ) + otherTeam = "axis"; + else + otherTeam = "allies"; + + if ( ownerTeam == "neutral" ) + { + + if( getTime() - level.lastDialogTime > 5000 ) + { + otherTeam = util::getOtherTeam( player.pers["team"] ); + statusDialog( "securing"+self.label, player.pers["team"] ); + //statusDialog( "losing"+self.label, otherTeam ); + level.lastDialogTime = getTime(); + } + self.objPoints[player.pers["team"]] thread objpoints::start_flashing(); + return; + } + + + + self.objPoints["allies"] thread objpoints::start_flashing(); + self.objPoints["axis"] thread objpoints::start_flashing(); + +} + + +function onUseUpdate( team, progress, change ) +{ + if ( !isdefined( level.resProgress ) ) + { + level.resProgress = progress; + } + + if ( progress > 0.05 && change && !self.didStatusNotify ) + { + ownerTeam = self gameobjects::get_owner_team(); + if( getTime() - level.lastDialogTime > 10000 ) + { + statusDialog( "losing"+self.label, ownerTeam ); + statusDialog( "securing"+self.label, team ); + level.lastDialogTime = getTime(); + } + + self.didStatusNotify = true; + } + + if ( level.resProgress < progress ) + { + // pause timer + globallogic_utils::pauseTimer(); + setGameEndTime( 0 ); + level.resProgressTeam = team; + } + else + { + // unpause the timer + globallogic_utils::resumeTimer(); + } + + level.resProgress = progress; + hud_setflagProgressBar( progress, team ); + + +} + + +function onUseClear( ) +{ + globallogic_utils::resumeTimer(); + hud_setflagProgressBar( 0 ); +} + +function statusDialog( dialog, team ) +{ + time = getTime(); + if ( getTime() < level.lastStatus[team] + 6000 ) + return; + + thread delayedLeaderDialog( dialog, team ); + level.lastStatus[team] = getTime(); +} + + +function onEndUse( team, player, success ) +{ + SetDvar( "scr_obj" + self gameobjects::get_label() + "_flash", 0 ); + + self.objPoints["allies"] thread objpoints::stop_flashing(); + self.objPoints["axis"] thread objpoints::stop_flashing(); + + +} + + +function resetFlagBaseEffect() +{ + // once these get setup we never change them + if ( isdefined( self.baseeffect ) ) + return; + + team = self gameobjects::get_owner_team(); + + if ( team != "axis" && team != "allies" ) + return; + + fxid = level.flagBaseFXid[ team ]; + + self.baseeffect = spawnFx( fxid, self.baseeffectpos, self.baseeffectforward, self.baseeffectright ); + triggerFx( self.baseeffect ); +} + +function onUse( player, team ) +{ + team = player.pers["team"]; + oldTeam = self gameobjects::get_owner_team(); + label = self gameobjects::get_label(); + + /#print( "flag captured: " + self.label );#/ + + setupNextFlag(self.orderIndex + 1); + + if ( (self.orderIndex + 1) == level.resFlags.size || level.overtime ) + { + setGameEndTime( 0 ); + wait 1; + res_endGame( player.team, game["strings"]["flags_capped"] ); + } + else + { + + level.useStartSpawns = false; + + assert( team != "neutral" ); + + if( [[level.getTimeLimit]]() > 0 && level.extraTime ) + { + level.usingExtraTime = true; + if ( !level.hardcoreMode ) + iPrintLn( &"MP_TIME_EXTENDED" ); + } + + spawnlogic::clear_spawn_points(); + spawnlogic::add_spawn_points( "allies", "mp_res_spawn_allies" ); + spawnlogic::add_spawn_points( "axis", "mp_res_spawn_axis" ); + + if ( label == "_a" ) + { + spawnlogic::clear_spawn_points(); + spawnlogic::add_spawn_points( "allies", "mp_res_spawn_allies_a" ); + spawnlogic::add_spawn_points( "axis", "mp_res_spawn_axis" ); + } + else if ( label == "_b" ) + { + spawnlogic::add_spawn_points( game["attackers"], "mp_res_spawn_allies_b" ); + spawnlogic::add_spawn_points( game["defenders"], "mp_res_spawn_axis" ); + } + else + { + spawnlogic::add_spawn_points( "allies", "mp_res_spawn_allies_c" ); + spawnlogic::add_spawn_points( "allies", "mp_res_spawn_axis" ); + } + + spawning::updateAllSpawnPoints(); + + string = &""; + switch ( label ) + { + case "_a": + string = &"MP_DOM_FLAG_A_CAPTURED_BY"; + break; + case "_b": + string = &"MP_DOM_FLAG_B_CAPTURED_BY"; + break; + case "_c": + string = &"MP_DOM_FLAG_C_CAPTURED_BY"; + break; + case "_d": + string = &"MP_DOM_FLAG_D_CAPTURED_BY"; + break; + case "_e": + string = &"MP_DOM_FLAG_E_CAPTURED_BY"; + break; + default: + break; + } + assert ( string != &"" ); + + // Copy touch list so there aren't any threading issues + touchList = []; + touchKeys = GetArrayKeys( self.touchList[team] ); + for ( i = 0 ; i < touchKeys.size ; i++ ) + touchList[touchKeys[i]] = self.touchList[team][touchKeys[i]]; + thread give_capture_credit( touchList, string ); + + thread util::printAndSoundOnEveryone( team, oldTeam, &"", &"", "mp_war_objective_taken", "mp_war_objective_lost", "" ); + + if ( getTeamFlagCount( team ) == level.flags.size ) + { + + statusDialog( "secure_all", team ); + statusDialog( "lost_all", oldTeam ); + } + else + { + statusDialog( "secured"+self.label, team ); + + statusDialog( "lost"+self.label, oldTeam ); + } + + level.bestSpawnFlag[ oldTeam ] = self.levelFlag; + + self update_spawn_influencers( team ); + } +} + + +function give_capture_credit( touchList, string ) +{ + wait .05; + util::WaitTillSlowProcessAllowed(); + + + players = getArrayKeys( touchList ); + for ( i = 0; i < players.size; i++ ) + { + player_from_touchlist = touchList[players[i]].player; + + //scoreevents::processScoreEvent( "position_secure", player_from_touchlist ); + player_from_touchlist RecordGameEvent("capture"); + if( isdefined(player_from_touchlist.pers["captures"]) ) + { + player_from_touchlist.pers["captures"]++; + player_from_touchlist.captures = player_from_touchlist.pers["captures"]; + } + demo::bookmark( "event", gettime(), player_from_touchlist ); + player_from_touchlist AddPlayerStatWithGameType( "CAPTURES", 1 ); + + level thread popups::DisplayTeamMessageToAll( string, player_from_touchlist ); + } +} + +function delayedLeaderDialog( sound, team ) +{ + wait .1; + util::WaitTillSlowProcessAllowed(); + + globallogic_audio::leader_dialog( sound, team ); +} + +function onScoreCloseMusic () +{ + axisScore = [[level._getTeamScore]]( "axis" ); + alliedScore = [[level._getTeamScore]]( "allies" ); + scoreLimit = level.scoreLimit; + scoreThreshold = scoreLimit * .1; + scoreDif = abs(axisScore - alliedScore); + scoreThresholdStart = abs(scoreLimit - scoreThreshold); + scoreLimitCheck = scoreLimit - 10; + + if( !isdefined( level.playingActionMusic ) ) + level.playingActionMusic = false; + + if (alliedScore > axisScore) + { + currentScore = alliedScore; + } + else + { + currentScore = axisScore; + } + if( GetDvarint( "debug_music" ) > 0 ) + { + /# + println ("Music System Resistance - scoreDif " + scoreDif); + println ("Music System Resistance - axisScore " + axisScore); + println ("Music System Resistance - alliedScore " + alliedScore); + println ("Music System Resistance - scoreLimit " + scoreLimit); + println ("Music System Resistance - currentScore " + currentScore); + println ("Music System Resistance - scoreThreshold " + scoreThreshold); + println ("Music System Resistance - scoreDif " + scoreDif); + println ("Music System Resistance - scoreThresholdStart " + scoreThresholdStart); + #/ + } + if ( scoreDif <= scoreThreshold && scoreThresholdStart <= currentScore && (level.playingActionMusic != true)) + { + //play some action music + thread globallogic_audio::set_music_on_team( "timeOut" ); + } + else + { + return; + } +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( self.touchTriggers.size && isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] ) + { + triggerIds = getArrayKeys( self.touchTriggers ); + ownerTeam = self.touchTriggers[triggerIds[0]].useObj.ownerTeam; + team = self.pers["team"]; + + if ( team == ownerTeam ) + { + if ( !isdefined( attacker.res_offends ) ) + attacker.res_offends = 0; + + attacker.res_offends++; + + if ( level.playerOffensiveMax >= attacker.res_offends ) + { + attacker medals::offenseGlobalCount(); + attacker thread challenges::killedBaseOffender( self.touchTriggers[triggerIds[0]], weapon ); + + scoreevents::processScoreEvent( "killed_defender", attacker ); + self RecordKillModifier("defending"); + } + } + else + { + if ( !isdefined( attacker.res_defends ) ) + attacker.res_defends = 0; + + attacker.res_defends++; + + if ( level.playerDefensiveMax >= attacker.res_defends ) + { + + attacker medals::defenseGlobalCount(); + attacker thread challenges::killedBaseDefender( self.touchTriggers[triggerIds[0]] ); + + if( isdefined(attacker.pers["defends"]) ) + { + attacker.pers["defends"]++; + attacker.defends = attacker.pers["defends"]; + } + + scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon ); + self RecordKillModifier("assaulting"); + } + } + } +} + +function getTeamFlagCount( team ) +{ + score = 0; + for (i = 0; i < level.flags.size; i++) + { + if ( level.resFlags[i] gameobjects::get_owner_team() == team ) + score++; + } + return score; +} + +function getFlagTeam() +{ + return self.useObj gameobjects::get_owner_team(); +} + +function updateObjectiveHintMessages( alliesObjective, axisObjective ) +{ + game["strings"]["objective_hint_allies"] = alliesObjective; + game["strings"]["objective_hint_axis" ] = axisObjective; +} + + +function createFlagSpawnInfluencers() +{ + ss = level.spawnsystem; + + for (flag_index = 0; flag_index < level.flags.size; flag_index++) + { + if ( level.resFlags[flag_index] == self ) + break; + } + + // Resistance: owned flag influencers + self.owned_flag_influencer = self spawning::create_influencer( "res_friendly", self.trigger.origin, 0 ); + + // Resistance: un-owned inner flag influencers + self.neutral_flag_influencer = self spawning::create_influencer( "res_neutral", self.trigger.origin, 0 ); + + // Resistance: enemy flag influencers + self.enemy_flag_influencer = self spawning::create_influencer( "res_enemy", self.trigger.origin, 0 ); + + + // default it to neutral + self update_spawn_influencers("neutral"); +} + +function update_spawn_influencers( team ) +{ + assert(isdefined(self.neutral_flag_influencer)); + assert(isdefined(self.owned_flag_influencer)); + assert(isdefined(self.enemy_flag_influencer)); + + if ( team == "neutral" ) + { + EnableInfluencer(self.neutral_flag_influencer, true); + EnableInfluencer(self.owned_flag_influencer, false); + EnableInfluencer(self.enemy_flag_influencer, false); + } + else + { + EnableInfluencer(self.neutral_flag_influencer, false); + EnableInfluencer(self.owned_flag_influencer, true); + EnableInfluencer(self.enemy_flag_influencer, true); + + SetInfluencerTeammask(self.owned_flag_influencer, util::getTeamMask(team) ); + SetInfluencerTeammask(self.enemy_flag_influencer, util::getOtherTeamsMask(team) ); + } +} + +function hud_createFlagProgressBar() +{ + level.attackersCaptureProgressHUD = hud::createTeamProgressBar( game["attackers"] ); + level.defendersCaptureProgressHUD = hud::createTeamProgressBar( game["defenders"] ); + + hud_hideFlagProgressBar(); +} + +function hud_hideFlagProgressBar() +{ + hud_setflagProgressBar( 0 ); + + level.attackersCaptureProgressHUD hud::hideElem(); + level.defendersCaptureProgressHUD hud::hideElem(); +} + +function hud_showFlagProgressBar() +{ + level.attackersCaptureProgressHUD hud::showElem(); + level.defendersCaptureProgressHUD hud::showElem(); +} + +function hud_setflagProgressBar( value, cappingTeam ) +{ + if ( value < 0.0 ) + value = 0.0; + if ( value > 1.0 ) + value = 1.0; + + if ( isdefined( cappingTeam ) ) + { + if ( cappingTeam == game["attackers"] ) + { + level.attackersCaptureProgressHUD.bar.color = (255,255,255); + level.defendersCaptureProgressHUD.bar.color = (255,0,0); + } + else + { + level.attackersCaptureProgressHUD.bar.color = (255,0,0); + level.defendersCaptureProgressHUD.bar.color = (255,255,255); + } + } + + level.attackersCaptureProgressHUD hud::updateBar(value ); + level.defendersCaptureProgressHUD hud::updateBar(value ); +} diff --git a/mp/gametypes/sab.csc b/mp/gametypes/sab.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/sab.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/sab.gsc b/mp/gametypes/sab.gsc new file mode 100644 index 0000000..5196da4 --- /dev/null +++ b/mp/gametypes/sab.gsc @@ -0,0 +1,904 @@ +#using scripts\shared\demo_shared; +#using scripts\shared\exploder_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_message_shared; +#using scripts\shared\math_shared; +#using scripts\shared\medals_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_hud_message; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; + +/* + Sabotage + + // ...etc... +*/ + +/*QUAKED mp_sab_spawn_axis (0.75 0.0 0.5) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near their team at one of these positions.*/ + +/*QUAKED mp_sab_spawn_allies (0.0 0.75 0.5) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions.*/ + +/*QUAKED mp_sab_spawn_axis_start (1.0 0.0 0.5) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_sab_spawn_allies_start (0.0 1.0 0.5) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +#precache( "material", "compass_waypoint_bomb" ); +#precache( "material", "compass_waypoint_defend" ); +#precache( "material", "compass_waypoint_defuse" ); +#precache( "material", "compass_waypoint_target" ); +#precache( "string", "OBJECTIVES_TDM" ); +#precache( "string", "OBJECTIVES_SAB" ); +#precache( "string", "OBJECTIVES_TDM_SCORE" ); +#precache( "string", "OBJECTIVES_SAB_SCORE" ); +#precache( "string", "OBJECTIVES_TDM_HINT" ); +#precache( "string", "OBJECTIVES_SAB_HINT" ); +#precache( "string", "MP_EXPLOSIVES_RECOVERED_BY"); +#precache( "string", "MP_EXPLOSIVES_RECOVERED_BY"); +#precache( "string", "MP_EXPLOSIVES_DROPPED_BY"); +#precache( "string", "MP_EXPLOSIVES_PLANTED_BY"); +#precache( "string", "MP_EXPLOSIVES_DEFUSED_BY"); +#precache( "string", "MP_YOU_HAVE_RECOVERED_THE_BOMB"); +#precache( "string", "PLATFORM_HOLD_TO_PLANT_EXPLOSIVES"); +#precache( "string", "PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES"); +#precache( "string", "MP_PLANTING_EXPLOSIVE"); +#precache( "string", "MP_DEFUSING_EXPLOSIVE"); +#precache( "string", "MP_TARGET_DESTROYED"); +#precache( "string", "MP_NO_RESPAWN"); +#precache( "string", "MP_TIE_BREAKER"); +#precache( "string", "MP_NO_RESPAWN"); +#precache( "string", "MP_SUDDEN_DEATH"); +#precache( "triggerstring", "PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); +#precache( "triggerstring", "PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); +#precache( "fx", "_t6/maps/mp_maps/fx_mp_exp_bomb" ); + +function main() +{ + globallogic::init(); + + level.teamBased = true; + level.overrideTeamScore = true; + + util::registerRoundSwitch( 0, 9 ); + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 500 ); + util::registerRoundLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + util::registerRoundWinLimit( 0, 10 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.onRoundEndGame =&onRoundEndGame; + + gameobjects::register_allowed_gameobject( level.gameType ); + + if ( !game["tiebreaker"] ) + { + level.onPrecacheGameType =&onPrecacheGameType; + level.onTimeLimit =&onTimeLimit; + level.onDeadEvent =&onDeadEvent; + level.onRoundSwitch =&onRoundSwitch; + level.onPlayerKilled =&onPlayerKilled; + + level.endGameOnScoreLimit = false; + + game["dialog"]["gametype"] = "sab_start"; + game["dialog"]["gametype_hardcore"] = "hcsab_start"; + game["dialog"]["offense_obj"] = "destroy_start"; + game["dialog"]["defense_obj"] = "destroy_start"; + game["dialog"]["sudden_death"] = "suddendeath"; + game["dialog"]["sudden_death_boost"] = "generic_boost"; + } + else + { + level.onEndGame =&onEndGame; + + level.endGameOnScoreLimit = false; + + game["dialog"]["gametype"] = "sab_start"; + game["dialog"]["gametype_hardcore"] = "hcsab_start"; + game["dialog"]["offense_obj"] = "generic_boost"; + game["dialog"]["defense_obj"] = "generic_boost"; + game["dialog"]["sudden_death"] = "suddendeath"; + game["dialog"]["sudden_death_boost"] = "generic_boost"; + + util::registerNumLives( 1, 1 ); + util::registerTimeLimit( 0, 0 ); + } + + badtrig = getent( "sab_bomb_defuse_allies", "targetname" ); + if ( isdefined( badtrig ) ) + badtrig delete(); + + badtrig = getent( "sab_bomb_defuse_axis", "targetname" ); + if ( isdefined( badtrig ) ) + badtrig delete(); + + level.lastDialogTime = 0; + + // Sets the scoreboard columns and determines with data is sent across the network + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "plants", "defuses" ); +} + +function onPrecacheGameType() +{ + game["bomb_dropped_sound"] = "mp_war_objective_lost"; + game["bomb_recovered_sound"] = "mp_war_objective_taken"; +} + + +function onRoundSwitch() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["teamScores"]["allies"] == level.scorelimit - 1 && game["teamScores"]["axis"] == level.scorelimit - 1 ) + { + level.halftimeType = "overtime"; + level.halftimeSubCaption = &"MP_TIE_BREAKER"; + game["tiebreaker"] = true; + } + else + { + level.halftimeType = "halftime"; + game["switchedsides"] = !game["switchedsides"]; + } +} + + +function onStartGameType() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + setClientNameMode("auto_change"); + + game["strings"]["target_destroyed"] = &"MP_TARGET_DESTROYED"; + + if ( !game["tiebreaker"] ) + { + util::setObjectiveText( "allies", &"OBJECTIVES_SAB" ); + util::setObjectiveText( "axis", &"OBJECTIVES_SAB" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_SAB" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_SAB" ); + } + else + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_SAB_SCORE" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_SAB_SCORE" ); + } + util::setObjectiveHintText( "allies", &"OBJECTIVES_SAB_HINT" ); + util::setObjectiveHintText( "axis", &"OBJECTIVES_SAB_HINT" ); + } + else + { + util::setObjectiveText( "allies", &"OBJECTIVES_TDM" ); + util::setObjectiveText( "axis", &"OBJECTIVES_TDM" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_TDM" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_TDM" ); + } + else + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_TDM_SCORE" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_TDM_SCORE" ); + } + util::setObjectiveHintText( "allies", &"OBJECTIVES_TDM_HINT" ); + util::setObjectiveHintText( "axis", &"OBJECTIVES_TDM_HINT" ); + } + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::place_spawn_points( "mp_sab_spawn_allies_start" ); + spawnlogic::place_spawn_points( "mp_sab_spawn_axis_start" ); + spawnlogic::add_spawn_points( "allies", "mp_sab_spawn_allies" ); + spawnlogic::add_spawn_points( "axis", "mp_sab_spawn_axis" ); + spawning::updateAllSpawnPoints(); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + level.spawn_axis = spawnlogic::get_spawnpoint_array( "mp_sab_spawn_axis" ); + level.spawn_allies = spawnlogic::get_spawnpoint_array( "mp_sab_spawn_allies" ); + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array("mp_sab_spawn_" + team + "_start"); + } + + + thread updateGametypeDvars(); + + thread sabotage(); +} + + +function onTimeLimit() +{ + if ( level.inOvertime ) + return; + + thread onOvertime(); +} + + +function onOvertime() +{ + level endon ( "game_ended" ); + + level.timeLimitOverride = true; + level.inOvertime = true; + globallogic_audio::leader_dialog( "sudden_death" ); + globallogic_audio::leader_dialog( "sudden_death_boost" ); + for ( index = 0; index < level.players.size; index++ ) + { + level.players[index] notify("force_spawn"); + level.players[index] thread hud_message::oldNotifyMessage( &"MP_SUDDEN_DEATH", &"MP_NO_RESPAWN", undefined, (1, 0, 0), "mp_last_stand" ); + + level.players[index] setClientUIVisibilityFlag( "g_compassShowEnemies", 1 ); + } + + SetMatchTalkFlag( "DeadChatWithDead", 1 ); + SetMatchTalkFlag( "DeadChatWithTeam", 0 ); + SetMatchTalkFlag( "DeadHearTeamLiving", 0 ); + SetMatchTalkFlag( "DeadHearAllLiving", 0 ); + SetMatchTalkFlag( "EveryoneHearsEveryone", 0 ); + + waitTime = 0; + while ( waitTime < 90 ) + { + if ( !level.bombPlanted ) + { + waitTime += 1; + setGameEndTime( getTime() + ((90-waitTime)*1000) ); + } + wait ( 1.0 ); + } + + thread globallogic::endGame( "tie", game["strings"]["tie"] ); +} + + +function onDeadEvent( team ) +{ + if ( level.bombExploded ) + return; + + if ( team == "all" ) + { + if ( level.bombPlanted ) + { + globallogic_score::giveTeamScoreForObjective( level.bombPlantedBy, 1 ); + thread globallogic::endGame( level.bombPlantedBy, game["strings"][level.bombPlantedBy+"_mission_accomplished"] ); + } + else + { + thread globallogic::endGame( "tie", game["strings"]["tie"] ); + } + } + else if ( level.bombPlanted ) + { + if ( team == level.bombPlantedBy ) + { + level.plantingTeamDead = true; + return; + } + + otherTeam = util::getOtherTeam( level.bombPlantedBy ); + globallogic_score::giveTeamScoreForObjective( level.bombPlantedBy, 1 ); + thread globallogic::endGame( level.bombPlantedBy, game["strings"][otherTeam+"_eliminated"] ); + } + else + { + otherTeam = util::getOtherTeam( team ); + globallogic_score::giveTeamScoreForObjective( otherTeam, 1 ); + thread globallogic::endGame( otherTeam, game["strings"][team+"_eliminated"] ); + } +} + +function onSpawnPlayer(predictedSpawn) +{ + self.isPlanting = false; + self.isDefusing = false; + self.isBombCarrier = false; + + if ( game["tiebreaker"] ) + { + self thread hud_message::oldNotifyMessage( &"MP_TIE_BREAKER", &"MP_NO_RESPAWN", undefined, (1, 0, 0), "mp_last_stand" ); + + self setClientUIVisibilityFlag( "g_compassShowEnemies", 1 ); + + // this is being redundantly set everytime a player spawns + // need to move this to a once only for eveyone when tiebreaker round + // starts + SetMatchTalkFlag( "DeadChatWithDead", 1 ); + SetMatchTalkFlag( "DeadChatWithTeam", 0 ); + SetMatchTalkFlag( "DeadHearTeamLiving", 0 ); + SetMatchTalkFlag( "DeadHearAllLiving", 0 ); + SetMatchTalkFlag( "EveryoneHearsEveryone", 0 ); + } + + spawning::onSpawnPlayer(predictedSpawn); +} + +function updateGametypeDvars() +{ + level.plantTime = GetGametypeSetting( "plantTime" ); + level.defuseTime = GetGametypeSetting( "defuseTime" ); + level.bombTimer = GetGametypeSetting( "bombTimer" ); + level.hotPotato = GetGametypeSetting( "hotPotato" ); +} + + +function sabotage() +{ + level.bombPlanted = false; + level.bombExploded = false; + + level._effect["bombexplosion"] = "_t6/maps/mp_maps/fx_mp_exp_bomb"; + + trigger = getEnt( "sab_bomb_pickup_trig", "targetname" ); + if ( !isdefined( trigger ) ) + { + util::error( "No sab_bomb_pickup_trig trigger found in map." ); + return; + } + + visuals[0] = getEnt( "sab_bomb", "targetname" ); + if ( !isdefined( visuals[0] ) ) + { + util::error( "No sab_bomb script_model found in map." ); + return; + } + + //visuals[0] setModel( "t5_weapon_briefcase_world" ); + level.sabBomb = gameobjects::create_carry_object( "neutral", trigger, visuals, (0,0,32) ); + level.sabBomb gameobjects::allow_carry( "any" ); + level.sabBomb gameobjects::set_2d_icon( "enemy", "compass_waypoint_bomb" ); + level.sabBomb gameobjects::set_3d_icon( "enemy", "waypoint_bomb" ); + level.sabBomb gameobjects::set_2d_icon( "friendly", "compass_waypoint_bomb" ); + level.sabBomb gameobjects::set_3d_icon( "friendly", "waypoint_bomb" ); + level.sabBomb gameobjects::set_carry_icon( "hud_suitcase_bomb" ); + level.sabBomb gameobjects::set_visible_team( "any" ); + level.sabBomb.objIDPingEnemy = true; + level.sabBomb.onPickup =&onPickup; + level.sabBomb.onDrop =&onDrop; + level.sabBomb.allowWeapons = true; + level.sabBomb.objPoints["allies"].archived = true; + level.sabBomb.objPoints["axis"].archived = true; + level.sabBomb.autoResetTime = 60.0; + + if ( !isdefined( getEnt( "sab_bomb_axis", "targetname" ) ) ) + { + /#util::error("No sab_bomb_axis trigger found in map.");#/ + return; + } + if ( !isdefined( getEnt( "sab_bomb_allies", "targetname" ) ) ) + { + /#util::error("No sab_bomb_allies trigger found in map.");#/ + return; + } + + if ( game["switchedsides"] ) + { + level.bombZones["allies"] = createBombZone( "allies", getEnt( "sab_bomb_axis", "targetname" ) ); + level.bombZones["axis"] = createBombZone( "axis", getEnt( "sab_bomb_allies", "targetname" ) ); + } + else + { + level.bombZones["allies"] = createBombZone( "allies", getEnt( "sab_bomb_allies", "targetname" ) ); + level.bombZones["axis"] = createBombZone( "axis", getEnt( "sab_bomb_axis", "targetname" ) ); + } +} + + +function createBombZone( team, trigger ) +{ + visuals = getEntArray( trigger.target, "targetname" ); + + bombZone = gameobjects::create_use_object( team, trigger, visuals, (0,0,64) ); + bombZone resetBombsite(); + bombZone.onUse =&onUse; + bombZone.onBeginUse =&onBeginUse; + bombZone.onEndUse =&onEndUse; + bombZone.onCantUse =&onCantUse; + bombZone.useWeapon = GetWeapon( "briefcase_bomb" ); + bombZone.visuals[0].killCamEnt = spawn( "script_model", bombZone.visuals[0].origin + (0,0,128) ); + + for ( i = 0; i < visuals.size; i++ ) + { + if ( isdefined( visuals[i].script_exploder ) ) + { + bombZone.exploderIndex = visuals[i].script_exploder; + break; + } + } + + return bombZone; +} + + +function onBeginUse( player ) +{ + // planted the bomb + if ( !self gameobjects::is_friendly_team( player.pers["team"] ) ) + { + player.isPlanting = true; + player thread battlechatter::gametype_specific_battle_chatter( "sd_friendlyplant", player.pers["team"] ); + } + else + { + player.isDefusing = true; + player thread battlechatter::gametype_specific_battle_chatter( "sd_enemyplant", player.pers["team"] ); + } + + player playSound( "fly_bomb_raise_plr" ); +} + +function onEndUse( team, player, result ) +{ + if ( !isAlive( player ) ) + return; + + player.isPlanting = false; + player.isDefusing = false; + player notify( "event_ended" ); +} + + +function onPickup( player ) +{ + level notify ( "bomb_picked_up" ); + + player RecordGameEvent("pickup"); + + self.autoResetTime = 60.0; + + level.useStartSpawns = false; + + team = player.pers["team"]; + + if ( team == "allies" ) + otherTeam = "axis"; + else + otherTeam = "allies"; + + //player iPrintLnBold( &"MP_YOU_HAVE_RECOVERED_THE_BOMB" ); + player playLocalSound( "mp_suitcase_pickup" ); + /#print( "bomb taken" );#/ + + excludeList[0] = player; + + if( getTime() - level.lastDialogTime > 10000 ) + { + globallogic_audio::leader_dialog( "bomb_acquired", team ); + player globallogic_audio::leader_dialog_on_player( "obj_destroy", "bomb" ); + + + if ( !level.splitscreen ) + { + globallogic_audio::leader_dialog( "bomb_taken", otherTeam ); + globallogic_audio::leader_dialog( "obj_defend", otherTeam ); + } + + level.lastDialogTime = getTime(); + } + player.isBombCarrier = true; + + player AddPlayerStatWithGameType( "PICKUPS", 1 ); + + + // recovered the bomb before abandonment timer elapsed + if ( team == self gameobjects::get_owner_team() ) + { + util::printOnTeamArg( &"MP_EXPLOSIVES_RECOVERED_BY", team, player ); + sound::play_on_players( game["bomb_recovered_sound"], team ); + } + else + { + util::printOnTeamArg( &"MP_EXPLOSIVES_RECOVERED_BY", team, player ); +// util::printOnTeamArg( &"MP_EXPLOSIVES_RECOVERED_BY", otherTeam, &"MP_THE_ENEMY" ); + sound::play_on_players( game["bomb_recovered_sound"] ); + } + + self gameobjects::set_owner_team( team ); + self gameobjects::set_visible_team( "any" ); + self gameobjects::set_2d_icon( "enemy", "compass_waypoint_target" ); + self gameobjects::set_3d_icon( "enemy", "waypoint_kill" ); + self gameobjects::set_2d_icon( "friendly", "compass_waypoint_defend" ); + self gameobjects::set_3d_icon( "friendly", "waypoint_defend" ); + + level.bombZones[team] gameobjects::set_visible_team( "none" ); + level.bombZones[otherTeam] gameobjects::set_visible_team( "any" ); + + level.bombZones[otherTeam].trigger SetInvisibleToAll(); + level.bombZones[otherTeam].trigger SetVisibleToPlayer( player ); +} + + +function onDrop( player ) +{ + if ( level.bombPlanted ) + { + + } + else + { + if ( isdefined( player ) ) + util::printOnTeamArg( &"MP_EXPLOSIVES_DROPPED_BY", self gameobjects::get_owner_team(), player ); +// else +// util::printOnTeamArg( &"MP_EXPLOSIVES_DROPPED_BY", self gameobjects::get_owner_team(), &"MP_YOUR_TEAM" ); + + sound::play_on_players( game["bomb_dropped_sound"], self gameobjects::get_owner_team() ); + /# + if ( isdefined( player ) ) + print( "bomb dropped" ); + else + print( "bomb dropped" ); + #/ + + globallogic_audio::leader_dialog( "bomb_lost", self gameobjects::get_owner_team() ); + + player notify( "event_ended" ); + + level.bombZones["axis"].trigger SetInvisibleToAll(); + level.bombZones["allies"].trigger SetInvisibleToAll(); + + thread abandonmentThink( 0.0 ); + } +} + + +function abandonmentThink( delay ) +{ + level endon ( "bomb_picked_up" ); + + wait ( delay ); + + if ( isdefined( self.carrier ) ) + return; + + if ( self gameobjects::get_owner_team() == "allies" ) + otherTeam = "axis"; + else + otherTeam = "allies"; + +// util::printOnTeamArg( &"MP_EXPLOSIVES_DROPPED_BY", otherTeam, &"MP_THE_ENEMY" ); + sound::play_on_players( game["bomb_dropped_sound"], otherTeam ); + + self gameobjects::set_owner_team( "neutral" ); + self gameobjects::set_visible_team( "any" ); + self gameobjects::set_2d_icon( "enemy", "compass_waypoint_bomb" ); + self gameobjects::set_3d_icon( "enemy", "waypoint_bomb" ); + self gameobjects::set_2d_icon( "friendly", "compass_waypoint_bomb" ); + self gameobjects::set_3d_icon( "friendly", "waypoint_bomb" ); + + level.bombZones["allies"] gameobjects::set_visible_team( "none" ); + level.bombZones["axis"] gameobjects::set_visible_team( "none" ); +} + + +function onUse( player ) +{ + team = player.pers["team"]; + otherTeam = util::getOtherTeam( team ); + + // planted the bomb + if ( !self gameobjects::is_friendly_team( player.pers["team"] ) ) + { + player notify ( "bomb_planted" ); +// removed old playsound entry CDC 2/18/10 +// player playSound( "mpl_sab_bomb_plant" ); + /# print( "bomb planted" );#/ + + if( isdefined(player.pers["plants"]) ) + { + player.pers["plants"]++; + player.plants = player.pers["plants"]; + } + + demo::bookmark( "event", gettime(), player ); + + player AddPlayerStatWithGameType( "PLANTS", 1 ); + + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_PLANTED_BY", player ); + + //thread sound::play_on_players( "mus_sab_planted"+"_"+level.teamPostfix[team] ); + // Play Action music + globallogic_audio::set_music_on_team( "ACTION", "both", true ); + + globallogic_audio::leader_dialog( "bomb_planted", team ); + + globallogic_audio::leader_dialog( "bomb_planted", otherTeam ); + + scoreevents::processScoreEvent( "planted_bomb", player ); + player RecordGameEvent("plant"); + + level thread bombPlanted( self, player.pers["team"] ); + + level.bombOwner = player; + + player.isBombCarrier = false; + +// self.keyObject gameobjects::disable_object(); + level.sabBomb.autoResetTime = undefined; + level.sabBomb gameobjects::allow_carry( "none" ); + level.sabBomb gameobjects::set_visible_team( "none" ); + level.sabBomb gameobjects::set_dropped(); + self.useWeapon = GetWeapon( "briefcase_bomb_defuse" ); + + self setUpForDefusing(); + } + else // defused the bomb + { + player notify ( "bomb_defused" ); + /#print( "bomb defused" );#/ + + if( isdefined(player.pers["defuses"]) ) + { + player.pers["defuses"]++; + player.defuses = player.pers["defuses"]; + } + + demo::bookmark( "event", gettime(), player ); + + player AddPlayerStatWithGameType( "DEFUSES", 1 ); + + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_DEFUSED_BY", player ); + //thread sound::play_on_players( "mus_sab_defused"+"_"+level.teamPostfix[team] ); + + globallogic_audio::leader_dialog( "bomb_defused" ); + + scoreevents::processScoreEvent( "defused_bomb", player ); + player RecordGameEvent("defuse"); + + level thread bombDefused( self ); + + if ( level.inOverTime && isdefined( level.plantingTeamDead ) ) + { + thread globallogic::endGame( player.pers["team"], game["strings"][level.bombPlantedBy+"_eliminated"] ); + return; + } + + self resetBombsite(); + + level.sabBomb gameobjects::allow_carry( "any" ); + level.sabBomb gameobjects::set_picked_up( player ); + } +} + + +function onCantUse( player ) +{ + player iPrintLnBold( &"MP_CANT_PLANT_WITHOUT_BOMB" ); +} + + +function bombPlanted( destroyedObj, team ) +{ + game["challenge"][team]["plantedBomb"] = true; + globallogic_utils::pauseTimer(); + level.bombPlanted = true; + level.bombPlantedBy = team; + level.timeLimitOverride = true; + setMatchFlag( "bomb_timer", 1 ); + + // communicate timer information to menus + setGameEndTime( int( getTime() + (level.bombTimer * 1000) ) ); + + destroyedObj.visuals[0] thread globallogic_utils::playTickingSound( "mpl_sab_ui_suitcasebomb_timer" ); + + starttime = gettime(); + bombTimerWait(); + + setMatchFlag( "bomb_timer", 0 ); + destroyedObj.visuals[0] globallogic_utils::stopTickingSound(); + + if ( !level.bombPlanted ) + { + if ( level.hotPotato ) + { + timePassed = (gettime() - starttime) / 1000; + level.bombTimer -= timePassed; + } + return; + } + /* + for ( index = 0; index < level.players.size; index++ ) + { + player = level.players[index]; + if ( player.pers["team"] == team ) + player thread hud_message::oldNotifyMessage( "Your team scored!", undefined, undefined, (0, 1, 0) ); + else if ( player.pers["team"] != team ) + player thread hud_message::oldNotifyMessage( "Enemy team scored!", undefined, undefined, (1, 0, 0) ); + } + */ + explosionOrigin = level.sabBomb.visuals[0].origin+(0,0,12); + level.bombExploded = true; + + + if ( isdefined( level.bombowner ) ) + { + destroyedObj.visuals[0] radiusDamage( explosionOrigin, 512, 200, 20, level.bombowner, "MOD_EXPLOSIVE", GetWeapon( "briefcase_bomb" ) ); + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_BLOWUP_BY", level.bombowner ); + + level.bombowner AddPlayerStatWithGameType( "DESTRUCTIONS", 1 ); + scoreevents::processScoreEvent( "bomb_detonated", level.bombowner ); + + } + else + destroyedObj.visuals[0] radiusDamage( explosionOrigin, 512, 200, 20, undefined, "MOD_EXPLOSIVE", GetWeapon( "briefcase_bomb" ) ); + + rot = randomfloat(360); + explosionEffect = spawnFx( level._effect["bombexplosion"], explosionOrigin + (0,0,50), (0,0,1), (cos(rot),sin(rot),0) ); + triggerFx( explosionEffect ); + + thread sound::play_in_space( "mpl_sab_exp_suitcase_bomb_main", explosionOrigin ); + + if ( isdefined( destroyedObj.exploderIndex ) ) + exploder::exploder( destroyedObj.exploderIndex ); + + [[level._setTeamScore]]( team, [[level._getTeamScore]]( team ) + 1 ); + + setGameEndTime( 0 ); + + level.bombZones["allies"] gameobjects::set_visible_team( "none" ); + level.bombZones["axis"] gameobjects::set_visible_team( "none" ); + wait 3; + + // end the round without resetting the timer + thread globallogic::endGame( team, game["strings"]["target_destroyed"] ); +} + +function bombTimerWait() +{ + level endon("bomb_defused"); + hostmigration::waitLongDurationWithGameEndTimeUpdate( level.bombTimer ); +} + + +function resetBombsite() +{ + self gameobjects::allow_use( "enemy" ); + self gameobjects::set_use_time( level.plantTime ); + self gameobjects::set_use_text( &"MP_PLANTING_EXPLOSIVE" ); + self gameobjects::set_use_hint_text( &"PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); + self gameobjects::set_key_object( level.sabBomb ); + self gameobjects::set_2d_icon( "friendly", "compass_waypoint_defend" ); + self gameobjects::set_3d_icon( "friendly", "waypoint_defend" ); + self gameobjects::set_2d_icon( "enemy", "compass_waypoint_target" ); + self gameobjects::set_3d_icon( "enemy", "waypoint_target" ); + self gameobjects::set_visible_team( "none" ); + self.trigger SetInvisibleToAll(); + self.useWeapon = GetWeapon( "briefcase_bomb" ); +} + +function setUpForDefusing() +{ + self gameobjects::allow_use( "friendly" ); + self gameobjects::set_use_time( level.defuseTime ); + self gameobjects::set_use_text( &"MP_DEFUSING_EXPLOSIVE" ); + self gameobjects::set_use_hint_text( &"PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); + self gameobjects::set_key_object( undefined ); + self gameobjects::set_2d_icon( "friendly", "compass_waypoint_defuse" ); + self gameobjects::set_3d_icon( "friendly", "waypoint_defuse" ); + self gameobjects::set_2d_icon( "enemy", "compass_waypoint_defend" ); + self gameobjects::set_3d_icon( "enemy", "waypoint_defend" ); + self gameobjects::set_visible_team( "any" ); + self.trigger SetVisibleToAll(); +} + +function bombDefused( object ) +{ + setMatchFlag( "bomb_timer", 0 ); + globallogic_utils::resumeTimer(); + level.bombPlanted = false; + if ( !level.inOvertime ) + level.timeLimitOverride = false; + + level notify("bomb_defused"); +} + +function onPlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration) +{ + inBombZone = false; + inBombZoneTeam = "none"; + + if ( isdefined( level.bombZones["allies"] ) ) + { + dist = Distance2dSquared(self.origin, level.bombZones["allies"].curorigin); + if ( dist < level.defaultOffenseRadiusSQ ) + { + inBombZoneTeam = "allies"; + inBombZone = true; + } + } + if ( isdefined( level.bombZones["axis"] ) ) + { + dist = Distance2dSquared(self.origin, level.bombZones["axis"].curorigin); + if ( dist < level.defaultOffenseRadiusSQ ) + { + inBombZoneTeam = "axis"; + inBombZone = true; + } + } + + if ( inBombZone && isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] ) + { + if ( inBombZoneTeam == self.pers["team"] ) + { + //attacker medals::offense( weapon ); + attacker thread challenges::killedBaseOffender( level.bombZones[inBombZoneTeam], weapon ); + self RecordKillModifier("defending"); + } + else + { + if( isdefined(attacker.pers["defends"]) ) + { + attacker.pers["defends"]++; + attacker.defends = attacker.pers["defends"]; + } + + attacker medals::defenseGlobalCount(); + attacker thread challenges::killedBaseDefender( level.bombZones[inBombZoneTeam] ); + self RecordKillModifier("assaulting"); + } + } + + if ( isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] && isdefined( self.isBombCarrier ) && self.isBombCarrier == true ) + { + self RecordKillModifier("carrying"); + } + + if( self.isPlanting == true ) + self RecordKillModifier("planting"); + + if( self.isDefusing == true ) + self RecordKillModifier("defusing"); +} + +function onEndGame( winningTeam ) +{ + if ( isdefined( winningTeam ) && (winningTeam == "allies" || winningTeam == "axis") ) + globallogic_score::giveTeamScoreForObjective( winningTeam, 1 ); +} + +function onRoundEndGame( roundWinner ) +{ + winner = globallogic::determineTeamWinnerByGameStat( "roundswon" ); + + return winner; +} \ No newline at end of file diff --git a/mp/gametypes/sas.csc b/mp/gametypes/sas.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/sas.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/sas.gsc b/mp/gametypes/sas.gsc new file mode 100644 index 0000000..643eefa --- /dev/null +++ b/mp/gametypes/sas.gsc @@ -0,0 +1,465 @@ +#using scripts\shared\array_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hud_message_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\persistence_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weapon_utils; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\_wager; + +#using scripts\mp\_util; + +/* + Deathmatch + Objective: Score points by eliminating other players + Map ends: When one player reaches the score limit, or time limit is reached + Respawning: No wait / Away from other players + + Level requirements + ------------------ + Spawnpoints: + classname mp_wager_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of enemies at the time of spawn. + Players generally spawn away from enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + Because Deathmatch doesn't have teams with regard to gameplay or scoring, this effectively sets the available weapons. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals +*/ + +//#define SAS_PRIMARY_WEAPON "crossbow" // brian barnes - crossbow weapon deleted, hoping just swapping this to a supported weapon will work + + + + +#precache( "string", "OBJECTIVES_SAS" ); +#precache( "string", "OBJECTIVES_SAS_SCORE" ); +#precache( "string", "OBJECTIVES_SAS_HINT" ); +#precache( "string", "MP_HUMILIATION" ); +#precache( "string", "MP_HUMILIATED" ); +#precache( "string", "MP_BANKRUPTED" ); +#precache( "string", "MP_BANKRUPTED_OTHER" ); + +function main() +{ + globallogic::init(); + + level.weapon_SAS_PRIMARY_WEAPON = GetWeapon( "special_crossbow" ); + level.weapon_SAS_SECONDARY_WEAPON = GetWeapon( "knife_ballistic" ); + level.weapon_SAS_PRIMARY_GRENADE_WEAPON = GetWeapon( "hatchet" ); + + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 5000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + level.onStartGameType =&onStartGameType; + level.onPlayerDamage =&onPlayerDamage; + level.onPlayerKilled =&onPlayerKilled; + level.onPlayerScore = &onPlayerScore; +// level.onWagerAwards =&onWagerAwards; + level.pointsPerPrimaryKill = GetGametypeSetting( "pointsPerPrimaryKill" ); + level.pointsPerSecondaryKill = GetGametypeSetting( "pointsPerSecondaryKill" ); + level.pointsPerPrimaryGrenadeKill = GetGametypeSetting( "pointsPerPrimaryGrenadeKill" ); + level.pointsPerMeleeKill = GetGametypeSetting( "pointsPerMeleeKill" ); + level.setBacks = GetGametypeSetting( "setbacks" ); + + switch ( GetGametypeSetting( "gunSelection" ) ) + { + case 0: + level.setBackWeapon = undefined; + break; + + case 1: + level.setBackWeapon = level.weapon_SAS_PRIMARY_GRENADE_WEAPON; + break; + + case 2: + level.setBackWeapon = level.weapon_SAS_PRIMARY_WEAPON; + break; + + case 3: + level.setBackWeapon = level.weapon_SAS_SECONDARY_WEAPON; + break; + + default: + assert( true, "Invalid setting for gunSelection" ); + break; + } + + game["dialog"]["gametype"] = "sns_start"; + game["dialog"]["wm_humiliation"] = "mpl_wager_bankrupt"; + game["dialog"]["wm_humiliated"] = "sns_hum"; + + gameobjects::register_allowed_gameobject( level.gameType ); + + level.giveCustomLoadout =&giveCustomLoadout; + + sas_perks = []; + if ( !isdefined( sas_perks ) ) sas_perks = []; else if ( !IsArray( sas_perks ) ) sas_perks = array( sas_perks ); sas_perks[sas_perks.size]="specialty_fastweaponswitch";; + if ( !isdefined( sas_perks ) ) sas_perks = []; else if ( !IsArray( sas_perks ) ) sas_perks = array( sas_perks ); sas_perks[sas_perks.size]="specialty_jetcharger";; + if ( !isdefined( sas_perks ) ) sas_perks = []; else if ( !IsArray( sas_perks ) ) sas_perks = array( sas_perks ); sas_perks[sas_perks.size]="specialty_tracker";; + level.sas_perks = sas_perks; + + sas_gadgets = []; + if ( !isdefined( sas_gadgets ) ) sas_gadgets = []; else if ( !IsArray( sas_gadgets ) ) sas_gadgets = array( sas_gadgets ); sas_gadgets[sas_gadgets.size]="gadget_camo";; + if ( !isdefined( sas_gadgets ) ) sas_gadgets = []; else if ( !IsArray( sas_gadgets ) ) sas_gadgets = array( sas_gadgets ); sas_gadgets[sas_gadgets.size]="gadget_clone";; + level.weapon_SAS_DEFAULT_GADGET = array::random( sas_gadgets ); + + globallogic::setvisiblescoreboardcolumns( "pointstowin", "kills", "deaths", "tomahawks", "humiliated" ); +} + +function giveCustomLoadout() +{ + self notify( "sas_spectator_hud" ); + + defaultWeapon = level.weapon_SAS_PRIMARY_WEAPON; + loadout::setClassNum( self.curClass ); + + self wager::setup_blank_random_player( true, true, defaultWeapon ); + + self givePerks(); + + self GiveWeapon( defaultWeapon ); + self SetWeaponAmmoClip( defaultWeapon, 6 ); + self SetWeaponAmmoStock( defaultWeapon, 6 ); + self.primaryLoadoutWeapon = defaultWeapon; + + secondaryWeapon = level.weapon_SAS_SECONDARY_WEAPON; + self GiveWeapon( secondaryWeapon ); + self SetWeaponAmmoStock( secondaryWeapon, 2 ); + self.secondaryLoadoutWeapon = defaultWeapon; + + offhandPrimary = level.weapon_SAS_PRIMARY_GRENADE_WEAPON; + self SetOffhandPrimaryClass( offhandPrimary ); + self GiveWeapon( offhandPrimary ); + self SetWeaponAmmoClip( offhandPrimary, 1 ); + self SetWeaponAmmoStock( offhandPrimary, 1 ); + self.grenadeTypePrimary = offhandPrimary; + self.grenadeTypePrimaryCount = 1; + + secondaryOffhand = GetWeapon( "null_offhand_secondary" ); + secondaryOffhandCount = 0; + self GiveWeapon( secondaryOffhand ); + self SetWeaponAmmoClip( secondaryOffhand, secondaryOffhandCount ); + self SwitchToOffhand( secondaryOffhand ); + self.grenadeTypeSecondary = secondaryOffhand; + self.grenadeTypeSecondaryCount = secondaryOffhandCount; + + self GiveWeapon( level.weaponBaseMelee ); + + self giveDefaultGadget(); + + self SwitchToWeapon( defaultWeapon ); + self SetSpawnWeapon( defaultWeapon ); + + self.killsWithSecondary = 0; + self.killsWithPrimary = 0; + self.killsWithBothAwarded = false; + + return defaultWeapon; +} + +function giveDefaultGadget() +{ + specialOffhand = self.sas_matchGadget; + resetCharge = false; + + if ( !isdefined( specialOffhand ) ) + { + specialOffhand = GetWeapon( level.weapon_SAS_DEFAULT_GADGET ); + resetCharge = true; + } + + specialOffhandCount = specialOffhand.startammo; + self GiveWeapon( specialOffhand ); + + self SetWeaponAmmoClip( specialOffhand, specialOffhandCount ); + self SwitchToOffhand( specialOffhand ); + self.grenadeTypeSpecial = specialOffhand; + self.grenadeTypeSpecialCount = specialOffhandCount; + + if ( ( isdefined( resetCharge ) && resetCharge ) ) + { + slot = self GadgetGetSlot( specialOffhand ); + self GadgetPowerSet( slot, 0 ); + } + + self.sas_matchGadget = specialOffhand; +} + +function givePerks() +{ + foreach ( perkName in level.sas_perks ) + { + self SetPerk( perkName ); + } +} + +function onPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime ) +{ + if ( ( weapon == level.weapon_SAS_PRIMARY_WEAPON ) && ( sMeansOfDeath == "MOD_IMPACT" ) ) + { + if ( isdefined( eAttacker ) && IsPlayer( eAttacker ) ) + { + if ( !isdefined( eAttacker.pers["sticks"] ) ) + eAttacker.pers["sticks"] = 1; + else + eAttacker.pers["sticks"]++; + eAttacker.sticks = eAttacker.pers["sticks"]; + } + } + + return iDamage; +} + +function onPlayerScore( event, player, victim ) +{ + score = player.pers["pointstowin"]; + + if ( !level.rankedMatch ) + { + player thread rank::updateRankScoreHUD( score - player.pers["score"] ); + } + + player.pers["score"] = score; + player.score = player.pers["score"]; + recordPlayerStats( player, "score" , player.pers["score"] ); +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( isdefined( attacker ) && IsPlayer( attacker ) && attacker != self ) + { + if ( weapon_utils::isMeleeMOD( sMeansOfDeath ) ) + { + //should probably make this a custom setting + attacker globallogic_score::givePointsToWin( level.pointsPerMeleeKill ); + onPlayerScore( undefined, attacker, undefined ); + } + else if ( weapon == level.weapon_SAS_PRIMARY_WEAPON ) + { + attacker.killsWithPrimary++; + if ( attacker.killsWithBothAwarded == false && attacker.killsWithSecondary > 0 ) + { + attacker.killsWithBothAwarded = true; + } + attacker globallogic_score::givePointsToWin( level.pointsPerPrimaryKill ); + onPlayerScore( undefined, attacker, undefined ); + } + else if ( weapon == level.weapon_SAS_PRIMARY_GRENADE_WEAPON ) + { + attacker globallogic_score::givePointsToWin( level.pointsPerPrimaryGrenadeKill ); + onPlayerScore( undefined, attacker, undefined ); + } + else + { + if ( weapon == level.weapon_SAS_SECONDARY_WEAPON ) // make sure this is not a kill by destructible + { + attacker.killsWithSecondary++; + if ( attacker.killsWithBothAwarded == false && attacker.killsWithPrimary > 0 ) + { + attacker.killsWithBothAwarded = true; + } + } + + attacker globallogic_score::givePointsToWin( level.pointsPerSecondaryKill ); + onPlayerScore( undefined, attacker, undefined ); + } + + if ( isdefined( level.setBackWeapon ) && ( weapon == level.setBackWeapon ) ) + { + self.pers["humiliated"]++; + self.humiliated = self.pers["humiliated"]; + + if ( level.setBacks == 0 ) + { + self globallogic_score::setPointsToWin( 0 ); + onPlayerScore( undefined, self, undefined ); + } + else + { + self globallogic_score::givePointsToWin( level.setBacks * -1 ); + onPlayerScore( undefined, self, undefined ); + } + attacker PlayLocalSound( "mpl_fracture_sting_moved" ); + attacker AddPlayerStatWithGameType( "HUMILIATE_ATTACKER", 1 ); + self thread playerHumiliation(); + } + } + else + { + self.pers["humiliated"]++; + self.humiliated = self.pers["humiliated"]; + if ( level.setBacks == 0 ) + { + self globallogic_score::setPointsToWin( 0 ); + onPlayerScore( undefined, self, undefined ); + } + else + { + self globallogic_score::givePointsToWin( level.setBacks * -1 ); + onPlayerScore( undefined, self, undefined ); + } + self thread playerHumiliation(); + } +} + +function playerHumiliation() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self AddPlayerStatWithGameType( "HUMILIATE_VICTIM", 1 ); + + self waittill( "spawned_player" ); + + self PlayLocalSound( "mpl_assassination_sting" ); + + humHud = self hud::createFontString( "default", 2.5 ); + humHud hud::setPoint( "CENTER", undefined, 0, -100 ); + humHud.label = &"MP_HUMILIATED"; + humHud.x = 0; + humHud.archived = true; + humHud.alpha = 1; + humHud.glowAlpha = 0; + humHud.hidewheninmenu = false; + + self thread playerHumiliationCleanup( humHud ); + + wait 0.1; + + humHud FadeOverTime( 0.2 ); + humHud.color = ( 1, 0, 0 ); + humHud ChangeFontScaleOverTime( 0.2 ); + humHud.fontScale = 3; + wait 0.5; + + humHud FadeOverTime( 0.5 ); + humHud.color = ( 1, 1, 1 ); + humHud ChangeFontScaleOverTime( 0.5 ); + humHud.fontScale = 2.5; + wait 1.0; + + self notify( "humHudDestroyed" ); + humHud Destroy(); +} + +function playerHumiliationCleanup( humHud ) +{ + self endon( "humHudDestroyed" ); + + self util::waittill_any( "disconnect", "death" ); + + if ( isdefined( humHud ) ) + humHud Destroy(); +} + +function setupTeam( team ) +{ + util::setObjectiveText( team, &"OBJECTIVES_SAS" ); + if ( level.splitscreen ) + { + util::setObjectiveScoreText( team, &"OBJECTIVES_SAS" ); + } + else + { + util::setObjectiveScoreText( team, &"OBJECTIVES_SAS_SCORE" ); + } + util::setObjectiveHintText( team, &"OBJECTIVES_SAS_HINT" ); + + spawnlogic::add_spawn_points( team, "mp_dm_spawn" ); + spawnlogic::place_spawn_points( "mp_dm_spawn_start" ); + + level.spawn_start = spawnlogic::get_spawnpoint_array( "mp_dm_spawn_start" ); +} + +function onStartGameType() +{ +// SetDvar( "scr_xpscalemp", 0 ); + SetDvar( "tu29_gametypeOverridesGadget", true ); + + setClientNameMode("auto_change"); + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + foreach( team in level.teams ) + { + setupTeam( team ); + } + + spawning::updateAllSpawnPoints(); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + // use the new spawn logic from the start + level.useStartSpawns = false; + + level.displayRoundEndText = false; + + if ( isdefined( game["roundsplayed"] ) && game["roundsplayed"] > 0 ) + { + game["dialog"]["gametype"] = undefined; + game["dialog"]["offense_obj"] = undefined; + game["dialog"]["defense_obj"] = undefined; + } +} + +function onWagerAwards() +{ + tomahawks = self globallogic_score::getPersStat( "tomahawks" ); + if ( !isdefined( tomahawks ) ) + tomahawks = 0; + self persistence::set_after_action_report_stat( "wagerAwards", tomahawks, 0 ); + + sticks = self globallogic_score::getPersStat( "sticks" ); + if ( !isdefined( sticks ) ) + sticks = 0; + self persistence::set_after_action_report_stat( "wagerAwards", sticks, 1 ); + + bestKillstreak = self globallogic_score::getPersStat( "best_kill_streak" ); + if ( !isdefined( bestKillstreak ) ) + bestKillstreak = 0; + self persistence::set_after_action_report_stat( "wagerAwards", bestKillstreak, 2 ); +} diff --git a/mp/gametypes/sd.csc b/mp/gametypes/sd.csc new file mode 100644 index 0000000..c00c305 --- /dev/null +++ b/mp/gametypes/sd.csc @@ -0,0 +1,43 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; + +#using scripts\mp\gametypes\_globallogic; + + + +function main() +{ + callback::on_spawned( &on_player_spawned ); + //callback::on_start_gametype( &onStartGameType ); + if( GetGametypeSetting( "silentPlant" ) != 0 ) + setsoundcontext( "bomb_plant", "silent" ); +} + +function onStartGameType() +{ +} + +function on_player_spawned( localClientNum ) +{ + self thread player_sound_context_hack(); + self thread globallogic::watch_plant_sound( localClientNum ); +} + +function player_sound_context_hack() +{ + if( GetGametypeSetting( "silentPlant" ) != 0 ) + { + self endon("entityshutdown"); + + self notify("player_sound_context_hack"); + self endon("player_sound_context_hack"); + + while(1) + { + self setsoundentcontext( "bomb_plant", "silent" ); + wait(1); + } + } + +} \ No newline at end of file diff --git a/mp/gametypes/sd.gsc b/mp/gametypes/sd.gsc new file mode 100644 index 0000000..988d828 --- /dev/null +++ b/mp/gametypes/sd.gsc @@ -0,0 +1,1191 @@ +#using scripts\shared\demo_shared; +#using scripts\shared\exploder_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\medals_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\util_shared; +#using scripts\mp\gametypes\_dogtags; +#using scripts\mp\gametypes\_globallogic_spawn; + +#using scripts\shared\abilities\gadgets\_gadget_resurrect; + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_defaults; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\_spectating; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; + +// Rallypoints should be destroyed on leaving your team/getting killed +// Compass icons need to be looked at +// Doesn't seem to be setting angle on spawn so that you are facing your rallypoint + +/* + Search and Destroy + Attackers objective: Bomb one of 2 positions + Defenders objective: Defend these 2 positions / Defuse planted bombs + Round ends: When one team is eliminated, bomb explodes, bomb is defused, or roundlength time is reached + Map ends: When one team reaches the score limit, or time limit or round limit is reached + Respawning: Players remain dead for the round and will respawn at the beginning of the next round + + Level requirements + ------------------ + Allied Spawnpoints: + classname mp_sd_spawn_attacker + Allied players spawn from these. Place at least 16 of these relatively close together. + + Axis Spawnpoints: + classname mp_sd_spawn_defender + Axis players spawn from these. Place at least 16 of these relatively close together. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Bombzones: + classname trigger_multiple + targetname bombzone + script_gameobjectname bombzone + script_bombmode_original + script_bombmode_single + script_bombmode_dual + script_team Set to allies or axis. This is used to set which team a bombzone is used by in dual bomb mode. + script_label Set to A or B. This sets the letter shown on the compass in original mode. + This is a volume of space in which the bomb can planted. Must contain an origin brush. + + Bomb: + classname trigger_lookat + targetname bombtrigger + script_gameobjectname bombzone + This should be a 16x16 unit trigger with an origin brush placed so that it's center lies on the bottom plane of the trigger. + Must be in the level somewhere. This is the trigger that is used when defusing a bomb. + It gets moved to the position of the planted bomb model. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + This sets the nationalities of the teams. Allies can be american, british, or russian. Axis can be german. + + game["attackers"] = "allies"; + game["defenders"] = "axis"; + This sets which team is attacking and which team is defending. Attackers plant the bombs. Defenders protect the targets. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals + + Exploder Effects: + Setting script_noteworthy on a bombzone trigger to an exploder group can be used to trigger additional effects. +*/ + +/*QUAKED mp_sd_spawn_attacker (0.0 1.0 0.0) (-16 -16 0) (16 16 72) +Attacking players spawn randomly at one of these positions at the beginning of a round.*/ + +/*QUAKED mp_sd_spawn_defender (1.0 0.0 0.0) (-16 -16 0) (16 16 72) +Defending players spawn randomly at one of these positions at the beginning of a round.*/ + + + + + + + +#precache( "fx", "explosions/fx_exp_bomb_demo_mp" ); +#precache( "material","compass_waypoint_target" ); +#precache( "material","compass_waypoint_target_a" ); +#precache( "material","compass_waypoint_target_b" ); +#precache( "material","compass_waypoint_defend" ); +#precache( "material","compass_waypoint_defend_a" ); +#precache( "material","compass_waypoint_defend_b" ); +#precache( "material","compass_waypoint_defuse" ); +#precache( "material","compass_waypoint_defuse_a" ); +#precache( "material","compass_waypoint_defuse_b" ); +#precache( "model", "p7_mp_suitcase_bomb" ); +#precache( "objective", "sd_bomb" ); +#precache( "objective", "sd_a" ); +#precache( "objective", "sd_defuse_a" ); +#precache( "objective", "sd_b" ); +#precache( "objective", "sd_defuse_b" ); +#precache( "string", "OBJECTIVES_SD_ATTACKER" ); +#precache( "string", "OBJECTIVES_SD_DEFENDER" ); +#precache( "string", "OBJECTIVES_SD_ATTACKER_SCORE" ); +#precache( "string", "OBJECTIVES_SD_DEFENDER_SCORE" ); +#precache( "string", "OBJECTIVES_SD_ATTACKER_HINT" ); +#precache( "string", "OBJECTIVES_SD_DEFENDER_HINT" ); +#precache( "string", "MP_EXPLOSIVES_BLOWUP_BY" ); +#precache( "string", "MP_EXPLOSIVES_RECOVERED_BY" ); +#precache( "string", "MP_EXPLOSIVES_DROPPED_BY" ); +#precache( "string", "MP_EXPLOSIVES_PLANTED_BY" ); +#precache( "string", "MP_EXPLOSIVES_DEFUSED_BY" ); +#precache( "string", "PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); +#precache( "string", "PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); +#precache( "string", "MP_CANT_PLANT_WITHOUT_BOMB" ); +#precache( "string", "MP_PLANTING_EXPLOSIVE" ); +#precache( "string", "MP_DEFUSING_EXPLOSIVE" ); +#precache( "string", "MP_TARGET_DESTROYED" ); +#precache( "string", "MP_BOMB_DEFUSED" ); +#precache( "string", "bomb" ); +#precache( "triggerstring", "PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); +#precache( "triggerstring", "PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); + +function main() +{ + globallogic::init(); + + util::registerRoundSwitch( 0, 9 ); + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 500 ); + util::registerRoundLimit( 0, 12 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.teamBased = true; + level.overrideTeamScore = true; + level.onPrecacheGameType =&onPrecacheGameType; + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.playerSpawnedCB =&sd_playerSpawnedCB; + level.onPlayerKilled =&onPlayerKilled; + level.onDeadEvent =&onDeadEvent; + level.onOneLeftEvent =&onOneLeftEvent; + level.onTimeLimit =&onTimeLimit; + level.onRoundSwitch =&onRoundSwitch; + level.getTeamKillPenalty =&sd_getTeamKillPenalty; + level.getTeamKillScore =&sd_getTeamKillScore; + level.isKillBoosting =&sd_isKillBoosting; + level.figure_out_gametype_friendly_fire = &figureOutGameTypeFriendlyFire; + + level.endGameOnScoreLimit = false; + + gameobjects::register_allowed_gameobject( level.gameType ); + gameobjects::register_allowed_gameobject( "bombzone" ); + gameobjects::register_allowed_gameobject( "blocker" ); + + globallogic_audio::set_leader_gametype_dialog ( "startSearchAndDestroy", "hcStartSearchAndDestroy", "objDestroy", "objDefend" ); + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "plants", "defuses", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "plants", "defuses" ); +} + +function onPrecacheGameType() +{ + //game["bombmodelname"] = "weapon_explosives"; + //game["bombmodelnameobj"] = "weapon_explosives"; + game["bomb_dropped_sound"] = "fly_bomb_drop_plr"; + game["bomb_recovered_sound"] = "fly_bomb_pickup_plr"; +} + +function sd_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_penalty = globallogic_defaults::default_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ); + + if ( ( isdefined( self.isDefusing ) && self.isDefusing ) || ( isdefined( self.isPlanting ) && self.isPlanting ) ) + { + teamkill_penalty = teamkill_penalty * level.teamKillPenaltyMultiplier; + } + + return teamkill_penalty; +} + +function sd_getTeamKillScore( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_score = rank::getScoreInfoValue( "team_kill" ); + + if ( ( isdefined( self.isDefusing ) && self.isDefusing ) || ( isdefined( self.isPlanting ) && self.isPlanting ) ) + { + teamkill_score = teamkill_score * level.teamKillScoreMultiplier; + } + + return int(teamkill_score); +} + + +function onRoundSwitch() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["teamScores"]["allies"] == level.scorelimit - 1 && game["teamScores"]["axis"] == level.scorelimit - 1 ) + { + // overtime! team that's ahead in kills gets to defend. + aheadTeam = getBetterTeam(); + if ( aheadTeam != game["defenders"] ) + { + game["switchedsides"] = !game["switchedsides"]; + } + level.halftimeType = "overtime"; + } + else + { + level.halftimeType = "halftime"; + game["switchedsides"] = !game["switchedsides"]; + } +} + +function getBetterTeam() +{ + kills["allies"] = 0; + kills["axis"] = 0; + deaths["allies"] = 0; + deaths["axis"] = 0; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + team = player.pers["team"]; + if ( isdefined( team ) && (team == "allies" || team == "axis") ) + { + kills[ team ] += player.kills; + deaths[ team ] += player.deaths; + } + } + + if ( kills["allies"] > kills["axis"] ) + return "allies"; + else if ( kills["axis"] > kills["allies"] ) + return "axis"; + + // same number of kills + + if ( deaths["allies"] < deaths["axis"] ) + return "allies"; + else if ( deaths["axis"] < deaths["allies"] ) + return "axis"; + + // same number of deaths + + if ( randomint(2) == 0 ) + return "allies"; + return "axis"; +} + +function onStartGameType() +{ + SetBombTimer( "A", 0 ); + SetMatchFlag( "bomb_timer_a", 0 ); + SetBombTimer( "B", 0 ); + SetMatchFlag( "bomb_timer_b", 0 ); + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + setClientNameMode( "manual_change" ); + + game["strings"]["target_destroyed"] = &"MP_TARGET_DESTROYED"; + game["strings"]["bomb_defused"] = &"MP_BOMB_DEFUSED"; + + level._effect["bombexplosion"] = "explosions/fx_exp_bomb_demo_mp"; + + util::setObjectiveText( game["attackers"], &"OBJECTIVES_SD_ATTACKER" ); + util::setObjectiveText( game["defenders"], &"OBJECTIVES_SD_DEFENDER" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_SD_ATTACKER" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_SD_DEFENDER" ); + } + else + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_SD_ATTACKER_SCORE" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_SD_DEFENDER_SCORE" ); + } + util::setObjectiveHintText( game["attackers"], &"OBJECTIVES_SD_ATTACKER_HINT" ); + util::setObjectiveHintText( game["defenders"], &"OBJECTIVES_SD_DEFENDER_HINT" ); + + level.alwaysUseStartSpawns = true; + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::place_spawn_points( "mp_sd_spawn_attacker" ); + spawnlogic::place_spawn_points( "mp_sd_spawn_defender" ); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + + level.spawn_start = []; + + level.spawn_start["axis"] = spawnlogic::get_spawnpoint_array( "mp_sd_spawn_defender" ); + level.spawn_start["allies"] = spawnlogic::get_spawnpoint_array( "mp_sd_spawn_attacker" ); + + thread updateGametypeDvars(); + + thread bombs(); +} + + +function onSpawnPlayer(predictedSpawn) +{ + self.isPlanting = false; + self.isDefusing = false; + self.isBombCarrier = false; + + spawning::onSpawnPlayer(predictedSpawn); +} + +function sd_playerSpawnedCB() +{ + level notify ( "spawned_player" ); +} + +function onPlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration) +{ + thread checkAllowSpectating(); + + if( ( isdefined( level.droppedTagRespawn ) && level.droppedTagRespawn ) ) + { + should_spawn_tags = self dogtags::should_spawn_tags(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + + // we should spawn tags if one the previous statements were true and we may not spawn + should_spawn_tags = should_spawn_tags && !globallogic_spawn::maySpawn(); + + if( should_spawn_tags ) + level thread dogtags::spawn_dog_tag( self, attacker, &dogtags::onUseDogTag, false ); + } + + if ( isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"]) + { + scoreevents::processScoreEvent( "kill_sd", attacker, self, weapon ); + } + + inBombZone = false; + + for ( index = 0; index < level.bombZones.size; index++ ) + { + dist = Distance2dSquared(self.origin, level.bombZones[index].curorigin); + if ( dist < level.defaultOffenseRadiusSQ ) + { + inBombZone = true; + currentObjective = level.bombZones[index]; + break; + } + } + + if ( inBombZone && isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] ) + { + if ( game["defenders"] == self.pers["team"] ) + { + attacker medals::offenseGlobalCount(); + attacker thread challenges::killedBaseDefender( currentObjective ); + self RecordKillModifier("defending"); + scoreevents::processScoreEvent( "killed_defender", attacker, self, weapon ); + } + else + { + if( isdefined(attacker.pers["defends"]) ) + { + attacker.pers["defends"]++; + attacker.defends = attacker.pers["defends"]; + } + + attacker medals::defenseGlobalCount(); + attacker thread challenges::killedBaseOffender( currentObjective, weapon ); + self RecordKillModifier("assaulting"); + scoreevents::processScoreEvent( "killed_attacker", attacker, self, weapon ); + } + } + + if ( isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] && isdefined( self.isBombCarrier ) && self.isBombCarrier == true ) + { + self RecordKillModifier("carrying"); + + attacker RecordGameEvent("kill_carrier"); + } + + if( self.isPlanting == true ) + self RecordKillModifier("planting"); + + if( self.isDefusing == true ) + self RecordKillModifier("defusing"); +} + + +function checkAllowSpectating() +{ + self endon("disconnect"); + + {wait(.05);}; + + update = false; + + livesLeft = !(level.numLives && !self.pers["lives"]); + + if ( !level.aliveCount[ game["attackers"] ] && !livesLeft ) + { + level.spectateOverride[game["attackers"]].allowEnemySpectate = 1; + update = true; + } + if ( !level.aliveCount[ game["defenders"] ] && !livesLeft ) + { + level.spectateOverride[game["defenders"]].allowEnemySpectate = 1; + update = true; + } + if ( update ) + spectating::update_settings(); +} + + +function sd_endGame( winningTeam, endReasonText ) +{ + if ( isdefined( winningTeam ) ) + globallogic_score::giveTeamScoreForObjective_DelayPostProcessing( winningTeam, 1 ); + + thread globallogic::endGame( winningTeam, endReasonText ); +} + +function sd_endGameWithKillcam( winningTeam, endReasonText ) +{ + sd_endGame( winningTeam, endReasonText ); +} + + +function onDeadEvent( team ) +{ + if ( level.bombExploded || level.bombDefused ) + return; + + if ( team == "all" ) + { + if ( level.bombPlanted ) + sd_endGameWithKillcam( game["attackers"], game["strings"][game["defenders"]+"_eliminated"] ); + else + sd_endGameWithKillcam( game["defenders"], game["strings"][game["attackers"]+"_eliminated"] ); + } + else if ( team == game["attackers"] ) + { + if ( level.bombPlanted ) + return; + + sd_endGameWithKillcam( game["defenders"], game["strings"][game["attackers"]+"_eliminated"] ); + } + else if ( team == game["defenders"] ) + { + sd_endGameWithKillcam( game["attackers"], game["strings"][game["defenders"]+"_eliminated"] ); + } +} + + +function onOneLeftEvent( team ) +{ + if ( level.bombExploded || level.bombDefused ) + return; + + //if ( team == game["attackers"] ) + warnLastPlayer( team ); +} + + +function onTimeLimit() +{ + if ( level.teamBased ) + sd_endGame( game["defenders"], game["strings"]["time_limit_reached"] ); + else + sd_endGame( undefined, game["strings"]["time_limit_reached"] ); +} + + +function warnLastPlayer( team ) +{ + if ( !isdefined( level.warnedLastPlayer ) ) + level.warnedLastPlayer = []; + + if ( isdefined( level.warnedLastPlayer[team] ) ) + return; + + level.warnedLastPlayer[team] = true; + + players = level.players; + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if ( isdefined( player.pers["team"] ) && player.pers["team"] == team && isdefined( player.pers["class"] ) ) + { + if ( player.sessionstate == "playing" && !player.afk ) + break; + } + } + + if ( i == players.size ) + return; + + players[i] thread giveLastAttackerWarning( team ); + + +} + + +function giveLastAttackerWarning( team ) +{ + self endon("death"); + self endon("disconnect"); + + fullHealthTime = 0; + interval = .05; + + self.lastManSD = true; + + enemyTeam = game["defenders"]; + + if ( team == enemyTeam ) + { + enemyTeam = game["attackers"]; + } + + + if ( level.aliveCount[enemyTeam] > 2 ) + { + self.lastManSDDefeat3Enemies = true; + } + + + while(1) + { + if ( self.health != self.maxhealth ) + fullHealthTime = 0; + else + fullHealthTime += interval; + + wait interval; + + if (self.health == self.maxhealth && fullHealthTime >= 3) + break; + } + + self globallogic_audio::leader_dialog_on_player( "roundEncourageLastPlayer" ); + self playlocalsound ("mus_last_stand"); +} + + +function updateGametypeDvars() +{ + level.plantTime = GetGametypeSetting( "plantTime" ); + level.defuseTime = GetGametypeSetting( "defuseTime" ); + level.bombTimer = GetGametypeSetting( "bombTimer" ); + level.multiBomb = GetGametypeSetting( "multiBomb" ); + + level.teamKillPenaltyMultiplier = GetGametypeSetting( "teamKillPenalty" ); + level.teamKillScoreMultiplier = GetGametypeSetting( "teamKillScore" ); + + level.playerKillsMax = GetGametypeSetting( "playerKillsMax" ); + level.totalKillsMax = GetGametypeSetting( "totalKillsMax" ); +} + +function bombs() +{ + level.bombPlanted = false; + level.bombDefused = false; + level.bombExploded = false; + + trigger = getEnt( "sd_bomb_pickup_trig", "targetname" ); + if ( !isdefined( trigger ) ) + { + /#util::error("No sd_bomb_pickup_trig trigger found in map.");#/ + return; + } + + visuals[0] = getEnt( "sd_bomb", "targetname" ); + if ( !isdefined( visuals[0] ) ) + { + /#util::error("No sd_bomb script_model found in map.");#/ + return; + } + + //visuals[0] setModel( "weapon_explosives" ); + + if ( !level.multiBomb ) + { + level.sdBomb = gameobjects::create_carry_object( game["attackers"], trigger, visuals, (0,0,32), &"sd_bomb" ); + level.sdBomb gameobjects::allow_carry( "friendly" ); + level.sdBomb gameobjects::set_2d_icon( "friendly", "compass_waypoint_bomb" ); + level.sdBomb gameobjects::set_3d_icon( "friendly", "waypoint_bomb" ); + level.sdBomb gameobjects::set_visible_team( "friendly" ); + level.sdBomb gameobjects::set_carry_icon( "hud_suitcase_bomb" ); + level.sdBomb.allowWeapons = true; + level.sdBomb.onPickup =&onPickup; + level.sdBomb.onDrop =&onDrop; + + foreach( visual in level.sdBomb.visuals ) + visual.team = "free"; // for preventing red reticles when pointing at the bomb + } + else + { + trigger delete(); + visuals[0] delete(); + } + + + level.bombZones = []; + + bombZones = getEntArray( "bombzone", "targetname" ); + + for ( index = 0; index < bombZones.size; index++ ) + { + trigger = bombZones[index]; + visuals = getEntArray( bombZones[index].target, "targetname" ); + + name = istring("sd"+trigger.script_label); + + bombZone = gameobjects::create_use_object( game["defenders"], trigger, visuals, (0,0,0), name, true, true ); + bombZone gameobjects::allow_use( "enemy" ); + bombZone gameobjects::set_use_time( level.plantTime ); + bombZone gameobjects::set_use_text( &"MP_PLANTING_EXPLOSIVE" ); + bombZone gameobjects::set_use_hint_text( &"PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); + if ( !level.multiBomb ) + bombZone gameobjects::set_key_object( level.sdBomb ); + label = bombZone gameobjects::get_label(); + bombZone.label = label; + bombZone gameobjects::set_2d_icon( "friendly", "compass_waypoint_defend" + label ); + bombZone gameobjects::set_3d_icon( "friendly", "waypoint_defend" + label ); + bombZone gameobjects::set_2d_icon( "enemy", "compass_waypoint_target" + label ); + bombZone gameobjects::set_3d_icon( "enemy", "waypoint_target" + label ); + bombZone gameobjects::set_visible_team( "any" ); + bombZone.onBeginUse =&onBeginUse; + bombZone.onEndUse =&onEndUse; + bombZone.onUse =&onUsePlantObject; + bombZone.onCantUse =&onCantUse; + bombZone.useWeapon = GetWeapon( "briefcase_bomb" ); + bombZone.visuals[0].killCamEnt = spawn( "script_model", bombZone.visuals[0].origin + (0,0,128) ); + + if ( isdefined( level.bomb_zone_fixup ) ) + [[ level.bomb_zone_fixup ]]( bombZone ); + + if ( !level.multiBomb ) + bombZone.trigger SetInvisibleToAll(); + + for ( i = 0; i < visuals.size; i++ ) + { + if ( isdefined( visuals[i].script_exploder ) ) + { + bombZone.exploderIndex = visuals[i].script_exploder; + break; + } + } + + foreach( visual in bombZone.visuals ) + visual.team = "free"; // for preventing red reticles when pointing at bomb zones + + level.bombZones[level.bombZones.size] = bombZone; + + bombZone.bombDefuseTrig = getent( visuals[0].target, "targetname" ); + assert( isdefined( bombZone.bombDefuseTrig ) ); + bombZone.bombDefuseTrig.origin += (0,0,-10000); + bombZone.bombDefuseTrig.label = label; + } + + for ( index = 0; index < level.bombZones.size; index++ ) + { + array = []; + for ( otherindex = 0; otherindex < level.bombZones.size; otherindex++ ) + { + if ( otherindex != index ) + array[ array.size ] = level.bombZones[otherindex]; + } + level.bombZones[index].otherBombZones = array; + } +} + +function setBombOverheatingAfterWeaponChange( useObject, overheated, heat ) // self == player +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "joined_team"); + self endon ( "joined_spectators"); + + self waittill( "weapon_change", weapon ); + + if ( weapon == useObject.useWeapon ) + { + self SetWeaponOverheating( overheated, heat, weapon ); // resetting overheating allows for quick drop anim to be played + } +} + +function onBeginUse( player ) +{ + if ( self gameobjects::is_friendly_team( player.pers["team"] ) ) + { + player playSound( "mpl_sd_bomb_defuse" ); + player.isDefusing = true; + player thread setBombOverheatingAfterWeaponChange( self, false, 0 ); // overheated specific use weapons play "drop" instead of "quick drop" anims + player thread battlechatter::gametype_specific_battle_chatter( "sd_enemyplant", player.pers["team"] ); + + if ( isdefined( level.sdBombModel ) ) + level.sdBombModel hide(); + } + else + { + player.isPlanting = true; + player thread setBombOverheatingAfterWeaponChange( self, false, 0 ); // overheated specific use weapons play "drop" instead of "quick drop" anims + player thread battlechatter::gametype_specific_battle_chatter( "sd_friendlyplant", player.pers["team"] ); + + if ( level.multibomb ) + { + for ( i = 0; i < self.otherBombZones.size; i++ ) + { + self.otherBombZones[i] gameobjects::disable_object(); + } + } + } + player playSound( "fly_bomb_raise_plr" ); +} + +function onEndUse( team, player, result ) +{ + if ( !isdefined( player ) ) + return; + + player.isDefusing = false; + player.isPlanting = false; + player notify( "event_ended" ); + + if ( self gameobjects::is_friendly_team( player.pers["team"] ) ) + { + if ( isdefined( level.sdBombModel ) && !result ) + { + level.sdBombModel show(); + } + } + else + { + if ( level.multibomb && !result ) + { + for ( i = 0; i < self.otherBombZones.size; i++ ) + { + self.otherBombZones[i] gameobjects::enable_object(); + } + } + } +} + +function onCantUse( player ) +{ + player iPrintLnBold( &"MP_CANT_PLANT_WITHOUT_BOMB" ); +} + +function onUsePlantObject( player ) +{ + // planted the bomb + if ( !self gameobjects::is_friendly_team( player.pers["team"] ) ) + { + self gameobjects::set_flags( 1 ); + level thread bombPlanted( self, player ); + /#print( "bomb planted: " + self.label );#/ + + // disable all bomb zones except this one + for ( index = 0; index < level.bombZones.size; index++ ) + { + if ( level.bombZones[index] == self ) + { + level.bombZones[index].isPlanted = true; + continue; + } + + level.bombZones[index] gameobjects::disable_object(); + } + thread sound::play_on_players( "mus_sd_planted"+"_"+level.teamPostfix[player.pers["team"]] ); +// removed plant audio until finalization of assest TODO : new plant sounds when assests are online +// player playSound( "mpl_sd_bomb_plant" ); + player notify ( "bomb_planted" ); + + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_PLANTED_BY", player ); + + if( isdefined(player.pers["plants"]) ) + { + player.pers["plants"]++; + player.plants = player.pers["plants"]; + } + + demo::bookmark( "event", gettime(), player ); + player AddPlayerStatWithGameType( "PLANTS", 1 ); + + globallogic_audio::leader_dialog( "bombPlanted" ); + + scoreevents::processScoreEvent( "planted_bomb", player ); + player RecordGameEvent("plant"); + } +} + +function onUseDefuseObject( player ) +{ + self gameobjects::set_flags( 0 ); + player notify ( "bomb_defused" ); + /#print( "bomb defused: " + self.label );#/ + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "sd_bombdefuse", self.label, player.pers["team"], player.origin ); + level thread bombDefused( self, player ); + + // disable this bomb zone + self gameobjects::disable_object(); + + for ( index = 0; index < level.bombZones.size; index++ ) + { + level.bombZones[index].isPlanted = false; + } + + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_DEFUSED_BY", player ); + + if( isdefined(player.pers["defuses"]) ) + { + player.pers["defuses"]++; + player.defuses = player.pers["defuses"]; + } + + player AddPlayerStatWithGameType( "DEFUSES", 1 ); + demo::bookmark( "event", gettime(), player ); + + globallogic_audio::leader_dialog( "bombDefused" ); + + if ( player.lastManSD === true && level.aliveCount[ game["attackers"] ] > 0 ) + { + scoreevents::processScoreEvent( "defused_bomb_last_man_alive", player ); + player addplayerstat( "defused_bomb_last_man_alive", 1 ); + } + else + { + scoreevents::processScoreEvent( "defused_bomb", player ); + } + player RecordGameEvent("defuse"); +} + + +function onDrop( player ) +{ + if ( !level.bombPlanted ) + { + globallogic_audio::leader_dialog( "bombFriendlyDropped", game["attackers"] ); + /# + if ( isdefined( player ) ) + print( "bomb dropped" ); + else + print( "bomb dropped" ); + #/ + } + + player notify( "event_ended" ); + + self gameobjects::set_3d_icon( "friendly", "waypoint_bomb" ); + + sound::play_on_players( game["bomb_dropped_sound"], game["attackers"] ); + + if ( isdefined(level.bombDropBotEvent) ) + { + [[level.bombDropBotEvent]](); + } +} + + +function onPickup( player ) +{ + player.isBombCarrier = true; + + player RecordGameEvent("pickup"); + + self gameobjects::set_3d_icon( "friendly", "waypoint_defend" ); + + if ( !level.bombDefused ) + { + if ( isdefined( player ) && isdefined( player.name ) ) + { + player AddPlayerStatWithGameType( "PICKUPS", 1 ); + } + + //thread sound::play_on_players( "mus_sd_pickup"+"_"+level.teamPostfix[player.pers["team"]], player.pers["team"] ); + // New Music System + team = self gameobjects::get_owner_team(); + otherTeam = util::getOtherTeam( team ); + + globallogic_audio::leader_dialog( "bombFriendlyTaken", game["attackers"] ); + /#print( "bomb taken" );#/ + } + //sound::play_on_players( game["bomb_recovered_sound"], game["attackers"] ); + player playsound ( "fly_bomb_pickup_plr" ); + + for ( i = 0; i < level.bombZones.size; i++ ) + { + level.bombZones[i].trigger SetInvisibleToAll(); + level.bombZones[i].trigger SetVisibleToPlayer( player ); + } + + if ( isdefined(level.bombPickupBotEvent) ) + { + [[level.bombPickupBotEvent]](); + } +} + + +function onReset() +{ + +} + +function bombPlantedMusicDelay() +{ + level endon ("bomb_defused"); + //wait for 30 seconds until explosion + + time = (level.bombtimer - 30); +/# + if( GetDvarint( "debug_music" ) > 0 ) + { + println ("Music System - waiting to set TIME_OUT: " + time ); + } +#/ + if (time > 1) + { + wait (time); + thread globallogic_audio::set_music_on_team( "timeOutQuiet" ); + } +} + +function bombPlanted( destroyedObj, player ) +{ + globallogic_utils::pauseTimer(); + level.bombPlanted = true; + player SetWeaponOverheating( true, 100, destroyedObj.useWeapon ); // overheating allows for non-quick drop anim to be played + team = player.pers["team"]; + + destroyedObj.visuals[0] thread globallogic_utils::playTickingSound( "mpl_sab_ui_suitcasebomb_timer" ); + //Play suspense music + level thread bombPlantedMusicDelay(); + + level.tickingObject = destroyedObj.visuals[0]; + + level.timeLimitOverride = true; + setGameEndTime( int( gettime() + (level.bombTimer * 1000) ) ); + + label = destroyedObj gameobjects::get_label(); + SetMatchFlag( "bomb_timer"+label, 1 ); + if ( label == "_a" ) + { + SetBombTimer( "A", int( gettime() + level.bombTimer * 1000 ) ); + SetMatchFlag( "bomb_timer_a", 1 ); + } + else + { + SetBombTimer( "B", int( gettime() + level.bombTimer * 1000 ) ); + SetMatchFlag( "bomb_timer_b", 1 ); + } + + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "sd_bombplant", label, team, player.origin ); + + if ( !level.multiBomb ) + { + level.sdBomb gameobjects::allow_carry( "none" ); + level.sdBomb gameobjects::set_visible_team( "none" ); + level.sdBomb gameobjects::set_dropped(); + level.sdBombModel = level.sdBomb.visuals[0]; + } + else + { + + for ( index = 0; index < level.players.size; index++ ) + { + if ( isdefined( level.players[index].carryIcon ) ) + level.players[index].carryIcon hud::destroyElem(); + } + + trace = bulletTrace( player.origin + (0,0,20), player.origin - (0,0,2000), false, player ); + + tempAngle = randomfloat( 360 ); + forward = (cos( tempAngle ), sin( tempAngle ), 0); + forward = vectornormalize( forward - VectorScale( trace["normal"], vectordot( forward, trace["normal"] ) ) ); + dropAngles = vectortoangles( forward ); + + level.sdBombModel = spawn( "script_model", trace["position"] ); + level.sdBombModel.angles = dropAngles; + level.sdBombModel setModel( "p7_mp_suitcase_bomb" ); + } + destroyedObj gameobjects::allow_use( "none" ); + destroyedObj gameobjects::set_visible_team( "none" ); + /* + destroyedObj gameobjects::set_2d_icon( "friendly", undefined ); + destroyedObj gameobjects::set_2d_icon( "enemy", undefined ); + destroyedObj gameobjects::set_3d_icon( "friendly", undefined ); + destroyedObj gameobjects::set_3d_icon( "enemy", undefined ); + */ + label = destroyedObj gameobjects::get_label(); + + // create a new object to defuse with. + trigger = destroyedObj.bombDefuseTrig; + trigger.origin = level.sdBombModel.origin; + visuals = []; + defuseObject = gameobjects::create_use_object( game["defenders"], trigger, visuals, (0,0,32), istring("sd_defuse"+label), true, true ); + defuseObject gameobjects::allow_use( "friendly" ); + defuseObject gameobjects::set_use_time( level.defuseTime ); + defuseObject gameobjects::set_use_text( &"MP_DEFUSING_EXPLOSIVE" ); + defuseObject gameobjects::set_use_hint_text( &"PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); + defuseObject gameobjects::set_visible_team( "any" ); + defuseObject gameobjects::set_2d_icon( "friendly", "compass_waypoint_defuse" + label ); + defuseObject gameobjects::set_2d_icon( "enemy", "compass_waypoint_defend" + label ); + defuseObject gameobjects::set_3d_icon( "friendly", "waypoint_defuse" + label ); + defuseObject gameobjects::set_3d_icon( "enemy", "waypoint_defend" + label ); + defuseObject gameobjects::set_flags( 1 ); + defuseObject.label = label; + defuseObject.onBeginUse =&onBeginUse; + defuseObject.onEndUse =&onEndUse; + defuseObject.onUse =&onUseDefuseObject; + defuseObject.useWeapon = GetWeapon( "briefcase_bomb_defuse" ); + + player.isBombCarrier = false; + player PlayBombPlant(); + + BombTimerWait(); + SetBombTimer( "A", 0 ); + SetBombTimer( "B", 0 ); + SetMatchFlag( "bomb_timer_a", 0 ); + SetMatchFlag( "bomb_timer_b", 0 ); + + destroyedObj.visuals[0] globallogic_utils::stopTickingSound(); + + if ( level.gameEnded || level.bombDefused ) + return; + + level.bombExploded = true; + + origin = (0,0,0); + if ( isdefined( player ) ) + { + origin = player.origin; + } + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "sd_bombexplode", label, team, origin ); + + explosionOrigin = level.sdBombModel.origin+(0,0,12); + level.sdBombModel hide(); + + if ( isdefined( player ) ) + { + destroyedObj.visuals[0] radiusDamage( explosionOrigin, 512, 200, 20, player, "MOD_EXPLOSIVE", GetWeapon( "briefcase_bomb" ) ); + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_BLOWUP_BY", player ); + scoreevents::processScoreEvent( "bomb_detonated", player ); + player AddPlayerStatWithGameType( "DESTRUCTIONS", 1 ); + player AddPlayerStatWithGameType( "captures", 1 ); // counts towards Destroyer challenge + player RecordGameEvent("destroy"); + } + else + destroyedObj.visuals[0] radiusDamage( explosionOrigin, 512, 200, 20, undefined, "MOD_EXPLOSIVE", GetWeapon( "briefcase_bomb" ) ); + + rot = randomfloat(360); + explosionEffect = spawnFx( level._effect["bombexplosion"], explosionOrigin + (0,0,50), (0,0,1), (cos(rot),sin(rot),0) ); + triggerFx( explosionEffect ); + + thread sound::play_in_space( "mpl_sd_exp_suitcase_bomb_main", explosionOrigin ); + + if ( isdefined( destroyedObj.exploderIndex ) ) + exploder::exploder( destroyedObj.exploderIndex ); + + defuseObject gameobjects::destroy_object(); + foreach ( zone in level.bombZones ) + zone gameobjects::disable_object(); + + setGameEndTime( 0 ); + + wait 3; + + sd_endGame( game["attackers"], game["strings"]["target_destroyed"] ); +} + +function BombTimerWait() +{ + level endon("game_ended"); + level endon("bomb_defused"); + hostmigration::waitLongDurationWithGameEndTimeUpdate( level.bombTimer ); +} + +function bombDefused( defusedObject, player ) +{ + level.tickingObject globallogic_utils::stopTickingSound(); + level.bombDefused = true; + player SetWeaponOverheating( true, 100, defusedObject.useWeapon ); // overheating allows for non-quick drop anim to be played + SetBombTimer( "A", 0 ); + SetBombTimer( "B", 0 ); + SetMatchFlag( "bomb_timer_a", 0 ); + SetMatchFlag( "bomb_timer_b", 0 ); + + player PlayBombDefuse(); + + level notify("bomb_defused"); + thread globallogic_audio::set_music_on_team( "silent" ); + + wait 1.5; + + setGameEndTime( 0 ); + + sd_endGame( game["defenders"], game["strings"]["bomb_defused"] ); +} + +function sd_isKillBoosting() +{ + roundsPlayed = util::getRoundsPlayed(); + + if ( level.playerKillsMax == 0 ) + return false; + + if ( game["totalKills"] > ( level.totalKillsMax * (roundsPlayed + 1) ) ) + return true; + + if ( self.kills > ( level.playerKillsMax * (roundsPlayed + 1)) ) + return true; + + if ( level.teambased && (self.team == "allies" || self.team == "axis" )) + { + if ( game["totalKillsTeam"][self.team] > ( level.playerKillsMax * (roundsPlayed + 1)) ) + return true; + } + + return false; +} + +function figureOutGameTypeFriendlyFire( victim ) +{ + if ( level.hardcoreMode && level.friendlyfire > 0 && isdefined( victim ) && ( victim.isPlanting === true || victim.isDefusing === true ) ) + { + return 2; // FF 2 = reflect; design wants reflect friendly fire whenever a player is planting or defusing in SD. + } + + return level.friendlyfire; +} \ No newline at end of file diff --git a/mp/gametypes/shrp.csc b/mp/gametypes/shrp.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/shrp.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/shrp.gsc b/mp/gametypes/shrp.gsc new file mode 100644 index 0000000..24503bd --- /dev/null +++ b/mp/gametypes/shrp.gsc @@ -0,0 +1,724 @@ +#using scripts\shared\array_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\persistence_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\weapons\_weapon_utils; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\_wager; + +#using scripts\mp\_util; + + + + + +#precache( "string", "PERKS_SCORE_MULTIPLIER" ); + +/* + Deathmatch + Objective: Score points by eliminating other players + Map ends: When one player reaches the score limit, or time limit is reached + Respawning: No wait / Away from other players + + Level requirements + ------------------ + Spawnpoints: + classname mp_wager_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of enemies at the time of spawn. + Players generally spawn away from enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + Because Deathmatch doesn't have teams with regard to gameplay or scoring, this effectively sets the available weapons. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals +*/ + +#precache( "string", "OBJECTIVES_SHRP" ); +#precache( "string", "OBJECTIVES_SHRP_SCORE" ); +#precache( "string", "OBJECTIVES_SHRP_HINT" ); +#precache( "string", "MP_SHRP_WEAPONS_CYCLED" ); +#precache( "string", "MP_SHRP_PENULTIMATE_RND" ); +#precache( "string", "MP_SHRP_PENULTIMATE_MULTIPLIER" ); +#precache( "string", "MP_SHRP_RND" ); +#precache( "string", "MP_SHRP_FINAL_MULTIPLIER" ); +#precache( "string", "MP_SHRP_COUNTDOWN" ); + +function main() +{ + globallogic::init(); + + level.pointsPerWeaponKill = GetGametypeSetting( "pointsPerWeaponKill" ); + level.pointsPerMeleeKill = GetGametypeSetting( "pointsPerMeleeKill" ); + level.shrpWeaponTimer = GetGametypeSetting( "weaponTimer" ); + level.shrpWeaponNumber = GetGametypeSetting( "weaponCount" ); + + util::registerTimeLimit( level.shrpWeaponNumber * level.shrpWeaponTimer / 60, level.shrpWeaponNumber * level.shrpWeaponTimer / 60 ); + util::registerScoreLimit( 0, 50000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.onPlayerKilled =&onPlayerKilled; + level.onWagerAwards =&onWagerAwards; + + gameobjects::register_allowed_gameobject( level.gameType ); + + level.giveCustomLoadout =&giveCustomLoadout; + + game["dialog"]["gametype"] = "ss_start"; + game["dialog"]["wm_weapons_cycled"] = "ssharp_cycle_01"; + game["dialog"]["wm_final_weapon"] = "ssharp_fweapon"; + game["dialog"]["wm_bonus_rnd"] = "ssharp_2multi_00"; + game["dialog"]["wm_shrp_rnd"] = "ssharp_sround"; + game["dialog"]["wm_bonus0"] = "boost_gen_05"; + game["dialog"]["wm_bonus1"] = "boost_gen_05"; + game["dialog"]["wm_bonus2"] = "boost_gen_05"; + game["dialog"]["wm_bonus3"] = "boost_gen_05"; + game["dialog"]["wm_bonus4"] = "boost_gen_05"; + game["dialog"]["wm_bonus5"] = "boost_gen_05"; + + globallogic::setvisiblescoreboardcolumns( "pointstowin", "kills", "deaths", "stabs", "x2score" ); +} + +function onStartGameType() +{ + SetDvar( "scr_disable_weapondrop", 1 ); + SetDvar( "scr_xpscalemp", 0 ); + SetDvar( "ui_guncycle", 0 ); + //makeDvarServerInfo( "ui_guncycle", 0 ); + + setClientNameMode("auto_change"); + + util::setObjectiveText( "allies", &"OBJECTIVES_SHRP" ); + util::setObjectiveText( "axis", &"OBJECTIVES_SHRP" ); + + attach_compatibility_init(); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_SHRP" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_SHRP" ); + } + else + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_SHRP_SCORE" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_SHRP_SCORE" ); + } + util::setObjectiveHintText( "allies", &"OBJECTIVES_SHRP_HINT" ); + util::setObjectiveHintText( "axis", &"OBJECTIVES_SHRP_HINT" ); + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + newSpawns = GetEntArray( "mp_wager_spawn", "classname" ); + if (newSpawns.size > 0) + { + spawnlogic::add_spawn_points( "allies", "mp_wager_spawn" ); + spawnlogic::add_spawn_points( "axis", "mp_wager_spawn" ); + } + else + { + spawnlogic::add_spawn_points( "allies", "mp_dm_spawn" ); + spawnlogic::add_spawn_points( "axis", "mp_dm_spawn" ); + } + + spawning::updateAllSpawnPoints(); + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + // use the new spawn logic from the start + level.useStartSpawns = false; + + // Toughness + wager::add_powerup( "specialty_bulletflinch", "perk", &"PERKS_TOUGHNESS", "perk_warrior" ); + + // Lightweight + wager::add_powerup( "specialty_movefaster", "perk", &"PERKS_LIGHTWEIGHT", "perk_lightweight" ); + wager::add_powerup( "specialty_fallheight", "perk", &"PERKS_LIGHTWEIGHT", "perk_lightweight" ); + + // Extreme conditioning + wager::add_powerup( "specialty_longersprint", "perk", &"PERKS_EXTREME_CONDITIONING", "perk_marathon" ); + + // x2 Score Multiplier + wager::add_powerup( 2, "score_multiplier", &"PERKS_SCORE_MULTIPLIER", "perk_times_two" ); + + level.gunCycleTimer = hud::createServerTimer( "extrasmall", 1.2 ); + level.gunCycleTimer.horzAlign = "user_left"; + level.gunCycleTimer.vertAlign = "user_top"; + level.gunCycleTimer.x = 10; + level.gunCycleTimer.y = 123; + level.gunCycleTimer.alignX = "left"; + level.gunCycleTimer.alignY = "top"; + level.gunCycleTimer.label = &"MP_SHRP_COUNTDOWN"; + level.gunCycleTimer.alpha = 0; + level.gunCycleTimer.hideWhenInKillcam = true; + + level.displayRoundEndText = false; + level.QuickMessageToAll = true; + level thread chooseRandomGuns(); + level thread clearPowerupsOnGameEnd(); +} + +function attach_compatibility_init() +{ + level.attach_compatible = []; + + set_attachtable_id(); + + for( i = 0; i < 33; i++ ) + { + itemRow = tableLookupRowNum( level.attachTableID, 9, i ); + + if ( itemRow > -1 ) + { + name = tableLookupColumnForRow( level.attachTableID, itemRow, 4 ); + + level.attach_compatible[name] = []; + + compatible = tableLookupColumnForRow( level.attachTableID, itemRow, 11 ); + + level.attach_compatible[name] = strTok( compatible, " " ); + + } + } +} + +function set_attachtable_id() +{ + if ( !isdefined( level.attachTableID ) ) + { + level.attachTableID = "gamedata/weapons/common/attachmentTable.csv"; + } +} + +function getRandomWeaponNameFromProgression() +{ + weaponIDKeys = GetArrayKeys( level.tbl_weaponIDs ); + numWeaponIDKeys = weaponIDKeys.size; + gunProgressionSize = 0; + if ( isdefined( level.gunProgression ) ) + { + size = level.gunProgression.size; + + } +/# + debug_weapon = GetDvarString( "scr_shrp_debug_weapon" ); +#/ + + allowProneBlock = true; + players = GetPlayers(); + + foreach( player in players ) + { + if ( player GetStance() == "prone" ) + { + allowProneBlock = false; + break; + } + } + + while ( true ) + { + randomIndex = RandomInt( numWeaponIDKeys + gunProgressionSize ); + baseWeaponName = ""; + weaponName = ""; + + if ( randomIndex < numWeaponIDKeys ) + { + id = array::random( level.tbl_weaponIDs ); + if ( ( id[ "group" ] != "weapon_launcher" ) && ( id[ "group" ] != "weapon_sniper" ) && ( id[ "group" ] != "weapon_lmg" ) && ( id[ "group" ] != "weapon_assault" ) && ( id[ "group" ] != "weapon_smg" ) && ( id[ "group" ] != "weapon_pistol" ) && ( id[ "group" ] != "weapon_cqb" ) && ( id[ "group" ] != "weapon_special" ) ) + continue; + + if ( id[ "reference" ] == "weapon_null" ) + continue; + + baseWeaponName = id[ "reference" ]; + attachmentList = id[ "attachment" ]; + if ( baseWeaponName == "m32" ) + baseWeaponName = "m32_wager"; + if ( baseWeaponName == "minigun" ) + baseWeaponName = "minigun_wager"; + if ( baseWeaponName == "riotshield" ) + continue; + + weaponName = addRandomAttachmentToWeaponName( baseWeaponName, attachmentList ); + + weapon = GetWeapon( weaponName ); + if ( !allowProneBlock && weapon.blocksProne ) + continue; + } + else + { + baseWeaponName = level.gunProgression[randomIndex - numWeaponIDKeys].names[0]; + weaponName = level.gunProgression[randomIndex - numWeaponIDKeys].names[0]; + } + + if ( !isdefined( level.usedBaseWeapons ) ) + { + level.usedBaseWeapons = []; + level.usedBaseWeapons[0] = "fhj18"; + } + skipWeapon = false; + for ( i = 0 ; i < level.usedBaseWeapons.size ; i++ ) + { + if ( level.usedBaseWeapons[i] == baseWeaponName ) + { + skipWeapon = true; + break; + } + } + if ( skipWeapon ) + continue; + level.usedBaseWeapons[level.usedBaseWeapons.size] = baseWeaponName; +/# + if ( debug_weapon != "" ) + { + weaponName = debug_weapon; + } +#/ + return weaponName; + } +} + +function addRandomAttachmentToWeaponName( baseWeaponName, attachmentList ) +{ + if ( !isdefined( attachmentList ) ) + return baseWeaponName; + + attachments = StrTok( attachmentList, " " ); + ArrayRemoveValue( attachments, "dw" ); // dw weapon madness in the statstable + if ( attachments.size <= 0 ) + return baseWeaponName; + + attachments[attachments.size] = ""; + attachment = array::random( attachments ); + if ( attachment == "" ) + return baseWeaponName; + + if( IsSubStr( attachment, "_" ) ) + { + + attachment = StrTok( attachment, "_" )[0]; + } + + //iprintlnbold( baseWeaponName+attachment ); + + if ( isdefined( level.attach_compatible[attachment] ) && level.attach_compatible[attachment].size > 0 ) + { + attachment2 = level.attach_compatible[attachment][randomInt(level.attach_compatible[attachment].size)]; + + //iprintlnbold( baseWeaponName+attachment+"+"+attachment2 ); + + contains = false; + for ( i=0; i 1 ; i-- ) + { + for ( j = 0 ; j < level.players.size ; j++ ) + level.players[j] playLocalSound( "uin_timer_wager_beep" ); + timePassed = waitLongDurationWithHostMigrationPause ( nextGunCycleTime, ( nextGunCycleTime - GetTime() ) / 1000 / i ); + nextGunCycleTime += timePassed; + } + + for ( i = 0 ; i < level.players.size ; i++ ) + { + level.players[i] playLocalSound( "uin_timer_wager_last_beep" ); + } + if ( ( nextGunCycleTime - GetTime() ) > 0 ) + wait ( ( nextGunCycleTime - GetTime() ) / 1000 ); + + // Next weapon + level.shrpRandomWeapon = GetWeapon( getRandomWeaponNameFromProgression() ); + + for ( i = 0 ; i < level.players.size ; i++ ) + { + level.players[i] notify( "remove_planted_weapons" ); + level.players[i] giveCustomLoadout( false, true ); + } + + return continueCycling; +} + +function chooseRandomGuns() +{ + level endon( "game_ended" ); + level thread awardMostPointsMedalGameEnd(); + waitTime = level.shrpWeaponTimer; + lightningWaitTime = 15; + + level.shrpRandomWeapon = GetWeapon( getRandomWeaponNameFromProgression() ); + + if ( level.inPrematchPeriod ) + level waittill( "prematch_over" ); + + gunCycle = 1; + numGunCycles = int( level.timeLimit * 60 / waitTime + 0.5 ); + + while( true ) + { + nextGunCycleTime = gettime() + waitTime * 1000; + isPenultimateRound = false; + isSharpshooterRound = ( gunCycle == numGunCycles-1 ); + for ( i = 0 ; i < level.players.size ; i++ ) + { + level.players[i].currentGunCyclePoints = 0; + } + level.currentGunCycleMaxPoints = 0; + gunCycleWaiter( nextGunCycleTime, waitTime ); + for ( i = 0 ; i < level.players.size ; i++ ) + { + player = level.players[i]; + + if ( gunCycle + 1 == numGunCycles ) + player wager::announcer( "wm_final_weapon" ); + else + player wager::announcer( "wm_weapons_cycled" ); + + player checkAwardMostPointsThisCycle(); + } + if ( isPenultimateRound ) + { + level.sharpshooterMultiplier = 2; + for ( i = 0 ; i < level.players.size ; i++ ) + level.players[i] thread wager::queue_popup( &"MP_SHRP_PENULTIMATE_RND", 0, &"MP_SHRP_PENULTIMATE_MULTIPLIER", "wm_bonus_rnd" ); + } + else if ( isSharpshooterRound ) + { + lastMultiplier = level.sharpshooterMultiplier; + if ( !isdefined( lastMultiplier ) ) + lastMultiplier = 1; + level.sharpshooterMultiplier = 2; + SetDvar( "ui_guncycle", 0 ); + level.gunCycleTimer.alpha = 0; + for ( i = 0 ; i < level.players.size ; i++ ) + level.players[i] thread wager::queue_popup( &"MP_SHRP_RND", 0, &"MP_SHRP_FINAL_MULTIPLIER", "wm_shrp_rnd" ); + break; + } + else + { + level.sharpshooterMultiplier = 1; + } + gunCycle++; + } +} + +function checkAwardMostPointsThisCycle() +{ + if ( isdefined ( self.currentGunCyclePoints ) && self.currentGunCyclePoints > 0 ) + { + if ( self.currentGunCyclePoints == level.currentGunCycleMaxPoints ) + { + scoreevents::processScoreEvent( "most_points_shrp", self ); + } + } +} + +function awardMostPointsMedalGameEnd() +{ + level waittill( "game_end" ); + + for ( i = 0 ; i < level.players.size ; i++ ) + { + level.players[i] checkAwardMostPointsThisCycle(); + } +} + + +function giveCustomLoadout( takeAllWeapons, alreadySpawned ) +{ + chooseRandomBody = false; + if ( !isdefined( alreadySpawned ) || !alreadySpawned ) + chooseRandomBody = true; + self wager::setup_blank_random_player( takeAllWeapons, chooseRandomBody, level.shrpRandomWeapon ); + self DisableWeaponCycling(); + + self giveWeapon( level.shrpRandomWeapon ); + self switchToWeapon( level.shrpRandomWeapon ); + self giveWeapon( level.weaponBaseMelee ); + + if ( !isdefined( alreadySpawned ) || !alreadySpawned ) + self setSpawnWeapon( level.shrpRandomWeapon ); + + if ( isdefined( takeAllWeapons ) && !takeAllWeapons ) + self thread takeOldWeapons(); + else + self EnableWeaponCycling(); + + return level.shrpRandomWeapon; +} + +function takeOldWeapons() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "weapon_change", newWeapon ); + if ( newWeapon != level.weaponNone ) + break; + } + + weaponsList = self GetWeaponsList(); + for ( i = 0 ; i < weaponsList.size ; i++ ) + { + if ( ( weaponsList[i] != level.shrpRandomWeapon ) && ( weaponsList[i] != level.weaponBaseMelee ) ) + self TakeWeapon( weaponsList[i] ); + } + + self EnableWeaponCycling(); +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( isdefined( attacker ) && IsPlayer( attacker ) && ( attacker != self ) ) + { + // Track Sharpshooter kills + if ( isdefined( level.sharpshooterMultiplier ) && ( level.sharpshooterMultiplier == 2 ) ) + { + if ( !isdefined( attacker.pers["x2kills"] ) ) + attacker.pers["x2kills"] = 1; + else + attacker.pers["x2kills"]++; + attacker.x2Kills = attacker.pers["x2kills"]; + } + else if ( isdefined( level.sharpshooterMultiplier ) && ( level.sharpshooterMultiplier == 3 ) ) + { + if ( !isdefined( attacker.pers["x3kills"] ) ) + attacker.pers["x3kills"] = 1; + else + attacker.pers["x3kills"]++; + attacker.x2Kills = attacker.pers["x3kills"]; + } + + if ( isdefined( self.scoreMultiplier ) && self.scoreMultiplier >= 2 ) + { + scoreevents::processScoreEvent( "kill_x2_score_shrp", attacker, self, weapon ); + } + + // Give next bonus + currentBonus = attacker.currentBonus; + if ( !isdefined( currentBonus ) ) + currentBonus = 0; + if ( currentBonus < level.powerupList.size ) + { + attacker wager::give_powerup( level.powerupList[currentBonus] ); + attacker thread wager::announcer( "wm_bonus"+currentBonus ); + if ( level.powerupList[currentBonus].type == "score_multiplier" && attacker.scoreMultiplier == 2 ) + { + scoreevents::processScoreEvent( "x2_score_shrp", attacker, self, weapon ); + } + currentBonus++; + attacker.currentBonus = currentBonus; + } + + if ( currentBonus >= level.powerupList.size ) // Play FX for kills with max bonus + { + if ( isdefined( attacker.powerups ) && isdefined( attacker.powerups.size ) && ( attacker.powerups.size > 0 ) ) + { + attacker thread wager::pulse_powerup_icon( attacker.powerups.size-1 ); + } + } + + // Give score with multiplier + scoreMultiplier = 1; + if ( isdefined( attacker.scoreMultiplier ) ) + scoreMultiplier = attacker.scoreMultiplier; + + if ( isdefined( level.sharpshooterMultiplier ) ) + scoreMultiplier *= level.sharpshooterMultiplier; + + scoreIncrease = attacker.pointstowin; + for ( i = 1 ; i <= scoreMultiplier ; i++ ) + { + if ( weapon_utils::isMeleeMOD( sMeansOfDeath ) && level.shrpRandomWeapon != level.weaponBaseMelee && level.shrpRandomWeapon.isRiotShield ) + { + attacker globallogic_score::givePointsToWin( level.pointsPerMeleeKill ); + if ( i != 1 ) + { + scoreevents::processScoreEvent( "kill", attacker, self, weapon ); + scoreevents::processScoreEvent( "wager_melee_kill", attacker, self, weapon ); + } + } + else + { + attacker globallogic_score::givePointsToWin( level.pointsPerWeaponKill ); + if ( !isdefined( attacker.currentGunCyclePoints ) ) + { + attacker.currentGunCyclePoints = 0; + } + attacker.currentGunCyclePoints += level.pointsPerWeaponKill; + if ( level.currentGunCycleMaxPoints < attacker.currentGunCyclePoints ) + { + level.currentGunCycleMaxPoints = attacker.currentGunCyclePoints; + } + if ( i != 1 ) + { + scoreevents::processScoreEvent( "kill", attacker, self, weapon ); + } + } + } + scoreIncrease = attacker.pointstowin - scoreIncrease; + if ( scoreMultiplier > 1 || ( isdefined( level.sharpshooterMultiplier ) && level.sharpshooterMultiplier > 1 ) ) + { + attacker playLocalSound( "uin_alert_cash_register" ); + attacker.pers["x2score"] += scoreIncrease; + attacker.x2score = attacker.pers["x2score"]; + } + } + + self.currentBonus = 0; + self.scoreMultiplier = 1; + self wager::clear_powerups(); +} + +function onSpawnPlayer(predictedSpawn) +{ + spawning::onSpawnPlayer(predictedSpawn); + self thread infiniteAmmo(); +} + +function infiniteAmmo() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + wait( 0.1 ); + + weapon = self GetCurrentWeapon(); + + self GiveMaxAmmo( weapon ); + } +} + +function onWagerAwards() +{ + x2kills = self globallogic_score::getPersStat( "x2kills" ); + if ( !isdefined( x2kills ) ) + x2kills = 0; + self persistence::set_after_action_report_stat( "wagerAwards", x2kills, 0 ); + + headshots = self globallogic_score::getPersStat( "headshots" ); + if ( !isdefined( headshots ) ) + headshots = 0; + self persistence::set_after_action_report_stat( "wagerAwards", headshots, 1 ); + + bestKillstreak = self globallogic_score::getPersStat( "best_kill_streak" ); + if ( !isdefined( bestKillstreak ) ) + bestKillstreak = 0; + self persistence::set_after_action_report_stat( "wagerAwards", bestKillstreak, 2 ); +} + +function clearPowerupsOnGameEnd() +{ + level waittill( "game_ended" ); + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + player wager::clear_powerups(); + } +} \ No newline at end of file diff --git a/mp/gametypes/sniperonly.csc b/mp/gametypes/sniperonly.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/sniperonly.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/sniperonly.gsc b/mp/gametypes/sniperonly.gsc new file mode 100644 index 0000000..a069963 --- /dev/null +++ b/mp/gametypes/sniperonly.gsc @@ -0,0 +1,388 @@ +#using scripts\shared\abilities\_ability_util; +#using scripts\shared\callbacks_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\loadout_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_deathicons; +#using scripts\mp\gametypes\_dogtags; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_player; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_wager; +#using scripts\mp\gametypes\_weapons; +#using scripts\mp\gametypes\tdm; + +#using scripts\mp\_util; + +#precache( "string", "MP_MAX_KILL_INDICATOR" ); +#precache( "string", "MP_RANGE_KILL_INDICATOR" ); +#precache( "string", "MP_MAX_MATCH_INDICATOR" ); + +function main() +{ + tdm::main(); + gameobjects::register_allowed_gameobject( "tdm" ); + + level.leaderDialog = undefined; + + level.CONST_FALLBACK_WEAPON = GetWeapon( "sniper_powerbolt", "extclip", "swayreduc" ); + + level.onStartGameType =&onStartGameType; + level.onPlayerKilled =&onPlayerKilled; + level.giveCustomLoadout =&giveCustomLoadout; + + callback::on_connect( &on_player_connect ); +} + +function onStartGameType() +{ + tdm::onStartGameType(); + + level matchBestKillRangeIndicator(); +} + +function on_player_connect() +{ + self thread killRangeIndicator(); +} + +function giveCustomLoadout() +{ + loadout::giveLoadout_init( true ); + + loadout::setClassNum( self.curClass ); + loadout::givePerks(); + + primaryWeapon = self GetLoadoutWeapon( self.class_num, "primary" ); + weaponClass = util::getWeaponClass( primaryWeapon ); + useCamo = true; + if ( !isdefined( primaryWeapon ) || primaryWeapon == level.weaponNone || primaryWeapon == level.weaponNull || !isdefined( weaponClass ) || weaponClass != "weapon_sniper" ) + { + primaryWeapon = level.CONST_FALLBACK_WEAPON; + useCamo = false; + } + + self playerGivePrimaryWeapon( primaryWeapon, useCamo ); + + sidearm = self GetLoadoutWeapon( self.class_num, "secondary" ); + weaponClass = util::getWeaponClass( sidearm ); + if ( isdefined( sidearm ) && sidearm != level.weaponNone && sidearm != level.weaponNull && isdefined( weaponClass ) && weaponClass == "weapon_sniper" ) + { + self playerGiveSecondaryWeapon( sidearm ); + } + + primaryOffhand = GetWeapon( "null_offhand_primary" ); + primaryOffhandCount = 0; + self GiveWeapon( primaryOffhand ); + self SetWeaponAmmoStock( primaryOffhand, primaryOffhandCount ); + self SwitchToOffhand( primaryOffhand ); + self.grenadeTypePrimary = primaryOffhand; + self.grenadeTypePrimaryCount = primaryOffhandCount; + + secondaryOffhand = GetWeapon( "null_offhand_secondary" ); + secondaryOffhandCount = 0; + self GiveWeapon( secondaryOffhand ); + self SetWeaponAmmoClip( secondaryOffhand, secondaryOffhandCount ); + self SwitchToOffhand( secondaryOffhand ); + self.grenadeTypeSecondary = secondaryOffhand; + self.grenadeTypeSecondaryCount = secondaryOffhandCount; + + self giveGadget(); + loadout::giveHeroWeapon(); + + self AllowMelee( false ); + + return primaryWeapon; +} + +function giveGadget() +{ + self.grenadeTypeSpecial = undefined; + + loadout::giveSpecialOffhand(); + + if ( !isdefined( self.grenadeTypeSpecial ) ) + { + bodyIndex = self GetCharacterBodyType(); + gadgetRef = "none"; + switch( bodyIndex ) + { + case 0: + gadgetRef = "gadget_speed_burst"; + break; + case 1: + gadgetRef = "gadget_vision_pulse"; + break; + case 2: + gadgetRef = "gadget_flashback"; + break; + case 3: + gadgetRef = "gadget_armor"; + break; + case 4: + gadgetRef = "gadget_combat_efficiency"; + break; + case 5: + gadgetRef = "gadget_resurrect"; + break; + case 6: + gadgetRef = "gadget_clone"; + break; + case 7: + gadgetRef = "gadget_camo"; + break; + case 8: + gadgetRef = "gadget_heat_wave"; + break; + case 9: + gadgetRef = "gadget_roulette"; + break; + default: + break; + } + + if ( gadgetRef != "none" ) + { + changedClass = self.pers["changed_class"]; + roundBased = !util::isOneRound(); + firstRound = util::isFirstRound(); + + specialOffhand = GetWeapon( gadgetRef ); + specialOffhandCount = specialOffhand.startammo; + self GiveWeapon( specialOffhand ); + + self SetWeaponAmmoClip( specialOffhand, specialOffhandCount ); + self SwitchToOffhand( specialOffhand ); + self.grenadeTypeSpecial = specialOffhand; + self.grenadeTypeSpecialCount = specialOffhandCount; + + self ability_util::gadget_reset( specialOffhand, changedClass, roundBased, firstRound ); + } + } +} + +function playerGivePrimaryWeapon( primaryWeapon, useCamo ) +{ + if ( useCamo ) + { + primaryWeaponOptions = self CalcWeaponOptions( self.class_num, 0 ); + acvi = self GetAttachmentCosmeticVariantForWeapon( self.class_num, "primary" ); + self GiveWeapon( primaryWeapon, primaryWeaponOptions, acvi ); + } + else + { + self GiveWeapon( primaryWeapon ); + } + + self.primaryLoadoutWeapon = primaryWeapon; + self.primaryLoadoutAltWeapon = primaryWeapon.altWeapon; + + if ( useCamo ) + self.primaryLoadoutGunSmithVariantIndex = self GetLoadoutGunSmithVariantIndex( self.class_num, 0 ); + + if ( self HasPerk( "specialty_extraammo" ) ) + { + self GiveMaxAmmo( primaryWeapon ); + } + + self thread loadout::initWeaponAttachments( primaryWeapon ); + + self.pers["changed_class"] = false; + self.spawnWeapon = primaryWeapon; + self.pers["spawnWeapon"] = self.spawnWeapon; + + switchImmediate = isdefined( self.alreadySetSpawnWeaponOnce ); + self SetSpawnWeapon( primaryWeapon, switchImmediate ); + self.alreadySetSpawnWeaponOnce = true; +} + +function playerGiveSecondaryWeapon( sidearm ) +{ + secondaryWeaponOptions = self CalcWeaponOptions( self.class_num, 1 ); + + acvi = self GetAttachmentCosmeticVariantForWeapon( self.class_num, "secondary" ); + self GiveWeapon( sidearm, secondaryWeaponOptions, acvi ); + self.secondaryLoadoutWeapon = sidearm; + self.secondaryLoadoutAltWeapon = sidearm.altWeapon; + self.secondaryLoadoutGunSmithVariantIndex = self GetLoadoutGunSmithVariantIndex( self.class_num, 1 ); + + if ( self HasPerk( "specialty_extraammo" ) ) + { + self giveMaxAmmo( sidearm ); + } +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + victim = self; + + if( ( isdefined( level.droppedTagRespawn ) && level.droppedTagRespawn ) ) + { + should_spawn_tags = self dogtags::should_spawn_tags(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + + // we should spawn tags if one the previous statements were true and we may not spawn + should_spawn_tags = should_spawn_tags && !globallogic_spawn::maySpawn(); + + if( should_spawn_tags ) + level thread dogtags::spawn_dog_tag( self, attacker, &dogtags::onUseDogTag, false ); + } + + if ( !IsDefined( attacker ) ) + { + return; + } + + wasSuicide = ( attacker == self ) || !isPlayer( attacker ); + if ( wasSuicide ) + { + return; + } + + if ( isdefined( attacker ) && isdefined( victim ) && isdefined( attacker.origin ) && isdefined( victim.origin ) ) + { + killDistanceInMeters = Distance( attacker.origin, victim.origin ) * 0.0254; + killRange = Int( Max( killDistanceInMeters, 1 ) ); + attacker thread updateKillIndicator( killRange ); + } + + attacker globallogic_score::giveTeamScoreForObjective( attacker.team, level.teamScorePerKill ); + self globallogic_score::giveTeamScoreForObjective( self.team, level.teamScorePerDeath * -1 ); + if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + { + attacker globallogic_score::giveTeamScoreForObjective( attacker.team, level.teamScorePerHeadshot ); + } +} + +function flashKillIndicator() +{ + level endon( "game_ended" ); + self endon( "death" ); + self endon( "disconnect" ); + + self notify( "newKillIndicator" ); + self endon( "newKillIndicator" ); + + self.killIndicator.alpha = 1; + self.killIndicator FadeOverTime( 3 ); + self.killIndicator.alpha = 0; +} + +function updateKillIndicator( killRange ) +{ + self endon( "disconnect" ); + level endon( "game_ended" ); + + killRangeColorIntensity = killrange; + if( killrange >= 150 ) + { + self.killIndicator.color = ( 0, 1, 0 ); + } + else + { + killRangeColorIntensity = 0.01 * ( 100 - ( 0.67 * killRangeColorIntensity ) ); + self.killIndicator.color = ( killRangeColorIntensity, 1, killRangeColorIntensity ); + } + self.killIndicator SetValue( killRange ); + + if( !isDefined( self.maxKillRange ) || self.maxKillRange < killRange ) + { + self.maxKillRange = killRange; + self.maxKillIndicator.color = ( killRangeColorIntensity, 1, killRangeColorIntensity ); + self.maxKillIndicator SetValue( self.maxKillRange ); + self.maxKillIndicator.alpha = 1; + + if( killRange >= 150 ) + { + self.maxKillIndicator.color = ( 0, 1, 0 ); + self.maxKillIndicator.glowAlpha = 0.5; + } + + if( !IsDefined( level.maxMatchRange ) || level.maxMatchRange < killRange ) + { + level.maxMatchRange = killrange; + level.maxMatchIndicator.color = ( killRangeColorIntensity, 1, killRangeColorIntensity ); + level.maxMatchIndicator setValue( level.maxMatchRange ); + level.maxMatchIndicator.alpha = 1; + + if( killRange >= 150 ) + { + level.maxMatchIndicator.color = ( 0, 1, 0 ); + level.maxMatchIndicator.glowAlpha = 0.5; + } + } + } + + self thread flashKillIndicator(); +} + +function hideKillIndicator() +{ + self endon( "disconnect" ); + + while( 1 ) + { + level waittill( "game_ended" ); + + self.killIndicator.alpha = 0; + self.maxKillIndicator.alpha = 0; + } +} + +function killRangeIndicator() +{ + self endon( "disconnect" ); + + self.killIndicator = hud::createFontString( "objective", 1 ); + self.killIndicator.label = &"MP_RANGE_KILL_INDICATOR"; + self.killIndicator SetValue( 0 ); + self.killIndicator.x = 0; + self.killIndicator.y = 20; + self.killIndicator.alignX = "center"; + self.killIndicator.alignY = "middle"; + self.killIndicator.horzAlign = "user_center"; + self.killIndicator.vertAlign = "middle"; + self.killIndicator.archived = true; + self.killIndicator.fontscale = 1; + self.killIndicator.alpha = 0; + self.killIndicator.glowAlpha = 0.5; + self.killIndicator.hidewheninmenu = false; + + self.maxKillIndicator = hud::createFontString( "objective", 1 ); + self.maxKillIndicator.x = -6; + self.maxKillIndicator.y = 2; + self.maxKillIndicator.alignX = "right"; + self.maxKillIndicator.alignY = "top"; + self.maxKillIndicator.horzAlign = "user_right"; + self.maxKillIndicator.vertAlign = "user_top"; + self.maxKillIndicator.label = &"MP_MAX_KILL_INDICATOR"; + self.maxKillIndicator SetValue( 0 ); + self.maxKillIndicator.alpha = 0; + self.maxKillIndicator.archived = false; + self.maxKillIndicator.hideWhenInMenu = true; + + self thread hideKillIndicator(); +} + +function matchBestKillRangeIndicator() +{ + level.maxMatchIndicator = hud::createServerFontString( "objective", 1 ); + level.maxMatchIndicator.x = -6; + level.maxMatchIndicator.y = 14; + level.maxMatchIndicator.alignX = "right"; + level.maxMatchIndicator.alignY = "top"; + level.maxMatchIndicator.horzAlign = "user_right"; + level.maxMatchIndicator.vertAlign = "user_top"; + level.maxMatchIndicator.label = &"MP_MAX_MATCH_INDICATOR"; + level.maxMatchIndicator SetValue( 0 ); + level.maxMatchIndicator.alpha = 0; + level.maxMatchIndicator.archived = false; + level.maxMatchIndicator.hideWhenInMenu = true; +} diff --git a/mp/gametypes/sr.csc b/mp/gametypes/sr.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/sr.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/sr.gsc b/mp/gametypes/sr.gsc new file mode 100644 index 0000000..0b5439d --- /dev/null +++ b/mp/gametypes/sr.gsc @@ -0,0 +1,1223 @@ +#using scripts\shared\demo_shared; +#using scripts\shared\exploder_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\math_shared; +#using scripts\shared\medals_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_dogtags; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_defaults; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\_spectating; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; + +// Rallypoints should be destroyed on leaving your team/getting killed +// Compass icons need to be looked at +// Doesn't seem to be setting angle on spawn so that you are facing your rallypoint + +/* + Search and Destroy + Attackers objective: Bomb one of 2 positions + Defenders objective: Defend these 2 positions / Defuse planted bombs + Round ends: When one team is eliminated, bomb explodes, bomb is defused, or roundlength time is reached + Map ends: When one team reaches the score limit, or time limit or round limit is reached + Respawning: Players remain dead for the round and will respawn at the beginning of the next round + + Level requirements + ------------------ + Allied Spawnpoints: + classname mp_sd_spawn_attacker + Allied players spawn from these. Place at least 16 of these relatively close together. + + Axis Spawnpoints: + classname mp_sd_spawn_defender + Axis players spawn from these. Place at least 16 of these relatively close together. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Bombzones: + classname trigger_multiple + targetname bombzone + script_gameobjectname bombzone + script_bombmode_original + script_bombmode_single + script_bombmode_dual + script_team Set to allies or axis. This is used to set which team a bombzone is used by in dual bomb mode. + script_label Set to A or B. This sets the letter shown on the compass in original mode. + This is a volume of space in which the bomb can planted. Must contain an origin brush. + + Bomb: + classname trigger_lookat + targetname bombtrigger + script_gameobjectname bombzone + This should be a 16x16 unit trigger with an origin brush placed so that it's center lies on the bottom plane of the trigger. + Must be in the level somewhere. This is the trigger that is used when defusing a bomb. + It gets moved to the position of the planted bomb model. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + This sets the nationalities of the teams. Allies can be american, british, or russian. Axis can be german. + + game["attackers"] = "allies"; + game["defenders"] = "axis"; + This sets which team is attacking and which team is defending. Attackers plant the bombs. Defenders protect the targets. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals + + Exploder Effects: + Setting script_noteworthy on a bombzone trigger to an exploder group can be used to trigger additional effects. +*/ + + + + + + + +#precache( "fx", "explosions/fx_exp_bomb_demo_mp" ); +#precache( "material","compass_waypoint_target" ); +#precache( "material","compass_waypoint_target_a" ); +#precache( "material","compass_waypoint_target_b" ); +#precache( "material","compass_waypoint_defend" ); +#precache( "material","compass_waypoint_defend_a" ); +#precache( "material","compass_waypoint_defend_b" ); +#precache( "material","compass_waypoint_defuse" ); +#precache( "material","compass_waypoint_defuse_a" ); +#precache( "material","compass_waypoint_defuse_b" ); +#precache( "material", "waypoint_dogtags" ); +#precache( "model", "p7_mp_suitcase_bomb" ); +#precache( "objective", "sd_bomb" ); +#precache( "objective", "sd_a" ); +#precache( "objective", "sd_defuse_a" ); +#precache( "objective", "sd_b" ); +#precache( "objective", "sd_defuse_b" ); +#precache( "string", "OBJECTIVES_SD_ATTACKER" ); +#precache( "string", "OBJECTIVES_SD_DEFENDER" ); +#precache( "string", "OBJECTIVES_SD_ATTACKER_SCORE" ); +#precache( "string", "OBJECTIVES_SD_DEFENDER_SCORE" ); +#precache( "string", "OBJECTIVES_SD_ATTACKER_HINT" ); +#precache( "string", "OBJECTIVES_SD_DEFENDER_HINT" ); +#precache( "string", "MP_EXPLOSIVES_BLOWUP_BY" ); +#precache( "string", "MP_EXPLOSIVES_RECOVERED_BY" ); +#precache( "string", "MP_EXPLOSIVES_DROPPED_BY" ); +#precache( "string", "MP_EXPLOSIVES_PLANTED_BY" ); +#precache( "string", "MP_EXPLOSIVES_DEFUSED_BY" ); +#precache( "string", "PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); +#precache( "string", "PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); +#precache( "string", "MP_CANT_PLANT_WITHOUT_BOMB" ); +#precache( "string", "MP_PLANTING_EXPLOSIVE" ); +#precache( "string", "MP_DEFUSING_EXPLOSIVE" ); +#precache( "string", "MP_TARGET_DESTROYED" ); +#precache( "string", "MP_BOMB_DEFUSED" ); +#precache( "string", "bomb" ); +#precache( "triggerstring", "PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); +#precache( "triggerstring", "PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); + +function main() +{ + globallogic::init(); + + util::registerRoundSwitch( 0, 9 ); + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 500 ); + util::registerRoundLimit( 0, 12 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.teamBased = true; + level.overrideTeamScore = true; + level.onPrecacheGameType =&onPrecacheGameType; + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.playerSpawnedCB =&sr_playerSpawnedCB; + level.onPlayerKilled =&onPlayerKilled; + level.onDeadEvent =&onDeadEvent; + level.onOneLeftEvent =&onOneLeftEvent; + level.onTimeLimit =&onTimeLimit; + level.onRoundSwitch =&onRoundSwitch; + level.getTeamKillPenalty =&sr_getTeamKillPenalty; + level.getTeamKillScore =&sr_getTeamKillScore; + level.isKillBoosting =&sr_isKillBoosting; + + level.endGameOnScoreLimit = false; + + gameobjects::register_allowed_gameobject( "sd" ); + gameobjects::register_allowed_gameobject( "bombzone" ); + gameobjects::register_allowed_gameobject( "blocker" ); + + globallogic_audio::set_leader_gametype_dialog ( "startSearchAndRescue", "hcStartSearchAndRescue", "objDestroy", "objDefend" ); + + // Sets the scoreboard columns and determines with data is sent across the network + if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() ) + // local matches only show the first three columns + globallogic::setvisiblescoreboardcolumns( "score", "kills", "plants", "defuses", "deaths" ); + else + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "plants", "defuses" ); +} + +function onPrecacheGameType() +{ + //game["bombmodelname"] = "weapon_explosives"; + //game["bombmodelnameobj"] = "weapon_explosives"; + game["bomb_dropped_sound"] = "fly_bomb_drop_plr"; + game["bomb_recovered_sound"] = "fly_bomb_pickup_plr"; +} + +function sr_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_penalty = globallogic_defaults::default_getTeamKillPenalty( eInflictor, attacker, sMeansOfDeath, weapon ); + + if ( ( isdefined( self.isDefusing ) && self.isDefusing ) || ( isdefined( self.isPlanting ) && self.isPlanting ) ) + { + teamkill_penalty = teamkill_penalty * level.teamKillPenaltyMultiplier; + } + + return teamkill_penalty; +} + +function sr_getTeamKillScore( eInflictor, attacker, sMeansOfDeath, weapon ) +{ + teamkill_score = rank::getScoreInfoValue( "team_kill" ); + + if ( ( isdefined( self.isDefusing ) && self.isDefusing ) || ( isdefined( self.isPlanting ) && self.isPlanting ) ) + { + teamkill_score = teamkill_score * level.teamKillScoreMultiplier; + } + + return int(teamkill_score); +} + + +function onRoundSwitch() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["teamScores"]["allies"] == level.scorelimit - 1 && game["teamScores"]["axis"] == level.scorelimit - 1 ) + { + // overtime! team that's ahead in kills gets to defend. + aheadTeam = getBetterTeam(); + if ( aheadTeam != game["defenders"] ) + { + game["switchedsides"] = !game["switchedsides"]; + } + level.halftimeType = "overtime"; + } + else + { + level.halftimeType = "halftime"; + game["switchedsides"] = !game["switchedsides"]; + } +} + +function getBetterTeam() +{ + kills["allies"] = 0; + kills["axis"] = 0; + deaths["allies"] = 0; + deaths["axis"] = 0; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + team = player.pers["team"]; + if ( isdefined( team ) && (team == "allies" || team == "axis") ) + { + kills[ team ] += player.kills; + deaths[ team ] += player.deaths; + } + } + + if ( kills["allies"] > kills["axis"] ) + return "allies"; + else if ( kills["axis"] > kills["allies"] ) + return "axis"; + + // same number of kills + + if ( deaths["allies"] < deaths["axis"] ) + return "allies"; + else if ( deaths["axis"] < deaths["allies"] ) + return "axis"; + + // same number of deaths + + if ( randomint(2) == 0 ) + return "allies"; + return "axis"; +} + +function onStartGameType() +{ + SetBombTimer( "A", 0 ); + SetMatchFlag( "bomb_timer_a", 0 ); + SetBombTimer( "B", 0 ); + SetMatchFlag( "bomb_timer_b", 0 ); + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + setClientNameMode( "manual_change" ); + + game["strings"]["target_destroyed"] = &"MP_TARGET_DESTROYED"; + game["strings"]["bomb_defused"] = &"MP_BOMB_DEFUSED"; + + level._effect["bombexplosion"] = "explosions/fx_exp_bomb_demo_mp"; + + util::setObjectiveText( game["attackers"], &"OBJECTIVES_SD_ATTACKER" ); + util::setObjectiveText( game["defenders"], &"OBJECTIVES_SD_DEFENDER" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_SD_ATTACKER" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_SD_DEFENDER" ); + } + else + { + util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_SD_ATTACKER_SCORE" ); + util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_SD_DEFENDER_SCORE" ); + } + util::setObjectiveHintText( game["attackers"], &"OBJECTIVES_SD_ATTACKER_HINT" ); + util::setObjectiveHintText( game["defenders"], &"OBJECTIVES_SD_DEFENDER_HINT" ); + + level.alwaysUseStartSpawns = true; + + dogtags::init(); + + initSpawns(); + + thread updateGametypeDvars(); + + thread bombs(); +} + +function initSpawns() +{ + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::place_spawn_points( "mp_sd_spawn_attacker" ); + spawnlogic::place_spawn_points( "mp_sd_spawn_defender" ); + + foreach( team in level.teams ) + { + spawnlogic::add_spawn_points( team, "mp_tdm_spawn" ); + } + + spawning::updateAllSpawnPoints(); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + level.spawn_start = []; + + level.spawn_start["axis"] = spawnlogic::get_spawnpoint_array( "mp_sd_spawn_defender" ); + level.spawn_start["allies"] = spawnlogic::get_spawnpoint_array( "mp_sd_spawn_attacker" ); +} + +function onSpawnPlayer(predictedSpawn) +{ + self.isPlanting = false; + self.isDefusing = false; + self.isBombCarrier = false; + + spawning::onSpawnPlayer(predictedSpawn); + + dogtags::on_spawn_player(); +} + +function sr_playerSpawnedCB() +{ + level notify ( "spawned_player" ); +} + + +function onPlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration) +{ + thread checkAllowSpectating(); + + if ( isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"]) + { + scoreevents::processScoreEvent( "kill_sd", attacker, self, weapon ); + } + + inBombZone = false; + + for ( index = 0; index < level.bombZones.size; index++ ) + { + dist = Distance2dSquared(self.origin, level.bombZones[index].curorigin); + if ( dist < level.defaultOffenseRadiusSQ ) + { + currentObjective = level.bombZones[index]; + inBombZone = true; + break; + } + } + + if ( inBombZone && isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] ) + { + if ( game["defenders"] == self.pers["team"] ) + { + attacker medals::offenseGlobalCount(); + attacker thread challenges::killedBaseOffender( currentObjective, weapon ); + self RecordKillModifier("defending"); + scoreevents::processScoreEvent( "killed_defender", attacker, self, weapon ); + } + else + { + if( isdefined(attacker.pers["defends"]) ) + { + attacker.pers["defends"]++; + attacker.defends = attacker.pers["defends"]; + } + + attacker medals::defenseGlobalCount(); + attacker thread challenges::killedBaseDefender( currentObjective ); + self RecordKillModifier("assaulting"); + scoreevents::processScoreEvent( "killed_attacker", attacker, self, weapon ); + } + } + + if ( isPlayer( attacker ) && attacker.pers["team"] != self.pers["team"] && isdefined( self.isBombCarrier ) && self.isBombCarrier == true ) + { + self RecordKillModifier("carrying"); + } + + if( self.isPlanting == true ) + self RecordKillModifier("planting"); + + if( self.isDefusing == true ) + self RecordKillModifier("defusing"); + + should_spawn_tags = self should_spawn_tags(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + + // we should spawn tags if one the previous statements were true and we may not spawn + should_spawn_tags = should_spawn_tags && !globallogic_spawn::maySpawn(); + + if( should_spawn_tags ) + level thread dogtags::spawn_dog_tag( self, attacker, &onUseDogTag, false ); +} + + +//self is victim +function should_spawn_tags( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( IsAlive( self ) ) + return false; + + //no on switching teams + if ( IsDefined( self.switching_teams ) ) + return false; + + //no on suicide + if ( isDefined( attacker ) && attacker == self ) + return false; + + //no on TK + if ( level.teamBased && isDefined( attacker ) && isDefined( attacker.team ) && attacker.team == self.team ) + return false; + + //no on world suicides + if ( + IsDefined( attacker ) + && ( !IsDefined( attacker.team ) || attacker.team == "free" ) + && ( attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" ) + ) + return false; + + return true; +} + +function checkAllowSpectating() +{ + self endon("disconnect"); + + {wait(.05);}; + + update = false; + + livesLeft = !(level.numLives && !self.pers["lives"]); + + if ( !level.aliveCount[ game["attackers"] ] && !livesLeft ) + { + level.spectateOverride[game["attackers"]].allowEnemySpectate = 1; + update = true; + } + if ( !level.aliveCount[ game["defenders"] ] && !livesLeft ) + { + level.spectateOverride[game["defenders"]].allowEnemySpectate = 1; + update = true; + } + if ( update ) + spectating::update_settings(); +} + + +function sr_endGame( winningTeam, endReasonText ) +{ + if ( isdefined( winningTeam ) ) + globallogic_score::giveTeamScoreForObjective_DelayPostProcessing( winningTeam, 1 ); + + thread globallogic::endGame( winningTeam, endReasonText ); +} + +function sr_endGameWithKillcam( winningTeam, endReasonText ) +{ + sr_endGame( winningTeam, endReasonText ); +} + + +function onDeadEvent( team ) +{ + if ( level.bombExploded || level.bombDefused ) + return; + + if ( team == "all" ) + { + if ( level.bombPlanted ) + sr_endGameWithKillcam( game["attackers"], game["strings"][game["defenders"]+"_eliminated"] ); + else + sr_endGameWithKillcam( game["defenders"], game["strings"][game["attackers"]+"_eliminated"] ); + } + else if ( team == game["attackers"] ) + { + if ( level.bombPlanted ) + return; + + sr_endGameWithKillcam( game["defenders"], game["strings"][game["attackers"]+"_eliminated"] ); + } + else if ( team == game["defenders"] ) + { + sr_endGameWithKillcam( game["attackers"], game["strings"][game["defenders"]+"_eliminated"] ); + } +} + + +function onOneLeftEvent( team ) +{ + if ( level.bombExploded || level.bombDefused ) + return; + + //if ( team == game["attackers"] ) + warnLastPlayer( team ); +} + + +function onTimeLimit() +{ + if ( level.teamBased ) + sr_endGame( game["defenders"], game["strings"]["time_limit_reached"] ); + else + sr_endGame( undefined, game["strings"]["time_limit_reached"] ); +} + + +function warnLastPlayer( team ) +{ + if ( !isdefined( level.warnedLastPlayer ) ) + level.warnedLastPlayer = []; + + if ( isdefined( level.warnedLastPlayer[team] ) ) + return; + + level.warnedLastPlayer[team] = true; + + players = level.players; + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if ( isdefined( player.pers["team"] ) && player.pers["team"] == team && isdefined( player.pers["class"] ) ) + { + if ( player.sessionstate == "playing" && !player.afk ) + break; + } + } + + if ( i == players.size ) + return; + + players[i] thread giveLastAttackerWarning( team ); + + +} + + +function giveLastAttackerWarning( team ) +{ + self endon("death"); + self endon("disconnect"); + + fullHealthTime = 0; + interval = .05; + + self.lastManSD = true; + + enemyTeam = game["defenders"]; + + if ( team == enemyTeam ) + { + enemyTeam = game["attackers"]; + } + + + if ( level.aliveCount[enemyTeam] > 2 ) + { + self.lastManSDDefeat3Enemies = true; + } + + + while(1) + { + if ( self.health != self.maxhealth ) + fullHealthTime = 0; + else + fullHealthTime += interval; + + wait interval; + + if (self.health == self.maxhealth && fullHealthTime >= 3) + break; + } + + self globallogic_audio::leader_dialog_on_player( "roundEncourageLastPlayer" ); + self playlocalsound ("mus_last_stand"); +} + + +function updateGametypeDvars() +{ + level.plantTime = GetGametypeSetting( "plantTime" ); + level.defuseTime = GetGametypeSetting( "defuseTime" ); + level.bombTimer = GetGametypeSetting( "bombTimer" ); + level.multiBomb = GetGametypeSetting( "multiBomb" ); + + level.teamKillPenaltyMultiplier = GetGametypeSetting( "teamKillPenalty" ); + level.teamKillScoreMultiplier = GetGametypeSetting( "teamKillScore" ); + + level.playerKillsMax = GetGametypeSetting( "playerKillsMax" ); + level.totalKillsMax = GetGametypeSetting( "totalKillsMax" ); +} + +function bombs() +{ + level.bombPlanted = false; + level.bombDefused = false; + level.bombExploded = false; + + trigger = getEnt( "sd_bomb_pickup_trig", "targetname" ); + if ( !isdefined( trigger ) ) + { + /#util::error("No sd_bomb_pickup_trig trigger found in map.");#/ + return; + } + + visuals[0] = getEnt( "sd_bomb", "targetname" ); + if ( !isdefined( visuals[0] ) ) + { + /#util::error("No sd_bomb script_model found in map.");#/ + return; + } + + //visuals[0] setModel( "weapon_explosives" ); + + if ( !level.multiBomb ) + { + level.sdBomb = gameobjects::create_carry_object( game["attackers"], trigger, visuals, (0,0,32), &"sd_bomb" ); + level.sdBomb gameobjects::allow_carry( "friendly" ); + level.sdBomb gameobjects::set_2d_icon( "friendly", "compass_waypoint_bomb" ); + level.sdBomb gameobjects::set_3d_icon( "friendly", "waypoint_bomb" ); + level.sdBomb gameobjects::set_visible_team( "friendly" ); + level.sdBomb gameobjects::set_carry_icon( "hud_suitcase_bomb" ); + level.sdBomb.allowWeapons = true; + level.sdBomb.onPickup =&onPickup; + level.sdBomb.onDrop =&onDrop; + } + else + { + trigger delete(); + visuals[0] delete(); + } + + + level.bombZones = []; + + bombZones = getEntArray( "bombzone", "targetname" ); + + for ( index = 0; index < bombZones.size; index++ ) + { + trigger = bombZones[index]; + visuals = getEntArray( bombZones[index].target, "targetname" ); + + name = istring("sd"+trigger.script_label); + + bombZone = gameobjects::create_use_object( game["defenders"], trigger, visuals, (0,0,0), name ); + bombZone gameobjects::allow_use( "enemy" ); + bombZone gameobjects::set_use_time( level.plantTime ); + bombZone gameobjects::set_use_text( &"MP_PLANTING_EXPLOSIVE" ); + bombZone gameobjects::set_use_hint_text( &"PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" ); + if ( !level.multiBomb ) + bombZone gameobjects::set_key_object( level.sdBomb ); + label = bombZone gameobjects::get_label(); + bombZone.label = label; + bombZone gameobjects::set_2d_icon( "friendly", "compass_waypoint_defend" + label ); + bombZone gameobjects::set_3d_icon( "friendly", "waypoint_defend" + label ); + bombZone gameobjects::set_2d_icon( "enemy", "compass_waypoint_target" + label ); + bombZone gameobjects::set_3d_icon( "enemy", "waypoint_target" + label ); + bombZone gameobjects::set_visible_team( "any" ); + bombZone.onBeginUse =&onBeginUse; + bombZone.onEndUse =&onEndUse; + bombZone.onUse =&onUsePlantObject; + bombZone.onCantUse =&onCantUse; + bombZone.useWeapon = GetWeapon( "briefcase_bomb" ); + bombZone.visuals[0].killCamEnt = spawn( "script_model", bombZone.visuals[0].origin + (0,0,128) ); + if ( !level.multiBomb ) + bombZone.trigger SetInvisibleToAll(); + + for ( i = 0; i < visuals.size; i++ ) + { + if ( isdefined( visuals[i].script_exploder ) ) + { + bombZone.exploderIndex = visuals[i].script_exploder; + break; + } + } + + level.bombZones[level.bombZones.size] = bombZone; + + bombZone.bombDefuseTrig = getent( visuals[0].target, "targetname" ); + assert( isdefined( bombZone.bombDefuseTrig ) ); + bombZone.bombDefuseTrig.origin += (0,0,-10000); + bombZone.bombDefuseTrig.label = label; + } + + for ( index = 0; index < level.bombZones.size; index++ ) + { + array = []; + for ( otherindex = 0; otherindex < level.bombZones.size; otherindex++ ) + { + if ( otherindex != index ) + array[ array.size ] = level.bombZones[otherindex]; + } + level.bombZones[index].otherBombZones = array; + } +} + +function onBeginUse( player ) +{ + if ( self gameobjects::is_friendly_team( player.pers["team"] ) ) + { + player playSound( "mpl_sd_bomb_defuse" ); + player.isDefusing = true; + player thread battlechatter::gametype_specific_battle_chatter( "sd_enemyplant", player.pers["team"] ); + + if ( isdefined( level.sdBombModel ) ) + level.sdBombModel hide(); + } + else + { + player.isPlanting = true; + player thread battlechatter::gametype_specific_battle_chatter( "sd_friendlyplant", player.pers["team"] ); + + if ( level.multibomb ) + { + for ( i = 0; i < self.otherBombZones.size; i++ ) + { + self.otherBombZones[i] gameobjects::disable_object(); + } + } + } + player playSound( "fly_bomb_raise_plr" ); +} + +function onEndUse( team, player, result ) +{ + if ( !isdefined( player ) ) + return; + + player.isDefusing = false; + player.isPlanting = false; + player notify( "event_ended" ); + + if ( self gameobjects::is_friendly_team( player.pers["team"] ) ) + { + if ( isdefined( level.sdBombModel ) && !result ) + { + level.sdBombModel show(); + } + } + else + { + if ( level.multibomb && !result ) + { + for ( i = 0; i < self.otherBombZones.size; i++ ) + { + self.otherBombZones[i] gameobjects::enable_object(); + } + } + } +} + +function onCantUse( player ) +{ + player iPrintLnBold( &"MP_CANT_PLANT_WITHOUT_BOMB" ); +} + +function onUsePlantObject( player ) +{ + // planted the bomb + if ( !self gameobjects::is_friendly_team( player.pers["team"] ) ) + { + self gameobjects::set_flags( 1 ); + level thread bombPlanted( self, player ); + /#print( "bomb planted: " + self.label );#/ + + // disable all bomb zones except this one + for ( index = 0; index < level.bombZones.size; index++ ) + { + if ( level.bombZones[index] == self ) + { + level.bombZones[index].isPlanted = true; + continue; + } + + level.bombZones[index] gameobjects::disable_object(); + } + thread sound::play_on_players( "mus_sd_planted"+"_"+level.teamPostfix[player.pers["team"]] ); +// removed plant audio until finalization of assest TODO : new plant sounds when assests are online +// player playSound( "mpl_sd_bomb_plant" ); + player notify ( "bomb_planted" ); + + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_PLANTED_BY", player ); + + if( isdefined(player.pers["plants"]) ) + { + player.pers["plants"]++; + player.plants = player.pers["plants"]; + } + + demo::bookmark( "event", gettime(), player ); + player AddPlayerStatWithGameType( "PLANTS", 1 ); + + globallogic_audio::leader_dialog( "bombPlanted" ); + + scoreevents::processScoreEvent( "planted_bomb", player ); + player RecordGameEvent("plant"); + } +} + +function onUseDefuseObject( player ) +{ + self gameobjects::set_flags( 0 ); + player notify ( "bomb_defused" ); + /#print( "bomb defused: " + self.label );#/ + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "sd_bombdefuse", self.label, player.pers["team"], player.origin ); + level thread bombDefused(); + + // disable this bomb zone + self gameobjects::disable_object(); + + for ( index = 0; index < level.bombZones.size; index++ ) + { + level.bombZones[index].isPlanted = false; + } + + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_DEFUSED_BY", player ); + + if( isdefined(player.pers["defuses"]) ) + { + player.pers["defuses"]++; + player.defuses = player.pers["defuses"]; + } + + player AddPlayerStatWithGameType( "DEFUSES", 1 ); + demo::bookmark( "event", gettime(), player ); + + globallogic_audio::leader_dialog( "bombDefused" ); + + if ( isdefined( player.lastManSD ) && player.lastManSD == true ) + { + scoreevents::processScoreEvent( "defused_bomb_last_man_alive", player ); + player addplayerstat( "defused_bomb_last_man_alive", 1 ); + } + else + { + scoreevents::processScoreEvent( "defused_bomb", player ); + } + player RecordGameEvent("defuse"); +} + + +function onDrop( player ) +{ + if ( !level.bombPlanted ) + { + globallogic_audio::leader_dialog( "bombFriendlyDropped", game["attackers"] ); + /# + if ( isdefined( player ) ) + print( "bomb dropped" ); + else + print( "bomb dropped" ); + #/ + } + + player notify( "event_ended" ); + + self gameobjects::set_3d_icon( "friendly", "waypoint_bomb" ); + + sound::play_on_players( game["bomb_dropped_sound"], game["attackers"] ); + + if ( isdefined(level.bombDropBotEvent) ) + { + [[level.bombDropBotEvent]](); + } +} + + +function onPickup( player ) +{ + player.isBombCarrier = true; + + player RecordGameEvent("pickup"); + + self gameobjects::set_3d_icon( "friendly", "waypoint_defend" ); + + if ( !level.bombDefused ) + { + if ( isdefined( player ) && isdefined( player.name ) ) + { + player AddPlayerStatWithGameType( "PICKUPS", 1 ); + } + + //thread sound::play_on_players( "mus_sd_pickup"+"_"+level.teamPostfix[player.pers["team"]], player.pers["team"] ); + // New Music System + team = self gameobjects::get_owner_team(); + otherTeam = util::getOtherTeam( team ); + + globallogic_audio::leader_dialog( "bombFriendlyTaken", game["attackers"] ); + /#print( "bomb taken" );#/ + } + sound::play_on_players( game["bomb_recovered_sound"], game["attackers"] ); + + for ( i = 0; i < level.bombZones.size; i++ ) + { + level.bombZones[i].trigger SetInvisibleToAll(); + level.bombZones[i].trigger SetVisibleToPlayer( player ); + } + + if ( isdefined(level.bombPickupBotEvent) ) + { + [[level.bombPickupBotEvent]](); + } +} + + +function onReset() +{ + +} + +function bombPlantedMusicDelay() +{ + level endon ("bomb_defused"); + //wait for 30 seconds until explosion + + time = (level.bombtimer - 30); +/# + if( GetDvarint( "debug_music" ) > 0 ) + { + println ("Music System - waiting to set TIME_OUT: " + time ); + } +#/ + if (time > 1) + { + wait (time); + thread globallogic_audio::set_music_on_team( "timeOut" ); + } +} + +function bombPlanted( destroyedObj, player ) +{ + globallogic_utils::pauseTimer(); + level.bombPlanted = true; + team = player.pers["team"]; + + destroyedObj.visuals[0] thread globallogic_utils::playTickingSound( "mpl_sab_ui_suitcasebomb_timer" ); + //Play suspense music + level thread bombPlantedMusicDelay(); + + level.tickingObject = destroyedObj.visuals[0]; + + level.timeLimitOverride = true; + setGameEndTime( int( gettime() + (level.bombTimer * 1000) ) ); + + label = destroyedObj gameobjects::get_label(); + SetMatchFlag( "bomb_timer"+label, 1 ); + if ( label == "_a" ) + { + SetBombTimer( "A", int( gettime() + level.bombTimer * 1000 ) ); + } + else + { + SetBombTimer( "B", int( gettime() + level.bombTimer * 1000 ) ); + } + + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "sd_bombplant", label, team, player.origin ); + + if ( !level.multiBomb ) + { + level.sdBomb gameobjects::allow_carry( "none" ); + level.sdBomb gameobjects::set_visible_team( "none" ); + level.sdBomb gameobjects::set_dropped(); + level.sdBombModel = level.sdBomb.visuals[0]; + } + else + { + + for ( index = 0; index < level.players.size; index++ ) + { + if ( isdefined( level.players[index].carryIcon ) ) + level.players[index].carryIcon hud::destroyElem(); + } + + trace = bulletTrace( player.origin + (0,0,20), player.origin - (0,0,2000), false, player ); + + tempAngle = randomfloat( 360 ); + forward = (cos( tempAngle ), sin( tempAngle ), 0); + forward = vectornormalize( forward - VectorScale( trace["normal"], vectordot( forward, trace["normal"] ) ) ); + dropAngles = vectortoangles( forward ); + + level.sdBombModel = spawn( "script_model", trace["position"] ); + level.sdBombModel.angles = dropAngles; + level.sdBombModel setModel( "p7_mp_suitcase_bomb" ); + } + destroyedObj gameobjects::allow_use( "none" ); + destroyedObj gameobjects::set_visible_team( "none" ); + /* + destroyedObj gameobjects::set_2d_icon( "friendly", undefined ); + destroyedObj gameobjects::set_2d_icon( "enemy", undefined ); + destroyedObj gameobjects::set_3d_icon( "friendly", undefined ); + destroyedObj gameobjects::set_3d_icon( "enemy", undefined ); + */ + label = destroyedObj gameobjects::get_label(); + + // create a new object to defuse with. + trigger = destroyedObj.bombDefuseTrig; + trigger.origin = level.sdBombModel.origin; + visuals = []; + defuseObject = gameobjects::create_use_object( game["defenders"], trigger, visuals, (0,0,32), istring("sd_defuse"+label) ); + defuseObject gameobjects::allow_use( "friendly" ); + defuseObject gameobjects::set_use_time( level.defuseTime ); + defuseObject gameobjects::set_use_text( &"MP_DEFUSING_EXPLOSIVE" ); + defuseObject gameobjects::set_use_hint_text( &"PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); + defuseObject gameobjects::set_visible_team( "any" ); + defuseObject gameobjects::set_2d_icon( "friendly", "compass_waypoint_defuse" + label ); + defuseObject gameobjects::set_2d_icon( "enemy", "compass_waypoint_defend" + label ); + defuseObject gameobjects::set_3d_icon( "friendly", "waypoint_defuse" + label ); + defuseObject gameobjects::set_3d_icon( "enemy", "waypoint_defend" + label ); + defuseObject gameobjects::set_flags( 1 ); + defuseObject.label = label; + defuseObject.onBeginUse =&onBeginUse; + defuseObject.onEndUse =&onEndUse; + defuseObject.onUse =&onUseDefuseObject; + defuseObject.useWeapon = GetWeapon( "briefcase_bomb_defuse" ); + + player.isBombCarrier = false; + + BombTimerWait(); + SetBombTimer( "A", 0 ); + SetBombTimer( "B", 0 ); + SetMatchFlag( "bomb_timer_a", 0 ); + SetMatchFlag( "bomb_timer_b", 0 ); + + destroyedObj.visuals[0] globallogic_utils::stopTickingSound(); + + if ( level.gameEnded || level.bombDefused ) + return; + + level.bombExploded = true; + + origin = (0,0,0); + if ( isdefined( player ) ) + { + origin = player.origin; + } + bbPrint( "mpobjective", "gametime %d objtype %s label %s team %s playerx %d playery %d playerz %d", gettime(), "sd_bombexplode", label, team, origin ); + + explosionOrigin = level.sdBombModel.origin+(0,0,12); + level.sdBombModel hide(); + + if ( isdefined( player ) ) + { + destroyedObj.visuals[0] radiusDamage( explosionOrigin, 512, 200, 20, player, "MOD_EXPLOSIVE", GetWeapon( "briefcase_bomb" ) ); + level thread popups::DisplayTeamMessageToAll( &"MP_EXPLOSIVES_BLOWUP_BY", player ); + scoreevents::processScoreEvent( "bomb_detonated", player ); + player AddPlayerStatWithGameType( "DESTRUCTIONS", 1 ); + player RecordGameEvent("destroy"); + } + else + destroyedObj.visuals[0] radiusDamage( explosionOrigin, 512, 200, 20, undefined, "MOD_EXPLOSIVE", GetWeapon( "briefcase_bomb" ) ); + + rot = randomfloat(360); + explosionEffect = spawnFx( level._effect["bombexplosion"], explosionOrigin + (0,0,50), (0,0,1), (cos(rot),sin(rot),0) ); + triggerFx( explosionEffect ); + + thread sound::play_in_space( "mpl_sd_exp_suitcase_bomb_main", explosionOrigin ); + + if ( isdefined( destroyedObj.exploderIndex ) ) + exploder::exploder( destroyedObj.exploderIndex ); + + defuseObject gameobjects::destroy_object(); + foreach ( zone in level.bombZones ) + zone gameobjects::disable_object(); + + setGameEndTime( 0 ); + + wait 3; + + sr_endGame( game["attackers"], game["strings"]["target_destroyed"] ); +} + +function BombTimerWait() +{ + level endon("game_ended"); + level endon("bomb_defused"); + hostmigration::waitLongDurationWithGameEndTimeUpdate( level.bombTimer ); +} + +function bombDefused() +{ + level.tickingObject globallogic_utils::stopTickingSound(); + level.bombDefused = true; + SetBombTimer( "A", 0 ); + SetBombTimer( "B", 0 ); + SetMatchFlag( "bomb_timer_a", 0 ); + SetMatchFlag( "bomb_timer_b", 0 ); + + level notify("bomb_defused"); + thread globallogic_audio::set_music_on_team( "silent" ); + + wait 1.5; + + setGameEndTime( 0 ); + + sr_endGame( game["defenders"], game["strings"]["bomb_defused"] ); +} + +function sr_isKillBoosting() +{ + roundsPlayed = util::getRoundsPlayed(); + + if ( level.playerKillsMax == 0 ) + return false; + + if ( game["totalKills"] > ( level.totalKillsMax * (roundsPlayed + 1) ) ) + return true; + + if ( self.kills > ( level.playerKillsMax * (roundsPlayed + 1)) ) + return true; + + if ( level.teambased && (self.team == "allies" || self.team == "axis" )) + { + if ( game["totalKillsTeam"][self.team] > ( level.playerKillsMax * (roundsPlayed + 1)) ) + return true; + } + + return false; +} + +function onUseDogTag( player ) +{ + // friendly pickup + if ( player.pers["team"] == self.victimTeam ) + { + player.pers["rescues"]++; + player.rescues = player.pers["rescues"]; + + if ( IsDefined( self.victim ) ) + { + if ( !level.gameEnded ) + self.victim thread sr_respawn(); + } + } +} + +function sr_respawn() +{ + // Need to count this player as alive immediately, because they can wait to spawn whenever they want. If we don't increment this until they become alive, + // then things get screwed up when level.aliveCount becomes 1. The game thinks that there's only one player left alive, and yet there are multiple players + // on the team with self.pers["lives"] greater than 0. +// self maps\mp\gametypes\_playerlogic::incrementAliveCount(self.team); +// self.alreadyAddedToAliveCount = true; + + self thread waitTillCanSpawnClient(); +} + +//fixes a potential race condition with spawning around the same frame as friendly tag pickup +function waitTillCanSpawnClient() +{ + for (;;) + { + wait ( .05 ); + if ( isDefined( self ) && ( self.sessionstate == "spectator" || !isAlive( self ) ) ) + { + self.pers["lives"] = 1; + self thread [[level.spawnClient]](); + + //we need to continue here because spawn client can fail for up to 3 server frames in this instance + continue; + } + + //player either disconnected or has spawned + return; + } +} \ No newline at end of file diff --git a/mp/gametypes/tdef.csc b/mp/gametypes/tdef.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/tdef.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/tdef.gsc b/mp/gametypes/tdef.gsc new file mode 100644 index 0000000..7e3ae98 --- /dev/null +++ b/mp/gametypes/tdef.gsc @@ -0,0 +1,599 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_util; +#using scripts\mp\teams\_teams; + +#using scripts\mp\killstreaks\_killstreaks; + + +/* + Team Defender + Objective: Score points for your team by eliminating players on the opposing team. + Team with flag scores double kill points. + First corpse spawns the flag. + Map ends: When one team reaches the score limit, or time limit is reached + Respawning: No wait / Near teammates + + Level requirementss + ------------------ + Spawnpoints: + classname mp_tdm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of teammates and enemies + at the time of spawn. Players generally spawn behind their teammates relative to the direction of enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. +*/ + + +#precache( "string", "OBJECTIVES_TDEF" ); +#precache( "string", "OBJECTIVES_TDEF_SCORE" ); +#precache( "string", "OBJECTIVES_TDEF_ATTACKER_HINT" ); +#precache( "string", "MP_NEUTRAL_FLAG_CAPTURED_BY" ); +#precache( "string", "MP_NEUTRAL_FLAG_DROPPED_BY" ); +#precache( "string", "MP_GRABBING_FLAG" ); +#precache( "string", "OBJECTIVES_TDEF_ATTACKER_HINT" ); +#precache( "string", "OBJECTIVES_TDEF_DEFENDER_HINT" ); +#precache( "string", "OBJECTIVES_TDEF" ); +#precache( "string", "OBJECTIVES_TDEF_SCORE" ); +#precache( "string", "OBJECTIVES_TDEF_HINT" ); +#precache( "string", "MP_FIRST_BLOOD" ); + +function main() +{ + globallogic::init(); + + util::registerRoundSwitch( 0, 9 ); + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 10000 ); + util::registerRoundLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + level.matchRules_enemyFlagRadar = true; + level.matchRules_damageMultiplier = 0; + level.matchRules_vampirism = 0; + + setSpecialLoadouts(); + + level.teamBased = true; + level.initGametypeAwards =&initGametypeAwards; + level.onPrecacheGameType =&onPrecacheGameType; + level.onStartGameType =&onStartGameType; + level.onPlayerKilled =&onPlayerKilled; + level.onSpawnPlayer =&onSpawnPlayer; + level.onRoundEndGame =&onRoundEndGame; + level.onRoundSwitch =&onRoundSwitch; + + gameobjects::register_allowed_gameobject( level.gameType ); + gameobjects::register_allowed_gameobject( "tdm" ); + + game["dialog"]["gametype"] = "team_def"; + if ( getDvarInt( "g_hardcore" ) ) + game["dialog"]["gametype"] = "hc_" + game["dialog"]["gametype"]; + + game["dialog"]["got_flag"] = "ctf_wetake"; + game["dialog"]["enemy_got_flag"] = "ctf_theytake"; + game["dialog"]["dropped_flag"] = "ctf_wedrop"; + game["dialog"]["enemy_dropped_flag"] = "ctf_theydrop"; + + game["strings"]["overtime_hint"] = &"MP_FIRST_BLOOD"; +} + + +function onPrecacheGameType() +{ + +} + + +function onStartGameType() +{ + setClientNameMode("auto_change"); + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + util::setObjectiveText( "allies", &"OBJECTIVES_TDEF" ); + util::setObjectiveText( "axis", &"OBJECTIVES_TDEF" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_TDEF" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_TDEF" ); + } + else + { + util::setObjectiveScoreText( "allies", &"OBJECTIVES_TDEF_SCORE" ); + util::setObjectiveScoreText( "axis", &"OBJECTIVES_TDEF_SCORE" ); + } + util::setObjectiveHintText( "allies", &"OBJECTIVES_TDEF_ATTACKER_HINT" ); + util::setObjectiveHintText( "axis", &"OBJECTIVES_TDEF_ATTACKER_HINT" ); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + spawnlogic::place_spawn_points( "mp_tdm_spawn_allies_start" ); + spawnlogic::place_spawn_points( "mp_tdm_spawn_axis_start" ); + spawnlogic::add_spawn_points( "allies", "mp_tdm_spawn" ); + spawnlogic::add_spawn_points( "axis", "mp_tdm_spawn" ); + spawning::updateAllSpawnPoints(); + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + if ( !util::isOneRound() ) + { + level.displayRoundEndText = true; + if( level.scoreRoundWinBased ) + { + globallogic_score::resetTeamScores(); + } + } + + tdef(); +} + +function tdef() +{ +// level.icon2D["allies"] = teams::get_flag_icon( "allies" ); +// level.icon2D["axis"] = teams::get_flag_icon( "axis" ); + + level.carryFlag["allies"] = teams::get_flag_carry_model( "allies" ); + level.carryFlag["axis"] = teams::get_flag_carry_model( "axis" ); + level.carryFlag["neutral"] = teams::get_flag_model( "neutral" ); + + level.gameFlag = undefined; +} + + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if ( !isPlayer( attacker ) || attacker.team == self.team ) + return; + + victim = self; + + score = rank::getScoreInfoValue( "kill" ); + assert( isdefined( score ) ); + + // we got the flag - give bonus + if ( isdefined( level.gameFlag ) && level.gameFlag gameobjects::get_owner_team() == attacker.team ) + { + // I'm the carrier + if ( isdefined( attacker.carryFlag ) ) + { + attacker AddPlayerStat( "KILLSASFLAGCARRIER", 1 ); + } + // someone else is + else + { + // give flag carrier a bonus for kills achieved by team + // scoreevents::processScoreEvent( "team_assist", level.gameFlag.carrier ); + } + + //scoreevents::processScoreEvent( "kill_bonus", attacker ); + + score *= 2; + } + // no flag yet - create it + else if ( !isdefined( level.gameFlag ) && canCreateFlagAtVictimOrigin( victim ) ) + { + level.gameFlag = createFlag( victim ); + + score += rank::getScoreInfoValue( "MEDAL_FIRST_BLOOD" ); + } + // killed carrier - give bonus + else if ( isdefined( victim.carryFlag ) ) + { + killCarrierBonus = rank::getScoreInfoValue( "kill_carrier" ); + + level thread popups::DisplayTeamMessageToAll( &"MP_KILLED_FLAG_CARRIER", attacker ); + scoreevents::processScoreEvent( "kill_flag_carrier", attacker ); + attacker RecordGameEvent("kill_carrier"); + attacker AddPlayerStat( "FLAGCARRIERKILLS", 1 ); + attacker notify( "objective", "kill_carrier" ); + + score += killCarrierBonus; + } + + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + attacker globallogic_score::giveTeamScoreForObjective( attacker.team, score ); + + otherTeam = util::getOtherTeam( attacker.team ); + if ( game["state"] == "postgame" && game["teamScores"][attacker.team] > game["teamScores"][otherTeam] ) + attacker.finalKill = true; +} + + +function onDrop( player ) +{ + // get the time when they dropped it + if( isdefined( player ) && isdefined( player.tdef_flagTime ) ) + { + flagTime = int( GetTime() - player.tdef_flagTime ); + player AddPlayerStat( "HOLDINGTEAMDEFENDERFLAG", flagTime ); + + if ( ( flagTime/100 ) / 60 < 1 ) + flagMinutes = 0; + else + flagMinutes = int( ( flagTime/100 ) / 60 ); + + player AddPlayerStatWithGameType( "DESTRUCTIONS", flagMinutes ); + + player.tdef_flagTime = undefined; + player notify( "dropped_flag" ); + } + + team = self gameobjects::get_owner_team(); + otherTeam = util::getOtherTeam( team ); + + self.currentCarrier = undefined; + + self gameobjects::set_owner_team( "neutral" ); + self gameobjects::allow_carry( "any" ); + self gameobjects::set_visible_team( "any" ); + self gameobjects::set_2d_icon( "friendly", level.iconCaptureFlag2D ); + self gameobjects::set_3d_icon( "friendly", level.iconCaptureFlag3D ); + self gameobjects::set_2d_icon( "enemy", level.iconCaptureFlag2D ); + self gameobjects::set_3d_icon( "enemy", level.iconCaptureFlag3D ); + + if ( isdefined( player ) ) + { + if ( isdefined( player.carryFlag ) ) + player detachFlag(); + + util::printAndSoundOnEveryone( team, undefined, &"MP_NEUTRAL_FLAG_DROPPED_BY", &"MP_NEUTRAL_FLAG_DROPPED_BY", "mp_war_objective_lost", "mp_war_objective_lost", player ); + } + else + { + sound::play_on_players( "mp_war_objective_lost", team ); + sound::play_on_players( "mp_war_objective_lost", otherTeam ); + } + + globallogic_audio::leader_dialog( "dropped_flag", team); + globallogic_audio::leader_dialog( "enemy_dropped_flag", otherTeam ); +} + + +function onPickup( player ) +{ + self notify ( "picked_up" ); + + // get the time when they picked it up + player.tdef_flagTime = GetTime(); + player thread watchForEndGame(); + + score = rank::getScoreInfoValue( "capture" ); + assert( isdefined( score ) ); + + team = player.team; + otherTeam = util::getOtherTeam( team ); + + // flag carrier class? (do before attaching flag) + if ( isdefined( level.tdef_loadouts ) && isdefined( level.tdef_loadouts[team] ) ) + player thread applyFlagCarrierClass(); // attaches flag + else + player attachFlag(); + + self.currentCarrier = player; + player.carryIcon setShader( level.icon2D[team], player.carryIcon.width, player.carryIcon.height ); + + self gameobjects::set_owner_team( team ); + self gameobjects::set_visible_team( "any" ); + self gameobjects::set_2d_icon( "friendly", level.iconEscort2D ); + self gameobjects::set_3d_icon( "friendly", level.iconEscort2D ); + self gameobjects::set_2d_icon( "enemy", level.iconKill3D ); + self gameobjects::set_3d_icon( "enemy", level.iconKill3D ); + + globallogic_audio::leader_dialog( "got_flag", team ); + globallogic_audio::leader_dialog( "enemy_got_flag", otherTeam ); + + level thread popups::DisplayTeamMessageToAll( &"MP_CAPTURED_THE_FLAG", player ); + scoreevents::processScoreEvent( "flag_capture", player ); + player RecordGameEvent("pickup"); + player AddPlayerStatWithGameType( "CAPTURES", 1 ); + player notify( "objective", "captured" ); + + util::printAndSoundOnEveryone( team, undefined, &"MP_NEUTRAL_FLAG_CAPTURED_BY", &"MP_NEUTRAL_FLAG_CAPTURED_BY", "mp_obj_captured", "mp_enemy_obj_captured", player ); + + // give a capture bonus to the capturing team if the flag is changing hands + if ( self.currentTeam == otherTeam ) + player globallogic_score::giveTeamScoreForObjective( team, score ); + self.currentTeam = team; + + // activate portable radar on flag for the opposing team + if ( level.matchRules_enemyFlagRadar ) + self thread flagAttachRadar( otherTeam ); +} + + +function applyFlagCarrierClass() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + // remove placement item if carrying + if ( isdefined( self.isCarrying ) && self.isCarrying == true ) + { + self notify( "force_cancel_placement" ); + {wait(.05);}; + } + + // set the gamemodeloadout for loadout::giveLoadout() to use + self.pers["gamemodeLoadout"] = level.tdef_loadouts[self.team]; + + // set faux TI to respawn in place + spawnPoint = spawn( "script_model", self.origin ); + spawnPoint.angles = self.angles; + spawnPoint.playerSpawnPos = self.origin; + spawnPoint.notTI = true; + self.setSpawnPoint = spawnPoint; + + // globallogic_spawn::spawnPlayer() calls loadout::giveLoadout() passing the player's class + // save their chosen class and override their current and last class + // - both so killstreaks don't get reset + // - this is automatically set back to chosen class within loadout::giveLoadout() + self.gamemode_chosenClass = self.curClass; + self.pers["class"] = "gamemode"; + self.pers["lastClass"] = "gamemode"; + self.curClass = "gamemode"; + self.lastClass = "gamemode"; + + // attach flag after faux spawn (model may change for sniper or juggernaut loadout) + self thread waitAttachFlag(); +} + + +function waitAttachFlag() +{ + level endon( "game_ende" ); + self endon( "disconnect" ); + self endon( "death" ); + + self waittill( "spawned_player" ); + self attachFlag(); +} + + +function watchForEndGame() +{ + self endon( "dropped_flag" ); + self endon( "disconnect" ); + + level waittill( "game_ended" ); + + if( isdefined( self ) ) + { + if( isdefined( self.tdef_flagTime ) ) + { + flagTime = int( GetTime() - self.tdef_flagTime ); + self AddPlayerStat( "HOLDINGTEAMDEFENDERFLAG", flagTime ); + + if ( ( flagTime/100 ) / 60 < 1 ) + flagMinutes = 0; + else + flagMinutes = int( (flagTime/100)/60 ); + + self AddPlayerStatWithGameType( "DESTRUCTIONS", flagMinutes ); + } + } +} + + +function canCreateFlagAtVictimOrigin( victim ) +{ + mineTriggers = getEntArray( "minefield", "targetname" ); + hurtTriggers = getEntArray( "trigger_hurt", "classname" ); + radTriggers = getEntArray( "radiation", "targetname" ); + + for ( index = 0; index < radTriggers.size; index++ ) + { + if ( victim isTouching( radTriggers[index] ) ) + return false; + } + + for ( index = 0; index < mineTriggers.size; index++ ) + { + if ( victim isTouching( mineTriggers[index] ) ) + return false; + } + + for ( index = 0; index < hurtTriggers.size; index++ ) + { + if ( victim isTouching( hurtTriggers[index] ) ) + return false; + } + + return true; +} + + +function createFlag( victim ) +{ + // flag + visuals[0] = spawn( "script_model", victim.origin ); + visuals[0] setModel( level.carryFlag["neutral"] ); + + // trigger + trigger = spawn( "trigger_radius", victim.origin, 0, 96, 72); + + gameFlag = gameobjects::create_carry_object( "neutral", trigger, visuals, (0,0,85) ); + gameFlag gameobjects::allow_carry( "any" ); + + gameFlag gameobjects::set_visible_team( "any" ); + gameFlag gameobjects::set_2d_icon( "enemy", level.iconCaptureFlag2D ); + gameFlag gameobjects::set_3d_icon( "enemy", level.iconCaptureFlag3D ); + gameFlag gameobjects::set_2d_icon( "friendly", level.iconCaptureFlag2D ); + gameFlag gameobjects::set_3d_icon( "friendly", level.iconCaptureFlag3D ); + + gameFlag gameobjects::set_carry_icon( level.icon2D["axis"] ); //temp, manually changed after picked up + + gameFlag.allowWeapons = true; + gameFlag.onPickup =&onPickup; + gameFlag.onPickupFailed =&onPickup; + gameFlag.onDrop =&onDrop; + + gameFlag.oldRadius = 96; + gameFlag.currentTeam = "none"; + gameFlag.requiresLOS = true; + + // set it as flag trigger when on ground + level.favorCloseSpawnEnt = gameFlag.trigger; + level.favorCloseSpawnScalar = 3; + + // for this mode, the flag's home position is wherever its last safe position was, not where it was initially created + gameFlag thread updateBasePosition(); + + return gameFlag; +} + + +function updateBasePosition() +{ + level endon( "game_ended" ); + + while( true ) + { + if ( isdefined( self.safeOrigin ) ) + { + self.baseOrigin = self.safeOrigin; + self.trigger.baseOrigin = self.safeOrigin; + self.visuals[0].baseOrigin = self.safeOrigin; + } + {wait(.05);}; + } +} + + +function attachFlag() +{ + self attach( level.carryFlag[self.team], "J_spine4", true ); + self.carryFlag = level.carryFlag[self.team]; + + // set it as flag carrier when carried + level.favorCloseSpawnEnt = self; +} + + +function detachFlag() +{ + self detach( self.carryFlag, "J_spine4" ); + self.carryFlag = undefined; + + // set it as flag trigger when on ground + level.favorCloseSpawnEnt = level.gameFlag.trigger; +} + + +function flagAttachRadar( team ) +{ + level endon("game_ended"); + self endon( "dropped" ); +} + + +function getFlagRadarOwner( team ) +{ + level endon("game_ended"); + self endon( "dropped" ); + + while ( true ) + { + foreach( player in level.players ) + { + if ( isAlive( player ) && player.team == team ) + return player; + } + {wait(.05);}; + } +} + + +function flagRadarMover() +{ + level endon("game_ended"); + self endon( "dropped" ); + self.portable_radar endon( "death" ); + + for( ;; ) + { + self.portable_radar MoveTo( self.currentCarrier.origin, .05 ); + {wait(.05);}; + } +} + + +function flagWatchRadarOwnerLost() +{ + level endon("game_ended"); + self endon( "dropped" ); + + radarTeam = self.portable_radar.team; + + self.portable_radar.owner util::waittill_any( "disconnect", "joined_team", "joined_spectators" ); + + // make a new one + flagAttachRadar( radarTeam ); +} + +function onRoundEndGame( roundWinner ) +{ + winner = globallogic::determineTeamWinnerByGameStat( "roundswon" ); + + return winner; +} + +function onSpawnPlayer(predictedSpawn) +{ + self.usingObj = undefined; + + if ( level.useStartSpawns && !level.inGracePeriod ) + { + level.useStartSpawns = false; + } + + spawning::onSpawnPlayer(predictedSpawn); +} + +function onRoundSwitch() +{ + game["switchedsides"] = !game["switchedsides"]; +} + +function initGametypeAwards() +{ +} + + +function setSpecialLoadouts() +{ +} \ No newline at end of file diff --git a/mp/gametypes/tdm.csc b/mp/gametypes/tdm.csc new file mode 100644 index 0000000..81d1649 --- /dev/null +++ b/mp/gametypes/tdm.csc @@ -0,0 +1,15 @@ +#using scripts\codescripts\struct; + + + +function main() +{ +} + +function onPrecacheGameType() +{ +} + +function onStartGameType() +{ +} diff --git a/mp/gametypes/tdm.gsc b/mp/gametypes/tdm.gsc new file mode 100644 index 0000000..e12e35a --- /dev/null +++ b/mp/gametypes/tdm.gsc @@ -0,0 +1,318 @@ +#using scripts\shared\gameobjects_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; + + + +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\gametypes\_dogtags; + +#using scripts\mp\_teamops; + +#using scripts\mp\_util; + +/* + TDM - Team Deathmatch + Objective: Score points for your team by eliminating players on the opposing team + Map ends: When one team reaches the score limit, or time limit is reached + Respawning: No wait / Near teammates + + Level requirements + ------------------ + Spawnpoints: + classname mp_tdm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of teammates and enemies + at the time of spawn. Players generally spawn behind their teammates relative to the direction of enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. + + Level script requirements + ------------------------- + Team Definitions: + game["allies"] = "marines"; + game["axis"] = "nva"; + game["team3"] = "guys_who_hate_both_other_teams"; + This sets the nationalities of the teams. Allies can be american, british, or russian. Axis can be german. + + If using minefields or exploders: + load::main(); + + Optional level script settings + ------------------------------ + Soldier Type and Variation: + game["soldiertypeset"] = "seals"; + This sets what character models are used for each nationality on a particular map. + + Valid settings: + soldiertypeset seals +*/ + +/*QUAKED mp_tdm_spawn (0.0 0.0 1.0) (-16 -16 0) (16 16 72) +Players spawn away from enemies and near their team at one of these positions.*/ + +/*QUAKED mp_tdm_spawn_axis_start (0.5 0.0 1.0) (-16 -16 0) (16 16 72) +Axis players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_tdm_spawn_allies_start (0.0 0.5 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_tdm_spawn_team1_start (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_tdm_spawn_team2_start (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_tdm_spawn_team3_start (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_tdm_spawn_team4_start (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_tdm_spawn_team5_start (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +/*QUAKED mp_tdm_spawn_team6_start (0.5 0.5 1.0) (-16 -16 0) (16 16 72) +Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ + +#precache( "string", "OBJECTIVES_TDM" ); +#precache( "string", "OBJECTIVES_TDM_SCORE" ); +#precache( "string", "OBJECTIVES_TDM_HINT" ); + +function main() +{ + globallogic::init(); + + util::registerRoundSwitch( 0, 9 ); + util::registerTimeLimit( 0, 1440 ); + util::registerScoreLimit( 0, 50000 ); + util::registerRoundLimit( 0, 10 ); + util::registerRoundWinLimit( 0, 10 ); + util::registerNumLives( 0, 100 ); + + globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 ); + + level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false ); + level.teamScorePerKill = GetGametypeSetting( "teamScorePerKill" ); + level.teamScorePerDeath = GetGametypeSetting( "teamScorePerDeath" ); + level.teamScorePerHeadshot = GetGametypeSetting( "teamScorePerHeadshot" ); + level.killstreaksGiveGameScore = GetGametypeSetting( "killstreaksGiveGameScore" ); + level.teamBased = true; + level.overrideTeamScore = true; + level.onStartGameType =&onStartGameType; + level.onSpawnPlayer =&onSpawnPlayer; + level.onRoundEndGame =&onRoundEndGame; + level.onRoundSwitch =&onRoundSwitch; + level.onPlayerKilled =&onPlayerKilled; + + gameobjects::register_allowed_gameobject( level.gameType ); + + globallogic_audio::set_leader_gametype_dialog ( "startTeamDeathmatch", "hcStartTeamDeathmatch", "gameBoost", "gameBoost" ); + + // Sets the scoreboard columns and determines with data is sent across the network + globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "kdratio", "assists" ); +} + +function onStartGameType() +{ + setClientNameMode("auto_change"); + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + level.displayRoundEndText = false; + + // now that the game objects have been deleted place the influencers + spawning::create_map_placed_influencers(); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + + foreach( team in level.teams ) + { + util::setObjectiveText( team, &"OBJECTIVES_TDM" ); + util::setObjectiveHintText( team, &"OBJECTIVES_TDM_HINT" ); + + if ( level.splitscreen ) + { + util::setObjectiveScoreText( team, &"OBJECTIVES_TDM" ); + } + else + { + util::setObjectiveScoreText( team, &"OBJECTIVES_TDM_SCORE" ); + } + + spawnlogic::add_spawn_points( team, "mp_tdm_spawn" ); + + + spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) ); + } + + spawning::updateAllSpawnPoints(); + + level.spawn_start = []; + + foreach( team in level.teams ) + { + level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array( spawning::getTDMStartSpawnName(team) ); + } + + level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + spawnpoint = spawnlogic::get_random_intermission_point(); + setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles ); + + + + //removed action loop -CDC + level thread onScoreCloseMusic(); + + if ( !util::isOneRound() ) + { + level.displayRoundEndText = true; + if( level.scoreRoundWinBased ) + { + globallogic_score::resetTeamScores(); + } + } + + if( ( isdefined( level.droppedTagRespawn ) && level.droppedTagRespawn ) ) + level.numLives = 1; +} + +function onSpawnPlayer(predictedSpawn) +{ + self.usingObj = undefined; + + if ( level.useStartSpawns && !level.inGracePeriod && !level.playerQueuedRespawn ) + { + level.useStartSpawns = false; + } + + spawning::onSpawnPlayer(predictedSpawn); +} + +function onEndGame( winningTeam ) +{ + if ( isdefined( winningTeam ) && isdefined( level.teams[winningTeam] ) ) + globallogic_score::giveTeamScoreForObjective( winningTeam, 1 ); +} + +function onRoundSwitch() +{ + game["switchedsides"] = !game["switchedsides"]; + + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, game["roundswon"][team] ); + } + } +} + +function onRoundEndGame( roundWinner ) +{ + if ( level.scoreRoundWinBased ) + { + foreach( team in level.teams ) + { + [[level._setTeamScore]]( team, game["roundswon"][team] ); + } + } + + return [[level.determineWinner]](); +} + +function onScoreCloseMusic() +{ + teamScores = []; + + while( !level.gameEnded ) + { + scoreLimit = level.scoreLimit; + scoreThreshold = scoreLimit * .1; + scoreThresholdStart = abs(scoreLimit - scoreThreshold); + scoreLimitCheck = scoreLimit - 10; + + topScore = 0; + runnerUpScore = 0; + foreach( team in level.teams ) + { + score = [[level._getTeamScore]]( team ); + + if ( score > topScore ) + { + runnerUpScore = topScore; + topScore = score; + } + else if ( score > runnerUpScore ) + { + runnerUpScore = score; + } + } + + scoreDif = (topScore - runnerUpScore); + +// if ( scoreDif <= scoreThreshold && scoreThresholdStart <= topScore ) +// { +// //play some action music and break the loop +// thread globallogic_audio::set_music_on_team( "timeOut" ); +// return; +// } + + if( topScore >= scoreLimit*.5) + { + level notify( "sndMusicHalfway" ); + return; + } + + wait(1); + } +} + +function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + if( ( isdefined( level.droppedTagRespawn ) && level.droppedTagRespawn ) ) + { + thread dogtags::checkAllowSpectating(); + + should_spawn_tags = self dogtags::should_spawn_tags(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + + // we should spawn tags if one the previous statements were true and we may not spawn + should_spawn_tags = should_spawn_tags && !globallogic_spawn::maySpawn(); + + if( should_spawn_tags ) + level thread dogtags::spawn_dog_tag( self, attacker, &dogtags::onUseDogTag, false ); + } + + if ( isPlayer( attacker ) == false || attacker.team == self.team ) + return; + + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + { + attacker globallogic_score::giveTeamScoreForObjective( attacker.team, level.teamScorePerKill ); + self globallogic_score::giveTeamScoreForObjective( self.team, level.teamScorePerDeath * -1 ); + if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + { + attacker globallogic_score::giveTeamScoreForObjective( attacker.team, level.teamScorePerHeadshot ); + } + } +} diff --git a/mp/killstreaks/_ai_tank.csc b/mp/killstreaks/_ai_tank.csc new file mode 100644 index 0000000..e60036c --- /dev/null +++ b/mp/killstreaks/_ai_tank.csc @@ -0,0 +1,197 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; + + + + +#using scripts\shared\visionset_mgr_shared; + +#using scripts\mp\_util; +#using scripts\mp\_vehicle; + +#precache( "client_fx", "killstreaks/fx_agr_emp_stun" ); + +#using_animtree ( "mp_vehicles" ); + +#namespace ai_tank; + +function autoexec __init__sytem__() { system::register("ai_tank",&__init__,undefined,undefined); } + +function __init__() +{ + bundle = struct::get_script_bundle( "killstreak", "killstreak_ai_tank_drop" ); + level.aiTankKillstreakBundle = bundle; + + level._ai_tank_fx = []; + level._ai_tank_fx[ "light_green" ] = "killstreaks/fx_agr_vlight_eye_grn"; + level._ai_tank_fx[ "light_red" ] = "killstreaks/fx_agr_vlight_eye_red"; + level._ai_tank_fx[ "stun" ] = "killstreaks/fx_agr_emp_stun"; + + clientfield::register( "vehicle", "ai_tank_death", 1, 1, "int",&death, !true, !true ); + clientfield::register( "vehicle", "ai_tank_missile_fire", 1, 2, "int",&missile_fire, !true, !true ); + clientfield::register( "vehicle", "ai_tank_stun", 1, 1, "int", &tank_stun, !true, !true ); + clientfield::register( "toplayer", "ai_tank_update_hud", 1, 1, "counter", &update_hud, !true, !true ); + + vehicle::add_vehicletype_callback( "ai_tank_drone_mp", &spawned ); + vehicle::add_vehicletype_callback( "spawner_bo3_ai_tank_mp", &spawned ); + vehicle::add_vehicletype_callback( "spawner_bo3_ai_tank_mp_player", &spawned ); + + visionset_mgr::register_visionset_info( "agr_visionset", 1, 16, undefined, "mp_vehicles_agr" ); +} + +function spawned( localClientNum, killstreak_duration ) +{ + self thread play_driving_rumble( localClientNum ); + self.killstreakBundle = level.aiTankKillstreakBundle; +} + +function missile_fire( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self endon( "entityshutdown" ); + self util::waittill_dobj( localClientNum ); + + if ( self HasAnimTree() == false ) + self UseAnimTree( #animtree ); + + missiles_loaded = newVal; + + if ( newVal == 2 ) + { + self SetAnimRestart( %o_drone_tank_missile1_fire, 1.0, 0.0, 0.5 ); + } + else if ( newVal == 1 ) + { + self SetAnimRestart( %o_drone_tank_missile2_fire, 1.0, 0.0, 0.5 ); + } + else if ( newVal == 0 ) + { + self SetAnimRestart( %o_drone_tank_missile3_fire, 1.0, 0.0, 0.5 ); + } + else if ( newVal == 3 ) + { + self SetAnimRestart( %o_drone_tank_missile_full_reload, 1.0, 0.0, 1.0 ); + } + + if ( missiles_loaded <= ( 3 ) ) + update_ui_ammo_count( localClientNum, missiles_loaded ); +} + +function update_hud( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) // self == player +{ + self endon( "disconnect" ); + + {wait(.016);}; + + vehicle = GetPlayerVehicle( self ); + if ( isdefined( vehicle ) ) + { + self update_ui_model_ammo_count( localClientNum, vehicle clientfield::get( "ai_tank_missile_fire" ) ); + } +} + +function update_ui_ammo_count( localClientNum, missiles_loaded ) // self == vehicle +{ + if ( self isLocalClientDriver( localClientNum ) || IsSpectating( localClientNum ) ) + { + update_ui_model_ammo_count( localclientnum, missiles_loaded ); + } +} + +function update_ui_model_ammo_count( localClientNum, missiles_loaded ) +{ + ammo_ui_data_model = GetUIModel( GetUIModelForController( localClientNum ), "vehicle.ammo" ); + + if ( isdefined( ammo_ui_data_model ) ) + SetUIModelValue( ammo_ui_data_model, missiles_loaded ); + + // /# IPrintLnBold( "Missile Count: " + missiles_loaded ); #/ +} + + +function tank_stun( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self endon( "entityshutdown" ); + self endon( "death" ); + + if ( newVal ) + { + self notify( "light_disable" ); + + self stop_stun_fx( localClientNum ); + self start_stun_fx( localClientNum ); + } + else + { + self stop_stun_fx( localClientNum ); + } +} + +function death( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + player = GetLocalPlayer( localClientNum ); + + if ( !isdefined( player ) ) + { + return; + } + + if ( player GetInKillcam( localClientNum ) ) + { + return; + } + + if ( newVal ) + { + self stop_stun_fx( localClientNum ); + self notify( "light_disable" ); + } +} + +function start_stun_fx( localClientNum ) +{ + self.stun_fx = PlayFxOnTag( localClientNum, level._ai_tank_fx[ "stun" ], self, "tag_origin" ); + PlaySound( localClientNum, "veh_talon_shutdown", self.origin ); +} + +function stop_stun_fx( localClientNum ) +{ + if ( isdefined( self.stun_fx ) ) + { + StopFx( localClientNum, self.stun_fx ); + self.stun_fx = undefined; + } +} + +function play_driving_rumble( localClientNum ) +{ + self notify( "driving_rumble" ); + + self endon( "entityshutdown" ); + self endon( "death" ); + self endon( "driving_rumble" ); + + for ( ;; ) + { + if ( IsInVehicle( localClientNum, self ) ) + { + speed = self GetSpeed(); + + if ( speed >= 40 || speed <= -40 ) + { + player = GetLocalPlayer( localClientNum ); + + if ( isdefined( player ) ) + { + player Earthquake( 0.1, 0.1, self.origin, 200 ); + } + } + } + + util::server_wait( localClientNum, 0.05 ); + } +} diff --git a/mp/killstreaks/_ai_tank.gsc b/mp/killstreaks/_ai_tank.gsc new file mode 100644 index 0000000..503327c --- /dev/null +++ b/mp/killstreaks/_ai_tank.gsc @@ -0,0 +1,1485 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\_oob; +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\hud_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weapons; +#using scripts\shared\vehicle_ai_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\vehicles\_amws; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_dev; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\killstreaks\_dogs; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_remote_weapons; +#using scripts\mp\killstreaks\_satellite; +#using scripts\mp\killstreaks\_supplydrop; +#using scripts\mp\killstreaks\_uav; +#using scripts\shared\visionset_mgr_shared; + + + + + + + + + + + + + + + + + + + + + + + + + + +#precache( "string", "KILLSTREAK_EARNED_AI_TANK_DROP" ); +#precache( "string", "KILLSTREAK_AI_TANK_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_AI_TANK_INBOUND" ); +#precache( "string", "KILLSTREAK_AI_TANK_HACKED" ); +#precache( "string", "KILLSTREAK_DESTROYED_AI_TANK" ); +#precache( "string", "mpl_killstreak_ai_tank" ); +#precache( "triggerstring", "MP_REMOTE_USE_TANK" ); +#precache( "fx", "killstreaks/fx_agr_emp_stun" ); +#precache( "fx", "killstreaks/fx_agr_rocket_flash_1p" ); +#precache( "fx", "killstreaks/fx_agr_rocket_flash_3p" ); +#precache( "fx", "killstreaks/fx_agr_damage_state" ); +#precache( "fx", "killstreaks/fx_agr_explosion" ); +#precache( "fx", "killstreaks/fx_agr_drop_box" ); + +#using_animtree ( "mp_vehicles" ); + +#namespace ai_tank; + +function init() +{ + bundle = struct::get_script_bundle( "killstreak", "killstreak_" + "ai_tank_drop" ); + + level.ai_tank_minigun_flash_3p = "killstreaks/fx_agr_rocket_flash_3p"; + + killstreaks::register( "ai_tank_drop", "ai_tank_marker", "killstreak_ai_tank_drop", "ai_tank_drop_used",&useKillstreakAITankDrop ); + + killstreaks::register_alt_weapon( "ai_tank_drop", "amws_gun_turret" ); + killstreaks::register_alt_weapon( "ai_tank_drop", "amws_launcher_turret" ); + killstreaks::register_alt_weapon( "ai_tank_drop", "amws_gun_turret_mp_player" ); + killstreaks::register_alt_weapon( "ai_tank_drop", "amws_launcher_turret_mp_player" ); + + killstreaks::register_remote_override_weapon( "ai_tank_drop", "killstreak_ai_tank" ); + killstreaks::register_strings( "ai_tank_drop", &"KILLSTREAK_EARNED_AI_TANK_DROP", &"KILLSTREAK_AI_TANK_NOT_AVAILABLE", &"KILLSTREAK_AI_TANK_INBOUND", undefined, &"KILLSTREAK_AI_TANK_HACKED" ); + killstreaks::register_dialog( "ai_tank_drop", "mpl_killstreak_ai_tank", "aiTankDialogBundle", "aiTankPilotDialogBundle", "friendlyAiTank", "enemyAiTank", "enemyAiTankMultiple", "friendlyAiTankHacked", "enemyAiTankHacked", "requestAiTank", "threatAiTank" ); + killstreaks::devgui_scorestreak_command( "ai_tank_drop", "Debug Routes", "set devgui_tank routes"); + + // TODO: Move to killstreak data + level.killstreaks["ai_tank_drop"].threatOnKill = true; + + remote_weapons::RegisterRemoteWeapon( "killstreak_ai_tank", &"MP_REMOTE_USE_TANK", &startTankRemoteControl, &endTankRemoteControl, false ); + + level.ai_tank_fov = Cos( 160 ); + level.ai_tank_turret_weapon = GetWeapon( "ai_tank_drone_gun" ); + level.ai_tank_turret_fire_rate = level.ai_tank_turret_weapon.fireTime; + level.ai_tank_remote_weapon = GetWeapon( "killstreak_ai_tank" ); + + spawns = spawnlogic::get_spawnpoint_array( "mp_tdm_spawn" ); + + level.ai_tank_damage_fx = "killstreaks/fx_agr_damage_state"; + level.ai_tank_explode_fx = "killstreaks/fx_agr_explosion"; + level.ai_tank_crate_explode_fx = "killstreaks/fx_agr_drop_box"; + + anims = []; + anims[ anims.size ] = %o_drone_tank_missile1_fire; + anims[ anims.size ] = %o_drone_tank_missile2_fire; + anims[ anims.size ] = %o_drone_tank_missile3_fire; + anims[ anims.size ] = %o_drone_tank_missile_full_reload; + + if(!isdefined(bundle.ksMainTurretRecoilForceZOffset))bundle.ksMainTurretRecoilForceZOffset=0; + if(!isdefined(bundle.ksWeaponReloadTime))bundle.ksWeaponReloadTime=0.5; + + visionset_mgr::register_info( "visionset", "agr_visionset", 1, 80, 16, true, &visionset_mgr::ramp_in_out_thread_per_player_death_shutdown, false ); + +/# + level thread tank_devgui_think(); +#/ + thread register(); +} + +function register() +{ + clientfield::register( "vehicle", "ai_tank_death", 1, 1, "int" ); + clientfield::register( "vehicle", "ai_tank_missile_fire", 1, 2, "int" ); + clientfield::register( "vehicle", "ai_tank_stun", 1, 1, "int" ); + clientfield::register( "toplayer", "ai_tank_update_hud", 1, 1, "counter" ); +} + +function useKillstreakAITankDrop(hardpointType) +{ + team = self.team; + + if( !self supplydrop::isSupplyDropGrenadeAllowed( hardpointType ) ) + { + return false; + } + + killstreak_id = self killstreakrules::killstreakStart( hardpointType, team, false, false ); + if ( killstreak_id == -1 ) + { + return false; + } + + context = SpawnStruct(); + if ( !isdefined( context ) ) + { + killstreak_stop_and_assert( hardpointType, team, killstreak_id, "Failed to spawn struct for ai tank." ); + return false; + } + + context.radius = level.killstreakCoreBundle.ksAirdropAITankRadius; + context.dist_from_boundary = 16; + context.max_dist_from_location = 4; + context.perform_physics_trace = true; + context.check_same_floor = true; + context.isLocationGood = &is_location_good; + context.objective = &"airdrop_aitank"; + context.killstreakRef = hardpointType; + context.validLocationSound = level.killstreakCoreBundle.ksValidAITankLocationSound; + context.tracemask = (1 << 0) | (1 << 2); + context.dropTag = "tag_attach"; + context.dropTagOffset = ( -35, 0, 10 ); + + result = self supplydrop::useSupplyDropMarker( killstreak_id, context ); + + // the marker is out but the chopper is yet to come + self notify( "supply_drop_marker_done" ); + + if ( !isdefined(result) || !result ) + { + //if( !self.supplyGrenadeDeathDrop ) + killstreakrules::killstreakStop( hardpointType, team, killstreak_id ); + return false; + } + + self killstreaks::play_killstreak_start_dialog( "ai_tank_drop", self.team, killstreak_id ); + self killstreakrules::displayKillstreakStartTeamMessageToAll( "ai_tank_drop" ); + self AddWeaponStat( GetWeapon( "ai_tank_marker" ), "used", 1 ); + + return result; +} + +function crateLand( crate, category, owner, team, context ) +{ + // note: original context is being changed here + context.perform_physics_trace = false; + context.dist_from_boundary = 24; + context.max_dist_from_location = 96; + + if ( !crate is_location_good( crate.origin, context ) || !isdefined( owner ) || team != owner.team || ( owner EMP::EnemyEMPActive() && !owner hasperk("specialty_immuneemp") ) ) + { + killstreakrules::killstreakStop( category, team, crate.package_contents_id ); + wait( 10 ); + + if ( isdefined( crate ) ) + crate delete(); + + return; + } + + origin = crate.origin; + + crateBottom = BulletTrace( origin, origin + (0, 0, -50), false, crate ); + if ( isdefined( crateBottom ) ) + { + origin = crateBottom["position"] + (0,0,1); + } + + PlayFX( level.ai_tank_crate_explode_fx, origin, (1, 0, 0), (0, 0, 1) ); + PlaySoundAtPosition( "veh_talon_crate_exp", crate.origin ); + + level thread ai_tank_killstreak_start( owner, origin, crate.package_contents_id, category ); + + crate delete(); +} + +function is_location_good( location, context ) +{ + return supplydrop::IsLocationGood( location, context ) && valid_location( location ); +} + +function valid_location( location ) +{ + if ( !isdefined( location ) ) + location = self.origin; + + // only do this check if we are not the player, intended for deploy box only + if ( !isPlayer( self ) ) + { + start = self GetCentroid(); + end = location + ( 0, 0, 16 ); + + trace = PhysicsTrace( start, end, ( 0, 0, 0 ), ( 0, 0, 0 ), self, (1 << 4) ); + + if ( trace["fraction"] < 1 ) + return false; + } + + if( self oob::IsTouchingAnyOOBTrigger() ) + { + return false; + } + + return true; +} + +function HackedCallbackPre( hacker ) +{ + drone = self; + drone clientfield::set( "enemyvehicle", 2 ); + drone.owner stop_remote(); + drone.owner clientfield::set_to_player( "static_postfx", 0 ); + if( drone.controlled === true ) + visionset_mgr::deactivate( "visionset", "agr_visionset", drone.owner ); + drone.owner remote_weapons::RemoveAndAssignNewRemoteControlTrigger( drone.useTrigger ); + drone remote_weapons::EndRemoteControlWeaponUse( true ); + drone.owner unlink(); + drone clientfield::set( "vehicletransition", 0 ); +} + +function HackedCallbackPost( hacker ) +{ + drone = self; + + hacker remote_weapons::UseRemoteWeapon( drone, "killstreak_ai_tank", false ); + drone notify("WatchRemoteControlDeactivate_remoteWeapons"); + drone.killstreak_end_time = hacker killstreak_hacking::set_vehicle_drivable_time_starting_now( drone ); +} + +function ConfigureTeamPost( owner, isHacked ) +{ + drone = self; + drone thread tank_watch_owner_events(); +} + +function ai_tank_killstreak_start( owner, origin, killstreak_id, category ) +{ + team = owner.team; + + waittillframeend; + + if ( level.gameEnded ) + return; + + drone = SpawnVehicle( "spawner_bo3_ai_tank_mp", origin, (0, 0, 0), "talon" ); + + if ( !isdefined( drone ) ) + { + killstreak_stop_and_assert( category, team, killstreak_id, "Failed to spawn ai tank vehicle." ); + return; + } + + drone.settings = struct::get_script_bundle( "vehiclecustomsettings", drone.scriptbundlesettings ); + + drone.customDamageMonitor = true; // Disable the default monitor_damage_as_occupant thread + drone.avoid_shooting_owner = true; + drone.avoid_shooting_owner_ref_tag = "tag_flash_gunner1"; + + drone killstreaks::configure_team( "ai_tank_drop", killstreak_id, owner, "small_vehicle", undefined, &ConfigureTeamPost ); + drone killstreak_hacking::enable_hacking( "ai_tank_drop", &HackedCallbackPre, &HackedCallbackPost ); + + drone killstreaks::setup_health( "ai_tank_drop", 1500, 0 ); + drone.original_vehicle_type = drone.vehicletype; + + drone clientfield::set( "enemyvehicle", 1 ); + drone SetVehicleAvoidance( true ); + drone clientfield::set( "ai_tank_missile_fire", ( 3 ) ); + drone.killstreak_id = killstreak_id; + drone.type = "tank_drone"; + drone.dontDisconnectPaths = 1; + drone.isStunned = false; + drone.soundmod = "drone_land"; + drone.ignore_vehicle_underneath_splash_scalar = true; + drone.treat_owner_damage_as_friendly_fire = true; + drone.ignore_team_kills = true; + + drone.controlled = false; + drone MakeVehicleUnusable(); + + drone.numberRockets = ( 3 ); + drone.warningShots = 3; + drone SetDrawInfrared( true ); + + //set up number for this drone + if (!isdefined(drone.owner.numTankDrones)) + drone.owner.numTankDrones=1; + else + drone.owner.numTankDrones++; + drone.ownerNumber = drone.owner.numTankDrones; + + // make the drone targetable + Target_Set( drone, (0,0,20) ); + Target_SetTurretAquire( drone, false ); + + // setup target group for missile lock on monitoring + drone vehicle::init_target_group(); + drone vehicle::add_to_target_group( drone ); + + drone setup_gameplay_think( category ); + + drone.killstreak_end_time = GetTime() + ( 120 * 1000 ); + + owner remote_weapons::UseRemoteWeapon( drone, "killstreak_ai_tank", false ); + + drone thread kill_monitor(); + drone thread deleteOnKillbrush( drone.owner ); + drone thread tank_rocket_watch_ai(); + level thread tank_game_end_think(drone); + +/# + drone thread tank_think_debug(); +#/ + +/# + //drone thread tank_debug_health(); +#/ +} + +function get_vehicle_name( vehicle_version ) +{ + switch( vehicle_version ) + { + case 2: + default: + return "spawner_bo3_ai_tank_mp"; + break; + + case 1: + return "ai_tank_drone_mp"; + break; + } +} + +function setup_gameplay_think( category ) +{ + drone = self; + + drone thread tank_abort_think(); + drone thread tank_team_kill(); + drone thread tank_too_far_from_nav_mesh_abort_think(); + drone thread tank_death_think( category ); + drone thread tank_damage_think(); + drone thread WatchWater(); +} + + +function tank_think_debug() // self == drone +{ + self endon ( "death" ); + + server_frames_to_persist = 1; + text_scale = 0.5; + text_alpha = 1.0; + text_color = ( 1, 1, 1 ); + + while ( 1 ) + { + if ( GetDvarInt( "scr_ai_tank_think_debug" ) == 0 ) + { + wait 5; + continue; + } + + target_name = "unknown"; + target_entity = undefined; + + tank_is_idle = !isdefined( self.enemy ); // NOTE: enemy is set by code-side ai system + target_entity = self.enemy; + + if ( isdefined( target_entity ) && !tank_is_idle ) + { + if ( isdefined ( target_entity.name ) ) + { + target_name = target_entity.name; + } + else if ( isdefined ( target_entity.remotename ) ) + { + target_name = target_entity.remotename; + } + } + + target_text = ( ( tank_is_idle ) ? "Target: none" : "Target: " + target_name ); + /# Print3d( self.origin, target_text, text_color, text_alpha, text_scale, server_frames_to_persist ); #/ + + duration_text = "Duration: " + ( (self.killstreak_end_time - GetTime()) * 0.001 ); + /# Print3d( self.origin + ( 0, 0, 12 ), duration_text, text_color, text_alpha, text_scale, server_frames_to_persist ); #/ + + can_see_text = "Can see: "; + if ( tank_is_idle ) + { + can_see_text += "---"; + } + else + { + can_see_text += ( ( self VehCanSee( target_entity ) ) ? "yes" : "no" ); + } + /# Print3d( self.origin + ( 0, 0, -12 ), can_see_text, text_color, text_alpha, text_scale, server_frames_to_persist ); #/ + + movement_type_text = "Movement: "; + if ( isdefined( self.debug_ai_movement_type ) ) + { + movement_type_text += self.debug_ai_movement_type; + } + else + { + movement_type_text += "---"; + } + /# Print3d( self.origin + ( 0, 0, -24 ), movement_type_text, text_color, text_alpha, text_scale, server_frames_to_persist ); #/ + + if ( isdefined( self.debug_ai_move_to_point ) ) + { + /# util::debug_sphere( self.debug_ai_move_to_point + ( 0, 0, 16 ), 10, ( 0.1, 0.95, 0.1 ), 0.9, server_frames_to_persist ); #/ + + if ( isdefined( self.debug_ai_move_to_points_considered ) ) + { + foreach( point in self.debug_ai_move_to_points_considered ) + { + point_color = ( 0.65, 0.65, 0.65 ); // grey-ish + + if ( isdefined( point.score ) ) + { + if ( point.score != 0 ) + { + if ( point.score < 0 ) + { + point_color = ( 0.65, 0.1, 0.1 ); //dark red-ish + } + else if ( point.score > 50 ) + { + point_color = ( 0.1, 0.65, 0.1 ); // dark green-ish + } + else + { + point_color = ( 0.95, 0.95, 0.1 ); // yellow-ish + } + + score_text_scale = text_scale; + score_text_color = text_color; + if ( point.origin != self.debug_ai_move_to_point ) + { + score_text_scale *= 0.67; + } + else + { + score_text_scale *= 1.5; + score_text_color = ( 0.05, 0.98, 0.05 ); // green-ish + } + + /# Print3d( point.origin + ( 0, 0, 16 ), point.score, score_text_color, text_alpha, score_text_scale, server_frames_to_persist ); #/ + } + } + + if ( point.origin != self.debug_ai_move_to_point ) + { + /# util::debug_sphere( point.origin + ( 0, 0, 16 ), 3, point_color, 0.5, server_frames_to_persist ); #/ + } + } + } + } + + {wait(.05);}; + } +} + +function tank_team_kill() +{ + self endon( "death" ); + self.owner waittill( "teamKillKicked" ); + self notify ( "death" ); +} + +function kill_monitor() +{ + self endon( "death" ); + + last_kill_vo = 0; + kill_vo_spacing = 4000; + + while(1) + { + self waittill( "killed", victim ); + + if ( !isdefined( self.owner ) || !isdefined( victim ) ) + continue; + + if ( self.owner == victim ) + continue; + + if ( level.teamBased && self.owner.team == victim.team ) + continue; + + if ( !self.controlled && last_kill_vo + kill_vo_spacing < GetTime() ) + { + self killstreaks::play_pilot_dialog_on_owner( "kill", "ai_tank_drop", self.killstreak_id ); + + last_kill_vo = GetTime(); + } + } +} + +function tank_abort_think() +{ + tank = self; + + tank thread killstreaks::WaitForTimeout( "ai_tank_drop", ( 120 * 1000 ), &tank_timeout_callback, "death", "emp_jammed" ); +} + +function tank_timeout_callback() +{ + self killstreaks::play_pilot_dialog_on_owner( "timeout", "ai_tank_drop" ); + + self.timed_out = true; + + self notify( "death" ); +} + +function tank_watch_owner_events() +{ + self notify( "tank_watch_owner_events_singleton" ); + self endon ( "tank_watch_owner_events_singleton" ); + self endon( "death" ); + + self.owner util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); + + self MakeVehicleUsable(); + self.controlled = false; + + if ( isdefined( self.owner ) ) + { + self.owner unlink(); + self clientfield::set( "vehicletransition", 0 ); + } + + self MakeVehicleUnusable(); + + if ( isdefined( self.owner ) && ( self.controlled === true ) ) + { + visionset_mgr::deactivate( "visionset", "agr_visionset", self.owner ); + self.owner stop_remote(); + } + + self.abandoned = true; + + self notify( "death" ); +} + +function tank_game_end_think(drone) +{ + drone endon( "death" ); + + level waittill("game_ended"); + + drone notify( "death" ); +} + + +function stop_remote() // dead +{ + if ( !isdefined( self ) ) + return; + + self killstreaks::clear_using_remote(); + self remote_weapons::destroyRemoteHUD(); + self util::clientNotify( "nofutz" ); +} + + +function tank_hacked_health_update( hacker ) +{ + tank = self; + hackedDamageTaken = tank.defaultMaxHealth - tank.hackedHealth; + assert ( hackedDamageTaken > 0 ); + if ( hackedDamageTaken > tank.damageTaken ) + { + tank.damageTaken = hackedDamageTaken; + } +} + + +function tank_damage_think() +{ + self endon( "death" ); + + assert( isdefined( self.maxhealth ) ); + self.defaultMaxHealth = self.maxhealth; + maxhealth = self.maxhealth; // actual max heath should be set now. + + self.maxhealth = 999999; + self.health = self.maxhealth; + self.isStunned = false; + + self.hackedHealthUpdateCallback = &tank_hacked_health_update; + self.hackedHealth = killstreak_bundles::get_hacked_health( "ai_tank_drop" ); + + low_health = false; + self.damageTaken = 0; + + for ( ;; ) + { + self waittill( "damage", damage, attacker, dir, point, mod, model, tag, part, weapon, flags, inflictor, chargeLevel ); + + self.maxhealth = 999999; + self.health = self.maxhealth; + +/# + self.damage_debug = ( damage + " (" + weapon.name + ")" ); +#/ + + if ( weapon.isEmp && (mod == "MOD_GRENADE_SPLASH")) + { + emp_damage_to_apply = killstreak_bundles::get_emp_grenade_damage( "ai_tank_drop", maxhealth ); + + if ( !isdefined( emp_damage_to_apply ) ) + emp_damage_to_apply = ( maxhealth / 2 ); + + self.damageTaken += emp_damage_to_apply; + damage = 0; + if ( !self.isStunned && emp_damage_to_apply > 0 ) + { + self.isStunned = true; + challenges::stunnedTankWithEMPGrenade( attacker ); + self thread tank_stun( 4 ); + } + } + + if ( !self.isStunned ) + { + if ( weapon.doStun && (mod == "MOD_GRENADE_SPLASH" || mod == "MOD_GAS") ) + { + self.isStunned = true; + self thread tank_stun( 1.5 ); + } + } + + weapon_damage = killstreak_bundles::get_weapon_damage( "ai_tank_drop", maxhealth, attacker, weapon, mod, damage, flags, chargeLevel ); + + if ( !isdefined( weapon_damage ) ) + { + if ( mod == "MOD_RIFLE_BULLET" || mod == "MOD_PISTOL_BULLET" || weapon.name == "hatchet" || (mod == "MOD_PROJECTILE_SPLASH" && weapon.bulletImpactExplode) ) + { + if ( isPlayer( attacker ) ) + if ( attacker HasPerk( "specialty_armorpiercing" ) ) + damage += int( damage * level.cac_armorpiercing_data ); + + if ( weapon.weapClass == "spread") + damage = damage * 1.5; + + weapon_damage = damage * .8; + } + + if ( ( mod == "MOD_PROJECTILE" || mod == "MOD_GRENADE_SPLASH" || mod == "MOD_PROJECTILE_SPLASH" ) && damage != 0 && !weapon.isEmp && !weapon.bulletImpactExplode) + { + weapon_damage = damage * 1; + } + + if ( !isdefined( weapon_damage ) ) + { + weapon_damage = damage; + } + } + + self.damageTaken += weapon_damage; + + if ( self.controlled ) + { + self.owner SendKillstreakDamageEvent( int( weapon_damage ) ); + self.owner vehicle::update_damage_as_occupant( self.damageTaken, maxhealth ); + } + + if ( self.damageTaken >= maxhealth ) + { + if( isdefined( self.owner ) ) + self.owner.dofutz = true; + + self.health = 0; + self notify( "death", attacker, mod, weapon ); + return; + } + + if ( !low_health && self.damageTaken > maxhealth / 1.8 ) + { + self killstreaks::play_pilot_dialog_on_owner( "damaged", "ai_tank_drop", self.killstreak_id ); + + self thread tank_low_health_fx(); + low_health = true; + } + } +} + +function tank_low_health_fx() +{ + self endon( "death" ); + + self.damage_fx = spawn( "script_model", self GetTagOrigin("tag_origin") + (0,0,-14) ); + if ( !isdefined( self.damage_fx ) ) + { + // intentionally not adding an AssertMsg() here + return; + } + + self.damage_fx SetModel( "tag_origin" ); + self.damage_fx LinkTo(self, "tag_turret", (0,0,-14), (0,0,0) ); + wait ( 0.1 ); + PlayFXOnTag( level.ai_tank_damage_fx, self.damage_fx, "tag_origin" ); +} + +function deleteOnKillbrush(player) +{ + player endon("disconnect"); + self endon("death"); + + killbrushes = GetEntArray( "trigger_hurt","classname" ); + + while(1) + { + for (i = 0; i < killbrushes.size; i++) + { + if (self istouching(killbrushes[i]) ) + { + if ( isdefined(self) ) + { + self notify( "death", self.owner ); + } + + return; + } + } + wait( 0.1 ); + } + +} + +function tank_stun( duration ) +{ + self endon( "death" ); + self notify( "stunned" ); + + self ClearVehGoalPos(); + forward = AnglesToForward( self.angles ); + forward = self.origin + forward * 128; + forward = forward - ( 0, 0, 64 ); + self SetTurretTargetVec( forward ); + self DisableGunnerFiring( 0, true ); + self LaserOff(); + + if (self.controlled) + { + self.owner FreezeControls( true ); + + self.owner SendKillstreakDamageEvent( 400 ); + } + if (isdefined(self.owner.fullscreen_static)) + { + self.owner thread remote_weapons::stunStaticFX( duration ); + } + + self clientfield::set( "ai_tank_stun", 1 ); + + if( self.controlled ) + self.owner clientfield::set_to_player( "static_postfx", 1 ); + + wait ( duration ); + + self clientfield::set( "ai_tank_stun", 0 ); + + if( self.controlled ) + self.owner clientfield::set_to_player( "static_postfx", 0 ); + + if (self.controlled) + { + self.owner FreezeControls( false ); + } + + self DisableGunnerFiring( 0, false ); + self.isStunned = false; +} + +function emp_crazy_death() +{ + self clientfield::set( "ai_tank_stun", 1 ); + self notify ("death"); + + time = 0; + randomAngle = RandomInt(360); + while (time < 1.45) + { + self SetTurretTargetVec(self.origin + AnglesToForward((RandomIntRange(305, 315), int((randomAngle + time * 180)), 0)) * 100); + if (time > 0.2) + { + self FireWeapon( 1 ); + if ( RandomInt(100) > 85) + { + rocket = self FireWeapon( 0 ); + + if ( isdefined( rocket ) ) + { + rocket.from_ai = true; + } + } + } + time += 0.05; + {wait(.05);}; + } + self clientfield::set( "ai_tank_death", 1 ); + + PlayFX( level.ai_tank_explode_fx, self.origin, (0, 0, 1) ); + PlaySoundAtPosition( "wpn_agr_explode", self.origin ); + {wait(.05);}; + self hide(); +} + +function tank_death_think( hardpointName ) +{ + team = self.team; + killstreak_id = self.killstreak_id; + + self waittill( "death", attacker, damageFromUnderneath, weapon ); + // self waittill( "death", attacker, damageFromUnderneath, weapon, point, dir, modType ); + + if ( !isdefined( self ) ) + { + killstreak_stop_and_assert( hardpointName, team, killstreak_id, "Failed to handle death. A." ); + return; + } + + self.dead = true; + self LaserOff(); + + self ClearVehGoalPos(); + + not_abandoned = ( !isdefined( self.abandoned ) || !self.abandoned ); + + if ( self.controlled == true ) + { + self.owner SendKillstreakDamageEvent( 600 ); + self.owner remote_weapons::destroyRemoteHUD(); + } + + self clientfield::set( "ai_tank_death", 1 ); + stunned = false; + + settings = self.settings; + + if ( isdefined( settings ) && ( self.timed_out === true || self.abandoned === true ) ) + { + fx_origin = self GetTagOrigin( (isdefined(settings.timed_out_death_tag_1)?settings.timed_out_death_tag_1:"tag_origin") ); + PlayFx( (isdefined(settings.timed_out_death_fx_1)?settings.timed_out_death_fx_1:level.ai_tank_explode_fx), (isdefined(fx_origin)?fx_origin:self.origin), ( 0, 0, 1 ) ); + PlaySoundAtPosition( (isdefined(settings.timed_out_death_sound_1)?settings.timed_out_death_sound_1:"wpn_agr_explode"), self.origin ); + } + else + { + PlayFX( level.ai_tank_explode_fx, self.origin, ( 0, 0, 1 ) ); + PlaySoundAtPosition( "wpn_agr_explode", self.origin ); + } + + + if ( not_abandoned ) + { + util::wait_network_frame(); + + if ( !isdefined( self ) ) + { + killstreak_stop_and_assert( hardpointName, team, killstreak_id, "Failed to handle death. B." ); + return; + } + } + + if ( self.controlled ) + { + self Ghost(); // keep the view for player with the dead by using ghost, otherwise, will end up at feet of player + } + else + { + self Hide(); + } + + if (isdefined(self.damage_fx)) + { + self.damage_fx delete(); + } + + attacker = self [[ level.figure_out_attacker ]]( attacker ); + + if ( isdefined( attacker ) && IsPlayer( attacker ) && isdefined( self.owner ) && attacker != self.owner ) + { + if ( self.owner util::IsEnemyPlayer( attacker ) ) + { + + scoreevents::processScoreEvent( "destroyed_aitank", attacker, self.owner, weapon ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_AI_TANK", attacker.entnum ); + attacker AddWeaponStat( weapon, "destroyed_aitank", 1 ); + controlled = false; + if ( isdefined( self.wasControlledNowDead ) && self.wasControlledNowDead ) + { + attacker AddWeaponStat( weapon, "destroyed_controlled_killstreak", 1 ); + controlled = true; + } + + attacker challenges::destroyScoreStreak( weapon, controlled, true ); + attacker challenges::destroyNonAirScoreStreak_PostStatsLock( weapon ); + attacker AddWeaponStat( weapon, "destroy_aitank_or_setinel", 1 ); + + self killstreaks::play_destroyed_dialog_on_owner( "ai_tank_drop", self.killstreak_id ); + } + else + { + //Destroyed Friendly Killstreak + } + } + + if ( not_abandoned ) + { + self util::waittill_any_timeout( 2.0, "remote_weapon_end" ); + + if ( !isdefined( self ) ) + { + killstreak_stop_and_assert( hardpointName, team, killstreak_id, "Failed to handle death. C." ); + return; + } + } + + killstreakrules::killstreakStop( hardpointName, team, self.killstreak_id ); + + if ( isdefined( self.aim_entity ) ) + self.aim_entity delete(); + + self delete(); +} + +function killstreak_stop_and_assert( hardpoint_name, team, killstreak_id, assert_msg ) +{ + killstreakrules::killstreakStop( hardpoint_name, team, killstreak_id ); + AssertMsg( assert_msg ); +} + +function tank_too_far_from_nav_mesh_abort_think() +{ + self endon( "death" ); + + not_on_nav_mesh_count = 0; + + for ( ;; ) + { + wait( 1 ); + + not_on_nav_mesh_count = ( isdefined( GetClosestPointOnNavMesh( self.origin, ( 40 * 12 ) ) ) ? 0 : not_on_nav_mesh_count + 1 ); + + if ( not_on_nav_mesh_count >= 4 ) + { + self notify( "death" ); + } + } +} + +function tank_has_radar() +{ + if ( level.teambased ) + { + return ( uav::HasUAV( self.team ) || satellite::HasSatellite( self.team ) ); + } + + return ( uav::HasUAV( self.entnum ) || satellite::HasSatellite( self.entnum ) ); +} + +function tank_get_player_enemies( on_radar ) +{ + enemies = []; + + if ( !isdefined( on_radar ) ) + { + on_radar = false; + } + + if ( on_radar ) + { + time = GetTime(); + } + + foreach( teamKey, team in level.alivePlayers ) + { + if ( level.teambased && teamKey == self.team ) + { + continue; + } + + foreach( player in team ) + { + if ( !valid_target( player, self.team, self.owner ) ) + { + continue; + } + + if ( on_radar ) + { + if ( time - player.lastFireTime > 3000 && !tank_has_radar() ) + { + continue; + } + } + + enemies[ enemies.size ] = player; + } + } + + return enemies; +} + +function tank_compute_enemy_position() +{ + enemies = tank_get_player_enemies( false ); + position = undefined; + + if ( enemies.size ) + { + x = 0; + y = 0; + z = 0; + + foreach( enemy in enemies ) + { + x += enemy.origin[0]; + y += enemy.origin[1]; + z += enemy.origin[2]; + } + + x /= enemies.size; + y /= enemies.size; + z /= enemies.size; + + position = ( x, y, z ); + } + + return position; +} + +function valid_target( target, team, owner ) +{ + if ( !isdefined( target ) ) + { + return false; + } + + if ( !IsAlive( target ) ) + { + return false; + } + + if ( target == owner ) + { + return false; + } + + if ( IsPlayer( target ) ) + { + if ( target.sessionstate != "playing" ) + { + return false; + } + + if ( isdefined( target.lastspawntime ) && GetTime() - target.lastspawntime < 3000 ) + { + return false; + } + + if ( target hasPerk( "specialty_nottargetedbyaitank" ) ) + { + return false; + } + +/# + if ( target IsInMoveMode( "ufo", "noclip" ) ) + { + return false; + } +#/ + } + + if ( level.teambased ) + { + if ( isdefined( target.team ) && team == target.team ) + { + return false; + } + } + + if ( isdefined( target.owner ) && target.owner == owner ) + { + return false; + } + + if ( isdefined( target.script_owner ) && target.script_owner == owner ) + { + return false; + } + + if ( ( isdefined( target.dead ) && target.dead ) ) + { + return false; + } + + if ( isdefined( target.targetname ) && target.targetname == "riotshield_mp" ) + { + if ( isdefined( target.damageTaken ) && target.damageTaken >= GetDvarInt( "riotshield_deployed_health" ) ) + { + return false; + } + } + + return true; +} + +function startTankRemoteControl( drone ) // self == player +{ + drone MakeVehicleUsable(); + drone ClearVehGoalPos(); + drone ClearTurretTarget(); + drone LaserOff(); + + drone.treat_owner_damage_as_friendly_fire = false; + drone.ignore_team_kills = false; + + if ( isdefined( drone.PlayerDrivenVersion ) ) + drone SetVehicleType( drone.PlayerDrivenVersion ); + + drone usevehicle( self, 0 ); + drone clientfield::set( "vehicletransition", 1 ); + + + drone MakeVehicleUnusable(); + drone SetBrake( false ); + + drone thread tank_rocket_watch( self ); + drone thread vehicle::monitor_missiles_locked_on_to_me( self ); + + self vehicle::set_vehicle_drivable_time( ( 120 * 1000 ), drone.killstreak_end_time ); + self vehicle::update_damage_as_occupant( (isdefined(drone.damageTaken)?drone.damageTaken:0), (isdefined(drone.defaultmaxhealth)?drone.defaultmaxhealth:100) ); + drone update_client_ammo( drone.numberRockets, true ); + + visionset_mgr::activate( "visionset", "agr_visionset", self, 1, 90000, 1 ); +} + +function endTankRemoteControl( drone, exitRequestedByOwner ) +{ + not_dead = !( isdefined( drone.dead ) && drone.dead ); + + if ( isdefined( drone.owner ) ) + { + drone.owner remote_weapons::destroyRemoteHUD(); + } + + drone.treat_owner_damage_as_friendly_fire = true; + drone.ignore_team_kills = true; + + if( drone.classname == "script_vehicle") + drone MakeVehicleUnusable(); + + if ( isdefined( drone.original_vehicle_type ) && not_dead ) + drone SetVehicleType( drone.original_vehicle_type ); + + if ( isdefined( drone.owner ) ) + drone.owner vehicle::stop_monitor_missiles_locked_on_to_me(); + + if( exitRequestedByOwner && not_dead ) + { + drone vehicle_ai::set_state( "combat" ); + } + + if ( drone.cobra === true && not_dead ) + drone thread amws::cobra_retract(); + + if ( isdefined( drone.owner ) && ( drone.controlled === true ) ) + visionset_mgr::deactivate( "visionset", "agr_visionset", drone.owner ); + + drone clientfield::set( "vehicletransition", 0 ); +} + +function perform_recoil_missile_turret( player ) // self == drone +{ + bundle = level.killstreakBundle["ai_tank_drop"]; + Earthquake( 0.4, 0.5, self.origin, 200 ); + self perform_recoil( "tag_barrel", ( ( ( isdefined( self.controlled ) && self.controlled ) ? bundle.ksMainTurretRecoilForceControlled : bundle.ksMainTurretRecoilForce ) ), bundle.ksMainTurretRecoilForceZOffset ); + + if ( self.controlled && isdefined( player ) ) + { + player PlayRumbleOnEntity( "sniper_fire" ); + } +} + +function perform_recoil( recoil_tag, force_scale_factor, force_z_offset ) // self == drone +{ + angles = self GetTagAngles( recoil_tag ); + dir = AnglesToForward( angles ); + self LaunchVehicle( dir * force_scale_factor, self.origin + ( 0, 0, force_z_offset ), false ); +} + +function update_client_ammo( ammo_count, driver_only_update = false ) // self == vehicle +{ + if ( !driver_only_update ) + { + self clientfield::set( "ai_tank_missile_fire", ammo_count ); + } + + if ( self.controlled ) + { + self.owner clientfield::increment_to_player( "ai_tank_update_hud", 1 ); + } +} + +function tank_rocket_watch( player ) +{ + self endon( "death" ); + player endon( "stopped_using_remote"); + + if ( self.numberRockets <= 0 ) + { + self reload_rockets( player ); + } + + if ( !self.isStunned ) + { + self DisableDriverFiring( false ); + } + + while( true ) + { + player waittill( "missile_fire", missile ); + missile.ignore_team_kills = self.ignore_team_kills; + + self.numberRockets--; + self update_client_ammo( self.numberRockets ); + + self perform_recoil_missile_turret( player ); + + if ( self.numberRockets <= 0 ) + { + self reload_rockets( player ); + } + } +} + +function tank_rocket_watch_ai() +{ + self endon( "death" ); + + while( true ) + { + self waittill( "missile_fire", missile ); + missile.ignore_team_kills = self.ignore_team_kills; + missile.killCamEnt = self; + } +} + +function reload_rockets( player ) +{ + bundle = level.killstreakBundle["ai_tank_drop"]; + self DisableDriverFiring( true ); + + // setup the "reload" time for the player's vehicle HUD + weapon_wait_duration_ms = Int( bundle.ksWeaponReloadTime * 1000 ); + player SetVehicleWeaponWaitDuration( weapon_wait_duration_ms ); + player SetVehicleWeaponWaitEndTime( GetTime() + weapon_wait_duration_ms ); + + wait ( bundle.ksWeaponReloadTime ); + + self.numberRockets = ( 3 ); + self update_client_ammo( self.numberRockets ); + + wait (0.4); + + if ( !self.isStunned ) + { + self DisableDriverFiring( false ); + } +} + + + + + + + + + + + + + +function WatchWater() +{ + self endon( "death" ); + + inWater = false; + while( !inWater ) + { + wait ( 0.3 ); + trace = physicstrace( self.origin + ( 0, 0, ( 42 ) ), self.origin + ( 0, 0, ( 12 ) ), ( -2, -2, -2 ), ( 2, 2, 2 ), self, ( (1 << 2) )); + inWater = ( trace["fraction"] < ( (( 42 ) - ( 36 )) / ( ( 42 ) - ( 12 ) ) ) && trace["fraction"] != 1.0 ); + + waterTraceDistanceFromEnd = ( ( 42 ) - ( 12 ) ) - ( trace["fraction"] * ( ( 42 ) - ( 12 ) ) ); + static_alpha = min( 1.0, waterTraceDistanceFromEnd / ( ( 36 ) - ( 12 ) ) ); + + // design does not want beeping audio for when water is an issue, maybe a different kind of audio? + if ( isdefined( self.owner ) && self.controlled ) + self.owner clientfield::set_to_player( "static_postfx", ( ( static_alpha > 0.0 ) ? 1 : 0 ) ); + } + + if( isdefined( self.owner ) ) + self.owner.dofutz = true; + + self notify( "death" ); +} + +/# +function tank_devgui_think() +{ + SetDvar( "devgui_tank", "" ); + + for ( ;; ) + { + wait( 0.25 ); + + level.ai_tank_turret_fire_rate = level.ai_tank_turret_weapon.fireTime; + + if ( GetDvarString( "devgui_tank" ) == "routes" ) + { + devgui_debug_route(); + SetDvar( "devgui_tank", "" ); + } + } +} + +function tank_debug_patrol( node1, node2 ) +{ + self endon( "death" ); + self endon( "debug_patrol" ); + + for( ;; ) + { + self SetVehGoalPos( node1.origin, true ); + self waittill( "reached_end_node" ); + + wait( 1 ); + + self SetVehGoalPos( node2.origin, true ); + self waittill( "reached_end_node" ); + + wait( 1 ); + } +} + +function devgui_debug_route() +{ + iprintln( "Choose nodes with 'A' or press 'B' to cancel" ); + nodes = dev::dev_get_node_pair(); + + if ( !isdefined( nodes ) ) + { + iprintln( "Route Debug Cancelled" ); + return; + } + + iprintln( "Sending talons to chosen nodes" ); + + tanks = GetEntArray( "talon", "targetname" ); + + foreach( tank in tanks ) + { + tank notify( "debug_patrol" ); + tank thread tank_debug_patrol( nodes[0], nodes[1] ); + } +} + +function tank_debug_hud_init() +{ + host = util::getHostPlayer(); + + while ( !isdefined( host ) ) + { + wait( 0.25 ); + host = util::getHostPlayer(); + } + + x = 80; + y = 40; + + level.ai_tank_bar = NewClientHudElem( host ); + level.ai_tank_bar.x = x + 80; + level.ai_tank_bar.y = y + 2; + level.ai_tank_bar.alignX = "left"; + level.ai_tank_bar.alignY = "top"; + level.ai_tank_bar.horzAlign = "fullscreen"; + level.ai_tank_bar.vertAlign = "fullscreen"; + level.ai_tank_bar.alpha = 0; + level.ai_tank_bar.foreground = 0; + level.ai_tank_bar setshader( "black", 1, 8 ); + + level.ai_tank_text = NewClientHudElem( host ); + level.ai_tank_text.x = x + 80; + level.ai_tank_text.y = y; + level.ai_tank_text.alignX = "left"; + level.ai_tank_text.alignY = "top"; + level.ai_tank_text.horzAlign = "fullscreen"; + level.ai_tank_text.vertAlign = "fullscreen"; + level.ai_tank_text.alpha = 0; + level.ai_tank_text.fontScale = 1; + level.ai_tank_text.foreground = 1; +} + +function tank_debug_health() +{ + self.damage_debug = ""; + + level.ai_tank_bar.alpha = 1; + level.ai_tank_text.alpha = 1; + + for ( ;; ) + { + {wait(.05);}; + + if ( !isdefined( self ) || !IsAlive( self ) ) + { + level.ai_tank_bar.alpha = 0; + level.ai_tank_text.alpha = 0; + return; + } + + width = self.health / self.maxhealth * 300; + width = int( max( width, 1 ) ); + level.ai_tank_bar setShader( "black", width, 8 ); + + str = ( self.health + " Last Damage: " + self.damage_debug ); + level.ai_tank_text SetText( str ); + } +} + +#/ diff --git a/mp/killstreaks/_airsupport.csc b/mp/killstreaks/_airsupport.csc new file mode 100644 index 0000000..33bedbc --- /dev/null +++ b/mp/killstreaks/_airsupport.csc @@ -0,0 +1,230 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\util_shared; + + + +#using scripts\mp\_rewindobjects; +#using scripts\mp\_util; +#using scripts\mp\_vehicle; + +#namespace airsupport; + +function planeSounds( localClientNum, spawnSound, flybySound, flybySoundLoop ) +{ + self endon("delete"); + + playsound (0, spawnSound, (0,0,0)); + if ( isdefined ( flybySound ) ) + self playsound (0, flybySound); + self playloopsound( flybySoundLoop, 0 ); +} + + +function getPlaneModel( teamFaction ) +{ + planemodel = "t5_veh_jet_f4_gearup"; + return planeModel; +} + + +function planeTurnRight( localClientNum, plane, yaw, halfLife, startTime) +{ + planeTurn( localClientNum, plane, yaw, halfLife, startTime, true ); +} + +function planeTurnLeft( localClientNum, plane, yaw, halfLife, startTime ) +{ + planeTurn( localClientNum, plane, yaw, halfLife, startTime, false ); +} + +function planeTurn( localClientNum, plane, yaw, halfLife, startTime, isTurningRight ) +{ + plane endon( "delete" ); + plane endon( "entityshutdown" ); + level endon( "demo_jump" + localClientNum ); + + leftTurn = -1; + rightTurn = 1; + + if( isTurningRight ) + turnDirection = rightTurn; + else + turnDirection = leftTurn; + + yawY = GetDvarFloat( "scr_planeyaw", -1.5 * turnDirection ); + rollZ = GetDvarFloat( "scr_planeroll", 1.5 * turnDirection ); + + maxYaw = GetDvarFloat( "scr_max_planeyaw", -45.0 * turnDirection ); + minRoll = GetDvarFloat( "scr_min_planeroll", 60.0 * turnDirection ); + + ox = GetDvarFloat( "scr_planeox", 30000.0 ); + oy = GetDvarFloat( "scr_planeoy", -30000.0 * turnDirection ); + maxoX = GetDvarFloat( "scr_maxo_planex", -1.0 ); + maxoY = GetDvarFloat( "scr_maxo_planey", -1.0 ); + + if (plane.angles[1] == 360) + plane.angles = ( plane.angles[0], 0, plane.angles[2] ); + + origX = plane.origin[0]; + origY = plane.origin[1]; + + accumTurn = 0; + looptime = 0.1; + waitAmount = 0.1; + waitForMoveDone = false; + while( loopTime <= halflife ) + { + if (plane.angles[1] == 360) + plane.angles = ( plane.angles[0], 0, plane.angles[2] ); + + if ( minRoll != -1 && plane.angles[2] >= minRoll * turnDirection ) + rollZ = 0.0; + + accumTurn += yawY; + + if ( accumTurn <= maxYaw * turnDirection ) + { + yawY = 0.0; + } + angles = ( plane.angles[0], plane.angles[1] + yawY, plane.angles[2] + rollZ); + + mathX = ( sin ( 45 * looptime / halflife ) ) * ox ; + mathY = ( cos ( 45 * looptime / halflife ) ) * oy ; + + oldX = mathX; + oldY = oy - mathY; + + rotatedX = Cos(yaw) * oldX - Sin(yaw) * oldY; + rotatedY = Sin(yaw) * oldX + Cos(yaw) * oldY; + + endPoint = ( origX + rotatedX, origY + rotatedY, plane.origin[2]); + if ( waitForMoveDone ) + plane waittill( "movedone" ); + waitForMoveDone = plane rewindobjects::serverTimedMoveTo( localClientNum, plane.origin, endPoint, startTime, waitAmount ); + plane rewindobjects::serverTimedRotateTo( localClientNum, angles, startTime, waitAmount ); + loopTime += waitAmount; + startTime += waitAmount * 1000; + } + + yawY = GetDvarFloat( "scr_planeyaw2", 1.5 ); + rollZ = GetDvarFloat( "scr_planeroll2", -0.9 ); + + ox = GetDvarFloat( "scr_planeox", 30000.0 ); + oy = GetDvarFloat( "scr_planeoy", -30000.0 * turnDirection ); + maxoX = GetDvarFloat( "scr_maxo_planex", -1.0 ); + maxoY = GetDvarFloat( "scr_maxo_planey", -1.0 ); + + y = GetDvarFloat( "scr_planey2", 0.6 ); + z = GetDvarFloat( "scr_planez2", -1.5 ); + maxy = GetDvarFloat( "scr_max_planey2", 90); + + accumTurn = 0; + + while( loopTime < halflife + halflife ) + { + if (plane.angles[1] == 360) + plane.angles = ( plane.angles[0], 0, plane.angles[2] ); + + if ( minRoll != -1 && plane.angles[2] >= 0 ) + rollZ = 0.0; + + accumTurn += yawY; + + if ( accumTurn >= maxYaw ) + { + yawY = 0.0; + } + + angles = ( plane.angles[0], plane.angles[1] + yawY, plane.angles[2] - rollZ); + + mathX = ( sin ( 45 * looptime / halflife ) ) * ox ; + mathY = ( cos ( 45 * looptime / halflife ) ) * oy ; + + oldX = mathX; + oldY = oy - mathY; + + rotatedX = Cos(yaw) * oldX - Sin(yaw) * oldY; + rotatedY = Sin(yaw) * oldX + Cos(yaw) * oldY; + + endPoint = ( origX + rotatedX, origY + rotatedY, plane.origin[2]); + + if ( waitForMoveDone ) + plane waittill( "movedone" ); + waitForMoveDone = plane rewindobjects::serverTimedMoveTo( localClientNum, plane.origin, endPoint, startTime, waitAmount ); + plane rewindobjects::serverTimedRotateTo( localClientNum, angles, startTime, waitAmount ); + loopTime += waitAmount; + startTime += waitAmount * 1000; + } +} + +function doABarrelRoll( localClientNum, plane, endPoint, flytime, startTime ) +{ + plane endon( "entityshutdown" ); + plane endon("delete"); + level endon("demo_jump"); + + origin = plane.origin; + originalHeight = origin[2]; + + loopWaitTime = GetDvarFloat( "scr_loopwaittime", 0.5 ); + loopHeightRand = GetDvarFloat( "scr_loopheightrand", 500 ); + loopHeight = GetDvarFloat( "scr_loopheight", 1200 ); + rollZ = GetDvarFloat( "scr_barrelroll", 10 ); + degreesToRoll = GetDvarFloat( "scr_degreesToRoll", 360 ); + unitsFromCentrePoint = 100; + + timeElapsed = 0; + degreesRolled = 0; + waitAmount = 0.1; + + loopHeight += randomFloatRange( 0-loopHeightRand, loopHeightRand ); + waitForMoveDone = false; + angles = plane.angles; + originalRoll = plane.angles[2]; + while ( timeElapsed < flytime ) + { + timeElapsed += waitAmount; + if ( ( timeElapsed > loopWaitTime ) && ( degreesRolled < degreesToRoll ) ) + { + pitch = degreesRolled / 8; + if ( pitch > 22.5 ) + pitch = 45 - pitch; + + originalAngle = plane.angles[2]; + + scr_degreesToRoll = GetDvarInt( "scr_degreesToRoll", 0 ); + if ( scr_degreesToRoll ) + plane.angles[1] = 0; + angles = ( 0 - pitch, plane.angles[1], originalRoll + degreesRolled ); + degreesRolled += rollZ; + } + + ratio = timeElapsed / ( flytime / 2 ); + + nextPoint = rewindobjects::getPointOnLine( origin, endPoint, ratio ); + + nextHeight = originalHeight + ( loopHeight - ( cos(degreesRolled/2) * loopHeight ) ); + nextPoint = ( nextPoint[0], nextPoint[1], nextHeight ); + + if ( waitForMoveDone ) + plane waittill( "movedone" ); + waitForMoveDone = plane rewindobjects::serverTimedMoveTo( localClientNum, plane.origin, nextPoint, startTime, waitAmount ); + plane rewindobjects::serverTimedRotateTo( localClientNum, angles, startTime, waitAmount ); + startTime += waitAmount * 1000; + } +} + + +function planeGoStraight( localClientNum, plane, startPoint, endPoint, moveTime, startTime ) +{ + plane endon("delete"); + level endon("demo_jump"); + + distanceIncreaseRatio = 2; + + destPoint = rewindobjects::getPointOnLine( startPoint, endPoint, distanceIncreaseRatio ); + if ( plane rewindobjects::serverTimedMoveTo( localClientNum, startPoint, destPoint, startTime, moveTime ) ) + plane waittill( "movedone" ); +} + diff --git a/mp/killstreaks/_airsupport.gsc b/mp/killstreaks/_airsupport.gsc new file mode 100644 index 0000000..0c9d1db --- /dev/null +++ b/mp/killstreaks/_airsupport.gsc @@ -0,0 +1,1289 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\challenges_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\math_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\weapons\_weapons; + + + +#using scripts\mp\_util; + +#namespace airsupport; + +// some common functions between all the air kill streaks + +function init() +{ + if ( !isdefined( level.airsupportHeightScale ) ) + level.airsupportHeightScale = 1; + + level.airsupportHeightScale = GetDvarInt( "scr_airsupportHeightScale", level.airsupportHeightScale ); + + level.noFlyZones = []; + level.noFlyZones = GetEntArray("no_fly_zone","targetname"); + + airsupport_heights = struct::get_array("air_support_height","targetname"); + + /# + if ( airsupport_heights.size > 1 ) + { + util::error( "Found more then one 'air_support_height' structs in the map" ); + } + #/ + + airsupport_heights = GetEntArray("air_support_height","targetname"); + + /# + if ( airsupport_heights.size > 0 ) + { + util::error( "Found an entity in the map with an 'air_support_height' targetname. There should be only structs." ); + } + #/ + + heli_height_meshes = GetEntArray("heli_height_lock","classname"); + + /# + if ( heli_height_meshes.size > 1 ) + { + util::error( "Found more then one 'heli_height_lock' classname in the map" ); + } + #/ + + InitRotatingRig(); +} + +function finishHardpointLocationUsage( location, usedCallback ) +{ + self notify( "used" ); + {wait(.05);}; + + if( isdefined( usedCallback ) ) + { + return self [[usedCallback]]( location ); + } + + return true; +} + +function finishDualHardpointLocationUsage( locationStart, locationEnd, usedCallback ) +{ + self notify( "used" ); + {wait(.05);}; + return self [[usedCallback]]( locationStart, locationEnd ); +} + +function endSelectionOnGameEnd() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "cancel_location" ); + self endon( "used" ); + self endon( "host_migration_begin" ); + + level waittill( "game_ended" ); + self notify( "game_ended" ); +} + +function endSelectionOnHostMigration() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "cancel_location" ); + self endon( "used" ); + self endon( "game_ended" ); + + level waittill( "host_migration_begin" ); + self notify( "cancel_location" ); +} + +function endSelectionThink() +{ + assert( IsPlayer( self ) ); + assert( IsAlive( self ) ); + assert( isdefined( self.selectingLocation ) ); + assert( self.selectingLocation == true ); + + self thread endSelectionOnGameEnd(); + self thread endSelectionOnHostMigration(); + + event = self util::waittill_any_return( "death", "disconnect", "cancel_location", "game_ended", "used", "weapon_change", "emp_jammed" ); + + if ( event != "disconnect" ) + { + self.selectingLocation = undefined; + self thread clearUpLocationSelection(); + } + + if ( event != "used" ) + { + // wake threads waiting for locations + self notify( "confirm_location", undefined, undefined ); + } +} + +function clearUpLocationSelection() +{ + event = self util::waittill_any_return( "death", "disconnect", "game_ended", "used", "weapon_change", "emp_jammed", "weapon_change_complete" ); + + if ( event != "disconnect" ) + { + self endLocationSelection(); + } +} + +function stopLoopSoundAfterTime( time ) +{ + self endon ( "death" ); + wait ( time ); + + self stoploopsound( 2 ); +} + +function calculateFallTime( flyHeight ) +{ + // this is the value that code uses + gravity = GetDvarint( "bg_gravity" ); + + time = sqrt( (2 * flyHeight) / gravity ); + + return time; +} + +function calculateReleaseTime( flyTime, flyHeight, flySpeed, bombSpeedScale ) +{ + falltime = calculateFallTime( flyHeight ); + + // bomb horizontal velocity is not the same as the plane speed so we need to take this + // into account when calculating the bomb time + bomb_x = (flySpeed * bombSpeedScale) * falltime; + release_time = bomb_x / flySpeed; + + return ( (flyTime * 0.5) - release_time); +} + +function getMinimumFlyHeight() +{ + airsupport_height = struct::get( "air_support_height", "targetname"); + if ( isdefined(airsupport_height) ) + { + planeFlyHeight = airsupport_height.origin[2]; + } + else + { +/# + PrintLn("WARNING: Missing air_support_height entity in the map. Using default height."); +#/ + // original system + planeFlyHeight = 850; + + if ( isdefined( level.airsupportHeightScale ) ) + { + level.airsupportHeightScale = GetDvarInt( "scr_airsupportHeightScale", level.airsupportHeightScale ); + planeFlyHeight *= GetDvarInt( "scr_airsupportHeightScale", level.airsupportHeightScale ); + } + + if ( isdefined( level.forceAirsupportMapHeight ) ) + { + planeFlyHeight += level.forceAirsupportMapHeight; + } + } + + return planeFlyHeight; +} + +function callStrike( flightPlan ) +{ + level.bomberDamagedEnts = []; + level.bomberDamagedEntsCount = 0; + level.bomberDamagedEntsIndex = 0; + + assert( flightPlan.distance != 0, "callStrike can not be passed a zero fly distance"); + + planeHalfDistance = flightPlan.distance / 2; + + path = getStrikePath( flightPlan.target, flightPlan.height, planeHalfDistance ); + startPoint = path["start"]; + endPoint = path["end"]; + flightPlan.height = path["height"]; + direction = path["direction"]; + + // Make the plane fly by + d = length( startPoint - endPoint ); + flyTime = ( d / flightPlan.speed ); + + bombTime = calculateReleaseTime( flyTime, flightPlan.height, flightPlan.speed, flightPlan.bombSpeedScale); + + if (bombTime < 0) + { + bombTime = 0; + } + + assert( flyTime > bombTime ); + + flightPlan.owner endon("disconnect"); + + requiredDeathCount = flightPlan.owner.deathCount; + + side = VectorCross( anglestoforward( direction ), (0,0,1) ); + plane_seperation = 25; + side_offset = VectorScale( side, plane_seperation ); + + level thread planeStrike( flightPlan.owner, requiredDeathCount, startPoint, endPoint, bombTime, flyTime, flightPlan.speed, flightPlan.bombSpeedScale, direction, flightPlan.planeSpawnCallback ); + wait( flightPlan.planeSpacing ); + level thread planeStrike( flightPlan.owner, requiredDeathCount, startPoint+side_offset, endPoint+side_offset, bombTime, flyTime, flightPlan.speed, flightPlan.bombSpeedScale, direction, flightPlan.planeSpawnCallback ); + wait( flightPlan.planeSpacing ); + + side_offset = VectorScale( side, -1 * plane_seperation ); + + level thread planeStrike( flightPlan.owner, requiredDeathCount, startPoint+side_offset, endPoint+side_offset, bombTime, flyTime, flightPlan.speed, flightPlan.bombSpeedScale, direction, flightPlan.planeSpawnCallback ); +} + + +function planeStrike( owner, requiredDeathCount, pathStart, pathEnd, bombTime, flyTime, flyspeed, bombSpeedScale, direction, planeSpawnedFunction ) +{ + // plane spawning randomness = up to 125 units, biased towards 0 + // radius of bomb damage is 512 + + if ( !isdefined( owner ) ) + return; + +// bomb_x = (flySpeed * bombSpeedScale) * bombTime; +// origin = VectorScale(pathEnd - pathStart, 0.5) + pathStart; +// plane = spawnplane( owner, "script_model", origin ); + + // Spawn the planes + plane = spawnplane( owner, "script_model", pathStart ); + plane.angles = direction; + + plane moveTo( pathEnd, flyTime, 0, 0 ); + + thread debug_plane_line( flyTime, flyspeed, pathStart, pathEnd ); + + if ( isdefined(planeSpawnedFunction) ) + { + plane [[planeSpawnedFunction]]( owner, requiredDeathCount, pathStart, pathEnd, bombTime, bombSpeedScale, flyTime, flyspeed ); + } + + // Delete the plane after its flyby + wait flyTime; + plane notify( "delete" ); + plane delete(); +} + +///////////////////////////////////////////////////////////////////////////// +// TARGETING + +function determineGroundPoint( player, position ) +{ + ground = (position[0], position[1], player.origin[2]); + + trace = bullettrace(ground + (0,0,10000), ground, false, undefined ); + return trace["position"]; +} + +function determineTargetPoint( player, position ) +{ + point = determineGroundPoint( player, position ); + + return clampTarget( point ); +} + +function getMinTargetHeight() +{ + return level.spawnMins[2] - 500; +} + +function getMaxTargetHeight() +{ + return level.spawnMaxs[2] + 500; +} + +function clampTarget( target ) +{ + min = getMinTargetHeight(); + max = getMaxTargetHeight(); + + if ( target[2] < min ) + target[2] = min; + + if ( target[2] > max ) + target[2] = max; + + return target; +} + + +///////////////////////////////////////////////////////////////////////////// +// NO FLY ZONE + +function _insideCylinder( point, base, radius, height ) +{ + // only going to test if the point is above the height + // if the point is below the cylinder going to treat it + // as being inside + if ( isdefined( height ) ) + { + if ( point[2] > base[2] + height ) + return false; + } + + dist = Distance2D( point, base ); + + if ( dist < radius ) + return true; + + return false; +} + +function _insideNoFlyZoneByIndex( point, index, disregardHeight ) +{ + height = level.noFlyZones[index].height; + + if ( isdefined(disregardHeight ) ) + height = undefined; + + return _insideCylinder( point, level.noFLyZones[index].origin, level.noFlyZones[index].radius, height ); +} + +// if not in a no fly zone then it just returns the height of the point passed in +function getNoFlyZoneHeight( point ) +{ + height = point[2]; + origin = undefined; + + for ( i = 0; i < level.noFlyZones.size; i++ ) + { + if ( _insideNoFlyZoneByIndex( point, i ) ) + { + if ( height < level.noFlyZones[i].height ) + { + height = level.noFlyZones[i].height; + origin = level.noFlyZones[i].origin; + } + } + } + + if ( !isdefined( origin ) ) + return point[2]; + + return origin[2] + height; +} + +function insideNoFlyZones( point, disregardHeight ) +{ + noFlyZones = []; + + for ( i = 0; i < level.noFlyZones.size; i++ ) + { + if ( _insideNoFlyZoneByIndex( point, i, disregardHeight ) ) + { + noFlyZones[noFlyZones.size] = i; + } + } + + return noFlyZones; +} + + +function crossesNoFlyZone( start, end ) +{ + for ( i = 0; i < level.noFlyZones.size; i++ ) + { + point = math::closest_point_on_line( level.noFlyZones[i].origin + (0,0,(0.5 * level.noFlyZones[i].height)), start, end ); + dist = Distance2D( point, level.noFlyZones[i].origin ); + + if ( point[2] > ( level.noFlyZones[i].origin[2] + level.noFlyZones[i].height ) ) + continue; + + if ( dist < level.noFlyZones[i].radius ) + { + return i; + } + } + + return undefined; +} + +function crossesNoFlyZones( start, end ) +{ + zones = []; + for ( i = 0; i < level.noFlyZones.size; i++ ) + { + point = math::closest_point_on_line( level.noFlyZones[i].origin, start, end ); + dist = Distance2D( point, level.noFlyZones[i].origin ); + + if ( point[2] > ( level.noFlyZones[i].origin[2] + level.noFlyZones[i].height ) ) + continue; + + if ( dist < level.noFlyZones[i].radius ) + { + zones[zones.size] = i; + } + } + + return zones; +} + +function getNoFlyZoneHeightCrossed( start, end, minHeight ) +{ + height = minHeight; + for ( i = 0; i < level.noFlyZones.size; i++ ) + { + point = math::closest_point_on_line( level.noFlyZones[i].origin, start, end ); + dist = Distance2D( point, level.noFlyZones[i].origin ); + + if ( dist < level.noFlyZones[i].radius ) + { + if ( height < level.noFlyZones[i].height ) + height = level.noFlyZones[i].height; + } + } + + return height; +} + +function _shouldIgnoreNoFlyZone( noFlyZone, noFlyZones ) +{ + if ( !isdefined( noFlyZone ) ) + return true; + + for ( i = 0; i < noFlyZones.size; i ++ ) + { + if ( isdefined( noFlyZones[i] ) && noFlyZones[i] == noFlyZone ) + return true; + } + + return false; +} + +function _shouldIgnoreStartGoalNoFlyZone( noFlyZone, startNoFlyZones, goalNoFlyZones ) +{ + if ( !isdefined( noFlyZone ) ) + return true; + + if ( _shouldIgnoreNoFlyZone( noFlyZone, startNoFlyZones ) ) + return true; + + if ( _shouldIgnoreNoFlyZone( noFlyZone, goalNoFlyZones ) ) + return true; + + return false; +} + + +function getHeliPath( start, goal ) +{ + startNoFlyZones = insideNoFlyZones( start, true ); + + thread debug_line( start, goal, (1,1,1) ); + + goalNoFlyZones = insideNoFlyZones( goal ); + + // if the end point is in a no fly zone then raise the height to the top of the zone + if ( goalNoFlyZones.size ) + { + goal = ( goal[0], goal[1], getNoFlyZoneHeight( goal ) ); + } + + goal_points = calculatePath(start, goal, startNoFlyZones, goalNoFlyZones ); + + if ( !isdefined( goal_points ) ) + return undefined; + + Assert(goal_points.size >= 1 ); + + return goal_points; +} + +function followPath( path, doneNotify, stopAtGoal ) +{ + for ( i = 0; i < (path.size - 1); i++ ) + { + self SetVehGoalPos( path[i], false ); + + thread debug_line( self.origin, path[i], (1,1,0) ); + self waittill("goal" ); + } + + self SetVehGoalPos( path[path.size - 1], stopAtGoal ); + thread debug_line( self.origin, path[i], (1,1,0) ); + + self waittill("goal" ); + + if ( isdefined( doneNotify ) ) + { + self notify(doneNotify); + } +} + +function setGoalPosition( goal, doneNotify, stopAtGoal ) +{ + if ( !isdefined( stopAtGoal ) ) + stopAtGoal = true; + + // should test the start to see if it is inside of a no fly zone + // and try and make the vehicle leave the no fly zone in as short of + // a path possible while still moving intelligently + start = self.origin; + + goal_points = getHeliPath(start, goal ); + + if ( !isdefined(goal_points) ) + { + goal_points = []; + goal_points[0] = goal; + } + + followPath( goal_points, doneNotify, stopAtGoal ); +} + +function clearPath( start, end, startNoFlyZone, goalNoFlyZone ) +{ + noFlyZones = crossesNoFlyZones( start, end ); + + for ( i = 0 ; i < noFlyZones.size; i++ ) + { + if ( !_shouldIgnoreStartGoalNoFlyZone( noFlyZones[i], startNoFlyZone, goalNoFlyZone) ) + { + return false; + } + } + + return true; +} + +function append_array( dst, src ) +{ + for ( i= 0; i < src.size; i++ ) + { + dst[ dst.size ]= src[ i ]; + } +} + +function calculatePath_r( start, end, points, startNoFlyZones, goalNoFlyZones, depth ) +{ + depth--; + + if ( depth <= 0 ) + { + points[points.size] = end; + return points; + } + + noFlyZones = crossesNoFlyZones( start, end ); + + for ( i = 0; i < noFlyZones.size; i++ ) + { + noFlyZone = noFlyZones[i]; + + // simple path right now + // probably need to modify this so it tests the new lines found + if ( !_shouldIgnoreStartGoalNoFlyZone( noFlyZone, startNoFlyZones, goalNoFlyZones) ) + { + return undefined; + } + } + + points[points.size] = end; + + return points; +} + +function calculatePath( start, end, startNoFlyZones, goalNoFlyZones ) +{ + points = []; + +// PrintLn( "starting path calc: " + start + " " + end ); +// points[0] = start; + + points = calculatePath_r( start, end, points, startNoFlyZones, goalNoFlyZones, 3 ); + + if ( !isdefined(points) ) + return undefined; + + Assert( points.size >= 1 ); + + + debug_sphere( points[points.size - 1], 10, (1,0,0), 1, 1000 ); + + point = start; + +// PrintLn( "Path Calculated: " + points.size ); + for ( i = 0 ; i < points.size; i++ ) + { + thread debug_line( point, points[i], (0,1,0) ); + debug_sphere( points[i], 10, (0,0,1), 1, 1000 ); + point = points[i]; + } + return points; +} + +function _getStrikePathStartAndEnd( goal, yaw, halfDistance ) +{ + direction = (0,yaw,0); + + startPoint = goal + VectorScale( anglestoforward( direction ), -1 * halfDistance ); + endPoint = goal + VectorScale( anglestoforward( direction ), halfDistance ); + + noFlyZone = crossesNoFlyZone( startPoint, endPoint ); + + path = []; + + if ( isdefined( noFlyZone ) ) + { + path["noFlyZone"] = noFlyZone; + + startPoint = ( startPoint[0], startPoint[1], level.noFlyZones[noFlyZone].origin[2] + level.noFlyZones[noFlyZone].height ); + endPoint = ( endPoint[0], endPoint[1], startPoint[2] ); + } + else + { + path["noFlyZone"] = undefined; + } + + path["start"] = startPoint; + path["end"] = endPoint; + path["direction"] = direction; + + return path; +} + +function getStrikePath( target, height, halfDistance, yaw ) +{ + noFlyZoneHeight = getNoFlyZoneHeight( target ); + + worldHeight = target[2] + height; + + if ( noFlyZoneHeight > worldHeight ) + { + worldHeight = noFlyZoneHeight; + } + + goal = ( target[0], target[1], worldHeight ); + + path = []; + + if ( !isdefined( yaw ) || yaw != "random" ) + { + // try a few times to find a path that is not through a no fly zone + for ( i = 0; i < 3; i++ ) + { + path = _getStrikePathStartAndEnd( goal, randomint( 360 ), halfDistance ); + + if ( !isdefined( path["noFlyZone"] ) ) + { + break; + } + } + } + else + { + path = _getStrikePathStartAndEnd( goal, yaw, halfDistance ); + } + + path["height"] = worldHeight - target[2]; + return path; +} + +function doGlassDamage(pos, radius, max, min, mod) +{ + wait(RandomFloatRange(0.05, 0.15)); + glassRadiusDamage( pos, radius, max, min, mod ); +} + +function entLOSRadiusDamage( ent, pos, radius, max, min, owner, eInflictor ) +{ + dist = distance(pos, ent.damageCenter); + + if ( ent.isPlayer || ent.isActor ) + { + assumed_ceiling_height = 800; // check for very high ceilings + eye_position = ent.entity GetEye(); + head_height = eye_position[2]; + debug_display_time = 40 * 100; + + // check if there is a path to this entity above his feet. if not, they're probably indoors + trace = weapons::damage_trace( ent.entity.origin, ent.entity.origin + (0,0,assumed_ceiling_height), 0, undefined ); + indoors = (trace["fraction"] != 1); + + if ( indoors ) + { + // the follow check will still fail indoors if the bomb is detonated above the player + // and the ceiling is under 130 units. This second check will have line of site to + // the "ceiling height" point. I dont want to change it at this point. + + test_point = trace["position"]; + debug_star(test_point, (0,1,0), debug_display_time); + + trace = weapons::damage_trace( (test_point[0],test_point[1],head_height) , (pos[0],pos[1], head_height), 0, undefined ); + indoors = (trace["fraction"] != 1); + + if ( indoors ) + { + debug_star((pos[0],pos[1], head_height), (0,1,0), debug_display_time); + // give them a distance advantage for being indoors. + dist *= 4; + if ( dist > radius ) + return false; + } + else + { + debug_star((pos[0],pos[1], head_height), (1,0,0), debug_display_time); + trace = weapons::damage_trace( (pos[0],pos[1], head_height), pos, 0, undefined ); + indoors = (trace["fraction"] != 1); + if ( indoors ) + { + debug_star(pos, (0,1,0), debug_display_time); + // give them a distance advantage for being indoors. + dist *= 4; + if ( dist > radius ) + return false; + } + else + { + debug_star(pos, (1,0,0), debug_display_time); + } + } + } + else + { + debug_star(ent.entity.origin + (0,0,assumed_ceiling_height), (1,0,0), debug_display_time ); + } + } + + ent.damage = int(max + (min-max)*dist/radius); + ent.pos = pos; + ent.damageOwner = owner; + ent.eInflictor = eInflictor; + + return true; +} + +//////////////////////////////////////////////////////////////////////////// +function GetMapCenter() +{ + minimapOrigins = getEntArray( "minimap_corner", "targetname" ); + if( miniMapOrigins.size ) + { + return math::find_box_center( miniMapOrigins[0].origin, miniMapOrigins[1].origin ); + } + + return ( 0, 0, 0 ); +} + +function GetRandomMapPoint( x_offset, y_offset, map_x_percentage, map_y_percentage ) +{ + minimapOrigins = getEntArray( "minimap_corner", "targetname" ); + if( miniMapOrigins.size ) + { + rand_x = 0; + rand_y = 0; + + if( miniMapOrigins[0].origin[0] < miniMapOrigins[1].origin[0] ) + { + rand_x = RandomFloatRange( miniMapOrigins[0].origin[0] * map_x_percentage, miniMapOrigins[1].origin[0] * map_x_percentage ); + rand_y = RandomFloatRange( miniMapOrigins[0].origin[1] * map_y_percentage, miniMapOrigins[1].origin[1] * map_y_percentage ); + } + else + { + rand_x = RandomFloatRange( miniMapOrigins[1].origin[0] * map_x_percentage, miniMapOrigins[0].origin[0] * map_x_percentage ); + rand_y = RandomFloatRange( miniMapOrigins[1].origin[1] * map_y_percentage, miniMapOrigins[0].origin[1] * map_y_percentage ); + } + + return ( x_offset + rand_x, y_offset + rand_y, 0 ); + } + + return ( x_offset, y_offset, 0 ); +} + +function GetMaxMapWidth() +{ + minimapOrigins = getEntArray( "minimap_corner", "targetname" ); + if( miniMapOrigins.size ) + { + x = abs( miniMapOrigins[0].origin[0] - miniMapOrigins[1].origin[0] ); + y = abs( miniMapOrigins[0].origin[1] - miniMapOrigins[1].origin[1] ); + + return max( x, y ); + } + + return 0; +} + +function InitRotatingRig() +{ + level.airsupport_rotator = spawn( "script_model", GetMapCenter() + ( (isdefined(level.rotator_x_offset)?level.rotator_x_offset:0), (isdefined(level.rotator_y_offset)?level.rotator_y_offset:0), 1200 ) ); + level.airsupport_rotator setModel( "tag_origin" ); + level.airsupport_rotator.angles = ( 0, 115, 0 ); + level.airsupport_rotator hide(); + level.airsupport_rotator thread RotateRig(); + level.airsupport_rotator thread SwayRig(); +} + +function RotateRig() +{ + for (;;) + { + self rotateyaw( -360, 60 ); + wait ( 60 ); + } +} + +function SwayRig() +{ + centerOrigin = self.origin; + + for (;;) + { + z = randomIntRange( -200, -100 ); + + time = randomIntRange( 3, 6 ); + self moveto( centerOrigin + (0,0,z), time, 1, 1 ); + wait ( time ); + + z = randomIntRange( 100, 200 ); + + time = randomIntRange( 3, 6 ); + self moveto( centerOrigin + (0,0,z), time, 1, 1 ); + wait ( time ); + } +} + +function StopRotation( time ) +{ + self endon( "death" ); + wait( time ); + self StopLoopSound(); +} + +function FlattenYaw( goal ) +{ + self endon( "death" ); + + increment = 3; + if ( self.angles[1] > goal ) + { + increment = increment * -1; + } + while( abs( self.angles[1] - goal ) > 3 ) + { + self.angles = (self.angles[0], self.angles[1] + increment, self.angles[2] ); + {wait(.05);}; + } +} + +function FlattenRoll() +{ + self endon( "death" ); + + while (self.angles[2] < 0) + { + self.angles = (self.angles[0], self.angles[1], self.angles[2] + 2.5 ); + {wait(.05);}; + } +} + +function Leave( duration ) +{ + self unlink(); + + self thread StopRotation( 1 ); + + tries = 10; + yaw = 0; + while( tries > 0 ) + { + exitVector = ( anglestoforward( self.angles + ( 0, yaw, 0 ) ) * 20000 ); + exitPoint = ( self.origin[0] + exitVector[0], self.origin[1] + exitVector[1], self.origin[2] - 2500); + exitPoint = self.origin + exitVector; + + nfz = airsupport::crossesNoFlyZone (self.origin, exitPoint); + if( isdefined(nfz)) + { + if ( tries != 1 ) + { + if ( tries % 2 == 1) + { + yaw = yaw * -1; + } + else + { + yaw = yaw + 10; + yaw = yaw * -1; + } + } + tries--; + } + else + { + tries = 0; + } + } + + self thread FlattenYaw( self.angles[1] + yaw ); + if (self.angles[2] != 0) + { + self thread FlattenRoll(); + } + + if ( IsVehicle( self ) ) + { + self SetSpeed( ( ( Length( exitVector ) / duration ) / 17.6 ), 60 ); + self SetVehGoalPos( exitPoint, false, false ); + } + else + { + self moveto( exitPoint, duration, 0, 0 ); + } + self notify ( "leaving"); +} + + +function GetRandomHelicopterStartOrigin() +{ + dist = -1 * GetDvarInt( "scr_supplydropIncomingDistance", 10000 ); + pathRandomness = 100; + direction = ( 0, RandomIntRange( -2, 3 ), 0 ); + + start_origin = ( AnglesToForward( direction ) * dist ); + start_origin += ( ( randomfloat( 2 ) - 1 ) * pathRandomness, ( randomfloat( 2 ) - 1 ) * pathRandomness, 0 ); + +/# + if ( GetDvarInt( "scr_noflyzones_debug", 0 ) ) + { + if ( level.noFlyZones.size ) + { + index = RandomIntRange( 0, level.noFlyZones.size ); + delta = level.noFlyZones[ index ].origin; + delta = ( delta[0] + RandomInt( 10 ), delta[ 1 ] + RandomInt( 10 ), 0 ); + delta = VectorNormalize( delta ); + start_origin = ( delta * dist ); + } + } +#/ + return start_origin; +} + + +//////////////////////////////////////////////////////////////////////////// +// debug + +function debug_no_fly_zones() +{ + /# + for ( i = 0; i < level.noFlyZones.size; i++ ) + { + debug_airsupport_cylinder( level.noFlyZones[i].origin, level.noFlyZones[i].radius, level.noFlyZones[i].height, (1,1,1), undefined, 5000 ); + } + #/ +} + +function debug_plane_line( flyTime, flyspeed,pathStart, pathEnd ) +{ + thread debug_line( pathStart, pathEnd, (1,1,1) ); + + delta = VectorNormalize(pathEnd - pathStart); + + for ( i = 0; i < flyTime; i++ ) + { + thread debug_star( pathStart + VectorScale(delta, i * flyspeed), (1,0,0) ); + } +} + +function debug_draw_bomb_explosion(prevpos) +{ + self notify("draw_explosion"); + {wait(.05);}; + self endon("draw_explosion"); + + self waittill("projectile_impact", weapon, position ); + + thread debug_line( prevpos, position, (.5,1,0) ); + thread debug_star( position, (1,0,0) ); +} + +function debug_draw_bomb_path( projectile, color, time ) +{ +/# + self endon("death"); + level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen + + if ( !isdefined( color ) ) + { + color = (.5,1,0); + } + + if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1.0 ) + { + prevpos = self.origin; + while(isdefined ( self.origin ) ) + { + thread debug_line( prevpos, self.origin, color, time ); + prevpos = self.origin; + + if ( isdefined(projectile) && projectile ) + { + thread debug_draw_bomb_explosion( prevpos ); + } + + wait .2; + } + } +#/ +} + +function debug_print3d_simple( message, ent, offset, frames ) +{ + /# + level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen + + if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1.0 ) + { + if( isdefined( frames ) ) + thread draw_text( message, ( 0.8, 0.8, 0.8 ), ent, offset, frames ); + else + thread draw_text( message, ( 0.8, 0.8, 0.8 ), ent, offset, 0 ); + } + #/ +} + +function draw_text( msg, color, ent, offset, frames ) +{ + /# + if( frames == 0 ) + { + while ( isdefined( ent ) && isdefined( ent.origin ) ) + { + print3d( ent.origin+offset, msg , color, 0.5, 4 ); + {wait(.05);}; + } + } + else + { + for( i=0; i < frames; i++ ) + { + if( !isdefined( ent ) ) + break; + print3d( ent.origin+offset, msg , color, 0.5, 4 ); + {wait(.05);}; + } + } + #/ +} + + +function debug_print3d( message, color, ent, origin_offset, frames ) +{ +/# + level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen + + if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1.0 ) + self thread draw_text( message, color, ent, origin_offset, frames ); +#/ +} + + +function debug_line( from, to, color, time, depthTest ) +{ +/# + level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen + + if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1.0 ) + { + if ( DistanceSquared( from, to ) < 0.01 ) + return; + + if ( !isdefined(time) ) + { + time = 1000; + } + if ( !isdefined(depthTest) ) + { + depthTest = true; + } + Line( from, to, color, 1, depthTest, time); + } +#/ + +} + +function debug_star( origin, color, time ) +{ +/# + level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen + + if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1.0 ) + { + if ( !isdefined(time) ) + { + time = 1000; + } + if ( !isdefined(color) ) + { + color = (1,1,1); + } + debugstar( origin, time, color ); + } +#/ +} + +function debug_circle( origin, radius, color, time ) +{ +/# + level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen + + if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1 ) + { + if ( !isdefined(time) ) + { + time = 1000; + } + if ( !isdefined(color) ) + { + color = (1,1,1); + } + circle( origin, radius, color, true, true, time ); + } +#/ +} + +function debug_sphere( origin, radius, color, alpha, time ) +{ +/# + level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen + + if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1 ) + { + if ( !isdefined(time) ) + { + time = 1000; + } + if ( !isdefined(color) ) + { + color = (1,1,1); + } + + sides = Int(10 * ( 1 + Int(radius / 100) )); + sphere( origin, radius, color, alpha, true, sides, time ); + } +#/ +} + +function debug_airsupport_cylinder( origin, radius, height, color, mustRenderHeight, time ) +{ +/# + level.airsupport_debug = GetDvarInt( "scr_airsupport_debug", 0 ); // debug mode, draws debugging info on screen + + if ( isdefined( level.airsupport_debug ) && level.airsupport_debug == 1 ) + { + debug_cylinder( origin, radius, height, color, mustRenderHeight, time ); + } +#/ +} + +function debug_cylinder( origin, radius, height, color, mustRenderHeight, time ) +{ +/# + + subdivision = 600; + + { + if ( !isdefined(time) ) + { + time = 1000; + } + if ( !isdefined(color) ) + { + color = (1,1,1); + } + + count = (height/subdivision); + + for ( i = 0; i < count; i++ ) + { + point = origin + ( 0, 0, i * subdivision ); + circle( point, radius, color, true, true, time ); + } + + if( isdefined( mustRenderHeight ) ) + { + point = origin + ( 0, 0, mustRenderHeight ); + circle( point, radius, color, true, true, time ); + } + } +#/ +} +function getPointOnLine( startPoint, endPoint, ratio ) +{ + nextPoint = ( startPoint[0] + ( ( endPoint[0] - startPoint[0] ) * ratio ) , + startPoint[1] + ( ( endPoint[1] - startPoint[1] ) * ratio ) , + startPoint[2] + ( ( endPoint[2] - startPoint[2] ) * ratio ) ); + + return nextPoint; +} + +function canTargetPlayerWithSpecialty() +{ + if ( self HasPerk( "specialty_nottargetedbyairsupport" ) || + ( isdefined( self.specialty_nottargetedbyairsupport ) && self.specialty_nottargetedbyairsupport ) ) + { + if ( !isdefined( self.notTargettedAI_underMinSpeedTimer ) || self.notTargettedAI_underMinSpeedTimer < GetDvarInt( "perk_nottargetedbyai_graceperiod" ) ) + return false; + } + return true; +} + + +function monitorSpeed( spawnProtectionTime ) +{ + self endon( "death" ); + self endon( "disconnect" ); + + if ( self HasPerk( "specialty_nottargetedbyairsupport" ) == false ) + { + return; + } + + GetDvarString( "perk_nottargetted_graceperiod" ); + gracePeriod = GetDvarInt( "perk_nottargetedbyai_graceperiod" ); + minspeed = GetDvarInt( "perk_nottargetedbyai_min_speed" ); + minspeedSq = minspeed * minspeed; + waitPeriod = 0.25; + waitPeriodMilliseconds = waitPeriod * 1000; + if ( minspeedSq == 0 ) // will never fail min speed check below so early out. + return; + + self.notTargettedAI_underMinSpeedTimer = 0; + + if ( isdefined ( spawnProtectionTime ) ) + { + wait( spawnProtectionTime ); + } + + while(1) + { + velocity = self GetVelocity(); + + speedsq = lengthsquared( velocity ); + + if ( speedSq < minspeedSq ) + { + self.notTargettedAI_underMinSpeedTimer += waitPeriodMilliseconds; + } + else + { + self.notTargettedAI_underMinSpeedTimer = 0; + } + + wait( waitPeriod ); + } +} + +function clearmonitoredspeed() +{ + if ( isdefined ( self.notTargettedAI_underMinSpeedTimer ) ) + self.notTargettedAI_underMinSpeedTimer = 0; +} diff --git a/mp/killstreaks/_combat_robot.gsc b/mp/killstreaks/_combat_robot.gsc new file mode 100644 index 0000000..3ba5fab --- /dev/null +++ b/mp/killstreaks/_combat_robot.gsc @@ -0,0 +1,954 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\ai_shared; +#using scripts\shared\ai_puppeteer_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\flagsys_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\objpoints_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\ai\archetype_utility; +#using scripts\shared\ai\systems\gib; +#using scripts\shared\entityheadicons_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicleriders_shared; +#using scripts\shared\weapons\_heatseekingmissile; +#using scripts\shared\ai\systems\blackboard; + +#using scripts\mp\_challenges; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_supplydrop; + + + + + + + + + + +#namespace combat_robot; + + + + + + + + + +// Tweaks to how the combat robot's body is thrown after exploding + // Scales the initial velocity + + + + + + + +// Time in seconds the combat robot will shutdown before exploding. + + + +// The combat robot will give up chasing an enemy if they haven't attacked them for this long. + + +// The combat robot will ignore unattackable enemies for this long. + + + + + + + +#precache( "string", "KILLSTREAK_COMBAT_ROBOT_ESCORT_HINT" ); +#precache( "string", "KILLSTREAK_COMBAT_ROBOT_GUARD_HINT" ); +#precache( "string", "KILLSTREAK_COMBAT_ROBOT_INBOUND" ); +#precache( "string", "KILLSTREAK_COMBAT_ROBOT_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_COMBAT_ROBOT_HACKED" ); +#precache( "string", "KILLSTREAK_COMBAT_ROBOT_PATROL_FAIL" ); +#precache( "string", "KILLSTREAK_DESTROYED_COMBAT_ROBOT" ); +#precache( "triggerstring", "KILLSTREAK_COMBAT_ROBOT_ESCORT_HINT" ); +#precache( "triggerstring", "KILLSTREAK_COMBAT_ROBOT_GUARD_HINT" ); + +#precache( "material", "t7_hud_ks_c54i_drop" ); + +function init() +{ + killstreaks::register( "combat_robot", "combat_robot_marker", "killstreak_" + "combat_robot", "combat_robot" + "_used", &ActivateCombatRobot, undefined, true ); + killstreaks::register_alt_weapon( "combat_robot", "lmg_light_robot" ); + killstreaks::register_strings( "combat_robot", &"KILLSTREAK_COMBAT_ROBOT_EARNED", &"KILLSTREAK_COMBAT_ROBOT_NOT_AVAILABLE", &"KILLSTREAK_COMBAT_ROBOT_INBOUND", undefined, &"KILLSTREAK_COMBAT_ROBOT_HACKED" ); + killstreaks::register_dialog( "combat_robot", "mpl_killstreak_combat_robot", "combatRobotDialogBundle", "combatRobotPilotDialogBundle", "friendlyCombatRobot", "enemyCombatRobot", "enemyCombatRobotMultiple", "friendlyCombatRobotHacked", "enemyCombatRobotHacked", "requestCombatRobot", "threatCombatRobot" ); + + // TODO: Move to killstreak data + level.killstreaks["inventory_combat_robot"].threatOnKill = true; + level.killstreaks["combat_robot"].threatOnKill = true; + + level thread _CleanupRobotCorpses(); +} + +function private _CalculateProjectedGuardPosition( player ) +{ + // Find the closest navmesh position projected out from the reticle. + //forwardVector = VectorScale( AnglesToForward( player GetPlayerAngles() ), 4000 ); + //guardPoint = BulletTrace( player GetEye(), player GetEye() + forwardVector, true, player ); + //return GetClosestPointOnNavMesh( guardPoint["position"], 48 ); + return GetClosestPointOnNavMesh( player.origin, 48 ); +} + +function private _CalculateRobotSpawnPosition( player ) +{ + desiredSpawnPosition = AnglesToForward( player.angles ) * + 72 + player.origin; + + return GetClosestPointOnNavMesh( desiredSpawnPosition, 48 ); +} + +function private _CleanupRobotCorpses() +{ + corpseDeleteTime = 15000; + + while ( true ) + { + deleteCorpses = []; + + foreach ( corpse in GetCorpseArray() ) + { + if ( IsDefined( corpse.birthtime ) && + IsDefined( corpse.archetype ) && + corpse.archetype == "robot" && + ( corpse.birthtime + corpseDeleteTime ) < GetTime() ) + { + deleteCorpses[ deleteCorpses.size ] = corpse; + } + } + + for ( index = 0; index < deleteCorpses.size; index++ ) + { + deleteCorpses[ index ] Delete(); + } + + wait ( corpseDeleteTime / 1000 ) / 2; + } +} + +function ConfigureTeamPost( player, isHacked ) +{ + robot = self; + robot.properName = ""; + // Prevent the robot from being damaged based on a hurt trigger when being called in. + robot.ignoreTriggerDamage = true; + + robot.empShutdownTime = ( 750 ); + robot.minWalkDistance = 60; + robot.superSprintDistance = 180; + robot.robotRusherMinRadius = 64; + robot.robotRusherMaxRadius = 120; + robot.allowPushActors = false; + robot.chargeMeleeDistance = 0; // Disable charge melee, more effective to shoot. + robot.fovcosine = 0; // 360 degree field of view + robot.fovcosinebusy = 0; // 360 degree field of view even when busy + robot.MaxSightDistSqrd = ( (2000) * (2000) ); + + Blackboard::SetBlackBoardAttribute( robot, "_robot_mode", "combat" ); + + // Disable head gibbing. + robot.gib_state = (0 | ( 8 & ( ( 1 << 9 ) - 1 ) )); + robot clientfield::set( "gib_state", robot.gib_state ); + + _ConfigureRobotTeam( robot, player, isHacked ); + + robot ai::set_behavior_attribute( "can_become_crawler", false ); + robot ai::set_behavior_attribute( "can_be_meleed", false ); + robot ai::set_behavior_attribute( "can_initiateaivsaimelee", false ); + robot ai::set_behavior_attribute( "supports_super_sprint", true ); +} + +function private _ConfigureRobotTeam( robot, player, isHacked ) +{ + if ( isHacked ) + { + lightsState = 3; + } + else + { + lightsState = 0; + } + robot ai::set_behavior_attribute( "robot_lights", lightsState ); + robot thread WatchCombatRobotOwnerDisconnect( player ); + + if ( !isdefined( robot.objective ) ) + { + robot.objective = GetEquipmentHeadObjective( GetWeapon( "combat_robot_marker" ) ); + } + + robot thread _WatchModeSwap( robot, player ); + robot thread _Underwater( robot ); +} + + +function private _CreateGuardMarker( robot, position ) +{ + owner = robot.owner; + guardMarker = spawn( "script_model", ( 0, 0, 0 ) ); + guardMarker.origin = position; + guardMarker entityheadicons::setEntityHeadIcon( owner.pers["team"], owner, undefined, &"airdrop_combatrobot" ); + + return guardMarker; +} + +function private _DestroyGuardMarker( robot ) +{ + if ( isdefined( robot.guardMarker ) ) + { + robot.guardMarker delete(); + } +} + +function private _Underwater( robot ) +{ + robot endon( "death" ); + + while ( true ) + { + if ( ( robot.origin[2] + 72 / 2.0 ) <= GetWaterHeight( robot.origin ) ) + { + robot ASMSetAnimationRate( 0.85 ); + } + else + { + robot ASMSetAnimationRate( 1.0 ); + } + + wait 0.1; + } +} + +function private _Escort( robot ) +{ + robot endon( "death" ); + + robot.escorting = true; + robot.guarding = false; + + _DestroyGuardMarker( robot ); + + while ( robot.escorting ) + { + attackingEnemy = false; + + if ( IsDefined( robot.enemy ) && IsAlive( robot.enemy ) ) + { + if ( ( robot LastKnownTime( robot.enemy ) + 10000 ) >= GetTime() ) + { + robot ai::set_behavior_attribute( "move_mode", "rusher" ); + + attackingEnemy = true; + } + else + { + robot ClearEnemy(); + } + } + + if ( !attackingEnemy && IsDefined( robot.owner ) && IsAlive( robot.owner ) ) + { + lookAheadTime = 1.0; + predicitedPosition = + robot.owner.origin + VectorScale( robot.owner GetVelocity(), lookAheadTime ); + + robot ai::set_behavior_attribute( "escort_position", predicitedPosition ); + robot ai::set_behavior_attribute( "move_mode", "escort" ); + } + + wait 1; + } +} + +function private _IgnoreUnattackableEnemy( robot, enemy ) +{ + robot endon( "death" ); + + robot SetIgnoreEnt( enemy, true ); + + wait 5000 / 1000; + + robot SetIgnoreEnt( enemy, false ); +} + +function private _GuardPosition( robot, position ) +{ + robot endon( "death" ); + + robot.goalradius = 1000; + robot SetGoal( position ); + + robot.escorting = false; + robot.guarding = true; + + _DestroyGuardMarker( robot ); + + robot.guardMarker = _CreateGuardMarker( robot, position ); + + while ( robot.guarding ) + { + attackingEnemy = false; + + if ( IsDefined( robot.enemy ) && IsAlive( robot.enemy ) ) + { + if ( ( robot LastKnownTime( robot.enemy ) + 10000 ) >= GetTime() ) + { + // Robot still within goalradius, continue pursuit. + robot ai::set_behavior_attribute( "move_mode", "rusher" ); + + attackingEnemy = true; + } + else + { + robot ClearEnemy(); + } + } + + if ( !attackingEnemy ) + { + robot ai::set_behavior_attribute( "move_mode", "guard" ); + } + + wait 1; + } +} + +function _WatchModeSwap( robot, player ) +{ + robot endon( "death" ); + + nextSwitchTime = GetTime(); + + while ( true ) + { + {wait(.05);}; + + if( !isdefined( robot.useTrigger ) ) + continue; + + robot.useTrigger waittill( "trigger" ); + + if ( nextSwitchTime <= GetTime() && IsAlive( player ) ) + { + if ( ( isdefined( robot.guarding ) && robot.guarding ) ) + { + robot.guarding = false; + robot.escorting = true; + + player playsoundtoplayer( "uin_mp_combat_bot_escort", player ); + robot thread _Escort( robot ); + if( isdefined( robot.useTrigger ) ) + robot.useTrigger SetHintString( &"KILLSTREAK_COMBAT_ROBOT_GUARD_HINT" ); + + if( isdefined( robot.markerFXHandle ) ) + robot.markerFXHandle delete(); + } + else + { + navGuardPosition = _CalculateProjectedGuardPosition( player ); + + if ( IsDefined( navGuardPosition ) ) + { + robot.guarding = true; + robot.escorting = false; + + player playsoundtoplayer( "uin_mp_combat_bot_guard", player ); + robot thread _GuardPosition( robot, navGuardPosition ); + if( isdefined( robot.useTrigger ) ) + robot.useTrigger SetHintString( &"KILLSTREAK_COMBAT_ROBOT_ESCORT_HINT" ); + + if( isdefined( robot.markerFXHandle ) ) + robot.markerFXHandle delete(); + + params = level.killstreakBundle["combat_robot"]; + if( isdefined( params.ksCombatRobotPatrolFX ) ) + { + point = player.origin; + if( !isdefined( point ) ) + point = navGuardPosition; + + robot.markerFXHandle = SpawnFx( params.ksCombatRobotPatrolFX, point + ( 0, 0, 3 ), ( 0, 0, 1 ), ( 1, 0, 0 ) ); + robot.markerFXHandle.team = player.team; + TriggerFX( robot.markerFXHandle ); + + robot.markerFXHandle SetInvisibleToAll(); + robot.markerFXHandle SetVisibleToPlayer( player ); + } + } + else + { + player iPrintLnBold( &"KILLSTREAK_COMBAT_ROBOT_PATROL_FAIL" ); + } + } + + robot notify("bhtn_action_notify", "modeSwap"); + + nextSwitchTime = GetTime() + 1000; + } + } +} + +function ActivateCombatRobot( killstreak ) +{ + player = self; + team = self.team; + + if( !self supplydrop::isSupplyDropGrenadeAllowed( killstreak ) ) + { + return false; + } + + killstreak_id = self killstreakrules::killstreakStart( killstreak, team, false, false ); + if ( killstreak_id == -1 ) + { + return false; + } + + context = SpawnStruct(); + context.prolog = &Prolog; + context.epilog = &Epilog; + + context.hasFlares = 1; + context.radius = level.killstreakCoreBundle.ksAirdropRobotRadius; + context.dist_from_boundary = 18; + context.max_dist_from_location = 4; + context.perform_physics_trace = true; + context.drop_from_goal_distance2d = 96; // combat robot doesn't need this value to be strict (note: drop ship related) + context.isLocationGood = &supplydrop::IsLocationGood; + context.objective = &"airdrop_combatrobot"; + context.killstreakRef = killstreak; + context.validLocationSound = level.killstreakCoreBundle.ksValidCombatRobotLocationSound; + context.vehiclename = "combat_robot_dropship"; + context.killstreak_id = killstreak_id; + context.tracemask = (1 << 0) | (1 << 2); + + // This offset is specific to the exit vtol animation of the combat rider. + context.dropOffset = (0, -120, 0); + + result = self supplydrop::useSupplyDropMarker( killstreak_id, context ); + + if ( !isdefined(result) || !result ) + { + killstreakrules::killstreakStop( killstreak, team, killstreak_id ); + return false; + } + + self killstreaks::play_killstreak_start_dialog( "combat_robot", self.team, killstreak_id ); + self killstreakrules::displayKillstreakStartTeamMessageToAll( "combat_robot" ); + + self AddWeaponStat( GetWeapon( "combat_robot_marker" ), "used", 1 ); + + return result; +} + + +function DropKillThread() +{ + robot = self; + robot endon( "death" ); + robot endon( "combat_robot_land" ); + + while( true ) + { + robot supplydrop::is_touching_crate(); + robot supplydrop::is_clone_touching_crate(); + {wait(.05);}; + } +} + +function WatchHelicopterDeath( context ) +{ + helicopter = self; + helicopter waittill( "death" ); + + callback::callback( #"on_vehicle_killed" ); + + if( isdefined( context.marker ) ) + { + context.marker delete(); + context.marker = undefined; + + if( isdefined( context.markerFXHandle ) ) + { + context.markerFXHandle delete(); + context.markerFXHandle = undefined; + } + supplydrop::DelDropLocation( context.killstreak_id ); + } +} + +function Prolog( context ) +{ + helicopter = self; + player = helicopter.owner; + + spawnPosition = ( 0,0,0 ); + spawnAngles = ( 0,0,0 ); + + combatRobot = SpawnActor( + "spawner_bo3_robot_grunt_assault_mp", + spawnPosition, + spawnAngles, + "", + true ); + combatRobot.missileTrackDamage = 0; + combatRobot killstreaks::configure_team( "combat_robot", context.killstreak_id, player, "small_vehicle", undefined, &ConfigureTeamPost ); + combatRobot killstreak_hacking::enable_hacking( "combat_robot", undefined, &HackedCallbackPost ); + combatRobot thread _Escort( combatRobot ); + + combatRobot thread WatchCombatRobotHelicopterHacked( helicopter ); + combatRobot thread WatchCombatRobotShutdown(); + combatRobot thread WatchCombatRobotDeath(); + combatRobot thread killstreaks::WaitForTimeout( "combat_robot", ( 90000 ), &OnCombatRobotTimeout, "combat_robot_shutdown" ); + combatRobot thread sndWatchCombatRobotVoxNotifies(); + //combatRobot thread debugThread(); + + helicopter thread WatchHelicopterDeath( context ); + helicopter.unloadTimeout = 6; + + killstreak_detect::killstreakTargetSet( combatRobot, ( 0, 0, 50 ) ); + + combatRobot.maxhealth = combatRobot.health; + + tableHealth = killstreak_bundles::get_max_health( "combat_robot" ); + + if ( isdefined( tableHealth ) ) + { + combatRobot.maxhealth = tableHealth; + } + + combatRobot.health = combatRobot.maxhealth; + combatRobot.treat_owner_damage_as_friendly_fire = true; + combatRobot.ignore_team_kills = true; + combatRobot.remoteMissileDamage = combatRobot.maxhealth + 1; + combatRobot.rocketDamage = combatRobot.maxhealth / 2 + 1; + combatRobot thread heatseekingmissile::MissileTarget_ProximityDetonateIncomingMissile("death"); + combatRobot clientfield::set( "enemyvehicle", 1 ); + combatRobot.soundmod = "drone_land"; + + AiUtility::AddAIOverrideDamageCallback( combatRobot, &combatRobotDamageOverride ); + + combatRobot.vehicle = helicopter; + combatRobot.vehicle.ignore_seat_check = true; + combatRobot vehicle::get_in( helicopter , "driver", true ); + + combatRobot.overrideDropPosition = player.markerPosition; + + combatRobot thread WatchCombatRobotLanding(); + combatRobot thread sndWatchExit(); + combatRobot thread sndWatchLanding(); + combatRobot thread sndWatchActivate(); + + foreach( player in level.players ) + { + combatRobot respectNotTargetedByRobotPerk( player ); + } + + callback::on_spawned( &respectNotTargetedByRobotPerk, combatRobot ); + context.robot = combatRobot; +} + +function respectNotTargetedByRobotPerk( player ) +{ + combatRobot = self; + combatRobot setignoreent( player, player hasperk( "specialty_nottargetedbyrobot" ) ); +} + +function Epilog( context ) +{ + helicopter = self; + + context.robot thread DropKillThread(); + context.robot.startTime = GetTime() + ( 750 ); // set killcam to start a time offset from when drop ship arrives + thread CleanupThread( context ); + + /# debug_delay_robot_deploy(); #/ + + helicopter WaitThenSetDeleteAfterDestructionWaitTime( 0.8, (isdefined(self.unloadTimeout)?self.unloadTimeout:0) + 0.1 ); + + helicopter vehicle::unload( "all", undefined, true, 0.8 ); // removes robot as rider so that it doesn't +} + +/# +function debug_delay_robot_deploy() +{ + seconds_to_wait = GetDvarInt( "scr_combat_robot_wait_to_deploy", 0 ); // 14 seconds is good to shoot down deploy ship given 3 flares and 2 rockets to kill and sustained ammo + while ( seconds_to_wait > 0 ) + { + IPrintLnBold( "Remaining time: " + seconds_to_wait ); + wait 1; + seconds_to_wait--; + + if ( seconds_to_wait == 0 ) + IPrintLnBold( "Jump!!" ); + } +} +#/ + +function WaitThenSetDeleteAfterDestructionWaitTime( set_wait_time, delete_after_destruction_wait_time ) +{ + wait set_wait_time; + + if ( isdefined( self ) ) + { + self.delete_after_destruction_wait_time = delete_after_destruction_wait_time; + } +} + +function HackedCallbackPost( hacker ) +{ + robot = self; + robot ClearEnemy(); + robot SetupCombatRobotHintTrigger( hacker ); +} + + +function WatchCombatRobotHelicopterHacked( helicopter ) +{ + robot = self; + robot endon( "death" ); + robot endon( "killstreak_hacked" ); + robot endon( "combat_robot_land" ); + + helicopter endon( "death" ); + + helicopter waittill( "killstreak_hacked", hacker ); + + if( robot flagsys::get( "in_vehicle" ) == false ) + return; + + robot [[ robot.killstreak_hackedCallback ]]( hacker ); +} + +function CleanupThread( context ) +{ + robot = context.robot; + while( isdefined( robot ) && isdefined( context.marker ) && ( robot flagsys::get( "in_vehicle" ) ) ) + { + wait 1; + } + if( isdefined( context.marker ) ) + { + context.marker delete(); + context.marker = undefined; + + if( isdefined( context.markerFXHandle ) ) + { + context.markerFXHandle delete(); + context.markerFXHandle = undefined; + } + supplydrop::DelDropLocation( context.killstreak_id ); + } +} + +function WatchCombatRobotDeath() +{ + combatRobot = self; + combatRobot endon( "combat_robot_shutdown" ); + callback::remove_on_spawned( &respectNotTargetedByRobotPerk, combatRobot ); + combatRobot waittill( "death", attacker, damageFromUnderneath, weapon ); + // combatRobot waittill( "death", attacker, damageFromUnderneath, weapon, point, dir, modType ); + + attacker = self [[ level.figure_out_attacker ]]( attacker ); + + if ( isdefined( attacker ) && IsPlayer( attacker ) && ( !isdefined( combatRobot.owner ) || combatRobot.owner util::IsEnemyPlayer( attacker ) ) ) + { + attacker challenges::destroyScoreStreak( weapon, false, true ); + attacker challenges::destroyNonAirScoreStreak_PostStatsLock( weapon ); + scoreevents::processScoreEvent( "destroyed_combat_robot", attacker, combatRobot.owner, weapon ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_COMBAT_ROBOT", attacker.entnum ); + } + + combatRobot killstreaks::play_destroyed_dialog_on_owner( "combat_robot", combatRobot.killstreak_id ); + + combatRobot notify( "combat_robot_shutdown" ); +} + +function WatchCombatRobotLanding() +{ + robot = self; + robot endon( "death" ); + robot endon( "combat_robot_shutdown" ); + + // wait for landing + while( robot flagsys::get( "in_vehicle" ) ) + { + wait 1; + } + + robot notify( "combat_robot_land" ); + + robot.ignoreTriggerDamage = false; + + // only check if on nav mesh after finishing traversals + while ( isdefined( robot.traverseStartNode ) ) + { + robot waittill( "traverse_end" ); + } + + v_on_navmesh = GetClosestPointOnNavMesh( robot.origin, 50, 20 ); + ///#sphere( robot.origin, 5, ( 1, 0, 0 ), 1, true, 10, 200 );#/ + + if ( isdefined ( v_on_navmesh ) ) + { + player = robot.owner; + + robot SetupCombatRobotHintTrigger( player ); + } + else + { + robot notify( "combat_robot_shutdown" ); + } +} + +function SetupCombatRobotHintTrigger( player ) +{ + robot = self; + if ( isdefined( robot.useTrigger ) ) + { + robot.useTrigger delete(); + } + robot.useTrigger = spawn( "trigger_radius_use", player.origin, 32, 32 ); + robot.useTrigger EnableLinkTo(); + robot.useTrigger LinkTo( player ); + robot.useTrigger SetHintLowPriority( true ); + robot.useTrigger SetCursorHint( "HINT_NOICON" ); + robot.useTrigger SetHintString( &"KILLSTREAK_COMBAT_ROBOT_GUARD_HINT" ); + + robot.useTrigger SetTeamForTrigger( player.team ); + robot.useTrigger.team = player.team; + + player ClientClaimTrigger( robot.useTrigger ); + player.remoteControlTrigger = robot.useTrigger; + robot.useTrigger.ClaimedBy = player; +} + +function WatchCombatRobotOwnerDisconnect( player ) +{ + combatRobot = self; + combatRobot notify( "WatchCombatRobotOwnerDisconnect_singleton" ); + combatRobot endon( "WatchCombatRobotOwnerDisconnect_singleton" ); + combatRobot endon( "combat_robot_shutdown" ); + + player util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); + combatRobot notify( "combat_robot_shutdown" ); +} + +function private _corpseWatcher() +{ + archetype = self.archetype; + self waittill("actor_corpse", corpse); + corpse clientfield::set("arch_actor_fire_fx", 3); +} + +function private _explodeRobot( combatRobot ) +{ + combatRobot clientfield::set("arch_actor_fire_fx", 1); + clientfield::set( + "robot_mind_control_explosion", 1 ); + combatRobot thread _corpseWatcher(); + + if ( RandomInt( 100 ) >= 50 ) + GibServerUtils::GibLeftArm( combatRobot ); + else + GibServerUtils::GibRightArm( combatRobot ); + + GibServerUtils::GibLegs( combatRobot ); + GibServerUtils::GibHead( combatRobot ); + + velocity = combatRobot GetVelocity() * ( 1 / 8 ); + + combatRobot StartRagdoll(); + combatRobot LaunchRagdoll( + ( velocity[0] + RandomFloatRange( -20, 20 ), + velocity[1] + RandomFloatRange( -20, 20 ), + RandomFloatRange( 60, 80 ) ), + "j_mainroot" ); +} + +function OnCombatRobotTimeout() +{ + combatRobot = self; + + combatRobot killstreaks::play_pilot_dialog_on_owner( "timeout", "combat_robot" ); + + combatRobot ai::set_behavior_attribute( "shutdown", true ); + + wait RandomFloatRange( 3.0, 4.5 ); + + _explodeRobot( combatRobot ); + + params = level.killstreakBundle["combat_robot"]; + + if( isdefined( params.ksExplosionFX ) ) + { + PlayFXOnTag( params.ksExplosionFX, combatRobot, "tag_origin" ); + } + Target_Remove( combatRobot ); + + if(!isdefined(params.ksExplosionOuterRadius))params.ksExplosionOuterRadius=200; + if(!isdefined(params.ksExplosionInnerRadius))params.ksExplosionInnerRadius=1; + if(!isdefined(params.ksExplosionOuterDamage))params.ksExplosionOuterDamage=25; + if(!isdefined(params.ksExplosionInnerDamage))params.ksExplosionInnerDamage=350; + if(!isdefined(params.ksExplosionMagnitude))params.ksExplosionMagnitude=1; + + PhysicsExplosionSphere( combatRobot.origin, + params.ksExplosionOuterRadius, + params.ksExplosionInnerRadius, + params.ksExplosionMagnitude, + params.ksExplosionOuterDamage, + params.ksExplosionInnerDamage ); + + if( isdefined( combatRobot.owner ) ) + { + RadiusDamage( combatRobot.origin, + params.ksExplosionOuterRadius, + params.ksExplosionInnerDamage, + params.ksExplosionOuterDamage, + combatRobot.owner, + "MOD_EXPLOSIVE", + GetWeapon( "combat_robot_marker" ) ); + + if( isdefined( params.ksExplosionRumble ) ) + combatRobot.owner PlayRumbleOnEntity( params.ksExplosionRumble ); + } + + wait( 0.2 ); + + combatRobot notify( "combat_robot_shutdown" ); +} + +function WatchCombatRobotShutdown() +{ + combatRobot = self; + combatRobotTeam = combatRobot.originalteam; + combatRobotKillstreakId = combatRobot.killstreak_id; + combatRobot waittill( "combat_robot_shutdown" ); + + combatRobot playsound ("evt_combat_bot_mech_fail_explode"); + + if( isdefined( combatRobot.useTrigger ) ) + combatRobot.useTrigger delete(); + + if( isdefined( combatRobot.markerFXHandle ) ) + combatRobot.markerFXHandle delete(); + + _DestroyGuardMarker( combatRobot ); + + killstreakrules::killstreakStop( "combat_robot", combatRobotTeam, combatRobotKillstreakId ); + + if( isdefined( combatRobot ) ) + { + if( Target_IsTarget( combatRobot ) ) + Target_Remove( combatRobot ); + if( !level.gameEnded ) // kill and do damage do nothing after game end + { + if( combatRobot flagsys::get( "in_vehicle" ) ) + combatRobot Unlink(); + combatRobot Kill(); + } + } +} + +function sndWatchCombatRobotVoxNotifies() +{ + combatRobot = self; + combatRobot endon( "combat_robot_shutdown" ); + combatRobot endon( "death" ); + + combatRobot PlaySoundOnTag( "vox_robot_chatter", "j_head" ); + + while( 1 ) + { + soundAlias = undefined; + combatRobot waittill("bhtn_action_notify", notify_string); + + switch( notify_string ) + { + case "charge": + case "attack_melee": + case "attack_kill": + case "modeSwap": + soundAlias = "vox_robot_chatter"; + break; + } + + if( isdefined( soundAlias ) ) + { + combatRobot PlaySoundOnTag( soundAlias, "j_head" ); + wait(1.2); + } + } +} +function sndWatchExit() +{ + combatRobot = self; + combatRobot endon( "combat_robot_shutdown" ); + combatRobot endon( "death" ); + + combatRobot waittill( "exiting_vehicle" ); + + combatRobot playsound( "veh_vtol_supply_robot_launch" ); +} +function sndWatchLanding() +{ + combatRobot = self; + combatRobot endon( "combat_robot_shutdown" ); + combatRobot endon( "death" ); + + combatRobot waittill( "falling", falltime ); + + wait_time = falltime - .5; + + if ( wait_time > 0 ) + wait( wait_time ); + + combatRobot playsound( "veh_vtol_supply_robot_land" ); +} +function sndWatchActivate() +{ + combatRobot = self; + combatRobot endon( "combat_robot_shutdown" ); + combatRobot endon( "death" ); + + combatRobot waittill( "landing" ); + wait(.1); + combatRobot playsound( "veh_vtol_supply_robot_activate" ); +} + +function combatRobotDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex, modelIndex ) +{ + combatRobot = self; + + if( combatRobot flagsys::get( "in_vehicle" ) && ( sMeansOfDeath == "MOD_TRIGGER_HURT" ) ) // the dropship goes through hurt triggers sometimes + iDamage = 0; + else + iDamage = killstreaks::OnDamagePerWeapon( "combat_robot", eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, self.maxhealth, undefined, self.maxhealth*0.4, undefined, 0, undefined, true, 1.0 ); + + combatRobot.missileTrackDamage += iDamage; + + if ( iDamage > 0 && isdefined( eAttacker ) ) + { + if ( isPlayer( eAttacker) ) + { + if ( isdefined( combatRobot.owner ) ) + { + challenges::combat_robot_damage( eAttacker, combatRobot.Owner ); + } + } + } + return iDamage; +} diff --git a/mp/killstreaks/_counteruav.csc b/mp/killstreaks/_counteruav.csc new file mode 100644 index 0000000..11104fc --- /dev/null +++ b/mp/killstreaks/_counteruav.csc @@ -0,0 +1,26 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + + +#namespace counteruav; + +function autoexec __init__sytem__() { system::register("counteruav",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "toplayer", "counteruav", 1, 1, "int", &CounterUAVChanged, !true, true ); +} + +function CounterUAVChanged( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + player = GetLocalPlayer( localClientNum ); + assert( isdefined( player ) ); + + player SetEnemyGlobalScrambler( newVal ); +} diff --git a/mp/killstreaks/_counteruav.gsc b/mp/killstreaks/_counteruav.gsc new file mode 100644 index 0000000..39a999a --- /dev/null +++ b/mp/killstreaks/_counteruav.gsc @@ -0,0 +1,684 @@ +#using scripts\codescripts\struct; + +#using scripts\mp\_util; +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_helicopter; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_satellite; +#using scripts\mp\teams\_teams; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_heatseekingmissile; +#using scripts\shared\weapons\_weaponobjects; + + + + + + + + + + + + +#namespace counteruav; + +#precache( "string", "KILLSTREAK_COUNTERUAV_INBOUND" ); +#precache( "string", "KILLSTREAK_COUNTERUAV_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_EARNED_COUNTERUAV" ); +#precache( "string", "KILLSTREAK_DESTROYED_COUNTERUAV" ); +#precache( "string", "KILLSTREAK_COUNTERUAV_HACKED" ); +#precache( "string", "mpl_killstreak_radar" ); + + +function init() +{ + level.activeCounterUAVs = []; + level.counter_uav_positions = GenerateRandomPoints( ( 20 ) ); + level.counter_uav_position_index = []; + level.counter_uav_offsets = BuildOffsetList( ( 0, 0, 0 ), ( 3 ), ( 450 ), ( 450 ) ); + + if ( level.teamBased ) + { + foreach( team in level.teams ) + { + level.activeCounterUAVs[ team ] = 0; + level.counter_uav_position_index[ team ] = 0; + + level thread MovementManagerThink( team ); + } + } + else + { + level.activeCounterUAVs = []; + } + + level.activePlayerCounterUAVs = []; + + level.counter_uav_entities = []; + + if( tweakables::getTweakableValue( "killstreak", "allowcounteruav" ) ) + { + killstreaks::register( "counteruav", "counteruav", "killstreak_counteruav", "counteruav_used", &ActivateCounterUAV ); + killstreaks::register_strings( "counteruav", &"KILLSTREAK_EARNED_COUNTERUAV", &"KILLSTREAK_COUNTERUAV_NOT_AVAILABLE", &"KILLSTREAK_COUNTERUAV_INBOUND", undefined, &"KILLSTREAK_COUNTERUAV_HACKED" ); + killstreaks::register_dialog( "counteruav", "mpl_killstreak_radar", "counterUavDialogBundle", "counterUavPilotDialogBundle", "friendlyCounterUav", "enemyCounterUav", "enemyCounterUavMultiple", "friendlyCounterUavHacked", "enemyCounterUavHacked", "requestCounterUav", "threatCounterUav" ); + } + + clientfield::register( "toplayer", "counteruav", 1, 1, "int" ); + level thread WatchCounterUAVs(); + + callback::on_connect( &OnPlayerConnect ); + callback::on_spawned( &OnPlayerSpawned ); + callback::on_joined_team( &OnPlayerJoinedTeam ); + +/# + if ( GetDvarInt( "scr_cuav_offset_debug" ) ) + level thread WaitAndDebugDrawOffsetList(); +#/ + +} + +function OnPlayerConnect() +{ + self.entNum = self getEntityNumber(); + + if( !level.teamBased ) + { + level.activeCounterUAVs[ self.entNum ] = 0; + level.counter_uav_position_index[ self.entNum ] = 0; + self thread MovementManagerThink( self.entnum ); + } + + level.activePlayerCounterUAVs[ self.entNum ] = 0; +} + +function OnPlayerSpawned() +{ + if( self EnemyCounterUAVActive() ) + { + self clientfield::set_to_player( "counteruav", 1 ); + } + else + { + self clientfield::set_to_player( "counteruav", 0 ); + } +} + +function GenerateRandomPoints( count ) +{ + points = []; + + for( i = 0; i < count; i++ ) + { + point = airsupport::GetRandomMapPoint( + (isdefined(level.cuav_map_x_offset)?level.cuav_map_x_offset:0), + (isdefined(level.cuav_map_y_offset)?level.cuav_map_y_offset:0), + (isdefined(level.cuav_map_x_percentage)?level.cuav_map_x_percentage:( 0.50 )), + (isdefined(level.cuav_map_y_percentage)?level.cuav_map_y_percentage:( 0.50 )) ); + + minFlyHeight = airsupport::getMinimumFlyHeight(); + point = point + ( 0, 0, minFlyHeight + (isdefined(level.counter_uav_position_z_offset)?level.counter_uav_position_z_offset:( 1000 )) ); + points[ i ] = point; + } + + return points; +} + +function MovementManagerThink( teamOrEntNum ) +{ + while( true ) + { + level waittill( "counter_uav_updated" ); + + activeCount = 0; + + while( level.activeCounterUAVs[ teamOrEntNum ] > 0 ) + { + if( activeCount == 0 ) + { + activeCount = level.activeCounterUAVs[ teamOrEntNum ]; + } + + currentIndex = level.counter_uav_position_index[ teamOrEntNum ]; + newIndex = currentIndex; + + while( newIndex == currentIndex ) + { + newIndex = RandomIntRange( 0, ( 20 ) ); + } + + destination = level.counter_uav_positions[ newIndex ]; + level.counter_uav_position_index[ teamOrEntNum ] = newIndex; + + level notify( "counter_uav_move_" + teamOrEntNum ); + wait( ( 5 ) + RandomIntRange( ( 5 ), ( 10 ) ) ); + } + } +} + +function GetCurrentPosition( teamOrEntNum ) +{ + basePosition = level.counter_uav_positions[ level.counter_uav_position_index[ teamOrEntNum ] ]; + offset = level.counter_uav_offsets[ self.cuav_offset_index ]; + + return basePosition + offset; +} + +function AssignFirstAvailableOffsetIndex() +{ + self.cuav_offset_index = GetFirstAvailableOffsetIndex(); + + MaintainCouterUavEntities(); +} + +function GetFirstAvailableOffsetIndex() +{ + // init available offset array + available_offsets = []; + for( i = 0; i < level.counter_uav_offsets.size; i++ ) + available_offsets[ i ] = true; + + // update available offsets array + foreach( cuav in level.counter_uav_entities ) + { + if ( isdefined( cuav ) ) + { + available_offsets[ cuav.cuav_offset_index ] = false; + } + } + + // return first available + for( i = 0; i < available_offsets.size; i++ ) + { + if ( available_offsets[ i ] ) + return i; + } + + /#util::warning("Max counter-uav available offset slots reached. Using slot 0 for now.");#/ + + return 0; +} + +function MaintainCouterUavEntities() +{ + for( i = level.counter_uav_entities.size; i >= 0; i-- ) + { + if ( !isdefined( level.counter_uav_entities[ i ] ) ) + { + ArrayRemoveIndex( level.counter_uav_entities, i ); + } + } +} + + +/# +function WaitAndDebugDrawOffsetList() +{ + level endon( "game_ended" ); + + wait 10; + DebugDrawOffsetList(); +} + +function DebugDrawOffsetList() +{ + basePosition = level.counter_uav_positions[ 0 ]; + + foreach ( offset in level.counter_uav_offsets ) + { + util::debug_sphere( basePosition + offset, 24, ( 0.95, 0.05, 0.05 ), 0.75, 9999999 ); + } +} +#/ + +function BuildOffsetList( startOffset, depth, offset_x, offset_y ) +{ + offsets = []; + for( col = 0; col < depth; col++ ) + { + itemCount = math::pow( 2, col ); + startingIndex = ( itemCount - 1 ); + + for( i = 0; i < itemCount; i++ ) + { + x = offset_x * col; + + y = 0; + if( itemCount > 1 ) + { + y = ( i * offset_y ); + total_y = offset_y * startingIndex; + y -= ( total_y / 2 ); + } + + offsets[ startingIndex + i ] = startOffset + ( x, y, 0 ); + } + } + + return offsets; +} + +function ActivateCounterUAV() +{ + if( self killstreakrules::isKillstreakAllowed( "counteruav", self.team ) == false ) + { + return false; + } + + killstreak_id = self killstreakrules::killstreakStart( "counteruav", self.team ); + if( killstreak_id == -1 ) + { + return false; + } + + counterUav = SpawnCounterUAV( self, killstreak_id ); + if( !isdefined( counterUav ) ) + { + return false; + } + + counterUAV SetScale( ( 1 ) ); + + counterUav clientfield::set( "enemyvehicle", 1 ); + counterUav.killstreak_id = killstreak_id; + + counterUav thread killstreaks::WaitTillEMP( &DestroyCounterUavByEMP ); + counterUav thread killstreaks::WaitForTimeout( "counteruav", ( 30000 ), &OnTimeout, "delete", "death", "crashing" ); + counterUav thread killstreaks::WaitForTimecheck( ( ( 30000 ) / 2 ), &OnTimecheck, "delete", "death", "crashing" ); + counterUav thread util::WaitTillEndOnThreaded( "death", &DestroyCounterUav, "delete", "leaving" ); + + counterUav SetCanDamage( true ); + counterUav thread killstreaks::MonitorDamage( "counteruav", ( 700 ), &DestroyCounterUAV, ( ( 700 ) * 0.5 ), &OnLowHealth, 0, undefined, true ); + + counterUav PlayLoopSound( "veh_uav_engine_loop", 1 ); + + counterUav thread ListenForMove(); + + self killstreaks::play_killstreak_start_dialog( "counteruav", self.team, killstreak_id ); + counterUav killstreaks::play_pilot_dialog_on_owner( "arrive", "counteruav", killstreak_id ); + counterUav thread killstreaks::player_killstreak_threat_tracking( "counteruav" ); + self AddWeaponStat( GetWeapon( "counteruav" ), "used", 1 ); + + return true; +} + +function HackedPreFunction( hacker ) +{ + cuav = self; + cuav ResetActiveCounterUAV(); +} + +function SpawnCounterUAV( owner, killstreak_id ) +{ + minFlyHeight = airsupport::getMinimumFlyHeight(); + //cuav = spawn( "script_model", airsupport::GetMapCenter() + ( 0, 0, ( minFlyHeight + COUNTER_UAV_POSITION_Z_OFFSET ) ) ); + + cuav = SpawnVehicle( "veh_counteruav_mp", airsupport::GetMapCenter() + ( 0, 0, ( minFlyHeight + (isdefined(level.counter_uav_position_z_offset)?level.counter_uav_position_z_offset:( 1000 )) ) ), ( 0, 0, 0 ), "counteruav" ); + cuav AssignFirstAvailableOffsetIndex(); + + cuav killstreaks::configure_team( "counteruav", killstreak_id, owner, undefined, undefined, &ConfigureTeamPost ); + cuav killstreak_hacking::enable_hacking( "counteruav", &HackedPreFunction, undefined ); + + cuav.targetname = "counteruav"; + + killstreak_detect::killstreakTargetSet( cuav ); + + cuav thread heatseekingmissile::MissileTarget_ProximityDetonateIncomingMissile( "crashing", undefined, true ); + + cuav.maxhealth = ( 700 ); + cuav.health = 99999; + cuav.rocketDamage = ( 700 ) + 1; + + cuav SetDrawInfrared( true ); + + if ( !isdefined( level.counter_uav_entities ) ) level.counter_uav_entities = []; else if ( !IsArray( level.counter_uav_entities ) ) level.counter_uav_entities = array( level.counter_uav_entities ); level.counter_uav_entities[level.counter_uav_entities.size]=cuav;; + + return cuav; +} + + +function ConfigureTeamPost( owner, isHacked ) +{ + cuav = self; + + if ( isHacked == false ) + { + cuav teams::HideToSameTeam(); + } + else + { + cuav SetVisibleToAll(); + } + cuav thread teams::WaitUntilTeamChangeSingleton( owner, "CUAV_watch_team_change", &OnTeamChange, self.entNum, "death", "leaving", "crashing" ); + cuav AddActiveCounterUAV(); +} + + +function ListenForMove() +{ + self endon( "death" ); + self endon( "leaving" ); + + while( true ) + { + self thread CounterUAVMove(); + level util::waittill_any( "counter_uav_move_" + self.team, "counter_uav_move_" + self.ownerEntNum ); + } +} + +function CounterUAVMove() +{ + self endon( "death" ); + self endon( "leaving" ); + level endon( "counter_uav_move_" + self.team ); + + destination = ( 0, 0, 0 ); + + if( level.teamBased ) + { + destination = self GetCurrentPosition( self.team ); + } + else + { + destination = self GetCurrentPosition( self.ownerEntNum ); + } + + lookAngles = VectorToAngles( destination - self.origin ); + rotationAccelerationDuration = ( 0.5 ) * ( 0.2 ); + rotationDecelerationDuration = ( 0.5 ) *( 0.2 ); + + // as a vehicle, we cannot use RotateTo anymore; we'll figure this out soon + // self RotateTo( lookAngles, COUNTER_UAV_ROTATION_DURATION, rotationAccelerationDuration, rotationDecelerationDuration ); + // self waittill( "rotatedone" ); + + travelAccelerationDuration = ( 5 ) * ( 0.2 ); + travelDecelerationDuration = ( 5 ) * ( 0.2 ); + //self MoveTo( destination, COUNTER_UAV_SPEED, travelAccelerationDuration, travelDecelerationDuration ); + self SetVehGoalPos( destination, true, false ); +} + +function PlayFx( name ) +{ + self endon( "death" ); + wait ( 0.1 ); + + if ( isdefined( self ) ) + { + PlayFXOnTag( name, self, "tag_origin" ); + } +} + +function OnLowHealth( attacker, weapon ) +{ + self.is_damaged = true; + params = level.killstreakBundle["counteruav"]; + if( isdefined( params.fxLowHealth ) ) + PlayFXOnTag( params.fxLowHealth, self, "tag_origin" ); +} + +function OnTeamChange( entNum, event ) +{ + DestroyCounterUAV( undefined, undefined ); +} + +function OnPlayerJoinedTeam() +{ + HideAllCounterUAVsToSameTeam(); +} + +function OnTimeout() +{ + self.leaving = true; + + self killstreaks::play_pilot_dialog_on_owner( "timeout", "counteruav" ); + + self airsupport::Leave( ( 5 ) ); + wait( ( 5 ) ); + self RemoveActiveCounterUAV(); + Target_Remove( self ); + self delete(); +} + +function OnTimecheck() +{ + self killstreaks::play_pilot_dialog_on_owner( "timecheck", "counteruav", self.killstreak_id ); +} + +function DestroyCounterUavByEMP( attacker, arg ) +{ + DestroyCounterUav( attacker, GetWeapon( "emp" ) ); +} + +function DestroyCounterUAV( attacker, weapon ) +{ + if ( self.leaving !== true ) + { + self killstreaks::play_destroyed_dialog_on_owner( "counteruav", self.killstreak_id ); + } + + attacker = self [[ level.figure_out_attacker ]]( attacker ); + if( isdefined( attacker ) && ( !isdefined( self.owner ) || self.owner util::IsEnemyPlayer( attacker ) ) ) + { + challenges::destroyedAircraft( attacker, weapon, false ); + scoreevents::processScoreEvent( "destroyed_counter_uav", attacker, self.owner, weapon ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_COUNTERUAV", attacker.entnum ); + attacker challenges::addFlySwatterStat( weapon, self ); + } + + self PlaySound( "evt_helicopter_midair_exp" ); + self RemoveActiveCounterUAV(); + + if ( Target_IsTarget( self ) ) + { + Target_Remove( self ); + } + + self thread DeleteCounterUAV(); +} + +function DeleteCounterUAV() +{ + self notify( "crashing" ); + + params = level.killstreakBundle["counteruav"]; + if( isdefined( params.ksExplosionFX ) && isdefined( self ) ) + self thread PlayFx( params.ksExplosionFX ); + + wait( 0.1 ); + + if ( isdefined( self ) ) + { + self setModel( "tag_origin" ); + } + + wait( 0.2 ); + + if ( isdefined( self ) ) + { + self notify( "delete" ); + self delete(); + } +} + +function EnemyCounterUAVActive() +{ + if( level.teamBased ) + { + foreach( team in level.teams ) + { + if( team == self.team ) + { + continue; + } + + if( TeamHasActiveCounterUAV( team ) ) + { + return true; + } + } + } + else + { + enemies = self teams::GetEnemyPlayers(); + foreach( player in enemies ) + { + if( player HasActiveCounterUAV() ) + { + return true; + } + } + } + + return false; +} + + +function HasActiveCounterUAV() +{ + return ( level.activeCounterUAVs[ self.entNum ] > 0 ); +} + +function TeamHasActiveCounterUAV( team ) +{ + return ( level.activeCounterUAVs[ team ] > 0 ); +} + +function HasIndexActiveCounterUAV( team_or_entnum ) +{ + return ( level.activeCounterUAVs[ team_or_entnum ] > 0 ); +} + + +function AddActiveCounterUAV() +{ + if ( level.teamBased ) + { + level.activeCounterUAVs[ self.team ]++; + + foreach( team in level.teams ) + { + if ( team == self.team ) + { + continue; + } + + if( satellite::HasSatellite( team ) ) + { + self.owner challenges::blockedSatellite(); + } + } + } + else + { + level.activeCounterUAVs[ self.ownerEntnum ]++; + + keys = getarraykeys( level.activeCounterUAVs ); + for ( i = 0; i < keys.size; i++ ) + { + if( keys[i] == self.ownerEntNum ) + { + continue; + } + + if( satellite::HasSatellite( keys[i] ) ) + { + self.owner challenges::blockedSatellite(); + break; + } + } + } + + level.activePlayerCounterUAVs[ self.ownerEntNum ]++; + + level notify( "counter_uav_updated" ); +} + +function RemoveActiveCounterUAV() +{ + cuav = self; + cuav ResetActiveCounterUAV(); + cuav killstreakrules::killstreakStop( "counteruav", self.originalteam, self.killstreak_id ); +} + +function ResetActiveCounterUAV() +{ + if ( level.teamBased ) + { + level.activeCounterUAVs[ self.team ]--; + assert( level.activeCounterUAVs[ self.team ] >= 0 ); + if ( level.activeCounterUAVs[ self.team ] < 0 ) + { + level.activeCounterUAVs[ self.team ] = 0; + } + } + else if ( isdefined( self.owner ) ) + { + assert( isdefined( self.ownerEntNum ) ); + if ( !isdefined( self.ownerEntNum ) ) + { + self.ownerEntNum = self.owner getEntityNumber(); + } + + level.activeCounterUAVs[self.ownerEntNum ]--; + + assert( level.activeCounterUAVs[ self.ownerEntNum ] >= 0 ); + if ( level.activeCounterUAVs[ self.ownerEntNum ] < 0 ) + { + level.activeCounterUAVs[ self.ownerEntNum ] = 0; + } + } + + level.activePlayerCounterUAVs[ self.ownerEntNum ]--; + + level notify ( "counter_uav_updated" ); +} + + +function WatchCounterUAVs() +{ + while( true ) + { + level waittill( "counter_uav_updated" ); + + foreach( player in level.players ) + { + if( player EnemyCounterUAVActive() ) + { + player clientfield::set_to_player( "counteruav", 1 ); + } + else + { + player clientfield::set_to_player( "counteruav", 0 ); + } + } + } +} + +function HideAllCounterUAVsToSameTeam() +{ + foreach( counteruav in level.counter_uav_entities ) + { + if ( isdefined( counteruav ) ) + { + counteruav teams::HideToSameTeam(); + + } + } +} diff --git a/mp/killstreaks/_dart.csc b/mp/killstreaks/_dart.csc new file mode 100644 index 0000000..244bf6b --- /dev/null +++ b/mp/killstreaks/_dart.csc @@ -0,0 +1,59 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\visionset_mgr_shared; + + + + +#using scripts\shared\visionset_mgr_shared; + + + + + +#namespace dart; + +function autoexec __init__sytem__() { system::register("dart",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "toplayer", "dart_update_ammo", 1, 2, "int", &update_ammo, !true, !true ); + clientfield::register( "toplayer", "fog_bank_3", 1, 1, "int", &fog_bank_3_callback, !true, !true); + + level.dartBundle = struct::get_script_bundle( "killstreak", "killstreak_dart" ); + vehicle::add_vehicletype_callback( level.dartBundle.ksDartVehicle,&spawned ); + visionset_mgr::register_visionset_info( "dart_visionset", 1, 1, undefined, "mp_vehicles_dart" ); + visionset_mgr::register_visionset_info( "sentinel_visionset", 1, 1, undefined, "mp_vehicles_sentinel" ); + visionset_mgr::register_visionset_info( "remote_missile_visionset", 1, 1, undefined, "mp_hellstorm" ); +} + +function update_ammo( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + SetUIModelValue( GetUIModel( GetUIModelForController( localClientNum ), "vehicle.ammo" ), newVal ); + + // /# IPrintLnBold( "Dart Ammo Count: " + newVal ); #/ +} + +function spawned(localClientNum) +{ + self.killstreakBundle = level.dartBundle; +} + +function fog_bank_3_callback(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + if ( oldVal != newVal ) + { + if ( newVal == 1 ) + { + SetWorldFogActiveBank(localClientNum, 4); + } + else + { + SetWorldFogActiveBank(localClientNum, 1); + } + } +} \ No newline at end of file diff --git a/mp/killstreaks/_dart.gsc b/mp/killstreaks/_dart.gsc new file mode 100644 index 0000000..c4837f4 --- /dev/null +++ b/mp/killstreaks/_dart.gsc @@ -0,0 +1,827 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\vehicle_death_shared; +#using scripts\shared\weapons\_hacker_tool; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\math_shared; +#using scripts\shared\weapons\_heatseekingmissile; +#using scripts\mp\killstreaks\_qrdrone; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\shared\vehicle_ai_shared; + +#using scripts\mp\_util; +#using scripts\mp\gametypes\_shellshock; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_remote_weapons; + + + + + + +#using scripts\shared\visionset_mgr_shared; + +#namespace dart; + + + + +#precache( "string", "KILLSTREAK_DART_HACKED" ); +#precache( "string", "KILLSTREAK_DART_EARNED" ); +#precache( "string", "KILLSTREAK_DART_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_DART_INBOUND" ); +#precache( "string", "KILLSTREAK_DART_HACKED" ); +#precache( "string", "KILLSTREAK_DESTROYED_DART" ); +#precache( "string", "mpl_killstreak_dart_strt" ); + +function init() +{ + killstreaks::register( "dart", "dart", "killstreak_dart", "dart_used", &ActivateDart, true) ; + killstreaks::register_strings( "dart", &"KILLSTREAK_DART_EARNED", &"KILLSTREAK_DART_NOT_AVAILABLE", &"KILLSTREAK_DART_INBOUND", undefined, &"KILLSTREAK_DART_HACKED" ); + killstreaks::register_dialog( "dart", "mpl_killstreak_dart_strt", "dartDialogBundle", "dartPilotDialogBundle", "friendlyDart", "enemyDart", "enemyDartMultiple", "friendlyDartHacked", "enemyDartHacked", "requestDart", "threatDart" ); + killstreaks::override_entity_camera_in_demo( "dart", true ); + + killstreaks::register_alt_weapon( "dart", "killstreak_remote" ); + killstreaks::register_alt_weapon( "dart", "dart_blade" ); + killstreaks::register_alt_weapon( "dart", "dart_turret" ); + + clientfield::register( "toplayer", "dart_update_ammo", 1, 2, "int" ); + clientfield::register( "toplayer", "fog_bank_3", 1, 1, "int" ); + + remote_weapons::RegisterRemoteWeapon( "dart", &"", &StartDartRemoteControl, &EndDartRemoteControl, true ); + + visionset_mgr::register_info( "visionset", "dart_visionset", 1, 90, 16, true, &visionset_mgr::ramp_in_out_thread_per_player_death_shutdown, false ); +} + +function wait_dart_timed_out( time ) +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "dart_throw_failed" ); + self endon( "dart_entered" ); + + wait( time ); + + self notify( "dart_throw_timed_out" ); +} + +function wait_for_throw_status() +{ + thread dart::wait_dart_timed_out( 5 ); + + notifyString = self util::waittill_any_return( "death", "disconnect", "dart_entered", "dart_throw_timed_out","dart_throw_failed" ); + + if ( notifyString == "dart_entered" || notifyString == "death" ) + return true; + + return false; +} + +function ActivateDart( killstreakType ) +{ + player = self; + + assert( IsPlayer( player ) ); + + if( !player killstreakrules::isKillstreakAllowed( "dart", player.team ) ) + return false; + + player DisableOffhandWeapons(); + + missileWeapon = player GetCurrentWeapon(); + if( !( isdefined( missileWeapon ) && ( ( missileWeapon.name == "dart" ) || ( missileWeapon.name == "inventory_dart" ) ) ) ) + return false; + + player thread WatchThrow( missileWeapon ); + + notifyString = player util::waittill_any_return( "weapon_change", "grenade_fire", "death", "disconnect", "joined_team", "emp_jammed", "emp_grenaded" ); + + if( notifyString == "death" || notifyString == "emp_jammed" || notifyString == "emp_grenaded" ) + { + ///#iprintln( "death" );#/ + if( player.waitingOnDartThrow ) + player notify( "dart_putaway" ); + + player EnableOffhandWeapons(); + return false; + } + + if( notifyString == "grenade_fire" ) + { + // wait for the killStreakStart results + return player wait_for_throw_status(); + } + + if( notifyString == "weapon_change" ) + { + if( player.waitingOnDartThrow ) + player notify( "dart_putaway" ); + + player EnableOffhandWeapons(); + return false; + } + + return true; +} + +function cleanup_grenade() +{ + self thread waitThenDelete( 0.05 ); + self.origin = self.origin + ( 0, 0, 1000 ); +} + +function WatchThrow( missileWeapon ) +{ + assert( IsPlayer( self ) ); + player = self; + playerEntNum = player.entNum; + + player endon( "disconnect" ); + player endon( "joined_team" ); + // player endon( "death" ); // once the dart is thrown, even after death, the player should control it + player endon( "dart_putaway" ); + + level endon( "game_ended" ); + + player.waitingOnDartThrow = 1; + player waittill( "grenade_fire", grenade, weapon ); + player.waitingOnDartThrow = 0; + + if( weapon != missileWeapon ) + { + self notify("dart_throw_failed"); + return; + } + + trace = player check_launch_space(grenade.origin); + + if ( trace["fraction"] < 1.0 ) + { + self iprintlnbold( &"KILLSTREAK_DART_NOT_AVAILABLE" ); + grenade cleanup_grenade(); + self notify("dart_throw_failed"); + return; + } + + killstreak_id = player killstreakrules::killstreakStart( "dart", player.team, undefined, false ); + if( killstreak_id == (-1) ) + { + grenade cleanup_grenade(); + self notify("dart_throw_failed"); + return; + } + + player.dart_thrown_time = GetTime(); + + player TakeWeapon( missileWeapon ); + + player killstreaks::set_killstreak_delay_killcam( "dart" ); // special case: death while watching does not prevent riding dart, and thus no killcam + player.resurrect_not_allowed_by = "dart"; + + player AddWeaponStat( GetWeapon( "dart" ), "used", 1 ); + level thread popups::DisplayKillstreakTeamMessageToAll( "dart", player ); + + dart = player SpawnDart( grenade, killstreak_id, trace["position"] ); + if( isdefined( dart ) ) + { + player killstreaks::play_killstreak_start_dialog( "dart", player.team, killstreak_id ); + } +} + +function HackedPreFunction( hacker ) +{ + dart = self; + dart.owner util::freeze_player_controls( false ); + visionset_mgr::deactivate( "visionset", "dart_visionset", dart.owner ); + dart.owner clientfield::set_to_player( "fog_bank_3", 0 ); + dart.owner unlink(); + dart clientfield::set( "vehicletransition", 0 ); + dart.owner killstreaks::clear_using_remote(); + dart.owner killstreaks::unhide_compass(); + dart.owner vehicle::stop_monitor_missiles_locked_on_to_me(); + dart.owner vehicle::stop_monitor_damage_as_occupant(); + dart DisableDartMissileLocking(); +} + +function HackedPostFunction( hacker ) +{ + dart = self; + hacker StartDartRemoteControl( dart ); + + hacker killstreak_hacking::set_vehicle_drivable_time_starting_now( dart ); + hacker remote_weapons::UseRemoteWeapon( dart, "dart", false ); + hacker killstreaks::set_killstreak_delay_killcam( "dart" ); +} + +function dart_hacked_health_update( hacker ) +{ + dart = self; + if ( dart.health > dart.hackedhealth ) + { + dart.health = dart.hackedhealth; + } +} + +function check_launch_space(origin) +{ + player_angles = self getPlayerAngles(); + forward = AnglesToForward( player_angles ); + spawn_origin = origin + VectorScale( forward, 50 ); + + radius = 10; + return physicstrace( origin, spawn_origin, ( -radius, -radius, 0 ), ( radius, radius, 2 * radius ), self, (1 << 0) ); +} + +function SpawnDart( grenade, killstreak_id, spawn_origin ) +{ + ///#iprintln( "Spawn Dart" );#/ + player = self; + assert( IsPlayer( player ) ); + playerEntNum = player.entNum; + player_angles = player getPlayerAngles(); + + grenade cleanup_grenade(); + + params = level.killstreakBundle["dart"]; + + if(!isdefined(params.ksDartVehicle))params.ksDartVehicle="veh_dart_mp"; + if(!isdefined(params.ksDartInitialSpeed))params.ksDartInitialSpeed=35; + if(!isdefined(params.ksDartAcceleration))params.ksDartAcceleration=35; + + dart = SpawnVehicle( params.ksDartVehicle, spawn_origin, player_angles, "dynamic_spawn_ai" ); + + //dart thread debug_origin(); + + dart.is_shutting_down = 0; + dart.team = player.team; + dart SetSpeedImmediate( params.ksDartInitialSpeed, params.ksDartAcceleration ); + dart.maxhealth = killstreak_bundles::get_max_health( "dart" ); + dart.health = dart.maxhealth; + dart.hackedhealth = killstreak_bundles::get_hacked_health( "dart" ); + dart.hackedHealthUpdateCallback = &dart_hacked_health_update; + + dart killstreaks::configure_team( "dart", killstreak_id, player, "small_vehicle" ); + dart killstreak_hacking::enable_hacking( "dart", &HackedPreFunction, &HackedPostFunction ); + + dart clientfield::set( "enemyvehicle", 1 ); + dart.killstreak_id = killstreak_id; + dart.hardpointType = "dart"; + dart thread killstreaks::WaitForTimeout( "dart", ( 30 * 1000 ), &stop_remote_weapon, "remote_weapon_end", "death" ); + dart hacker_tool::registerWithhackerTool( ( 50 ), ( 2000 ) ); + dart.overrideVehicleDamage = &dartDamageOverride; + dart.DetonateViaEMP = &emp_damage_cb; + dart.do_scripted_crash = false; + dart.delete_on_death = true; + dart.one_remote_use = true; + dart.vehcheckforpredictedcrash = true; + dart.predictedCollisionTime = 0.2; + dart.glasscollision_alt = true; + dart.damageTaken = 0; + dart.death_enter_cb = &waitRemoteControl; + + Target_Set( dart ); + + dart vehicle::init_target_group(); + dart vehicle::add_to_target_group( dart ); + + dart thread WatchCollision(); + + dart thread WatchDeath(); + + dart thread WatchOwnerNonDeathEvents(); + + dart.forceWaitRemoteControl = true; + + player util::waittill_any( "weapon_change", "death" ); // wait for the killstreak weapon to go away, or the player dies (special case for Dart) + + player remote_weapons::UseRemoteWeapon( dart, "dart", true, true, true ); + + player notify( "dart_entered" ); + + return dart; +} + +function debug_origin() +{ + self endon( "death" ); + while( 1 ) + { + /#sphere( self.origin, 5, ( 1.0, 0, 0 ), 1.0, true, 2, 120 );#/ + {wait(.05);}; + } +} + +function waitRemoteControl() +{ + dart = self; + + remote_controlled = ( isdefined( dart.control_initiated ) && dart.control_initiated ) || ( isdefined( dart.controlled ) && dart.controlled ); + + if( remote_controlled ) + { + notifyString = dart util::waittill_any_return( "remote_weapon_end", "dart_left" ); + if ( isdefined( notifyString ) ) + { + if( notifyString == "remote_weapon_end" ) + dart waittill( "dart_left" ); + else + dart waittill( "remote_weapon_end" ); + } + } + else + dart waittill( "dart_left" ); +} + +function StartDartRemoteControl( dart ) +{ + ///#iprintln( "StartDartRemoteControl" );#/ + player = self; + assert( IsPlayer( player ) ); + + if( !dart.is_shutting_down ) + { + player.dart_thrown_time = undefined; + + ///#iprintln( "UseVehicle" );#/ + dart UseVehicle( player, 0 );/// + player.resurrect_not_allowed_by = undefined; + dart clientfield::set( "vehicletransition", 1 ); + dart thread WatchAmmo(); + dart thread vehicle::monitor_missiles_locked_on_to_me( player ); + dart thread vehicle::monitor_damage_as_occupant( player ); + + player vehicle::set_vehicle_drivable_time_starting_now( ( 30 * 1000 ) ); + player.no_fade2black = true; + + dart.inHeliProximity = false; + + minHeightOverride = undefined; + minz_struct = struct::get( "vehicle_oob_minz", "targetname"); + if( isdefined( minz_struct ) ) + minHeightOverride = minz_struct.origin[2]; + + dart thread qrdrone::QRDrone_watch_distance( ( 2000 ), minHeightOverride ); + dart.distance_shutdown_override = &DartDistanceFailure; + + dart EnableDartMissileLocking(); + visionset_mgr::activate( "visionset", "dart_visionset", self, 1, 90000, 1 ); + player clientfield::set_to_player( "fog_bank_3", 1 ); + } +} + +function EndDartRemoteControl( dart, exitRequestedByOwner ) +{ + dart thread leave_dart(); +} + +function DartDistanceFailure() +{ + thread stop_remote_weapon(); +} + +function stop_remote_weapon( attacker, weapon ) +{ + dart = self; + + dart.DetonateViaEMP = undefined; + + attacker = self [[ level.figure_out_attacker ]]( attacker ); + if ( isdefined( attacker ) && ( !isdefined( dart.owner ) || dart.owner util::IsEnemyPlayer( attacker ) ) ) + { + challenges::destroyedAircraft( attacker, weapon, true ); + attacker challenges::addFlySwatterStat( weapon, self ); + scoreevents::processScoreEvent( "destroyed_dart", attacker, dart.owner, weapon ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_DART", attacker.entnum ); + } + + if( isdefined( attacker ) && attacker != dart.owner ) + { + dart killstreaks::play_destroyed_dialog_on_owner( "dart", dart.killstreak_id ); + } + + dart thread remote_weapons::EndRemoteControlWeaponUse( false ); +} + +function dartDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) +{ + dart = self; + + if( ( sMeansOfDeath == "MOD_TRIGGER_HURT" ) || ( isdefined( dart.is_shutting_down ) && dart.is_shutting_down ) ) + return 0; + + player = dart.owner; + + iDamage = killstreaks::OnDamagePerWeapon( "dart", eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, self.maxhealth, &stop_remote_weapon, self.maxhealth*0.4, undefined, + 0, &emp_damage_cb, true, 1.0 ); + + return iDamage; +} + +function emp_damage_cb( attacker, weapon ) +{ + dart = self; + dart stop_remote_weapon( attacker, weapon ); +} + +function DarPredictedCollision() +{ + self endon( "death" ); + + while( 1 ) + { + self waittill( "veh_predictedcollision", velocity, normal, ent, stype ); + self notify( "veh_collision", velocity, normal, ent, stype ); + + if( stype == "glass" ) + continue; + else + break; + } +} + +function WatchCollision() +{ + dart = self; + dart endon( "death" ); + dart.owner endon( "disconnect" ); + + dart thread DarPredictedCollision(); + + + while( 1 ) + { + dart waittill( "veh_collision", velocity, normal, ent, stype ); + + ///#sphere( dart.origin, 5, ( 1.0, 0, 0 ), 1.0, true, 100, 120 );#/ + if( stype === "glass" ) + continue; + + dart SetSpeedImmediate( 0 ); + dart vehicle_death::death_fx(); + + dart thread stop_remote_weapon(); + break; + } +} + +function WatchDeath() +{ + dart = self; + player = dart.owner; + + player endon( "dart_entered" ); // the remote weapon script should handle it from here on + dart endon( "delete" ); + + dart waittill( "death" ); + //dart waittill( "death", attacker, damageFromUnderneath, weapon, point, dir, modType ); + + dart thread leave_dart(); +} + +function WatchOwnerNonDeathEvents( endCondition1, endCondition2 ) +{ + dart = self; + player = dart.owner; + + player endon( "dart_entered" ); // the remote weapon script should handle it from here on + dart endon( "death" ); + + dart thread WatchForGameEnd(); + + player util::waittill_any( "joined_team", "disconnect", "joined_spectators", "emp_jammed" ); + + dart thread leave_dart(); +} + +function WatchForGameEnd() +{ + dart = self; + dart endon( "death" ); + + level waittill( "game_ended" ); + + dart thread leave_dart(); +} + +function WatchAmmo() +{ + dart = self; + dart endon( "death" ); + + player = dart.owner; + + player endon( "disconnect" ); + + shotCount = 0; + + params = level.killstreakBundle["dart"]; + + if(!isdefined(params.ksDartShotCount))params.ksDartShotCount=3; + if(!isdefined(params.ksDartBladeCount))params.ksDartBladeCount=6; + if(!isdefined(params.ksDartWaitTimeAfterLastShot))params.ksDartWaitTimeAfterLastShot=1; + + if(!isdefined(params.ksBladeStartDistance))params.ksBladeStartDistance=0; + if(!isdefined(params.ksBladeEndDistance))params.ksBladeEndDistance=10000; + if(!isdefined(params.ksBladeStartSpreadRadius))params.ksBladeStartSpreadRadius=50; + if(!isdefined(params.ksBladeEndSpreadRadius))params.ksBladeEndSpreadRadius=1; + + player clientfield::set_to_player( "dart_update_ammo", params.ksDartShotCount ); + + while( true ) + { + dart waittill( "weapon_fired" ); + shotCount++; + + player clientfield::set_to_player( "dart_update_ammo", params.ksDartShotCount - shotCount ); + + if( shotCount >= params.ksDartShotCount ) + { + dart DisableDriverFiring( true ); + wait( params.ksDartWaitTimeAfterLastShot ); + dart stop_remote_weapon(); + } + } +} + +function leave_dart() +{ + dart = self; + owner = dart.owner; + + if( isdefined( owner ) ) + { + visionset_mgr::deactivate( "visionset", "dart_visionset", owner ); + owner clientfield::set_to_player( "fog_bank_3", 0 ); + owner qrdrone::destroyHud(); + } + + if( isdefined( dart ) && ( dart.is_shutting_down == 1 ) ) + return; + + dart.is_shutting_down = 1; + + dart clientfield::set( "timeout_beep", 0 ); + dart vehicle::lights_off(); + dart vehicle_death::death_fx(); + dart Hide(); + + dart_original_team = dart.originalteam; + dart_killstreak_id = dart.killstreak_id; + + if( Target_IsTarget( dart ) ) + Target_Remove( dart ); + + iF( isalive( dart ) ) + dart notify( "death" ); + + params = level.killstreakBundle["dart"]; + + if(!isdefined(params.ksDartExplosionOuterRadius))params.ksDartExplosionOuterRadius=200; + if(!isdefined(params.ksDartExplosionInnerRadius))params.ksDartExplosionInnerRadius=1; + if(!isdefined(params.ksDartExplosionOuterDamage))params.ksDartExplosionOuterDamage=25; + if(!isdefined(params.ksDartExplosionInnerDamage))params.ksDartExplosionInnerDamage=350; + if(!isdefined(params.ksDartExplosionMagnitude))params.ksDartExplosionMagnitude=1; + + PhysicsExplosionSphere( dart.origin, + params.ksDartExplosionOuterRadius, + params.ksDartExplosionInnerRadius, + params.ksDartExplosionMagnitude, + params.ksDartExplosionOuterDamage, + params.ksDartExplosionInnerDamage ); + + if( isdefined( owner ) ) + { + owner killstreaks::set_killstreak_delay_killcam( "dart" ); // hold the respawn till we finish here + + dart RadiusDamage( dart.origin, + params.ksDartExplosionOuterRadius, + params.ksDartExplosionInnerDamage, + params.ksDartExplosionOuterDamage, + owner, + "MOD_EXPLOSIVE", + GetWeapon( "dart" ) ); + + owner thread play_bda_dialog( self.pilotIndex ); + + if( ( isdefined( dart.controlled ) && dart.controlled ) || ( isdefined( dart.control_initiated ) && dart.control_initiated ) ) + { + owner SetClientUIVisibilityFlag( "hud_visible", 0 ); + owner unlink(); + dart clientfield::set( "vehicletransition", 0 ); + + if( isdefined( params.ksExplosionRumble ) ) + owner PlayRumbleOnEntity( params.ksExplosionRumble ); + + owner vehicle::stop_monitor_missiles_locked_on_to_me(); + owner vehicle::stop_monitor_damage_as_occupant(); + + dart DisableDartMissileLocking(); + + owner util::freeze_player_controls( true ); + + forward = AnglesToForward( dart.angles ); + if(!isdefined(params.ksDartCameraWatchDistance))params.ksDartCameraWatchDistance=350; + moveAmount = VectorScale( forward, -( params.ksDartCameraWatchDistance ) ); + + size = 4; + trace = physicstrace( dart.origin, dart.origin + moveAmount, ( -size, -size, -size ), ( size, size, size ), undefined, (1 << 0) ); + + cam = spawn( "script_model", trace["position"] ); + cam SetModel( "tag_origin" ); + cam LinkTo( dart ); + dart SetSpeedImmediate( 0 ); + + owner CameraSetPosition( cam.origin ); + owner CameraSetLookAt( dart.origin ); + owner CameraActivate( true ); + + if(!isdefined(params.ksDartCameraWatchDuration))params.ksDartCameraWatchDuration=2; + wait( params.ksDartCameraWatchDuration ); + + if ( isdefined( owner ) ) + { + owner CameraActivate( false ); + } + + cam delete(); + + if( isdefined( owner ) ) + { + if( !level.gameEnded ) + owner util::freeze_player_controls( false ); + + owner SetClientUIVisibilityFlag( "hud_visible", 1 ); + } + } + + if ( isdefined( owner ) ) + { + owner killstreaks::reset_killstreak_delay_killcam(); + } + } + + killstreakrules::killstreakStop( "dart", dart_original_team, dart_killstreak_id ); + + if ( isdefined( dart ) ) + { + dart notify( "dart_left" ); + } +} + +function DeleteOnConditions( condition ) +{ + dart = self; + dart endon( "delete" ); + + if( isdefined( condition ) ) + dart waittill( condition ); + + dart notify( "delete" ); + dart delete(); +} + +function waitThenDelete( waitTime ) +{ + self endon( "delete" ); + self endon( "death" ); + wait( waitTime ); + self delete(); +} + +function play_bda_dialog( pilotIndex ) +{ + self endon( "game_ended" ); + + wait( 0.5 ); + + if ( !isdefined( self.dartBda ) || self.dartBda == 0 ) + { + bdaDialog = "killNone"; + } + else if ( self.dartBda == 1 ) + { + bdaDialog = "kill1"; + } + else if ( self.dartBda == 2 ) + { + bdaDialog = "kill2"; + } + else if ( self.dartBda == 3 ) + { + bdaDialog = "kill3"; + } + else if ( self.dartBda > 3 ) + { + bdaDialog = "killMultiple"; + } + + self killstreaks::play_pilot_dialog( bdaDialog, "dart", undefined, pilotIndex ); + + self.dartBda = undefined; +} + +function EnableDartMissileLocking() // self == dart +{ + dart = self; + player = dart.owner; + weapon = dart SeatGetWeapon( 0 ); + + player.get_stinger_target_override = &GetDartMissileTargets; + player.is_still_valid_target_for_stinger_override = &IsStillValidDartMissileTarget; + player.is_valid_target_for_stinger_override = &IsValidDartMissileTarget; + player.dart_killstreak_weapon = weapon; + + player thread heatseekingmissile::StingerIRTLoop( weapon ); +} + +function DisableDartMissileLocking() // self == dart +{ + player = self.owner; + + player.get_stinger_target_override = undefined; + player.is_still_valid_target_for_stinger_override = undefined; + player.is_valid_target_for_stinger_override = undefined; + player.dart_killstreak_weapon = undefined; + + player notify( "stinger_IRT_off" ); + player heatseekingmissile::ClearIRTarget(); +} + +function GetDartMissileTargets() +{ + targets = ArrayCombine( target_getArray(), level.MissileEntities, false, false ); + targets = ArrayCombine( targets, level.players, false, false ); + + return targets; +} + +function IsValidDartMissileTarget( ent ) // self == player +{ + player = self; + + if ( !isdefined( ent ) ) + return false; + + entIsPlayer = IsPlayer( ent ); + + if ( entIsPlayer && !IsAlive( ent ) ) + return false; + + if ( ent.ignoreme === true ) + return false; + + dart = player GetVehicleOccupied(); + if ( !isdefined( dart ) ) + return false; + + if ( DistanceSquared( dart.origin, ent.origin ) > ( (player.dart_killstreak_weapon.lockOnMaxRange) * (player.dart_killstreak_weapon.lockOnMaxRange) ) ) + return false; + + if ( entIsPlayer && ent HasPerk( "specialty_nokillstreakreticle" ) ) + return false; + + return true; +} + +function IsStillValidDartMissileTarget( ent, weapon ) // self == player +{ + player = self; + + if ( !( target_isTarget( ent ) || IsPlayer( ent ) ) && !( isdefined( ent.allowContinuedLockonAfterInvis ) && ent.allowContinuedLockonAfterInvis ) ) + return false; + + dart = player GetVehicleOccupied(); + if ( !isdefined( dart ) ) + return false; + + entIsPlayer = IsPlayer( ent ); + + if ( entIsPlayer && !IsAlive( ent ) ) + return false; + + if ( ent.ignoreme === true ) + return false; + + if ( DistanceSquared( dart.origin, ent.origin ) > ( (player.dart_killstreak_weapon.lockOnMaxRange) * (player.dart_killstreak_weapon.lockOnMaxRange) ) ) + return false; + + if ( entIsPlayer && ent HasPerk( "specialty_nokillstreakreticle" ) ) + return false; + + if ( !heatseekingmissile::InsideStingerReticleLocked( ent, weapon ) ) + return false; + + return true; +} diff --git a/mp/killstreaks/_dogs.gsc b/mp/killstreaks/_dogs.gsc new file mode 100644 index 0000000..ca6ad2a --- /dev/null +++ b/mp/killstreaks/_dogs.gsc @@ -0,0 +1,1111 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\system_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weapons; +#using scripts\shared\weapons\_weapon_utils; + + + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_dev; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_supplydrop; + + + + + + + + + + + + + + + + + + + + + +#precache( "string", "KILLSTREAK_EARNED_DOGS" ); +#precache( "string", "KILLSTREAK_DOGS_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_DOGS_INBOUND" ); +#precache( "string", "KILLSTREAK_DOGS_HACKED" ); +#precache( "eventstring", "mpl_killstreak_dogs" ); + +#namespace dogs; + +//REGISTER_SYSTEM( "dogs", &__init__, undefined ) + +//Please note, the killstreak init is a separate call below +function init() +{ + level.dog_targets = []; + level.dog_targets[ level.dog_targets.size ] = "trigger_radius"; + level.dog_targets[ level.dog_targets.size ] = "trigger_multiple"; + level.dog_targets[ level.dog_targets.size ] = "trigger_use_touch"; + + level.dog_spawns = []; + //init_spawns(); + + /# + level thread devgui_dog_think(); + #/ + + level.dogsOnFlashDogs = &flash_dogs; +} + +function init_spawns() +{ + spawns = GetNodeArray( "spawn", "script_noteworthy" ); + + if ( !IsDefined( spawns ) || !spawns.size ) + { + /# println( "No dog spawn nodes found in map" ); #/ + return; + } + + dog_spawner = GetEnt( "dog_spawner", "targetname" ); + + if ( !IsDefined( dog_spawner ) ) + { + /# println( "No dog_spawner entity found in map" ); #/ + return; + } + + valid = spawnlogic::get_spawnpoint_array( "mp_tdm_spawn" ); + dog = dog_spawner SpawnFromSpawner(); + + foreach( spawn in spawns ) + { + valid = ArraySort( valid, spawn.origin, false ); + + for( i = 0; i < 5; i++ ) + { + if ( dog FindPath( spawn.origin, valid[i].origin, true, false ) ) + { + level.dog_spawns[ level.dog_spawns.size ] = spawn; + break; + } + } + } + +/# + if ( !level.dog_spawns.size ) + { + println( "No dog spawns connect to MP spawn nodes" ); + } +#/ + + dog delete(); +} + +function initKillstreak() +{ + // register the dog hardpoint + if ( tweakables::getTweakableValue( "killstreak", "allowdogs" ) ) + { + //killstreaks::register( "dogs", "dogs", "killstreak_dogs","dogs_used",&useKillstreakDogs, true ); + //killstreaks::register_strings( "dogs", &"KILLSTREAK_EARNED_DOGS", &"KILLSTREAK_DOGS_NOT_AVAILABLE", &"KILLSTREAK_DOGS_INBOUND", undefined, &"KILLSTREAK_DOGS_HACKED" ); + //killstreaks::register_dialog( "dogs", "mpl_killstreak_dogs", "kls_dogs_used", "","kls_dogs_enemy", "", "kls_dogs_ready" ); + //killstreaks::set_team_kill_penalty_scale( "dogs", 0.0 ); + + //killstreaks::register_alt_weapon( "dogs", "dog_bite" ); + } +} + +function useKillstreakDogs(hardpointType) +{ + if ( !dog_killstreak_init() ) + return false; + + if ( !self killstreakrules::isKillstreakAllowed( hardpointType, self.team ) ) + return false; + + killstreak_id = self killstreakrules::killstreakStart( "dogs", self.team ); + + self thread ownerHadActiveDogs(); + + if ( killstreak_id == -1 ) + return false; + + if ( level.teambased ) + { + foreach( team in level.teams ) + { + if ( team == self.team ) + continue; + } + } + + self killstreaks::play_killstreak_start_dialog( "dogs", self.team, true ); + //self AddPlayerStat( "DOGS_USED", 1 ); + self AddWeaponStat( GetWeapon( "dogs" ), "used", 1 ); + + ownerDeathCount = self.deathCount; + + level thread dog_manager_spawn_dogs( self, ownerDeathCount, killstreak_id ); + level notify( "called_in_the_dogs" ); + return true; +} + +function ownerHadActiveDogs() +{ + self endon( "disconnect" ); + self.dogsActive = true; + self.dogsActiveKillstreak = 0; + self util::waittill_any( "death", "game_over", "dogs_complete" ); + + self.dogsActiveKillstreak = 0; + self.dogsActive = undefined; +} + +function dog_killstreak_init() +{ + dog_spawner = GetEnt( "dog_spawner", "targetname" ); + + if( !isdefined( dog_spawner ) ) + { + /# println( "No dog spawners found in map" ); #/ + return false; + } + + spawns = GetNodeArray( "spawn", "script_noteworthy" ); + + if ( level.dog_spawns.size <= 0 ) + { + /# println( "No dog spawn nodes found in map" ); #/ + return false; + } + + exits = GetNodeArray( "exit", "script_noteworthy" ); + + if ( exits.size <= 0 ) + { + /# println( "No dog exit nodes found in map" ); #/ + return false; + } + + return true; +} + +function dog_set_model() +{ + self SetModel( "german_shepherd_vest" ); + self SetEnemyModel( "german_shepherd_vest_black" ); +} + +function init_dog() +{ + assert( IsAi( self ) ); + + self.targetname = "attack_dog"; + + self.animTree = "dog.atr"; + self.type = "dog"; + self.accuracy = 0.2; + self.health = 100; + self.maxhealth = 100; // this currently does not hook to code maxhealth + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeAmmo = 0; + self.goalradius = 128; + self.noDodgeMove = true; + self.ignoreSuppression = true; + self.suppressionThreshold = 1; + self.disableArrivals = false; + self.pathEnemyFightDist = 512; + self.soundMod = "dog"; + + self thread dog_health_regen(); + self thread selfDefenseChallenge(); +} + +function get_spawn_node( owner, team ) +{ + assert( level.dog_spawns.size > 0 ); + return array::random( level.dog_spawns ); +} + +function get_score_for_spawn( origin, team ) +{ + players = GetPlayers(); + score = 0; + + foreach( player in players ) + { + if ( !isdefined( player ) ) + { + continue; + } + + if ( !IsAlive( player ) ) + { + continue; + } + + if ( player.sessionstate != "playing" ) + { + continue; + } + + if ( DistanceSquared( player.origin, origin ) > 2048 * 2048 ) + { + continue; + } + + if ( player.team == team ) + { + score++; + } + else + { + score--; + } + } + + return score; +} + +function dog_set_owner( owner, team, requiredDeathCount ) +{ + self SetEntityOwner( owner ); + self.team = team; + + self.requiredDeathCount = requiredDeathCount; +} + +function dog_create_spawn_influencer( team ) +{ + self spawning::create_entity_enemy_influencer( "dog", team ); +} + +function dog_manager_spawn_dog( owner, team, spawn_node, requiredDeathCount ) +{ + dog_spawner = GetEnt( "dog_spawner", "targetname" ); + + dog = dog_spawner SpawnFromSpawner(); + dog ForceTeleport( spawn_node.origin, spawn_node.angles ); + + dog init_dog(); + dog dog_set_owner( owner, team, requiredDeathCount ); + dog dog_set_model(); + dog dog_create_spawn_influencer( team ); + + dog thread dog_owner_kills(); + dog thread dog_notify_level_on_death(); + dog thread dog_patrol(); + dog thread monitor_dog_special_grenades(); + + return dog; +} + + +function monitor_dog_special_grenades() // self == dog +{ + // watch and see if the dog gets damage from a flash or concussion + // smoke and tabun handle themselves + self endon("death"); + + while(1) + { + self waittill( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, weapon, iDFlags ); + + if( weapon_utils::isFlashOrStunWeapon( weapon ) ) + { + damage_area = spawn( "trigger_radius", self.origin, 0, 128, 128 ); + attacker thread dogs::flash_dogs( damage_area ); + {wait(.05);}; + damage_area delete(); + } + } +} + + +function dog_manager_spawn_dogs( owner, deathCount, killstreak_id ) +{ + requiredDeathCount = deathCount; + team = owner.team; + + level.dog_abort = false; + owner thread dog_manager_abort(); + level thread dog_manager_game_ended(); + + for ( count = 0; count < 10; ) + { + if ( level.dog_abort ) + { + break; + } + + dogs = dog_manager_get_dogs(); + + while ( dogs.size < 5 && count < 10 && !level.dog_abort ) + { + node = get_spawn_node( owner, team ); + level dog_manager_spawn_dog( owner, team, node, requiredDeathCount ); + count++; + + wait ( randomfloatrange( 2, 5 ) ); + dogs = dog_manager_get_dogs(); + } + + level waittill( "dog_died" ); + } + + for ( ;; ) + { + dogs = dog_manager_get_dogs(); + + if ( dogs.size <= 0 ) + { + killstreakrules::killstreakStop( "dogs", team, killstreak_id ); + if ( isdefined( owner ) ) + { + owner notify( "dogs_complete" ); + } + return; + } + + level waittill( "dog_died" ); + } +} + +function dog_abort() +{ + level.dog_abort = true; + + dogs = dog_manager_get_dogs(); + + foreach( dog in dogs ) + { + dog notify( "abort" ); + } + + level notify( "dog_abort" ); +} + +function dog_manager_abort() +{ + level endon( "dog_abort" ); + self util::wait_endon( 45, "disconnect", "joined_team", "joined_spectators" ); + dog_abort(); +} + +function dog_manager_game_ended() +{ + level endon( "dog_abort" ); + + level waittill( "game_ended" ); + dog_abort(); +} + +function dog_notify_level_on_death() +{ + self waittill( "death" ); + level notify( "dog_died" ); +} + +function dog_leave() +{ + // have them run to an exit node + self clearentitytarget(); + self.ignoreall = true; + self.goalradius = 30; + self SetGoal( self dog_get_exit_node() ); + + self util::wait_endon( 20, "goal", "bad_path" ); + self delete(); +} + +function dog_patrol() +{ + self endon( "death" ); +/# + self endon( "debug_patrol" ); +#/ + + for ( ;; ) + { + if ( level.dog_abort ) + { + self dog_leave(); + return; + } + + if ( isdefined( self.enemy ) ) + { + wait( RandomIntRange( 3, 5 ) ); + continue; + } + + nodes = []; + + objectives = dog_patrol_near_objective(); + + for ( i = 0; i < objectives.size; i++ ) + { + objective = array::random( objectives ); + + nodes = GetNodesInRadius( objective.origin, 256, 64, 512, "Path", 16 ); + + if ( nodes.size ) + { + break; + } + } + + if ( !nodes.size ) + { + player = self dog_patrol_near_enemy(); + + if ( isdefined( player ) ) + { + nodes = GetNodesInRadius( player.origin, 1024, 0, 128, "Path", 8 ); + } + } + + if ( !nodes.size && isdefined( self.script_owner ) ) + { + if ( IsAlive( self.script_owner ) && self.script_owner.sessionstate == "playing" ) + { + nodes = GetNodesInRadius( self.script_owner.origin, 512, 256, 512, "Path", 16 ); + } + } + + if ( !nodes.size ) + { + nodes = GetNodesInRadius( self.origin, 1024, 512, 512, "Path" ); + } + + if ( nodes.size ) + { + nodes = array::randomize( nodes ); + + foreach( node in nodes ) + { + if ( isdefined( node.script_noteworthy ) ) + { + continue; + } + + if ( isdefined( node.dog_claimed ) && IsAlive( node.dog_claimed ) ) + { + continue; + } + + self SetGoal( node ); + node.dog_claimed = self; + + nodes = []; + event = self util::waittill_any_return( "goal", "bad_path", "enemy", "abort" ); + + if ( event == "goal" ) + { + util::wait_endon( RandomIntRange( 3, 5 ), "damage", "enemy", "abort" ); + } + + node.dog_claimed = undefined; + break; + } + } + + wait( 0.5 ); + } +} + +function dog_patrol_near_objective() +{ + if ( !isdefined( level.dog_objectives ) ) + { + level.dog_objectives = []; + level.dog_objective_next_update = 0; + } + + if ( level.gameType == "tdm" || level.gameType == "dm" ) + { + return level.dog_objectives; + } + + if ( GetTime() >= level.dog_objective_next_update ) + { + level.dog_objectives = []; + + foreach( target in level.dog_targets ) + { + ents = GetEntArray( target, "classname" ); + + foreach( ent in ents ) + { + if ( level.gameType == "koth" ) + { + if ( isdefined( ent.targetname ) && ent.targetname == "radiotrigger" ) + { + level.dog_objectives[ level.dog_objectives.size ] = ent; + } + + continue; + } + + if ( level.gameType == "sd" ) + { + if ( isdefined( ent.targetname ) && ent.targetname == "bombzone" ) + { + level.dog_objectives[ level.dog_objectives.size ] = ent; + } + + continue; + } + + if ( !isdefined( ent.script_gameobjectname ) ) + { + continue; + } + + if ( !IsSubStr( ent.script_gameobjectname, level.gameType ) ) + { + continue; + } + + level.dog_objectives[ level.dog_objectives.size ] = ent; + } + } + + level.dog_objective_next_update = GetTime() + RandomIntRange( 5000, 10000 ); + } + + return level.dog_objectives; +} + +function dog_patrol_near_enemy() +{ + players = GetPlayers(); + + closest = undefined; + distSq = 99999999; + + foreach( player in players ) + { + if ( !isdefined( player ) ) + { + continue; + } + + if ( !IsAlive( player ) ) + { + continue; + } + + if ( player.sessionstate != "playing" ) + { + continue; + } + + if ( isdefined( self.script_owner ) && player == self.script_owner ) + { + continue; + } + + if ( level.teambased ) + { + if ( player.team == self.team ) + { + continue; + } + } + + if ( GetTime() - player.lastFireTime > 3000 ) + { + continue; + } + + if ( !isdefined( closest ) ) + { + closest = player; + distSq = DistanceSquared( self.origin, player.origin ); + continue; + } + + d = DistanceSquared( self.origin, player.origin ); + + if ( d < distSq ) + { + closest = player; + distSq = d; + } + } + + return closest; +} + +function dog_manager_get_dogs() +{ + dogs = GetEntArray( "attack_dog", "targetname" ); + return dogs; +} + +function dog_owner_kills() +{ + if ( !isdefined( self.script_owner ) ) + return; + + self endon("clear_owner"); + self endon("death"); + self.script_owner endon("disconnect"); + + while(1) + { + self waittill("killed", player); + self.script_owner notify( "dog_handler" ); + } +} + +function dog_health_regen() +{ + self endon( "death" ); + + interval = 0.5; + regen_interval = Int( ( self.health / 5 ) * interval ); + regen_start = 2; + + for ( ;; ) + { + self waittill( "damage", damage, attacker, direction, point, type, tagName, modelName, partname, weapon, iDFlags ); + self trackAttackerDamage( attacker ); + + self thread dog_health_regen_think( regen_start, interval, regen_interval ); + } +} + +function trackAttackerDamage( attacker ) +{ + if ( !isdefined( attacker ) || !isPlayer( attacker ) || !isdefined( self.script_owner ) ) + { + return; + } + + if ( ( level.teambased && attacker.team == self.script_owner.team ) || attacker == self ) + { + return; + } + + if ( !isdefined( self.attackerData ) || !isdefined( self.attackers ) ) + { + self.attackerData = []; + self.attackers = []; + } + if ( !isdefined( self.attackerData[attacker.clientid] ) ) + { + self.attackerClientID[attacker.clientid] = spawnstruct(); + self.attackers[ self.attackers.size ] = attacker; + } +} + +function resetAttackerDamage() +{ + self.attackerData = []; + self.attackers = []; +} + +function dog_health_regen_think( delay, interval, regen_interval ) +{ + self endon( "death" ); + self endon( "damage" ); + + wait( delay ); + + for ( step = 0; step <= 5; step += interval ) + { + if ( self.health >= 100 ) + { + break; + } + + self.health += regen_interval; + wait( interval ); + } + + self resetAttackerDamage(); + self.health = 100; +} + +function selfDefenseChallenge() +{ + self waittill ("death", attacker); + + if ( isdefined( attacker ) && isPlayer( attacker ) ) + { + if (isdefined ( self.script_owner ) && self.script_owner == attacker) + return; + if ( level.teambased && isdefined ( self.script_owner ) && self.script_owner.team == attacker.team ) + return; + + if ( isdefined( self.attackers ) ) + { + foreach ( player in self.attackers ) + { + if ( player != attacker ) + { + scoreevents::processScoreEvent( "killed_dog_assist", player ); + } + } + } + attacker notify ("selfdefense_dog"); + } + +} + +function dog_get_exit_node() +{ + exits = GetNodeArray( "exit", "script_noteworthy" ); + return ArrayGetClosest( self.origin, exits ); +} + +function flash_dogs( area ) +{ + self endon("disconnect"); + + dogs = dog_manager_get_dogs(); + + foreach( dog in dogs ) + { + if ( !isalive(dog) ) + continue; + + if ( dog istouching(area) ) + { + do_flash = true; + if ( isPlayer( self ) ) + { + if ( level.teamBased && (dog.team == self.team) ) + { + do_flash = false; + } + else if ( !level.teambased && isdefined(dog.script_owner) && self == dog.script_owner ) + { + do_flash = false; + } + } + + if ( isdefined( dog.lastFlashed ) && dog.lastFlashed + 1500 > gettime() ) + { + do_flash = false; + } + + if ( do_flash ) + { + dog setFlashBanged( true, 500 ); + dog.lastFlashed = gettime(); + } + } + } +} + +/# + +function devgui_dog_think() +{ + SetDvar( "devgui_dog", "" ); + + debug_patrol = false; + + for( ;; ) + { + cmd = GetDvarString( "devgui_dog" ); + + switch( cmd ) + { + case "spawn_friendly": + player = util::getHostPlayer(); + devgui_dog_spawn( player.team ); + break; + + case "spawn_enemy": + player = util::getHostPlayer(); + foreach( team in level.teams ) + { + if ( team == player.team ) + continue; + + devgui_dog_spawn( team ); + } + break; + + case "delete_dogs": + level dog_abort(); + break; + + case "dog_camera": + devgui_dog_camera(); + break; + + case "spawn_crate": + devgui_crate_spawn(); + break; + + case "delete_crates": + devgui_crate_delete(); + break; + + case "show_spawns": + devgui_spawn_show(); + break; + + case "show_exits": + devgui_exit_show(); + break; + + case "debug_route": + devgui_debug_route(); + break; + } + + if ( cmd != "" ) + { + SetDvar( "devgui_dog", "" ); + } + + wait( 0.5 ); + } +} + +function devgui_dog_spawn( team ) +{ + player = util::getHostPlayer(); + + dog_spawner = GetEnt( "dog_spawner", "targetname" ); + level.dog_abort = false; + + if( !isdefined( dog_spawner ) ) + { + iprintln( "No dog spawners found in map" ); + return; + } + + // Trace to where the player is looking + direction = player GetPlayerAngles(); + direction_vec = AnglesToForward( direction ); + eye = player GetEye(); + + scale = 8000; + direction_vec = ( direction_vec[0] * scale, direction_vec[1] * scale, direction_vec[2] * scale ); + trace = bullettrace( eye, eye + direction_vec, 0, undefined ); + + nodes = GetNodesInRadius( trace["position"], 256, 0, 128, "Path", 8 ); + + if ( !nodes.size ) + { + iprintln( "No nodes found near crosshair position" ); + return; + } + + iprintln( "Spawning dog at your crosshair position" ); + node = ArrayGetClosest( trace["position"], nodes ); + + dog = dog_manager_spawn_dog( player, player.team, node, 5 ); + + if ( team != player.team ) + { + dog.team = team; + dog ClearEntityOwner(); + dog notify ( "clear_owner" ); + } +} + +function devgui_dog_camera() +{ + player = util::getHostPlayer(); + + if ( !isdefined( level.devgui_dog_camera ) ) + { + level.devgui_dog_camera = 0; + } + + dog = undefined; + dogs = dog_manager_get_dogs(); + + if ( dogs.size <= 0 ) + { + level.devgui_dog_camera = undefined; + player CameraActivate( false ); + return; + } + + for ( i = 0; i < dogs.size; i++ ) + { + dog = dogs[i]; + + if ( !isdefined( dog ) || !IsAlive( dog ) ) + { + dog = undefined; + continue; + } + + if ( !isdefined( dog.cam ) ) + { + forward = AnglesToForward( dog.angles ); + dog.cam = spawn( "script_model", dog.origin + ( 0, 0, 50 ) + forward * -100 ); + dog.cam SetModel( "tag_origin" ); + dog.cam LinkTo( dog ); + } + + if ( dog GetEntityNumber() <= level.devgui_dog_camera ) + { + dog = undefined; + continue; + } + + break; + } + + if ( isdefined( dog ) ) + { + level.devgui_dog_camera = dog GetEntityNumber(); + + player CameraSetPosition( dog.cam ); + player CameraSetLookAt( dog ); + player CameraActivate( true ); + } + else + { + level.devgui_dog_camera = undefined; + player CameraActivate( false ); + } +} + +function devgui_crate_spawn() +{ + player = util::getHostPlayer(); + + // Trace to where the player is looking + direction = player GetPlayerAngles(); + direction_vec = AnglesToForward( direction ); + eye = player GetEye(); + + scale = 8000; + direction_vec = ( direction_vec[0] * scale, direction_vec[1] * scale, direction_vec[2] * scale ); + trace = bullettrace( eye, eye + direction_vec, 0, undefined ); + + killCamEnt = spawn( "script_model", player.origin ); + level thread supplydrop::dropCrate( trace[ "position" ] + ( 0, 0, 25 ), direction, "supplydrop", player, player.team, killcamEnt ); +} + +function devgui_crate_delete() +{ + if ( !isdefined( level.devgui_crates ) ) + { + return; + } + + for ( i = 0; i < level.devgui_crates.size; i++ ) + { + level.devgui_crates[i] delete(); + } + + level.devgui_crates = []; +} + +function devgui_spawn_show() +{ + if ( !isdefined( level.dog_spawn_show ) ) + { + level.dog_spawn_show = true; + } + else + { + level.dog_spawn_show = !level.dog_spawn_show; + } + + if ( !level.dog_spawn_show ) + { + level notify( "hide_dog_spawns" ); + return; + } + + spawns = level.dog_spawns; + color = ( 0, 1, 0 ); + + for ( i = 0; i < spawns.size; i++ ) + { + dev::showOneSpawnPoint( spawns[i], color, "hide_dog_spawns", 32, "dog_spawn" ); + } +} + +function devgui_exit_show() +{ + if ( !isdefined( level.dog_exit_show ) ) + { + level.dog_exit_show = true; + } + else + { + level.dog_exit_show = !level.dog_exit_show; + } + + if ( !level.dog_exit_show ) + { + level notify( "hide_dog_exits" ); + return; + } + + exits = GetNodeArray( "exit", "script_noteworthy" ); + color = ( 1, 0, 0 ); + + for ( i = 0; i < exits.size; i++ ) + { + dev::showOneSpawnPoint( exits[i], color, "hide_dog_exits", 32, "dog_exit" ); + } +} + +function dog_debug_patrol( node1, node2 ) +{ + self endon( "death" ); + self endon( "debug_patrol" ); + + for( ;; ) + { + self SetGoal( node1 ); + self util::waittill_any( "goal", "bad_path" ); + wait( 1 ); + + self SetGoal( node2 ); + self util::waittill_any( "goal", "bad_path" ); + wait( 1 ); + } +} + +function devgui_debug_route() +{ + iprintln( "Choose nodes with 'A' or press 'B' to cancel" ); + nodes = dev::dev_get_node_pair(); + + if ( !isdefined( nodes ) ) + { + iprintln( "Route Debug Cancelled" ); + return; + } + + iprintln( "Sending dog to chosen nodes" ); + dogs = dog_manager_get_dogs(); + + if ( isdefined( dogs[0] ) ) + { + dogs[0] notify( "debug_patrol" ); + dogs[0] thread dog_debug_patrol( nodes[0], nodes[1] ); + } +} + +#/ diff --git a/mp/killstreaks/_drone_strike.gsc b/mp/killstreaks/_drone_strike.gsc new file mode 100644 index 0000000..4617243 --- /dev/null +++ b/mp/killstreaks/_drone_strike.gsc @@ -0,0 +1,342 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_satellite; +#using scripts\mp\killstreaks\_planemortar; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\shared\weapons\_hacker_tool; +#using scripts\mp\killstreaks\_killstreak_hacking; + + + + + + +#namespace drone_strike; + + +#precache( "locationselector", "map_directional_selector" ); +#precache( "string", "KILLSTREAK_DRONE_STRIKE_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_DRONE_STRIKE_EARNED" ); +#precache( "string", "KILLSTREAK_DRONE_STRIKE_INBOUND" ); +#precache( "string", "KILLSTREAK_DRONE_STRIKE_INBOUND_NEAR_PLAYER" ); +#precache( "string", "KILLSTREAK_DRONE_STRIKE_HACKED" ); +#precache( "string", "KILLSTREAK_DESTROYED_ROLLING_THUNDER_DRONE" ); +#precache( "string", "KILLSTREAK_DESTROYED_ROLLING_THUNDER_ALL_DRONES" ); + +#precache( "eventstring", "mpl_killstreak_DRONE_STRIKE" ); +#precache( "fx", "killstreaks/fx_rolling_thunder_thruster_trails" ); + +function init() +{ + killstreaks::register( "drone_strike", "drone_strike", "killstreak_drone_strike", "drone_strike_used", &ActivateDroneStrike, true ); + killstreaks::register_strings( "drone_strike", &"KILLSTREAK_DRONE_STRIKE_EARNED", &"KILLSTREAK_DRONE_STRIKE_NOT_AVAILABLE", &"KILLSTREAK_DRONE_STRIKE_INBOUND", &"KILLSTREAK_DRONE_STRIKE_INBOUND_NEAR_PLAYER", &"KILLSTREAK_DRONE_STRIKE_HACKED" ); + killstreaks::register_dialog( "drone_strike", "mpl_killstreak_drone_strike", "droneStrikeDialogBundle", undefined, "friendlyDroneStrike", "enemyDroneStrike", "enemyDroneStrikeMultiple", "friendlyDroneStrikeHacked", "enemyDroneStrikeHacked", "requestDroneStrike", "threatDroneStrike" ); + killstreaks::set_team_kill_penalty_scale( "drone_strike", level.teamKillReducedPenalty ); +} + +function ActivateDroneStrike() +{ + if ( self killstreakrules::isKillstreakAllowed( "drone_strike", self.team ) == false ) + { + return false; + } + + result = self SelectDroneStrikePath(); + + if ( !isdefined( result ) || !result ) + { + return false; + } + + return true; +} + +function SelectDroneStrikePath() +{ + self BeginLocationNapalmSelection( "map_directional_selector" ); + self.selectingLocation = true; + self thread airsupport::EndSelectionThink(); + + locations = []; + if( !isdefined( self.pers["drone_strike_radar_used"] ) || !self.pers["drone_strike_radar_used"] ) + { + self thread planemortar::SingleRadarSweep(); + } + + location = self WaitForLocationSelection(); + + // if the player gets disconnected, self will be undefined + if( !isdefined( self ) ) + { + return false; + } + + if ( !isdefined( location.origin ) ) + { + self.pers["drone_strike_radar_used"] = true; + self notify( "cancel_selection" ); + return false; + } + + if ( self killstreakrules::isKillstreakAllowed( "drone_strike", self.team ) == false) + { + self.pers["drone_strike_radar_used"] = true; + self notify("cancel_selection"); + return false; + } + + self.pers["drone_strike_radar_used"] = false; + return self airsupport::finishHardpointLocationUsage( location, &DroneStrikeLocationSelected ); +} + +function WaitForLocationSelection() +{ + self endon( "emp_jammed" ); + self endon( "emp_grenaded" ); + + self waittill( "confirm_location", location, yaw ); + + locationInfo = SpawnStruct(); + locationInfo.origin = location; + locationInfo.yaw = yaw; + + return locationInfo; +} + +function DroneStrikeLocationSelected( location ) +{ + team = self.team; + killstreak_id = self killstreakrules::killstreakStart( "drone_strike", team, false, true ); + if( killstreak_id == (-1) ) + { + return false; + } + + self killstreaks::play_killstreak_start_dialog( "drone_strike", team, killstreak_id ); + self AddWeaponStat( GetWeapon( "drone_strike" ), "used", 1 ); + + spawn_influencer = level spawning::create_enemy_influencer( "artillery", location.origin, team ); + + self thread WatchForKillstreakEnd( team, spawn_influencer, killstreak_id ); + self thread StartDroneStrike( location.origin, location.yaw, team, killstreak_id ); + + return true; +} + +function WatchForKillstreakEnd( team, influencer, killstreak_id ) +{ + self util::waittill_any( "disconnect", "joined_team", "joined_spectators", "drone_strike_complete", "emp_jammed" ); + killstreakrules::killstreakStop( "drone_strike", team, killstreak_id ); +} + +function StartDroneStrike( position, yaw, team, killstreak_id ) +{ + self endon( "emp_jammed" ); + self endon( "joined_team" ); + self endon( "joined_spectators" ); + self endon( "disconnect" ); + + angles = ( 0, yaw, 0 ); + direction = AnglesToForward( angles ); + height = airsupport::getMinimumFlyHeight() + ( 3000 ); + + selectedPosition = ( position[0], position[1], height ); + startPoint = selectedPosition + VectorScale( direction, ( -14000 ) ); + endPoint = selectedPosition + VectorScale( direction, ( -6000 ) ); + + // trace to get the target point + traceStartPos = ( position[0], position[1], height ); + traceEndPos = ( position[0], position[1], -height ); + trace = BulletTrace( traceStartPos, traceEndPos, 0, undefined ); + targetPoint = ( ( trace[ "fraction" ] < 1.0 ) ? trace[ "position" ] : ( position[0], position[1], 0.0 ) ); + + ///#sphere( targetPoint, 20, ( 1, 0, 0 ), 1, true, 10, 400 );#/ + + initialOffset = -VectorScale( direction, ( ( ( 12 ) * 0.5 ) - 1 ) * ( 500 ) ); + + for( i = 0; i < ( 12 ); i++ ) + { + right = AnglesToRight( angles ); + rightOffset = VectorScale( right, ( 300 ) ); + leftOffset = VectorScale( right, ( 900 ) ); + forwardOffset = endPoint + initialOffset + VectorScale( direction, i * ( 500 ) ); + + self thread SpawnDrone( startPoint + rightOffset, forwardOffset + rightOffset, targetPoint, angles, self.team, killstreak_id ); + self thread SpawnDrone( startPoint - rightOffset, forwardOffset - rightOffset, targetPoint, angles, self.team, killstreak_id ); + self thread SpawnDrone( startPoint + leftOffset, forwardOffset + leftOffset, targetPoint, angles, self.team, killstreak_id ); + wait( ( 1 ) ); + + self playsound ("mpl_thunder_flyover_wash"); + } + + wait( 3 ); //Wait for the last drone to explode + self notify( "drone_strike_complete" ); +} + +function SpawnDrone( startPoint, endPoint, targetPoint, angles, team, killstreak_id ) +{ + drone = SpawnPlane( self, "script_model", startPoint ); + drone.team = team; + drone.targetname = "drone_strike"; + drone SetOwner( self ); + drone.owner = self; + drone.owner thread WatchOwnerEvents( drone ); + drone killstreaks::configure_team( "drone_strike", killstreak_id, self ); + drone killstreak_hacking::enable_hacking( "drone_strike" ); + Target_Set( drone ); + + drone endon( "delete" ); + drone endon( "death" ); + + drone.angles = angles; + drone SetModel( "veh_t7_drone_rolling_thunder" ); + drone SetEnemyModel( "veh_t7_drone_rolling_thunder" ); + drone NotSolid(); + + PlayFxOnTag( "killstreaks/fx_rolling_thunder_thruster_trails", drone, "tag_fx"); + drone clientfield::set( "enemyvehicle", 1 ); + + drone SetupDamageHandling(); + drone thread WatchForEmp( self ); + + drone MoveTo( endpoint, ( 1.8 ), 0, 0 ); + wait ( ( 1.8 ) ); + + weapon = GetWeapon( "drone_strike" ); + velocity = drone GetVelocity(); + + halfGravity = 386; + dXY = Abs( ( -6000 ) ); + dZ = endPoint[2] - targetPoint[2]; + dVxy = dXY * sqrt( halfGravity / dZ ); + + nvel = VectorNormalize( velocity ); + launchVel = nvel * dVxy; + + bomb = self LaunchBomb( weapon, drone.origin, launchVel ); + + Target_Set( bomb ); + + bomb killstreaks::configure_team( "drone_strike", killstreak_id, self ); + bomb killstreak_hacking::enable_hacking( "drone_strike" ); + drone notify( "hackertool_update_ent", bomb ); + + bomb clientfield::set( "enemyvehicle", 1 ); + bomb.targetname = "drone_strike"; + bomb SetOwner( self ); + bomb.owner = self; + bomb.team = team; + bomb playsound( "mpl_thunder_incoming_start" ); + bomb SetupDamageHandling(); + bomb thread WatchForEmp( self ); + + bomb.owner thread WatchOwnerEvents( bomb ); + {wait(.05);}; + + drone Hide(); + + {wait(.05);}; + + drone Delete(); +} + +function SetupDamageHandling() +{ + drone = self; + drone SetCanDamage( true ); + drone.maxhealth = killstreak_bundles::get_max_health( "drone_strike" ); + drone.lowhealth = killstreak_bundles::get_low_health( "drone_strike" ); + drone.health = drone.maxhealth; + drone thread killstreaks::MonitorDamage( "drone_strike", drone.maxhealth, &DestroyDronePlane, drone.lowhealth, undefined, 0, &EmpDamageDrone, true ); +} + +function DestroyDronePlane( attacker, weapon ) +{ + self endon( "death" ); + + attacker = self [[ level.figure_out_attacker ]]( attacker ); + if( isdefined( attacker ) && ( !isdefined( self.owner ) || self.owner util::IsEnemyPlayer( attacker ) ) ) + { + challenges::destroyedAircraft( attacker, weapon, false ); + attacker challenges::addFlySwatterStat( weapon, self ); + scoreevents::processScoreEvent( "destroyed_rolling_thunder_drone", attacker, self.owner, weapon ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_ROLLING_THUNDER_DRONE", attacker.entnum ); + } + + params = level.killstreakBundle["drone_strike"]; + if( isdefined( params.ksExplosionFX ) ) + PlayFXOnTag( params.ksExplosionFX, self, "tag_origin" ); + + self setModel( "tag_origin" ); + + wait( 0.5 ); + + self delete(); +} + +function WatchOwnerEvents( bomb ) +{ + player = self; + + bomb endon( "death" ); + + player util::waittill_any( "disconnect", "joined_team", "joined_spectators" ); + + if( isdefined( isalive( bomb ) ) ) + bomb delete(); +} + +function WatchForEmp( owner ) +{ + self endon( "delete" ); + self endon( "death" ); + + self waittill( "emp_deployed", attacker ); + + thread DroneStrikeAwardEMPScoreEvent( attacker, self ); + self BlowUpDroneStrike(); +} + +function EmpDamageDrone( attacker ) +{ + thread DroneStrikeAwardEMPScoreEvent( attacker, self ); + self BlowUpDroneStrike(); +} + +function DroneStrikeAwardEMPScoreEvent( attacker, victim ) +{ + owner = self.owner; + + attacker endon( "disconnect" ); + attacker notify( "DroneStrikeAwardScoreEvent_singleton" ); + attacker endon( "DroneStrikeAwardScoreEvent_singleton" ); + waittillframeend; + + attacker = self [[ level.figure_out_attacker ]]( attacker ); + scoreevents::processScoreEvent( "destroyed_rolling_thunder_all_drones", attacker, victim, GetWeapon( "emp" ) ); + challenges::destroyedAircraft( attacker, GetWeapon( "emp" ), false ); + attacker challenges::addFlySwatterStat( GetWeapon( "emp" ), self ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_ROLLING_THUNDER_ALL_DRONES", attacker.entnum ); + + owner globallogic_audio::play_taacom_dialog( "destroyed", "drone_strike" ); +} + +function BlowUpDroneStrike() +{ + params = level.killstreakBundle["drone_strike"]; + if( isdefined( self ) && isdefined( params.ksExplosionFX ) ) + PlayFX( params.ksExplosionFX, self.origin ); + self delete(); +} diff --git a/mp/killstreaks/_emp.csc b/mp/killstreaks/_emp.csc new file mode 100644 index 0000000..82ef319 --- /dev/null +++ b/mp/killstreaks/_emp.csc @@ -0,0 +1,162 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; + + + + + + +#using_animtree ( "mp_emp_power_core" ); + +#precache( "client_fx", "killstreaks/fx_emp_core" ); + +#namespace emp; + + + + + + +function autoexec __init__sytem__() { system::register("emp",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "scriptmover", "emp_turret_init", 1, 1, "int", &emp_turret_init, !true, !true ); + clientfield::register( "vehicle", "emp_turret_deploy", 1, 1, "int", &emp_turret_deploy_start, !true, !true ); + + thread monitor_emp_killstreaks(); +} + +function monitor_emp_killstreaks() +{ + level endon( "disconnect" ); + + if ( !isdefined( level.emp_killstreaks ) ) + level.emp_killstreaks = []; + + for(;;) + { + has_at_least_one_active_enemy_turret = false; + ArrayRemoveValue( level.emp_killstreaks, undefined ); + local_players = GetLocalPlayers(); + + foreach( local_player in local_players ) + { + if ( local_player IsLocalPlayer() == false ) + continue; // Note: a player joining a game is not a local player until after spawning in (is this a bug?) + + closest_enemy_emp = emp::get_closest_enemy_emp_killstreak( local_player ); + + if ( isdefined( closest_enemy_emp ) ) + { + has_at_least_one_active_enemy_turret = true; + + localClientNum = local_player GetLocalClientNumber(); + update_distance_to_closest_emp( localClientNum, Distance( local_player.origin, closest_enemy_emp.origin ) ); + } + } + + wait ( has_at_least_one_active_enemy_turret ? 0.1 : 0.7 ); + } +} + +function get_closest_enemy_emp_killstreak( local_player ) +{ + closest_emp = undefined; + closest_emp_distance_squared = 99999999; + + foreach( emp in level.emp_killstreaks ) + { + if ( emp.owner == local_player || emp.team == local_player.team ) + continue; + + distance_squared = DistanceSquared( local_player.origin, emp.origin ); + if ( distance_squared < closest_emp_distance_squared ) + { + closest_emp = emp; + closest_emp_distance_squared = distance_squared; + } + } + + return closest_emp; +} + +function update_distance_to_closest_emp( localClientNum, new_value ) +{ + if ( !isdefined( localClientNum ) ) + return; + + distance_to_closest_enemy_emp_ui_model = GetUIModel( GetUIModelForController( localClientNum ), "distanceToClosestEnemyEmpKillstreak" ); + + if ( isdefined( distance_to_closest_enemy_emp_ui_model ) ) + SetUIModelValue( distance_to_closest_enemy_emp_ui_model, new_value ); + + // /# IPrintLnBold( "Distance to Closest Enemy EMP: " + new_value ); #/ +} + +function emp_turret_init( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self endon("entityshutdown"); + + if ( !newVal ) + return; + + self UseAnimTree( #animtree ); + self SetAnimRestart( %o_turret_emp_core_deploy, 1.0, 0.0, 0.0 ); + self SetAnimTime( %o_turret_emp_core_deploy, 0.0 ); +} + +function cleanup_fx_on_shutdown( localClientNum, handle ) +{ + self endon("kill_fx_cleanup"); + self waittill( "entityshutdown" ); + StopFx( localClientNum, handle ); +} + +function emp_turret_deploy_start( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self endon("entityshutdown"); + + self util::waittill_dobj( localClientNum ); + + if ( !isdefined(self) ) + return; + + if ( newVal ) + { + self thread emp_turret_deploy(localClientNum); + } + else + { + self notify("kill_fx_cleanup"); + if ( isdefined( self.fxHandle ) ) + { + StopFx( localClientNum, self.fxHandle ); + self.fxHandle = undefined; + } + } +} + +function emp_turret_deploy( localClientNum ) +{ + self endon("entityshutdown"); + + self UseAnimTree( #animtree ); + self SetAnimRestart( %o_turret_emp_core_deploy, 1.0, 0.0, 1.0 ); + length = GetAnimLength( %o_turret_emp_core_deploy ); + // self waittill("end"); // length of deploy anim + wait length * 0.75; + + self UseAnimTree( #animtree ); + self SetAnim( %o_turret_emp_core_spin, 1.0 ); + self.fxHandle = PlayFxOnTag( localClientNum, "killstreaks/fx_emp_core", self, "tag_fx" ); + + self thread cleanup_fx_on_shutdown( localClientNum, self.fxHandle ); + + wait length * 0.25; + self SetAnim( %o_turret_emp_core_deploy, 0.0 ); // stop deploy anim +} \ No newline at end of file diff --git a/mp/killstreaks/_emp.gsc b/mp/killstreaks/_emp.gsc new file mode 100644 index 0000000..b034fe9 --- /dev/null +++ b/mp/killstreaks/_emp.gsc @@ -0,0 +1,470 @@ +#using scripts\codescripts\struct; + +#using scripts\mp\_util; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_placeables; +#using scripts\mp\teams\_teams; +#using scripts\mp\teams\_teams; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\turret_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\scoreevents_shared; + + + + + + +#using_animtree ( "mp_emp_power_core" ); + +#precache( "string", "KILLSTREAK_EARNED_EMP" ); +#precache( "string", "KILLSTREAK_EMP_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_EMP_INBOUND" ); +#precache( "string", "KILLSTREAK_EMP_HACKED" ); +#precache( "string", "KILLSTREAK_DESTROYED_EMP" ); +#precache( "triggerstring", "KILLSTREAK_EMP_PLACE_TURRET_HINT" ); +#precache( "triggerstring", "KILLSTREAK_EMP_INVALID_TURRET_LOCATION" ); +#precache( "triggerstring", "KILLSTREAK_EMP_TURRET_PICKUP" ); +#precache( "string", "mpl_killstreak_emp_activate" ); + +//#precache( "fx", "killstreaks/fx_emp_core" ); +#precache( "fx", "killstreaks/fx_emp_exp_death" ); + + + + +#namespace emp; + +function init() +{ + bundle = struct::get_script_bundle( "killstreak", "killstreak_emp" ); + level.empKillstreakBundle = bundle; + + level.ActivePlayerEMPs = []; + level.ActiveEMPs = []; + foreach( team in level.teams ) + { + level.ActiveEMPs[ team ] = false; + } + + level.enemyEMPActiveFunc = &EnemyEMPActive; + + level thread EMPTracker(); + + killstreaks::register( "emp", "emp", "killstreak_emp", "emp_used", &ActivateEMP ); + killstreaks::register_strings( "emp", &"KILLSTREAK_EARNED_EMP", &"KILLSTREAK_EMP_NOT_AVAILABLE", &"KILLSTREAK_EMP_INBOUND", undefined, &"KILLSTREAK_EMP_HACKED", false ); + killstreaks::register_dialog( "emp", "mpl_killstreak_emp_activate", "empDialogBundle", undefined, "friendlyEmp", "enemyEmp", "enemyEmpMultiple", "friendlyEmpHacked", "enemyEmpHacked", "requestEmp", "threatEmp" ); + + clientfield::register( "scriptmover", "emp_turret_init", 1, 1, "int" ); // re-export model in close position to save this clientfield + clientfield::register( "vehicle", "emp_turret_deploy", 1, 1, "int" ); + + spinAnim = %o_turret_emp_core_spin; + deployAnim = %o_turret_emp_core_deploy; + + callback::on_spawned( &OnPlayerSpawned ); + callback::on_connect( &OnPlayerConnect ); + vehicle::add_main_callback( "emp_turret", &InitTurretVehicle ); +} + +function InitTurretVehicle() +{ + turretVehicle = self; + + turretVehicle killstreaks::setup_health( "emp" ); + turretVehicle.damageTaken = 0; + turretVehicle.health = turretVehicle.maxhealth; + + turretVehicle clientfield::set( "enemyvehicle", 1 ); + turretVehicle.soundmod = "drone_land"; // TODO: update this to the correct value + + turretVehicle.overrideVehicleDamage = &OnTurretDamage; + turretVehicle.overrideVehicleDeath = &OnTurretDeath; + + Target_Set( turretVehicle, ( 0, 0, 36 ) ); +} + +function OnPlayerSpawned() +{ + self endon( "disconnect" ); + + self UpdateEMP(); +} + +function OnPlayerConnect() +{ + self.entNum = self getEntityNumber(); + level.ActivePlayerEMPs[ self.entNum ] = false; +} + +function ActivateEMP() +{ + player = self; + + killstreakId = player killstreakrules::killstreakStart( "emp", player.team, false, false ); + if( killstreakId == (-1) ) + { + return false; + } + + bundle = level.empKillstreakBundle; + + empBase = player placeables::SpawnPlaceable( "emp", killstreakId, &OnPlaceEMP, &OnCancelPlacement, undefined, &OnShutdown, undefined, undefined, + "wpn_t7_turret_emp_core", "wpn_t7_turret_emp_core_yellow", "wpn_t7_turret_emp_core_red", true, "", undefined, undefined, 0, + bundle.ksPlaceableHint, bundle.ksPlaceableInvalidLocationHint ); + + empBase thread util::ghost_wait_show_to_player( player ); + empBase.otherModel thread util::ghost_wait_show_to_others( player ); + empBase clientfield::set( "emp_turret_init", 1 ); + empBase.otherModel clientfield::set( "emp_turret_init", 1 ); + + event = empBase util::waittill_any_return( "placed", "cancelled", "death", "disconnect" ); + if( event != "placed" ) + { + return false; + } + + return true; +} + +function OnPlaceEMP( emp ) +{ + player = self; + assert( IsPlayer( player ) ); + assert( !isdefined( emp.vehicle ) ); + + emp.vehicle = SpawnVehicle( "emp_turret", emp.origin, emp.angles ); + emp.vehicle thread util::ghost_wait_show( 0.05 ); + + emp.vehicle.killstreakType = emp.killstreakType; // need to do this for enable_hacking + + emp.vehicle.owner = player; + emp.vehicle SetOwner( player ); + emp.vehicle.ownerEntNum = player.entNum; + emp.vehicle.parentStruct = emp; + + player.EMPTime = GetTime(); + player killstreaks::play_killstreak_start_dialog( "emp", player.pers["team"], emp.killstreakId ); + player AddWeaponStat( GetWeapon( "emp" ), "used", 1 ); + level thread popups::DisplayKillstreakTeamMessageToAll( "emp", player ); + emp.vehicle killstreaks::configure_team( "emp", emp.killstreakId, player ); + emp.vehicle killstreak_hacking::enable_hacking( "emp", &HackedCallbackPre, &HackedCallbackPost ); + emp thread killstreaks::WaitForTimeout( "emp", ( 60 * 1000 ), &on_timeout, "death" ); + if ( IsSentient( emp.vehicle ) == false ) + emp.vehicle MakeSentient(); // so other sentients will consider this as a potential enemy + + emp.vehicle vehicle::disconnect_paths( 0, false ); + + // perform deploy on separate thread because of the wait delays + // always complete OnPlace() in same frame for killstreak accounting; otherwise exploits can happen + player thread DeployEmpTurret( emp ); +} + +function DeployEmpTurret( emp ) +{ + player = self; + + player endon( "disconnect" ); + player endon( "joined_team" ); + player endon( "joined_spectators" ); + + emp endon( "death" ); + + // deploy emp + emp.vehicle UseAnimTree( #animtree ); + emp.vehicle SetAnim( %o_turret_emp_core_deploy, 1.0 ); + length = GetAnimLength( %o_turret_emp_core_deploy ); + emp.vehicle clientfield::set( "emp_turret_deploy", 1 ); + wait length * 0.75; + + // fire emp pulse + emp.vehicle thread PlayEMPFx(); + emp.vehicle playsound( "mpl_emp_turret_activate" ); + emp.vehicle SetAnim( %o_turret_emp_core_spin, 1.0 ); + + // Jam Enemies and destroy other scorestreaks! + player thread EMP_JamEnemies( emp, false ); + + wait length * 0.25; + emp.vehicle ClearAnim( %o_turret_emp_core_deploy, 0 ); // stop deploy anim +} + +function HackedCallbackPre( hacker ) +{ + emp_vehicle = self; + emp_vehicle clientfield::set( "enemyvehicle", 2 ); + emp_vehicle.parentStruct killstreaks::configure_team( "emp", emp_vehicle.parentStruct.killstreakId, hacker, undefined, undefined, undefined, true ); +} + +function HackedCallbackPost( hacker ) +{ + emp_vehicle = self; + hacker thread EMP_JamEnemies( emp_vehicle.parentStruct, true ); +} + +function DoneEMPFx( fxTagOrigin ) +{ + PlayFx( "killstreaks/fx_emp_exp_death", fxTagOrigin ); + playsoundatposition( "mpl_emp_turret_deactivate", fxTagOrigin ); +} + +function PlayEMPFx() +{ + emp_vehicle = self; + emp_vehicle playloopsound( "mpl_emp_turret_loop_close" ); + + {wait(.05);}; // workaround for a bug where the fx would not play on subsequent deployment of power cores +} + +function on_timeout() +{ + emp = self; + + if ( isdefined( emp.vehicle ) ) + { + fxTagOrigin = emp.vehicle GetTagorigin( "tag_fx" ); + DoneEMPFx( fxTagOrigin ); + } + ShutdownEMP( emp ); +} + +function OnCancelPlacement( emp ) +{ + StopEMP( emp.team, emp.ownerEntNum, emp.originalTeam, emp.killstreakId ); +} + +function OnTurretDamage( eInflictor, attacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) +{ + empDamage = 0; // emp power core is not affected by emp damage + + iDamage = self killstreaks::OnDamagePerWeapon( "emp", attacker, iDamage, iDFlags, sMeansOfDeath, weapon, self.maxhealth, undefined, self.maxhealth*0.4, undefined, empDamage, undefined, true, 1.0 ); + self.damageTaken += iDamage; + + // turret death + if ( self.damageTaken > self.maxHealth && !isdefined( self.will_die ) ) + { + self.will_die = true; + self thread OnDeathAfterFrameEnd( attacker, weapon ); + } + + return iDamage; +} + +function OnTurretDeath( inflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime ) +{ + // currently, OnTurretDeath is not getting called, so we call OnDeath directly from OnTurretDamage + self OnDeath( attacker, weapon ); +} + +function OnDeathAfterFrameEnd( attacker, weapon ) +{ + waittillframeend; + + if ( isdefined( self ) ) + { + self OnDeath( attacker, weapon ); + } +} + +function OnDeath( attacker, weapon ) +{ + emp_vehicle = self; + + fxTagOrigin = self GetTagorigin( "tag_fx" ); + DoneEMPFx( fxTagOrigin ); + if ( isdefined( attacker ) && IsPlayer( attacker ) && ( !isdefined( emp_vehicle.owner ) || emp_vehicle.owner util::IsEnemyPlayer( attacker ) ) ) + { + attacker challenges::destroyScoreStreak( weapon, false, true, false ); + attacker challenges::destroyNonAirScoreStreak_PostStatsLock( weapon ); + attacker AddPlayerStat( "destroy_turret", 1 ); + attacker AddWeaponStat( weapon, "destroy_turret", 1 ); + scoreevents::processScoreEvent( "destroyed_emp", attacker, emp_vehicle.owner, weapon ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_EMP", attacker.entnum ); + } + + if ( isdefined( attacker ) && isdefined( emp_vehicle.owner ) && ( attacker != emp_vehicle.owner ) ) + emp_vehicle killstreaks::play_destroyed_dialog_on_owner( "emp", emp_vehicle.parentStruct.killstreakId ); + + ShutdownEMP( emp_vehicle.parentStruct ); +} + +function OnShutdown( emp ) +{ + ShutdownEMP( emp ); +} + +function ShutdownEMP( emp ) +{ + if (!isdefined( emp ) ) + return; + + if ( isdefined( emp.already_shutdown ) ) + return; + + emp.already_shutdown = true; + + if ( isdefined( emp.vehicle ) ) + emp.vehicle clientfield::set( "emp_turret_deploy", 0 ); + + StopEMP( emp.team, emp.OwnerEntNum, emp.originalTeam, emp.killstreakId ); + + if ( isdefined( emp.otherModel ) ) + { + emp.otherModel delete(); + } + + if ( isdefined( emp.vehicle ) ) + { + emp.vehicle delete(); + } + + emp delete(); +} + +function StopEMP( currentTeam, currentOwnerEntNum, originalTeam, killstreakID ) +{ + StopEMPEffect( currentTeam, currentOwnerEntNum ); + StopEMPRule( originalTeam, killstreakID ); +} + +function StopEMPEffect( team, ownerEntNum ) +{ + level.ActiveEMPs[ team ] = false; + level.ActivePlayerEMPs[ ownerEntNum ] = false; + level notify ( "emp_updated" ); +} + +function StopEMPRule( killstreakOriginalTeam, killstreakId ) +{ + killstreakrules::killstreakStop( "emp", killstreakOriginalTeam, killstreakId ); +} + +function HasActiveEMP() +{ + return ( level.ActivePlayerEMPs[ self.entNum ] ); +} + +function TeamHasActiveEMP( team ) +{ + return ( level.ActiveEMPs[ team ] > 0 ); +} + +function EnemyEMPActive() +{ + if( level.teamBased ) + { + foreach( team in level.teams ) + { + if( ( team != self.team ) && TeamHasActiveEMP( team ) ) + { + return true; + } + } + } + else + { + enemies = self teams::GetEnemyPlayers(); + foreach( player in enemies ) + { + if( player HasActiveEMP() ) + { + return true; + } + } + } + + return false; +} + +function EnemyEMPOwner() +{ + enemies = self teams::GetEnemyPlayers(); + foreach( player in enemies ) + { + if( player HasActiveEMP() ) + { + return player; + } + } + + return undefined; +} + +function EMP_JamEnemies( empEnt, hacked ) +{ + level endon ( "game_ended" ); + self endon( "killstreak_hacked" ); + + if( level.teamBased ) + { + if ( hacked ) + { + level.ActiveEMPs[ empEnt.OriginalTeam ] = false; + } + level.ActiveEMPs[ self.team ] = true; + } + + if( hacked ) + { + level.ActivePlayerEMPs[ empEnt.originalOwnerEntNum ] = false; + } + level.ActivePlayerEMPs[ self.entNum ] = true; + level notify( "emp_updated" ); + level notify( "emp_deployed" ); + + VisionSetNaked( "flash_grenade", 1.5 ); + wait ( 0.1 ); + VisionSetNaked( "flash_grenade", 0 ); + VisionSetNaked( GetDvarString( "mapname" ), 5.0 ); + + empKillstreakWeapon = GetWeapon( "emp" ); + empKillstreakWeapon.isEmpKillstreak = true; + level killstreaks::DestroyOtherTeamsActiveVehicles( self, empKillstreakWeapon ); + level killstreaks::DestroyOtherTeamsEquipment( self, empKillstreakWeapon ); + level weaponobjects::destroy_other_teams_supplemental_watcher_objects( self, empKillstreakWeapon ); +} + +function EMPTracker() +{ + level endon ( "game_ended" ); + + while( true ) + { + level waittill( "emp_updated" ); + + foreach ( player in level.players ) + { + player UpdateEMP(); + } + } +} + +function UpdateEMP() +{ + player = self; + + enemy_emp_active = player EnemyEMPActive(); + player setEMPJammed( enemy_emp_active ); + + emped = player isEMPJammed(); // ask because a perk may stop it + player clientfield::set_to_player( "empd_monitor_distance", emped ); + + if( emped ) + { + player notify( "emp_jammed" ); + } +} diff --git a/mp/killstreaks/_flak_drone.csc b/mp/killstreaks/_flak_drone.csc new file mode 100644 index 0000000..66181e5 --- /dev/null +++ b/mp/killstreaks/_flak_drone.csc @@ -0,0 +1,82 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; + + + + +#using scripts\mp\_helicopter_sounds; +#using scripts\mp\_util; + + + +#using scripts\shared\duplicaterender_mgr; + + +#namespace flak_drone; + + + +function autoexec __init__sytem__() { system::register("flak_drone",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "vehicle", "flak_drone_camo", 1, 3, "int", &active_camo_changed, !true, !true ); +} + +function active_camo_changed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + flags_changed = self duplicate_render::set_dr_flag( "active_camo_flicker", newVal == ( 2 ) ); + flags_changed = self duplicate_render::set_dr_flag( "active_camo_on", false ) || flags_changed; + flags_changed = self duplicate_render::set_dr_flag( "active_camo_reveal", true ) || flags_changed; + if ( flags_changed ) + { + self duplicate_render::update_dr_filters(localClientNum); + } + + self notify( "endtest" ); + + self thread doReveal( localClientNum, newVal != ( 0 ) ); +} + +function doReveal( localClientNum, direction ) +{ + self notify( "endtest" ); + self endon( "endtest" ); + + self endon( "entityshutdown" ); + + if( direction ) + { + startVal = 1; + } + else + { + startVal = 0; + } + + while( ( startVal >= 0 ) && ( startVal <= 1 ) ) + { + self MapShaderConstant( localClientNum, 0, "scriptVector0", startVal, 0, 0, 0 ); + if( direction ) + { + startVal -= .016 / 0.5; + } + else + { + startVal += .016 / 0.5; + } + wait( .016 ); + } + + flags_changed = self duplicate_render::set_dr_flag( "active_camo_reveal", false ); + flags_changed = self duplicate_render::set_dr_flag( "active_camo_on", direction ) || flags_changed; + if ( flags_changed ) + { + self duplicate_render::update_dr_filters(localClientNum); + } +} \ No newline at end of file diff --git a/mp/killstreaks/_flak_drone.gsc b/mp/killstreaks/_flak_drone.gsc new file mode 100644 index 0000000..679b437 --- /dev/null +++ b/mp/killstreaks/_flak_drone.gsc @@ -0,0 +1,539 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\statemachine_shared; +#using scripts\shared\system_shared; +#using scripts\shared\turret_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_ai_shared; +#using scripts\shared\vehicle_death_shared; +#using scripts\shared\vehicle_shared; + +#using scripts\mp\_util; +#using scripts\mp\gametypes\_shellshock; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_remote_weapons; +#using scripts\mp\teams\_teams; +#using scripts\shared\weapons\_heatseekingmissile; + + + + + + + + + +#precache( "fx", "explosions/fx_vexp_wasp_gibb_death" ); + +#namespace flak_drone; + + + + +function init() +{ + clientfield::register( "vehicle", "flak_drone_camo", 1, 3, "int" ); + + vehicle::add_main_callback( "veh_flak_drone_mp", &InitFlakDrone ); +} + +function InitFlakDrone() +{ + self.health = self.healthdefault; + self vehicle::friendly_fire_shield(); + self EnableAimAssist(); + self SetNearGoalNotifyDist( ( 40 ) ); + self SetHoverParams( ( 50.0 ), ( 75.0 ), ( 100.0 ) ); + self SetVehicleAvoidance( true ); + self.fovcosine = 0; // +/-90 degrees = 180 + self.fovcosinebusy = 0; //+/- 55 degrees = 110 fov + self.vehAirCraftCollisionEnabled = true; + self.goalRadius = 999999; + self.goalHeight = 999999; + self SetGoal( self.origin, false, self.goalRadius, self.goalHeight ); + self thread vehicle_ai::nudge_collision(); + + self.overrideVehicleDamage = &FlakDroneDamageOverride; + + self vehicle_ai::init_state_machine_for_role( "default" ); + self vehicle_ai::get_state_callbacks( "combat" ).enter_func = &state_combat_enter; + self vehicle_ai::get_state_callbacks( "combat" ).update_func = &state_combat_update; + self vehicle_ai::get_state_callbacks( "off" ).enter_func = &state_off_enter; + self vehicle_ai::get_state_callbacks( "off" ).update_func = &state_off_update; + self vehicle_ai::get_state_callbacks( "death" ).update_func = &state_death_update; + + self vehicle_ai::StartInitialState( "off" ); +} + +function state_off_enter( params ) +{ +} + +function state_off_update( params ) +{ + self endon( "change_state" ); + self endon( "death" ); + + while( !isdefined( self.parent ) ) + { + wait( 0.1 ); //Wait for parent to be setup + } + + self.parent endon( "death" ); + + while( true ) + { + self SetSpeed( ( 400 ) ); + + if( ( isdefined( self.inpain ) && self.inpain ) ) + { + wait( ( 0.1 ) ); + } + + self ClearLookAtEnt(); + + self.current_pathto_pos = undefined; + + queryOrigin = self.parent.origin + ( 0, 0, ( -75 ) ); + queryResult = PositionQuery_Source_Navigation( queryOrigin, + ( 25 ), + ( 75 ), + ( 40 ), + ( 40 ), + self ); + + if( isdefined( queryResult ) ) + { + PositionQuery_Filter_DistanceToGoal( queryResult, self ); + vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult ); + + best_point = undefined; + best_score = -999999; + + foreach ( point in queryResult.data ) + { + randomScore = randomFloatRange( 0, 100 ); + distToOriginScore = point.distToOrigin2D * 0.2; + + point.score += randomScore + distToOriginScore; + /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToOrigin" ] = distToOriginScore; #/ point.score += distToOriginScore;; + + if ( point.score > best_score ) + { + best_score = point.score; + best_point = point; + } + } + + self vehicle_ai::PositionQuery_DebugScores( queryResult ); + + if( isdefined( best_point ) ) + { + self.current_pathto_pos = best_point.origin; + } + } + + if( IsDefined( self.current_pathto_pos ) ) + { + self UpdateFlakDroneSpeed(); + if( self SetVehGoalPos( self.current_pathto_pos, true, false ) ) + { + self playsound ("veh_wasp_vox"); + } + else + { + self SetSpeed( ( 400 ) * 3 ); + self.current_pathto_pos = self GetClosestPointOnNavVolume( self.origin, 999999 ); + self SetVehGoalPos( self.current_pathto_pos, true, false ); + } + } + else + { + if( isDefined( self.parent.heliGoalPos ) ) + { + self.current_pathto_pos = self.parent.heliGoalPos; + } + else + { + self.current_pathto_pos = queryOrigin; + } + self UpdateFlakDroneSpeed(); + self SetVehGoalPos( self.current_pathto_pos, true, false ); + } + + wait RandomFloatRange( ( .1 ), ( .2 ) ); + } +} + +function UpdateFlakDroneSpeed() // self == flak drone +{ + desiredSpeed = ( 400 ); + + if ( isdefined( self.parent ) ) + { + parentSpeed = self.parent GetSpeed(); + desiredSpeed = parentSpeed * 0.9; // lag a little back + if ( Distance2DSquared( self.parent.origin, self.origin ) > ( (36) * (36) ) ) // match parent speed if too far from parent + { + if ( isdefined( self.current_pathto_pos ) ) + { + flakDroneDistanceToGoalSquared = Distance2DSquared( self.origin, self.current_pathto_pos ); + parentDistanceToGoalSquared = Distance2DSquared( self.parent.origin, self.current_pathto_pos ); + if ( flakDroneDistanceToGoalSquared > parentDistanceToGoalSquared ) + { + desiredSpeed = parentSpeed * 1.3; + } + else + { + desiredSpeed = parentSpeed * 0.8; + } + } + } + } + + self SetSpeed( max( desiredSpeed, 10 ) ); +} + +function state_combat_enter( params ) +{ +} + +function state_combat_update( params ) +{ + drone = self; + drone endon( "change_state" ); + drone endon( "death" ); + + drone thread SpawnFlakRocket( drone.incoming_missile, drone.origin, drone.parent ); + drone Ghost(); +} + +function SpawnFlakRocket( missile, spawnPos, parent ) +{ + drone = self; + + missile endon("death"); + missile Missile_SetTarget( parent ); + + rocket = MagicBullet( GetWeapon( "flak_drone_rocket" ), spawnPos, missile.origin, parent, missile ); + rocket.team = parent.team; + rocket setTeam( parent.team ); + rocket clientfield::set( "enemyvehicle", 1 ); + rocket Missile_SetTarget( missile ); + missile thread CleanupAfterMissileDeath( rocket, drone ); // sometimes missile gets destroyed by the rocket + + curDist = Distance( missile.origin, rocket.origin ); // note: algorithm requires distance (not distance squared) + + tooCloseToPredictedParent = false; + +/# + debug_draw = GetDvarInt( "scr_flak_drone_debug_trails", 0 ); + debug_duration = GetDvarInt( "scr_flak_drone_debug_trails_duration", 20 * 20 ); // duration in number of server frames +#/ + + while( true ) + { + {wait(.05);}; + + prevDist = curDist; + + if ( isdefined( rocket ) ) + { + curDist = Distance( missile.origin, rocket.origin ); // can't use DistanceSquared() here + + distDelta = prevDist - curDist; + + predictedDist = curDist - distDelta; + } + +/# + if ( debug_draw && isdefined( missile ) ) + util::debug_sphere( missile.origin, 6, ( 0.9, 0, 0 ), 0.9, debug_duration ); // small red sphere for missile trail + + if ( debug_draw && isdefined( rocket ) ) + util::debug_sphere( rocket.origin, 6, ( 0, 0, 0.9 ), 0.9, debug_duration ); // small blue spheres for flak drone trail (as rocket) +#/ + + if ( isdefined( parent ) ) + { + parentVelocity = parent GetVelocity(); + parentPredictedLocation = parent.origin + ( parentVelocity * 0.05 ); + missileVelocity = missile GetVelocity(); + missilePredictedLocation = missile.origin + ( missileVelocity * 0.05 ); + if ( DistanceSquared( parentPredictedLocation, missilePredictedLocation ) < ( (1000) * (1000) ) + || DistanceSquared( parent.origin, missilePredictedLocation ) < ( (1000) * (1000) ) ) + { + tooCloseToPredictedParent = true; + } + } + + if( ( predictedDist < 0 ) || ( curDist > prevDist ) || tooCloseToPredictedParent || !isdefined( rocket ) ) + { +/# + if ( debug_draw && isdefined( parent ) ) + { + if ( tooCloseToPredictedParent && ! ( ( predictedDist < 0 ) || ( curDist > prevDist ) ) ) + { + util::debug_sphere( parent.origin, 18, ( 0.9, 0, 0.9 ), 0.9, debug_duration ); // large purple sphere means too close to parent + } + else + { + util::debug_sphere( parent.origin, 18, ( 0, 0.9, 0 ), 0.9, debug_duration ); // large green sphere means intercepted + } + } +#/ + + if ( isdefined( rocket ) ) + { + rocket detonate(); + } + missile thread heatseekingmissile::_missileDetonate( missile.target_attacker, missile.target_weapon, missile.target_weapon.explosionradius, 10, 20 ); + return; + } + } +} + +function CleanupAfterMissileDeath( rocket, flak_drone ) +{ + missile = self; + missile waittill( "death" ); + + wait 0.5; // make sure explosions fire off before deleting + + if ( isdefined( rocket ) ) + { + rocket delete(); + } + + if ( isdefined( flak_drone ) ) + { + flak_drone delete(); + } +} + +function state_death_update( params ) +{ + self endon( "death" ); + doGibbedDeath = false; + + if( isdefined( self.death_info ) ) + { + if( isdefined( self.death_info.weapon ) ) + { + if( self.death_info.weapon.dogibbing || self.death_info.weapon.doannihilate ) + { + doGibbedDeath = true; + } + } + if( isdefined( self.death_info.meansOfDeath ) ) + { + meansOfDeath = self.death_info.meansOfDeath; + if( meansOfDeath == "MOD_EXPLOSIVE" || meansOfDeath == "MOD_GRENADE_SPLASH" || meansOfDeath == "MOD_PROJECTILE_SPLASH" || meansOfDeath == "MOD_PROJECTILE" ) + { + doGibbedDeath = true; + } + } + } + + if( doGibbedDeath ) + { + self playsound ("veh_wasp_gibbed"); + PlayFxOnTag( "explosions/fx_vexp_wasp_gibb_death", self, "tag_origin" ); + self Ghost(); + self NotSolid(); + + wait( 5 ); + if( isdefined( self ) ) + { + self Delete(); + } + } + else + { + self vehicle_death::flipping_shooting_death(); + } +} + +function drone_pain_for_time( time, stablizeParam, restoreLookPoint ) +{ + self endon( "death" ); + + self.painStartTime = GetTime(); + + if ( !( isdefined( self.inpain ) && self.inpain ) ) + { + self.inpain = true; + + while ( GetTime() < self.painStartTime + time * 1000 ) + { + self SetVehVelocity( self.velocity * stablizeParam ); + self SetAngularVelocity( self GetAngularVelocity() * stablizeParam ); + wait 0.1; + } + + if ( isdefined( restoreLookPoint ) ) + { + restoreLookEnt = Spawn( "script_model", restoreLookPoint ); + restoreLookEnt SetModel( "tag_origin" ); + + self ClearLookAtEnt(); + self SetLookAtEnt( restoreLookEnt ); + self setTurretTargetEnt( restoreLookEnt ); + wait 1.5; + + self ClearLookAtEnt(); + self ClearTurretTarget(); + restoreLookEnt delete(); + } + + self.inpain = false; + } +} + +function drone_pain( eAttacker, damageType, hitPoint, hitDirection, hitLocationInfo, partName ) +{ + if ( !( isdefined( self.inpain ) && self.inpain ) ) + { + yaw_vel = math::randomSign() * RandomFloatRange( 280, 320 ); + + ang_vel = self GetAngularVelocity(); + ang_vel += ( RandomFloatRange( -120, -100 ), yaw_vel, RandomFloatRange( -200, 200 ) ); + self SetAngularVelocity( ang_vel ); + + self thread drone_pain_for_time( 0.8, 0.7 ); + } +} + +function FlakDroneDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) +{ + if( sMeansOfDeath == "MOD_TRIGGER_HURT" ) + return 0; + + if( isdefined( eAttacker ) && isdefined( eAttacker.team ) && eAttacker.team != self.team ) + { + drone_pain( eAttacker, sMeansOfDeath, vPoint, vDir, sHitLoc, partName ); + } + + return iDamage; +} + +function Spawn( parent, onDeathCallback ) +{ + if( !IsNavVolumeLoaded() ) + { + /# IPrintLnBold( "Error: NavVolume Not Loaded" ); #/ + return undefined; + } + + spawnPoint = parent.origin + ( 0, 0, -50 ); + + drone = SpawnVehicle( "veh_flak_drone_mp", spawnPoint, parent.angles, "dynamic_spawn_ai" ); + drone.death_callback = onDeathCallback; + drone configureTeam( parent, false ); + drone thread WatchGameEvents(); + drone thread WatchDeath(); + drone thread WatchParentDeath(); + drone thread WatchParentMissiles(); + return drone; +} + +function configureTeam( parent, isHacked ) +{ + drone = self; + drone.team = parent.team; + drone SetTeam( parent.team ); + if ( isHacked ) + { + drone clientfield::set( "enemyvehicle", 2 ); + } + else + { + drone clientfield::set( "enemyvehicle", 1 ); + } + drone.parent = parent; + +} + +function WatchGameEvents() +{ + drone = self; + drone endon( "death" ); + + drone.parent.owner util::waittill_any( "game_ended", "emp_jammed", "disconnect", "joined_team" ); + drone Shutdown( true ); +} + +function WatchDeath() +{ + drone = self; + drone.parent endon( "death" ); + + drone waittill( "death" ); + drone Shutdown( true ); +} + +function WatchParentDeath() +{ + drone = self; + drone endon( "death" ); + + drone.parent waittill( "death" ); + drone Shutdown( true ); +} + +function WatchParentMissiles() +{ + drone = self; + drone endon( "death" ); + drone.parent endon( "death" ); + + drone.parent waittill( "stinger_fired_at_me", missile, weapon, attacker ); + + drone.incoming_missile = missile; + drone.incoming_missile.target_weapon = weapon; + drone.incoming_missile.target_attacker = attacker; + drone vehicle_ai::set_state( "combat" ); +} + +function SetCamoState( state ) +{ + self clientfield::set( "flak_drone_camo", state ); +} + +function Shutdown( explode ) +{ + drone = self; + + if( isdefined( drone.death_callback ) ) + { + drone.parent thread [[ drone.death_callback ]](); + } + + if( isdefined( drone ) && !isdefined( drone.parent ) ) + { + drone Ghost(); + drone NotSolid(); + wait( 5 ); + if( isdefined( drone ) ) + drone Delete(); + } + + if( isdefined( drone ) ) + { + if( explode ) + { + drone DoDamage( drone.health + 1000, drone.origin, drone, drone, "none", "MOD_EXPLOSIVE" ); + } + else + { + drone Delete(); + } + } +} diff --git a/mp/killstreaks/_helicopter.csc b/mp/killstreaks/_helicopter.csc new file mode 100644 index 0000000..1a7e692 --- /dev/null +++ b/mp/killstreaks/_helicopter.csc @@ -0,0 +1,661 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\callbacks_shared; + + + + +#using scripts\mp\_helicopter_sounds; +#using scripts\mp\_util; + + + +#using scripts\shared\duplicaterender_mgr; + + +#precache( "client_fx", "killstreaks/fx_heli_smk_trail_engine_33" ); +#precache( "client_fx", "killstreaks/fx_heli_smk_trail_engine_66" ); +#precache( "client_fx", "killstreaks/fx_drgnfire_rotor_wash_runner" ); +#precache( "client_fx", "killstreaks/fx_ed_lights_green" ); +#precache( "client_fx", "killstreaks/fx_ed_lights_red" ); +#precache( "client_fx", "killstreaks/fx_sc_lights_grn" ); +#precache( "client_fx", "killstreaks/fx_sc_lights_red" ); +#precache( "client_fx", "killstreaks/fx_vtol_lights_grn" ); +#precache( "client_fx", "killstreaks/fx_vtol_lights_red" ); +#precache( "client_fx", "killstreaks/fx_vtol_thruster" ); +#precache( "client_fx", "killstreaks/fx_drone_hunter_lights" ); + +#using_animtree( "mp_vehicles" ); + +#namespace helicopter; + + + + + + + + +function autoexec __init__sytem__() { system::register("helicopter",&__init__,undefined,undefined); } + +function __init__() +{ + level.chopper_fx["damage"]["light_smoke"] = "killstreaks/fx_heli_smk_trail_engine_33"; + level.chopper_fx["damage"]["heavy_smoke"] = "killstreaks/fx_heli_smk_trail_engine_66"; + + level._effect["qrdrone_prop"] = "killstreaks/fx_drgnfire_rotor_wash_runner"; + level._effect["heli_guard_light"]["friendly"] = "killstreaks/fx_sc_lights_grn"; + level._effect["heli_guard_light"]["enemy"] = "killstreaks/fx_sc_lights_red"; + level._effect["heli_comlink_light"]["common"] = "killstreaks/fx_drone_hunter_lights"; + level._effect["heli_gunner_light"]["friendly"] = "killstreaks/fx_vtol_lights_grn"; + level._effect["heli_gunner_light"]["enemy"] = "killstreaks/fx_vtol_lights_red"; + + level._effect["heli_gunner"]["vtol_fx"] = "killstreaks/fx_vtol_thruster"; + level._effect["heli_gunner"]["vtol_fx_ft"] = "killstreaks/fx_vtol_thruster"; + + clientfield::register( "helicopter", "heli_warn_targeted", 1, 1, "int", &warnMissileLocking, !true, !true); + clientfield::register( "helicopter", "heli_warn_locked", 1, 1, "int", &warnMissileLocked, !true, !true); + clientfield::register( "helicopter", "heli_warn_fired", 1, 1, "int", &warnMissileFired, !true, !true); + + clientfield::register( "helicopter", "supplydrop_care_package_state", 1, 1, "int",&supplydrop_care_package_state, !true, !true); + clientfield::register( "helicopter", "supplydrop_ai_tank_state", 1, 1, "int",&supplydrop_ai_tank_state, !true, !true); + + clientfield::register( "helicopter", "heli_comlink_bootup_anim", 1, 1, "int",&heli_comlink_bootup_anim, !true, !true); + + + clientfield::register( "vehicle", "heli_warn_targeted", 1, 1, "int", &warnMissileLocking, !true, !true); + clientfield::register( "vehicle", "heli_warn_locked", 1, 1, "int", &warnMissileLocked, !true, !true); + clientfield::register( "vehicle", "heli_warn_fired", 1, 1, "int", &warnMissileFired, !true, !true); + + clientfield::register( "vehicle", "supplydrop_care_package_state", 1, 1, "int",&supplydrop_care_package_state, !true, !true); + clientfield::register( "vehicle", "supplydrop_ai_tank_state", 1, 1, "int",&supplydrop_ai_tank_state, !true, !true); + + clientfield::register( "vehicle", "heli_comlink_bootup_anim", 1, 1, "int",&heli_comlink_bootup_anim, !true, !true); + + duplicate_render::set_dr_filter_framebuffer( "active_camo_scorestreak", 90, "active_camo_on", "", 0, "mc/hud_outline_predator_camo_active_enemy_scorestreak", 0 ); + duplicate_render::set_dr_filter_framebuffer( "active_camo_flicker_scorestreak", 80, "active_camo_flicker", "", 0, "mc/hud_outline_predator_camo_disruption_enemy_scorestreak", 0 ); + duplicate_render::set_dr_filter_framebuffer_duplicate( "active_camo_reveal_scorestreak_dr", 90, "active_camo_reveal", "hide_model", 1, "mc/hud_outline_predator_camo_active_enemy_scorestreak", 0 ); + duplicate_render::set_dr_filter_framebuffer( "active_camo_reveal_scorestreak", 80, "active_camo_reveal,hide_model", "", 0, "mc/hud_outline_predator_scorestreak", 0 ); + + clientfield::register( "helicopter", "active_camo", 1, 3, "int", &active_camo_changed, !true, !true ); + clientfield::register( "vehicle", "active_camo", 1, 3, "int", &active_camo_changed, !true, !true ); + + clientfield::register( "toplayer", "marker_state", 1, 2, "int", &marker_state_changed, !true, !true ); + + clientfield::register( "scriptmover", "supplydrop_thrusters_state", 1, 1, "int", &setSupplydropThrustersState, !true, !true ); + clientfield::register( "scriptmover", "aitank_thrusters_state", 1, 1, "int", &setAITankhrustersState, !true, !true ); + + clientfield::register( "vehicle", "mothership", 1, 1, "int", &mothership_cb, !true, !true ); + + callback::on_spawned( &on_player_spawned ); +} + +function on_player_spawned( localClientNum ) +{ + player = self; + player waittill( "entityshutdown" ); + player.markerFX = undefined; + if( isdefined( player.markerObj ) ) + { + player.markerObj Delete(); + } + if( isdefined( player.markerFXHandle ) ) + { + KillFX( localClientNum, player.markerFXHandle ); + player.markerFXHandle = undefined; + } +} + +function SetupAnimTree() +{ + if ( self HasAnimTree() == false ) + self UseAnimTree( #animtree ); +} + +function active_camo_changed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if( newVal == ( 0 ) ) + { + self thread heli_comlink_lights_on_after_wait( localClientNum, ( 0.7 ) ); + } + else + { + self heli_comlink_lights_off( localClientNum ); + } + + flags_changed = self duplicate_render::set_dr_flag( "active_camo_flicker", newVal == ( 2 ) ); + flags_changed = self duplicate_render::set_dr_flag( "active_camo_on", false ) || flags_changed; + flags_changed = self duplicate_render::set_dr_flag( "active_camo_reveal", true ) || flags_changed; + + if ( flags_changed ) + { + self duplicate_render::update_dr_filters(localClientNum); + } + + self notify( "endtest" ); + + self thread doReveal( localClientNum, newVal != ( 0 ) ); +} + +function doReveal( local_client_num, direction ) +{ + self notify( "endtest" ); + self endon( "endtest" ); + + self endon( "entityshutdown" ); + + if( direction ) + { + self duplicate_render::update_dr_flag( local_client_num, "hide_model", false ); + startVal = 0; + endVal = 1; + } + else + { + self duplicate_render::update_dr_flag( local_client_num, "hide_model", true ); + startVal = 1; + endVal = 0; + } + + priorValue = startVal; + while( ( startVal >= 0 ) && ( startVal <= 1 ) ) + { + self MapShaderConstant( local_client_num, 0, "scriptVector0", startVal, 0, 0, 0 ); + if( direction ) + { + startVal += .016 / 0.5; + if( ( priorValue < .5 ) && ( startVal >= .5 ) ) + { + self duplicate_render::set_dr_flag( "hide_model", true ); + self duplicate_render::change_dr_flags(local_client_num); + } + } + else + { + startVal -= .016 / 0.5; + if( ( priorValue > .5 ) && ( startVal <= .5 ) ) + { + self duplicate_render::set_dr_flag( "hide_model", false ); + self duplicate_render::change_dr_flags(local_client_num); + } + } + priorValue = startVal; + wait( .016 ); + } + self MapShaderConstant( local_client_num, 0, "scriptVector0", endVal, 0, 0, 0 ); + + flags_changed = self duplicate_render::set_dr_flag( "active_camo_reveal", false ); + flags_changed = self duplicate_render::set_dr_flag( "active_camo_on", direction ) || flags_changed; + if ( flags_changed ) + { + self duplicate_render::update_dr_filters(local_client_num); + } +} + +function heli_comlink_bootup_anim( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + self endon( "entityshutdown" ); + self endon( "death" ); + + self SetupAnimTree(); + self SetAnim( %veh_anim_future_heli_gearup_bay_open, 1.0, 0.0, 1.0 ); +} + +function supplydrop_care_package_state( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + self endon( "entityshutdown" ); + self endon( "death" ); + self SetupAnimTree(); + if ( newVal == 1 ) + { + self SetAnim( %o_drone_supply_care_idle, 1.0, 0.0, 1.0 ); + } + else + { + self SetAnim( %o_drone_supply_care_drop, 1.0, 0.0, 0.3 ); + } +} + +function supplydrop_ai_tank_state( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + self endon( "entityshutdown" ); + self endon( "death" ); + self SetupAnimTree(); + + if ( newVal == 1 ) + { + self SetAnim( %o_drone_supply_agr_idle, 1.0, 0.0, 1.0 ); + } + else + { + self SetAnim( %o_drone_supply_agr_drop, 1.0, 0.0, 0.3 ); + } +} + +function warnMissileLocking( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( newVal && !(self IsLocalClientDriver(localClientNum)) ) + return; + + helicopter_sounds::play_targeted_sound( newVal ); +} + +function warnMissileLocked( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( newVal && !(self IsLocalClientDriver(localClientNum)) ) + return; + + helicopter_sounds::play_locked_sound( newVal ); +} + +function warnMissileFired( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( newVal && !(self IsLocalClientDriver(localClientNum)) ) + return; + + helicopter_sounds::play_fired_sound( newVal ); +} + +function heli_deletefx(localClientNum) +{ + if (isdefined(self.exhaustLeftFxHandle)) + { + deletefx( localClientNum, self.exhaustLeftFxHandle ); + self.exhaustLeftFxHandle = undefined; + } + + if (isdefined(self.exhaustRightFxHandlee)) + { + deletefx( localClientNum, self.exhaustRightFxHandle ); + self.exhaustRightFxHandle = undefined; + } + + if (isdefined(self.lightFXID)) + { + deletefx( localClientNum, self.lightFXID ); + self.lightFXID = undefined; + } + + if (isdefined(self.propFXID)) + { + deletefx( localClientNum, self.propFXID ); + self.propFXID = undefined; + } + + if (isdefined(self.vtolLeftFXID)) + { + deletefx( localClientNum, self.vtolLeftFXID ); + self.vtolLeftFXID = undefined; + } + + if (isdefined(self.vtolRightFXID)) + { + deletefx( localClientNum, self.vtolRightFXID ); + self.vtolRightFXID = undefined; + } + +} + +function startfx(localClientNum) +{ + self endon( "entityshutdown" ); + + if ( isdefined( self.vehicletype ) ) + { + if ( self.vehicletype == "remote_mortar_vehicle_mp" ) + { + return; + } + + if ( self.vehicletype == "vehicle_straferun_mp" ) + { + return; + } + } + + if( isdefined( self.exhaustfxname ) && self.exhaustfxname != "" ) + { + self.exhaustFx = self.exhaustfxname; + } + + if( isdefined(self.exhaustFx) ) + { + self.exhaustLeftFxHandle = PlayFXOnTag( localClientNum, self.exhaustFx, self, "tag_engine_left" ); + if( !( isdefined( self.oneexhaust ) && self.oneexhaust ) /*!self.oneexhaust*/ )//TODO T7 - not sure why this isn't getting set anymore + { + self.exhaustRightFxHandle = PlayFXOnTag( localClientNum, self.exhaustFx, self, "tag_engine_right" ); + } + } + else + { + /# PrintLn("Client: _helicopter.csc - startfx() - exhaust rotor fx is not loaded"); #/ + } + + if( isdefined( self.vehicletype ) ) + { + light_fx = undefined; + prop_fx = undefined; + + switch( self.vehicletype ) + { + case "heli_ai_mp": + light_fx = "heli_comlink_light"; + break; + case "heli_player_gunner_mp": + //TODO store FX not on self to prevent edge cases where it doesnt get cleaned up + self.vtolLeftFXID = PlayFXOnTag( localClientNum, level._effect["heli_gunner"]["vtol_fx"], self, "tag_engine_left" ); + self.vtolRightFXID = PlayFXOnTag( localClientNum, level._effect["heli_gunner"]["vtol_fx_ft"], self, "tag_engine_right" ); + light_fx = "heli_gunner_light"; + break; + case "heli_guard_mp": + light_fx = "heli_guard_light"; + break; + case "qrdrone_mp": + prop_fx = "qrdrone_prop"; + break; + }; + + if ( isdefined( light_fx ) ) + { + if ( self util::friend_not_foe( localClientNum ) ) + { + self.lightFXID = PlayFXOnTag( localClientNum, level._effect[light_fx]["friendly"], self, "tag_origin" ); + } + else + { + self.lightFXID = PlayFXOnTag( localClientNum, level._effect[light_fx]["enemy"], self, "tag_origin" ); + } + } + + if ( isdefined( prop_fx ) && !self IsLocalClientDriver( localClientNum )) + { + self.propFXID = PlayFXOnTag( localClientNum, level._effect[prop_fx], self, "tag_origin" ); + } + } + + self damage_fx_stages(localClientNum); +} + +function startfx_loop(localClientNum) +{ + self endon( "entityshutdown" ); + + self thread helicopter_sounds::aircraft_dustkick(localClientNum); + + startfx( localClientNum ); + + serverTime = getServerTime( 0 ); + lastServerTime = serverTime; + + while( isdefined( self ) ) + { + if (serverTime < lastServerTime) + { + heli_deletefx( localClientNum ); + startfx( localClientNum ); + } + wait( 0.05 ); // small for added granularity. any bigger and rapid time switching can cause problems. + lastServerTime = serverTime; + serverTime = getServerTime( 0 ); + } +} + +function damage_fx_stages(localClientNum) +{ + self endon( "entityshutdown" ); + + last_damage_state = self GetHeliDamageState(); + fx = undefined; + + for ( ;; ) + { + if ( last_damage_state != self GetHeliDamageState() ) + { + if ( self GetHeliDamageState() == 2 ) + { + if ( isdefined(fx) ) + stopfx( localClientNum, fx ); + + fx = trail_fx( localClientNum, level.chopper_fx["damage"]["light_smoke"], "tag_engine_left" ); + } + else if ( self GetHeliDamageState() == 1 ) + { + if ( isdefined(fx) ) + stopfx( localClientNum, fx ); + + fx = trail_fx( localClientNum, level.chopper_fx["damage"]["heavy_smoke"], "tag_engine_left" ); + } + else + { + if ( isdefined(fx) ) + stopfx( localClientNum, fx ); + + self notify( "stop trail" ); + } + last_damage_state = self GetHeliDamageState(); + } + wait(0.25); + } +} + +function trail_fx( localClientNum, trail_fx, trail_tag ) +{ + id = playfxontag( localClientNum, trail_fx, self, trail_tag ); + + return id; +} + +function heli_comlink_lights_on_after_wait( localClientNum, wait_time ) +{ + self endon( "entityshutdown" ); + self endon( "heli_comlink_lights_off" ); + + wait wait_time; + + self heli_comlink_lights_on( localclientnum ); +} + +function heli_comlink_lights_on( localClientNum ) +{ + if( !isdefined( self.light_fx_handles_heli_comlink ) ) + { + self.light_fx_handles_heli_comlink = []; + } + + self.light_fx_handles_heli_comlink[ 0 ] = PlayFXOnTag( localClientNum, level._effect["heli_comlink_light"]["common"], self, "tag_fx_light_left" ); + self.light_fx_handles_heli_comlink[ 1 ] = PlayFXOnTag( localClientNum, level._effect["heli_comlink_light"]["common"], self, "tag_fx_light_right" ); + self.light_fx_handles_heli_comlink[ 2 ] = PlayFXOnTag( localClientNum, level._effect["heli_comlink_light"]["common"], self, "tag_fx_tail" ); + self.light_fx_handles_heli_comlink[ 3 ] = PlayFXOnTag( localClientNum, level._effect["heli_comlink_light"]["common"], self, "tag_fx_scanner" ); + + if( isdefined( self.team ) ) + { + for ( i = 0; i < self.light_fx_handles_heli_comlink.size; i++ ) + SetFXTeam( localClientNum, self.light_fx_handles_heli_comlink[ i ], self.owner.team ); + } +} + +function heli_comlink_lights_off( localClientNum ) +{ + self notify( "heli_comlink_lights_off" ); + + if ( isdefined( self.light_fx_handles_heli_comlink ) ) + { + for( i = 0; i < self.light_fx_handles_heli_comlink.size; i++ ) + { + if ( isdefined( self.light_fx_handles_heli_comlink[ i ] ) ) + { + DeleteFX( localClientNum, self.light_fx_handles_heli_comlink[ i ] ); + } + } + + self.light_fx_handles_heli_comlink = undefined; + } +} + +function UpdateMarkerThread( localClientNum ) +{ + self endon( "entityshutdown" ); + + player = self; + + killstreakCoreBundle = struct::get_script_bundle( "killstreak", "killstreak_core" ); + + while( isdefined( player.markerObj ) ) + { + viewAngles = GetLocalClientAngles( localClientNum ); + + forwardVector = VectorScale( AnglesToForward( viewAngles ), killstreakCoreBundle.ksMaxAirdropTargetRange ); + results = BulletTrace( player GetEye(), player GetEye() + forwardVector, false, player ); + + player.markerObj.origin = results["position"]; + + wait( .016 ); + } +} + +function StopCrateEffects( localClientNum ) +{ + crate = self; + + if( isdefined( crate.thrusterFxHandle0 ) ) + StopFX( localClientNum, crate.thrusterFxHandle0 ); + if( isdefined( crate.thrusterFxHandle1 ) ) + StopFX( localClientNum, crate.thrusterFxHandle1 ); + if( isdefined( crate.thrusterFxHandle2 ) ) + StopFX( localClientNum, crate.thrusterFxHandle2 ); + if( isdefined( crate.thrusterFxHandle3 ) ) + StopFX( localClientNum, crate.thrusterFxHandle3 ); + + crate.thrusterFxHandle0 = undefined; + crate.thrusterFxHandle1 = undefined; + crate.thrusterFxHandle2 = undefined; + crate.thrusterFxHandle3 = undefined; +} + +function CleanupThrustersThread( localClientNum ) +{ + crate = self; + + crate notify( "CleanupThrustersThread_singleton" ); + crate endon( "CleanupThrustersThread_singleton" ); + + crate waittill( "entityshutdown" ); + + crate StopCrateEffects( localClientNum ); +} + +function setSupplydropThrustersState( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + crate = self; + params = struct::get_script_bundle( "killstreak", "killstreak_supply_drop" ); + + if( ( newVal != oldVal ) && isdefined( params.ksThrusterFX ) ) + { + if( newVal == 1 ) + { + crate StopCrateEffects( localClientNum ); + + crate.thrusterFxHandle0 = PlayFXOnTag( localClientNum, params.ksThrusterFX, crate, "tag_thruster_fx_01" ); + crate.thrusterFxHandle1 = PlayFXOnTag( localClientNum, params.ksThrusterFX, crate, "tag_thruster_fx_02" ); + crate.thrusterFxHandle2 = PlayFXOnTag( localClientNum, params.ksThrusterFX, crate, "tag_thruster_fx_03" ); + crate.thrusterFxHandle3 = PlayFXOnTag( localClientNum, params.ksThrusterFX, crate, "tag_thruster_fx_04" ); + + crate thread CleanupThrustersThread( localClientNum ); + } + else + { + crate StopCrateEffects( localClientNum ); + } + } +} + +function mothership_cb( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ +} + +function setAITankhrustersState( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + crate = self; + params = struct::get_script_bundle( "killstreak", "killstreak_ai_tank_drop" ); + + if( ( newVal != oldVal ) && isdefined( params.ksThrusterFX ) ) + { + if( newVal == 1 ) + { + crate StopCrateEffects( localClientNum ); + + crate.thrusterFxHandle0 = PlayFXOnTag( localClientNum, params.ksThrusterFX, crate, "tag_thruster_fx_01" ); + crate.thrusterFxHandle1 = PlayFXOnTag( localClientNum, params.ksThrusterFX, crate, "tag_thruster_fx_02" ); + crate.thrusterFxHandle2 = PlayFXOnTag( localClientNum, params.ksThrusterFX, crate, "tag_thruster_fx_03" ); + crate.thrusterFxHandle3 = PlayFXOnTag( localClientNum, params.ksThrusterFX, crate, "tag_thruster_fx_04" ); + + crate thread CleanupThrustersThread( localClientNum ); + } + else + { + crate StopCrateEffects( localClientNum ); + } + } +} + +function marker_state_changed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self endon( "entityshutdown" ); + + player = self; + + killstreakCoreBundle = struct::get_script_bundle( "killstreak", "killstreak_core" ); + + // Pick the new effect + if( newVal == 1 ) + { + player.markerFX = killstreakCoreBundle.fxValidLocation; + } + else if ( newVal == 2 ) + { + player.markerFX = killstreakCoreBundle.fxInvalidLocation; + } + else + { + player.markerFX = undefined; + } + + // Another thread is waiting for dobj creation + if ( isdefined( player.markerObj ) && !( player.markerObj hasdobj( localClientNum ) ) ) + { + return; + } + + // Remove the old effect + if( isdefined( player.markerFXHandle ) ) + { + KillFX( localClientNum, player.markerFXHandle ) ; + player.markerFXHandle = undefined; + } + + if ( isdefined( player.markerFX ) ) + { + if( !isdefined( player.markerObj ) ) + { + player.markerObj = Spawn( localClientNum, ( 0, 0, 0 ), "script_model" ); + player.markerObj.angles = ( 270, 0, 0 ); + + player.markerObj SetModel( "wpn_t7_none_world" ); // No-model model to create dobj + + player.markerObj util::waittill_dobj( localClientNum ); + + player thread UpdateMarkerThread( localClientNum ); + } + + player.markerFXHandle = PlayFXOnTag( localClientNum, player.markerFX, player.markerObj, "tag_origin" ); + } + else if( isdefined( player.markerObj ) ) + { + player.markerObj Delete(); + } +} \ No newline at end of file diff --git a/mp/killstreaks/_helicopter.gsc b/mp/killstreaks/_helicopter.gsc new file mode 100644 index 0000000..1f78da3 --- /dev/null +++ b/mp/killstreaks/_helicopter.gsc @@ -0,0 +1,3246 @@ +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\weapons\_heatseekingmissile; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_player; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_dogs; +#using scripts\mp\killstreaks\_flak_drone; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; + + + + + + +#using_animtree ( "mp_vehicles" ); + +#namespace helicopter; + +#precache( "locationselector", "compass_objpoint_helicopter" ); +#precache( "string", "MP_DESTROYED_HELICOPTER"); +#precache( "string", "KILLSTREAK_DESTROYED_HELICOPTER_GUNNER"); +#precache( "string", "KILLSTREAK_EARNED_HELICOPTER_COMLINK" ); +#precache( "string", "KILLSTREAK_HELICOPTER_COMLINK_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_HELICOPTER_COMLINK_INBOUND" ); +#precache( "string", "KILLSTREAK_HELICOPTER_COMLINK_HACKED" ); +#precache( "string", "KILLSTREAK_DESTROYED_SUPPLY_DROP_DEPLOY_SHIP" ); +#precache( "string", "mpl_killstreak_heli" ); +#precache( "fx", "killstreaks/fx_heli_exp_lg" ); +#precache( "fx", "killstreaks/fx_heli_exp_md" ); +#precache( "fx", "killstreaks/fx_vtol_exp" ); +#precache( "fx", "killstreaks/fx_heli_exp_sm" ); +#precache( "fx", "killstreaks/fx_heli_smk_trail_engine_33" ); +#precache( "fx", "killstreaks/fx_heli_smk_trail_engine_66" ); +#precache( "fx", "killstreaks/fx_heli_smk_trail_tail" ); +#precache( "fx", "killstreaks/fx_heli_smk_trail_engine" ); +#precache( "fx", "killstreaks/fx_sc_lights_grn" ); +#precache( "fx", "killstreaks/fx_sc_lights_red" ); + + + + + +function precachehelicopter(model,type) +{ + if(!isdefined(type)) + type = "blackhawk"; + + level.vehicle_deathmodel[model] = model; + + /******************************************************/ + /* SETUP WEAPON TAGS */ + /******************************************************/ + + // helicopter sounds: + level.heli_sound["hit"] = "evt_helicopter_hit"; + level.heli_sound["hitsecondary"] = "evt_helicopter_hit"; + level.heli_sound["damaged"] = "null"; + level.heli_sound["spinloop"] = "evt_helicopter_spin_loop"; + level.heli_sound["spinstart"] = "evt_helicopter_spin_start"; + level.heli_sound["crash"] = "evt_helicopter_midair_exp"; + level.heli_sound["missilefire"] = "wpn_hellfire_fire_npc"; +} + +function useKillstreakHelicopter( hardpointType ) +{ + if ( self killstreakrules::isKillstreakAllowed( hardpointType, self.team ) == false) + return false; + + if ( (!isdefined( level.heli_paths ) || !level.heli_paths.size) ) + { + /#iprintlnbold("Need to add helicopter paths to the level");#/ + return false; + } + + if ( ( hardpointType == "helicopter_comlink" ) || ( hardpointType == "inventory_helicopter_comlink" ) ) + { + result = self selectHelicopterLocation( hardpointType ); + if ( !isdefined(result) || result == false ) + return false; + } + + destination = 0; + missilesEnabled = false; + if ( hardpointType == "helicopter_x2" ) + { + missilesEnabled = true; + } + + assert( level.heli_paths.size > 0, "No non-primary helicopter paths found in map" ); + + random_path = randomint( level.heli_paths[destination].size ); + + startnode = level.heli_paths[destination][random_path]; + + protectLocation = undefined; + armored = false; + if ( ( hardpointType == "helicopter_comlink" ) || ( hardpointType == "inventory_helicopter_comlink" ) ) + { + protectLocation = (level.helilocation[0], level.helilocation[1], int(airsupport::getMinimumFlyHeight()) -200 ); + armored = false; + + startnode = getValidProtectLocationStart(random_path, protectLocation, destination ); + } + + killstreak_id = self killstreakrules::killstreakStart( hardpointType, self.team ); + if ( killstreak_id == -1 ) + return false; + + if ( ( hardpointType == "helicopter_comlink" ) || ( hardpointType == "inventory_helicopter_comlink" ) ) + { + self challenges::calledInComlinkChopper(); + } + + self killstreaks::play_killstreak_start_dialog( hardpointType, self.team, killstreak_id ); + self thread announceHelicopterInbound( hardpointType ); + + thread heli_think( self, startnode, self.team, missilesEnabled, protectLocation, hardpointType, armored, killstreak_id ); + + return true; +} + +function announceHelicopterInbound(hardpointType) +{ + team = self.team; + + self AddWeaponStat( killstreaks::get_killstreak_weapon( hardpointtype ), "used", 1 ); +} + +// generate path graph from script_origins +function heli_path_graph() +{ + // collecting all start nodes in the map to generate path arrays + path_start = getentarray( "heli_start", "targetname" ); // start pointers, point to the actual start node on path + path_dest = getentarray( "heli_dest", "targetname" ); // dest pointers, point to the actual dest node on path + loop_start = getentarray( "heli_loop_start", "targetname" ); // start pointers for loop path in the map + gunner_loop_start = getentarray( "heli_gunner_loop_start", "targetname" ); // start pointers for gunner loop path in the map + leave_nodes = getentarray( "heli_leave", "targetname" ); // points where the helicopter leaves to + crash_start = getentarray( "heli_crash_start", "targetname" ); // start pointers, point to the actual start node on crash path + + assert( ( isdefined( path_start ) && isdefined( path_dest ) ), "Missing path_start or path_dest" ); + + // for each destination, loop through all start nodes in level to populate array of start nodes that leads to this destination + for (i=0; i 0 ), "No path(s) to destination" ); + + // load the array of start nodes that lead to this destination node into level.heli_paths array as an element + if ( isPrimaryDest ) + level.heli_primary_path = startnode_array; + else + level.heli_paths[level.heli_paths.size] = startnode_array; + } + + // loop paths array + for (i=0; i= level.heli_missile_max ) + self waittill( "missile fired" ); + else + { + // regenerates faster when damaged + if ( self.currentstate == "heavy smoke" ) + wait( level.heli_missile_regen_time/4 ); + else if ( self.currentstate == "light smoke" ) + wait( level.heli_missile_regen_time/2 ); + else + wait( level.heli_missile_regen_time ); + } + if( self.missile_ammo < level.heli_missile_max ) + self.missile_ammo++; + } +} + +// helicopter targeting logic +function heli_targeting( missilesEnabled, hardpointType ) +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + // targeting sweep cycle + for ( ;; ) + { + // array of helicopter's targets + targets = []; + targetsMissile = []; + // scan for all players in game + players = level.players; + for (i = 0; i < players.size; i++) + { + player = players[i]; + + if ( self canTargetPlayer_turret( player, hardpointType ) ) + { + if( isdefined( player ) ) + targets[targets.size] = player; + } + if ( missilesEnabled && ( self canTargetPlayer_missile( player, hardpointType ) ) ) + { + if( isdefined( player ) ) + targetsMissile[targetsMissile.size] = player; + } + else + continue; + } + + dogs = dogs::dog_manager_get_dogs(); + + foreach( dog in dogs ) + { + if ( self canTargetDog_turret( dog ) ) + { + targets[targets.size] = dog; + } + if ( missilesEnabled && (self canTargetDog_missile( dog ) ) ) + { + targetsMissile[targetsMissile.size] = dog; + } + } + + tanks = GetEntArray( "talon", "targetname" ); + tanks = ArrayCombine( tanks, GetEntArray( "siegebot", "targetname" ), false, false ); + + foreach( tank in tanks ) + { + if ( self canTargetTank_turret( tank ) ) + { + targets[targets.size] = tank; + } + } + + actors = GetActorArray(); + foreach( actor in actors ) + { + if( isdefined( actor ) && isdefined( actor.isAiClone ) && IsAlive( actor ) ) + { + if ( self canTargetActor_turret( actor, hardpointType ) ) + { + targets[targets.size] = actor; + } + } + } + + // no targets found + if ( targets.size == 0 && targetsMissile.size == 0 ) + { + self.primaryTarget = undefined; + self.secondaryTarget = undefined; + debug_print_target(); // debug + self SetGoalYaw(RandomInt(360)); + wait ( self.targeting_delay ); + continue; + } + + if( targets.size == 1 ) + { + if( isdefined( targets[0].isAiClone ) ) + { + killstreaks::update_actor_threat( targets[0] ); + } + else if( isdefined( targets[0].type ) && (targets[0].type == "dog" || targets[0].type == "tank_drone")) + { + killstreaks::update_dog_threat( targets[0] ); + } + else if ( isdefined( targets[0].killstreakType ) ) + { + killstreaks::update_non_player_threat( targets[0] ); + } + else + { + killstreaks::update_player_threat( targets[0] ); + } + + self.primaryTarget = targets[0]; // primary only + self notify( "primary acquired" ); + self.secondaryTarget = undefined; + debug_print_target(); // debug + } + else if ( targets.size > 1 ) + assignPrimaryTargets( targets ); + + if( targetsMissile.size == 1 ) + { + if( !isdefined( targetsMissile[0].type ) || targetsMissile[0].type != "dog" || targets[0].type == "tank_drone") + { + self killstreaks::update_missile_player_threat( targetsMissile[0] ); + } + else if( targetsMissile[0].type == "dog" ) + { + self killstreaks::update_missile_dog_threat( targetsMissile[0] ); + } + + self.secondaryTarget = targetsMissile[0]; // primary only + self notify( "secondary acquired" ); + debug_print_target(); // debug + } + else if( targetsMissile.size > 1 ) + assignSecondaryTargets( targetsMissile ); + + wait ( self.targeting_delay ); + + debug_print_target(); //debug + } +} + +// targetability +function canTargetPlayer_turret( player, hardpointType ) +{ + canTarget = true; + + if ( !isalive( player ) || player.sessionstate != "playing" ) + return false; + + if ( player.ignoreme === true ) + return false; + + if ( player == self.owner ) + { + self check_owner( hardpointType ); + return false; + } + + if ( player airsupport::canTargetPlayerWithSpecialty() == false ) + return false; + + if ( distance( player.origin, self.origin ) > level.heli_visual_range ) + return false; + + if ( !isdefined( player.team ) ) + return false; + + if ( level.teamBased && player.team == self.team ) + return false; + + if ( player.team == "spectator" ) + return false; + + if ( isdefined( player.spawntime ) && ( gettime() - player.spawntime )/1000 <= level.heli_target_spawnprotection ) + return false; + + heli_centroid = self.origin + ( 0, 0, -160 ); + heli_forward_norm = anglestoforward( self.angles ); + heli_turret_point = heli_centroid + 144*heli_forward_norm; + + visible_amount = player sightConeTrace( heli_turret_point, self); + + if ( visible_amount < level.heli_target_recognition ) + return false; + + return canTarget; +} + +function canTargetActor_turret( actor, hardpointType ) +{ + helicopter = self; + + canTarget = true; + + if( !isalive( actor ) ) + return actor; + + if( !isdefined( actor.team ) ) + return false; + + if( level.teamBased && actor.team == helicopter.team ) + return false; + + if( DistanceSquared( actor.origin, helicopter.origin ) > ( level.heli_visual_range * level.heli_visual_range ) ) + return false; + + heli_centroid = helicopter.origin + ( 0, 0, -160 ); + heli_forward_norm = anglestoforward( helicopter.angles ); + heli_turret_point = heli_centroid + 144 * heli_forward_norm; + + visible_amount = actor sightConeTrace( heli_turret_point, helicopter ); + if( visible_amount < level.heli_target_recognition ) + return false; + + return canTarget; +} + +function getVerticalTan( startOrigin, endOrigin ) +{ + vector = endOrigin - startOrigin; + + opposite = startOrigin[2] - endOrigin[2]; + if ( opposite < 0 ) + opposite *= 1; + + adjacent = distance2d( startOrigin, endOrigin ); + + if ( adjacent < 0 ) + adjacent *= 1; + + if ( adjacent < 0.01 ) + adjacent = 0.01; + + tangent = opposite / adjacent; + + return tangent; +} + + + +// targetability +function canTargetPlayer_missile( player, hardpointType ) +{ + canTarget = true; + + if ( !isalive( player ) || player.sessionstate != "playing" ) + return false; + + if ( player.ignoreme === true ) + return false; + + if ( player == self.owner ) + { + self check_owner( hardpointType ); + return false; + } + + if ( player airsupport::canTargetPlayerWithSpecialty() == false ) + return false; + + if ( distance( player.origin, self.origin ) > level.heli_missile_range ) + return false; + + if ( !isdefined( player.team ) ) + return false; + + if ( level.teamBased && player.team == self.team ) + return false; + + if ( player.team == "spectator" ) + return false; + + if ( isdefined( player.spawntime ) && ( gettime() - player.spawntime )/1000 <= level.heli_target_spawnprotection ) + return false; + + if ( self target_cone_check( player, level.heli_missile_target_cone ) == false ) + return false; + + heli_centroid = self.origin + ( 0, 0, -160 ); + heli_forward_norm = anglestoforward( self.angles ); + heli_turret_point = heli_centroid + 144*heli_forward_norm; + + if (!isdefined(player.lastHit)) + player.lastHit = 0; + + player.lastHit = self HeliTurretSightTrace( heli_turret_point, player, player.lastHit ); + if (player.lastHit != 0) + return false; + + return canTarget; +} + +// targetability +function canTargetDog_turret( dog ) +{ + canTarget = true; + + if ( !isdefined( dog ) ) + return false; + + if ( distance( dog.origin, self.origin ) > level.heli_visual_range ) + return false; + + if ( !isdefined( dog.team ) ) + return false; + + if ( level.teamBased && (dog.team == self.team) ) + return false; + + if ( isdefined(dog.script_owner) && self.owner == dog.script_owner ) + return false; + + heli_centroid = self.origin + ( 0, 0, -160 ); + heli_forward_norm = anglestoforward( self.angles ); + heli_turret_point = heli_centroid + 144*heli_forward_norm; + + if (!isdefined(dog.lastHit)) + dog.lastHit = 0; + + dog.lastHit = self HeliTurretDogTrace( heli_turret_point, dog, dog.lastHit ); + if ( dog.lastHit != 0 ) + return false; + + return canTarget; +} + +// targetability +function canTargetDog_missile( dog ) +{ + canTarget = true; + + if ( !isdefined( dog ) ) + return false; + + if ( distance( dog.origin, self.origin ) > level.heli_missile_range ) + return false; + + if ( !isdefined( dog.team ) ) + return false; + + if ( level.teamBased && (dog.team == self.team) ) + return false; + + if ( isdefined(dog.script_owner) && self.owner == dog.script_owner ) + return false; + + // TODO + //should do a view cone to cut down on processing + + heli_centroid = self.origin + ( 0, 0, -160 ); + heli_forward_norm = anglestoforward( self.angles ); + heli_turret_point = heli_centroid + 144*heli_forward_norm; + + if (!isdefined(dog.lastHit)) + dog.lastHit = 0; + + dog.lastHit = self HeliTurretDogTrace( heli_turret_point, dog, dog.lastHit ); + if (dog.lastHit != 0) + return false; + + return canTarget; +} + + +// targetability +function canTargetTank_turret( tank ) +{ + canTarget = true; + + if ( !isdefined( tank ) ) + return false; + + if ( tank.ignoreme === true ) + return false; + + if ( distance( tank.origin, self.origin ) > level.heli_visual_range ) + return false; + + if ( !isdefined( tank.team ) ) + return false; + + if ( level.teamBased && (tank.team == self.team) ) + return false; + + if ( isdefined(tank.owner) && self.owner == tank.owner ) + return false; + + return canTarget; +} + +// assign targets to primary and secondary +function assignPrimaryTargets( targets ) +{ + for( idx=0; idx= 2, "Not enough targets to assign primary and secondary" ); + + // find primary target, highest threat level + highest = 0; + second_highest = 0; + primaryTarget = undefined; + + // find max and second max, 2n + for( idx=0; idx= highest ) + { + highest = targets[idx].threatlevel; + primaryTarget = targets[idx]; + } + } + + assert( isdefined( primaryTarget ), "Targets exist, but none was assigned as primary" ); + self.primaryTarget = primaryTarget; + self notify( "primary acquired" ); +} + +// assign targets to primary and secondary +function assignSecondaryTargets( targets ) +{ + for( idx = 0; idx < targets.size; idx++ ) + { + if( !isdefined( targets[idx].type ) || targets[idx].type != "dog" ) + { + self killstreaks::update_missile_player_threat ( targets[idx] ); + } + else if( targets[idx].type == "dog" || targets[0].type == "tank_drone") + { + killstreaks::update_missile_dog_threat( targets[idx] ); + } + } + + assert( targets.size >= 2, "Not enough targets to assign primary and secondary" ); + + // find primary target, highest threat level + highest = 0; + second_highest = 0; + primaryTarget = undefined; + secondaryTarget = undefined; + + // find max and second max, 2n + for( idx=0; idx= highest ) + { + highest = targets[idx].missilethreatlevel; + secondaryTarget = targets[idx]; + } + } + + assert( isdefined( secondaryTarget ), "1+ targets exist, but none was assigned as secondary" ); + self.secondaryTarget = secondaryTarget; + self notify( "secondary acquired" ); + + + //TODO Maybe missiles do not target the ones on the primary list? + //assert( self.secondaryTarget != self.primaryTarget, "Primary and secondary targets are the same" ); +} + +// resets helicopter's motion values +function heli_reset() +{ + self clearTargetYaw(); + self clearGoalYaw(); + self setspeed( 60, 25 ); + self setyawspeed( 75, 45, 45 ); + //self setjitterparams( (30, 30, 30), 4, 6 ); + self setmaxpitchroll( 30, 30 ); + self setneargoalnotifydist( 256 ); + self setturningability(0.9); +} + +function heli_wait( waittime ) +{ + self endon ( "death" ); + self endon ( "crashing" ); + self endon ( "evasive" ); + + self thread heli_hover(); + wait( waittime ); + heli_reset(); + + self notify( "stop hover" ); +} + +// hover movements +function heli_hover() +{ + // stop hover when anything at all happens + self endon( "death" ); + self endon( "stop hover" ); + self endon( "evasive" ); + self endon( "leaving" ); + self endon( "crashing" ); + randInt = randomint(360); + self setgoalyaw( self.angles[1]+randInt ); + +} + +function wait_for_killed() +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + self.bda = 0; + + while(1) + { + self waittill( "killed", victim ); + + if ( !isdefined( self.owner ) || !isdefined( victim ) ) + continue; + + if ( self.owner == victim ) // killed himself + continue; + + // no kill confirm on team kill. May want another VO. + if ( level.teamBased && self.owner.team == victim.team ) + continue; + + self thread wait_for_bda_timeout(); + } +} + +function wait_for_bda_timeout() +{ + self endon( "killed" ); + + wait( 2.5 ); + + if ( !isdefined( self ) ) + return; + + self play_bda_dialog(); +} + +function play_bda_dialog( ) +{ + if (self.bda == 1) + { + bdaDialog = "kill1"; + } + else if (self.bda == 2) + { + bdaDialog = "kill2"; + } + else if (self.bda == 3) + { + bdaDialog = "kill3"; + } + else if (self.bda > 3) + { + bdaDialog = "killMultiple"; + } + + self killstreaks::play_pilot_dialog_on_owner( bdaDialog, self.killstreakType, self.killstreak_id ); + + self notify( "bda_dialog", bdaDialog ); + + self.bda = 0; +} + +function heli_hacked_health_update( hacker ) +{ + helicopter = self; + hackedDamageTaken = helicopter.maxhealth - helicopter.hackedHealth; + assert ( hackedDamageTaken > 0 ); + if ( hackedDamageTaken > helicopter.damageTaken ) + { + helicopter.damageTaken = hackedDamageTaken; + } +} + +function heli_damage_monitor( hardpointtype ) +{ + helicopter = self; + self endon( "death" ); + self endon( "crashing" ); + + self.damageTaken = 0; + + last_hit_vo = 0; + hit_vo_spacing = 6000; + + tableHealth = killstreak_bundles::get_max_health( hardpointtype ); + if ( isdefined( tableHealth ) ) + { + self.maxhealth = tableHealth; + } + + helicopter.hackedHealthUpdateCallback = &heli_hacked_health_update; + helicopter.hackedHealth = killstreak_bundles::get_hacked_health( hardpointtype ); + + if ( !isdefined( self.attackerData ) ) + { + self.attackers = []; + self.attackerData = []; + self.attackerDamage = []; + self.flareAttackerDamage = []; + } + + for( ;; ) + { + // this damage is done to self.health which isnt used to determine the helicopter's health, damageTaken is. + self waittill( "damage", damage, attacker, direction, point, type, modelName, tagName, partname, weapon, flags, inflictor, chargeLevel ); + + if( !isdefined( attacker ) || !isplayer( attacker ) ) + continue; + + heli_friendlyfire = weaponobjects::friendlyFireCheck( self.owner, attacker ); + // skip damage if friendlyfire is disabled + if( !heli_friendlyfire ) + continue; + + if ( !level.hardcoreMode ) + { + if( isdefined( self.owner ) && attacker == self.owner ) + continue; + + if ( level.teamBased ) + isValidAttacker = (isdefined( attacker.team ) && attacker.team != self.team); + else + isValidAttacker = true; + + if ( !isValidAttacker ) + continue; + } + + self.attacker = attacker; + + weapon_damage = killstreak_bundles::get_weapon_damage( hardpointType, self.maxhealth, attacker, weapon, type, damage, flags, chargeLevel ); + + if ( !isdefined( weapon_damage ) ) + { + if ( type == "MOD_RIFLE_BULLET" || type == "MOD_PISTOL_BULLET" ) + { + hasFMJ = attacker HasPerk( "specialty_armorpiercing" ); + + if ( hasFMJ ) + { + damage += int( damage * level.cac_armorpiercing_data ); + } + + damage *= level.heli_armor_bulletdamage; + } + else if( type == "MOD_PROJECTILE" || type == "MOD_PROJECTILE_SPLASH" || type == "MOD_EXPLOSIVE" ) + { + shouldUpdateDamage = ( ( weapon.statIndex != level.weaponPistolEnergy.statIndex ) + && ( weapon.statIndex != level.weaponSpecialCrossbow.statIndex ) + && ( weapon.statIndex != level.weaponSmgNailGun.statIndex ) ); + + if ( shouldUpdateDamage ) + { + switch ( weapon.name ) + { + case "tow_turret": + if( isdefined( self.rocketDamageTwoShot ) ) + { + // 2 shot kill + damage = self.rocketDamageTwoShot; + } + else if( isdefined( self.rocketDamageOneShot ) ) + { + // 1 shot kill + damage = self.rocketDamageOneShot; + } + break; + default: + if( isdefined( self.rocketDamageOneShot ) ) + { + // 1 shot kill + damage = self.rocketDamageOneShot; + } + break; + } + } + } + + weapon_damage = damage; + } + + if ( weapon_damage > 0 ) + self challenges::trackAssists( attacker, weapon_damage, false ); + + self.damageTaken += weapon_damage; + + playerControlled = false; + + if( self.damageTaken > self.maxhealth && !isdefined(self.xpGiven) ) + { + self.xpGiven = true; + + switch( hardpointtype ) + { + case "helicopter_gunner": + playerControlled = true; + event = "destroyed_vtol_mothership"; + break; + case "helicopter_comlink": + case "inventory_helicopter_comlink": + event = "destroyed_helicopter_comlink"; + if ( self.leaving !== true ) + { + self killstreaks::play_destroyed_dialog_on_owner( self.killstreakType, self.killstreak_id ); + } + break; + case "supply_drop": + case "supply_drop_combat_robot": + if( isdefined( helicopter.killstreakWeaponName ) ) + { + switch( helicopter.killstreakWeaponName ) + { + case "inventory_ai_tank_drop": + case "ai_tank_drop": + case "inventory_ai_tank_marker": + case "ai_tank_drop_marker": + case "ai_tank_marker": + { + event = "destroyed_helicopter_agr_drop"; + } + break; + case "combat_robot_drop": + case "inventory_combat_robot_drop": + case "combat_robot_marker": + case "inventory_combat_robot_marker": + { + event = "destroyed_helicopter_giunit_drop"; + } + break; + default: + { + event = "destroyed_helicopter_supply_drop"; + } + break; + } + } + else + { + event = "destroyed_helicopter_supply_drop"; + } + break; + } + + if ( isdefined( event ) ) + { + if( isdefined( self.owner ) && self.owner util::IsEnemyPlayer( attacker ) ) + { + challenges::destroyedHelicopter( attacker, weapon, type, false ); + challenges::destroyedAircraft( attacker, weapon, playerControlled ); + scoreevents::processScoreEvent( event, attacker, self.owner, weapon ); + attacker challenges::addFlySwatterStat( weapon, self ); + if ( playerControlled == true ) + { + attacker challenges::destroyedPlayerControlledAircraft(); + } + if ( hardpointtype == "helicopter_player_gunner" ) + { + attacker AddWeaponStat( weapon, "destroyed_controlled_killstreak", 1 ); + } + } + else + { + //Destroyed Friendly Killstreak + } + } + + // do give stats to killstreak weapons + // we need the kill stat so that we know how many kills the weapon has gotten (even on helicopters) for challenges + // we need the destroyed stat for the same reason as the kill stat and these have different challenges associated + //attacker _properks::destroyedKillstreak(); + + weaponStatName = "destroyed"; + switch( weapon.name ) + { + // SAM Turrets keep the kills stat for shooting things down because we used destroyed for when you destroy a SAM Turret + case "auto_tow": + case "tow_turret": + case "tow_turret_drop": + weaponStatName = "kills"; + break; + } + attacker AddWeaponStat( weapon, weaponStatName, 1 ); + + notifyString = undefined; + killstreakReference = undefined; + switch( hardpointtype ) + { + case "helicopter_gunner": + killstreakReference = "killstreak_helicopter_gunner"; + break; + case "helicopter_player_gunner": + killstreakReference = "killstreak_helicopter_player_gunner"; + break; + case "helicopter_player_firstperson": + killstreakReference = "killstreak_helicopter_player_firstperson"; + break; + case "helicopter_comlink": + case "inventory_helicopter_comlink": + case "helicopter": + case "helicopter_x2": + notifyString = &"KILLSTREAK_DESTROYED_HELICOPTER"; + killstreakReference = "killstreak_helicopter_comlink"; + break; + case "supply_drop": + notifyString = &"KILLSTREAK_DESTROYED_SUPPLY_DROP_DEPLOY_SHIP"; + killstreakReference = "killstreak_supply_drop"; + break; + case "helicopter_guard": + killstreakReference = "killstreak_helicopter_guard"; + } + + // increment the destroyed stat for this, we aren't using the weaponStatName variable from above because it could be "kills" and we don't want that + if( isdefined( killstreakReference ) ) + { + level.globalKillstreaksDestroyed++; + attacker AddWeaponStat( GetWeapon( hardpointtype ), "destroyed", 1 ); + } + + if( hardpointtype == "helicopter_player_gunner" ) + { + self.owner SendKillstreakDamageEvent( 600 ); + } + + if ( isdefined( notifyString ) ) + { + LUINotifyEvent( &"player_callout", 2, notifyString, attacker.entnum ); + } + + if ( isdefined( self.attackers ) ) + { + for ( j = 0; j < self.attackers.size; j++ ) + { + player = self.attackers[j]; + + if ( !isdefined( player ) ) + continue; + + if ( player == attacker ) + continue; + + flare_done = self.flareAttackerDamage[player.clientId]; + if ( isdefined ( flare_done ) && flare_done == true ) + { + scoreevents::processScoreEvent( "aircraft_flare_assist", player ); + } + else + { + damage_done = self.attackerDamage[player.clientId]; + player thread processCopterAssist( self, damage_done); + } + } + self.attackers = []; + } + attacker notify( "destroyed_helicopter" ); + + if( Target_IsTarget( self ) ) + { + Target_remove( self ); + } + } + else if ( isdefined( self.owner ) && IsPlayer( self.owner ) ) + { + if ( last_hit_vo + hit_vo_spacing < GetTime() ) + { + if ( type == "MOD_PROJECTILE" || RandomIntRange(0,3) == 0 ) + { + // TODO CDC - change to new pilot dialog + //self.owner PlayLocalSound(level.heli_vo[self.team]["hit"]); + last_hit_vo = GetTime(); + } + } + } + + if( ( hardpointtype == "helicopter_comlink" ) || ( hardpointtype == "inventory_helicopter_comlink" ) ) + { + self thread heli_active_camo_damage_update( damage ); + } + } +} + +function init_active_camo() +{ + heli = self; + heli.active_camo_supported = true; + heli.active_camo_damage = 0; + heli.active_camo_disabled = false; + heli.camo_state = ( 0 ); + heli_set_active_camo_state( ( 1 ) ); + + if( isdefined( heli.flak_drone ) ) + { + heli.flak_drone flak_drone::SetCamoState( ( 1 ) ); + } +} + +function heli_set_active_camo_state( state ) +{ + heli = self; + + if( !isdefined( heli.active_camo_supported ) ) + { + return; + } + + if( state == ( 0 ) ) + { + //Target_Set( heli, heli.target_offset ); + heli clientfield::set( "toggle_lights", 1 ); + if( heli.camo_state == ( 1 ) ) + heli playsound ("veh_hind_cloak_off"); + heli.camo_state = ( 0 ); + heli.camo_state_switch_time = gettime(); + } + else if( state == ( 1 ) ) + { + if( heli.active_camo_disabled ) + { + return; + } + + //if( Target_IsTarget( heli ) ) + //{ + //Target_Remove( heli ); + //} + + heli clientfield::set( "toggle_lights", 0 ); + + if( heli.camo_state == ( 0 ) ) + heli playsound ("veh_hind_cloak_on"); + heli.camo_state = ( 1 ); + heli.camo_state_switch_time = gettime(); + + if ( isdefined( heli.owner ) ) + { + if ( isdefined( heli.play_camo_dialog ) && heli.play_camo_dialog ) + { + heli killstreaks::play_pilot_dialog_on_owner( "activateCounter", "helicopter_comlink", self.killstreak_id ); + // Only play the cloak line once + heli.play_camo_dialog = false; + } + else if ( !isdefined( heli.play_camo_dialog ) ) + { + // Don't play camo dialog when initializing the copter + heli.play_camo_dialog = true; + } + } + } + else if( state == ( 2 ) ) + { + heli clientfield::set( "toggle_lights", 1 ); + } + + if( isdefined( heli.flak_drone ) ) + { + heli.flak_drone flak_drone::SetCamoState( state ); + } + + heli clientfield::set( "active_camo", state ); +} + +function heli_active_camo_damage_update( damage ) +{ + self endon( "death" ); + self endon( "crashing" ); + + heli = self; + + heli.active_camo_damage += damage; + + if( heli.active_camo_damage > ( 100 ) ) + { + heli.active_camo_disabled = true; + heli thread heli_active_camo_damage_disable(); + } + else + { + heli heli_set_active_camo_state( ( 2 ) ); + wait( ( 1 ) ); + heli heli_set_active_camo_state( ( 1 ) ); + } +} + +function heli_active_camo_damage_disable() +{ + self endon( "death" ); + self endon( "crashing" ); + + heli = self; + heli notify( "heli_active_camo_damage_disable" ); + heli endon( "heli_active_camo_damage_disable" ); + + heli heli_set_active_camo_state( ( 0 ) ); + + wait( ( 10 ) ); + + heli.active_camo_damage = 0; + heli.active_camo_disabled = false; + heli heli_set_active_camo_state( ( 1 ) ); +} + +function heli_health( hardpointType, playerNotify ) +{ + self endon( "death" ); + self endon( "crashing" ); + + self.currentstate = "ok"; + self.laststate = "ok"; + self setdamagestage( 3 ); + damageState = 3; + + tableHealth = killstreak_bundles::get_max_health( hardpointType ); + + if ( isdefined( tableHealth ) ) + { + self.maxhealth = tableHealth; + } + + for ( ;; ) + { + self waittill( "damage", damage, attacker, direction, point, type, modelName, tagName, partname, weapon ); + {wait(.05);}; + + if( self.damageTaken > self.maxhealth ) + { + damageState = 0; + self setDamageStage( damageState ); + + self heli_set_active_camo_state( ( 0 ) ); + + self thread heli_crash( hardpointType, self.owner, playerNotify ); + } + else if ( self.damageTaken >= (self.maxhealth * 0.66) && damageState >= 2 ) + { + self killstreaks::play_pilot_dialog_on_owner( "damaged", "helicopter_comlink", self.killstreak_id ); + + //self setdamagestage( 1 ); + if ( isdefined( self.vehicletype ) && self.vehicletype == "heli_player_gunner_mp" ) + { + PlayFXOnTag( level.chopper_fx["damage"]["heavy_smoke"], self, "tag_origin"); + } + else + { + PlayFXOnTag( level.chopper_fx["damage"]["heavy_smoke"], self, "tag_main_rotor"); + } + damageState = 1; + self.currentstate = "heavy smoke"; + self.evasive = true; + self notify("damage state"); + } + else if ( self.damageTaken >= (self.maxhealth * 0.33) && damageState == 3 ) + { + //self setdamagestage( 2 ); + if ( isdefined( self.vehicletype ) && self.vehicletype == "heli_player_gunner_mp" ) + { + PlayFXOnTag( level.chopper_fx["damage"]["light_smoke"], self, "tag_origin"); + } + else + { + PlayFXOnTag( level.chopper_fx["damage"]["light_smoke"], self, "tag_main_rotor"); + } + damageState = 2; + self.currentstate = "light smoke"; + self notify("damage state"); + } + + // debug ================================= + if( self.damageTaken <= level.heli_armor ) + airsupport::debug_print3d_simple( "Armor: " + (level.heli_armor-self.damageTaken), self, ( 0,0,100 ), 20 ); + else + airsupport::debug_print3d_simple( "Health: " + ( self.maxhealth - self.damageTaken ), self, ( 0,0,100 ), 20 ); + + } +} + +// evasive manuvering - helicopter circles the map for awhile then returns to path +function heli_evasive( hardpointType ) +{ + // only one instance allowed + self notify( "evasive" ); + + self.evasive = true; + + // set helicopter path to circle the map level.heli_loopmax number of times + loop_startnode = level.heli_loop_paths[0]; + + gunnerPathFound = true; + if ( hardpointType == "helicopter_gunner" ) + { + gunnerPathFound = false; + for ( i = 0 ; i < level.heli_loop_paths.size ; i++ ) + { + if ( isdefined( level.heli_loop_paths[i].isGunnerPath ) && level.heli_loop_paths[i].isGunnerPath ) + { + loop_startnode = level.heli_loop_paths[i]; + gunnerPathFound = true; + break; + } + } + } + assert( gunnerPathFound, "No chopper gunner loop paths found in map" ); + + startwait = 2; + if ( isdefined( self.doNotStop ) && self.doNotStop ) + startwait = 0; + + self thread heli_fly( loop_startnode, startwait, hardpointType ); +} + +function notify_player( player, playerNotify, delay ) +{ + if ( !isdefined(player) ) + return; + + if ( !isdefined(playerNotify) ) + return; + + player endon( "disconnect" ); + player endon( playerNotify ); + + wait (delay); + + player notify( playerNotify ); +} + +function play_going_down_vo( delay ) +{ + self.owner endon( "disconnect" ); + self endon( "death" ); + + wait (delay); +} + +// attach helicopter on crash path +function heli_crash( hardpointType, player, playerNotify ) +{ + self endon( "death" ); + self notify( "crashing" ); + + self spawning::remove_influencers(); + + self stoploopsound(0); + if( isdefined( self.minigun_snd_ent ) ) + { + self.minigun_snd_ent StopLoopSound(); + } + if( isdefined( self.alarm_snd_ent ) ) + { + self.alarm_snd_ent StopLoopSound(); + } + + //TODO : Play crash alert vo CDC + + // these types are for the chopper and player controlled heli + crashTypes = []; + crashTypes[0] = "crashOnPath"; + crashTypes[1] = "spinOut"; + + crashType = crashTypes[randomInt(2)]; + + // ai chopper should just explode + if ( isdefined( self.crashType ) ) + crashType = self.crashType; + +/# + if ( level.heli_debug_crash ) + { + switch( level.heli_debug_crash ) + { + case 1: + crashType = "explode"; + break; + case 2: + crashType = "crashOnPath"; + break; + case 3: + crashType = "spinOut"; + break; + default: + }; + } +#/ + + switch (crashType) + { + case "explode": + { + thread notify_player( player, playerNotify, 0 ); + self thread heli_explode(); + } + break; + case "crashOnPath": + { + if ( isdefined( player ) ) + self thread play_going_down_vo( 0.5 ); + + thread notify_player( player, playerNotify, 4 ); + self clear_client_flags(); + self thread crashOnNearestCrashPath( hardpointType ); + } + break; + case "spinOut": + { + if ( isdefined( player ) ) + self thread play_going_down_vo( 0.5 ); + thread notify_player( player, playerNotify, 4 ); + self clear_client_flags(); + + heli_reset(); + + heli_speed = 30+randomInt(50); + heli_accel = 10+randomInt(25); + + // helicopter leaves randomly towards one of the leave origins + leavenode = getValidRandomCrashNode( self.origin ); + // movement change due to damage + self setspeed( heli_speed, heli_accel ); + self set_goal_pos( (leavenode.origin), 0 ); + + rateOfSpin = 45 + randomint(90); + + thread heli_secondary_explosions(); + + // helicopter losing control and spins + self thread heli_spin( rateOfSpin ); + //TODO : pilot call in VO + + + self util::waittill_any_timeout( RandomIntRange(4, 6), "near_goal" ); //self waittillmatch( "goal" ); + + if ( isdefined( player ) && isdefined( playerNotify ) ) + player notify( playerNotify ); // make sure + self thread heli_explode(); + } + break; + } + + self thread explodeOnContact( hardpointtype ); + + time = randomIntRange(4, 6); + self thread waitThenExplode( time ); +} + +function damagedRotorFX() +{ + self endon ( "death" ); + self SetRotorSpeed( 0.6 ); +} + +function waitThenExplode( time ) +{ + self endon( "death" ); + + wait( time ); + + self thread heli_explode(); +} + +function crashOnNearestCrashPath( hardpointType ) +{ + crashPathDistance = -1; + crashPath = level.heli_crash_paths[0]; + for ( i = 0; i < level.heli_crash_paths.size; i++ ) + { + currentDistance = distance(self.origin, level.heli_crash_paths[i].origin); + if ( crashPathDistance == -1 || crashPathDistance > currentDistance ) + { + crashPathDistance = currentDistance; + crashPath = level.heli_crash_paths[i]; + } + } + + heli_speed = 30+randomInt(50); + heli_accel = 10+randomInt(25); + + // movement change due to damage + self setspeed( heli_speed, heli_accel ); + + thread heli_secondary_explosions(); + + // fly to crash path + self thread heli_fly( crashPath, 0, hardpointType ); + + rateOfSpin = 45 + randomint(90); + + // helicopter losing control and spins + self thread heli_spin( rateOfSpin ); + + // wait until helicopter is on the crash path + self waittill ( "path start" ); + + + self waittill( "destination reached" ); + self thread heli_explode(); +} + +//This is a temporary solution for playing fx while we are switching to new models with new tag naming conventions. +function CheckHelicopterTag( tagName ) +{ + if( isdefined( self.model ) ) + { + if( self.model == "veh_t7_drone_hunter" ) + { + switch( tagName ) + { + case "tag_engine_left": + return "tag_fx_exhaust2"; + case "tag_engine_right": + return "tag_fx_exhaust1"; + case "tail_rotor_jnt": + return "tag_fx_tail"; + default: + break; + } + } + } + + return tagName; +} + +function heli_secondary_explosions() +{ + self endon( "death" ); + + playFxOnTag( level.chopper_fx["explode"]["large"], self, self CheckHelicopterTag( "tag_engine_left" ) ); +// self playSound ( level.heli_sound["hitsecondary"] ); + self playSound ( level.heli_sound["hit"] ); + + // form smoke trails on tail after explosion + if ( isdefined( self.vehicletype ) && self.vehicletype == "heli_player_gunner_mp" ) + { + self thread trail_fx( level.chopper_fx["smoke"]["trail"], self CheckHelicopterTag( "tag_engine_right" ), "stop tail smoke" ); + } + else + { + self thread trail_fx( level.chopper_fx["smoke"]["trail"], self CheckHelicopterTag( "tail_rotor_jnt" ), "stop tail smoke" ); + } + + self setdamagestage( 0 ); + // form fire smoke trails on body after explosion + self thread trail_fx( level.chopper_fx["fire"]["trail"]["large"], self CheckHelicopterTag( "tag_engine_left" ), "stop body fire" ); + + wait ( 3.0 ); + + if ( !isdefined( self ) ) + return; + + playFxOnTag( level.chopper_fx["explode"]["large"], self, self CheckHelicopterTag( "tag_engine_left" ) ); + self playSound ( level.heli_sound["hitsecondary"] ); +} + +// self spin at one rev per 2 sec +function heli_spin( speed ) +{ + self endon( "death" ); + + // tail explosion that caused the spinning +// playfxontag( level.chopper_fx["explode"]["medium"], self, "tail_rotor_jnt" ); + // play hit sound immediately so players know they got it + + // play heli crashing spinning sound + self thread spinSoundShortly(); + + // spins until death + self setyawspeed( speed, speed / 3 , speed / 3 ); + while ( isdefined( self ) ) + { + self settargetyaw( self.angles[1]+(speed*0.9) ); + wait ( 1 ); + } +} + +function spinSoundShortly() +{ + self endon( "death" ); + + wait .25; + + self stopLoopSound(); + wait .05; + self playLoopSound( level.heli_sound["spinloop"] ); + wait .05; + self playSound( level.heli_sound["spinstart"] ); +} + +// TO DO: Robert will replace the for-loop to use geotrails for smoke trail fx +// this plays single smoke trail puff on origin per 0.05 +// trail_fx is the fx string, trail_tag is the tag string +function trail_fx( trail_fx, trail_tag, stop_notify ) +{ + // only one instance allowed +// self notify( stop_notify ); +// self endon( stop_notify ); +// self endon( "death" ); + +// for ( ;; ) + { + playfxontag( trail_fx, self, trail_tag ); +// WAIT_SERVER_FRAME; + } +} + +function destroyHelicopter() +{ + team = self.originalteam; + + if ( Target_IsTarget(self) ) + Target_remove( self ); + + self spawning::remove_influencers(); + + if( isdefined( self.interior_model ) ) + { + self.interior_model Delete(); + self.interior_model = undefined; + } + if( isdefined( self.minigun_snd_ent ) ) + { + self.minigun_snd_ent StopLoopSound(); + self.minigun_snd_ent Delete(); + self.minigun_snd_ent = undefined; + } + if( isdefined( self.alarm_snd_ent ) ) + { + self.alarm_snd_ent Delete(); + self.alarm_snd_ent = undefined; + } + if ( isdefined( self.flare_ent ) ) + { + self.flare_ent Delete(); + self.flare_ent = undefined; + } + + killstreakrules::killstreakStop( self.hardpointType, team, self.killstreak_id ); + + self delete(); +} + +// crash explosion +function heli_explode() +{ + self endon( "death" ); + + forward = ( self.origin + ( 0, 0, 100 ) ) - self.origin; + if( isdefined(self.helitype) && self.helitype == "littlebird" ) + { + playfx( level.chopper_fx["explode"]["guard"], self.origin, forward ); + } + else if ( isdefined( self.vehicletype ) && self.vehicletype == "heli_player_gunner_mp" ) + { + playfx( level.chopper_fx["explode"]["gunner"], self.origin, forward ); + } + else + { + playfx( level.chopper_fx["explode"]["death"], self.origin, forward ); + } + + // play heli explosion sound + self PlaySound ( level.heli_sound["crash"] ); + + wait( 0.1 ); + + assert( isdefined( self.destroyFunc ) ); + self [[self.destroyFunc]](); +} + +function clear_client_flags() +{ + self clientfield::set( "heli_warn_fired", 0 ); + self clientfield::set( "heli_warn_targeted", 0 ); + self clientfield::set( "heli_warn_locked", 0 ); +} + +// helicopter leaving parameter, can not be damaged while leaving +function heli_leave() +{ + self notify( "destintation reached" ); + self notify( "leaving" ); + + hardpointType = self.hardpointType; + + self.leaving = true; + + if( !( isdefined( self.detroyScoreEventGiven ) && self.detroyScoreEventGiven ) ) + { + self killstreaks::play_pilot_dialog_on_owner( "timeout", hardpointType ); + self killstreaks::play_taacom_dialog_response_on_owner( "timeoutConfirmed", hardpointType ); + } + + //self thread playpilotdialog ("attackheli_leave", 2.5 ); + + //self clear_client_flags(); + + // helicopter leaves randomly towards one of the leave origins + leavenode = getValidRandomLeaveNode( self.origin ); + + heli_reset(); + self ClearLookAtEnt(); + exitAngles = VectorToAngles(leavenode.origin - self.origin); + self SetGoalYaw( exitAngles[1] ); + wait ( 1.5 ); + + if ( !isdefined( self ) ) + { + return; + } + + self setspeed( 180, 65 ); + + /# self util::debug_slow_heli_speed(); #/ + + self set_goal_pos( self.origin + ( leavenode.origin - self.origin ) / 2 + ( 0, 0, 1000 ), false ); + self waittill( "near_goal" ); + if( isdefined( self ) ) + { + self set_goal_pos( leavenode.origin, true ); + self waittillmatch( "goal" ); + if( isdefined( self ) ) + { + self stoploopsound(1); + self util::death_notify_wrapper(); + if( isdefined( self.alarm_snd_ent ) ) + { + self.alarm_snd_ent StopLoopSound(); + self.alarm_snd_ent Delete(); + self.alarm_snd_ent = undefined; + } + + assert( isdefined( self.destroyFunc ) ); + self [[self.destroyFunc]](); + } + } +} + +// flys helicopter from given start node to a destination on its path +function heli_fly( currentnode, startwait, hardpointType ) +{ + self endon( "death" ); + self endon( "leaving" ); + + // only one thread instance allowed + self notify( "flying" ); + self endon( "flying" ); + + // if owner switches teams, helicopter should leave + self endon( "abandoned" ); + + self.reached_dest = false; + heli_reset(); + + pos = self.origin; + wait( startwait ); + + while ( isdefined( currentnode.target ) ) + { + nextnode = getent( currentnode.target, "targetname" ); + assert( isdefined( nextnode ), "Next node in path is undefined, but has targetname" ); + + // offsetted + pos = nextnode.origin+(0,0,30); + + // motion change via node + if( isdefined( currentnode.script_airspeed ) && isdefined( currentnode.script_accel ) ) + { + heli_speed = currentnode.script_airspeed; + heli_accel = currentnode.script_accel; + } + else + { + heli_speed = 30+randomInt(20); + heli_accel = 10+randomInt(5); + } + + if ( isdefined( self.pathSpeedScale ) ) + { + heli_speed *= self.pathSpeedScale; + heli_accel *= self.pathSpeedScale; + } + + // fly nonstop until final destination + if ( !isdefined( nextnode.target ) ) + stop = 1; + else + stop = 0; + + // debug ============================================================== + airsupport::debug_line( currentnode.origin, nextnode.origin, ( 1, 0.5, 0.5 ), 200 ); + + // if in damaged state, do not stop at any node other than destination + if( self.currentstate == "heavy smoke" || self.currentstate == "light smoke" ) + { + // movement change due to damage + self setspeed( heli_speed, heli_accel ); + self set_goal_pos( (pos), stop ); + + self waittill( "near_goal" ); //self waittillmatch( "goal" ); + self notify( "path start" ); + } + else + { + // if the node has helicopter stop time value, we stop + if( isdefined( nextnode.script_delay ) && !isdefined( self.doNotStop ) ) + stop = 1; + + self setspeed( heli_speed, heli_accel ); + self set_goal_pos( (pos), stop ); + + if ( !isdefined( nextnode.script_delay ) || isdefined( self.doNotStop ) ) + { + self waittill( "near_goal" ); //self waittillmatch( "goal" ); + self notify( "path start" ); + } + else + { + // post beta addition --- ( + self setgoalyaw( nextnode.angles[1] ); + // post beta addition --- ) + + self waittillmatch( "goal" ); + heli_wait( nextnode.script_delay ); + } + } + + // increment loop count when helicopter is circling the map + for( index = 0; index < level.heli_loop_paths.size; index++ ) + { + if ( level.heli_loop_paths[index].origin == nextnode.origin ) + self.loopcount++; + } + if( self.loopcount >= level.heli_loopmax ) + { + self thread heli_leave(); + return; + } + currentnode = nextnode; + } + + self setgoalyaw( currentnode.angles[1] ); + self.reached_dest = true; // sets flag true for helicopter circling the map + self notify ( "destination reached" ); + // wait at destination + if ( isdefined( self.waittime ) && self.waittime > 0 ) + heli_wait( self.waittime ); + + // if still alive, switch to evasive manuvering + if( isdefined( self ) ) + self thread heli_evasive( hardpointType ); +} + +function heli_random_point_in_radius( protectDest, nodeHeight ) +{ + min_distance = Int(level.heli_protect_radius * .2); + direction = randomintrange(0,360); + distance = randomintrange(min_distance, level.heli_protect_radius); + + x = cos(direction); + y = sin(direction); + x = x * distance; + y = y * distance; + + return (protectDest[0] + x, protectDest[1] + y, nodeHeight); +} + +function heli_get_protect_spot(protectDest, nodeHeight) +{ + protect_spot = heli_random_point_in_radius( protectDest, nodeHeight ); + + tries = 10; + noFlyZone = airsupport::crossesNoFlyZone( protectDest, protect_spot ); + while( tries != 0 && isdefined( noFlyZone ) ) + { + protect_spot = heli_random_point_in_radius( protectDest, nodeHeight ); + tries--; + noFlyZone = airsupport::crossesNoFlyZone( protectDest, protect_spot ); + } + + noFlyZoneHeight = airsupport::getNoFlyZoneHeightCrossed( protectDest, protect_spot, nodeHeight ); + return ( protect_spot[0], protect_spot[1], noFlyZoneHeight ); +} + +function wait_or_waittill( time, msg1, msg2, msg3 ) +{ + self endon( msg1 ); + self endon( msg2 ); + self endon( msg3 ); + wait( time ); + return true; +} + +function set_heli_speed_normal() +{ + self setmaxpitchroll( 30, 30 ); + heli_speed = 30+randomInt(20); + heli_accel = 10+randomInt(5); + self setspeed( heli_speed, heli_accel ); + self setyawspeed( 75, 45, 45 ); +} + +function set_heli_speed_evasive() +{ + self setmaxpitchroll( 30, 90 ); + heli_speed = 50+randomInt(20); + heli_accel = 30+randomInt(5); + self setspeed( heli_speed, heli_accel ); + self setyawspeed( 100, 75, 75 ); +} + +function set_heli_speed_hover() +{ + self setmaxpitchroll( 0, 90 ); + self setspeed( 20, 10 ); + self setyawspeed( 55, 25, 25 ); +} + + +function is_targeted() +{ + if ( isdefined(self.locking_on) && self.locking_on ) + return true; + + if ( isdefined(self.locked_on) && self.locked_on ) + return true; + + if ( isdefined(self.locking_on_hacking) && self.locking_on_hacking ) + return true; + + return false; +} + +function heli_mobilespawn( protectDest ) +{ + self endon( "death" ); + + self notify( "flying" ); + self endon( "flying" ); + + self endon( "abandoned" ); + + IPrintLnBold( "PROTECT ORIGIN: ("+protectDest[0]+","+protectDest[1]+","+protectDest[2]+")\n" ); + + heli_reset(); + + self SetHoverParams( 50, 100, 50 ); + + wait( 2 ); + + set_heli_speed_normal(); + + self set_goal_pos( protectDest, 1 ); + + self waittill( "near_goal" ); + + set_heli_speed_hover(); +} + +// flys helicopter from given start node to a destination on its path +function heli_protect( startNode, protectDest, hardpointType, heli_team ) +{ + self endon( "death" ); + + // only one thread instance allowed + self notify( "flying" ); + self endon( "flying" ); + + // if owner switches teams, helicopter should leave + self endon( "abandoned" ); + + self.reached_dest = false; + heli_reset(); + + self SetHoverParams( 50, 100, 50); + + wait( 2 ); + + currentDest = protectDest; + + nodeHeight = protectDest[2]; + + nextnode = startNode; + + heightOffset = 0; + if ( heli_team == "axis" ) + { + heightOffset = 400; + } + + protectDest = ( protectDest[0], protectDest[1], nodeHeight ); + noFlyZoneHeight = airsupport::getNoFlyZoneHeight( protectDest ); + protectDest = ( protectDest[0], protectDest[1], noFlyZoneHeight + heightOffset ); + currentDest = protectDest; + startTime = gettime(); + self.endTime = startTime + ( level.heli_protect_time * 1000 ); + self.killstreakEndTime = int( self.endTime ); + + self SetSpeed( 150, 80 ); + + /# self util::debug_slow_heli_speed(); #/ + + self set_goal_pos( self.origin + ( currentDest - self.origin ) / 3 + ( 0, 0, 1000 ), false ); + self waittill( "near_goal" ); + + heli_speed = 30+randomInt(20); + heli_accel = 10+randomInt(5); + + self thread updateTargetYaw(); + + mapEnter = true; + + while ( getTime() < self.endTime ) + { + stop = 1; + + if( !mapEnter ) + { + self updateSpeed(); + } + else + { + mapEnter = false; + } + + // movement change due to damage + self set_goal_pos( (currentDest), stop ); + + self thread updateSpeedOnLock(); + self util::waittill_any( "near_goal", "locking on", "locking on hacking" ); + hostmigration::waitTillHostMigrationDone(); + self notify( "path start" ); + + if ( !self is_targeted() ) + { + + waittillframeend; + + time = level.heli_protect_pos_time; + + if ( self.evasive == true ) + { + time = 2.0; + } + + set_heli_speed_hover(); + + wait_or_waittill ( time, "locking on", "locking on hacking", "damage state" ); + } + else + { + wait 2; + } + + prevDest = currentDest; + currentDest = heli_get_protect_spot(protectDest, nodeHeight); + noFlyZoneHeight = airsupport::getNoFlyZoneHeight( currentDest ); + currentDest = ( currentDest[0], currentDest[1], noFlyZoneHeight + heightOffset ); + noFlyZones = airsupport::crossesNoFlyZones( prevDest, currentDest ); + if ( isdefined( noFlyZones ) && ( noFlyZones.size > 0 ) ) + { + currentDest = prevDest; + } + } + + self heli_set_active_camo_state( ( 1 ) ); + self thread heli_leave(); +} + +function updateSpeedOnLock() +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + self util::waittill_any( "near_goal", "locking on", "locking on hacking" ); + + self updateSpeed(); +} + +function updateSpeed() +{ + if ( self is_targeted() || ( isdefined(self.evasive) && self.evasive ) ) + { + set_heli_speed_evasive(); + } + else + { + set_heli_speed_normal(); + } +} + +function updateTargetYaw() +{ + self notify( "endTargetYawUpdate" ); + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + self endon( "endTargetYawUpdate" ); + + for(;;) + { + if ( isdefined( self.primaryTarget ) ) + { + yaw = math::get_2d_yaw( self.origin, self.primaryTarget.origin ); + self setTargetYaw( yaw ); + } + + wait( 1 ); + } +} + +function fire_missile( sMissileType, iShots, eTarget ) +{ + if ( !isdefined( iShots ) ) + iShots = 1; + assert( self.health > 0 ); + + weapon = undefined; + weaponShootTime = undefined; + tags = []; + switch( sMissileType ) + { + case "ffar": + weapon = GetWeapon( "hind_FFAR" ); + tags[ 0 ] = "tag_store_r_2"; + break; + default: + assertMsg( "Invalid missile type specified. Must be ffar" ); + break; + } + assert( isdefined( weapon ) ); + assert( tags.size > 0 ); + + weaponShootTime = weapon.fireTime; + assert( isdefined( weaponShootTime ) ); + + self setVehWeapon( weapon ); + nextMissileTag = -1; + for( i = 0 ; i < iShots ; i++ ) // I don't believe iShots > 1 is properly supported; we don't set the weapon each time + { + nextMissileTag++; + if ( nextMissileTag >= tags.size ) + nextMissileTag = 0; + + eMissile = self fireWeapon( 0, eTarget ); + eMissile.killcament = self; + self.lastRocketFireTime = gettime(); + + if ( i < iShots - 1 ) + wait weaponShootTime; + } + // avoid calling setVehWeapon again this frame or the client doesn't hear about the original weapon change +} + +function check_owner( hardpointType ) +{ + if ( !isdefined( self.owner ) || !isdefined( self.owner.team ) || self.owner.team != self.team ) + { + self notify ( "abandoned" ); + self thread heli_leave(); + } +} + +function attack_targets( missilesEnabled, hardpointType ) +{ + //self thread turret_kill_players(); + self thread attack_primary( hardpointType ); + if ( missilesEnabled ) + self thread attack_secondary( hardpointType ); +} + +// missile only +function attack_secondary( hardpointType ) +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + for( ;; ) + { + if ( isdefined( self.secondaryTarget ) ) + { + self.secondaryTarget.antithreat = undefined; + self.missileTarget = self.secondaryTarget; + + antithreat = 0; + + while( isdefined( self.missileTarget ) && isalive( self.missileTarget ) ) + { + // if selected target is not in missile hit range, skip + if( self target_cone_check( self.missileTarget, level.heli_missile_target_cone ) ) + self thread missile_support( self.missileTarget, level.heli_missile_rof, true, undefined ); + else + break; + + // lower targets threat after shooting + antithreat += 100; + self.missileTarget.antithreat = antithreat; + + wait level.heli_missile_rof; + + // target might disconnect or change during last assault cycle + if ( !isdefined( self.secondaryTarget ) || ( isdefined( self.secondaryTarget ) && self.missileTarget != self.secondaryTarget ) ) + break; + } + // reset the antithreat factor + if ( isdefined( self.missileTarget ) ) + self.missileTarget.antithreat = undefined; + } + self waittill( "secondary acquired" ); + + // check if owner has left, if so, leave + self check_owner( hardpointType ); + } +} + +function turret_target_check( turretTarget, attackAngle ) +{ + + targetYaw = math::get_2d_yaw( self.origin, turretTarget.origin ); + chopperYaw = self.angles[1]; + + if ( targetYaw < 0 ) + targetYaw = targetYaw * -1; + + targetYaw = int( targetYaw ) % 360; + + if ( chopperYaw < 0 ) + chopperYaw = chopperYaw * -1; + + chopperYaw = int( chopperYaw ) % 360; + + if ( chopperYaw > targetYaw ) + difference = chopperYaw - targetYaw; + else + difference = targetYaw - chopperYaw; + + return ( difference <= attackAngle ); +} + +// check if missile is in hittable sight zone +function target_cone_check( target, coneCosine ) +{ + heli2target_normal = vectornormalize( target.origin - self.origin ); + heli2forward = anglestoforward( self.angles ); + heli2forward_normal = vectornormalize( heli2forward ); + + heli_dot_target = vectordot( heli2target_normal, heli2forward_normal ); + + if ( heli_dot_target >= coneCosine ) + { + airsupport::debug_print3d_simple( "Cone sight: " + heli_dot_target, self, ( 0,0,-40 ), 40 ); + return true; + } + return false; +} + + + +// if wait for turret turning is too slow, enable missile assault support +function missile_support( target_player, rof, instantfire, endon_notify ) +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + if ( isdefined ( endon_notify ) ) + self endon( endon_notify ); + + self.turret_giveup = false; + + if ( !instantfire ) + { + wait( rof ); + self.turret_giveup = true; + self notify( "give up" ); + } + + if ( isdefined( target_player ) ) + { + if ( level.teambased ) + { + // if target near friendly, do not shoot missile, target already has lower threat level at this stage + for (i = 0; i < level.players.size; i++) + { + player = level.players[i]; + if ( isdefined( player.team ) && player.team == self.team && distance( player.origin, target_player.origin ) <= level.heli_missile_friendlycare ) + { + airsupport::debug_print3d_simple( "Missile omitted due to nearby friendly", self, ( 0,0,-80 ), 40 ); + self notify ( "missile ready" ); + return; + } + } + } + else + { + player = self.owner; + if ( isdefined( player ) && isdefined( player.team ) && player.team == self.team && distance( player.origin, target_player.origin ) <= level.heli_missile_friendlycare ) + { + airsupport::debug_print3d_simple( "Missile omitted due to nearby friendly", self, ( 0,0,-80 ), 40 ); + self notify ( "missile ready" ); + return; + } + } + } + + if ( self.missile_ammo > 0 && isdefined( target_player ) ) + { + self fire_missile( "ffar", 1, target_player ); + self.missile_ammo--; + self notify( "missile fired" ); + } + else + { + return; + } + + if ( instantfire ) + { + wait ( rof ); + self notify ( "missile ready" ); + } +} + +// mini-gun with missile support +function attack_primary( hardpointType ) +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + level endon( "game_ended" ); + + for( ;; ) + { + if ( isdefined( self.primaryTarget ) ) + { + self.primaryTarget.antithreat = undefined; + self.turretTarget = self.primaryTarget; + antithreat = 0; + last_pos = undefined; + + while( isdefined( self.turretTarget ) && isalive( self.turretTarget ) ) + { + + //Look at target + if ( (hardpointType == "helicopter_comlink") || (hardpointType == "inventory_helicopter_comlink") ) + self SetLookAtEnt( self.turretTarget ); + + helicopterTurretMaxAngle = heli_get_dvar_int( "scr_helicopterTurretMaxAngle", level.helicopterTurretMaxAngle ); + while ( isdefined( self.turretTarget ) && isalive( self.turretTarget ) && self turret_target_check( self.turretTarget, helicopterTurretMaxAngle ) == false ) + wait( 0.1 ); + + if ( !isdefined( self.turretTarget ) || !isalive( self.turretTarget ) ) + break; + + // shoots one clip of mini-gun non stop + self setTurretTargetEnt( self.turretTarget, ( 0, 0, 50 ) ); + + self waittill( "turret_on_target" ); + hostmigration::waitTillHostMigrationDone(); + + /* + self util::waittill_any( "turret_on_target", "give up" ); + if( isdefined( self.turret_giveup ) && self.turret_giveup ) + break; + */ + + self notify( "turret_on_target" ); + //play some targeting Dialog CDC + if (!self.pilotIsTalking) + { + //self thread PlayPilotDialog ("attackheli_target"); + } + + self thread turret_target_flag( self.turretTarget ); + + wait 1; + + self heli_set_active_camo_state( ( 0 ) ); + + // wait for turret to spinup and fire + wait( level.heli_turret_spinup_delay ); + + // fire gun ================================= + weaponShootTime = self.defaultWeapon.fireTime; + self setVehWeapon( self.defaultWeapon ); + + // shoot full clip at target, if target lost, shoot at the last position recorded, if target changed, sweep onto next target + for( i = 0 ; i < level.heli_turretClipSize ; i++ ) + { + // if turret on primary target, keep last position of the target in case target lost + if ( isdefined( self.turretTarget ) && isdefined( self.primaryTarget ) ) + { + if ( self.primaryTarget != self.turretTarget ) + self setTurretTargetEnt( self.primaryTarget, ( 0, 0, 40 ) ); + } + else + { + if ( isdefined( self.targetlost ) && self.targetlost && isdefined( self.turret_last_pos ) ) + { + //println( "Target lost ---- shooting last pos: " + self.turret_last_pos ); // debug + self setturrettargetvec( self.turret_last_pos ); + } + else + { + self clearturrettarget(); + } + } + if ( gettime() != self.lastRocketFireTime ) + { + // fire one bullet + self setVehWeapon( self.defaultWeapon ); + miniGun = self fireWeapon(); + //self.minigun_snd_ent PlayLoopSound( "wpn_attack_chopper_minigun_fire_loop_npc" );//now in gdt + } + + // wait for RoF + if ( i < level.heli_turretClipSize - 1 ) + wait weaponShootTime; + } + //self.minigun_snd_ent StopLoopSound(); + //self.minigun_snd_ent PlaySound("wpn_attack_chopper_minigun_fire_loop_ring_npc");//now in gdt + self notify( "turret reloading" ); + // end fire gun ============================== + + // wait for turret reload + wait( level.heli_turretReloadTime ); + + wait( 3 ); // cooldown before recloaking + + self heli_set_active_camo_state( ( 1 ) ); + + // lower the target's threat since already assaulted on + if ( isdefined( self.turretTarget ) && isalive( self.turretTarget ) ) + { + antithreat += 100; + self.turretTarget.antithreat = antithreat; + } + + // primary target might disconnect or change during last assault cycle, if so, find new target + if ( !isdefined( self.primaryTarget ) || ( isdefined( self.turretTarget ) && isdefined( self.primaryTarget ) && self.primaryTarget != self.turretTarget ) ) + break; + } + // reset the antithreat factor + if ( isdefined( self.turretTarget ) ) + self.turretTarget.antithreat = undefined; + } + self waittill( "primary acquired" ); + + // check if owner has left, if so, leave + self check_owner( hardpointType ); + } +} + +// target lost flaging +function turret_target_flag( turrettarget ) +{ + // forcing single thread instance + self notify( "flag check is running" ); + self endon( "flag check is running" ); + + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + self endon( "turret reloading" ); + + // ends on target player death or undefined + if ( isdefined( turrettarget ) ) + { + turrettarget endon( "death" ); + turrettarget endon( "disconnect" ); + } + + self.targetlost = false; + self.turret_last_pos = undefined; + + while( isdefined( turrettarget ) ) + { + heli_centroid = self.origin + ( 0, 0, -160 ); + heli_forward_norm = anglestoforward( self.angles ); + heli_turret_point = heli_centroid + 144*heli_forward_norm; + + sight_rec = turrettarget sightconetrace( heli_turret_point, self ); + if ( sight_rec < level.heli_target_recognition ) + break; + + {wait(.05);}; + } + + if( isdefined( turrettarget ) && isdefined( turrettarget.origin ) ) + { + assert( isdefined( turrettarget.origin ), "turrettarget.origin is undefined after isdefined check" ); + self.turret_last_pos = turrettarget.origin + ( 0, 0, 40 ); + assert( isdefined( self.turret_last_pos ), "self.turret_last_pos is undefined after setting it #1" ); + self setturrettargetvec( self.turret_last_pos ); + assert( isdefined( self.turret_last_pos ), "self.turret_last_pos is undefined after setting it #2" ); + airsupport::debug_print3d_simple( "Turret target lost at: " + self.turret_last_pos, self, ( 0,0,-70 ), 60 ); + self.targetlost = true; + } + else + { + self.targetlost = undefined; + self.turret_last_pos = undefined; + } +} + +// debug on screen elements =========================================================== +function debug_print_target() +{ + if ( isdefined( level.heli_debug ) && level.heli_debug == 1.0 ) + { + // targeting debug print + if( isdefined( self.primaryTarget ) && isdefined( self.primaryTarget.threatlevel ) ) + { + if ( isdefined(self.primaryTarget.type) && self.primaryTarget.type == "dog" ) + name = "dog"; + else + name = self.primaryTarget.name; + primary_msg = "Primary: " + name + " : " + self.primaryTarget.threatlevel; + } + else + primary_msg = "Primary: "; + + if( isdefined( self.secondaryTarget ) && isdefined( self.secondaryTarget.threatlevel ) ) + { + if ( isdefined(self.secondaryTarget.type) && self.secondaryTarget.type == "dog" ) + name = "dog"; + else + name = self.secondaryTarget.name; + secondary_msg = "Secondary: " + name + " : " + self.secondaryTarget.threatlevel; + } + else + secondary_msg = "Secondary: "; + + frames = int( self.targeting_delay*20 )+1; + + thread airsupport::draw_text( primary_msg, (1, 0.6, 0.6), self, ( 0, 0, 40), frames ); + thread airsupport::draw_text( secondary_msg, (1, 0.6, 0.6), self, ( 0, 0, 0), frames ); + } +} + +function waittill_confirm_location() +{ + self endon( "emp_jammed" ); + self endon( "emp_grenaded" ); + + self waittill( "confirm_location", location ); + + return location; +} + +function selectHelicopterLocation(hardpointtype) +{ + self beginLocationComlinkSelection( "compass_objpoint_helicopter", 1500 ); + self.selectingLocation = true; + + self thread airsupport::endSelectionThink(); + + location = self waittill_confirm_location(); + + if ( !isdefined( location ) ) + { + // selection was cancelled + return false; + } + + if ( self killstreakrules::isKillstreakAllowed( hardpointType, self.team ) == false ) + { + return false; + } + + level.helilocation = location; + + return airsupport::finishHardpointLocationUsage( location, undefined ); +} + +function processCopterAssist( destroyedCopter, damagedone ) +{ + self endon( "disconnect" ); + destroyedCopter endon( "disconnect" ); + + wait .05; + + if ( !isdefined( level.teams[self.team] ) ) + return; + + if ( self.team == destroyedCopter.team ) + return; + + assist_level = "aircraft_destruction_assist"; + + assist_level_value = int( ceil( ( damagedone.damage / destroyedCopter.maxhealth ) * 4 ) ); + + if ( assist_level_value > 0 ) + { + if ( assist_level_value > 3 ) + { + assist_level_value = 3; + } + assist_level = assist_level + "_" + ( assist_level_value * 25 ); + } + + scoreevents::processScoreEvent( assist_level, self ); +} + +//Allow the SAM turret to attack choppers +function SAMTurretWatcher() +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + level endon( "game_ended" ); + + self util::waittill_any( "turret_on_target", "path start", "near_goal" ); + + Target_SetTurretAquire( self, true ); +} + +function PlayPilotDialog( dialog, time, voice, shouldWait ) +{ + self endon( "death" ); + level endon( "remote_end" ); + + if (isdefined(time)) + { + wait time; + } + if (!isdefined(self.pilotVoiceNumber)) + { + self.pilotVoiceNumber = 0; + } + if (isdefined(voice)) + { + voicenumber = voice; + } + else + { + voicenumber = self.pilotVoiceNumber; + } + soundAlias = level.teamPrefix[self.team] + voicenumber + "_" + dialog; + + if ( isdefined ( self.owner ) ) + { + self.owner playPilotTalking( shouldWait, soundAlias ); + } +} + +function playPilotTalking( shouldWait, soundAlias ) +{ + self endon( "disconnect" ); + self endon( "joined_team" ); + self endon( "joined_spectators" ); + + tryCounter = 0; + while( isdefined(self.pilotTalking) && self.pilotTalking && tryCounter < 10 ) + { + if ( isdefined( shouldWait ) && !shouldWait ) + return; + wait 1; + tryCounter++; + } + self.pilotTalking = true; + self playLocalSound(soundAlias); + wait 3; + self.pilotTalking = false; +} + +function watchForEarlyLeave( chopper ) +{ + chopper notify( "watchForEarlyLeave_helicopter" ); + chopper endon( "watchForEarlyLeave_helicopter" ); + chopper endon( "death" ); + self endon( "heli_timeup" ); + + self util::waittill_any( "joined_team", "disconnect" ); + + if ( isdefined( chopper ) ) + chopper thread heli_leave(); + + if ( isdefined( self ) ) + self notify( "heli_timeup" ); +} + +function watchForEMP() +{ + heli = self; + + heli endon( "death" ); + heli endon( "heli_timeup" ); + + heli.owner waittill( "emp_jammed" ); + heli thread heli_explode(); +} diff --git a/mp/killstreaks/_helicopter_gunner.csc b/mp/killstreaks/_helicopter_gunner.csc new file mode 100644 index 0000000..ac4df09 --- /dev/null +++ b/mp/killstreaks/_helicopter_gunner.csc @@ -0,0 +1,70 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\visionset_mgr_shared; + + + + + + +#namespace helicopter_gunner; + +function autoexec __init__sytem__() { system::register("helicopter_gunner",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "vehicle", "vtol_turret_destroyed_0", 1, 1, "int", &turret_destroyed_0, !true, !true ); + clientfield::register( "vehicle", "vtol_turret_destroyed_1", 1, 1, "int", &turret_destroyed_1, !true, !true ); + clientfield::register( "toplayer", "vtol_update_client", 1, 1, "counter", &update_client, !true, !true ); + clientfield::register( "toplayer", "fog_bank_2", 1, 1, "int", &fog_bank_2_callback, !true, !true); + + visionset_mgr::register_visionset_info( "mothership_visionset", 1, 1, undefined, "mp_vehicles_mothership" ); +} + +function turret_destroyed_0( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + // /# IPrintLnBold( "Turret Destroyed A: " + newVal ); #/ +} + +function turret_destroyed_1( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + // /# IPrintLnBold( "Turret Destroyed B: " + newVal ); #/ +} + +function update_turret_destroyed( localClientNum, ui_model_name, new_value ) +{ + part_destroyed_ui_model = GetUIModel( GetUIModelForController( localClientNum ), ui_model_name ); + + if ( isdefined( part_destroyed_ui_model ) ) + SetUIModelValue( part_destroyed_ui_model, new_value ); + + // /# IPrintLnBold( ui_model_name + " set to: " + new_value ); #/ +} + +function update_client( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + veh = GetPlayerVehicle( self ); + if( isdefined( veh ) ) + { + update_turret_destroyed( localClientNum, "vehicle.partDestroyed.0", veh clientfield::get( "vtol_turret_destroyed_0" ) ); + update_turret_destroyed( localClientNum, "vehicle.partDestroyed.1", veh clientfield::get( "vtol_turret_destroyed_1" ) ); + } +} + +function fog_bank_2_callback(localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump) +{ + if ( oldVal != newVal ) + { + if ( newVal == 1 ) + { + SetLitFogBank( localClientNum, -1, 1, 0); + } + else + { + SetLitFogBank( localClientNum, -1, 0, 0); + } + } +} \ No newline at end of file diff --git a/mp/killstreaks/_helicopter_gunner.gsc b/mp/killstreaks/_helicopter_gunner.gsc new file mode 100644 index 0000000..cd8a340 --- /dev/null +++ b/mp/killstreaks/_helicopter_gunner.gsc @@ -0,0 +1,1498 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\audio_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; +#using scripts\shared\_oob; +#using scripts\shared\popups_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\weapons\_heatseekingmissile; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\weapons\_hacker_tool; +#using scripts\shared\visionset_mgr_shared; + +#using scripts\mp\_util; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_helicopter; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\teams\_teams; +#using scripts\mp\killstreaks\_remote_weapons; + + + + + + + +#precache( "string", "KILLSTREAK_EARNED_HELICOPTER_GUNNER" ); +#precache( "string", "KILLSTREAK_HELICOPTER_GUNNER_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_HELICOPTER_GUNNER_INBOUND" ); +#precache( "string", "KILLSTREAK_HELICOPTER_GUNNER_HACKED" ); +#precache( "string", "KILLSTREAK_DESTROYED_HELICOPTER_GUNNER" ); +#precache( "string", "KILLSTREAK_HELICOPTER_GUNNER_DAMAGED" ); +#precache( "eventstring", "mpl_killstreak_osprey_strt" ); + +#namespace helicopter_gunner; + + + + + + + + + + +function init() +{ + killstreaks::register( "helicopter_gunner", "helicopter_player_gunner", "killstreak_helicopter_player_gunner", "helicopter_used", &ActivateMainGunner, true ); + killstreaks::register_strings( "helicopter_gunner", &"KILLSTREAK_EARNED_HELICOPTER_GUNNER", &"KILLSTREAK_HELICOPTER_GUNNER_NOT_AVAILABLE", &"KILLSTREAK_HELICOPTER_GUNNER_INBOUND", undefined, &"KILLSTREAK_HELICOPTER_GUNNER_HACKED" ); + killstreaks::register_dialog( "helicopter_gunner", "mpl_killstreak_osprey_strt", "helicopterGunnerDialogBundle", "helicopterGunnerPilotDialogBundle", "friendlyHelicopterGunner", "enemyHelicopterGunner", "enemyHelicopterGunnerMultiple", "friendlyHelicopterGunnerHacked", "enemyHelecopterGunnerHacked", "requestHelicopterGunner", "threatHelicopterGunner" ); + killstreaks::register_alt_weapon( "helicopter_gunner", "helicopter_gunner_turret_rockets" ); + killstreaks::register_alt_weapon( "helicopter_gunner", "helicopter_gunner_turret_primary" ); + killstreaks::register_alt_weapon( "helicopter_gunner", "helicopter_gunner_turret_secondary" ); + killstreaks::register_alt_weapon( "helicopter_gunner", "helicopter_gunner_turret_tertiary" ); + killstreaks::set_team_kill_penalty_scale( "helicopter_gunner", level.teamKillReducedPenalty ); + killstreaks::devgui_scorestreak_command( "helicopter_gunner", "Debug Paths", "toggle scr_devHeliPathsDebugDraw 1 0"); + + killstreaks::register( "helicopter_gunner_assistant", "helicopter_gunner_assistant", "killstreak_" + "helicopter_gunner_assistant", "helicopter_used", &ActivateSupportGunner, true, undefined, false, false ); + killstreaks::register_strings( "helicopter_gunner_assistant", &"KILLSTREAK_EARNED_HELICOPTER_GUNNER", &"KILLSTREAK_HELICOPTER_GUNNER_NOT_AVAILABLE", &"KILLSTREAK_HELICOPTER_GUNNER_INBOUND", undefined, &"KILLSTREAK_HELICOPTER_GUNNER_HACKED" ); + killstreaks::register_dialog( "helicopter_gunner_assistant", "mpl_killstreak_osprey_strt", "helicopterGunnerDialogBundle", "helicopterGunnerPilotDialogBundle", "friendlyHelicopterGunner", "enemyHelicopterGunner", "enemyHelicopterGunnerMultiple", "friendlyHelicopterGunnerHacked", "enemyHelecopterGunnerHacked", "requestHelicopterGunner", "threatHelicopterGunner" ); + killstreaks::set_team_kill_penalty_scale( "helicopter_gunner_assistant", level.teamKillReducedPenalty ); + + // TODO: Move to killstreak data + level.killstreaks["helicopter_gunner"].threatOnKill = true; + + callback::on_connect( &OnPlayerConnect ); + callback::on_spawned( &UpdatePlayerState ); + callback::on_joined_team( &UpdatePlayerState ); + callback::on_joined_spectate( &UpdatePlayerState ); + callback::on_disconnect( &UpdatePlayerState ); + callback::on_player_killed( &UpdatePlayerState ); + + clientfield::register( "vehicle", "vtol_turret_destroyed_0", 1, 1, "int" ); + clientfield::register( "vehicle", "vtol_turret_destroyed_1", 1, 1, "int" ); + clientfield::register( "vehicle", "mothership", 1, 1, "int" ); + clientfield::register( "toplayer", "vtol_update_client", 1, 1, "counter" ); + clientfield::register( "toplayer", "fog_bank_2", 1, 1, "int" ); + + visionset_mgr::register_info( "visionset", "mothership_visionset", 1, 70, 16, true, &visionset_mgr::ramp_in_out_thread_per_player_death_shutdown, false ); + + level thread WaitForGameEndThread(); + + level.vtol = undefined; +} + +function OnPlayerConnect() +{ + if( !isdefined( self.entNum ) ) + { + self.entNum = self getEntityNumber(); + } +} + +function UpdatePlayerState() +{ + player = self; + UpdateAllKillstreakInventory(); +} + +function UpdateAllKillstreakInventory() +{ + foreach( player in level.players ) + { + if( isdefined( player.sessionstate ) && player.sessionstate == "playing" ) + UpdateKillstreakInventory( player ); + } +} + +function UpdateKillstreakInventory( player ) +{ + if( !isdefined( player ) ) + return; + + heli_team = undefined; + if( isdefined( level.vtol ) && isdefined( level.vtol.owner ) && !level.vtol.shuttingDown && ( level.vtol.totalRocketHits < ( 6 ) ) ) + heli_team = level.vtol.owner.team; + + if( isdefined( heli_team ) && ( player.team == heli_team ) ) + { + if( ( GetFirstAvailableSeat( player ) != -1 ) && !isdefined( level.vtol.usage[player.entNum] ) ) + { + if( !player killstreaks::has_killstreak( "inventory_helicopter_gunner_assistant" ) ) + player killstreaks::give( "inventory_helicopter_gunner_assistant", undefined, undefined, true, true ); + return; + } + } + + if( player killstreaks::has_killstreak( "inventory_helicopter_gunner_assistant" ) ) + player killstreaks::take( "inventory_helicopter_gunner_assistant" ); +} + +function ActivateMainGunner( killstreakType ) +{ + player = self; + + while( isdefined( level.vtol ) && level.vtol.shuttingdown ) + { + if( !player killstreakrules::isKillstreakAllowed( "helicopter_gunner", player.team ) ) + return false; + } + + player util::freeze_player_controls( true ); + + result = player SpawnHeliGunner(); + + player util::freeze_player_controls( false ); + + if( level.gameEnded ) + return true; + + if( !isdefined( result ) ) + return false; + + return result; +} + +function ActivateSupportGunner( killstreakType ) +{ + player = self; + + if( isdefined( level.vtol ) && level.vtol.shuttingdown ) + return false; + + if( isdefined( level.vtol.usage[player.entNum] ) ) + return false; + + player util::freeze_player_controls( true ); + + result = player EnterHelicopter( false ); + + player util::freeze_player_controls( false ); + + return result; +} + +function GetFirstAvailableSeat( player ) +{ + if( isdefined( level.vtol ) && ( !level.vtol.shuttingDown ) && ( level.vtol.team == player.team ) && ( level.vtol.owner != player ) ) + { + for( i = 0; i < ( 2 ); i++ ) + { + if( !isdefined( level.vtol.assistants[i].occupant ) && !level.vtol.assistants[i].destroyed ) + { + return i; + } + } + } + + return -1; +} + +function InitHelicopterSeat( index, destroyTag ) +{ + level.vtol.assistants[index] = SpawnStruct(); + assistant = level.vtol.assistants[index]; + + assistant.occupant = undefined; + assistant.destroyed = false; + assistant.rocketHits = 0; + assistant.targetTag = destroyTag; + + assistant.targetEnt = spawn( "script_model", ( 0, 0, 0 ) ); + assistant.targetEnt.useVTOLTime = true; + assistant.targetEnt SetModel( "p7_dogtags_enemy" ); // hack to send ent to clients for targeting + assistant.targetEnt LinkTo( level.vtol, assistant.targetTag, ( 0, 0, 0 ), ( 0, 0, 0 ) ); + assistant.targetEnt.team = level.vtol.team; + Target_Set( assistant.targetEnt, ( 0, 0, 0 ) ); + Target_SetAllowHighSteering( assistant.targetEnt, true ); + assistant.targetEnt.parent = level.vtol; + + level.vtol vehicle::add_to_target_group( assistant.targetEnt ); +} + +function HackedPreFunction( hacker ) +{ + heliGunner = self; + heliGunner.owner unlink(); + level.vtol clientfield::set( "vehicletransition", 0 ); + visionset_mgr::deactivate( "visionset", "mothership_visionset", heliGunner.owner ); + heliGunner.owner SetModelLodBias( 0 ); + heliGunner.owner clientfield::set_to_player( "fog_bank_2", 0 ); + heliGunner.owner clientfield::set_to_player( "toggle_flir_postfx", 0 ); + heliGunner.owner notify( "gunner_left" ); + heliGunner.owner killstreaks::clear_using_remote(); + heliGunner.owner killstreaks::unhide_compass(); + heliGunner.owner vehicle::stop_monitor_missiles_locked_on_to_me(); + heliGunner.owner vehicle::stop_monitor_damage_as_occupant(); + + foreach( assistant in heliGunner.assistants ) + { + if( isdefined( assistant.occupant ) ) + assistant.occupant iPrintLnBold( &"KILLSTREAK_HELICOPTER_GUNNER_DAMAGED" ); + LeaveHelicopter( assistant.occupant, false ); + } + + heliGunner MakeVehicleUnusable(); +} + +function HackedPostFunction( hacker ) +{ + heliGunner = self; + heliGunner clientfield::set( "enemyvehicle", 2 ); + heliGunner MakeVehicleUsable(); + heliGunner UseVehicle( hacker, 0 ); + level.vtol clientfield::set( "vehicletransition", 1 ); + heliGunner thread vehicle::monitor_missiles_locked_on_to_me( hacker ); + heliGunner thread vehicle::monitor_damage_as_occupant( hacker ); + + hacker thread WatchVisionSwitchThread(); + hacker killstreaks::hide_compass(); + heliGunner thread WatchPlayerExitRequestThread( hacker ); + visionset_mgr::activate( "visionset", "mothership_visionset", hacker, 1, heliGunner killstreak_hacking::get_hacked_timeout_duration_ms(), 1 ); + hacker SetModelLodBias( (isdefined(level.mothership_lod_bias)?level.mothership_lod_bias:8) ); + heliGunner.owner GiveDedicatedShadow( level.vtol ); + heliGunner.owner clientfield::set_to_player( "fog_bank_2", 1 ); + + hacker thread WatchPlayerTeamChangeThread( heliGunner ); + hacker killstreaks::set_killstreak_delay_killcam( "helicopter_gunner" ); + + if ( heliGunner.killstreak_timer_started ) + { + heliGunner.killstreak_duration = heliGunner killstreak_hacking::get_hacked_timeout_duration_ms(); + heliGunner.killstreak_end_time = hacker killstreak_hacking::set_vehicle_drivable_time_starting_now( heliGunner ); + heliGunner.killstreakEndTime = int( heliGunner.killstreak_end_time ); + } + else + { + heliGunner.killstreak_timer_start_using_hacked_time = true; + } +} + +function SpawnHeliGunner() +{ + player = self; + player endon( "disconnect" ); + level endon( "game_ended" ); + + if( !isdefined( level.heli_paths ) || !level.heli_paths.size ) + return false; + + if( !isdefined( level.Heli_primary_path ) || !level.heli_primary_path.size ) + return false; + + if( ( isdefined( player.isPlanting ) && player.isPlanting ) || ( isdefined( player.isDefusing ) && player.isDefusing ) || player util::isUsingRemote() || player IsWallRunning() || player oob::IsOutOfBounds() ) + return false; + + killstreak_id = player killstreakrules::killstreakStart( "helicopter_gunner", player.team, undefined, true ); + if( killstreak_id == (-1) ) + return false; + + startNode = level.heli_primary_path[0]; + + level.vtol = SpawnVehicle( "veh_bo3_mil_gunship_mp", startnode.origin, startnode.angles, "dynamic_spawn_ai" ); + level.vtol killstreaks::configure_team( "helicopter_gunner", killstreak_id, player, "helicopter" ); + level.vtol killstreak_hacking::enable_hacking( "helicopter_gunner", &HackedPreFunction, &HackedPostFunction ); + level.vtol.killstreak_id = killstreak_id; + level.vtol.destroyFunc = &DeleteHelicopterCallback; + level.vtol.hardpointType = "helicopter_gunner"; + level.vtol clientfield::set( "enemyvehicle", 1 ); + level.vtol clientfield::set( "vtol_turret_destroyed_0", 0 ); + level.vtol clientfield::set( "vtol_turret_destroyed_1", 0 ); + level.vtol clientfield::set( "mothership", 1 ); + level.vtol vehicle::init_target_group(); + level.vtol.killstreak_timer_started = false; + level.vtol.allowdeath = false; + + level.vtol.playerMovedRecently = false; + level.vtol.soundmod = "default_loud"; + level.vtol hacker_tool::registerwithhackertool(( 50 ), ( 10000 ) ); + + level.vtol.assistants = []; + level.vtol.usage = []; + + InitHelicopterSeat( 0, "tag_gunner_barrel1" ); + InitHelicopterSeat( 1, "tag_gunner_barrel2"); + + level.destructible_callbacks["turret_destroyed"] = &VTOLDestructibleCallback; + level.destructible_callbacks["turret1_destroyed"] = &VTOLDestructibleCallback; + level.destructible_callbacks["turret2_destroyed"] = &VTOLDestructibleCallback; + + level.vtol.shuttingDown = false; + level.vtol thread PlayLockOnSoundsThread( player, level.vtol ); + + level.vtol thread helicopter::wait_for_killed(); + level.vtol thread wait_for_bda_dialog(); + + level.vtol.maxhealth = ( 15000 ); + tableHealth = killstreak_bundles::get_max_health( "helicopter_gunner" ); + + if ( isdefined( tableHealth ) ) + { + level.vtol.maxhealth = tableHealth; + } + level.vtol.original_health = level.vtol.maxhealth; + level.vtol.health = level.vtol.maxhealth; + + level.vtol SetCanDamage( true ); + level.vtol thread heatseekingmissile::MissileTarget_ProximityDetonateIncomingMissile( "death" ); + level.vtol thread WatchMissilesThread(); + + attack_nodes = GetEntArray( "heli_attack_area", "targetname" ); + if( attack_nodes.size ) + { + level.vtol thread HelicopterThinkThread( startNode, attack_nodes ); + player thread WatchLocationChangeThread( attack_nodes ); + } + else + { + level.vtol thread helicopter::heli_fly( startNode, 0.0, "helicopter_gunner" ); + } + + level.vtol.totalRocketHits = 0; + level.vtol.turretRocketHits = 0; + level.vtol.targetEnt = undefined; + + level.vtol.overrideVehicleDamage = &HelicopterGunnerDamageOverride; + level.vtol.hackedHealthUpdateCallback = &HelicopterGunner_hacked_health_callback; + level.vtol.DetonateViaEMP = &helicopteDetonateViaEMP; + + player thread killstreaks::play_killstreak_start_dialog( "helicopter_gunner", player.team, killstreak_id ); + level.vtol killstreaks::play_pilot_dialog_on_owner( "arrive", "helicopter_gunner", killstreak_id ); + + player AddWeaponStat( GetWeapon( "helicopter_player_gunner" ), "used", 1 ); + + level.vtol thread WaitForVTOLShutdownThread(); + + result = player EnterHelicopter( true ); + + return result; +} + +function HelicopterGunner_hacked_health_callback() +{ + helicopter = self; + + if ( helicopter.shuttingDown == true ) + { + return; + } + +// Not sure what design wants here. +// for( seatIndex = 0; seatIndex < HELICOPTER_GUNNER_ASSISTANT_SEAT_COUNT; seatIndex++ ) +// { +// assistant = helicopter.assistants[seatIndex]; +// if( !assistant.destroyed ) +// { +// damage = 1000; +// helicopter.noDamageFeedback = 1; +// helicopter DoDamage( damage, assistant.targetEnt.origin, undefined, undefined, undefined, "MOD_UNKNOWN", 0, undefined, seatIndex + 8 ); +// helicopter.noDamageFeedback = 0; +// +// SupportTurretDestroyed( helicopter, seatIndex ); +// } +// } +// helicopter AllowMainTurretLockon(); + + hackedHealth = killstreak_bundles::get_hacked_health( "helicopter_gunner" ); + assert( isdefined( hackedHealth ) ); + if ( helicopter.health > hackedhealth ) + { + helicopter.health = hackedhealth; + } +} + + +function WaitForGameEndThread() +{ + level waittill( "game_ended" ); + if( isdefined( level.vtol ) && isdefined( level.vtol.owner )) + LeaveHelicopter( level.vtol.owner, true ); +} + +function WaitForVTOLShutdownThread() +{ + helicopter = self; + helicopter waittill( "vtol_shutdown", attacker ); + + if( isdefined( attacker ) ) + { + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_HELICOPTER_GUNNER", attacker.entnum ); + } + + if( isdefined( helicopter.targetEnt ) ) + { + Target_Remove( helicopter.targetEnt ); + helicopter.targetEnt Delete(); + helicopter.targetEnt = undefined; + } + + for( seatIndex = 0; seatIndex < ( 2 ); seatIndex++ ) + { + assistant = level.vtol.assistants[seatIndex]; + if( isdefined( assistant.targetEnt ) ) + { + Target_Remove( assistant.targetEnt ); + assistant.targetEnt Delete(); + assistant.targetEnt = undefined; + } + } + + killstreakrules::killstreakStop( "helicopter_gunner", helicopter.originalTeam, helicopter.killstreak_id ); + + LeaveHelicopter( level.vtol.owner, true ); + + level.vtol = undefined; + + helicopter delete(); +} + +function DeleteHelicopterCallback() +{ + helicopter = self; + helicopter notify( "vtol_shutdown", undefined ); +} + +function OnTimeoutCallback() +{ + for( i = 0; i < ( 2 ); i++ ) + { + if( isdefined(level.vtol.assistants[i].occupant ) ) + { + level.vtol.assistants[i].occupant killstreaks::play_pilot_dialog( "timeout", "helicopter_gunner", undefined, level.vtol.killstreak_id ); + } + } + + LeaveHelicopter( level.vtol.owner, true ); +} + +function WatchPlayerTeamChangeThread( helicopter ) +{ + helicopter notify( "mothership_team_change" ); + helicopter endon ( "mothership_team_change" ); + + assert( IsPlayer( self ) ); + player = self; + + player endon( "gunner_left" ); + + player util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); + + ownerLeft = helicopter.ownerEntNum == player.entNum; + + player thread LeaveHelicopter( player, ownerLeft ); // need to thread to prevent endon( "gunner_left" ) to terminate the LeaveHelicopter + + if( ownerLeft ) + helicopter notify( "vtol_shutdown", undefined ); +} + +function WatchPlayerExitRequestThread( player ) +{ + player notify( "WatchPlayerExitRequestThread_singleton" ); + player endon ( "WatchPlayerExitRequestThread_singleton" ); + assert( IsPlayer( player ) ); + mothership = self; + + level endon( "game_ended" ); + player endon( "disconnect" ); + player endon( "gunner_left" ); + + owner = mothership.ownerEntNum == player.entNum; + + while( true ) + { + timeUsed = 0; + while( player UseButtonPressed() ) + { + timeUsed += 0.05; + if( timeUsed > 0.25 ) + { + mothership killstreaks::play_pilot_dialog_on_owner( "remoteOperatorRemoved", "helicopter_gunner", level.vtol.killstreak_id ); + player thread LeaveHelicopter( player, owner ); // need to thread this so that endon( "gunner_left" ) does not self termniate in LeaveHelicopter() + return; + } + {wait(.05);}; + } + {wait(.05);}; + } +} + +function EnterHelicopter( isOwner ) +{ + assert( IsPlayer( self ) ); + player = self; + + seatIndex = -1; + if( !isOwner ) + { + seatIndex = GetFirstAvailableSeat( player ); + if( seatIndex == -1 ) + { + return false; + } + level.vtol.assistants[ seatIndex ].occupant = player; + } + + level.vtol.occupied = true; // needed for killcam to function properly + + player util::setUsingRemote( "helicopter_gunner" ); + player.ignoreEMPJammed = true; + result = player killstreaks::init_ride_killstreak( "helicopter_gunner" ); + player.ignoreEMPJammed = false; + if( result != "success" ) + { + if( result != "disconnect" ) + { + player killstreaks::clear_using_remote(); + } + + if( !isOwner ) + level.vtol.assistants[ seatIndex ].occupant = undefined; + + if( isOwner ) + { + level.vtol.failed2enter = true; + level.vtol notify( "vtol_shutdown" ); + } + + return false; + } + + if( isOwner ) + { + level.vtol UseVehicle( player, 0 ); + level.vtol clientfield::set( "vehicletransition", 1 ); + } + else + { + if( level.vtol.shuttingdown ) + { + player killstreaks::clear_using_remote(); + return false; + } + level.vtol UseVehicle( player, seatIndex + ( 1 ) ); + level.vtol clientfield::set( "vehicletransition", 1 ); + + level.vtol killstreaks::play_pilot_dialog_on_owner( "remoteOperatorAdd", "helicopter_gunner", level.vtol.killstreak_id ); + } + + killcament = spawn( "script_model", ( 0, 0, 0 ) ); + killcament SetModel( "tag_origin" ); + killcament.angles = ( 0, 0, 0 ); + killcament SetWeapon( GetWeapon( "helicopter_gunner_turret_primary" ) ); + killcament linkto( level.vtol, "tag_barrel", ( 370, 0, 25 ), ( 0, 0, 0 ) ); + level.vtol.killcament = killcament; + + level.vtol.usage[player.entNum] = 1; + + level.vtol thread audio::sndUpdateVehicleContext(true); + + level.vtol thread vehicle::monitor_missiles_locked_on_to_me( player ); + level.vtol thread vehicle::monitor_damage_as_occupant( player ); + + if ( level.vtol.killstreak_timer_started ) + { + player vehicle::set_vehicle_drivable_time( level.vtol.killstreak_duration, level.vtol.killstreak_end_time ); + } + else + { + player vehicle::set_vehicle_drivable_time( 9009009, GetTime() + 9009009 ); + } + + update_client_for_player( player ); + + UpdateAllKillstreakInventory(); + + player thread WatchVisionSwitchThread(); + level.vtol thread WatchPlayerExitRequestThread( player ); + player thread WatchPlayerTeamChangeThread( level.vtol ); + + visionset_mgr::activate( "visionset", "mothership_visionset", player, 1, ( 60000 ), 1 ); + player SetModelLodBias( (isdefined(level.mothership_lod_bias)?level.mothership_lod_bias:8) ); + player GiveDedicatedShadow( level.vtol ); + player clientfield::set_to_player( "fog_bank_2", 1 ); + + if ( true ) + { + player thread HideCompassAfterWait( 0.1 ); // need to do this due to the way this scorestreak starts up + } + + return true; +} + +function HideCompassAfterWait( waittime ) +{ + self endon( "death" ); + self endon( "disconnect" ); + + wait waittime; + + self killstreaks::hide_compass(); +} + +function MainTurretDestroyed( helicopter, eAttacker, weapon ) +{ + helicopter.owner iPrintLnBold( &"KILLSTREAK_HELICOPTER_GUNNER_DAMAGED" ); + + if ( isdefined(helicopter.targetEnt)) + { + Target_Remove( helicopter.targetEnt ); + helicopter.targetEnt Delete(); + helicopter.targetEnt = undefined; + } + helicopter.shuttingDown = true; + UpdateAllKillstreakInventory(); + + eAttacker = self [[ level.figure_out_attacker ]]( eAttacker ); + if( !isdefined( helicopter.destroyScoreEventGiven ) && isdefined( eAttacker ) && ( !isdefined( helicopter.owner ) || helicopter.owner util::IsEnemyPlayer( eAttacker ) ) ) + { + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_HELICOPTER_GUNNER_DAMAGED", eAttacker.entnum ); + challenges::destroyedAircraft( eAttacker, weapon, true ); + eAttacker challenges::addFlySwatterStat( weapon, helicopter ); + scoreevents::processScoreEvent( "destroyed_vtol_mothership", eAttacker, helicopter.owner, weapon ); + helicopter killstreaks::play_destroyed_dialog_on_owner( "helicopter_gunner", helicopter.killstreak_id ); + helicopter.destroyScoreEventGiven = 1; + } + + helicopter thread PerformLeaveHelicopterFromDamage(); +} + +function wait_for_bda_dialog( killstreakId ) +{ + self endon( "vtol_shutdown" ); + + while(true) + { + self waittill( "bda_dialog", dialogKey ); + + for( i = 0; i < ( 2 ); i++ ) + { + if( isdefined( level.vtol.assistants[i].occupant ) ) + { + level.vtol.assistants[i].occupant killstreaks::play_pilot_dialog( dialogKey, "helicopter_gunner", killstreakId, self.pilotIndex ); + } + } + } +} + +function SupportTurretDestroyed( helicopter, seatIndex ) +{ + assistant = helicopter.assistants[seatIndex]; + if( !assistant.destroyed ) + { + Target_Remove( assistant.targetEnt ); + level.vtol vehicle::remove_from_target_group(); + + assistant.targetEnt Delete(); + assistant.targetEnt = undefined; + assistant.destroyed = true; + + if ( isdefined( helicopter.owner ) && isdefined( helicopter.hardpointType ) ) + { + helicopter killstreaks::play_pilot_dialog_on_owner( "weaponDestroyed", helicopter.hardpointType, helicopter.killstreak_id ); + } + + if( isdefined( assistant.occupant ) ) + { + assistant.occupant globallogic_audio::flush_killstreak_dialog_on_player( helicopter.killstreak_id ); + assistant.occupant killstreaks::play_pilot_dialog( "weaponDestroyed", helicopter.hardpointType, undefined, helicopter.pilotIndex ); + wait 2.0; + LeaveHelicopter( assistant.occupant, false ); + } + } + + // update destroyed states + + if ( seatIndex == 0 ) + { + level.vtol clientfield::set( "vtol_turret_destroyed_0", 1 ); + level.vtol update_client_for_driver_and_occupants(); + } + else if ( seatIndex == 1 ) + { + level.vtol clientfield::set( "vtol_turret_destroyed_1", 1 ); + level.vtol update_client_for_driver_and_occupants(); + } +} + +function update_client_for_driver_and_occupants() // self == vtol +{ + vtol = self; + + update_client_for_player( vtol.owner ); + + foreach( assistant in vtol.assistants ) + { + update_client_for_player( assistant.occupant ); + } +} + +function update_client_for_player( player ) +{ + if ( isdefined( player ) ) + { + player clientfield::increment_to_player( "vtol_update_client", 1 ); + } +} + +function VTOLDestructibleCallback( brokenNotify, eAttacker, weapon ) +{ + helicopter = self; + + helicopter endon ( "delete" ); + helicopter endon ( "vtol_shutdown" ); + + notifies = []; + notifies[0] = "turret1_destroyed"; + notifies[1] = "turret2_destroyed"; + + for( seatIndex = 0; seatIndex < ( 2 ); seatIndex++ ) + { + if( brokenNotify == notifies[seatIndex] ) + { + SupportTurretDestroyed( helicopter, seatIndex ); + break; + } + } + + if( brokenNotify == "turret_destroyed" ) + { + MainTurretDestroyed( helicopter, eAttacker, weapon ); + return; + } + + helicopter AllowMainTurretLockon(); +} + +function AllowMainTurretLockon() +{ + helicopter = self; + + // allow lockon on the main turrets + if( helicopter.assistants[0].destroyed && helicopter.assistants[1].destroyed ) + { + if( !isdefined( helicopter.targetEnt ) ) + { + helicopter.targetEnt = spawn( "script_model", ( 0, 0, 0 ) ); + helicopter.targetEnt SetModel( "p7_dogtags_enemy" ); // hack to send ent to clients for targeting + helicopter.targetEnt LinkTo( level.vtol, "tag_barrel", ( 0, 0, 0 ), ( 0, 0, 0 ) ); + helicopter.targetEnt.parent = level.vtol; + helicopter.targetEnt.team = level.vtol.team; + Target_Set( helicopter.targetEnt, ( 0, 0, 0 ) ); + helicopter.targetEnt.useVTOLTime = true; + Target_SetAllowHighSteering( helicopter.targetEnt, true ); + + level.vtol vehicle::add_to_target_group( helicopter.targetEnt ); + } + } +} + +function LeaveHelicopter( player, ownerLeft ) +{ + if( !isdefined( level.vtol ) || level.vtol.completely_shutdown === true ) + return; + + if( isdefined( player ) ) + { + player vehicle::stop_monitor_missiles_locked_on_to_me(); + player vehicle::stop_monitor_damage_as_occupant(); + } + + if( isdefined( player ) && isdefined( level.vtol ) && isdefined( level.vtol.owner ) ) + { + if( isdefined( player.usingvehicle ) && player.usingvehicle ) + { + player unlink(); + level.vtol clientfield::set( "vehicletransition", 0 ); + + if( ownerLeft ) + player killstreaks::take( "helicopter_gunner" ); + else + player killstreaks::take( "inventory_helicopter_gunner_assistant" ); + } + } + + if( ownerLeft ) + { + level.vtol.shuttingDown = true; + foreach( assistant in level.vtol.assistants ) + { + if( isdefined( assistant.occupant ) ) + { + assistant.occupant iPrintLnBold( &"KILLSTREAK_HELICOPTER_GUNNER_DAMAGED" ); + LeaveHelicopter( assistant.occupant, false ); + } + } + + level.vtol.occupied = false; + level.vtol.hardpointType = "helicopter_gunner"; + level.vtol thread helicopter::heli_leave(); + level.vtol thread audio::sndUpdateVehicleContext(false); + } + else + { + if( isdefined( player ) ) + { + player globallogic_audio::flush_killstreak_dialog_on_player( level.vtol.killstreak_id ); + + foreach( assistant in level.vtol.assistants ) + { + if( isdefined( assistant.occupant ) && assistant.occupant == player ) + { + assistant.occupant = undefined; + break; + } + } + } + } + + if( isdefined( player ) ) + { + player clientfield::set_to_player( "toggle_flir_postfx", 0 ); + visionset_mgr::deactivate( "visionset", "mothership_visionset", player ); + player SetModelLodBias( 0 ); + player GiveDedicatedShadow( player ); + + player clientfield::set_to_player( "fog_bank_2", 0 ); + player killstreaks::unhide_compass(); + + player notify( "gunner_left" ); + + player killstreaks::clear_using_remote(); + + if( level.gameEnded ) + player util::freeze_player_controls( true ); + } + + UpdateAllKillstreakInventory(); + + if ( ownerLeft ) + level.vtol.completely_shutdown = true; +} + +function vtol_shake() +{ + if( isdefined( level.vtol ) && isdefined( level.vtol.owner ) ) + { + org = level.vtol GetTagOrigin( "tag_barrel" ); + + magnitude = 0.3; + duration = 2; + radius = 500; + v_pos = self.origin; + Earthquake( magnitude, duration, org, 500 ); + } +} + + +function HelicopterGunnerDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) +{ + helicopter = self; + + if( sMeansOfDeath == "MOD_TRIGGER_HURT" ) + return 0; + + if( helicopter.shuttingDown ) + return 0; + + iDamage = self killstreaks::OnDamagePerWeapon( "helicopter_gunner", eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, level.vtol.maxhealth, undefined, level.vtol.maxhealth*0.4, undefined, 0, undefined, true, 1.0 ); + if( iDamage == 0 ) + return 0; + + // handle rocket damage + handleAsRocketDamage = ( ( sMeansOfDeath == "MOD_PROJECTILE" ) || ( sMeansOfDeath == "MOD_EXPLOSIVE" ) ); + if ( weapon.statIndex == level.weaponShotgunEnergy.statIndex || weapon.statIndex == level.weaponPistolEnergy.statIndex || weapon.statIndex == level.weaponSmgNailGun.statIndex ) + handleAsRocketDamage = false; + + if( handleAsRocketDamage ) + { + updateInventory = 1; + + missileTarget = eInflictor missile_gettarget(); + + vtol_shake(); + + rocketHit = 1.0; + if ( weapon.statIndex == level.weaponLauncherMulti.statIndex ) + rocketHit = 0.5; + + helicopter.totalRocketHits += rocketHit; + + if( isdefined( missileTarget ) ) + { + // handle rocket damage to the support turrets + for( seatIndex = 0; seatIndex < ( 2 ); seatIndex++ ) + { + assistant = helicopter.assistants[seatIndex]; + + if( !assistant.destroyed && ( assistant.targetEnt == missileTarget ) ) + { + assistant.rocketHits += rocketHit; + + if ( assistant.rocketHits >= 2 ) + { + helicopter DoDamage( iDamage, assistant.targetEnt.origin, eAttacker, eInflictor, sHitLoc, "MOD_UNKNOWN", 0, weapon, seatIndex + 8 ); + iDamage = 0; + SupportTurretDestroyed( helicopter, seatIndex ); + } + } + } + + // handle rocket damage to the main turrets + if( isdefined( helicopter.targetEnt ) && ( helicopter.targetEnt == missileTarget ) ) + { + helicopter.turretRocketHits += rocketHit; + + // main turret need 2 rockets + if( helicopter.turretRocketHits >= 2 ) + { + Target_Remove( helicopter.targetEnt ); + helicopter.targetEnt Delete(); + helicopter.targetEnt = undefined; + } + } + } + + // allow lockon on the main turret + if( helicopter.assistants[0].destroyed && helicopter.assistants[1].destroyed && ( !isdefined( helicopter.targetEnt ) ) ) + { + helicopter.targetEnt = spawn( "script_model", ( 0, 0, 0 ) ); + helicopter.targetEnt SetModel( "p7_dogtags_enemy" ); // hack to send ent to clients for targeting + helicopter.targetEnt LinkTo( level.vtol, "tag_barrel", ( 0, 0, 0 ), ( 0, 0, 0 ) ); + helicopter.targetEnt.parent = level.vtol; + helicopter.targetEnt.team = level.vtol.team; + Target_Set( helicopter.targetEnt, ( 0, 0, 0 ) ); + helicopter.targetEnt.useVTOLTime = true; + Target_SetAllowHighSteering( helicopter.targetEnt, true ); + } + + if( helicopter.totalRocketHits >= ( 6 ) ) + { + MainTurretDestroyed( helicopter, eAttacker, weapon ); + updateInventory = 0; + } + + if ( updateInventory ) + UpdateAllKillstreakInventory(); + } + + if( iDamage >= level.vtol.health && !helicopter.shuttingDown ) + { + helicopter.shuttingDown = true; + UpdateAllKillstreakInventory(); + + if ( !isdefined( helicopter.destroyScoreEventGiven ) && isdefined( eAttacker ) && ( !isdefined( helicopter.owner ) || helicopter.owner util::IsEnemyPlayer( eAttacker ) ) ) + { + eAttacker = self [[ level.figure_out_attacker ]]( eAttacker ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_HELICOPTER_GUNNER_DAMAGED", eAttacker.entnum ); + scoreevents::processScoreEvent( "destroyed_vtol_mothership", eAttacker, helicopter.owner, weapon ); + helicopter killstreaks::play_destroyed_dialog_on_owner( "helicopter_gunner", helicopter.killstreak_id ); + helicopter.destroyScoreEventGiven = 1; + } + + helicopter thread PerformLeaveHelicopterFromDamage(); + } + + if( helicopter.shuttingDown ) + { + if( iDamage >= helicopter.health ) + iDamage = helicopter.health - 1; // keep it alive. We want it to go away not explode + } + + ///#iprintln( partName + " health:" + helicopter.health + " damage:" + iDamage );#/ + return iDamage; +} + +function PerformLeaveHelicopterFromDamage() +{ + helicopter = self; + helicopter endon( "death" ); + + if ( self.leave_by_damage_initiated === true ) + return; + + self.leave_by_damage_initiated = true; + + helicopter thread remote_weapons::do_static_fx(); + failsafe_timeout = 5.0; + helicopter util::waittill_any_timeout( failsafe_timeout, "static_fx_done" ); + LeaveHelicopter( helicopter.owner, true ); +} + +function helicopteDetonateViaEMP( attacker, weapon ) +{ + MainTurretDestroyed( level.vtol, attacker, weapon ); +} + +function MissileCleanupThread( missile ) +{ + targetEnt = self; + + targetEnt endon( "delete" ); + targetEnt endon( "death" ); + + missile util::waittill_any( "death", "delete" ); + + targetEnt Delete(); +} + +function WatchMissilesThread() +{ + helicopter = self; + player = helicopter.owner; + + player endon( "disconnect" ); + player endon( "gunner_left" ); + + heliMissile = GetWeapon( "helicopter_gunner_turret_rockets" ); + + while( true ) + { + player waittill( "missile_fire", missile ); + + trace_origin = level.vtol GetTagOrigin( "tag_flash" ); + trace_direction = level.vtol GetTagAngles( "tag_barrel" ); + trace_direction = AnglesToForward( trace_direction ) * 8000; + trace = BulletTrace( trace_origin, trace_origin + trace_direction, false, level.vtol ); + end_origin = trace["position"]; + + missiles = getentarray( "rocket", "classname" ); + + /# + //Box( end_origin, (-4, -4, 0 ), ( 4, 4, 1000 ), 0, ( 0, 0.7, 0 ), 0.6, false, 9999999 ); + #/ + + foreach( missile in missiles ) + { + if( missile.item == heliMissile ) + { + targetEnt = Spawn( "script_model", end_origin ); + missile Missile_SetTarget( targetEnt ); + targetEnt thread MissileCleanupThread( missile ); + } + } + + // setup the "reload" time for the player's vehicle HUD + weapon_wait_duration_ms = Int( heliMissile.fireTime * 1000 ); + player SetVehicleWeaponWaitDuration( weapon_wait_duration_ms ); + player SetVehicleWeaponWaitEndTime( GetTime() + weapon_wait_duration_ms ); + } +} + +function WatchVisionSwitchThread() +{ + assert( IsPlayer( self ) ); + player = self; + + player endon( "disconnect" ); + player endon( "gunner_left" ); + + inverted = false; + player clientfield::set_to_player( "toggle_flir_postfx", 2 ); + + while( true ) + { + if( player JumpButtonPressed() ) + { + if( inverted ) + { + player clientfield::set_to_player( "toggle_flir_postfx", 2 ); + player PlaySoundToPlayer( "mpl_cgunner_flir_off", player ); + } + else + { + player clientfield::set_to_player( "toggle_flir_postfx", 1 ); + player PlaySoundToPlayer( "mpl_cgunner_flir_on", player ); + } + + inverted = !inverted; + + while( player JumpButtonPressed() ) + {wait(.05);}; + } + + {wait(.05);}; + } +} + +function PlayLockOnSoundsThread( player, heli ) +{ + player endon( "disconnect" ); + player endon( "gunner_left" ); + + heli endon( "death" ); + heli endon ( "crashing" ); + heli endon ( "leaving" ); + + heli.lockSounds = spawn( "script_model", heli.origin ); + wait ( 0.1 ); + heli.lockSounds LinkTo( heli, "tag_player" ); + + while( true ) + { + heli waittill( "locking on" ); + + while( true ) + { + if( EnemyIsLocking( heli ) ) + { + heli.lockSounds PlaySoundToPlayer( "uin_alert_lockon", player ); + wait ( 0.125 ); + } + + if( EnemyLockedOn( heli ) ) + { + heli.lockSounds PlaySoundToPlayer( "uin_alert_lockon", player ); + wait ( 0.125 ); + } + + if( !EnemyIsLocking( heli ) && !EnemyLockedOn( heli ) ) + { + heli.lockSounds StopSounds(); + break; + } + } + } +} + +function EnemyIsLocking( heli ) +{ + return ( isdefined( heli.locking_on ) && heli.locking_on ); +} + +function EnemyLockedOn( heli ) +{ + return ( isdefined( heli.locked_on ) && heli.locked_on ); +} + +function HelicopterThinkThread( startNode, destNodes ) +{ + self notify( "flying"); + self endon( "flying" ); + + self endon ( "death" ); + self endon ( "crashing" ); + self endon ( "leaving" ); + + nextnode = getent( startNode.target, "targetname" ); + assert( isdefined( nextnode ), "Next node in path is undefined, but has targetname" ); + self SetSpeed( 150, 80 ); + self setvehgoalpos( nextnode.origin + ( 0, 0, ( 2000 ) ), 1 ); + self waittill( "near_goal" ); + + firstpass = true; + //while( true ) + { + if( !self.playerMovedRecently ) + { + node = self UpdateAreaNodes( destNodes, false ); + level.vtol.currentNode = node; + targetNode = getEnt( node.target, "targetname" ); + + TravelToNode( targetNode ); + + if( isdefined( targetNode.script_airspeed ) && isdefined( targetNode.script_accel ) ) + { + heli_speed = targetNode.script_airspeed; + heli_accel = targetNode.script_accel; + } + else + { + heli_speed = 150+randomInt(20); + heli_accel = 40+randomInt(10); + } + + self SetSpeed( heli_speed, heli_accel ); + self setvehgoalpos( targetNode.origin + ( 0, 0, ( 2000 ) ), 1 ); + self setgoalyaw( targetNode.angles[ 1 ] + ( 0 ) ); + } + + if( ( 0 ) != 0 ) + { + self waittill( "near_goal" ); + waitTime = ( 0 ); + } + else if( !isdefined( targetNode.script_delay ) ) + { + self waittill( "near_goal" ); + waitTime = 10 + randomInt( 5 ); + } + else + { + self waittillmatch( "goal" ); + waitTime = targetNode.script_delay; + } + + if( firstpass ) + { + self.killstreak_duration = ( ( self.killstreak_timer_start_using_hacked_time === true ) ? self killstreak_hacking::get_hacked_timeout_duration_ms() : ( 60000 ) ); + self.killstreak_end_time = GetTime() + self.killstreak_duration; + self.killstreakEndTime = int( self.killstreak_end_time ); + self thread killstreaks::WaitForTimeout( "helicopter_gunner", self.killstreak_duration, &OnTimeoutCallback, "delete", "death" ); + self.killstreak_timer_started = true; + self UpdateDrivableTimeForAllOccupants( self.killstreak_duration, self.killstreak_end_time ); + + firstpass = false; + } + + wait( waitTime ); + } +} + +function UpdateDrivableTimeForAllOccupants( duration_ms, end_time_ms ) // self == vtol +{ + if ( isdefined( self.owner ) ) + { + self.owner vehicle::set_vehicle_drivable_time( duration_ms, end_time_ms ); + } + + for( i = 0; i < ( 2 ); i++ ) + { + if( isdefined( self.assistants[i].occupant ) && !self.assistants[i].destroyed ) + { + self.assistants[i].occupant vehicle::set_vehicle_drivable_time( duration_ms, end_time_ms ); + } + } +} + +function WatchLocationChangeThread( destNodes ) +{ + player = self; + + player endon( "disconnect" ); + player endon( "gunner_left" ); + + helicopter = level.vtol; + + helicopter endon ( "delete" ); + helicopter endon ( "vtol_shutdown" ); + + player.moves = 0; + helicopter waittill ( "near_goal" ); + helicopter waittill ( "goal" ); + + while( true ) + { + if( self SecondaryOffhandButtonPressed() ) + { + player.moves++; + player thread SetPlayerMovedRecentlyThread(); + node = self UpdateAreaNodes( destNodes, true ); + helicopter.currentNode = node; + targetNode = getEnt( node.target, "targetname" ); + + player playlocalsound ( "mpl_cgunner_nav" ); + helicopter TravelToNode( targetNode ); + + if( isdefined( targetNode.script_airspeed ) && isdefined( targetNode.script_accel ) ) + { + heli_speed = targetNode.script_airspeed; + heli_accel = targetNode.script_accel; + } + else + { + heli_speed = 80+randomInt(20); + heli_accel = 40+randomInt(10); + } + + helicopter SetSpeed( heli_speed, heli_accel ); + helicopter setvehgoalpos( targetNode.origin + ( 0, 0, ( 2000 ) ), 1 ); + helicopter setgoalyaw( targetNode.angles[ 1 ] + ( 0 ) ); + + helicopter waittill( "goal" ); + + // wait for the button to release: + while ( self SecondaryOffhandButtonPressed() ) + { + {wait(.05);}; + } + } + + {wait(.05);}; + } +} + +function SetPlayerMovedRecentlyThread() +{ + player = self; + + player endon( "disconnect" ); + player endon( "gunner_left" ); + + helicopter = level.vtol; + + helicopter endon ( "delete" ); + helicopter endon ( "vtol_shutdown" ); + + myMove = self.moves; + level.vtol.playerMovedRecently = true; + wait ( 100 ); + + //only remove the flag if I am still the most recent move + if( myMove == self.moves && isdefined( level.vtol ) ) + { + level.vtol.playerMovedRecently = false; + } +} + +function UpdateAreaNodes( areaNodes, forceMove ) +{ + validEnemies = []; + + foreach( node in areaNodes ) + { + node.validPlayers = []; + node.nodeScore = 0; + } + + foreach( player in level.players ) + { + if( !isAlive( player ) ) + { + continue; + } + + if( player.team == self.team ) + { + continue; + } + + foreach( node in areaNodes ) + { + if( distanceSquared( player.origin, node.origin ) > 1048576 ) + { + continue; + } + + node.validPlayers[node.validPlayers.size] = player; + } + } + + bestNode = undefined; + foreach ( node in areaNodes ) + { + if( isdefined( level.vtol.currentNode ) && ( node == level.vtol.currentNode ) ) + { + continue; + } + + heliNode = getEnt( node.target, "targetname" ); + foreach( player in node.validPlayers ) + { + node.nodeScore += 1; + + if( bulletTracePassed( player.origin + (0,0,32), heliNode.origin, false, player ) ) + { + node.nodeScore += 3; + } + } + + if( forceMove && ( distance( level.vtol.origin, heliNode.origin ) < 200 ) ) + { + node.nodeScore = -1; + } + + if( !isdefined( bestNode ) || ( node.nodeScore > bestNode.nodeScore ) ) + { + bestNode = node; + } + } + + return bestNode; +} + +function TravelToNode( goalNode ) +{ + originOffets = GetOriginOffsets( goalNode ); + + if( originOffets["start"] != self.origin ) + { + if( isdefined( goalNode.script_airspeed ) && isdefined( goalNode.script_accel ) ) + { + heli_speed = goalNode.script_airspeed; + heli_accel = goalNode.script_accel; + } + else + { + heli_speed = 30 + randomInt(20); + heli_accel = 15 + randomInt(15); + } + + self SetSpeed( heli_speed, heli_accel ); + self setvehgoalpos( originOffets["start"] + (0,0,30), 0 ); + self setgoalyaw( goalNode.angles[ 1 ] + ( 0 ) ); + + self waittill ( "goal" ); + } + + if( originOffets["end"] != goalNode.origin ) + { + if( isdefined( goalNode.script_airspeed ) && isdefined( goalNode.script_accel ) ) + { + heli_speed = goalNode.script_airspeed; + heli_accel = goalNode.script_accel; + } + else + { + heli_speed = 30+randomInt(20); + heli_accel = 15+randomInt(15); + } + + self SetSpeed( heli_speed, heli_accel ); + self setvehgoalpos( originOffets["end"] + (0,0,30), 0 ); + self setgoalyaw( goalNode.angles[ 1 ] + ( 0 ) ); + + self waittill ( "goal" ); + } +} + +function GetOriginOffsets( goalNode ) +{ + startOrigin = self.origin; + endOrigin = goalNode.origin; + + numTraces = 0; + maxTraces = 40; + + traceOffset = (0,0,-196); + + traceOrigin = BulletTrace( startOrigin+traceOffset, endOrigin+traceOffset, false, self ); + + while( DistanceSquared( traceOrigin[ "position" ], endOrigin+traceOffset ) > 10 && numTraces < maxTraces ) + { + /#println( "trace failed: " + DistanceSquared( traceOrigin[ "position" ], endOrigin+traceOffset ) );#/ + + if( startOrigin[2] < endOrigin[2] ) + { + startOrigin += (0,0,128); + } + else if( startOrigin[2] > endOrigin[2] ) + { + endOrigin += (0,0,128); + } + else + { + startOrigin += (0,0,128); + endOrigin += (0,0,128); + } + + numTraces++; + traceOrigin = BulletTrace( startOrigin+traceOffset, endOrigin+traceOffset, false, self ); + } + + offsets = []; + offsets["start"] = startOrigin; + offsets["end"] = endOrigin; + return offsets; +} \ No newline at end of file diff --git a/mp/killstreaks/_killstreak_bundles.gsc b/mp/killstreaks/_killstreak_bundles.gsc new file mode 100644 index 0000000..a2b23a6 --- /dev/null +++ b/mp/killstreaks/_killstreak_bundles.gsc @@ -0,0 +1,471 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_killstreaks; + + + + + + + +#namespace killstreak_bundles; + +function register_killstreak_bundle( killstreakType ) +{ + level.killstreakBundle[killstreakType] = struct::get_script_bundle( "killstreak", "killstreak_" + killstreakType ); + level.killstreakBundle["inventory_" + killstreakType] = level.killstreakBundle[killstreakType]; + level.killstreakMaxHealthFunction = &killstreak_bundles::get_max_health; + assert( isdefined( level.killstreakBundle[killstreakType] ) ); +} + +function get_bundle( killstreak ) +{ + if( killstreak.archetype === "raps" ) + return level.killstreakBundle["raps_drone"]; + else + return level.killstreakBundle[killstreak.killstreakType]; +} + +function get_hack_timeout() +{ + killstreak = self; + bundle = get_bundle( killstreak ); + + return bundle.ksHackTimeout; +} + +function get_hack_protection() +{ + killstreak = self; + hackedProtection = false; + + bundle = get_bundle( killstreak ); + if ( isdefined( bundle.ksHackProtection ) ) + { + hackedProtection = bundle.ksHackProtection; + } + + return hackedProtection; +} + +function get_hack_tool_inner_time() +{ + killstreak = self; + hackToolInnerTime = 10000; + + bundle = get_bundle( killstreak ); + if ( isdefined( bundle.ksHackToolInnerTime ) ) + { + hackToolInnerTime = bundle.ksHackToolInnerTime; + } + + return hackToolInnerTime; +} + +function get_hack_tool_outer_time() +{ + killstreak = self; + hackToolOuterTime = 10000; + + bundle = get_bundle( killstreak ); + if ( isdefined( bundle.ksHackToolOuterTime ) ) + { + hackToolOuterTime = bundle.ksHackToolOuterTime; + } + + return hackToolOuterTime; +} + +function get_hack_tool_inner_radius() +{ + killstreak = self; + hackedToolInnerRadius = 10000; + + bundle = get_bundle( killstreak ); + if ( isdefined( bundle.ksHackToolInnerRadius ) ) + { + hackedToolInnerRadius = bundle.ksHackToolInnerRadius; + } + + return hackedToolInnerRadius; +} + + +function get_hack_tool_outer_radius() +{ + killstreak = self; + hackedToolOuterRadius = 10000; + + bundle = get_bundle( killstreak ); + if ( isdefined( bundle.ksHackToolOuterRadius ) ) + { + hackedToolOuterRadius = bundle.ksHackToolOuterRadius; + } + + return hackedToolOuterRadius; +} + + +function get_lost_line_of_sight_limit_msec() +{ + killstreak = self; + hackedToolLostLineOfSightLimitMs = 1000; + + bundle = get_bundle( killstreak ); + if ( isdefined( bundle.ksHackToolLostLineOfSightLimitMs ) ) + { + hackedToolLostLineOfSightLimitMs = bundle.ksHackToolLostLineOfSightLimitMs; + } + + return hackedToolLostLineOfSightLimitMs; +} + + +function get_hack_tool_no_line_of_sight_time() +{ + killstreak = self; + hackToolNoLineOfSightTime = 1000; + + bundle = get_bundle( killstreak ); + if ( isdefined( bundle.ksHackToolNoLineOfSightTime ) ) + { + hackToolNoLineOfSightTime = bundle.ksHackToolNoLineOfSightTime; + } + + return hackToolNoLineOfSightTime; +} + + + +function get_hack_scoreevent() +{ + killstreak = self; + hackedScoreEvent = undefined; + bundle = get_bundle( killstreak ); + if ( isdefined( bundle.ksHackScoreEvent ) ) + { + hackedScoreEvent = bundle.ksHackScoreEvent; + } + + return hackedScoreEvent; +} + +function get_hack_fx() +{ + killstreak = self; + hackFX = ""; + + bundle = get_bundle( killstreak ); + if ( isdefined( bundle.ksHackFX ) ) + { + hackFX = bundle.ksHackFX; + } + + return hackFX; +} + +function get_hack_loop_fx() +{ + killstreak = self; + hackLoopFX = ""; + + bundle = get_bundle( killstreak ); + if ( isdefined( bundle.ksHackLoopFX ) ) + { + hackLoopFX = bundle.ksHackLoopFX; + } + + return hackLoopFX; +} + +function get_max_health( killstreakType ) +{ + bundle = level.killstreakBundle[killstreakType]; + + return bundle.ksHealth; +} + +function get_low_health( killstreakType ) +{ + bundle = level.killstreakBundle[killstreakType]; + + return bundle.ksLowHealth; +} + +function get_hacked_health( killstreakType ) +{ + bundle = level.killstreakBundle[killstreakType]; + + return bundle.ksHackedHealth; +} + +function get_shots_to_kill( weapon, meansOfDeath, bundle ) +{ + shotsToKill = undefined; + + switch( weapon.rootweapon.name ) + { + case "remote_missile_missile": + shotsToKill = bundle.ksRemote_missile_missile; + break; + case "hero_annihilator": + shotsToKill = bundle.ksHero_annihilator; + break; + case "hero_armblade": + shotsToKill = bundle.ksHero_armblade; + break; + case "hero_bowlauncher": + case "hero_bowlauncher2": + case "hero_bowlauncher3": + case "hero_bowlauncher4": + if ( meansOfDeath == "MOD_PROJECTILE_SPLASH" || meansOfDeath == "MOD_PROJECTILE" ) + { + shotsToKill = bundle.ksHero_bowlauncher; + } + else + { + shotstoKill = -1; + } + break; + case "hero_gravityspikes": + shotsToKill = bundle.ksHero_gravityspikes; + break; + case "hero_lightninggun": + shotsToKill = bundle.ksHero_lightninggun; + break; + case "hero_minigun": + case "hero_minigun_body3": + shotsToKill = bundle.ksHero_minigun; + break; + case "hero_pineapplegun": + shotsToKill = bundle.ksHero_pineapplegun; + break; + case "hero_firefly_swarm": + shotsToKill = (isdefined(bundle.ksHero_firefly_swarm)?bundle.ksHero_firefly_swarm:0) * 4; + break; + case "dart_blade": + case "dart_turret": + shotsToKill = bundle.ksDartsToKill; + break; + case "gadget_heat_wave": + shotsToKill = bundle.ksHero_heatwave; + break; + } + + return (isdefined(shotsToKill)?shotsToKill:0); +} + +function get_emp_grenade_damage( killstreakType, maxhealth ) +{ + // weapon_damage returns as undefined if it is not handled here + + emp_weapon_damage = undefined; + + if ( isdefined( level.killstreakBundle[killstreakType] ) ) + { + bundle = level.killstreakBundle[killstreakType]; + + empGrenadesToKill = (isdefined(bundle.ksEmpGrenadesToKill)?bundle.ksEmpGrenadesToKill:0); + + if ( empGrenadesToKill == 0 ) + { + // not handled here + } + else if ( empGrenadesToKill > 0 ) + { + emp_weapon_damage = maxhealth / empGrenadesToKill + 1; + } + else + { + // immune + emp_weapon_damage = 0; + } + } + + return emp_weapon_damage; +} + +function get_weapon_damage( killstreakType, maxhealth, attacker, weapon, type, damage, flags, chargeShotLevel ) +{ + // weapon_damage returns as undefined if it is not handled here + + weapon_damage = undefined; + + if ( isdefined( level.killstreakBundle[killstreakType] ) ) + { + bundle = level.killstreakBundle[killstreakType]; + + if ( isdefined( weapon ) ) + { + shotsToKill = get_shots_to_kill( weapon, type, bundle ); + + if ( shotsToKill == 0 ) + { + // not handled here + } + else if ( shotsToKill > 0 ) + { + if ( isdefined( chargeShotLevel ) && chargeShotLevel > 0 ) + { + // chargeShotLevel should be between 0 and 1. + // 1 = full charge + // > 0 = fraction of charge + shotsToKill = shotsToKill / chargeShotLevel; + } + + weapon_damage = maxhealth / shotsToKill + 1; + } + else + { + // immune + weapon_damage = 0; + } + + } + + if ( !isdefined( weapon_damage ) ) + { + if ( type == "MOD_RIFLE_BULLET" || type == "MOD_PISTOL_BULLET" || type == "MOD_HEAD_SHOT" ) + { + hasArmorPiercing = isdefined( attacker ) && isPlayer( attacker ) && attacker HasPerk( "specialty_armorpiercing" ); + + clipsToKill = (isdefined(bundle.ksClipsToKill)?bundle.ksClipsToKill:0); + if( clipsToKill == -1 ) + { + // immune + weapon_damage = 0; + } + else if ( hasArmorPiercing && self.aitype !== "spawner_bo3_robot_grunt_assault_mp_escort" ) // HACK TU4 FFOTD DT 150126 - Don't apply FMJ damage to the escort robot + { + weapon_damage = damage + int( damage * level.cac_armorpiercing_data); + } + + if ( weapon.weapClass == "spread" ) + { + ksShotgunMultiplier = (isdefined(bundle.ksShotgunMultiplier)?bundle.ksShotgunMultiplier:1); + + if ( ksShotgunMultiplier == 0 ) + { + // not handled here + } + else if ( ksShotgunMultiplier > 0 ) + { + weapon_damage = (isdefined(weapon_damage)?weapon_damage:damage) * ksShotgunMultiplier; + } + } + } + else if ( ( type == "MOD_PROJECTILE" || type == "MOD_EXPLOSIVE" ) + && ( !isdefined( weapon.isEmpKillstreak ) || !weapon.isEmpKillstreak ) + && ( weapon.statIndex != level.weaponPistolEnergy.statIndex ) + && ( weapon.statIndex != level.weaponSpecialCrossbow.statIndex ) + && ( weapon.statIndex != level.weaponSmgNailGun.statIndex ) + && ( weapon.statIndex != level.weaponBouncingBetty.statIndex ) ) + { + if ( weapon.statIndex == level.weaponShotgunEnergy.statIndex ) + { + shotgunEnergyToKill = (isdefined(bundle.ksShotgunEnergyToKill)?bundle.ksShotgunEnergyToKill:0); + + if ( shotgunEnergyToKill == 0 ) + { + // not handled here + } + else if ( shotgunEnergyToKill > 0 ) + { + weapon_damage = maxhealth / shotgunEnergyToKill + 1; + } + else + { + // immune + weapon_damage = 0; + } + } + else + { + rocketsToKill = (isdefined(bundle.ksRocketsToKill)?bundle.ksRocketsToKill:0); + + if ( rocketsToKill == 0 ) + { + // not handled here + } + else if ( rocketsToKill > 0 ) + { + if ( weapon.rootweapon.name == "launcher_multi" ) + { + rocketsToKill *= 2; + } + + weapon_damage = maxhealth / rocketsToKill + 1; + } + else + { + // immune + weapon_damage = 0; + } + } + } + else if (( type == "MOD_GRENADE" || type == "MOD_GRENADE_SPLASH" ) && ( !isdefined( weapon.isEmpKillstreak ) || !weapon.isEmpKillstreak ) ) + { + grenadeDamageMultiplier = (isdefined(bundle.ksGrenadeDamageMultiplier)?bundle.ksGrenadeDamageMultiplier:0); + + if ( grenadeDamageMultiplier == 0 ) + { + // not handled here + } + else if ( grenadeDamageMultiplier > 0 ) + { + weapon_damage = damage * grenadeDamageMultiplier; + } + else + { + // immune + weapon_damage = 0; + } + } + else if ( type == "MOD_MELEE_WEAPON_BUTT" || type == "MOD_MELEE" ) + { + ksMeleeDamageMultiplier = (isdefined(bundle.ksMeleeDamageMultiplier)?bundle.ksMeleeDamageMultiplier:0); + + if ( ksMeleeDamageMultiplier == 0 ) + { + // not handled here + } + else if ( ksMeleeDamageMultiplier > 0 ) + { + weapon_damage = damage * ksMeleeDamageMultiplier; + } + else + { + // immune + weapon_damage = 0; + } + } + else if ( type == "MOD_PROJECTILE_SPLASH" ) + { + ksProjectileSpashMultiplier = (isdefined(bundle.ksProjectileSpashMultiplier)?bundle.ksProjectileSpashMultiplier:0); + + if ( ksProjectileSpashMultiplier == 0 ) + { + // not handled here + } + else if ( ksProjectileSpashMultiplier > 0 ) + { + weapon_damage = damage * ksProjectileSpashMultiplier; + } + else + { + // immune + weapon_damage = 0; + } + } + } + } + + return weapon_damage; +} \ No newline at end of file diff --git a/mp/killstreaks/_killstreak_detect.csc b/mp/killstreaks/_killstreak_detect.csc new file mode 100644 index 0000000..78c463d --- /dev/null +++ b/mp/killstreaks/_killstreak_detect.csc @@ -0,0 +1,297 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\duplicaterender_mgr; +#using scripts\shared\filter_shared; +#using scripts\shared\util_shared; + + + + + + + + +#using scripts\shared\system_shared; + +#namespace killstreak_detect; + +function autoexec __init__sytem__() { system::register("killstreak_detect",&__init__,undefined,undefined); } + + +function __init__() +{ + callback::on_localplayer_spawned( &watch_killstreak_detect_perks_changed ); + + clientfield::register( "scriptmover", "enemyvehicle", 1, 2, "int", &enemyScriptMoverVehicle_changed, !true, !true ); + clientfield::register( "vehicle", "enemyvehicle", 1, 2, "int", &enemyvehicle_changed, !true, true ); + clientfield::register( "helicopter", "enemyvehicle", 1, 2, "int", &enemyvehicle_changed, !true, true ); + clientfield::register( "missile", "enemyvehicle", 1, 2, "int", &enemyMissileVehicle_changed, !true, true ); + clientfield::register( "actor", "enemyvehicle", 1, 2, "int", &enemyvehicle_changed, !true, true ); + + clientfield::register( "vehicle", "vehicletransition", 1, 1, "int", &vehicle_transition, !true, true ); + + if(!isdefined(level.enemyvehicles))level.enemyvehicles=[]; + if(!isdefined(level.enemymissiles))level.enemymissiles=[]; + + level.emp_killstreaks = []; +} + +function vehicle_transition( local_client_num, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + player = GetLocalPlayer( local_client_num ); + friend = self util::friend_not_foe( local_client_num, true ); + + if( friend && isdefined( player ) && player duplicate_render::show_friendly_outlines( local_client_num ) ) + { + showOutlines = !(self IsLocalClientDriver( local_client_num ) ); + self duplicate_render::set_item_friendly_vehicle( local_client_num, showOutlines ); + } +} + +function should_set_compass_icon( local_client_num ) +{ + local_player = GetLocalPlayer( local_client_num ); + + return ( isdefined( local_player ) && isdefined( self.team ) && ( local_player.team === self.team || local_player HasPerk( local_client_num, "specialty_showenemyvehicles" ) ) ); +} + +function enemyScriptMoverVehicle_changed( local_client_num, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( isdefined( level.scriptMoverCompassIcons ) && isdefined( self.model ) ) + { + if ( isdefined( level.scriptMoverCompassIcons[self.model] ) ) + { + if ( self should_set_compass_icon( local_client_num ) ) + { + self setCompassIcon( level.scriptMoverCompassIcons[self.model] ); + } + } + } + + enemyvehicle_changed( local_client_num, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ); +} + +function enemyMissileVehicle_changed( local_client_num, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( isdefined( level.missileCompassIcons ) && isdefined( self.weapon ) ) + { + if ( isdefined( level.missileCompassIcons[self.weapon] ) ) + { + if ( self should_set_compass_icon( local_client_num ) ) + { + self setCompassIcon( level.missileCompassIcons[self.weapon] ); + } + } + } + + enemymissile_changed( local_client_num, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ); +} + +function enemymissile_changed( local_client_num, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self updateTeamMissiles( local_client_num, newVal ); + self util::add_remove_list( level.enemymissiles, newVal ); + self updateEnemyMissiles( local_client_num, newVal ); +} + +function enemyvehicle_changed( local_client_num, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self updateTeamVehicles( local_client_num, newVal ); + self util::add_remove_list( level.enemyvehicles, newVal ); + + self updateEnemyVehicles( local_client_num, newVal ); + + if ( isdefined( self.model ) && self.model == "wpn_t7_turret_emp_core" && self.type === "vehicle" ) + { + if ( !isdefined( level.emp_killstreaks ) ) level.emp_killstreaks = []; else if ( !IsArray( level.emp_killstreaks ) ) level.emp_killstreaks = array( level.emp_killstreaks ); level.emp_killstreaks[level.emp_killstreaks.size]=self;; + } +} + +function updateTeamVehicles( local_client_num, newVal ) +{ + self checkTeamVehicles( local_client_num ); +} + +function updateTeamMissiles( local_client_num, newVal ) +{ + self checkTeamMissiles( local_client_num ); +} + +function updateEnemyVehicles( local_client_num, newVal ) +{ + if ( !( isdefined( self ) ) ) + { + return; + } + watcher = GetLocalPlayer( local_client_num ); + friend = self util::friend_not_foe( local_client_num, true ); + + self duplicate_render::set_dr_flag( "enemyvehicle_fb", !friend ); + + self duplicate_render::set_item_enemy_vehicle( local_client_num, false ); + self duplicate_render::set_item_friendly_vehicle( local_client_num, false ); + self.isEnemyVehicle = false; + if ( !friend && IsDefined( watcher ) && watcher HasPerk( local_client_num, "specialty_showenemyvehicles" ) ) + { + if ( !isdefined( self.isbreachingfirewall ) || self.isbreachingfirewall == false ) + { + self duplicate_render::set_item_enemy_vehicle( local_client_num, newVal ); + } + self.isEnemyVehicle = true; + self duplicate_render::set_item_friendly_vehicle( local_client_num, false ); + } + else if( ( friend === true ) && isDefined( watcher ) && watcher duplicate_render::show_friendly_outlines(local_client_num) ) + { + driver = ( self.type === "vehicle" ) && self IsLocalClientDriver( local_client_num ); + showOutlines = ( driver === false ) && ( newVal === 1 || newVal === 2 ); + self duplicate_render::set_item_friendly_vehicle( local_client_num, showOutlines ); + } + else + { + self duplicate_render::set_item_friendly_vehicle( local_client_num, false ); + } + + if ( newVal == 2 ) + { + //self duplicate_render::set_hacker_tool_hacked( local_client_num, true ); + self.killstreakIsHacked = true; + } + + self duplicate_render::update_dr_filters( local_client_num ); +} + +function updateEnemyMissiles( local_client_num, newVal ) +{ + if ( !( isdefined( self ) ) ) + { + return; + } + watcher = GetLocalPlayer( local_client_num ); + friend = self util::friend_not_foe( local_client_num, true ); + + self duplicate_render::set_dr_flag( "enemyvehicle_fb", !friend ); + + self duplicate_render::set_item_enemy_explosive( local_client_num, false ); + self duplicate_render::set_item_friendly_explosive( local_client_num, false ); + self.isEnemyVehicle = false; + if ( !friend && IsDefined( watcher ) && watcher HasPerk( local_client_num, "specialty_showenemyvehicles" ) ) + { + if ( !isdefined( self.isbreachingfirewall ) || self.isbreachingfirewall == false ) + { + self duplicate_render::set_item_enemy_explosive( local_client_num, newVal ); + } + self.isEnemyVehicle = true; + self duplicate_render::set_item_friendly_explosive( local_client_num, false ); + } + else if( ( friend === true ) && isDefined( watcher ) && watcher duplicate_render::show_friendly_outlines(local_client_num) ) + { + showOutlines = ( newVal === 1 || newVal === 2 ); + self duplicate_render::set_item_friendly_explosive( local_client_num, showOutlines ); + } + else + { + self duplicate_render::set_item_friendly_explosive( local_client_num, false ); + } + + if ( newVal == 2 ) + { + //self duplicate_render::set_hacker_tool_hacked( local_client_num, true ); + self.killstreakIsHacked = true; + } + + self duplicate_render::update_dr_filters( local_client_num ); +} + +function watch_killstreak_detect_perks_changed(local_client_num) +{ + if( self != GetLocalPlayer( local_client_num ) ) + return; + + self notify( "watch_killstreak_detect_perks_changed" ); + self endon( "watch_killstreak_detect_perks_changed" ); + self endon( "death" ); + self endon( "disconnect" ); + self endon( "entityshutdown" ); + + while(IsDefined(self)) + { + {wait(.016);}; + util::clean_deleted(level.enemyvehicles); + util::clean_deleted(level.enemymissiles); + array::thread_all( level.enemyvehicles, &updateEnemyVehicles, local_client_num, 1 ); + array::thread_all( level.enemymissiles, &updateEnemyMissiles, local_client_num, 1 ); + self waittill("perks_changed"); + } +} + + +function checkTeamVehicles( localClientNum ) +{ + if ( !isdefined ( self.owner ) || !isdefined ( self.owner.team ) ) + { + return; + } + + if ( !isdefined( self.vehicleOldTeam ) ) + { + self.vehicleOldTeam = self.team; + } + + if ( !isdefined( self.vehicleOldOwnerTeam ) ) + { + self.vehicleOldOwnerTeam = self.owner.team; + } + + watcher = GetLocalPlayer( localClientNum ); + + if ( !isdefined( self.vehicleOldWatcherTeam ) ) + { + self.vehicleOldWatcherTeam = watcher.team; + } + + if ( self.vehicleOldTeam != self.team || self.vehicleOldOwnerTeam != self.owner.team || self.vehicleOldWatcherTeam != watcher.team) + { + self.vehicleOldTeam = self.team; + self.vehicleOldOwnerTeam = self.owner.team; + self.vehicleOldWatcherTeam = watcher.team; + + self notify( "team_changed" ); + } +} + +function checkTeamMissiles( localClientNum ) +{ + if ( !isdefined ( self.owner ) || !isdefined ( self.owner.team ) ) + { + return; + } + + if ( !isdefined( self.missileOldTeam ) ) + { + self.missileOldTeam = self.team; + } + + if ( !isdefined( self.missileOldOwnerTeam ) ) + { + self.missileOldOwnerTeam = self.owner.team; + } + + watcher = GetLocalPlayer( localClientNum ); + + if ( !isdefined( self.missileOldWatcherTeam ) ) + { + self.missileOldWatcherTeam = watcher.team; + } + + if ( self.missileOldTeam != self.team || self.missileOldOwnerTeam != self.owner.team || self.missileOldWatcherTeam != watcher.team) + { + self.missileOldTeam = self.team; + self.missileOldOwnerTeam = self.owner.team; + self.missileOldWatcherTeam = watcher.team; + + self notify( "team_changed" ); + } +} diff --git a/mp/killstreaks/_killstreak_detect.gsc b/mp/killstreaks/_killstreak_detect.gsc new file mode 100644 index 0000000..0fc674b --- /dev/null +++ b/mp/killstreaks/_killstreak_detect.gsc @@ -0,0 +1,48 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\weapons\_proximity_grenade; +#using scripts\shared\util_shared; + +#using scripts\mp\killstreaks\_killstreak_hacking; + + + + +#namespace killstreak_detect; + +function autoexec __init__sytem__() { system::register("killstreak_detect",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "vehicle", "enemyvehicle", 1, 2, "int" ); + clientfield::register( "scriptmover", "enemyvehicle", 1, 2, "int" ); + clientfield::register( "helicopter", "enemyvehicle", 1, 2, "int" ); + clientfield::register( "missile", "enemyvehicle", 1, 2, "int" ); + clientfield::register( "actor", "enemyvehicle", 1, 2, "int" ); + clientfield::register( "vehicle", "vehicletransition", 1, 1, "int" ); +} + + +function killstreakTargetSet( killstreakEntity, offset ) +{ + if ( !isdefined( offset ) ) + { + offset = ( 0, 0, 0 ); + } + Target_Set( killstreakEntity, offset ); +/# + killstreakEntity thread killstreak_hacking::killstreak_switch_team( killstreakEntity.owner ); +#/ +} + + +function killstreakTargetClear( killstreakEntity ) +{ + Target_Remove( killstreakEntity ); +/# + killstreakEntity thread killstreak_hacking::killstreak_switch_team_end(); +#/ +} diff --git a/mp/killstreaks/_killstreak_hacking.gsc b/mp/killstreaks/_killstreak_hacking.gsc new file mode 100644 index 0000000..be651a0 --- /dev/null +++ b/mp/killstreaks/_killstreak_hacking.gsc @@ -0,0 +1,236 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_killstreak_bundles; + + + + + + +#namespace killstreak_hacking; + +function enable_hacking( killstreakName, preHackFunction, postHackFunction ) +{ + killstreak = self; + + level.challenge_scorestreaksenabled = true; + killstreak.challenge_isScoreStreak = true; + killstreak.killstreak_hackedCallback = &_hacked_callback; + killstreak.killstreakPreHackFunction = preHackFunction; + killstreak.killstreakPostHackFunction = postHackFunction; + killstreak.hackerToolInnerTimeMs = killstreak killstreak_bundles::get_hack_tool_inner_time(); + killstreak.hackerToolOuterTimeMs = killstreak killstreak_bundles::get_hack_tool_outer_time(); + killstreak.hackerToolInnerRadius = killstreak killstreak_bundles::get_hack_tool_inner_radius(); + killstreak.hackerToolOuterRadius = killstreak killstreak_bundles::get_hack_tool_outer_radius(); + killstreak.hackerToolRadius = killstreak.hackerToolOuterRadius; + killstreak.killstreakHackLoopFX = killstreak killstreak_bundles::get_hack_loop_fx(); + killstreak.killstreakHackFX = killstreak killstreak_bundles::get_hack_fx(); + killstreak.killstreakHackScoreEvent = killstreak killstreak_bundles::get_hack_scoreevent(); + killstreak.killstreakHackLostLineOfSightLimitMs = killstreak killstreak_bundles::get_lost_line_of_sight_limit_msec(); + killstreak.killstreakHackLostLineOfSightTimeMs = killstreak killstreak_bundles::get_hack_tool_no_line_of_sight_time(); + + killstreak.killstreak_hackedProtection = killstreak killstreak_bundles::get_hack_protection(); +} + +function disable_hacking() +{ + killstreak = self; + + killstreak.killstreak_hackedCallback = undefined; +} + +function hackerFX() +{ + killstreak = self; + + if ( isdefined( killstreak.killstreakHackFX ) && killstreak.killstreakHackFX != "" ) + { + playfxontag( killstreak.killstreakHackFX, killstreak, "tag_origin" ); + } +} + +function hackerLoopFX() +{ + killstreak = self; + + if ( isdefined( killstreak.killstreakLoopHackFX ) && killstreak.killstreakLoopHackFX != "" ) + { + playfxontag( killstreak.killstreakLoopHackFX, killstreak, "tag_origin" ); + } +} + +function private _hacked_callback( hacker ) +{ + killstreak = self; + + originalOwner = killstreak.owner; + + if ( isdefined( killstreak.killstreakHackScoreEvent ) ) + { + scoreevents::processscoreevent( killstreak.killstreakHackScoreEvent, hacker, originalOwner, level.weaponHackerTool ); + } + + if ( isdefined( killstreak.killstreakPreHackFunction ) ) + { + killstreak thread [[killstreak.killstreakPreHackFunction]]( hacker ); + } + + killstreak killstreaks::configure_team_internal( hacker, true ); + killstreak clientfield::set( "enemyvehicle", 2 ); + if ( isdefined ( killstreak.killstreakHackFX ) ) + { + killstreak thread hackerFX(); + } + if ( isdefined ( killstreak.killstreakHackLoopFX ) ) + { + killstreak thread hackerLoopFX(); + } + + if ( isdefined( killstreak.killstreakPostHackFunction ) ) + { + killstreak thread [[killstreak.killstreakPostHackFunction]]( hacker ); + } + + killstreakType = killstreak.killstreakType; + if ( isdefined ( killstreak.hackedKillstreakRef ) ) + { + killstreakType = killstreak.hackedKillstreakRef; + } + + level thread popups::DisplayKillstreakHackedTeamMessageToAll( killstreakType, hacker ); + + killstreak _update_health( hacker ); +} + +function override_hacked_killstreak_reference( KillstreakRef ) +{ + killstreak = self; + + killstreak.hackedKillstreakRef = KillstreakRef; +} + +function get_hacked_timeout_duration_ms() +{ + killstreak = self; + + timeout = killstreak killstreak_bundles::get_hack_timeout(); + + + if ( !isdefined( timeout ) || timeout <= 0 ) + { +/# + assertmsg( "get_hacked_timeout_duration_ms(): Set \"" + killstreak.killstreakType + "\" to a greater than zero value, in the killstreaks GDT" ); +#/ + return; + } + + return timeout * 1000; +} + +function set_vehicle_drivable_time_starting_now( killstreak, duration_ms = (-1) ) // self == player +{ + if ( duration_ms == -1 ) + duration_ms = killstreak get_hacked_timeout_duration_ms(); + + return self vehicle::set_vehicle_drivable_time_starting_now( duration_ms ); +} + +function _update_health( hacker ) +{ + killstreak = self; + + if ( isdefined ( killstreak.hackedHealthUpdateCallback ) ) + { + killstreak [[ killstreak.hackedHealthUpdateCallback ]]( hacker ); + } + else if ( IsSentient( killstreak ) ) + { + hackedHealth = killstreak_bundles::get_hacked_health( killstreak.killstreakType ); + assert( isdefined( hackedHealth ) ); + if ( self.health > hackedhealth ) + { + self.health = hackedhealth; + } + } + else + { + /#hacker iprintlnbold( "Hacked but no update of health occured" );#/ + } +} + +/# +function killstreak_switch_team_end() +{ + killstreakEntity = self; + killstreakEntity notify( "killstreak_switch_team_end" ); +} + +function killstreak_switch_team( owner ) +{ + killstreakEntity = self; + killstreakEntity notify( "killstreak_switch_team_singleton" ); + killstreakEntity endon( "killstreak_switch_team_singleton" ); + killstreakEntity endon( "death" ); + + //Init my dvar + SetDvar("scr_killstreak_switch_team", ""); + + while( true ) + { + wait(0.5); + + //Grab my dvar every .5 seconds in the form of an int + devgui_int = GetDvarint( "scr_killstreak_switch_team"); + + //"" returns as zero with GetDvarInt + if(devgui_int != 0) + { + // spawn a larry to be the opposing team + team = "autoassign"; + + if( isdefined( level.getEnemyTeam ) && isdefined( owner ) && isdefined( owner.team ) ) + { + team = [[level.getEnemyTeam]]( owner.team ); + } + + if ( isdefined( level.devOnGetOrMakeBot ) ) + { + player = [[level.devOnGetOrMakeBot]]( team ); + } + + if( !isdefined( player ) ) + { + println("Could not add test client"); + wait 1; + continue; + } + + if ( !isdefined( killstreakEntity.killstreak_hackedCallback ) ) + { +/# + iprintlnbold( "missing hacked callback" ); +#/ + return; + } + killstreakEntity notify( "killstreak_hacked", player ); + killstreakEntity.previouslyHacked = true; + killstreakEntity [[ killstreakEntity.killstreak_hackedCallback ]]( player ); + + wait( 0.5 ); + SetDvar("scr_killstreak_switch_team", "0"); + return; + } + } +} +#/ + diff --git a/mp/killstreaks/_killstreak_weapons.gsc b/mp/killstreaks/_killstreak_weapons.gsc new file mode 100644 index 0000000..c300ba1 --- /dev/null +++ b/mp/killstreaks/_killstreak_weapons.gsc @@ -0,0 +1,552 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_weapons; + + + +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_loadout; + + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_supplydrop; + +#precache( "material", "hud_ks_minigun" ); +#precache( "material", "hud_ks_m32" ); +#precache( "string", "KILLSTREAK_EARNED_MINIGUN" ); +#precache( "string", "KILLSTREAK_MINIGUN_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_MINIGUN_INBOUND" ); +#precache( "eventstring", "mpl_killstreak_minigun" ); +#precache( "eventstring", "mpl_killstreak_m32" ); + +#namespace killstreak_weapons; + +function init() +{ + killstreaks::register("minigun", "minigun", "killstreak_minigun", "minigun_used",&useCarriedKillstreakWeapon, false, true, "MINIGUN_USED" ); + killstreaks::register_strings("minigun", &"KILLSTREAK_EARNED_MINIGUN", &"KILLSTREAK_MINIGUN_NOT_AVAILABLE", &"KILLSTREAK_MINIGUN_INBOUND", undefined, &"KILLSTREAK_MINIGUN_HACKED" ); + killstreaks::register_dialog("minigun", "mpl_killstreak_minigun", "kls_death_used", "","kls_death_enemy", "", "kls_death_ready"); + + killstreaks::register("m32", "m32", "killstreak_m32", "m32_used",&useCarriedKillstreakWeapon, false, true, "M32_USED" ); + killstreaks::register_strings("m32", &"KILLSTREAK_EARNED_M32", &"KILLSTREAK_M32_NOT_AVAILABLE", &"KILLSTREAK_M32_INBOUND", undefined, &"KILLSTREAK_M32_HACKED" ); + killstreaks::register_dialog("m32", "mpl_killstreak_m32", "kls_mgl_used", "","kls_mgl_enemy", "", "kls_mgl_ready"); + killstreaks::override_entity_camera_in_demo("m32", true); + + level.killStreakIcons["killstreak_minigun"] = "hud_ks_minigun"; + level.killStreakIcons["killstreak_m32"] = "hud_ks_m32"; + level.killStreakIcons["killstreak_m202_flash"] = "hud_ks_m202"; + level.killStreakIcons["killstreak_m220_tow_drop"] = "hud_ks_tv_guided_marker"; + level.killStreakIcons["killstreak_m220_tow"] = "hud_ks_tv_guided_missile"; + //level.killStreakIcons["killstreak_mp40"] = "hud_mp40"; + + callback::on_spawned( &on_player_spawned ); + + SetDvar( "scr_HeldKillstreak_Penalty", 0 ); +} + +function on_player_spawned() +{ + self endon( "disconnect" ); + + self.firedKillstreakWeapon = false; + self.usingKillstreakHeldWeapon = undefined; + + if ( !util::isFirstRound() && !util::isOneRound() ) + { + if ( level.roundStartKillstreakDelay > (globallogic_utils::getTimePassed() / 1000) ) + { + self thread watchKillstreakWeaponDelay(); + } + } +} + +function watchKillstreakWeaponDelay() +{ + self endon( "disconnect" ); + self endon( "death" ); + + while(1) + { + currentWeapon = self GetCurrentWeapon(); + self waittill( "weapon_change", newWeapon ); + + if ( level.roundStartKillstreakDelay < (globallogic_utils::getTimePassed() / 1000) ) + return; + + if ( !killstreaks::is_killstreak_weapon(newWeapon) ) + { + wait( 0.5 ); + continue; + } + + killstreak = killstreaks::get_killstreak_for_weapon( newWeapon ); + if ( killstreaks::is_delayable_killstreak( killstreak ) && newWeapon.isCarriedKillstreak ) + { + timeLeft = Int( level.roundStartKillstreakDelay - (globallogic_utils::getTimePassed() / 1000) ); + + if( !timeLeft ) + timeLeft = 1; + + self iPrintLnBold( &"MP_UNAVAILABLE_FOR_N", " " + timeLeft + " ", &"EXE_SECONDS" ); + + self switchToWeapon( currentWeapon ); + wait(0.5); + } + } +} + +function useKillstreakWeaponDrop( hardpointType ) +{ + if( self supplydrop::isSupplyDropGrenadeAllowed(hardpointType) == false ) + return false; + + result = self supplydrop::useSupplyDropMarker(); + + self notify( "supply_drop_marker_done" ); + + if ( !isdefined(result) || !result ) + { + return false; + } + + return result; +} + +function useCarriedKillstreakWeapon( hardpointType ) +{ + if( !isdefined(hardpointType) ) + return false; + + if ( self killstreakrules::isKillstreakAllowed( hardpointType, self.team ) == false ) + { + self switchToWeapon( self.lastDroppableWeapon ); + return false; + } + + currentWeapon = self GetCurrentWeapon(); + killstreakWeapon = killstreaks::get_killstreak_weapon( hardpointType ); + + if ( killstreakWeapon == level.weaponNone ) + return false; + + level weapons::add_limited_weapon( killstreakWeapon, self, 3 ); + + if ( issubstr( killstreakWeapon.name, "inventory" ) ) + isFromInventory = true; + else + isFromInventory = false; + + currentAmmo = self getammocount( killstreakWeapon ); + if ( ( ( hardpointType == "minigun" || hardpointType == "inventory_minigun" ) && !( isdefined( self.minigunStart ) && self.minigunStart ) ) || + ( ( hardpointType == "m32" || hardpointType == "inventory_m32" ) && !( isdefined( self.m32Start ) && self.m32Start ) ) ) + { + if ( hardpointType == "minigun" || hardpointType == "inventory_minigun" ) + { + self.minigunStart = true; + } + else + { + self.m32Start = true; + } + self killstreaks::play_killstreak_start_dialog( hardpointType, self.team, true ); + self AddWeaponStat( killstreakWeapon, "used", 1 ); + level thread popups::DisplayTeamMessageToAll( level.killstreaks[hardpointType].inboundText, self ); + + self.pers["held_killstreak_clip_count"][killstreakWeapon] = ( killstreakWeapon.clipSize > currentAmmo ? currentAmmo : killstreakWeapon.clipSize ); + + + if ( isFromInventory == false ) + { + if( self.pers["killstreak_quantity"][killstreakWeapon] > 0 ) + ammoPool = killstreakWeapon.maxAmmo; + else + ammoPool = self.pers["held_killstreak_ammo_count"][killstreakWeapon]; + + self setWeaponAmmoClip( killstreakWeapon, self.pers["held_killstreak_clip_count"][killstreakWeapon] ); + self setWeaponAmmoStock( killstreakWeapon, ammoPool - self.pers["held_killstreak_clip_count"][killstreakWeapon] ); + } + } + if ( hardpointType == "minigun" || hardpointType == "inventory_minigun" ) + { + if ( !( isdefined( self.minigunActive ) && self.minigunActive ) ) + { + killstreak_id = self killstreakrules::killstreakStart( hardpointType, self.team, false, false ); + + if( hardpointType == "inventory_minigun" ) + killstreak_id = self.pers["killstreak_unique_id"][self.pers["killstreak_unique_id"].size-1]; + + self.minigunId = killstreak_id; + self.minigunActive = true; + } + else + { + killstreak_id = self.minigunId; + } + } + else + { + if ( !( isdefined( self.m32Active ) && self.m32Active ) ) + { + killstreak_id = self killstreakrules::killstreakStart( hardpointType, self.team, false, false ); + + if( hardpointType == "inventory_m32" ) + killstreak_id = self.pers["killstreak_unique_id"][self.pers["killstreak_unique_id"].size-1]; + + self.m32Id = killstreak_id; + self.m32Active = true; + } + else + { + killstreak_id = self.m32Id; + } + } + //right now we dont have handling for the killstreak not working + assert ( killstreak_id != -1 ); + self.firedKillstreakWeapon = false; + //This will make it so the player cannot pick up weapons while using this weapon for the first time. + self setBlockWeaponPickup( killstreakWeapon, true ); + + if( isFromInventory ) + { + self setWeaponAmmoClip( killstreakWeapon, self.pers["held_killstreak_clip_count"][killstreakWeapon] ); + self setWeaponAmmoStock( killstreakWeapon, self.pers["killstreak_ammo_count"][self.pers["killstreak_ammo_count"].size - 1] - self.pers["held_killstreak_clip_count"][killstreakWeapon] ); + } + + notifyString = "killstreakWeapon_" + killstreakWeapon.name; + self notify( notifyString ); + //Monitor weapon switching from the killstreak weapon + self thread watchKillstreakWeaponSwitch( killstreakWeapon, killstreak_id, isFromInventory ); + self thread watchKillstreakWeaponDeath( killstreakWeapon, killstreak_id, isFromInventory ); + self thread watchKillstreakRoundChange( isFromInventory, killstreak_id ); + self thread WatchPlayerDeath( killstreakWeapon ); + + if( isFromInventory ) + self thread watchKillstreakRemoval( hardpointType, killstreak_id ); + + self.usingKillstreakHeldWeapon = true; + return false; +} + +function useKillstreakWeaponFromCrate( hardpointType ) +{ + if( !isdefined(hardpointType) ) + return false; + + killstreakWeapon = killstreaks::get_killstreak_weapon( hardpointType ); + if ( killstreakWeapon == level.weaponNone ) + return false; + + self.firedKillstreakWeapon = false; + + self setBlockWeaponPickup( killstreakWeapon, true ); + + killstreak_id = self killstreakrules::killstreakStart( hardpointType, self.team, false, false ); + //right now we dont have handling for the killstreak not working + assert ( killstreak_id != -1 ); + + if( issubstr( killstreakWeapon.name, "inventory" ) ) + isFromInventory = true; + else + isFromInventory = false; + + self thread watchKillstreakWeaponSwitch( killstreakWeapon, killstreak_id, isFromInventory ); + self thread watchKillstreakWeaponDeath( killstreakWeapon, killstreak_id, isFromInventory ); + + if( isFromInventory ) + self thread watchKillstreakRemoval( hardpointType, killstreak_id ); + + self.usingKillstreakHeldWeapon = true; + + return true; +} + +function watchKillstreakWeaponSwitch( killstreakWeapon, killstreak_id, isFromInventory ) +{ + self endon( "disconnect" ); + self endon( "death" ); + noneWeapon = getWeapon( "none" ); + minigunWeapon = getWeapon( "minigun" ); + minigunInventoryWeapon = getWeapon( "inventory_minigun" ); + //self endon( "killstreak_weapon_taken" ); + + while(1) + { + currentWeapon = self GetCurrentWeapon(); + self waittill( "weapon_change", newWeapon ); + + if ( level.inFinalKillcam ) + continue; + + if( newWeapon == noneWeapon ) + continue; + + currentAmmo = self getammocount( killstreakWeapon ); + currentAmmoInClip = self GetWeaponAmmoClip( killstreakWeapon ); + + //If an inventory weapon, make sure the ammo is stored at the right index in the stack + if( isFromInventory && currentAmmo > 0 ) + { + killstreakIndex = self killstreaks::get_killstreak_index_by_id( killstreak_id ); + + if( isdefined( killstreakIndex ) ) + { + self.pers["killstreak_ammo_count"][killstreakIndex] = currentAmmo; + self.pers["held_killstreak_clip_count"][killstreakWeapon] = currentAmmoInClip; + } + } + + if( killstreaks::is_killstreak_weapon( newWeapon ) && !newWeapon.isCarriedKillstreak ) + continue; + + if( newWeapon.isGameplayWeapon ) + continue; + + if( newWeapon == self.lastNonKillstreakWeapon && newWeapon.isCarriedKillstreak ) + continue; + + KillstreakId = killstreaks::get_top_killstreak_unique_id(); + + self.pers["held_killstreak_ammo_count"][killstreakWeapon] = currentAmmo; + self.pers["held_killstreak_clip_count"][killstreakWeapon] = currentAmmoInClip; + + if ( killstreak_id != -1 ) + { + self notify( "killstreak_weapon_switch" ); + } + self.firedKillstreakWeapon = false; + self.usingKillstreakHeldWeapon = undefined; + + waittillframeend; + + if ( currentAmmo == 0 || self.pers["killstreak_quantity"][killstreakWeapon] > 0 || ( isFromInventory && isdefined(KillstreakId) && KillstreakId != killstreak_id ) ) + { + killstreakrules::killstreakStop( killstreaks::get_killstreak_for_weapon( killstreakWeapon ), self.team, killstreak_id ); + if ( killstreakWeapon == minigunInventoryWeapon || killstreakWeapon == minigunWeapon ) + { + self.minigunStart = false; + self.minigunActive = false; + } + else + { + self.m32Start = false; + self.m32Active = false; + } + + //Check if we have earned another one, if so refill ammo. + if( self.pers["killstreak_quantity"][killstreakWeapon] > 0 ) + { + self.pers["held_killstreak_ammo_count"][killstreakWeapon] = killstreakWeapon.maxAmmo; + self loadout::setWeaponAmmoOverall( killstreakWeapon, self.pers["held_killstreak_ammo_count"][killstreakWeapon] ); + self.pers["killstreak_quantity"][killstreakWeapon]--; + } + } + + if( isFromInventory && currentAmmo == 0 ) + { + self TakeWeapon( killstreakWeapon ); + self killstreaks::remove_used_killstreak( killstreaks::get_killstreak_for_weapon( killstreakWeapon ), killstreak_id ); + self killstreaks::activate_next(); + } + break; + + } +} + +function watchKillstreakWeaponDeath( killstreakWeapon, killstreak_id, isFromInventory ) +{ + self endon( "disconnect" ); + self endon( "killstreak_weapon_switch" ); + + if( killstreak_id == -1 ) + { + return; + } + oldTeam = self.team; + + self waittill( "death" ); + + penalty = GetDvarFloat( "scr_HeldKillstreak_Penalty", 0.5 ); + maxAmmo = killstreakWeapon.maxAmmo; + currentAmmo = self getammocount( killstreakWeapon ); + currentAmmoInClip = self GetWeaponAmmoClip( killstreakWeapon ); + + if ( self.pers["killstreak_quantity"].size == 0 ) // player changed teams + { + currentAmmo = 0; + currentAmmoInClip = 0; + } + + maxClipSize = killstreakWeapon.clipSize; + newAmmo = int( currentAmmo - (maxAmmo * penalty) ); + KillstreakId = killstreaks::get_top_killstreak_unique_id(); + + + //Check if we should penalize the player. + if( self.lastNonKillstreakWeapon == killstreakWeapon ) + { + if( newAmmo < 0 ) + { + self.pers["held_killstreak_ammo_count"][killstreakWeapon] = 0; + self.pers["held_killstreak_clip_count"][killstreakWeapon] = 0; + } + else + { + self.pers["held_killstreak_ammo_count"][killstreakWeapon] = newAmmo; + self.pers["held_killstreak_clip_count"][killstreakWeapon] = ( maxClipSize <= newAmmo ? maxClipSize : newAmmo ); + } + } + + self.usingKillstreakHeldWeapon = false; + killstreakType = killstreaks::get_killstreak_for_weapon( killstreakWeapon ); + if ( newAmmo <= 0 || self.pers["killstreak_quantity"][killstreakWeapon] > 0 || ( isFromInventory && isdefined(KillstreakId) && KillstreakId != killstreak_id ) ) + { + killstreakrules::killstreakStop( killstreakType, oldTeam, killstreak_id ); + if ( killstreakType == "minigun" || killstreakType == "inventory_minigun" ) + { + self.minigunStart = false; + self.minigunActive = false; + } + else + { + self.m32Start = false; + self.m32Active = false; + } + //Check if we have earned another one, if so refill ammo. + if( isdefined( self.pers["killstreak_quantity"][killstreakWeapon] ) && self.pers["killstreak_quantity"][killstreakWeapon] > 0 ) + { + self.pers["held_killstreak_ammo_count"][killstreakWeapon] = maxAmmo; + self.pers["held_killstreak_clip_count"][killstreakWeapon] = maxClipSize; + self setWeaponAmmoClip( killstreakWeapon, self.pers["held_killstreak_clip_count"][killstreakWeapon] ); + self setWeaponAmmoStock( killstreakWeapon, self.pers["held_killstreak_ammo_count"][killstreakWeapon] - self.pers["held_killstreak_clip_count"][killstreakWeapon] ); + self.pers["killstreak_quantity"][killstreakWeapon]--; + } + } + if( isFromInventory && newAmmo <= 0 ) + { + self TakeWeapon( killstreakWeapon ); + self killstreaks::remove_used_killstreak( killstreakType, killstreak_id ); + self killstreaks::activate_next(); + } + else if( isFromInventory )//If an inventory weapon, make sure the ammo is stored at the right index in the stack + { + killstreakIndex = self killstreaks::get_killstreak_index_by_id( killstreak_id ); + + if( isdefined( killstreakIndex ) ) + { + self.pers["killstreak_ammo_count"][killstreakIndex] = self.pers["held_killstreak_ammo_count"][killstreakWeapon]; + } + } +} + +function WatchPlayerDeath( killstreakWeapon ) +{ + self endon( "disconnect" ); + + endonWeaponString = "killstreakWeapon_" + killstreakWeapon.name; + self endon( endonWeaponString ); + + self waittill( "death" ); + + currentAmmo = self getammocount( killstreakWeapon ); + + self.pers["held_killstreak_clip_count"][killstreakWeapon] = ( killstreakWeapon.clipSize <= currentAmmo ? killstreakWeapon.clipSize : currentAmmo ); +} + +function watchKillstreakRemoval( killstreakType, killstreak_id ) +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "killstreak_weapon_switch" ); + + self waittill( "oldest_killstreak_removed", removedKillstreakType, removed_id ); + + if ( killstreakType == removedKillstreakType && killstreak_id == removed_id ) + { + removedKillstreakWeapon = killstreaks::get_killstreak_weapon( removedKillstreakType ); + if ( removedKillstreakWeapon.name == "inventory_minigun" ) + { + self.minigunStart = false; + self.minigunActive = false; + } + else + { + self.m32Start = false; + self.m32Active = false; + } + } +} + +function watchKillstreakRoundChange( isFromInventory, killstreak_id ) +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "killstreak_weapon_switch" ); + + self waittill( "round_ended" ); + + currentWeapon = self getCurrentWeapon(); + + if( !currentWeapon.isCarriedKillstreak ) + return; + + currentAmmo = self getammocount( currentWeapon ); + maxClipSize = currentWeapon.clipSize; + + //If an inventory weapon, make sure the ammo is stored at the right index in the stack + if( isFromInventory && currentAmmo > 0 ) + { + killstreakIndex = self killstreaks::get_killstreak_index_by_id( killstreak_id ); + + if( isDefined( killstreakIndex ) ) + { + self.pers["killstreak_ammo_count"][killstreakIndex] = currentAmmo; + self.pers["held_killstreak_clip_count"][currentWeapon] = ( maxClipSize <= currentAmmo ? maxClipSize : currentAmmo ); + } + } + else + { + self.pers["held_killstreak_ammo_count"][currentWeapon] = currentAmmo; + self.pers["held_killstreak_clip_count"][currentWeapon] = ( maxClipSize <= currentAmmo ? maxClipSize : currentAmmo ); + } +} + +function checkIfSwitchableWeapon( currentWeapon, newWeapon, killstreakWeapon, currentKillstreakId ) +{ + switchableWeapon = true; + topKillstreak = killstreaks::get_top_killstreak(); + killstreakId = killstreaks::get_top_killstreak_unique_id(); + + if( !isdefined(killstreakId) ) + killstreakId = -1; + + if ( self HasWeapon( killstreakWeapon ) && !self GetAmmoCount( killstreakWeapon ) )//Safety check that we're holding an empty killstreak weapon + switchableWeapon = true; + else if( self.firedKillstreakWeapon && newWeapon == killstreakWeapon && currentWeapon.isCarriedKillstreak )//We have a new version of the killstreak weapon and we've already shot the equipped one + switchableWeapon = true; + else if( newWeapon.isEquipment ) + switchableWeapon = true; + else if( isdefined( level.grenade_array[newWeapon] ) ) + switchableWeapon = false; + else if( newWeapon.isCarriedKillstreak && currentWeapon.isCarriedKillstreak && (!isdefined(currentKillstreakID) || currentKillstreakId != killstreakId) )//new held killstreak weapon + switchableWeapon = true; + else if( killstreaks::is_killstreak_weapon( newWeapon ) )//allow killstreaks to be called in + switchableWeapon = false; + else if( newWeapon.isGameplayWeapon )//check for briefcase bomb, syrette, etc. + switchableWeapon = false; + else if( self.firedKillstreakWeapon ) + switchableWeapon = true; + else if( self.lastNonKillstreakWeapon == killstreakWeapon ) + switchableWeapon = false; + else if( isdefined(topKillstreak) && topKillstreak == killstreakWeapon && currentKillstreakId == killstreakId )//putting the killstreak away + switchableWeapon = false; + + + return switchableWeapon; + +} diff --git a/mp/killstreaks/_killstreakrules.gsc b/mp/killstreaks/_killstreakrules.gsc new file mode 100644 index 0000000..e242270 --- /dev/null +++ b/mp/killstreaks/_killstreakrules.gsc @@ -0,0 +1,488 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\popups_shared; +#using scripts\shared\util_shared; + + + + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\gametypes\_globallogic_audio; + +#namespace killstreakrules; + +function init() +{ + level.killstreakrules = []; + level.killstreaktype = []; + level.killstreaks_triggered = []; + level.matchRecorderKillstreakKills = []; + + if( !isdefined( level.globalKillstreaksCalled ) ) + { + level.globalKillstreaksCalled = 0; + } + + // Rule name, max count per team count + createRule( "ai_tank", 4, 2); + createRule( "airsupport", 1, 1); + createRule( "combatrobot", 4, 2); + createRule( "chopper", 2, 1); + createRule( "chopperInTheAir", 2, 1); + createRule( "counteruav", 6, 3); + createRule( "dart", 4, 2); + createRule( "dogs", 1, 1); + createRule( "drone_strike", 1, 1); + createRule( "emp", 2, 1); + createRule( "firesupport", 1, 1); + createRule( "missiledrone", 3, 3); + createRule( "missileswarm", 1, 1); + createRule( "planemortar", 1, 1); + createRule( "playercontrolledchopper", 1, 1); + createRule( "qrdrone", 3, 2); + createRule( "uav", 10, 5); + createRule( "raps" , 2, 1); + createRule( "rcxd", 4, 2); + createRule( "remote_missile", 2, 1); + createRule( "remotemortar", 1, 1); + createRule( "satellite", 2, 1); + createRule( "sentinel", 4, 2); + createRule( "straferun", 1, 1); + createRule( "supplydrop", 4, 4); + createRule( "targetableent", 32, 32); + createRule( "turret", 8, 4); + createRule( "vehicle", 7, 7); + createRule( "weapon", 12, 6); + + // KILLSTREAK Rule Name adds checks + addKillstreakToRule( "ai_tank_drop", "ai_tank", true, true ); + addKillstreakToRule( "airstrike", "airsupport", true, true ); + addKillstreakToRule( "airstrike", "vehicle", true, true ); + addKillstreakToRule( "artillery", "firesupport", true, true ); + addKillstreakToRule( "auto_tow", "turret", true, true ); + addKillstreakToRule( "autoturret", "turret", true, true ); + addKillstreakToRule( "combat_robot", "combatrobot", true, true ); + addKillstreakToRule( "counteruav", "counteruav", true, true ); + addKillstreakToRule( "counteruav", "targetableent", true, true ); + addKillstreakToRule( "dart", "dart", true, true ); + addKillstreakToRule( "dogs", "dogs", true, true ); + addKillstreakToRule( "dogs_lvl2", "dogs", true, true ); + addKillstreakToRule( "dogs_lvl3", "dogs", true, true ); + addKillstreakToRule( "drone_strike", "drone_strike", true, true ); + addKillstreakToRule( "emp", "emp", true, true ); + addKillstreakToRule( "helicopter", "chopper", true, true ); + addKillstreakToRule( "helicopter", "chopperInTheAir", true, false ); + addKillstreakToRule( "helicopter", "playercontrolledchopper", false, true ); + addKillstreakToRule( "helicopter", "targetableent", true, true ); + addKillstreakToRule( "helicopter", "vehicle", true, true ); + + addKillstreakToRule( "helicopter_comlink", "chopper", true, true ); + addKillstreakToRule( "helicopter_comlink", "chopperInTheAir", true, false ); + addKillstreakToRule( "helicopter_comlink", "targetableent", true, true ); + addKillstreakToRule( "helicopter_comlink", "vehicle", true, true ); + + addKillstreakToRule( "helicopter_guard", "airsupport", true, true ); + + addKillstreakToRule( "helicopter_gunner", "chopperInTheAir", true, false ); + addKillstreakToRule( "helicopter_gunner", "playercontrolledchopper", true, true ); + addKillstreakToRule( "helicopter_gunner", "targetableent", true, true ); + addKillstreakToRule( "helicopter_gunner", "vehicle", true, true ); + + addKillstreakToRule( "helicopter_gunner_assistant", "chopperInTheAir", true, false ); + addKillstreakToRule( "helicopter_gunner_assistant", "playercontrolledchopper", true, true ); + addKillstreakToRule( "helicopter_gunner_assistant", "targetableent", true, true ); + addKillstreakToRule( "helicopter_gunner_assistant", "vehicle", true, true ); + + addKillstreakToRule( "helicopter_player_firstperson", "vehicle", true, true ); + addKillstreakToRule( "helicopter_player_firstperson", "chopperInTheAir", true, true ); + addKillstreakToRule( "helicopter_player_firstperson", "playercontrolledchopper", true, true ); + addKillstreakToRule( "helicopter_player_firstperson", "targetableent", true, true ); + addKillstreakToRule( "helicopter_player_gunner", "chopperInTheAir", true, true ); + addKillstreakToRule( "helicopter_player_gunner", "playercontrolledchopper", true, true ); + addKillstreakToRule( "helicopter_player_gunner", "targetableent", true, true ); + addKillstreakToRule( "helicopter_player_gunner", "vehicle", true, true ); + addKillstreakToRule( "helicopter_x2", "chopper", true, true ); + addKillstreakToRule( "helicopter_x2", "chopperInTheAir", true, false ); + addKillstreakToRule( "helicopter_x2", "playercontrolledchopper", false, true ); + addKillstreakToRule( "helicopter_x2", "targetableent", true, true ); + addKillstreakToRule( "helicopter_x2", "vehicle", true, true ); + addKillstreakToRule( "m202_flash", "weapon", true, true ); + addKillstreakToRule( "m220_tow", "weapon", true, true ); + addKillstreakToRule( "m220_tow_drop", "supplydrop", true, true ); + addKillstreakToRule( "m220_tow_drop", "vehicle", true, true ); + addKillstreakToRule( "m220_tow_killstreak", "weapon", true, true ); + addKillstreakToRule( "m32", "weapon", true, true ); + addKillstreakToRule( "m32_drop", "weapon", true, true ); + addKillstreakToRule( "microwave_turret", "turret", true, true ); + addKillstreakToRule( "minigun", "weapon", true, true ); + addKillstreakToRule( "minigun_drop", "weapon", true, true ); + addKillstreakToRule( "missile_drone", "missiledrone", true, true ); + addKillstreakToRule( "missile_swarm", "missileswarm", true, true ); + addKillstreakToRule( "mortar", "firesupport", true, true ); + addKillstreakToRule( "mp40_drop", "weapon", true, true ); + addKillstreakToRule( "napalm", "airsupport", true, true ); + addKillstreakToRule( "napalm", "vehicle", true, true ); + addKillstreakToRule( "planemortar", "planemortar", true, true ); + addKillstreakToRule( "qrdrone", "qrdrone", true, true ); + addKillstreakToRule( "qrdrone", "vehicle", true, true ); + addKillstreakToRule( "uav", "uav", true, true ); + addKillstreakToRule( "uav", "targetableent", true, true ); + addKillstreakToRule( "satellite", "satellite", true, true ); + addKillstreakToRule( "raps", "raps", true, true ); + addKillstreakToRule( "rcbomb", "rcxd", true, true ); + addKillstreakToRule( "remote_missile", "targetableent", true, true ); + addKillstreakToRule( "remote_missile", "remote_missile", true, true ); + addKillstreakToRule( "remote_mortar", "remotemortar", true, true ); + addKillstreakToRule( "remote_mortar", "targetableent", true, true ); + addKillstreakToRule( "sentinel", "sentinel", true, true ); + addKillstreakToRule( "straferun", "straferun", true, true ); + addKillstreakToRule( "supply_drop", "supplydrop", true, true ); + addKillstreakToRule( "supply_drop", "targetableent", true, true ); + addKillstreakToRule( "supply_drop", "vehicle", true, true ); + addKillstreakToRule( "supply_station", "supplydrop", true, true ); + addKillstreakToRule( "supply_station", "targetableent", true, true ); + addKillstreakToRule( "supply_station", "vehicle", true, true ); + addKillstreakToRule( "tow_turret_drop", "supplydrop", true, true ); + addKillstreakToRule( "tow_turret_drop", "vehicle", true, true ); + addKillstreakToRule( "turret_drop", "supplydrop", true, true ); + addKillstreakToRule( "turret_drop", "vehicle", true, true ); +} + +function createRule( rule, maxAllowable, maxAllowablePerTeam ) +{ + level.killstreakrules[rule] = spawnstruct(); + level.killstreakrules[rule].cur = 0; + level.killstreakrules[rule].curTeam = []; + level.killstreakrules[rule].max = maxAllowable; + level.killstreakrules[rule].maxPerTeam = maxAllowablePerTeam; +} + +function addKillstreakToRule( killstreak, rule, countTowards, checkAgainst, inventoryVariant ) +{ + if ( !isdefined (level.killstreaktype[killstreak] ) ) + level.killstreaktype[killstreak] = []; + + keys = GetArrayKeys( level.killstreaktype[killstreak] ); + + // you need to add a rule before adding it to a killstreak + assert( isdefined(level.killstreakrules[rule] ) ); + + if ( !isdefined( level.killstreaktype[killstreak][rule] ) ) + level.killstreaktype[killstreak][rule] = spawnstruct(); + + level.killstreaktype[killstreak][rule].counts = countTowards; + + level.killstreaktype[killstreak][rule].checks = checkAgainst; + + if( !( isdefined( inventoryVariant ) && inventoryVariant ) ) + addKillstreakToRule( "inventory_" + killstreak, rule, countTowards, checkAgainst, true ); +} + +// returns killstreakid or if killstreak is not allowed +function killstreakStart( hardpointType, team, hacked, displayTeamMessage ) +{ + /# + assert( isdefined( team ), "team needs to be defined" ); + #/ + + if ( self isKillstreakAllowed( hardpointType, team ) == false ) + return (-1); + + assert ( isdefined ( hardpointType ) ); + + if( !isdefined( hacked ) ) + hacked = false; + + if ( !isdefined( displayTeamMessage ) ) + displayTeamMessage = true; + + if( GetDvarInt( "teamOpsEnabled" ) == 1 ) + displayTeamMessage = false; + + if ( displayTeamMessage == true ) + { + if ( !hacked ) + self displayKillstreakStartTeamMessageToAll( hardpointType ); + } + + keys = GetArrayKeys( level.killstreaktype[hardpointType] ); + + foreach( key in keys ) + { + // Check if killstreak is counted by this rule + if ( !level.killstreaktype[hardpointType][key].counts ) + continue; + + assert( isdefined(level.killstreakrules[key] ) ); + level.killstreakrules[key].cur++; + if ( level.teambased ) + { + if ( !isdefined( level.killstreakrules[key].curTeam[team] ) ) + level.killstreakrules[key].curTeam[team] = 0; + level.killstreakrules[key].curTeam[team]++; + } + } + + level notify( "killstreak_started", hardpointType, team, self ); + + killstreak_id = level.globalKillstreaksCalled; + level.globalKillstreaksCalled++; + + killstreak_data = []; + killstreak_data[ "caller" ] = self GetXUID(); + killstreak_data[ "spawnid" ] = getplayerspawnid( self ); + killstreak_data[ "starttime" ] = gettime(); + killstreak_data[ "type" ] = hardpointType; + killstreak_data[ "endtime" ] = 0; + level.matchRecorderKillstreakKills[ killstreak_id ] = 0; + + level.killstreaks_triggered[ killstreak_id ] = killstreak_data; + + /# + killstreak_debug_text( "Started killstreak: " + hardpointtype + " for team: " + team + " id: " + killstreak_id ); + #/ + + return killstreak_id; +} + +function displayKillstreakStartTeamMessageToAll( hardpointType ) +{ + if( GetDvarInt( "teamOpsEnabled" ) == 1 ) + return; + + if ( isdefined( level.killstreaks[hardpointType] ) && isdefined( level.killstreaks[hardpointType].inboundtext ) ) + level thread popups::DisplayKillstreakTeamMessageToAll( hardpointType, self ); +} + +function RecordKillstreakEndDirect(eventIndex, recordStreakIndex, totalKills) +{ + player = self; + + player RecordKillstreakEndEvent( eventIndex, recordStreakIndex, totalKills ); + + player.killstreakEvents[recordStreakIndex] = undefined; +} + +function RecordKillstreakEnd(recordStreakIndex, totalKills) +{ + player = self; + + if(!IsPlayer(player)) + return; + + if(!IsDefined(totalKills)) + totalkills = 0; + + if(!isDefined(player.killstreakEvents)) + { + // This may store an eventIndex or number of kills, depending on whether a killstreak event or an end event happens first, respectively + player.killstreakEvents = associativeArray(); + } + eventIndex = player.killstreakEvents[recordStreakIndex]; + // Note that some killstreaks fire their end before their begin, so we need to check if the eventIndex is defined to determine that + // Two cases - 1) KillstreakEvent happens first (correctly) + if(isDefined(eventIndex)) + { + player RecordKillstreakEndDirect(eventIndex, recordStreakIndex, totalKills); + } + else + { + // KillstreakEndEvent happens first + player.killstreakEvents[recordStreakIndex] = totalKills; + } +} + +function killstreakStop( hardpointType, team, id ) +{ + /# + assert( isdefined( team ), "team needs to be defined" ); + #/ + + assert ( isdefined ( hardpointType ) ); + //assert( isdefined( id ), "Must provide the associated killstreak_id for " + hardpointType ); + + /# + idstr = "undefined"; + if (IsDefined(id)) + idstr = id; + killstreak_debug_text( "Stopped killstreak: " + hardpointtype + " for team: " + team + " id: " + idstr ); + #/ + keys = GetArrayKeys( level.killstreaktype[hardpointType] ); + + foreach( key in keys ) + { + // Check if killstreak is counted by this rule + if ( !level.killstreaktype[hardpointType][key].counts ) + continue; + + assert( isdefined(level.killstreakrules[key] ) ); + level.killstreakrules[key].cur--; + + assert (level.killstreakrules[key].cur >= 0 ); + + if ( level.teambased ) + { + assert( isdefined( team ) ); + assert( isdefined( level.killstreakrules[key].curTeam[team] ) ); + + level.killstreakrules[key].curTeam[team]--; + assert (level.killstreakrules[key].curTeam[team] >= 0 ); + } + } + + if ( !isdefined(id) || (id == (-1)) ) + { + killstreak_debug_text("WARNING! Invalid killstreak id detected for " + hardpointType ); + + // log a lightweight entry in the DB to assist tracking down invalid cases + bbPrint( "mpkillstreakuses", "starttime %d endtime %d name %s team %s", 0, GetTime(), hardpointType, team ); + return; + } + level.killstreaks_triggered[ id ][ "endtime" ] = GetTime(); + + totalKillsWithThisKillstreak = level.matchRecorderKillstreakKills[ id ]; + + bbPrint( "mpkillstreakuses", "starttime %d endtime %d spawnid %d name %s team %s", + level.killstreaks_triggered[ id ][ "starttime" ], + level.killstreaks_triggered[ id ][ "endtime" ], + level.killstreaks_triggered[ id ][ "spawnid" ], + hardpointType, + team ); + + level.killstreaks_triggered[ id ] = undefined; + level.matchRecorderKillstreakKills[ id ] = undefined; + + if( isdefined( level.killstreaks[hardpointType].menuname ) ) + { + recordStreakIndex = level.killstreakindices[level.killstreaks[hardpointType].menuname]; + if ( isdefined( self ) && isdefined( recordStreakIndex ) && ( !isdefined( self.activatingKillstreak ) || !self.activatingKillstreak ) ) + { + entity = self; + if(isDefined(entity.owner) ) + { + entity = entity.owner; + } + + entity RecordKillstreakEnd(recordstreakindex, totalkillswiththiskillstreak); + } + } +} + +function isKillstreakAllowed( hardpointType, team ) +{ + /# + assert( isdefined( team ), "team needs to be defined" ); + #/ + + assert ( isdefined ( hardpointType ) ); + + // general failsafe for all scorestreaks + if ( self killstreaks::is_killstreak_start_blocked() ) + return false; + + isAllowed = true; + + keys = GetArrayKeys( level.killstreaktype[hardpointType] ); + + foreach( key in keys ) + { + // Check if killstreak is restricted by this rule + if ( !level.killstreaktype[hardpointType][key].checks ) + continue; + + if ( level.killstreakrules[key].max != 0 ) + { + if (level.killstreakrules[key].cur >= level.killstreakrules[key].max) + { + /# + killstreak_debug_text( "Exceeded " + key + " overall" ); + #/ + isAllowed = false; + break; + } + } + + if ( level.teambased && level.killstreakrules[key].maxPerTeam != 0 ) + { + if ( !isdefined( level.killstreakrules[key].curTeam[team] ) ) + level.killstreakrules[key].curTeam[team] = 0; + + if (level.killstreakrules[key].curTeam[team] >= level.killstreakrules[key].maxPerTeam) + { + isAllowed = false; + /# + killstreak_debug_text( "Exceeded " + key + " team" ); + #/ + break; + } + } + } + + + if ( isdefined( self.lastStand ) && self.lastStand ) + { + /# + killstreak_debug_text( "In LastStand" ); + #/ + isAllowed = false; + } + + isEMPed = false; + // should only be needed in case of a hacked client, the client checks the EMP flag prior to switching to the killstreak weapon + if ( self IsEMPJammed() ) + { + /# + killstreak_debug_text( "EMP active" ); + #/ + isAllowed = false; + isEMPed = true; + if ( self EMP::EnemyEMPActive() ) + { + if ( isdefined( level.empEndTime ) ) + { + secondsLeft = int( ( level.empendtime - getTime() ) / 1000 ); + if ( secondsLeft > 0 ) + { + self iprintlnbold( &"KILLSTREAK_NOT_AVAILABLE_EMP_ACTIVE", secondsLeft ); + return false; + } + } + } + } + + if ( isAllowed == false ) + { + if ( isdefined( level.killstreaks[hardpointType] ) && isdefined( level.killstreaks[hardpointType].notAvailableText ) ) + { + self iprintlnbold( level.killstreaks[hardpointType].notAvailableText ); + + if ( !isdefined( self.currentKillstreakDialog ) && level.killstreaks[hardpointType].utilizesAirspace && isEMPed == false ) + { + self globallogic_audio::play_taacom_dialog( "airspaceFull" ); + } + } + } + + return isAllowed; +} + +function killstreak_debug_text( text ) +{ +/# + level.killstreak_rule_debug = GetDvarInt( "scr_killstreak_rule_debug", 0 ); // debug mode, draws debugging info on screen + + if ( isdefined( level.killstreak_rule_debug ) ) + { + if ( level.killstreak_rule_debug == 1.0 ) + { + iprintln( "KSR: " + text + "\n" ); + } + else if ( level.killstreak_rule_debug == 2.0 ) + { + iprintlnbold( "KSR: " + text ); + } + } +#/ +} \ No newline at end of file diff --git a/mp/killstreaks/_killstreaks.gsc b/mp/killstreaks/_killstreaks.gsc new file mode 100644 index 0000000..521cb17 --- /dev/null +++ b/mp/killstreaks/_killstreaks.gsc @@ -0,0 +1,3510 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\abilities\_ability_player; +#using scripts\shared\array_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_shared; +#using scripts\shared\hud_message_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons_shared; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\weapons\_weapons; + +#using scripts\mp\_teamops; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_hud_message; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\killstreaks\_ai_tank; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_combat_robot; +#using scripts\mp\killstreaks\_counteruav; +#using scripts\mp\killstreaks\_dart; +#using scripts\mp\killstreaks\_dogs; +#using scripts\mp\killstreaks\_drone_strike; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\killstreaks\_flak_drone; +#using scripts\mp\killstreaks\_helicopter; +#using scripts\mp\killstreaks\_helicopter_gunner; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_microwave_turret; +#using scripts\mp\killstreaks\_planemortar; +#using scripts\mp\killstreaks\_qrdrone; +#using scripts\mp\killstreaks\_raps; +#using scripts\mp\killstreaks\_rcbomb; +#using scripts\mp\killstreaks\_remote_weapons; +#using scripts\mp\killstreaks\_remotemissile; +#using scripts\mp\killstreaks\_satellite; +#using scripts\mp\killstreaks\_sentinel; +#using scripts\mp\killstreaks\_supplydrop; +#using scripts\mp\killstreaks\_turret; +#using scripts\mp\killstreaks\_uav; + + + + + + + + + + +#precache( "string", "MP_KILLSTREAK_N" ); + +#namespace killstreaks; + +function autoexec __init__sytem__() { system::register("killstreaks",&__init__,undefined,undefined); } + +function __init__() +{ + level.killstreaks = []; + level.killstreakWeapons = []; + level.dropLocations = []; + level.zOffsetCounter = 0; + + clientfield::register( "vehicle", "timeout_beep", 1, 2, "int" ); + + callback::on_start_gametype( &init ); +} + +function init() +{ +/# + level.killstreak_init_start_time = GetMillisecondsRaw(); + thread debug_ricochet_protection(); +#/ + + if ( GetDvarString( "scr_allow_killstreak_building") == "" ) + { + SetDvar( "scr_allow_killstreak_building", "0" ); + } + + level.menuReferenceForKillStreak = []; + level.numKillstreakReservedObjectives = 0; + level.killstreakCounter = 0; + level.play_killstreak_firewall_being_hacked_dialog = &play_killstreak_firewall_being_hacked_dialog; + level.play_killstreak_firewall_hacked_dialog = &play_killstreak_firewall_hacked_dialog; + level.play_killstreak_being_hacked_dialog = &play_killstreak_being_hacked_dialog; + level.play_killstreak_hacked_dialog = &play_killstreak_hacked_dialog; + + if( !isdefined(level.roundStartKillstreakDelay) ) + { + level.roundStartKillstreakDelay = 0; + } + + level.isKillstreakWeapon =&killstreaks::is_killstreak_weapon; + + level.killstreakCoreBundle = struct::get_script_bundle( "killstreak", "killstreak_core" ); + + remote_weapons::init(); + + ai_tank::init(); + airsupport::init(); + combat_robot::init(); + counteruav::init(); + dart::init(); + drone_strike::init(); + emp::init(); + flak_drone::init(); + helicopter::init(); + helicopter_gunner::init(); + killstreakrules::init(); + microwave_turret::init(); + planemortar::init(); + qrdrone::init(); + raps_mp::init(); + rcbomb::init(); + remotemissile::init(); + satellite::init(); + sentinel::init(); + turret::init(); + uav::init(); + + supplydrop::init(); + +/# + level.killstreak_init_end_time = GetMillisecondsRaw(); + + elapsed_time = level.killstreak_init_end_time - level.killstreak_init_start_time; + PrintLn ("Total killstreaks init time: " + elapsed_time + " ms" ); +#/ + + callback::on_spawned( &on_player_spawned ); + callback::on_joined_team( &on_joined_team ); + +/# + level thread killstreak_debug_think(); +#/ + if( GetDvarint( "teamOpsEnabled" ) == 1 ) + { + level teamops::main(); + } +} + +function register( killstreakType, // killstreak name + killstreakWeaponName, // weapon name associated with deploying this killstreak + killstreakMenuName, // killstreak name from the cac loadout (could be merged with the type name) + killstreakUsageKey, // variable that shows the usage for the killstreak ( could be merged with type name ) + killstreakUseFunction, // function that gets called when the killstreak gets activated + killstreakDelayStreak, // weather or not to delay the killstreak at round start + weaponHoldAllowed = false, // if this killstreak weapon can be held by the player, as opposed to activate and remove (i.e. UAV) + killstreakStatsName = undefined, // Stats name for killstreak weapons (optional) + registerDvars = true, + registerInventory = true ) +{ + assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name."); + assert( !isdefined(level.killstreaks[killstreakType]), "Killstreak " + killstreakType + " already registered"); + assert( isdefined(killstreakUseFunction), "No use function defined for killstreak " + killstreakType); + + level.killstreaks[killstreakType] = SpawnStruct(); + + statsTableName = util::getStatsTableName(); + + // number of kills required to achieve killstreak + level.killstreaks[killstreakType].killstreakLevel = int( tablelookup( statsTableName, 4, killstreakMenuName, 5 ) ); + level.killstreaks[killstreakType].momentumCost = int( tablelookup( statsTableName, 4, killstreakMenuName, 16 ) ); + level.killstreaks[killstreakType].iconMaterial = tablelookup( statsTableName, 4, killstreakMenuName, 6 ); + level.killstreaks[killstreakType].quantity = int( tablelookup( statsTableName, 4, killstreakMenuName, 5 ) ); + level.killstreaks[killstreakType].usageKey = killstreakUsageKey; + level.killstreaks[killstreakType].useFunction = killstreakUseFunction; + level.killstreaks[killstreakType].menuName = killstreakMenuName; + level.killstreaks[killstreakType].delayStreak = killstreakDelayStreak; + level.killstreaks[killstreakType].allowAssists = false; + level.killstreaks[killstreakType].overrideEntityCameraInDemo = false; + level.killstreaks[killstreakType].teamKillPenaltyScale = 1.0; +/# + level.killstreaks[killstreakType].uiName = ( tablelookup( statsTableName, 4, killstreakMenuName, 3 ) ); + if ( level.killstreaks[killstreakType].uiName == "" ) + { + level.killstreaks[killstreakType].uiName = killstreakMenuName; + } +#/ + if ( isdefined( killstreakWeaponName ) ) + { + killstreakWeapon = GetWeapon( killstreakWeaponName ); + assert( killstreakWeapon != level.weaponNone ); + assert( !isdefined(level.killstreakWeapons[killstreakWeapon]), "Can not have a weapon associated with multiple killstreaks."); + level.killstreaks[killstreakType].weapon = killstreakWeapon; + level.killstreakWeapons[killstreakWeapon] = killstreakType; + } + + if( isdefined( killstreakStatsName ) ) + { + level.killstreaks[killstreakType].killstreakStatsName = killstreakStatsName; + } + + level.killstreaks[killstreakType].weaponHoldAllowed = weaponHoldAllowed; + + if( ( isdefined( registerInventory ) && registerInventory ) ) + { + level.menuReferenceForKillStreak[killstreakMenuName] = killstreakType; + killstreak_bundles::register_killstreak_bundle( killstreakType ); + } + + if( ( isdefined( registerInventory ) && registerInventory ) ) + { + if( ( isdefined( registerDvars ) && registerDvars ) ) + register_dev_dvars( killstreakType ); + + register( "inventory_" + killstreakType, + "inventory_" + killstreakWeaponName, + killstreakMenuName, + killstreakUsageKey, + killstreakUseFunction, + killstreakDelayStreak, + weaponHoldAllowed, + killstreakStatsName, + registerDvars, + false ); + } +} + +function is_registered(killstreakType) +{ + return isdefined(level.killstreaks[killstreakType]); +} + +function register_strings( killstreakType, receivedText, notUsableText, inboundText, inboundNearPlayerText, hackedText, utilizesAirspace = true, isInventory = false ) +{ + assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name."); + assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_strings."); + + level.killstreaks[killstreakType].receivedText = receivedText; + level.killstreaks[killstreakType].notAvailableText = notUsableText; + level.killstreaks[killstreakType].inboundText = inboundText; + level.killstreaks[killstreakType].inboundNearPlayerText = inboundNearPlayerText; + level.killstreaks[killstreakType].hackedText = hackedText; + level.killstreaks[killstreakType].utilizesAirspace = utilizesAirspace; // does the killstreak utilize airspace when deployed or while active? + + if( !( isdefined( isInventory ) && isInventory ) ) + register_strings( "inventory_" + killstreakType, receivedText, notUsableText, inboundText, inboundNearPlayerText, hackedText, utilizesAirspace, true ); +} + +function register_dialog( + killstreakType, + informDialog, + taacomDialogBundleKey, + pilotDialogArrayKey, + startDialogKey, // Commander + enemyStartDialogKey, // Commander + enemyStartMultipleDialogKey, // Commander + hackedDialogKey, // Commander + hackedStartDialogKey, // Commander + requestDialogKey, // Player + threatDialogKey, // Player + isInventory + ) +{ + assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name."); + assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_dialog."); + + level.killstreaks[killstreakType].informDialog = informDialog; + + level.killstreaks[killstreakType].taacomDialogBundleKey = taacomDialogBundleKey; + + level.killstreaks[killstreakType].startDialogKey = startDialogKey; + level.killstreaks[killstreakType].enemyStartDialogKey = enemyStartDialogKey; + level.killstreaks[killstreakType].enemyStartMultipleDialogKey = enemyStartMultipleDialogKey; + + level.killstreaks[killstreakType].hackedDialogKey = hackedDialogKey; + level.killstreaks[killstreakType].hackedStartDialogKey = hackedStartDialogKey; + + level.killstreaks[killstreakType].requestDialogKey = requestDialogKey; + level.killstreaks[killstreakType].threatDialogKey = threatDialogKey; + + if ( isdefined( pilotDialogarrayKey ) ) + { + // Set up Pilot Dialog Arrays + taacomBundles = struct::get_script_bundles( "mpdialog_taacom" ); + + foreach ( bundle in taacomBundles ) + { + if ( !isdefined( bundle.pilotBundles ) ) + { + bundle.pilotBundles = []; + } + + bundle.pilotBundles[killstreakType] = []; + + i = 0; + field = pilotDialogArrayKey + i; + fieldValue = GetStructField( bundle, field ); + + while ( isdefined( fieldValue ) ) + { + bundle.pilotBundles[killstreakType][i] = fieldValue; + + i++; + field = pilotDialogArrayKey + i; + fieldValue = GetStructField( bundle, field ); + } + } + } + + if( !( isdefined( isInventory ) && isInventory ) ) + register_dialog( + "inventory_" + killstreakType, + informDialog, + taacomDialogBundleKey, + pilotDialogArrayKey, + startDialogKey, + enemyStartDialogKey, + enemyStartMultipleDialogKey, + hackedDialogKey, + hackedStartDialogKey, + requestDialogKey, + threatDialogKey, + true ); + +} + +// additional weapons associated with this killstreak +function register_alt_weapon( killstreakType, weaponName, isInventory ) +{ + assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name."); + assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_alt_weapon."); + + weapon = GetWeapon( weaponName ); + + if( weapon == level.weaponNone ) + return; + + if ( level.killstreaks[killstreakType].weapon == weapon ) + { + return; + } + + if ( !isdefined( level.killstreaks[killstreakType].altWeapons ) ) + { + level.killstreaks[killstreakType].altWeapons = []; + } + + if( !isdefined( level.killstreakWeapons[weapon] ) ) + { + level.killstreakWeapons[weapon] = killstreakType; + } + level.killstreaks[killstreakType].altWeapons[level.killstreaks[killstreakType].altWeapons.size] = weapon; + + if( !( isdefined( isInventory ) && isInventory ) ) + register_alt_weapon( "inventory_" + killstreakType, weaponName, true ); +} + +// remote override weapons associated with this killstreak +function register_remote_override_weapon( killstreakType, weaponName, isInventory ) +{ + assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name."); + assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_remote_override_weapon."); + + weapon = GetWeapon( weaponName ); + if ( level.killstreaks[killstreakType].weapon == weapon ) + { + return; + } + + if ( !isdefined( level.killstreaks[killstreakType].remoteOverrideWeapons ) ) + { + level.killstreaks[killstreakType].remoteOverrideWeapons = []; + } + + if( !isdefined( level.killstreakWeapons[weapon] ) ) + { + level.killstreakWeapons[weapon] = killstreakType; + } + level.killstreaks[killstreakType].remoteOverrideWeapons[level.killstreaks[killstreakType].remoteOverrideWeapons.size] = weapon; + + if( !( isdefined( isInventory ) && isInventory ) ) + register_remote_override_weapon( "inventory_" + killstreakType, weaponName, true ); +} + +function is_remote_override_weapon( killstreakType, weapon ) +{ + if ( isdefined( level.killstreaks[killstreakType].remoteOverrideWeapons ) ) + { + for ( i=0; i index && util::isRoundBased() ) + { + hasAlreadyEarnedKillstreak = true; + } + else + { + hasAlreadyEarnedKillstreak = false; + } + + if ( isdefined( killstreak ) && is_available(killstreak) && !hasAlreadyEarnedKillstreak ) + { + killstreakLevel = get_level( index, killstreak ); + + if ( self HasPerk( "specialty_killstreak" ) ) + { + reduction = GetDvarint( "perk_killstreakReduction" ); + killstreakLevel -= reduction; + + // a fix for custom game types being able to adjust the killstreak reduction perk + if( killstreakLevel <= 0 ) + { + killstreakLevel = 1; + } + } + + if ( killstreakLevel == streakCount ) + { + self give( get_by_menu_name( killstreak ), streakCount ); + self.pers["killstreaksEarnedThisKillstreak"] = index + 1; + pixendevent(); + return true; + } + } + + pixendevent(); + return false; +} + +//Self is the player. This function looks at the player current killstreak and decides if he should be award a killstreak reward. +//It also manages the prompt that appears when the player gets killstreaks at intervals of 5 kills once they reach 10 kills. -Leif +function give_for_streak() +{ + if ( !util::isKillStreaksEnabled() ) + { + return; + } + + //Equals total kills within one life + if( !isdefined(self.pers["totalKillstreakCount"]) ) + { + self.pers["totalKillstreakCount"] = 0; + } + + // send the running tally to see what kill streak we should get + given = false; + + for ( i = 0; i < self.killstreak.size; i++ ) + { + given |= give_if_streak_count_matches( i, self.killstreak[i], self.pers["cur_kill_streak"] ); + } +} + +function is_an_a_killstreak() +{ + onKillstreak = false; + if( !isdefined( self.pers["kill_streak_before_death"] ) ) + { + self.pers["kill_streak_before_death"] = 0; + } + + streakPlusOne = self.pers["kill_streak_before_death"] + 1; + + if ( self.pers["kill_streak_before_death"] >= 5 ) + { + onKillstreak = true; + } + + + return onKillstreak; +} + +function give( killstreakType, streak, suppressNotification, noXP, toBottom ) +{ + pixbeginevent( "giveKillstreak" ); + self endon("disconnect"); + level endon( "game_ended" ); + + had_to_delay = false; + + killstreakGiven = false; + if( isdefined( noXP ) ) + { + if ( self give_internal( killstreakType, undefined, noXP, toBottom ) ) + { + killstreakGiven = true; + if ( self.just_given_new_inventory_killstreak === true ) + { + self add_to_notification_queue( level.killstreaks[killstreakType].menuname, streak, killstreakType, noXP ); + } + } + } + else if ( self give_internal( killstreakType, noXP ) ) + { + killstreakGiven = true; + if ( self.just_given_new_inventory_killstreak === true ) + { + self add_to_notification_queue( level.killstreaks[killstreakType].menuname, streak, killstreakType, noXP ); + } + } + pixendevent(); // "giveKillstreak" +} + +function take( killstreak ) +{ + self endon( "disconnect" ); + + killstreak_weapon = get_killstreak_weapon( killstreak ); + remove_used_killstreak( killstreak ); + + if ( self GetInventoryWeapon() == killstreak_weapon ) + { + self SetInventoryWeapon( level.weaponNone ); + } + + waittillframeend; + + currentWeapon = self GetCurrentWeapon(); + if( currentWeapon != killstreak_weapon || killstreak_weapon.isCarriedKillstreak ) + { + return; + } + + killstreaks::switch_to_last_non_killstreak_weapon(); + activate_next(); +} + +function remove_oldest() +{ + if( isdefined( self.pers["killstreaks"][0] ) ) + { + currentWeapon = self getCurrentWeapon(); + + if( currentWeapon == get_killstreak_weapon( self.pers["killstreaks"][0] ) ) + { + primaries = self GetWeaponsListPrimaries(); + + if( primaries.size > 0 ) + { + self SwitchToWeapon( primaries[0] ); + } + } + + self notify("oldest_killstreak_removed", self.pers["killstreaks"][0], self.pers["killstreak_unique_id"][0] ); + self remove_used_killstreak( self.pers["killstreaks"][0], self.pers["killstreak_unique_id"][0], false ); + } +} + +function give_internal( killstreakType, do_not_update_death_count, noXP, toBottom ) +{ + self.just_given_new_inventory_killstreak = undefined; + + if ( level.gameEnded ) + { + return false; + } + + if ( !util::isKillStreaksEnabled() ) + { + return false; + } + + if ( !isdefined( level.killstreaks[killstreakType] ) ) + { + return false; + } + + if ( !isdefined( self.pers["killstreaks"] ) ) + { + self.pers["killstreaks"] = []; + } + if( !isdefined( self.pers["killstreak_has_been_used"] ) ) + { + self.pers["killstreak_has_been_used"] = []; + } + if( !isdefined( self.pers["killstreak_unique_id"] ) ) + { + self.pers["killstreak_unique_id"] = []; + } + if( !isdefined( self.pers["killstreak_ammo_count"] ) ) + { + self.pers["killstreak_ammo_count"] = []; + } + + just_max_stack_removed_inventory_killstreak = undefined; + + if( isdefined( toBottom ) && toBottom ) + { + size = self.pers["killstreaks"].size; + + if( self.pers["killstreaks"].size >= level.maxInventoryScoreStreaks ) + { + self remove_oldest(); + just_max_stack_removed_inventory_killstreak = self.just_removed_used_killstreak; + } + + for( i = size; i > 0; i-- ) + { + self.pers["killstreaks"][i] = self.pers["killstreaks"][i - 1]; + self.pers["killstreak_has_been_used"][i] = self.pers["killstreak_has_been_used"][i - 1]; + self.pers["killstreak_unique_id"][i] = self.pers["killstreak_unique_id"][i - 1]; + self.pers["killstreak_ammo_count"][i] = self.pers["killstreak_ammo_count"][i - 1]; + } + self.pers["killstreaks"][0] = killstreakType; + self.pers["killstreak_unique_id"][0] = level.killstreakCounter; + level.killstreakCounter++; + + if( isdefined(noXP) ) + { + self.pers["killstreak_has_been_used"][0] = noXP; + } + else + { + self.pers["killstreak_has_been_used"][0] = false; + } + + + if( size == 0 ) + { + weapon = get_killstreak_weapon( killstreakType ); + ammoCount = give_weapon( weapon, true ); + } + + self.pers["killstreak_ammo_count"][0] = 0; + } + else + { + self.pers["killstreaks"][self.pers["killstreaks"].size] = killstreakType; + self.pers["killstreak_unique_id"][self.pers["killstreak_unique_id"].size] = level.killstreakCounter; + level.killstreakCounter++; + + if( self.pers["killstreaks"].size > level.maxInventoryScoreStreaks ) + { + self remove_oldest(); + just_max_stack_removed_inventory_killstreak = self.just_removed_used_killstreak; + } + + if( isdefined(noXP) ) + { + self.pers["killstreak_has_been_used"][self.pers["killstreak_has_been_used"].size] = noXP; + } + else + { + self.pers["killstreak_has_been_used"][self.pers["killstreak_has_been_used"].size] = false; + } + + weapon = get_killstreak_weapon( killstreakType ); + + ammoCount = give_weapon( weapon, true ); + + self.pers["killstreak_ammo_count"][self.pers["killstreak_ammo_count"].size] = ammoCount; + } + + self.just_given_new_inventory_killstreak = ( killstreakType !== just_max_stack_removed_inventory_killstreak ); + + return true; +} + +function add_to_notification_queue( menuName, streakCount, hardpointType, noNotify ) +{ + killstreakTableNumber = level.killStreakIndices[ menuName ]; + + if ( !isdefined( killstreakTableNumber ) ) + { + return; + } + + if( isdefined( noNotify ) && noNotify ) + { + return; + } + + informDialog = get_killstreak_inform_dialog( hardpointType ); + + if( GetDvarInt( "teamOpsEnabled" ) == 0 ) + { + self thread play_killstreak_ready_dialog( hardpointType, 2.4 ); + self thread play_killstreak_ready_sfx ( hardpointType ); + self LUINotifyEvent( &"killstreak_received", 2, killstreakTableNumber, istring( informDialog ) ); + self LUINotifyEventToSpectators( &"killstreak_received", 2, killstreakTableNumber, istring( informDialog ) ); + } + +} + + +function has_equipped( ) +{ + currentWeapon = self getCurrentWeapon(); + + keys = getarraykeys( level.killstreaks ); + for ( i = 0; i < keys.size; i++ ) + { + if ( level.killstreaks[keys[i]].weapon == currentWeapon ) + { + return true; + } + } + + return false; +} + +function _get_from_weapon( weapon ) +{ + keys = getarraykeys( level.killstreaks ); + + foreach( key in keys ) + { + killstreak = level.killstreaks[ key ]; + + if ( killstreak.weapon == weapon ) + { + return key; + } + + if ( isdefined( killstreak.altweapons ) ) + { + foreach( altweapon in killstreak.altweapons ) + { + if ( altweapon == weapon ) + { + return key; + } + } + } + + if ( isdefined( killstreak.remoteoverrideweapons ) ) + { + foreach( remoteOverrideWeapon in killstreak.remoteoverrideweapons ) + { + if( remoteOverrideWeapon == weapon ) + { + return key; + } + } + } + } + + return undefined; +} + + +function get_from_weapon( weapon ) +{ + if( weapon == level.weaponNone ) + { + return undefined; + } + + res = _get_from_weapon( weapon ); + if( !isdefined( res ) ) + return _get_from_weapon( weapon.rootweapon ); + else + return res; +} + +// dont need the isinventory it will be inventory they all are +function give_weapon( weapon, isinventory, useStoredAmmo ) +{ + currentWeapon = self GetCurrentWeapon(); + + if ( currentWeapon != level.weaponNone && !( isdefined( level.usingMomentum ) && level.usingMomentum ) ) + { + weaponsList = self GetWeaponsList(); + for( idx = 0; idx < weaponsList.size; idx++ ) + { + carriedWeapon = weaponsList[idx]; + + if ( currentWeapon == carriedWeapon ) + { + continue; + } + + // special case weapons that are killstreak weapons but shouldn't be taken from the player + switch ( carriedWeapon.name ) + { + case "minigun": + case "m32": + continue; + } + + if ( killstreaks::is_killstreak_weapon( carriedWeapon ) ) + { + self TakeWeapon( carriedWeapon ); + } + } + } + + // take the weapon in-case we already have it. + // otherwise giveweapon will not give the weapon or ammo + if( currentWeapon != weapon && ( self hasWeapon(weapon) == false ) ) + { + self TakeWeapon( weapon ); + self GiveWeapon( weapon ); + } + + if ( ( isdefined( level.usingMomentum ) && level.usingMomentum ) ) + { + self SetInventoryWeapon( weapon ); + + if( weapon.isCarriedKillstreak ) + { + if( !isdefined( self.pers["held_killstreak_ammo_count"][weapon] ) ) + { + self.pers["held_killstreak_ammo_count"][weapon] = 0; + } + + if( !isdefined( self.pers["held_killstreak_clip_count"][weapon] ) ) + { + self.pers["held_killstreak_clip_count"][weapon] = weapon.clipSize; + } + + if( !isdefined( self.pers["killstreak_quantity"][weapon] ) ) + { + self.pers["killstreak_quantity"][weapon] = 0; + } + + if( currentWeapon == weapon && !killstreaks::isHeldInventoryKillstreakWeapon( weapon ) ) + { + return weapon.maxAmmo; + } + else if( ( isdefined( useStoredAmmo ) && useStoredAmmo ) && self.pers["killstreak_ammo_count"][self.pers["killstreak_ammo_count"].size - 1] > 0 ) + { + switch( weapon.name ) + { + case "inventory_minigun": + if( ( isdefined( self.minigunActive ) && self.minigunActive ) ) + { + return self.pers["held_killstreak_ammo_count"][weapon]; + } + break; + case "inventory_m32": + if( ( isdefined( self.m32Active ) && self.m32Active ) ) + { + return self.pers["held_killstreak_ammo_count"][weapon]; + } + break; + default: + break; + } + self.pers["held_killstreak_ammo_count"][weapon] = self.pers["killstreak_ammo_count"][self.pers["killstreak_ammo_count"].size - 1]; + self loadout::setWeaponAmmoOverall( weapon, self.pers["killstreak_ammo_count"][self.pers["killstreak_ammo_count"].size - 1] ); + } + else + { + self.pers["held_killstreak_ammo_count"][weapon] = weapon.maxAmmo; + self.pers["held_killstreak_clip_count"][weapon] = weapon.clipSize; + self loadout::setWeaponAmmoOverall( weapon, self.pers["held_killstreak_ammo_count"][weapon] ); + } + return self.pers["held_killstreak_ammo_count"][weapon]; + } + else + { + switch ( weapon.name ) + { + case "inventory_minigun_drop": + case "inventory_m32_drop": + case "inventory_missile_drone": + + case "combat_robot_marker": + case "inventory_combat_robot_marker": + + case "dart": + case "inventory_dart": + + case "ai_tank_marker": + case "inventory_ai_tank_marker": + + case "supplydrop_marker": + case "inventory_supplydrop_marker": + delta = 1; + break; + default: + delta = 0; + break; + } + + return change_killstreak_quantity( weapon, delta ); + } + } + else + { + self setActionSlot( 4, "weapon", weapon ); + return 1; + } +} + +function activate_next( do_not_update_death_count ) +{ + if ( level.gameEnded ) + { + return false; + } + + if ( ( isdefined( level.usingMomentum ) && level.usingMomentum ) ) + { + self SetInventoryWeapon( level.weaponNone ); + } + else + { + self setActionSlot( 4, "" ); + } + + if ( !isdefined( self.pers["killstreaks"] ) || self.pers["killstreaks"].size == 0 ) + { + return false; + } + + killstreakType = self.pers["killstreaks"][self.pers["killstreaks"].size - 1]; + + if ( !isdefined( level.killstreaks[killstreakType] ) ) + { + return false; + } + + weapon = level.killstreaks[killstreakType].weapon; + {wait(.05);}; + + ammoCount = give_weapon( weapon, false, true ); + + //Set the ammo now so we don't get a flash on the HUD when we use this weapon later + if( weapon.isCarriedKillstreak ) + { + self setWeaponAmmoClip( weapon, self.pers["held_killstreak_clip_count"][weapon] ); + self setWeaponAmmoStock( weapon, ammoCount - self.pers["held_killstreak_clip_count"][weapon] ); + } + + if ( !isdefined( do_not_update_death_count ) || do_not_update_death_count != false ) + { + self.pers["killstreakItemDeathCount"+killstreakType] = self.deathCount; + } + + return true; +} + +function give_owned() +{ + if ( isdefined( self.pers["killstreaks"] ) && self.pers["killstreaks"].size > 0 ) + { + self activate_next( false ); + } +} + +function get_killstreak_quantity( killstreakWeapon ) +{ + return (isdefined(self.pers["killstreak_quantity"][killstreakWeapon])?self.pers["killstreak_quantity"][killstreakWeapon]:0); +} + +function change_killstreak_quantity( killstreakWeapon, delta ) +{ + quantity = get_killstreak_quantity( killstreakWeapon ); + + previousQuantity = quantity; + quantity += delta; + + if ( quantity > level.scoreStreaksMaxStacking ) + { + quantity = level.scoreStreaksMaxStacking; + } + + // take the weapon in-case we already have it. + // otherwise giveweapon will not give the weapon or ammo + if(self hasWeapon( killstreakWeapon ) == false ) + { + self TakeWeapon( killstreakWeapon ); + self GiveWeapon( killstreakWeapon ); + self SetEverHadWeaponAll( true ); + } + + self.pers["killstreak_quantity"][killstreakWeapon] = quantity; + self SetWeaponAmmoClip( killstreakWeapon, quantity ); + return quantity; +} + +function has_killstreak_in_class( killstreakMenuName ) +{ + foreach ( equippedKillstreak in self.killstreak ) + { + if ( equippedKillstreak == killstreakMenuName ) + { + return true; + } + } + return false; +} + +function has_killstreak( killstreak ) +{ + player = self; + + if( !isdefined( killstreak ) || !isdefined( player.pers["killstreaks"] ) ) + return false; + + for( i = 0; i < self.pers["killstreaks"].size; i++ ) + { + if( player.pers["killstreaks"][i] == killstreak ) + return true; + } + return false; +} + +function RecordKillstreakBeginDirect(recordStreakIndex) +{ + player = self; + if(!isPlayer(player) || !isDefined(recordstreakindex)) + { + return; + } + + if( !isdefined(self.killstreakEvents) ) + player.killstreakEvents = associativeArray(); + + // Already defined means the End happened first, so lets call both start and end. + // Note that in this case, the killstreakEvents is storing the number of kills + if(isDefined(self.killstreakEvents[recordStreakIndex])) + { + kills = player.killstreakEvents[recordStreakIndex]; + eventIndex = player RecordKillStreakEvent( recordStreakIndex ); + player killstreakrules::RecordKillstreakEndDirect(eventIndex, recordStreakIndex, kills); + + player.killstreakEvents[recordStreakIndex] = undefined; + } + else + { + // Should be called in correct order + eventIndex = player RecordKillStreakEvent( recordStreakIndex ); + player.killstreakEvents[recordStreakIndex] = eventIndex; + } +} + +function remove_when_done( killstreak, hasKillstreakBeenUsed, isFromInventory ) +{ + self endon( "disconnect" ); + + continue_wait = true; + + while( continue_wait ) + { + self waittill( "killstreak_done", successful, killstreakType ); + + if ( killstreakType == killstreak ) + continue_wait = false; + } + + if ( successful ) + { + /#print( "killstreak: " + get_menu_name( killstreak ) );#/ + + // good place to hook into killstreak usage + killstreak_weapon = get_killstreak_weapon( killstreak ); + recordStreakIndex = undefined; + if( isdefined( level.killstreaks[killstreak].menuname ) ) + { + recordStreakIndex = level.killstreakindices[level.killstreaks[killstreak].menuname]; + self RecordKillstreakBeginDirect(recordStreakIndex); + } + + if ( ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks ) ) + { + if ( ( isdefined( isFromInventory ) && isFromInventory ) ) + { + remove_used_killstreak( killstreak ); + if ( self GetInventoryWeapon() == killstreak_weapon ) + { + self SetInventoryWeapon( level.weaponNone ); + } + } + else + { + self change_killstreak_quantity( killstreak_weapon, -1 ); + } + } + else if ( ( isdefined( level.usingMomentum ) && level.usingMomentum ) ) + { + if ( ( isdefined( isFromInventory ) && isFromInventory ) && ( self GetInventoryWeapon() == killstreak_weapon ) ) + { + remove_used_killstreak( killstreak ); + self SetInventoryWeapon( level.weaponNone ); + } + else + { + globallogic_score::_setPlayerMomentum( self, self.momentum - level.killstreaks[killstreakType].momentumCost ); + } + } + else + { + remove_used_killstreak( killstreak ); + } + + if ( !( isdefined( level.usingMomentum ) && level.usingMomentum ) ) + { + self setActionSlot( 4, "" ); + } + + success = true; + } + + waittillframeend; + + // each killstreak should hide the compass via this clientfield if so desired + self unhide_compass(); + + currentWeapon = self GetCurrentWeapon(); + killstreak_weapon = get_killstreak_weapon( killstreakType ); + if( currentWeapon == killstreak_weapon && killstreak_weapon.isCarriedKillstreak ) + { + return; + } + + if ( successful && ( !self has_killstreak_in_class( get_menu_name( killstreak ) ) || ( isdefined( isFromInventory ) && isFromInventory ) ) ) + { + killstreaks::switch_to_last_non_killstreak_weapon(); + } + else + { + // the killstreak could have failed because we switched to another killstreak weapon + killstreakForCurrentWeapon = get_from_weapon( currentWeapon ); + + if ( currentWeapon.isGameplayWeapon ) + { + if ( ( isdefined( self.isPlanting ) && self.isPlanting ) || ( isdefined( self.isDefusing ) && self.isDefusing ) ) + { + return; + } + } + + // not sure why we would switch when !isdefined( killstreakForCurrentWeapon ) so this is so we don't when we have switched to a HeroWeapon + if ( !isdefined( killstreakForCurrentWeapon ) && currentWeapon.isHeroWeapon ) + { + return; + } + + if ( successful || !isdefined( killstreakForCurrentWeapon ) || killstreakForCurrentWeapon == killstreak ) + { + killstreaks::switch_to_last_non_killstreak_weapon(); + } + } + + if ( !( isdefined( level.usingMomentum ) && level.usingMomentum ) || ( isdefined( isFromInventory ) && isFromInventory ) ) + { + if ( successful ) + { + activate_next(); + } + } +} + +function useKillstreak( killstreak, isFromInventory ) +{ + hasKillstreakBeenUsed = get_if_top_killstreak_has_been_used(); + + if ( isdefined( self.selectingLocation ) ) + { + return; + } + + self thread remove_when_done( killstreak, hasKillstreakBeenUsed, isFromInventory ); + self thread trigger_killstreak( killstreak, isFromInventory ); +} + +function remove_used_killstreak( killstreak, killstreakId, take_weapon_after_use = true ) +{ + self.just_removed_used_killstreak = undefined; + + if( !isdefined( self.pers["killstreaks"] ) ) + return; + + // the killstreak stack is a lifo stack + // find the top most killstreak in the list + // remove it + killstreakIndex = undefined; + + for ( i = self.pers["killstreaks"].size - 1; i >= 0; i-- ) + { + if ( self.pers["killstreaks"][i] == killstreak ) + { + if( isdefined( killstreakId ) && self.pers["killstreak_unique_id"][i] != killstreakId ) + { + continue; + } + + killstreakIndex = i; + break; + } + } + + if ( !isdefined(killstreakIndex) ) + { + return false; + } + + self.just_removed_used_killstreak = killstreak; + + if( take_weapon_after_use && !self has_killstreak_in_class( get_menu_name( killstreak ) ) ) + { + self thread take_weapon_after_use( get_killstreak_weapon( killstreak ) ); + } + + arraySize = self.pers["killstreaks"].size; + for ( i = killstreakIndex; i < arraySize - 1; i++ ) + { + self.pers["killstreaks"][i] = self.pers["killstreaks"][i + 1]; + self.pers["killstreak_has_been_used"][i] = self.pers["killstreak_has_been_used"][i + 1]; + self.pers["killstreak_unique_id"][i] = self.pers["killstreak_unique_id"][i + 1]; + self.pers["killstreak_ammo_count"][i] = self.pers["killstreak_ammo_count"][i + 1]; + } + + self.pers["killstreaks"][arraySize-1] = undefined; + self.pers["killstreak_has_been_used"][arraySize-1] = undefined; + self.pers["killstreak_unique_id"][arraySize-1] = undefined; + self.pers["killstreak_ammo_count"][arraySize-1] = undefined; + + return true; +} + +function take_weapon_after_use( killstreakWeapon ) +{ + self endon("disconnect"); + self endon("death"); + self endon( "joined_team" ); + self endon( "joined_spectators" ); + + self waittill( "weapon_change" ); + + inventoryWeapon = self GetInventoryWeapon(); + if ( inventoryWeapon != killstreakWeapon ) + { + self TakeWeapon( killstreakWeapon ); + } +} + +function get_top_killstreak() +{ + if ( self.pers["killstreaks"].size == 0 ) + { + return undefined; + } + + return self.pers["killstreaks"][self.pers["killstreaks"].size-1]; +} + +function get_if_top_killstreak_has_been_used() +{ + if ( !( isdefined( level.usingMomentum ) && level.usingMomentum ) ) + { + if ( self.pers["killstreak_has_been_used"].size == 0 ) + { + return undefined; + } + + return self.pers["killstreak_has_been_used"][self.pers["killstreak_has_been_used"].size-1]; + } +} + +function get_top_killstreak_unique_id() +{ + if ( self.pers["killstreak_unique_id"].size == 0 ) + { + return undefined; + } + + return self.pers["killstreak_unique_id"][self.pers["killstreak_unique_id"].size-1]; +} + +function get_killstreak_index_by_id( killstreakId ) +{ + for( index = self.pers["killstreak_unique_id"].size - 1; index >= 0; index-- ) + { + if( self.pers["killstreak_unique_id"][index] == killstreakId ) + { + return index; + } + } + + return undefined; +} + + +function get_killstreak_momentum_cost( killstreak ) +{ + if ( !( isdefined( level.usingMomentum ) && level.usingMomentum ) ) + { + return 0; + } + + if ( !isdefined( killstreak ) ) + { + return 0; + } + + Assert( isdefined(level.killstreaks[killstreak]) ); + + return level.killstreaks[killstreak].momentumCost; +} + +function get_killstreak_for_weapon( weapon ) +{ + if( isdefined( level.killstreakWeapons[weapon] ) ) + return level.killstreakWeapons[weapon]; + else + return level.killstreakWeapons[weapon.rootweapon]; +} + +function get_killstreak_for_weapon_for_stats( weapon ) +{ + prefix = "inventory_"; + + killstreak = get_killstreak_for_weapon( weapon ); + + if ( isdefined( killstreak ) ) + { + if ( StrStartsWith( killstreak, prefix ) ) + killstreak = getSubStr( killstreak, prefix.size ); + } + + return killstreak; +} + +function is_killstreak_weapon_assist_allowed( weapon ) +{ + killstreak = get_killstreak_for_weapon( weapon ); + + if ( !isdefined( killstreak ) ) + { + return false; + } + + if ( level.killstreaks[killstreak].allowAssists ) + { + return true; + } + + return false; +} + +function get_killstreak_team_kill_penalty_scale( weapon ) +{ + killstreak = get_killstreak_for_weapon( weapon ); + + if ( !isdefined( killstreak ) ) + { + return 1.0; + } + + return level.killstreaks[killstreak].teamKillPenaltyScale; +} + +function should_override_entity_camera_in_demo( player, weapon ) +{ + killstreak = get_killstreak_for_weapon( weapon ); + + if ( !isdefined( killstreak ) ) + { + return false; + } + + if ( level.killstreaks[killstreak].overrideEntityCameraInDemo ) + { + return true; + } + + if ( isdefined( player.remoteWeapon ) && ( isdefined( player.remoteWeapon.controlled ) && player.remoteWeapon.controlled ) ) + { + return true; + } + + return false; +} + +function wait_till_hero_weapon_is_fully_on( weapon ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "weapon_change" ); + + slot = self GadgetGetSlot( weapon ); + + while (1) + { + if ( self ability_player::gadget_is_in_use( slot ) ) + { + self.lastNonKillstreakWeapon = weapon; + return; + } + {wait(.05);}; + } +} + +function track_weapon_usage() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self.lastNonKillstreakWeapon = self GetCurrentWeapon(); + lastValidPimary = self GetCurrentWeapon(); + if ( self.lastNonKillstreakWeapon == level.weaponNone ) + { + weapons = self GetWeaponsListPrimaries(); + if ( weapons.size > 0 ) + { + self.lastNonKillstreakWeapon = weapons[0]; + } + else + { + self.lastNonKillstreakWeapon = level.weaponBaseMelee; + } + } + Assert( self.lastNonKillstreakWeapon != level.weaponNone ); + + for ( ;; ) + { + currentWeapon = self GetCurrentWeapon(); + self waittill( "weapon_change", weapon ); + + if ( weapons::is_primary_weapon( weapon ) ) + { + lastValidPimary = weapon; + } + + if ( weapon == self.lastNonKillstreakWeapon || weapon == level.weaponNone || weapon == level.weaponBaseMelee ) + { + continue; + } + + if ( weapon.isGameplayWeapon ) + { + continue; + } + + if( isdefined( self.resurrect_weapon ) && ( weapon == self.resurrect_weapon ) ) + { + continue; + } + + name = get_killstreak_for_weapon( weapon ); + + if ( isdefined( name ) && !weapon.isCarriedKillstreak ) + { + killstreak = level.killstreaks[ name ]; + continue; + } + + if ( currentWeapon.isEquipment ) + { + if ( self.lastNonKillstreakWeapon.isCarriedKillstreak ) + { + self.lastNonKillstreakWeapon = lastValidPimary; + } + continue; + } + + if ( weapon.isHeroWeapon ) + { + if ( weapon.gadget_heroversion_2_0 ) + { + if ( weapon.isGadget && self GetAmmoCount(weapon ) > 0 ) + { + self thread wait_till_hero_weapon_is_fully_on( weapon ); + continue; + } + } + } + + + self.lastNonKillstreakWeapon = weapon; + } +} + +function killstreak_waiter() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + self thread track_weapon_usage(); + + self give_owned(); + + for ( ;; ) + { + self waittill( "weapon_change", weapon ); + + if( !killstreaks::is_killstreak_weapon( weapon ) ) + { + continue; + } + + killstreak = get_killstreak_for_weapon( weapon ); + + if ( !( isdefined( level.usingMomentum ) && level.usingMomentum ) ) + { + killstreak = get_top_killstreak(); + if( weapon != get_killstreak_weapon(killstreak) ) + continue; + } + + if( is_remote_override_weapon( killstreak, weapon ) ) + { + continue; + } + + inventoryButtonPressed = ( self InventoryButtonPressed() ) || ( isdefined( self.pers["isBot"] ) ); + + waittillframeend; + + if( ( isdefined( self.usingKillstreakHeldWeapon ) && self.usingKillstreakHeldWeapon ) && weapon.isCarriedKillstreak ) + { + continue; + } + + isFromInventory = undefined; + + if ( ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks ) ) + { + if ( ( weapon == self GetInventoryWeapon() ) ) + { + isFromInventory = true; + } + else if (( self GetAmmoCount( weapon ) <= 0 ) && (weapon.name != "killstreak_ai_tank")) + { + self killstreaks::switch_to_last_non_killstreak_weapon(); + continue; + } + } + else if ( ( isdefined( level.usingMomentum ) && level.usingMomentum ) ) + { + if ( ( weapon == self GetInventoryWeapon() ) && inventoryButtonPressed ) + { + isFromInventory = true; + } + else if ( self.momentum < level.killstreaks[killstreak].momentumCost ) + { + self killstreaks::switch_to_last_non_killstreak_weapon(); + continue; + } + } + + // this catches the between round cases + if ( !isdefined( level.startTime ) && ( level.roundStartKillstreakDelay > 0 ) ) + { + display_unavailable_time(); + continue; + } + + thread useKillstreak( killstreak, isFromInventory ); + } +} + +function should_delay_killstreak( killstreakType ) +{ + if( !isdefined(level.startTime) ) + { + return false; + } + + if( level.roundStartKillstreakDelay < ( ( ( gettime() - level.startTime ) - level.discardTime ) / 1000 ) ) + { + return false; + } + + if( !is_delayable_killstreak(killstreakType) ) + { + return false; + } + + killstreakWeapon = get_killstreak_weapon( killstreakType ); + if( killstreakWeapon.isCarriedKillstreak ) + { + return false; + } + + if ( util::isFirstRound() || util::isOneRound() ) + { + return false; + } + + return true; +} + +//check if this is a killstreak we want to delay at the start of a round +function is_delayable_killstreak( killstreakType ) +{ + if( isdefined( level.killstreaks[killstreakType] ) && ( isdefined( level.killstreaks[killstreakType].delayStreak ) && level.killstreaks[killstreakType].delayStreak ) ) + { + return true; + } + + return false; +} + +function get_xp_amount_for_killstreak( killstreakType ) +{ + // looks like only the rcxd does this + // all killstreaks need this? + xpAmount = 0; + switch( level.killstreaks[killstreakType].killstreakLevel ) + { + case 1: + case 2: + case 3: + case 4: + xpAmount = 100; + break; + case 5: + xpAmount = 150; + break; + case 6: + case 7: + xpAmount = 200; + break; + case 8: + xpAmount = 250; + break; + case 9: + xpAmount = 300; + break; + case 10: + case 11: + xpAmount = 350; + break; + case 12: + case 13: + case 14: + case 15: + xpAmount = 500; + break; + } + + return xpAmount; +} + +function display_unavailable_time() +{ + timeLeft = Int( level.roundStartKillstreakDelay - (globallogic_utils::getTimePassed() / 1000) ); + + if ( timeLeft <= 0 ) + { + timeLeft = 1; + } + + self iPrintLnBold( &"MP_UNAVAILABLE_FOR_N", " " + timeLeft + " ", &"EXE_SECONDS" ); +} + +function trigger_killstreak( killstreakType, isFromInventory ) +{ + assert( isdefined(level.killstreaks[killstreakType].useFunction), "No use function defined for killstreak " + killstreakType); + + self.usingKillstreakFromInventory = isFromInventory; + + if ( level.inFinalKillcam ) + { + return false; + } + + if( should_delay_killstreak( killstreakType ) ) + { + display_unavailable_time(); + } + else if ( [[level.killstreaks[killstreakType].useFunction]](killstreakType) ) + { + //Killstreak of 3-4:+100, 5: +150, 6-7 +200, 8: +250, 9: +300, 11: +350, Above: +500 + + if ( isdefined( self ) ) + { + //bbPrint( "mpkillstreakuses", "gametime %d spawnid %d name %s", getTime(), getplayerspawnid( self ), killstreakType ); + + if ( !isdefined( self.pers[level.killstreaks[killstreakType].usageKey] ) ) + { + self.pers[level.killstreaks[killstreakType].usageKey] = 0; + } + + self.pers[level.killstreaks[killstreakType].usageKey]++; + self notify( "killstreak_used", killstreakType ); + self notify( "killstreak_done", true, killstreakType ); + } + + self.usingKillstreakFromInventory = undefined; + + return true; + } + + self.usingKillstreakFromInventory = undefined; + + if ( isdefined( self ) ) + { + self notify( "killstreak_done", false, killstreakType ); + } + return false; +} + +function add_to_killstreak_count( weapon ) +{ + if ( !isdefined( self.pers["totalKillstreakCount"] ) ) + { + self.pers["totalKillstreakCount"] = 0; + } + +// The check is now done further up the stack to see if this should be counted + self.pers["totalKillstreakCount"]++; +} + +function get_first_valid_killstreak_alt_weapon( killstreakType ) +{ + assert( isdefined(level.killstreaks[killstreakType]), "Killstreak not registered."); + + if( isdefined( level.killstreaks[killstreakType].altWeapons ) ) + { + for( i = 0; i < level.killstreaks[killstreakType].altWeapons.size; i++ ) + { + if( isdefined( level.killstreaks[killstreakType].altWeapons[i] ) ) + { + return level.killstreaks[killstreakType].altWeapons[i]; + } + } + } + + return level.weaponNone; +} + +function should_give_killstreak( weapon ) +{ + if( GetDvarInt( "teamOpsEnabled" ) == 1 ) + return false; + + killstreakBuilding = GetDvarint( "scr_allow_killstreak_building" ); + + if ( killstreakBuilding == 0 ) + { + if ( killstreaks::is_weapon_associated_with_killstreak(weapon) ) + { + return false; + } + } + + return true; +} + +function point_is_in_danger_area( point, targetpos, radius ) +{ + return distance2d( point, targetpos ) <= radius * 1.25; +} + +function print_killstreak_start_text( killstreakType, owner, team, targetpos, dangerRadius ) +{ + if ( !isdefined( level.killstreaks[killstreakType] ) ) + { + return; + } + + if ( level.teambased ) + { + players = level.players; + if ( !level.hardcoreMode && isdefined(level.killstreaks[killstreakType].inboundNearPlayerText)) + { + for(i = 0; i < players.size; i++) + { + if(isalive(players[i]) && (isdefined(players[i].pers["team"])) && (players[i].pers["team"] == team)) + { + if ( point_is_in_danger_area( players[i].origin, targetpos, dangerRadius ) ) + { + players[i] iprintlnbold(level.killstreaks[killstreakType].inboundNearPlayerText); + } + } + } + } + + if ( isdefined(level.killstreaks[killstreakType]) ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + playerteam = player.pers["team"]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == team ) + { + player iprintln( level.killstreaks[killstreakType].inboundText, owner ); + } + } + } + } + } + else + { + if ( !level.hardcoreMode && isdefined(level.killstreaks[killstreakType].inboundNearPlayerText) ) + { + if ( point_is_in_danger_area( owner.origin, targetpos, dangerRadius ) ) + { + owner iprintlnbold(level.killstreaks[killstreakType].inboundNearPlayerText); + } + } + } +} + +function play_killstreak_firewall_being_hacked_dialog( killstreakType, killstreakId ) +{ + if ( self globallogic_audio::killstreak_dialog_queued( "firewallBeingHacked", killstreakType, killstreakId ) ) + { + return; + } + + self globallogic_audio::play_taacom_dialog( "firewallBeingHacked", killstreakType, killstreakId ); +} + +function play_killstreak_firewall_hacked_dialog( killstreakType, killstreakId ) +{ + if ( self globallogic_audio::killstreak_dialog_queued( "firewallHacked", killstreakType, killstreakId ) ) + { + return; + } + + self globallogic_audio::play_taacom_dialog( "firewallHacked", killstreakType, killstreakId ); +} + +function play_killstreak_being_hacked_dialog( killstreakType, killstreakId ) +{ + if ( self globallogic_audio::killstreak_dialog_queued( "beingHacked", killstreakType, killstreakId ) ) + { + return; + } + + self globallogic_audio::play_taacom_dialog( "beingHacked", killstreakType, killstreakId ); +} + +function play_killstreak_hacked_dialog( killstreakType, killstreakId, hacker ) +{ + self globallogic_audio::flush_killstreak_dialog_on_player( killstreakId ); + self globallogic_audio::play_taacom_dialog( "hacked", killstreakType ); + + excludeSelf = []; + excludeSelf[0] = self; + + if ( level.teambased ) + { + globallogic_audio::leader_dialog( level.killstreaks[killstreakType].hackedDialogKey, self.team, excludeSelf ); + globallogic_audio::leader_dialog_for_other_teams( level.killstreaks[killstreakType].hackedStartDialogKey, self.team, undefined, killstreakId ); + } + else + { + self globallogic_audio::leader_dialog_on_player( level.killstreaks[killstreakType].hackedDialogKey ); + hacker globallogic_audio::leader_dialog_on_player( level.killstreaks[killstreakType].hackedStartDialogKey ); + } +} + +function play_killstreak_start_dialog( killstreakType, team, killstreakId ) +{ + if ( !isdefined( killstreakType ) || + !isdefined( killstreakId ) ) + { + return; + } + + // Kill any waiting 'scorestreak ready' taacom threads + self notify ( "killstreak_start_" + killstreakType ); + self notify ( "killstreak_start_inventory_" + killstreakType ); + + dialogKey = level.killstreaks[killstreakType].requestDialogKey; + + if ( !isdefined( self.currentKillstreakDialog ) && isdefined( dialogKey ) && isdefined( level.heroPlayDialog ) ) + { + self thread [[level.heroPlayDialog]]( dialogKey ); + } + + excludeSelf = []; + excludeSelf[0] = self; + + if ( level.teambased ) + { + // Don't play the friendly incoming audio over your own request + globallogic_audio::leader_dialog( level.killstreaks[killstreakType].startDialogKey, team, excludeSelf, undefined, killstreakId ); + + globallogic_audio::leader_dialog_for_other_teams( level.killstreaks[killstreakType].enemyStartDialogKey, team, undefined, killstreakId ); + } + else + { + globallogic_audio::leader_dialog( level.killstreaks[killstreakType].enemyStartDialogKey, undefined, excludeSelf, undefined, killstreakId ); + } +} + +function play_killstreak_ready_sfx (killstreaktype) +{ + if ( !isdefined( level.gameEnded ) || !level.gameEnded ) + { + ready_sfx_alias = "mpl_killstreak_" + killstreaktype; + + if ( isdefined (ready_sfx_alias)) + { + self playsoundtoplayer (ready_sfx_alias, self ); + } + } +} + +function play_killstreak_ready_dialog( killstreakType, taacomWaitTime ) +{ + self notify( "killstreak_ready_" + killstreakType ); + + self endon( "death" ); + self endon( "killstreak_start_" + killstreakType ); + self endon( "killstreak_ready_" + killstreakType ); + + level endon( "game_ended" ); + + if ( isdefined( level.gameEnded ) && level.gameEnded ) + { + return; + } + + if ( globallogic_audio::killstreak_dialog_queued( "ready", killstreakType ) ) + { + return; + } + + if ( isdefined( taacomWaitTime ) ) + { + wait ( taacomWaitTime ); + } + + self globallogic_audio::play_taacom_dialog( "ready", killstreakType ); +} + +// Self is killstreak +function play_destroyed_dialog_on_owner( killstreakType, killstreakId ) +{ + if ( !isdefined( self.owner ) || + !isdefined( self.team ) || + self.team != self.owner.team ) + { + return; + } + + self.owner globallogic_audio::flush_killstreak_dialog_on_player( killstreakId ); + + self.owner globallogic_audio::play_taacom_dialog( "destroyed", killstreakType ); +} + +// Self is killstreak +function play_taacom_dialog_on_owner( dialogKey, killstreakType, killstreakId ) +{ + if ( !isdefined( self.owner ) || + !isdefined( self.team ) || + self.team != self.owner.team ) + { + return; + } + + self.owner globallogic_audio::play_taacom_dialog( dialogKey, killstreakType, killstreakId ); +} + +// Self is killstreak +function play_pilot_dialog_on_owner( dialogKey, killstreakType, killstreakId ) +{ + if ( !isdefined( self.owner ) || + !isdefined( self.owner.team ) || + !isdefined( self.team ) || + self.team != self.owner.team ) + { + return; + } + + self.owner play_pilot_dialog( dialogKey, killstreakType, killstreakId, self.pilotIndex ); +} + +// self is player +function play_pilot_dialog( dialogKey, killstreakType, killstreakId, pilotIndex ) +{ + if ( !isdefined( killstreakType ) || + !isdefined( pilotIndex ) ) + { + return; + } + + self globallogic_audio::killstreak_dialog_on_player( dialogKey, killstreakType, killstreakId, pilotIndex ); +} + +// Self is killstreak +function play_taacom_dialog_response_on_owner( dialogKey, killstreakType, killstreakId ) +{ + assert( isdefined( dialogKey ) ); + assert( isdefined( killstreakType ) ); + + if ( !isdefined( self.owner ) || + !isdefined( self.team ) || + self.team != self.owner.team ) + { + return; + } + + self.owner play_taacom_dialog_response( dialogKey, killstreakType, killstreakId, self.pilotIndex ); +} + +// self is player +function play_taacom_dialog_response( dialogKey, killstreakType, killstreakId, pilotIndex ) +{ + assert( isdefined( dialogKey ) ); + assert( isdefined( killstreakType ) ); + + if ( !isdefined( pilotIndex ) ) + { + return; + } + + self globallogic_audio::play_taacom_dialog( dialogKey + pilotIndex, killstreakType, killstreakId ); +} + + +// Self is player +function get_random_pilot_index( killstreakType ) +{ + if ( !isdefined( killstreakType ) ) + { + return undefined; + } + + taacomBundle = struct::get_script_bundle( "mpdialog_taacom", self.pers["mptaacom"] ); + + if( !isdefined( taacomBundle.pilotBundles[killstreakType] ) ) + { + return undefined; + } + + numPilots = taacomBundle.pilotBundles[killStreakType].size; + + if ( numPilots <= 0 ) + { + return undefined; + } + + return RandomInt( numPilots ); +} + +// Self is killstreak +function player_killstreak_threat_tracking( killstreakType ) +{ + assert( isdefined( killstreakType ) ); + + self endon ( "death" ); + self endon ( "delete" ); + self endon ( "leaving" ); + level endon( "game_ended" ); + + while( 1 ) + { + if ( !isdefined( self.owner ) ) + { + return; + } + + players = self.owner battlechatter::get_enemy_players(); + players = array::randomize( players ); + + foreach( player in players ) + { + if ( !player battlechatter::can_play_dialog( true ) ) + { + continue; + } + + lookAngles = player GetPlayerAngles(); + + if ( lookAngles[0] < 270 || lookAngles[0] > 330 ) + { + continue; + } + + lookDir = AnglesToForward( lookAngles ); + eyePoint = player getEye(); + + streakDir = VectorNormalize( self.origin - eyePoint ); + + dot = VectorDot( streakDir, lookDir ); + + if ( dot < 0.94 ) + { + continue; + } + + traceResult = BulletTrace( eyePoint, self.origin, true, player ); + if ( traceResult["fraction"] >= 1.0 || traceResult["entity"] === self ) + { + if ( battlechatter::dialog_chance( "killstreakSpotChance" ) ) + { + player battlechatter::play_killstreak_threat( killstreakType ); + } + wait ( battlechatter::mpdialog_value( "killstreakSpotDelay", 0 ) ); + break; + } + } + + wait ( battlechatter::mpdialog_value( "killstreakSpotInterval", .05 ) ); + } +} + +function get_killstreak_inform_dialog( killstreakType ) +{ + // please add inform dialog to killstreak + //assert( isdefined ( level.killstreaks[killstreakType].informDialog ) ); + + if ( isdefined( level.killstreaks[killstreakType].informDialog ) ) + { + return level.killstreaks[killstreakType].informDialog; + } + return ""; +} + +function get_killstreak_usage_by_killstreak(killstreakType) +{ + assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling get_killstreak_usage."); + + return get_killstreak_usage( level.killstreaks[killstreakType].usageKey ); +} + +function get_killstreak_usage(usageKey) +{ + if ( !isdefined( self.pers[usageKey] ) ) + { + return 0; + } + + return self.pers[usageKey]; +} + +function on_player_spawned() +{ + self endon("disconnect"); + + pixbeginevent("_killstreaks.gsc/onPlayerSpawned"); + + give_owned(); + + if ( !isdefined( self.pers["killstreaks"] ) ) + { + self.pers["killstreaks"] = []; + } + if ( !isdefined( self.pers["killstreak_has_been_used"] ) ) + { + self.pers["killstreak_has_been_used"] = []; + } + if ( !isdefined( self.pers["killstreak_unique_id"] ) ) + { + self.pers["killstreak_unique_id"] = []; + } + if( !isdefined( self.pers["killstreak_ammo_count"] ) ) + { + self.pers["killstreak_ammo_count"] = []; + } + + size = self.pers["killstreaks"].size; + + if ( size > 0 ) + { + self thread play_killstreak_ready_dialog( self.pers["killstreaks"][size - 1] ); + } + + self.killcamKilledByEnt = undefined; + + pixendevent(); +} + +function on_joined_team() +{ + self endon("disconnect"); + + self SetInventoryWeapon( level.weaponNone ); + self.pers["cur_kill_streak"] = 0; + self.pers["cur_total_kill_streak"] = 0; + self setplayercurrentstreak( 0 ); + self.pers["totalKillstreakCount"] = 0; + self.pers["killstreaks"] = []; + self.pers["killstreak_has_been_used"] = []; + self.pers["killstreak_unique_id"] = []; + self.pers["killstreak_ammo_count"] = []; + + if ( ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks ) ) + { + self.pers["killstreak_quantity"] = []; + self.pers["held_killstreak_ammo_count"] = []; + self.pers["held_killstreak_clip_count"] = []; + } +} + +function init_ride_killstreak( streak, always_allow = false ) +{ + self disableUsability(); + result = self init_ride_killstreak_internal( streak, always_allow ); + + if ( isdefined( self ) ) + { + self enableUsability(); + } + + return result; +} + +function watch_for_remove_remote_weapon() +{ + self endon( "endWatchForRemoveRemoteWeapon" ); + for ( ;; ) + { + self waittill( "remove_remote_weapon" ); + self killstreaks::switch_to_last_non_killstreak_weapon(); + self enableUsability(); + } +} + +function init_ride_killstreak_internal( streak, always_allow ) +{ + if ( isdefined( streak ) && ( ( streak == "qrdrone" ) || ( streak == "dart" ) || ( streak == "killstreak_remote_turret" ) || ( streak == "killstreak_ai_tank" ) || (streak == "qrdrone") || (streak == "sentinel") ) ) + { + laptopWait = "timeout"; + } + else + { + laptopWait = self util::waittill_any_timeout( 0.6, "disconnect", "death", "weapon_switch_started" ); + } + + hostmigration::waitTillHostMigrationDone(); + + if ( laptopWait == "weapon_switch_started" ) + { + return ( "fail" ); + } + + if ( !isAlive( self ) && !always_allow ) + { + return "fail"; + } + + if ( laptopWait == "disconnect" || laptopWait == "death" ) + { + if ( laptopWait == "disconnect" ) + { + return ( "disconnect" ); + } + + if ( self.team == "spectator" ) + { + return "fail"; + } + + return ( "success" ); + } + + if ( self IsEMPJammed() && !( isdefined( self.ignoreEMPJammed ) && self.ignoreEMPJammed ) ) + { + return ( "fail" ); + } + + if ( self is_interacting_with_object() ) + { + return "fail"; + } + + self thread hud::fade_to_black_for_x_sec( 0, 0.2, 0.4, 0.25 ); + self thread watch_for_remove_remote_weapon(); + blackOutWait = self util::waittill_any_timeout( 0.60, "disconnect", "death" ); + self notify( "endWatchForRemoveRemoteWeapon" ); + + hostmigration::waitTillHostMigrationDone(); + + if ( blackOutWait != "disconnect" ) + { + self thread clear_ride_intro( 1.0 ); + + if ( self.team == "spectator" ) + { + return "fail"; + } + } + + if ( always_allow ) + { + if ( blackOutWait == "disconnect" ) + { + return ( "disconnect" ); + } + else + { + return ( "success" ); + } + } + + if ( self isOnLadder() ) + { + return "fail"; + } + + if ( !isAlive( self ) ) + { + return "fail"; + } + + if ( self IsEMPJammed() && !( isdefined( self.ignoreEMPJammed ) && self.ignoreEMPJammed ) ) + { + return ( "fail" ); + } + + if ( ( isdefined( self.laststand ) && self.laststand ) ) + { + return "fail"; + } + + if ( self is_interacting_with_object() ) + { + return "fail"; + } + + if ( blackOutWait == "disconnect" ) + { + return ( "disconnect" ); + } + else + { + return ( "success" ); + } +} + +function clear_ride_intro( delay ) +{ + self endon( "disconnect" ); + + if ( isdefined( delay ) ) + wait( delay ); + + //self util::freeze_player_controls( false ); + + self thread hud::screen_fade_in( 0 ); +} + +/# + +function killstreak_debug_think() +{ + SetDvar( "debug_killstreak", "" ); + + for( ;; ) + { + cmd = GetDvarString( "debug_killstreak" ); + + switch( cmd ) + { + case "data_dump": + killstreak_data_dump(); + break; + } + + if ( cmd != "" ) + { + SetDvar( "debug_killstreak", "" ); + } + + wait( 0.5 ); + } +} + +function killstreak_data_dump() +{ + iprintln( "Killstreak Data Sent to Console" ); + println( "##### Killstreak Data #####"); + println( "killstreak,killstreaklevel,weapon,altweapon1,altweapon2,altweapon3,altweapon4,type1,type2,type3,type4" ); + + keys = GetArrayKeys( level.killstreaks ); + + for( i = 0; i < keys.size; i++ ) + { + data = level.killstreaks[ keys[i] ]; + type_data = level.killstreaktype[ keys[i] ]; + + print( keys[i] + "," ); + print( data.killstreaklevel + "," ); + print( data.weapon.name + "," ); + + alt = 0; + + if ( isdefined( data.altweapons ) ) + { + assert( data.altweapons.size <= 4 ); + + for ( alt = 0; alt < data.altweapons.size; alt++ ) + { + print( data.altweapons[alt].name + "," ); + } + } + + for ( ; alt < 4; alt++ ) + { + print( "," ); + } + + type = 0; + + if ( isdefined( type_data ) ) + { + assert( type_data.size < 4 ); + type_keys = GetArrayKeys( type_data ); + + for ( ; type < type_keys.size; type++ ) + { + if ( type_data[ type_keys[type] ] == 1 ) + { + print( type_keys[type] + "," ); + } + } + } + + for ( ; type < 4; type++ ) + { + print( "," ); + } + + println( "" ); + } + + println( "##### End Killstreak Data #####"); +} + +#/ + + +function is_interacting_with_object() +{ + if ( self isCarryingTurret() ) + { + return true; + } + if ( ( isdefined( self.isPlanting ) && self.isPlanting ) ) + { + return true; + } + if ( ( isdefined( self.isDefusing ) && self.isDefusing ) ) + { + return true; + } + + return false; +} + + +function clear_using_remote( immediate, skipNotify ) +{ + if ( !isdefined( self ) ) + { + return; + } + + self.dofutz = false; + self.no_fade2black = false; + self clientfield::set_to_player( "static_postfx", 0 ); + + if ( isdefined( self.carryIcon ) ) + { + self.carryIcon.alpha = 1; + } + + self.usingRemote = undefined; + self reset_killstreak_delay_killcam(); + self enableOffhandWeapons(); + self enableWeaponCycling(); + + curWeapon = self getCurrentWeapon(); + + if ( isalive( self ) ) + { + self killstreaks::switch_to_last_non_killstreak_weapon( immediate ); + } + + if( !level.gameEnded ) + self util::freeze_player_controls( false ); + if( !( isdefined( skipNotify ) && skipNotify )) + self notify( "stopped_using_remote" ); + + thread hide_tablet(); +} + +function hide_tablet() +{ + self endon("disconnect"); + wait .2; + self clientfield::set_player_uimodel( "hudItems.remoteKillstreakActivated", 0 ); +} + +function set_killstreak_delay_killcam( killstreak_name ) +{ + self.killstreak_delay_killcam = killstreak_name; +} + +function reset_killstreak_delay_killcam() // self == player +{ + self.killstreak_delay_killcam = undefined; +} + +function hide_compass() +{ + self clientfield::set( "killstreak_hides_compass", 1 ); +} + +function unhide_compass() +{ + self clientfield::set( "killstreak_hides_compass", 0 ); +} + +function setup_health( killstreak_ref, max_health, low_health ) +{ + self.maxhealth = max_health; + self.lowhealth = low_health; + + self.hackedHealthUpdateCallback = &defaultHackedHealthUpdateCallback; + + tableMaxHealth = killstreak_bundles::get_max_health( killstreak_ref ); + + if ( isdefined( tableMaxHealth ) ) + { + self.maxhealth = tableMaxHealth; + } + + tableLowHealth = killstreak_bundles::get_low_health( killstreak_ref ); + + if ( isdefined( tableLowHealth ) ) + { + self.lowhealth = tableLowHealth; + } + + tableHackedHealth = killstreak_bundles::get_hacked_health( killstreak_ref ); + + if ( isdefined( tableHackedHealth ) ) + { + self.hackedHealth = tableHackedHealth; + } + else + { + self.hackedHealth = self.maxhealth; + } +} + +function MonitorDamage( killstreak_ref, + max_health, destroyed_callback, + low_health, low_health_callback, + emp_damage, emp_callback, + allow_bullet_damage ) +{ + self endon( "death" ); + self endon( "delete" ); + + self.health = 9999999; + self.damageTaken = 0; + + self setup_health( killstreak_ref, max_health, low_health ); + + assert( ( !IsVehicle( self ) || !IsSentient( self ) ), "MonitorDamage should not be called on a sentient vehicle. For sentient vehicles, use overrideVehicleDamage instead."); + + while( true ) + { + weapon_damage = undefined; + // this damage is done to self.health which isnt used to determine the helicopter's health, damageTaken is. + self waittill( "damage", damage, attacker, direction, point, type, tagName, modelName, partname, weapon, flags, inflictor, chargeLevel ); + + if( ( isdefined( self.invulnerable ) && self.invulnerable ) ) + { + continue; + } + + if( !isdefined( attacker ) || !isplayer( attacker ) ) + { + continue; + } + + friendlyfire = weaponobjects::friendlyFireCheck( self.owner, attacker ); + if( !friendlyfire ) + { + continue; + } + + if( isdefined( self.owner ) && attacker == self.owner ) + { + continue; + } + + isValidAttacker = true; + if( level.teambased ) + { + isValidAttacker = ( isdefined( attacker.team ) && attacker.team != self.team ); + } + + if( !isValidAttacker ) + { + continue; + } + + if ( isdefined( self.killstreakDamageModifier ) ) + { + damage = [[self.killstreakDamageModifier]]( damage, attacker, direction, point, type, tagName, modelName, partname, weapon, flags, inflictor, chargeLevel ); + if ( damage <= 0 ) + continue; + } + + if( weapon.isEmp && type == "MOD_GRENADE_SPLASH" ) + { + emp_damage_to_apply = killstreak_bundles::get_emp_grenade_damage( killstreak_ref, self.maxhealth ); + + if ( !isdefined( emp_damage_to_apply ) ) + emp_damage_to_apply = ( isdefined( emp_damage ) ? emp_damage : 1 ); + + if( isdefined( emp_callback ) && emp_damage_to_apply > 0 ) + { + self [[ emp_callback ]]( attacker ); + } + + weapon_damage = emp_damage_to_apply; + } + + if ( ( isdefined( self.selfDestruct ) && self.selfDestruct ) ) + { + weapon_damage = self.maxhealth + 1; + } + + if ( !isdefined( weapon_damage ) ) + { + weapon_damage = killstreak_bundles::get_weapon_damage( killstreak_ref, self.maxhealth, attacker, weapon, type, damage, flags, chargeLevel ); + + if ( !isdefined( weapon_damage ) ) + { + weapon_damage = get_old_damage( attacker, weapon, type, damage, allow_bullet_damage ); + } + } + + if ( weapon_damage > 0 ) + { + if( damagefeedback::doDamageFeedback( weapon, attacker ) ) + { + attacker thread damagefeedback::update( type ); + } + + self challenges::trackAssists( attacker, weapon_damage, false ); + } + + self.damageTaken += weapon_damage; + + if ( !IsSentient( self ) && weapon_damage > 0 ) + self.attacker = attacker; + + if( self.damageTaken > self.maxhealth ) + { + weaponStatName = "destroyed"; + switch( weapon.name ) + { + case "auto_tow": + case "tow_turret": + case "tow_turret_drop": + weaponStatName = "kills"; + break; + } + + level.globalKillstreaksDestroyed++; + attacker AddWeaponStat( GetWeapon( killstreak_ref ), "destroyed", 1 ); + + if( isdefined( destroyed_callback ) ) + { + self thread [[ destroyed_callback ]]( attacker, weapon ); + } + + return; + } + + remaining_health = ( max_health - self.damageTaken ); + + if( ( remaining_health < low_health ) && weapon_damage > 0 ) + { + if( isdefined( low_health_callback ) && ( !isdefined( self.currentState ) || self.currentState != "damaged" ) ) + { + self [[ low_health_callback ]]( attacker, weapon ); + } + + self.currentstate = "damaged"; + } + + if( isdefined( self.extra_low_health ) && ( remaining_health < self.extra_low_health ) && weapon_damage > 0 ) + { + if( isdefined( self.extra_low_health_callback ) && ( !isdefined( self.extra_low_damage_notified ) ) ) + { + self [[ self.extra_low_health_callback ]]( attacker, weapon ); + + self.extra_low_damage_notified = true; + } + } + } +} + +function defaultHackedHealthUpdateCallback( hacker ) +{ + killstreak = self; + + assert( isdefined( self.maxHealth ) ); + assert( isdefined( self.hackedHealth ) ); + assert( isdefined( self.damageTaken ) ); + + damageAfterHacking = self.maxHealth - self.hackedHealth; + if ( self.damageTaken < damageAfterHacking ) + { + self.damageTaken = damageAfterHacking; + } +} + +function OnDamagePerWeapon( killstreak_ref, + attacker, damage, flags, type, weapon, + max_health, destroyed_callback, + low_health, low_health_callback, + emp_damage, emp_callback, + allow_bullet_damage, chargeLevel ) +{ + self.maxhealth = max_health; + self.lowhealth = low_health; + + tableHealth = killstreak_bundles::get_max_health( killstreak_ref ); + + if ( isdefined( tableHealth ) ) + { + self.maxhealth = tableHealth; + } + + tableHealth = killstreak_bundles::get_low_health( killstreak_ref ); + + if ( isdefined( tableHealth ) ) + { + self.lowhealth = tableHealth; + } + + if( ( isdefined( self.invulnerable ) && self.invulnerable ) ) + { + return 0; + } + + if( !isdefined( attacker ) || !isplayer( attacker ) ) + { + return get_old_damage( attacker, weapon, type, damage, allow_bullet_damage ); + } + + friendlyfire = weaponobjects::friendlyFireCheck( self.owner, attacker ); + if( !friendlyfire ) + { + return 0; + } + + isValidAttacker = true; + if( level.teambased ) + { + isValidAttacker = ( isdefined( attacker.team ) && attacker.team != self.team ); + } + + if( !isValidAttacker ) + { + return 0; + } + + if( weapon.isEmp && type == "MOD_GRENADE_SPLASH" ) + { + emp_damage_to_apply = killstreak_bundles::get_emp_grenade_damage( killstreak_ref, self.maxhealth ); + + if ( !isdefined( emp_damage_to_apply ) ) + emp_damage_to_apply = ( isdefined( emp_damage ) ? emp_damage : 1 ); + + if( isdefined( emp_callback ) && emp_damage_to_apply > 0 ) + { + self [[ emp_callback ]]( attacker, weapon ); + } + + return emp_damage_to_apply; + } + + weapon_damage = killstreak_bundles::get_weapon_damage( killstreak_ref, self.maxhealth, attacker, weapon, type, damage, flags, chargeLevel ); + + if ( !isdefined( weapon_damage ) ) + { + weapon_damage = get_old_damage( attacker, weapon, type, damage, allow_bullet_damage ); + } + + if ( weapon_damage <= 0 ) + { + return 0; + } + + iDamage = int( weapon_damage ); + if( iDamage > self.health ) + { + if( isdefined( destroyed_callback ) ) + { + self thread [[ destroyed_callback ]]( attacker, weapon ); + } + } + + return iDamage; +} + +function get_old_damage( attacker, weapon, type, damage, allow_bullet_damage) +{ + switch( type ) + { + case "MOD_RIFLE_BULLET": + case "MOD_PISTOL_BULLET": + { + if( !allow_bullet_damage ) + { + damage = 0; + break; + } + + if ( isdefined( attacker ) && isplayer( attacker ) ) + { + hasFMJ = attacker HasPerk( "specialty_armorpiercing" ); + } + + if ( ( isdefined( hasFMJ ) && hasFMJ ) ) + { + damage = int( damage * level.cac_armorpiercing_data ); + } + } + break; + + case "MOD_PROJECTILE": + case "MOD_EXPLOSIVE": + case "MOD_PROJECTILE_SPLASH": + if ( ( weapon.statIndex == level.weaponPistolEnergy.statIndex ) || ( weapon.statIndex != level.weaponShotgunEnergy.statIndex ) || ( weapon.statIndex == level.weaponSpecialCrossbow.statIndex ) ) + break; + + if( isdefined( self.remoteMissileDamage ) && isdefined( weapon ) && weapon.name == "remote_missile_missile") + { + damage = self.remoteMissileDamage; + } + else if( isdefined( self.rocketDamage ) ) + { + damage = self.rocketDamage; + } + break; + default: + break; + } + + return damage; +} + + + +function configure_team( killstreakType, killstreakId, owner, influencerType, configureTeamPreFunction, configureTeamPostFunction, isHacked = false ) +{ + killstreak = self; + + killstreak.killstreakType = killstreakType; + killstreak.killstreakId = killstreakId; + killstreak _setup_configure_team_callbacks( influencerType, configureTeamPreFunction, configureTeamPostFunction ); + killstreak configure_team_internal( owner, isHacked ); + + owner thread trackActiveKillstreak( killstreak ); +} + + +function trackActiveKillstreak( killstreak ) +{ + self endon( "disconnect" ); + + killstreakIndex = killstreak.killstreakID; + if( isdefined( killstreakIndex ) ) + { + self.pers["activeKillstreaks"][ killstreakIndex ] = killstreak; + + killstreak util::waittill_any( "killstreak_hacked", "death" ); + + self.pers["activeKillstreaks"][ killstreakIndex ] = undefined; + } +} + +function getActiveKillstreaks() +{ + return self.pers["activeKillstreaks"]; +} + +function configure_team_internal( owner, isHacked ) +{ + killstreak = self; + if ( isHacked == false ) + { + killstreak.originalOwner = owner; + killstreak.originalteam = owner.team; +/# + killstreak thread killstreak_hacking::killstreak_switch_team( owner ); +#/ + } + else + { + assert( killstreak.killstreakTeamConfigured, "configure_team must be called before a killstreak can be hacked" ); + } + + if ( isdefined( killstreak.killstreakConfigureTeamPreFunction ) ) + { + killstreak thread [[killstreak.killstreakConfigureTeamPreFunction]]( owner, ishacked ); + } + + if ( isdefined( killstreak.killstreakInfluencerType ) ) + { + killstreak spawning::remove_influencers(); + } + + killstreak SetTeam( owner.team ); + killstreak.team = owner.team; + if ( !IsAI( killstreak ) ) + { + killstreak SetOwner( owner ); + } + killstreak.owner = owner; + killstreak.ownerEntnum = owner.entnum; + + killstreak.pilotIndex = killstreak.owner get_random_pilot_index( killstreak.killstreakType ); + + if ( isdefined( killstreak.killstreakInfluencerType ) ) + { + killstreak spawning::create_entity_enemy_influencer( killstreak.killstreakInfluencerType, owner.team ); + } + + if ( isdefined( killstreak.killstreakConfigureTeamPostFunction ) ) + { + killstreak thread [[killstreak.killstreakConfigureTeamPostFunction]]( owner, ishacked ); + } +} + +function private _setup_configure_team_callbacks( influencerType, configureTeamPreFunction, configureTeamPostFunction ) +{ + killstreak = self; + + killstreak.killstreakTeamConfigured = true; + killstreak.killstreakInfluencerType = influencerType; + killstreak.killstreakConfigureTeamPreFunction = configureTeamPreFunction; + killstreak.killstreakConfigureTeamPostFunction = configureTeamPostFunction; +} + + +function WatchTeamChange( teamChangeNotify ) +{ + self notify( teamChangeNotify+ "_Singleton" ); + self endon ( teamChangeNotify+ "_Singleton" ); + + killstreak = self; + killstreak endon( "death" ); + + killstreak endon( teamChangeNotify ); + killstreak.owner util::waittill_any( "joined_team", "disconnect", "joined_spectators", "emp_jammed" ); + killstreak notify( teamChangeNotify ); +} + +function should_not_timeout( killstreak ) +{ +/# + assert( isdefined(killstreak), "Can not register a killstreak without a valid type name."); + assert( isdefined(level.killstreaks[killstreak]), "Killstreak needs to be registered before calling register_dev_dvar."); + + if ( isdefined( level.killstreaks[killstreak].devTimeoutDvar ) ) + return GetDvarInt( level.killstreaks[killstreak].devTimeoutDvar ); +#/ + + return false; +} + +function WaitForTimeout( killstreak, duration, callback, endCondition1, endCondition2, endCondition3 ) +{ +/# + if (killstreaks::should_not_timeout(killstreak)) + { + return; + } +#/ + self endon( "killstreak_hacked" ); + + if( isdefined( endCondition1 ) ) + self endon( endCondition1 ); + if( isdefined( endCondition2 ) ) + self endon( endCondition2 ); + if( isdefined( endCondition3 ) ) + self endon( endCondition3 ); + + self thread waitForTimeoutHacked( killstreak, callback, endCondition1, endCondition2, endCondition3 ); + + killstreakBundle = level.killstreakBundle[self.killstreakType]; + self.killstreakEndTime = getTime() + duration; + if ( isdefined( killstreakBundle ) && isdefined( killstreakBundle.ksTimeoutBeepDuration ) ) + { + self WaitForTimeoutBeep( killstreakBundle, duration ); + } + else + { + hostmigration::MigrationAwareWait( duration ); + } + + self notify( "kill_WaitForTimeoutHacked_thread" ); + self.killstreakTimedOut = true; + self.killstreakEndTime = 0; + self notify( "timed_out" ); + self [[ callback ]](); +} + + +function WaitForTimeoutBeep( killstreakBundle, duration ) +{ + self endon("death"); + beepDuration = killstreakBundle.ksTimeoutBeepDuration * 1000; + hostmigration::MigrationAwareWait( max( duration - beepDuration, 0 ) ); + + if ( IsVehicle( self ) ) + { + self clientfield::set( "timeout_beep", 1 ); + } + + if ( isdefined( killstreakBundle.ksTimeoutFastBeepDuration ) ) + { + fastBeepDuration = killstreakBundle.ksTimeoutFastBeepDuration * 1000; + hostmigration::MigrationAwareWait( max( beepDuration - fastBeepDuration, 0 ) ); + + if ( IsVehicle( self ) ) + { + self clientfield::set( "timeout_beep", 2 ); + } + + hostmigration::MigrationAwareWait( fastBeepDuration ); + } + + if ( IsVehicle( self ) ) + { + self clientfield::set( "timeout_beep", 0 ); + } +} + + +function WaitForTimeoutHacked( killstreak, callback, endCondition1, endCondition2, endCondition3 ) +{ + self endon( "kill_WaitForTimeoutHacked_thread" ); + + if( isdefined( endCondition1 ) ) + self endon( endCondition1 ); + if( isdefined( endCondition2 ) ) + self endon( endCondition2 ); + if( isdefined( endCondition3 ) ) + self endon( endCondition3 ); + + self waittill( "killstreak_hacked" ); + + hackedDuration = self killstreak_hacking::get_hacked_timeout_duration_ms(); + self.killstreakEndTime = getTime() + hackedDuration; + hostmigration::MigrationAwareWait( hackedDuration ); + self.killstreakEndTime = 0; + self notify( "timed_out" ); + self [[ callback ]](); +} + +function update_player_threat( player ) +{ + heli = self; + + player.threatlevel = 0; + + // distance factor + dist = distance( player.origin, heli.origin ); + player.threatlevel += ( ( level.heli_visual_range - dist ) / level.heli_visual_range ) * 100; // inverse distance % with respect to helicopter targeting range + + // behavior factor + if( isdefined( heli.attacker ) && player == heli.attacker ) + player.threatlevel += 100; + + if( isdefined( player.carryObject ) ) //flag carrier + player.threatlevel += 200; + + // player score factor + if( isdefined( player.score ) ) + player.threatlevel += player.score * 2; + + if( player weapons::has_launcher() ) + { + if( player weapons::has_lockon( heli ) ) + player.threatlevel += 1000; + else + player.threatlevel += 500; + } + + if( player weapons::has_hero_weapon() ) + player.threatlevel += 300; + + if( player weapons::has_lmg() ) + player.threatlevel += 200; + + if( isdefined( player.antithreat ) ) + player.threatlevel -= player.antithreat; + + if( player.threatlevel <= 0 ) + player.threatlevel = 1; +} + +function update_non_player_threat( non_player ) +{ + heli = self; + + non_player.threatlevel = 0; + + // distance factor + dist = distance( non_player.origin, heli.origin ); + non_player.threatlevel += ( ( level.heli_visual_range - dist ) / level.heli_visual_range ) * 100; // inverse distance % with respect to helicopter targeting range + + if( non_player.threatlevel <= 0 ) + non_player.threatlevel = 1; +} + +function update_actor_threat( actor ) +{ + heli = self; + actor.threatlevel = 0; + + // distance factor + dist = distance( actor.origin, heli.origin ); + actor.threatlevel += ( ( level.heli_visual_range - dist ) / level.heli_visual_range ) * 100; // inverse distance % with respect to helicopter targeting range + + // player score factor + if( isdefined( actor.owner ) ) + { + // behavior factor + if( isdefined( heli.attacker ) && actor.owner == heli.attacker ) + actor.threatlevel += 100; + + if( isdefined( actor.owner.carryObject ) ) //flag carrier + actor.threatlevel += 200; + + if( isdefined( actor.owner.score ) ) + actor.threatlevel += actor.owner.score * 4; + + if( isdefined( actor.owner.antithreat ) ) + actor.threatlevel -= actor.owner.antithreat; + } + + if( actor.threatlevel <= 0 ) + actor.threatlevel = 1; +} + +function update_dog_threat( dog ) +{ + heli = self; + dog.threatlevel = 0; + + // distance factor + dist = distance( dog.origin, heli.origin ); + dog.threatlevel += ( ( level.heli_visual_range - dist ) / level.heli_visual_range ) * 100; // inverse distance % with respect to helicopter targeting range +} + +// check if missile is in hittable sight zone +function missile_valid_target_check( missiletarget ) +{ + heli2target_normal = vectornormalize( missiletarget.origin - self.origin ); + heli2forward = anglestoforward( self.angles ); + heli2forward_normal = vectornormalize( heli2forward ); + + heli_dot_target = vectordot( heli2target_normal, heli2forward_normal ); + + if ( heli_dot_target >= level.heli_valid_target_cone ) + { + return true; + } + return false; +} + +function update_missile_player_threat( player ) +{ + player.missilethreatlevel = 0; + + // distance factor + dist = distance( player.origin, self.origin ); + player.missilethreatlevel += ( (level.heli_missile_range - dist)/level.heli_missile_range )*100; // inverse distance % with respect to helicopter targeting range + + + if( self missile_valid_target_check( player ) == false ) + { + player.missilethreatlevel = 1; + return; + } + + // behavior factor + if ( isdefined( self.attacker ) && player == self.attacker ) + player.missilethreatlevel += 100; + + // player score factor + player.missilethreatlevel += player.score*4; + + if( isdefined( player.antithreat ) ) + player.missilethreatlevel -= player.antithreat; + + if( player.missilethreatlevel <= 0 ) + player.missilethreatlevel = 1; +} + +// threat missile factors +function update_missile_dog_threat( dog ) +{ + dog.missilethreatlevel = 1; +} + +function killstreak_assist(victim, assister, killstreak) +{ + victim RecordKillstreakAssist(victim, assister, killstreak); +} + +function add_ricochet_protection( killstreak_id, owner, origin, ricochet_distance ) +{ + testing = false; + +/# + testing = ( GetDvarInt( "scr_ricochet_protection_debug", 0 ) == 2 ); +#/ + + if ( !level.hardcoreMode && !testing ) + return; + + if ( !isdefined( ricochet_distance ) || ricochet_distance == 0 ) + return; + + if(!isdefined(owner.ricochet_protection))owner.ricochet_protection=[]; + + owner.ricochet_protection[ killstreak_id ] = SpawnStruct(); + owner.ricochet_protection[ killstreak_id ].origin = origin; + owner.ricochet_protection[ killstreak_id ].distanceSq = ( (ricochet_distance) * (ricochet_distance) ); +} + +function set_ricochet_protection_endtime( killstreak_id, owner, endTime ) +{ + if ( !isdefined( owner ) || !isdefined( owner.ricochet_protection ) || !isdefined( killstreak_id ) ) + return; + + if ( !isdefined( owner.ricochet_protection[ killstreak_id ] ) ) + return; + + owner.ricochet_protection[ killstreak_id ].endTime = endTime; +} + +function remove_ricochet_protection( killstreak_id, owner ) +{ + if ( !isdefined( owner ) || !isdefined( owner.ricochet_protection ) || !isdefined( killstreak_id ) ) + return; + + owner.ricochet_protection[ killstreak_id ] = undefined; +} + +function is_ricochet_protected( player ) +{ + if ( !isdefined( player ) || !isdefined( player.ricochet_protection ) ) + return false; + + foreach( protection in player.ricochet_protection ) + { + if ( !isdefined( protection ) ) + continue; + + if ( isdefined( protection.endTime ) && protection.endTime < GetTime() ) + continue; + + if ( DistanceSquared( protection.origin, player.origin ) < protection.distanceSq ) + return true; + } + + return false; +} + +function is_killstreak_start_blocked() +{ + return ( isdefined( self.dart_thrown_time ) && ( GetTime() - self.dart_thrown_time < 1500 ) ); +} + +/# +function debug_ricochet_protection() +{ + debug_wait = 0.5; + debug_frames = int( ( debug_wait / .05 ) ) + 1; + + while( 1 ) + { + if ( GetDvarInt( "scr_ricochet_protection_debug", 0 ) == 0 ) + { + wait 2.0; + continue; + } + + wait debug_wait; + + foreach( player in level.players ) + { + if ( !isdefined( player ) ) + continue; + + if ( !isdefined( player.ricochet_protection ) ) + continue; + + foreach( protection in player.ricochet_protection ) + { + if ( !isdefined( protection ) ) + continue; + + if ( isdefined( protection.endTime ) && protection.endTime < GetTime() ) + continue; + + radius = sqrt( protection.distanceSq ); + sphere( protection.origin, radius, ( 1, 1, 0 ), 0.25, false, 36, debug_frames ); + circle( protection.origin, radius, ( 1, .5, 0 ), false, true, debug_frames ); + circle( protection.origin + ( 0, 0, 2 ), radius, ( 1, .5, 0 ), false, true, debug_frames ); + } + } + } +} +#/ \ No newline at end of file diff --git a/mp/killstreaks/_killstreaks.gsh b/mp/killstreaks/_killstreaks.gsh new file mode 100644 index 0000000..66adbda --- /dev/null +++ b/mp/killstreaks/_killstreaks.gsh @@ -0,0 +1,348 @@ +//Killstreak Rules + + + + + + +//Hellfire missile + + +//Hellfire missile Bomblets + + + +//AI_TANK + + + + + +//TURRET + + + + + + + + + + + + + + + + +//MICROWAVE TURRET + + + + + + + + + + + + + + + + + + + + +//UAV + + + + + + + + + + + + + +//CUAV + + + + + + + + //Percentage of minimap coords to generate valid points from + + + + + + + + + + + + +//SATELLITE + + + + + + + + + // spawns from an angle relative to map center (min) + // spawns from an angle relative to map center (max) + // the scale of the distance traveled for DURATION_MS + +//RAPS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//RAPS HELICOPTER DEPLOY POINT SELECTION + + + + + + + +//RAPS HELICOPTER AVOIDANCE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// RAPS damage/death fx + + + + + + + + + + +//EMP + + + + + + + + + +//DRONE STRIKE + + + + + + + + + + + + +//HELICOPTER + + + + + + + +//FLAK DRONE + //Spawn offset from parent + + + + + + + + + + + + + + + +//HELICOPTER GUNNER + + + + + + + + + + + + + + + + + + + + + + +//DART + + + + //Time in seconds to watch the death of the dart + + + + //Offset from qrdrone_height node before out of range notification/shutdown + + + + +//SENTINEL + + + + + + + + //2D distance between sentinel and enemy to begin combat state + //Minimum distance between sentinel and enemy during combat + //Maximum distance between sentinel and enemy during combat + //Time to wait after unsuccessful burst fire attempt ( sight line / distance check ) + //Min time to wait after reaching goal in unaware state + //Max time to wait after reaching goal in unaware state + //Minimum radius from player for unaware target pos selection + //Maximum radius from player for unaware target pos selection + //Minimum spacing between valid target pos points + //Height above sentinel owner for unaware target pos selection + //Variance +/- in height from SENTINEL_HOVER_HEIGHT + //Minimum height for combat position selection + //Maximum height for combat position selection + //Time to wait before attacking enemy after being shot + //When in combat, if the owner exceeds this 2d distance, the sentinel will breakaway from combat + //Offset from qrdrone_height node before out of range notification/shutdown + //Vertical offset from rcbomb spawn height + //Percentage of healthDefault ( set in veh gdt ) that each emp grenade will damage + //Number of missiles required to destroy + +//RCBOMB + + + + + + + + + + +//COMBAT ROBOT + + + +//HELICOPTER COMLINK + + +//HIDE COMPASS (aka minimap) + + + + + + + + + + + + + + + + + + + + + + diff --git a/mp/killstreaks/_microwave_turret.csc b/mp/killstreaks/_microwave_turret.csc new file mode 100644 index 0000000..39d1caa --- /dev/null +++ b/mp/killstreaks/_microwave_turret.csc @@ -0,0 +1,493 @@ +#using scripts\codescripts\struct; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + +#precache( "client_fx", "killstreaks/fx_sg_distortion_cone_ash" ); +#precache( "client_fx", "killstreaks/fx_sg_distortion_cone_ash_sm" ); + +#using_animtree( "mp_microwaveturret" ); + + + + + + + + + + + +#namespace microwave_turret; + +function autoexec __init__sytem__() { system::register("microwave_turret",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "vehicle", "turret_microwave_open", 1, 1, "int", µwave_open, !true, !true ); + clientfield::register( "scriptmover", "turret_microwave_init", 1, 1, "int", µwave_init_anim, !true, !true ); + clientfield::register( "scriptmover", "turret_microwave_close", 1, 1, "int", µwave_close_anim, !true, !true ); +} + +function turret_microwave_sounds( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if( newVal == ( 1 ) ) + { + self thread turret_microwave_sound_start( localClientNum ); + } + else if( newVal == ( 0 ) ) + { + self notify( "sound_stop" ); + } +} + +function turret_microwave_sound_start( localClientNum ) +{ + self endon( "entityshutdown" ); + self endon( "sound_stop" ); + + if ( ( isdefined( self.sound_loop_enabled ) && self.sound_loop_enabled ) ) + return; + + self playsound ( 0, "wpn_micro_turret_start"); + + wait 0.7; // wait until deploy animation is finished + + origin = self GetTagOrigin( "tag_flash" ); + angles = self GetTagAngles( "tag_flash" ); + + forward = AnglesToForward( angles ); + forward = VectorScale( forward, 750 ); + + trace = BulletTrace( origin, origin + forward, false, self ); + + start = origin; + end = trace[ "position" ]; + + self.microwave_audio_start = start; + self.microwave_audio_end = end; + self thread turret_microwave_sound_updater(); + + if ( !( isdefined( self.sound_loop_enabled ) && self.sound_loop_enabled ) ) + { + self.sound_loop_enabled = true; + soundLineEmitter( "wpn_micro_turret_loop", self.microwave_audio_start, self.microwave_audio_end ); + self thread turret_microwave_sound_off_waiter( localClientNum ); + } +} + +function turret_microwave_sound_off_waiter( localClientNum ) +{ + msg = self util::waittill_any( "sound_stop", "entityshutdown" ); + + if ( ( msg === "sound_stop" ) ) + playsound (0, "wpn_micro_turret_stop", self.microwave_audio_start); + + soundStopLineEmitter ( "wpn_micro_turret_loop", self.microwave_audio_start, self.microwave_audio_end ); + + if ( isdefined( self ) ) + { + self.sound_loop_enabled = false; + } +} + +function turret_microwave_sound_updater() +{ + self endon( "beam_stop" ); + self endon( "entityshutdown" ); + + while( 1 ) + { + origin = self GetTagOrigin( "tag_flash" ); + + if ( origin[0] != self.microwave_audio_start[0] || origin[1] != self.microwave_audio_start[1] || origin[2] != self.microwave_audio_start[2] ) + { + previousStart = self.microwave_audio_start; + previousEnd = self.microwave_audio_end; + + angles = self GetTagAngles( "tag_flash" ); + + forward = AnglesToForward( angles ); + forward = VectorScale( forward, 750 ); + + trace = BulletTrace( origin, origin + forward, false, self ); + + self.microwave_audio_start = origin; + self.microwave_audio_end = trace[ "position" ]; + + soundUpdateLineEmitter( "wpn_micro_turret_loop", previousStart, previousEnd, self.microwave_audio_start, self.microwave_audio_end ); + } + + wait 0.1; + } +} + +function microwave_init_anim( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( !newVal ) + return; + + self UseAnimTree( #animtree ); + self SetAnimRestart( %o_turret_guardian_close, 1.0, 0.0, 1.0 ); + self SetAnimTime( %o_turret_guardian_close, 1.0 ); +} + +function microwave_open( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( !newVal ) + { + self UseAnimTree( #animtree ); + self SetAnim( %o_turret_guardian_open, 0.0 ); // stop open anim + self SetAnimRestart( %o_turret_guardian_close, 1.0, 0.0, 1.0 ); + + self notify( "beam_stop" ); + self notify( "sound_stop" ); + return; + } + + self UseAnimTree( #animtree ); + self SetAnim( %o_turret_guardian_close, 0.0 ); // stop close anim + self SetAnimRestart( %o_turret_guardian_open, 1.0, 0.0, 1.0 ); + + self thread StartMicrowaveFx(localClientNum); +} + +function microwave_close_anim( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( !newVal ) + return; + + self UseAnimTree( #animtree ); + self SetAnimRestart( %o_turret_guardian_close, 1.0, 0.0, 1.0 ); +} + +/# +function debug_trace(origin, trace) +{ + if ( trace[ "fraction" ] < 1.0 ) + { + color = ( 0.95, 0.05, 0.05 ); + } + else + { + color = ( 0.05, 0.95, 0.05 ); + } + + Sphere( trace[ "position" ], 5, color, 0.75, true, 10, 100 ); + util::debug_line( origin, trace[ "position" ], color, 100 ); +} +#/ + +function StartMicrowaveFx( localClientNum ) +{ + turret = self; + turret endon( "entityshutdown" ); + turret endon( "beam_stop" ); + + turret.should_update_fx = true; + + self thread turret_microwave_sound_start( localClientNum ); + + origin = turret GetTagOrigin( "tag_flash" ); + angles = turret GetTagAngles( "tag_flash" ); + microwaveFXEnt = spawn(localClientNum, origin, "script_model"); + microwaveFXEnt SetModel("tag_microwavefx"); + microwaveFXEnt.angles = angles; + microwaveFXEnt linkto(turret, "tag_flash"); + + microwaveFXEnt.fxHandles = []; + microwaveFXEnt.fxNames = []; + microwaveFXEnt.fxHashs = []; + + self thread UpdateMicrowaveAim( microwaveFXEnt ); + self thread CleanupFx( localClientNum, microwaveFXEnt ); + wait 0.3; + + while( true ) + { +/# + if ( GetDvarInt( "scr_microwave_turret_fx_debug" ) ) + { + turret.should_update_fx = true; + microwaveFXEnt.fxHashs["center"] = 0; + } +#/ + if ( turret.should_update_fx == false ) + { + wait ( 1.0 ); + continue; + } + + // limit traces per frame when there are multiple microwave turrets on the field + if ( isdefined( level.last_microwave_turret_fx_trace ) && level.last_microwave_turret_fx_trace == GetTime() ) + { + wait(0.05); + + // /# IPrintLnBold( "Delaying microwave turret fx! Time: " + GetTime() ); #/ + continue; + } + + angles = turret GetTagAngles( "tag_flash" ); + origin = turret GetTagOrigin( "tag_flash" ); + forward = AnglesToForward( angles ); + forward = VectorScale( forward, ( ( 750 ) + 40 ) ); + forwardRight = AnglesToForward( angles - (0, ( 55 ) / 3, 0) ); + forwardRight = VectorScale( forwardRight, ( ( 750 ) + 40 ) ); + forwardLeft = AnglesToForward( angles + (0, ( 55 ) / 3, 0) ); + forwardLeft = VectorScale( forwardLeft, ( ( 750 ) + 40 ) ); + + trace = BulletTrace( origin, origin + forward, false, turret ); + traceRight = BulletTrace( origin, origin + forwardRight, false, turret ); + traceLeft = BulletTrace( origin, origin + forwardLeft, false, turret ); + +/# + if ( GetDvarInt( "scr_microwave_turret_fx_debug" ) ) + { + debug_trace( origin, trace ); + debug_trace( origin, traceRight ); + debug_trace( origin, traceLeft ); + } +#/ + + need_to_rebuild = microwaveFXEnt MicrowaveFxHash( trace, origin, "center" ); + need_to_rebuild |= microwaveFXEnt MicrowaveFxHash( traceRight, origin, "right" ); + need_to_rebuild |= microwaveFXEnt MicrowaveFxHash( traceLeft, origin, "left" ); + + level.last_microwave_turret_fx_trace = getTime(); + + if( !need_to_rebuild ) + { + wait ( 1.0 ); + continue; + } + + wait( 0.1 ); + + microwaveFXEnt PlayMicrowaveFx( localClientNum, trace, traceRight, TraceLeft, origin ); + + turret.should_update_fx = false; + wait ( 1.0 ); + } +} + +function UpdateMicrowaveAim( microwaveFXEnt ) +{ + turret = self; + turret endon( "entityshutdown" ); + turret endon( "beam_stop" ); + + last_angles = turret GetTagAngles( "tag_flash" ); + + while( true ) + { + angles = turret GetTagAngles( "tag_flash" ); + + if ( last_angles != angles ) + { + turret.should_update_fx = true; + last_angles = angles; + } + + wait 0.1; + } +} + +function MicrowaveFxHash( trace, origin, name ) +{ + hash = 0; + counter = 2; + for ( i = 0; i < 5; i++ ) + { + endOfHalfFxSq = ( (( i * ( 150 ) ) + ( 125 )) * (( i * ( 150 ) ) + ( 125 )) ); + endOfFullFxSq = ( (( i * ( 150 ) ) + ( 200 )) * (( i * ( 150 ) ) + ( 200 )) ); + + traceDistSq = DistanceSquared( origin, trace[ "position" ] ); + if( traceDistSq >= endOfHalfFxSq || i == 0 ) + { + if ( traceDistSq < endOfFullFxSq ) + { + hash += 1; + } + else + { + hash += counter; + } + } + + counter *= 2; + } + + if ( !isDefined( self.fxHashs[name] ) ) + self.fxHashs[name] = 0; + + last_hash = self.fxHashs[name]; + + self.fxHashs[name] = hash; + + return last_hash != hash; +} + +function CleanupFx( localClientNum, microwaveFXEnt ) +{ + self util::waittill_any( "entityshutdown", "beam_stop" ); + + foreach ( handle in microwaveFXEnt.fxHandles ) + { + if ( isdefined( handle ) ) + { + StopFx( localClientNum, handle ); + } + } + + microwaveFXEnt delete(); +} + +function play_fx_on_tag( localClientNum, fxName, tag ) +{ + if ( !isdefined( self.fxHandles[tag] ) || fxName != self.fxNames[tag] ) + { + stop_fx_on_tag( localClientNum, fxName, tag ); + + self.fxNames[tag] = fxName; + self.fxHandles[tag] = PlayFxOnTag( localCLientNum, fxName, self, tag ); + } +} + +function stop_fx_on_tag( localClientNum, fxName, tag ) +{ + if ( isdefined( self.fxHandles[tag] ) ) + { + StopFx( localClientNum, self.fxHandles[tag] ); + + self.fxHandles[tag] = undefined; + self.fxNames[tag] = undefined; + } +} + +/# +function render_debug_sphere( tag, color, fxName ) +{ + if ( GetDvarInt( "scr_microwave_turret_fx_debug" ) ) + { + origin = self GetTagOrigin( tag ); + + Sphere( origin, 2, color, 0.75, true, 10, 100 ); + } +} +#/ + +function stop_or_start_fx( localClientNum, fxName, tag, start ) +{ + if ( start ) + { + self play_fx_on_tag( localClientNum, fxName, tag ); +/# + if ( fxName == "killstreaks/fx_sg_distortion_cone_ash_sm" ) + { + render_debug_sphere( tag, ( 0.5, 0.5, 0 ), fxName ); + } + else + { + render_debug_sphere( tag, ( 0, 1, 0 ), fxName ); + } +#/ + } + else + { + stop_fx_on_tag( localClientNum, fxName, tag ); +/# + render_debug_sphere( tag, ( 1, 0, 0 ), fxName ); +#/ + } +} + +function PlayMicrowaveFx( localCLientNum, trace, traceRight, traceLeft, origin ) +{ + rows = 5; + + // /# IPrintLnBold( "Playing Microwave Fx: " + GetTime() ); #/ + + for ( i = 0; i < rows; i++ ) + { + endOfHalfFxSq = ( (( i * ( 150 ) ) + ( 125 )) * (( i * ( 150 ) ) + ( 125 )) ); + endOfFullFxSq = ( (( i * ( 150 ) ) + ( 200 )) * (( i * ( 150 ) ) + ( 200 )) ); + + traceDistSq = DistanceSquared( origin, trace[ "position" ] ); + + startFx = traceDistSq >= endOfHalfFxSq || i == 0; + fxName = ( ( traceDistSq < endOfFullFxSq ) ? "killstreaks/fx_sg_distortion_cone_ash_sm" : "killstreaks/fx_sg_distortion_cone_ash" ); + + switch ( i ) + { + case 0: + // we always want this one to play + self play_fx_on_tag( localCLientNum, fxName, "tag_fx11" ); + break; + case 1: + break; + case 2: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx32", startFx ); + break; + case 3: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx42", startFx ); + self stop_or_start_fx( localCLientNum, fxName, "tag_fx43", startFx ); + break; + case 4: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx53", startFx ); + break; + } + + traceDistSq = DistanceSquared( origin, traceLeft[ "position" ] ); + + startFx = traceDistSq >= endOfHalfFxSq; + fxName = ( ( traceDistSq < endOfFullFxSq ) ? "killstreaks/fx_sg_distortion_cone_ash_sm" : "killstreaks/fx_sg_distortion_cone_ash" ); + + switch ( i ) + { + case 0: + break; + case 1: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx22", startFx ); + break; + case 2: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx33", startFx ); + break; + case 3: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx44", startFx ); + break; + case 4: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx54", startFx ); + self stop_or_start_fx( localCLientNum, fxName, "tag_fx55", startFx ); + break; + } + + traceDistSq = DistanceSquared( origin, traceRight[ "position" ] ); + + startFx = traceDistSq >= endOfHalfFxSq; + fxName = ( ( traceDistSq < endOfFullFxSq ) ? "killstreaks/fx_sg_distortion_cone_ash_sm" : "killstreaks/fx_sg_distortion_cone_ash" ); + + switch ( i ) + { + case 0: + break; + case 1: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx21", startFx ); + break; + case 2: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx31", startFx ); + break; + case 3: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx41", startFx ); + break; + case 4: + self stop_or_start_fx( localCLientNum, fxName, "tag_fx51", startFx ); + self stop_or_start_fx( localCLientNum, fxName, "tag_fx52", startFx ); + break; + } + } + + // /# IPrintLnBold( "Done playing Microwave Fx: " + GetTime() ); #/ +} \ No newline at end of file diff --git a/mp/killstreaks/_microwave_turret.gsc b/mp/killstreaks/_microwave_turret.gsc new file mode 100644 index 0000000..3615108 --- /dev/null +++ b/mp/killstreaks/_microwave_turret.gsc @@ -0,0 +1,710 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\turret_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_death_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\weapons\_weaponobjects; + +#using scripts\mp\_util; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_placeables; +#using scripts\mp\killstreaks\_remote_weapons; +#using scripts\mp\killstreaks\_turret; +#using scripts\mp\teams\_teams; + + + + + + +#precache( "string", "KILLSTREAK_EARNED_AUTO_TURRET" ); +#precache( "string", "KILLSTREAK_AUTO_TURRET_NOT_AVAILABLE" ); + +#precache( "string", "KILLSTREAK_AUTO_TURRET_CRATE" ); +#precache( "string", "KILLSTREAK_MICROWAVE_TURRET_CRATE" ); +#precache( "string", "KILLSTREAK_EARNED_AUTO_TURRET" ); +#precache( "string", "KILLSTREAK_AUTO_TURRET_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_AIRSPACE_FULL" ); +#precache( "string", "KILLSTREAK_EARNED_MICROWAVE_TURRET" ); +#precache( "string", "KILLSTREAK_MICROWAVE_TURRET_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_MICROWAVE_TURRET_HACKED" ); +#precache( "string", "KILLSTREAK_MICROWAVE_TURRET_INBOUND" ); +#precache( "string", "KILLSTREAK_DESTROYED_MICROWAVE_TURRET" ); +#precache( "triggerstring", "KILLSTREAK_MICROWAVE_TURRET_PLACE_TURRET_HINT" ); +#precache( "triggerstring", "KILLSTREAK_MICROWAVE_TURRET_INVALID_TURRET_LOCATION" ); +#precache( "triggerstring", "KILLSTREAK_MICROWAVE_TURRET_PICKUP" ); +#precache( "string", "mpl_killstreak_turret" ); +#precache( "string", "mpl_killstreak_auto_turret" ); +#precache( "fx", "killstreaks/fx_sentry_emp_stun" ); +#precache( "fx", "killstreaks/fx_sentry_damage_state" ); +#precache( "fx", "killstreaks/fx_sentry_death_state" ); +#precache( "fx", "killstreaks/fx_sentry_exp" ); +#precache( "fx", "killstreaks/fx_sentry_disabled_spark" ); +#precache( "fx", "killstreaks/fx_sg_emp_stun" ); +#precache( "fx", "killstreaks/fx_sg_damage_state" ); +#precache( "fx", "killstreaks/fx_sg_death_state" ); +#precache( "fx", "killstreaks/fx_sg_exp" ); +#precache( "fx", "killstreaks/fx_sg_distortion_cone_ash" ); +#precache( "fx", "killstreaks/fx_sg_distortion_cone_ash_sm" ); +#precache( "fx", "explosions/fx_exp_equipment_lg" ); +#precache( "model", "veh_t7_turret_guardian_red" ); +#precache( "model", "veh_t7_turret_guardian_yellow" ); +#precache( "model", "wpn_t7_none_world" ); + +#using_animtree( "mp_microwaveturret" ); + + + + + + + + + + + + + + + +#namespace microwave_turret; + +function init() +{ + killstreaks::register( "microwave_turret", "microwave_turret_deploy", "killstreak_" + "microwave_turret", "microwave_turret" + "_used", &ActivateMicrowaveTurret, false, true ); + killstreaks::register_strings( "microwave_turret", &"KILLSTREAK_EARNED_MICROWAVE_TURRET", &"KILLSTREAK_MICROWAVE_TURRET_NOT_AVAILABLE", &"KILLSTREAK_MICROWAVE_TURRET_INBOUND", undefined, &"KILLSTREAK_MICROWAVE_TURRET_HACKED", false ); + killstreaks::register_dialog( "microwave_turret", "mpl_killstreak_turret", "microwaveTurretDialogBundle", undefined, "friendlyMicrowaveTurret", "enemyMicrowaveTurret", "enemyMicrowaveTurretMultiple", "friendlyMicrowaveTurretHacked", "enemyMicrowaveTurretHacked", "requestMicrowaveTurret", "threatMicrowaveTurret" ); + killstreaks::register_remote_override_weapon( "microwave_turret", "microwave_turret" ); + + level.microwaveOpenAnim = %o_turret_guardian_open; + level.microwaveCloseAnim = %o_turret_guardian_close; + + clientfield::register( "vehicle", "turret_microwave_open", 1, 1, "int" ); + clientfield::register( "scriptmover", "turret_microwave_init", 1, 1, "int" ); // re-export model in close position to save this clientfield + clientfield::register( "scriptmover", "turret_microwave_close", 1, 1, "int" ); + + vehicle::add_main_callback( "microwave_turret", &InitTurretVehicle ); + + callback::on_spawned( &on_player_spawned ); + callback::on_vehicle_spawned( &on_vehicle_spawned ); +} + +function InitTurretVehicle() +{ + turretVehicle = self; + //turretVehicle.delete_on_death = true; + + turretVehicle killstreaks::setup_health( "microwave_turret" ); + turretVehicle.damageTaken = 0; + turretVehicle.deal_no_crush_damage = true; + turretVehicle.health = turretVehicle.maxhealth; + + turretVehicle turret::set_max_target_distance( ( 750 ) * 1.2, 0 ); + turretVehicle turret::set_on_target_angle( (15), 0 ); + turretVehicle clientfield::set( "enemyvehicle", 1 ); + turretVehicle.soundmod = "hpm"; + + turretVehicle.overrideVehicleDamage = &OnTurretDamage; + turretVehicle.overrideVehicleDeath = &OnTurretDeath; + turretVehicle.overrideVehicleDeathPostGame = &OnTurretDeathPostGame; + + turretVehicle.aim_only_no_shooting = true; +} + +function on_player_spawned() +{ + // needs to reset this whenever a player spawns, could be switching teams and this var remains defined + self reset_being_microwaved(); +} + +function on_vehicle_spawned() +{ + self reset_being_microwaved(); +} + +function reset_being_microwaved() +{ + self.lastMicrowavedBy = undefined; + self.beingMicrowavedBy = undefined; +} + +function ActivateMicrowaveTurret() +{ + player = self; + assert( IsPlayer( player ) ); + + killstreakId = self killstreakrules::killstreakStart( "microwave_turret", player.team, false, false ); + if( killstreakId == (-1) ) + { + return false; + } + + bundle = level.killstreakBundle["microwave_turret"]; + + turret = player placeables::SpawnPlaceable( "microwave_turret", killstreakId, + &OnPlaceTurret, &OnCancelPlacement, &OnPickupTurret, &OnShutdown, undefined, &OnEMP, + "veh_t7_turret_guardian", "veh_t7_turret_guardian_yellow", "veh_t7_turret_guardian_red", true, + &"KILLSTREAK_MICROWAVE_TURRET_PICKUP", ( 90 * 1000 ), undefined, ( ( 1800 ) + 1 ), + bundle.ksPlaceableHint, bundle.ksPlaceableInvalidLocationHint ); + turret killstreaks::setup_health( "microwave_turret" ); + turret.damageTaken = 0; + turret.killstreakEndTime = getTime() + ( 90 * 1000 ); + turret thread WatchKillstreakEnd( killstreakId, player.team ); + turret thread util::ghost_wait_show_to_player( player ); + turret.otherModel thread util::ghost_wait_show_to_others( player ); + turret clientfield::set( "turret_microwave_init", 1 ); + turret.otherModel clientfield::set( "turret_microwave_init", 1 ); + + event = turret util::waittill_any_return( "placed", "cancelled", "death", "disconnect" ); + if( event != "placed" ) + { + return false; + } + + return true; +} + +function OnPlaceTurret( turret ) +{ + player = self; + assert( IsPlayer( player ) ); + + if( isdefined( turret.vehicle ) ) + { + turret.vehicle.origin = turret.origin; + turret.vehicle.angles = turret.angles; + turret.vehicle thread util::ghost_wait_show( 0.05 ); + //turret.vehicle playsound ("wpn_micro_turret_start"); + } + else + { + turret.vehicle = SpawnVehicle( "microwave_turret", turret.origin, turret.angles, "dynamic_spawn_ai" ); + turret.vehicle.owner = player; + turret.vehicle SetOwner( player ); + turret.vehicle.ownerEntNum = player.entNum; + turret.vehicle.parentStruct = turret; + + turret.vehicle.team = player.team; + turret.vehicle SetTeam( player.team ); + turret.vehicle turret::set_team( player.team, 0 ); + turret.vehicle.ignore_vehicle_underneath_splash_scalar = true; + turret.vehicle.use_non_teambased_enemy_selection = true; + turret.vehicle.turret = turret; + + turret.vehicle thread util::ghost_wait_show( 0.05 ); + + level thread popups::DisplayKillstreakTeamMessageToAll( "microwave_turret", player ); + player AddWeaponStat( GetWeapon( "microwave_turret" ), "used", 1 ); + + turret.vehicle killstreaks::configure_team( "microwave_turret", turret.killstreakId, player ); + turret.vehicle killstreak_hacking::enable_hacking( "microwave_turret", &HackedPreFunction, &HackedPostFunction ); + player killstreaks::play_killstreak_start_dialog( "microwave_turret", player.pers["team"], turret.killstreakId ); + } + + turret.vehicle turret::enable( 0, false ); + Target_Set( turret.vehicle, ( 0, 0, 36 ) ); + + turret.vehicle vehicle::disconnect_paths( 0, false ); + + turret StartMicrowave(); +} + +function HackedPreFunction( hacker ) +{ + turretVehicle = self; + turretvehicle.turret notify( "hacker_delete_placeable_trigger" ); + turretvehicle.turret StopMicrowave(); + turretvehicle.turret killstreaks::configure_team( "microwave_turret", turretvehicle.turret.killstreakId, hacker, undefined, undefined, undefined, true ); +} + +function HackedPostFunction( hacker ) +{ + turretVehicle = self; + turretvehicle.turret StartMicrowave(); +} + +function OnCancelPlacement( turret ) +{ + turret notify( "microwave_turret_shutdown" ); +} + +function OnPickupTurret( turret ) +{ + turret StopMicrowave(); + + turret.vehicle thread GhostAfterWait( 0.05 ); + turret.vehicle turret::disable( 0 ); + turret.vehicle LinkTo( turret ); + Target_Remove( turret.vehicle ); + + turret.vehicle vehicle::connect_paths(); + + //turret.vehicle playsound ("wpn_micro_turret_stop"); +} + +function GhostAfterWait( wait_time ) +{ + self endon( "death" ); + + wait wait_time; + self Ghost(); +} + +function OnEMP( attacker ) +{ + turret = self; + //TODO: Play Turret EMP FX +} + +function OnTurretDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) +{ + empDamage = int( iDamage + ( self.healthdefault * ( 1 ) ) + 0.5 ); + + iDamage = self killstreaks::OnDamagePerWeapon( "microwave_turret", eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, self.maxhealth, undefined, self.maxhealth*0.4, undefined, empDamage, undefined, true, 1.0 ); + self.damageTaken += iDamage; + return iDamage; +} + +function OnTurretDeath( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime ) +{ + turretVehicle = self; + + eAttacker = self [[ level.figure_out_attacker ]]( eAttacker ); + + if ( isdefined( turretVehicle.parentStruct ) ) + { + turretVehicle.parentStruct placeables::ForceShutdown(); + + if ( turretVehicle.parentStruct.killstreakTimedOut === true && isdefined( turretVehicle.owner ) ) + { + turretVehicle.owner globallogic_audio::play_taacom_dialog( "timeout", turretVehicle.parentStruct.killstreakType ); + } + else + { + if ( isdefined( eAttacker ) && IsPlayer( eAttacker ) && isdefined( turretVehicle.owner ) && ( eAttacker != turretVehicle.owner ) ) + turretVehicle.parentStruct killstreaks::play_destroyed_dialog_on_owner( turretVehicle.parentStruct.killstreakType, turretVehicle.parentStruct.killstreakId ); + } + } + + if( isdefined( eAttacker ) && IsPlayer( eAttacker ) && ( !isdefined( self.owner ) || self.owner util::IsEnemyPlayer( eAttacker ) ) ) + { + scoreevents::processScoreEvent( "destroyed_microwave_turret", eAttacker, self.owner, weapon ); + eAttacker challenges::destroyScoreStreak( weapon, false, true, false ); + eAttacker challenges::destroyNonAirScoreStreak_PostStatsLock( weapon ); + eAttacker AddPlayerStat( "destroy_turret", 1 ); + eAttacker AddWeaponStat( weapon, "destroy_turret", 1 ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_MICROWAVE_TURRET", eAttacker.entnum ); + } + + if ( isdefined( turretVehicle.parentStruct ) ) + { + turretVehicle.parentStruct notify( "microwave_turret_shutdown" ); + } + + turretVehicle vehicle_death::death_fx(); + + wait 0.1; + + turretVehicle delete(); +} + +function OnTurretDeathPostGame( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime ) +{ + turretVehicle = self; + + if ( isdefined( turretVehicle.parentStruct ) ) + { + turretVehicle.parentStruct placeables::ForceShutdown(); + } + if ( isdefined( turretVehicle.parentStruct ) ) + { + turretVehicle.parentStruct notify( "microwave_turret_shutdown" ); + } + + turretVehicle vehicle_death::death_fx(); + + wait 0.1; + + turretVehicle delete(); +} + + +function OnShutdown( turret ) +{ + turret StopMicrowave(); + + if ( isdefined( turret.vehicle ) ) + { + turret.vehicle playsound ("mpl_m_turret_exp"); + turret.vehicle Kill(); + } + + turret notify( "microwave_turret_shutdown" ); +} + +function WatchKillstreakEnd( killstreak_id, team ) +{ + turret = self; + + turret waittill( "microwave_turret_shutdown" ); + + killstreakrules::killstreakStop( "microwave_turret", team, killstreak_id ); +} + +function StartMicrowave() +{ + turret = self; + if ( isdefined( turret.trigger ) ) + { + turret.trigger delete(); + } + turret.trigger = spawn("trigger_radius", turret.origin + (0,0,-( 750 )), level.aiTriggerSpawnFlags | level.vehicleTriggerSpawnFlags, ( 750 ), ( 750 )*2); + turret thread TurretThink(); + + turret clientfield::set( "turret_microwave_close", 0 ); + turret.otherModel clientfield::set( "turret_microwave_close", 0 ); + + if ( isdefined( turret.vehicle )) + { + turret.vehicle clientfield::set( "turret_microwave_open", 1 ); + } + + turret turret::CreateTurretInfluencer( "turret" ); + turret turret::CreateTurretInfluencer( "turret_close" ); + + /# + turret thread TurretDebugWatch(); + #/ +} + +function StopMicrowave() +{ + turret = self; + turret spawning::remove_influencers(); + + if( isdefined( turret ) ) + { + turret clientfield::set( "turret_microwave_close", 1 ); + turret.otherModel clientfield::set( "turret_microwave_close", 1 ); + + if ( isdefined( turret.vehicle ) ) + { + turret.vehicle clientfield::set( "turret_microwave_open", 0 ); + } + + turret playsound ("mpl_microwave_beam_off"); + + if( isdefined( turret.microwaveFXEnt ) ) + { + turret.microwaveFXEnt delete(); + + // /# IPrintLnBold( "Deleted Microwave Fx Ent: " + GetTime() ); #/ + } + + if( isdefined( turret.trigger ) ) + { + turret.trigger notify( "microwave_end_fx" ); + turret.trigger Delete(); + } + + /# + turret notify( "stop_turret_debug" ); + #/ + } +} + +function TurretDebugWatch() +{ + turret = self; + turret endon( "stop_turret_debug" ); + + for(;;) + { + if ( GetDvarInt( "scr_microwave_turret_debug" ) != 0 ) + { + turret TurretDebug(); + + {wait(.05);}; + } + else + { + wait 1.0; + } + } +} + +function TurretDebug() +{ + turret = self; + + debug_line_frames = 3; + + angles = turret.vehicle GetTagAngles( "tag_flash" ); + origin = turret.vehicle GetTagOrigin( "tag_flash" ); + + cone_apex = origin; + forward = AnglesToForward( angles ) ; + dome_apex = cone_apex + VectorScale( forward, ( 750 ) ); + + util::debug_spherical_cone( cone_apex, dome_apex, ( 15 ), 16, ( 0.95, 0.1, 0.1 ), 0.3, true, debug_line_frames ); +} + + +function TurretThink() +{ + turret = self; + turret endon( "microwave_turret_shutdown" ); + + turret.trigger endon( "death" ); + turret.trigger endon( "delete" ); + + turret.turret_vehicle_entnum = turret.vehicle GetEntityNumber(); + + while( true ) + { + turret.trigger waittill( "trigger", ent ); + + if ( ent == turret ) + continue; + + if ( !isdefined( ent.beingMicrowavedBy ) ) + { + ent.beingMicrowavedBy = []; + } + + if( !isdefined( ent.beingMicrowavedBy[ turret.turret_vehicle_entnum ] ) ) + { + turret thread MicrowaveEntity( ent ); + } + } +} + +function MicrowaveEntityPostShutdownCleanup( entity ) +{ + entity endon( "disconnect" ); + entity endon( "end_MicrowaveEntityPostShutdownCleanup" ); + + turret = self; + + turret_vehicle_entnum = turret.turret_vehicle_entnum; + + turret waittill( "microwave_turret_shutdown" ); + + if ( isdefined(entity) ) + { + if ( isdefined( entity.beingMicrowavedBy ) && isdefined( entity.beingMicrowavedBy[ turret_vehicle_entnum ] ) ) + { + entity.beingMicrowavedBy[ turret_vehicle_entnum ] = undefined; + } + } +} + +function MicrowaveEntity( entity ) +{ + turret = self; + + turret endon( "microwave_turret_shutdown" ); + entity endon( "disconnect" ); + entity endon( "death" ); + + if ( IsPlayer( entity ) ) + { + entity endon( "joined_team" ); + entity endon( "joined_spectators" ); + } + + turret thread MicrowaveEntityPostShutdownCleanup( entity ); + + entity.beingMicrowavedBy[ turret.turret_vehicle_entnum ] = turret.owner; + entity.microwaveDamageInitialDelay = true; + entity.microwaveEffect = 0; + + shellShockScalar = 1; + viewKickScalar = 1; + damageScalar = 1; + + if ( IsPlayer( entity ) && entity hasPerk( "specialty_microwaveprotection" ) ) + { + shellShockScalar = getDvarFloat( "specialty_microwaveprotection_shellshock_scalar", 0.5 ); + viewKickScalar = getDvarFloat( "specialty_microwaveprotection_viewkick_scalar", 0.5 ); + damageScalar = getDvarFloat( "specialty_microwaveprotection_damage_scalar", 0.5 ); + } + + turretWeapon = GetWeapon( "microwave_turret" ); + + while( true ) + { + if( !isdefined( turret ) || !turret MicrowaveTurretAffectsEntity( entity ) || !isdefined( turret.trigger ) ) + { + if( !isdefined(entity)) + { + return; + } + + entity.beingMicrowavedBy[ turret.turret_vehicle_entnum ] = undefined; + + if( isdefined( entity.microwavePoisoning ) && entity.microwavePoisoning ) + { + entity.microwavePoisoning = false; + } + + entity notify( "end_MicrowaveEntityPostShutdownCleanup" ); + + return; + } + + damage = ( 15 ) * damageScalar; + + if ( level.hardcoreMode ) + { + damage = damage / 2; + } + + if ( !IsAi( entity ) && entity util::mayApplyScreenEffect() ) + { + if ( !isdefined( entity.microwavePoisoning ) || !entity.microwavePoisoning ) + { + entity.microwavePoisoning = true; + entity.microwaveEffect = 0; + } + } + + // randomly wait a bit before applying intial damage to "stagger" it and prevent performance spikes + if ( isdefined( entity.microwaveDamageInitialDelay ) ) + { + wait RandomFloatRange( ( 0.1 ), ( 0.3 ) ); + entity.microwaveDamageInitialDelay = undefined; + } + + entity DoDamage( damage, // iDamage Integer specifying the amount of damage done + turret.origin, // vPoint The point the damage is from? + turret.owner, // eAttacker The entity that is attacking. + turret.vehicle, // eInflictor The entity that causes the damage.(e.g. a turret) + 0, + "MOD_TRIGGER_HURT", // sMeansOfDeath Integer specifying the method of death + 0, // iDFlags Integer specifying flags that are to be applied to the damage + turretWeapon ); // Weapon The weapon used to inflict the damage + + entity.microwaveEffect++; + entity.lastMicrowavedBy = turret.owner; + time = GetTime(); + + if( IsPlayer(entity) && !(entity IsRemoteControlling() ) ) + { + if ( time - (isdefined(entity.microwaveShellshockAndViewKickTime)?entity.microwaveShellshockAndViewKickTime:0) > 950 ) // the time here ties in with the wait 0.5 below and the microwaveEffect % 2 + { + if( entity.microwaveEffect % 2 == 1 ) + { + if ( DistanceSquared( entity.origin, turret.origin ) > (( 750 ) * 2/3) * (( 750 ) * 2/3) ) + { + entity shellshock( "mp_radiation_low", 1.5 * shellShockScalar ); + entity ViewKick( int( 25 * viewKickScalar ), turret.origin ); + } + else if ( DistanceSquared( entity.origin, turret.origin ) > (( 750 ) * 1/3) * (( 750 ) * 1/3) ) + { + entity shellshock( "mp_radiation_med", 1.5 * shellShockScalar ); + entity ViewKick( int( 50 * viewKickScalar ), turret.origin ); + } + else + { + entity shellshock( "mp_radiation_high", 1.5 * shellShockScalar ); + entity ViewKick( int( 75 * viewKickScalar ), turret.origin ); + } + + entity.microwaveShellshockAndViewKickTime = time; + } + } + } + + if( IsPlayer( entity ) && entity.microwaveEffect % 3 == 2 ) + { + scoreevents::processScoreEvent( "hpm_suppress", turret.owner, entity, turretWeapon ); + } + + wait 0.5; + } +} + +function MicrowaveTurretAffectsEntity( entity ) +{ + turret = self; + + if( !IsAlive( entity ) ) + { + return false; + } + + if( !IsPlayer( entity ) && !IsAi( entity ) ) + { + return false; + } + + if ( entity.ignoreme === true ) + { + return false; + } + + + if( isdefined( turret.carried ) && turret.carried ) + { + return false; + } + + if( turret weaponobjects::isStunned() ) + { + return false; + } + + if( isdefined( turret.owner ) && entity == turret.owner ) + { + return false; + } + + if( !weaponobjects::friendlyFireCheck( turret.owner, entity, 0 ) ) + { + return false; + } + + if( DistanceSquared( entity.origin, turret.origin ) > ( 750 ) * ( 750 ) ) + { + return false; + } + + angles = turret.vehicle GetTagAngles( "tag_flash" ); + origin = turret.vehicle GetTagOrigin( "tag_flash" ); + + shoot_at_pos = entity GetShootAtPos( turret ); + + entDirection = vectornormalize( shoot_at_pos - origin ); + forward = AnglesToForward( angles ) ; + dot = vectorDot( entDirection, forward ); + if( dot < cos( ( 15 ) ) ) + { + return false; + } + + if( entity damageConeTrace( origin, turret, forward ) <= 0 ) + { + return false; + } + + return true; +} diff --git a/mp/killstreaks/_placeables.gsc b/mp/killstreaks/_placeables.gsc new file mode 100644 index 0000000..fbb9dd5 --- /dev/null +++ b/mp/killstreaks/_placeables.gsc @@ -0,0 +1,534 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\_oob; +#using scripts\shared\clientfield_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\util_shared; + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_killstreak_detect; + + + + +#namespace placeables; + + + + +function SpawnPlaceable( killstreakRef, killstreakId, + onPlaceCallback, onCancelCallback, onMoveCallback, onShutdownCallback, onDeathCallback, onEmpCallback, + model, validModel, invalidModel, spawnsVehicle, + pickupString, + timeout, + health, + empDamage, + placeHintString, + invalidLocationHintString ) +{ + player = self; + + self killstreaks::switch_to_last_non_killstreak_weapon(); + + placeable = spawn( "script_model", player.origin ); + placeable.cancelable = true; + placeable.held = false; + placeable.validModel = validModel; + placeable.invalidModel = invalidModel; + placeable.killstreakId = killstreakId; + placeable.killstreakRef = killstreakRef; + placeable.onCancel = onCancelCallback; + placeable.onEmp = onEmpCallback; + placeable.onMove = onMoveCallback; + placeable.onPlace = onPlaceCallback; + placeable.onShutdown = onShutdownCallback; + placeable.onDeath = onDeathCallback; + placeable.owner = player; + placeable.originalOwner = player; + placeable.ownerEntNum = player.entNum; + placeable.originalOwnerEntNum = player.entNum; + placeable.pickupString = pickupString; + placeable.placedModel = model; + placeable.spawnsVehicle = spawnsVehicle; + + placeable.originalTeam = player.team; + placeable.timedOut = false; + placeable.timeout = timeout; + placeable.timeoutStarted = false; + placeable.angles = ( 0, player.angles[1], 0 ); + placeable.placeHintString = placeHintString; + placeable.invalidLocationHintString = invalidLocationHintString; + + if(!isdefined(placeable.placeHintString))placeable.placeHintString=""; + if(!isdefined(placeable.invalidLocationHintString))placeable.invalidLocationHintString=""; + + placeable NotSolid(); + if ( isdefined( placeable.vehicle ) ) + placeable.vehicle NotSolid(); + + placeable.otherModel = spawn( "script_model", player.origin ); + placeable.otherModel SetModel( placeable.placedModel ); + placeable.otherModel SetInvisibleToPlayer( player ); + placeable.otherModel NotSolid(); + placeable.otherModel clientfield::set( "enemyvehicle", 1 ); + + placeable killstreaks::configure_team( killstreakRef, killstreakId, player ); + + if( isdefined( health ) && health > 0 ) + { + placeable.health = health; + placeable SetCanDamage( false ); + placeable thread killstreaks::MonitorDamage( killstreakRef, health, &OnDeath, 0, undefined, empDamage, &OnEMP, true ); + } + + player thread CarryPlaceable( placeable ); + level thread CancelOnGameEnd( placeable ); + + player thread ShutdownOnCancelEvent( placeable ); + player thread CancelOnPlayerDisconnect( placeable ); + + placeable thread WatchOwnerGameEvents(); + + return placeable; +} + +function UpdatePlacementModels( model, validModel, invalidModel ) +{ + placeable = self; + placeable.placedModel = model; + placeable.validModel = validModel; + placeable.invalidModel = invalidModel; +} + +function CarryPlaceable( placeable ) +{ + player = self; + + placeable Show(); + placeable NotSolid(); + if ( isdefined( placeable.vehicle ) ) + placeable.vehicle NotSolid(); + + if ( isdefined( placeable.otherModel ) ) + { + placeable thread util::ghost_wait_show_to_player( player, 0.05, "abort_ghost_wait_show" ); + placeable.otherModel thread util::ghost_wait_show_to_others( player, 0.05, "abort_ghost_wait_show" ); + placeable.otherModel NotSolid(); + } + + placeable.held = true; + player.holding_placeable = placeable; + + player CarryTurret( placeable, ( 40, 0, 0 ), ( 0, 0, 0 ) ); + player util::_disableWeapon(); + player thread WatchPlacement( placeable ); +} + +function InNoPlacementTrigger() +{ + placeable = self; + + if( isdefined( level.noTurretPlacementTriggers ) ) + { + for( i = 0; i < level.noTurretPlacementTriggers.size; i++ ) + { + if( placeable IsTouching( level.noTurretPlacementTriggers[i] ) ) + { + return true; + } + } + } + + if( isdefined( level.fatal_triggers ) ) + { + for( i = 0; i < level.fatal_triggers.size; i++ ) + { + if( placeable IsTouching( level.fatal_triggers[i] ) ) + { + return true; + } + } + } + + if( placeable oob::IsTouchingAnyOOBTrigger() ) + { + return true; + } + + return false; +} + +function WatchPlacement( placeable ) +{ + player = self; + + player endon( "disconnect" ); + player endon( "death" ); + placeable endon( "placed" ); + placeable endon( "cancelled" ); + + player thread WatchCarryCancelEvents( placeable ); + + lastAttempt = -1; + placeable.canBePlaced = false; + waitingForAttackButtonRelease = true; + + while( true ) + { + placement = player CanPlayerPlaceTurret(); + + placeable.origin = placement["origin"]; + placeable.angles = placement["angles"]; + placeable.canBePlaced = placement["result"] && !( placeable InNoPlacementTrigger() ); + + if ( player.laststand === true ) + { + placeable.canBePlaced = false; + } + + if ( isdefined( placeable.otherModel ) ) + { + placeable.otherModel.origin = placement["origin"]; + placeable.otherModel.angles = placement["angles"]; + } + + if( placeable.canBePlaced != lastAttempt ) + { + if( placeable.canBePlaced ) + { + placeable SetModel( placeable.validModel ); + player SetHintString( IString( placeable.placeHintString ) ); + } + else + { + placeable SetModel( placeable.invalidModel ); + player SetHintString( IString( placeable.invalidLocationHintString ) ); + } + + lastAttempt = placeable.canBePlaced; + } + + while( waitingForAttackButtonRelease && !player AttackButtonPressed() ) + { + waitingForAttackButtonRelease = false; + } + + if( !waitingForAttackButtonRelease && placeable.canBePlaced && player AttackButtonPressed() ) + { + if( placement["result"] ) + { + placeable.origin = placement["origin"]; + placeable.angles = placement["angles"]; + + player SetHintString( "" ); + player StopCarryTurret( placeable ); + + if( !player util::isWeaponEnabled() ) + { + player util::_enableWeapon(); + } + + placeable.held = false; + player.holding_placeable = undefined; + placeable.cancelable = false; + + if( ( isdefined( placeable.health ) && placeable.health ) ) + { + placeable SetCanDamage( true ); + placeable Solid(); + } + + if ( isdefined( placeable.vehicle ) ) + { + placeable.vehicle SetCanDamage( true ); + placeable.vehicle Solid(); + } + + if( isdefined( placeable.placedModel ) && !placeable.spawnsVehicle ) + { + placeable SetModel( placeable.placedModel ); + } + else + { + placeable notify( "abort_ghost_wait_show" ); + placeable.abort_ghost_wait_show_to_player = true; + placeable.abort_ghost_wait_show_to_others = true; + placeable Ghost(); // need to ghost instead of hide because of resulting issue with client side animations + + if ( isdefined( placeable.otherModel ) ) + { + placeable.otherModel notify( "abort_ghost_wait_show" ); + placeable.otherModel.abort_ghost_wait_show_to_player = true; + placeable.otherModel.abort_ghost_wait_show_to_others = true; + placeable.otherModel Ghost(); // need to ghost instead of hide because of resulting issue with client side animations + } + } + + if( isdefined( placeable.timeout ) ) + { + if( !placeable.timeoutStarted ) + { + placeable.timeoutStarted = true; + placeable thread killstreaks::WaitForTimeout( placeable.killstreakRef, placeable.timeout, &OnTimeout, "death", "cancelled" ); + } + else if( placeable.timedOut ) + { + placeable thread killstreaks::WaitForTimeout( placeable.killstreakRef, 5000, &OnTimeout, "cancelled" ); + } + } + + if( isdefined( placeable.onPlace ) ) + { + player [[ placeable.onPlace ]]( placeable ); + if( isdefined( placeable.onMove ) && !placeable.timedOut ) + { + SpawnMoveTrigger( placeable, player ); + } + } + + placeable notify( "placed" ); + } + } + + if( placeable.cancelable && player ActionSlotFourButtonPressed() ) + { + placeable notify( "cancelled" ); + } + + {wait(.05);}; + } +} + +function WatchCarryCancelEvents( placeable ) +{ + player = self; + assert( IsPlayer( player ) ); + + placeable endon( "cancelled" ); + placeable endon( "placed" ); + + player util::waittill_any( "death", "emp_jammed", "emp_grenaded", "disconnect", "joined_team" ); + placeable notify( "cancelled" ); +} + +function OnTimeout() +{ + placeable = self; + if( ( isdefined( placeable.held ) && placeable.held ) ) + { + placeable.timedOut = true; + return; + } + + placeable notify( "delete_placeable_trigger" ); + placeable thread killstreaks::WaitForTimeout( placeable.killstreakRef, 5000, &ForceShutdown, "cancelled" ); +} + +function OnDeath( attacker, weapon ) +{ + placeable = self; + + if( isdefined( placeable.onDeath ) ) + { + [[ placeable.onDeath ]]( attacker, weapon ); + } + + placeable notify( "cancelled" ); +} + +function OnEMP( attacker ) +{ + placeable = self; + + if( isdefined( placeable.onEmp ) ) + { + placeable [[ placeable.onEmp ]]( attacker ); + } +} + +function CancelOnPlayerDisconnect( placeable ) +{ + placeable endon( "hacked" ); + + player = self; + assert( IsPlayer( player ) ); + + placeable endon( "cancelled" ); + placeable endon( "death" ); + + player util::waittill_any( "disconnect", "joined_team" ); + placeable notify( "cancelled" ); +} + +function CancelOnGameEnd( placeable ) +{ + placeable endon( "cancelled" ); + placeable endon( "death" ); + + level waittill( "game_ended" ); + placeable notify( "cancelled" ); +} + +function SpawnMoveTrigger( placeable, player ) +{ + pos = placeable.origin + ( 0, 0, 15 ); + placeable.pickupTrigger = spawn( "trigger_radius_use", pos ); + placeable.pickupTrigger SetCursorHint( "HINT_NOICON", placeable ); + placeable.pickupTrigger SetHintString( placeable.pickupString ); + placeable.pickupTrigger SetTeamForTrigger( player.team ); + + player ClientClaimTrigger( placeable.pickupTrigger ); + placeable thread WatchPickup( player); + placeable.pickupTrigger thread WatchMoveTriggerShutdown( placeable ); +} + +function WatchMoveTriggerShutdown( placeable ) +{ + trigger = self; + placeable util::waittill_any( "cancelled", "picked_up", "death", "delete_placeable_trigger", "hacker_delete_placeable_trigger" ); + placeable.pickupTrigger delete(); +} + +function WatchPickup( player ) +{ + placeable = self; + placeable endon( "death" ); + placeable endon( "cancelled" ); + + assert( isdefined( placeable.pickupTrigger ) ); + trigger = placeable.pickupTrigger; + + while ( true ) + { + trigger waittill( "trigger", player ); + + if( !isAlive( player ) ) + { + continue; + } + + if( player isUsingOffhand() ) + { + continue; + } + + if( !player isOnGround() ) + { + continue; + } + + if ( isdefined( placeable.vehicle ) && ( placeable.vehicle.control_initiated === true ) ) + { + continue; + } + + if ( isdefined( player.carryObject ) && player.carryObject.disallowPlaceablePickup === true ) + { + continue; + } + + if( isdefined( trigger.triggerTeam ) && ( player.team != trigger.triggerTeam ) ) + { + continue; + } + + if( isdefined( trigger.claimedBy ) && ( player != trigger.claimedBy ) ) + { + continue; + } + + if( player useButtonPressed() && !player.throwingGrenade && !player meleeButtonPressed() && !player attackButtonPressed() && + !( isdefined( player.isPlanting ) && player.isPlanting ) && !( isdefined( player.isDefusing ) && player.isDefusing ) && !player IsRemoteControlling() && !isdefined( player.holding_placeable ) ) + { + placeable notify( "picked_up" ); + placeable.held = true; + placeable SetCanDamage( false ); + assert( isdefined( placeable.onMove ) ); + player [[ placeable.onMove ]]( placeable ); + player thread CarryPlaceable( placeable ); + return; + } + } +} + +function ForceShutdown() +{ + placeable = self; + placeable.cancelable = false; + placeable notify( "cancelled" ); +} + +function WatchOwnerGameEvents() +{ + self notify("WatchOwnerGameEvents_singleton"); + self endon ("WatchOwnerGameEvents_singleton"); + + placeable = self; + placeable endon( "cancelled" ); + + placeable.owner util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); + + if ( isdefined( placeable ) ) + { + placeable.abandoned = true; + placeable ForceShutdown(); + } +} + +function ShutdownOnCancelEvent( placeable ) +{ + placeable endon( "hacked" ); + + player = self; + assert( IsPlayer( player ) ); + + placeable util::waittill_any( "cancelled", "death" ); + + if( isdefined( player ) && isdefined( placeable ) && ( placeable.held === true ) ) + { + player SetHintString( "" ); + player StopCarryTurret( placeable ); + + if( !player util::isWeaponEnabled() ) + { + player util::_enableWeapon(); + } + } + + if( isdefined( placeable ) ) + { + if( placeable.cancelable ) + { + if( isdefined( placeable.onCancel ) ) + { + [[ placeable.onCancel ]]( placeable ); + } + } + else + { + if( isdefined( placeable.onShutdown ) ) + { + [[ placeable.onShutdown ]]( placeable ); + } + } + + if ( isdefined( placeable ) ) + { + if ( isdefined( placeable.vehicle ) ) + { + vehicle_to_kill = placeable.vehicle; + vehicle_to_kill.selfDestruct = true; + vehicle_to_kill Kill(); // need to kill the vehicle before placeable is deleted + placeable.vehicle = undefined; + placeable.otherModel delete(); + placeable delete(); + } + else + { + placeable.otherModel delete(); + placeable delete(); + } + } + } +} diff --git a/mp/killstreaks/_planemortar.csc b/mp/killstreaks/_planemortar.csc new file mode 100644 index 0000000..b250d6a --- /dev/null +++ b/mp/killstreaks/_planemortar.csc @@ -0,0 +1,31 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; + + + + +#precache( "client_fx", "killstreaks/fx_ls_exhaust_afterburner" ); + +#namespace planemortar; + +function autoexec __init__sytem__() { system::register("planemortar",&__init__,undefined,undefined); } + +function __init__() +{ + level.planeMortarExhaustFX = "killstreaks/fx_ls_exhaust_afterburner"; + + clientfield::register( "scriptmover", "planemortar_contrail", 1, 1, "int",&planemortar_contrail, !true, !true ); +} + +function planemortar_contrail( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self endon( "death" ); + self endon( "entityshutdown" ); + + if ( newVal ) + { + self.fx = PlayFXOnTag( localClientNum, level.planeMortarExhaustFX, self, "tag_fx" ); + } +} diff --git a/mp/killstreaks/_planemortar.gsc b/mp/killstreaks/_planemortar.gsc new file mode 100644 index 0000000..bc255ef --- /dev/null +++ b/mp/killstreaks/_planemortar.gsc @@ -0,0 +1,428 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_satellite; + + + + + + + + + + + + + + + + + + + +#precache( "locationselector", "map_mortar_selector" ); +#precache( "locationselector", "map_mortar_selector_done" ); +#precache( "string", "MP_EARNED_PLANEMORTAR" ); +#precache( "string", "KILLSTREAK_PLANEMORTAR_NOT_AVAILABLE" ); +#precache( "string", "MP_WAR_PLANEMORTAR_INBOUND" ); +#precache( "string", "MP_WAR_PLANEMORTAR_INBOUND_NEAR_YOUR_POSITION" ); +#precache( "string", "KILLSTREAK_PLANEMORTAR_HACKED" ); +#precache( "eventstring", "mpl_killstreak_planemortar" ); +#precache( "fx", "killstreaks/fx_ls_exhaust_afterburner" ); + +#namespace planemortar; + +function init() +{ + level.planeMortarExhaustFX = "killstreaks/fx_ls_exhaust_afterburner"; + clientfield::register( "scriptmover", "planemortar_contrail", 1, 1, "int" ); + killstreaks::register( "planemortar", "planemortar", "killstreak_planemortar", "planemortar_used",&useKillstreakPlaneMortar, true ); + killstreaks::register_strings( "planemortar", &"MP_EARNED_PLANEMORTAR", &"KILLSTREAK_PLANEMORTAR_NOT_AVAILABLE", &"MP_WAR_PLANEMORTAR_INBOUND", &"MP_WAR_PLANEMORTAR_INBOUND_NEAR_YOUR_POSITION", &"KILLSTREAK_PLANEMORTAR_HACKED" ); + killstreaks::register_dialog( "planemortar", "mpl_killstreak_planemortar", "planeMortarDialogBundle", "planeMortarPilotDialogBundle", "friendlyPlaneMortar", "enemyPlaneMortar", "enemyPlaneMortarMultiple", "friendlyPlaneMortarHacked", "enemyPlaneMortarHacked", "requestPlaneMortar" ); + killstreaks::set_team_kill_penalty_scale( "planemortar", level.teamKillReducedPenalty ); +} + +function useKillstreakPlaneMortar( hardpointType ) +{ + if ( self killstreakrules::isKillstreakAllowed( hardpointType, self.team ) == false ) + { + return false; + } + + result = self selectPlaneMortarLocation( hardpointType ); + + if ( !isdefined( result ) || !result ) + { + return false; + } + + return true; +} + +function waittill_confirm_location() +{ + self endon( "emp_jammed" ); + self endon( "emp_grenaded" ); + + self waittill( "confirm_location", location ); + + return location; +} + +function selectPlaneMortarLocation( hardpointType ) +{ + self beginLocationMortarSelection( "map_mortar_selector", 800, "map_mortar_selector_done" ); + self.selectingLocation = true; + + self thread airsupport::endSelectionThink(); + + locations = []; + if (!isdefined(self.pers["mortarRadarUsed"]) || !self.pers["mortarRadarUsed"]) + { + self thread singleRadarSweep(); + + otherTeam = util::getOtherTeam( self.team ); + globallogic_audio::leader_dialog( "enemyPlaneMortarUsed", otherTeam ); + } + + for ( i = 0 ; i < 3 ; i++ ) + { + location = self waittill_confirm_location(); + + // if the player gets disconnected, self will be undefined + if( !isdefined( self ) ) + return false; + + if ( !isdefined( location ) ) + { + self.pers["mortarRadarUsed"] = true; + self notify("cancel_selection"); + // selection was canceled + return false; + } + + locations[i] = location; + } + + if ( self killstreakrules::isKillstreakAllowed( hardpointType, self.team ) == false) + { + self.pers["mortarRadarUsed"] = true; + self notify("cancel_selection"); + return false; + } + + self.pers["mortarRadarUsed"] = false; + + return self airsupport::finishHardpointLocationUsage( locations,&usePlaneMortar ); +} + +function waitPlayBackTime( soundAlias ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + playbackTime = soundgetplaybacktime ( soundAlias ); + + if ( playbackTime >= 0 ) + { + waitTime = playbackTime * .001; + + wait ( waitTime ); + } + else + { + wait ( 1.0 ); + } + self notify ( soundAlias ); +} + +function singleRadarSweep() +{ + self endon( "disconnect" ); + self endon("cancel_selection"); + + // give a bit for the map to come up + wait(0.5); + + self PlayLocalSound("mpl_killstreak_satellite"); + + if ( level.teamBased ) + { + has_satellite = satellite::HasSatellite( self.team ); + } + else + { + has_satellite = satellite::HasSatellite( self.entnum ); + } + + if (self.hasSpyplane == 0 && !has_satellite && !level.forceRadar ) + { + self thread doRadarSweep(); + } +} + +function doRadarSweep() +{ + self setClientUIVisibilityFlag( "g_compassShowEnemies", 1 ); + + wait ( 0.2 ); + + self setClientUIVisibilityFlag( "g_compassShowEnemies", 0 ); +} + +function usePlaneMortar( positions ) +{ + team = self.team; + killstreak_id = self killstreakrules::killstreakStart( "planemortar", team, false, true ); + if ( killstreak_id == -1 ) + { + return false; + } + + self killstreaks::play_killstreak_start_dialog( "planemortar", team, killstreak_id ); + + self.planeMortarPilotIndex = killstreaks::get_random_pilot_index( "planemortar" ); + self killstreaks::play_pilot_dialog( "arrive", "planemortar", undefined, self.planeMortarPilotIndex ); + + self AddWeaponStat( GetWeapon( "planemortar" ), "used", 1 ); + self thread planeMortar_watchForEndNotify( team, killstreak_id ); + self thread doPlaneMortar( positions, team, killstreak_id ); + + return true; +} + +function doPlaneMortar( positions, team, killstreak_id ) +{ + self endon( "emp_jammed" ); + self endon( "disconnect" ); + + yaw = RandomIntRange( 0,360 ); + odd = 0; + wait ( 1.25 ); + foreach( position in positions ) + { + level spawning::create_enemy_influencer( "artillery", position, team ); + self thread doBombRun( position, yaw, team ); + + if (odd == 0) + { + yaw = ( yaw + 35 ) % 360; + } + else + { + yaw = ( yaw + 290 ) % 360; + } + odd = ( odd + 1 ) % 2; + + wait 0.8; + } + + self notify( "planemortarcomplete" ); + wait ( 1 ); + self thread plane_mortar_bda_dialog(); +} + +function plane_mortar_bda_dialog() +{ + if ( isdefined( self.planeMortarBda ) ) + { + if (self.planeMortarBda === 1) + { + bdaDialog = "kill1"; + } + else if (self.planeMortarBda === 2) + { + bdaDialog = "kill2"; + } + else if (self.planeMortarBda === 3) + { + bdaDialog = "kill3"; + } + else if (isdefined( self.planeMortarBda ) && self.planeMortarBda > 3) + { + bdaDialog = "killMultiple"; + } + + self killstreaks::play_pilot_dialog( bdaDialog, "planemortar", undefined, self.planeMortarPilotIndex ); + + if ( battlechatter::dialog_chance( "taacomPilotKillConfirmChance" ) ) + { + self killstreaks::play_taacom_dialog_response( "killConfirmed", "planemortar", undefined, self.planeMortarPilotIndex ); + } + else + { + self globallogic_audio::play_taacom_dialog( "confirmHit" ); + } + } + else + { + killstreaks::play_pilot_dialog( "killNone", "planemortar", undefined, self.planeMortarPilotIndex ); + globallogic_audio::play_taacom_dialog( "confirmMiss" ); + } + + self.planeMortarBda = undefined; +} + +function planeMortar_watchForEndNotify( team, killstreak_id ) +{ + self util::waittill_any( "disconnect", "joined_team", "joined_spectators", "planemortarcomplete", "emp_jammed" ); + + planeMortar_killstreakStop( team, killstreak_id ); +} + +function planeMortar_killstreakStop( team, killstreak_id ) +{ + killstreakrules::killstreakStop( "planemortar", team, killstreak_id ); +} + + +function doBombRun( position, yaw, team ) +{ + self endon( "emp_jammed" ); + player = self; + + angles = (0,yaw,0); + direction = AnglesToForward( angles ); + + height = airsupport::getMinimumFlyHeight() + 2000; + position = ( position[0], position[1], height ); + startPoint = position + VectorScale( direction, -1 * 12000 ); + endPoint = position + VectorScale( direction, 12000 * 1.5 ); + height = airsupport::getNoFlyZoneHeightCrossed( startPoint, endPoint, height ); + startPoint = ( startPoint[0], startPoint[1], height ); + position = ( position[0], position[1], height ); + endPoint = ( endPoint[0], endPoint[1], height ); + + plane = SpawnPlane( self, "script_model", startPoint ); + plane.team = team; + plane.targetname = "plane_mortar"; + plane.owner = self; + + plane endon( "delete" ); + plane endon( "death" ); + + plane thread planeWatchForEmp( self ); + + plane.angles = angles; + plane SetModel( "veh_t7_mil_vtol_fighter_mp" ); + plane SetEnemyModel( "veh_t7_mil_vtol_fighter_mp_dark" ); + plane clientfield::set( "planemortar_contrail", 1 ); + plane clientfield::set( "enemyvehicle", 1 ); + plane playsound( "mpl_lightning_flyover_boom" ); + plane SetDrawInfrared( true ); + + plane.killcamEnt = spawn( "script_model", plane.origin+(0,0,700)+VectorScale( direction, -1 * 1500 ) ); + plane.killcamEnt util::deleteAfterTime( ( 12000 * 2 / 12000 ) * 3 ); + plane.killcamEnt.angles = (15,yaw,0); + plane.killcamEnt.startTime = gettime(); + plane.killcamEnt LinkTo( plane ); + start = (position[0], position[1], plane.origin[2]); + impact = BulletTrace( start, start + (0,0,-100000), true, plane); + + plane MoveTo( endpoint, ( 12000 * 2 / 12000 ) * 5/4, 0, 0 ); + + plane.killcamEnt thread followBomb( plane, position, direction, impact, player ); + + wait ( ( 12000 * 2 / 12000 ) /2 ); + if ( isdefined ( self ) ) + { + self thread dropBomb( plane, position ); + } + + wait ( ( 12000 * 2 / 12000 ) * 3/4 ); + plane Plane_CleanUpOnDeath(); +} + +function followBomb( plane, position, direction, impact, player ) +{ + player endon( "emp_jammed" ); + + wait ( ( 12000 * 2 / 12000 ) * 5 / 12 ); + plane.killcamEnt Unlink(); + plane.killcamEnt MoveTo( impact["position"] + (0,0,1000) + VectorScale( direction, -1 * 600 ), 0.8, 0, 0.2); +} + +function lookAtExplosion( bomb ) +{ + while (isdefined(self) && isdefined( bomb )) + { + angles = vectorToAngles( VectorNormalize( bomb.origin - self.origin )); + self.angles = ( max(angles[0], 15), angles[1], angles[2] ); + {wait(.05);}; + } +} + +function planeWatchForEmp( owner ) +{ + self endon( "delete" ); + self endon( "death" ); + + self waittill( "emp_deployed", attacker ); + + thread planeAwardScoreEvent( attacker, self ); + + // possibly play destroyed effect + self Plane_CleanUpOnDeath(); +} + + +function planeAwardScoreEvent( attacker, plane ) +{ + attacker endon( "disconnect" ); + attacker notify( "planeAwardScoreEvent_singleton" ); + attacker endon( "planeAwardScoreEvent_singleton" ); + waittillframeend; + + + if( isdefined( attacker ) && ( !isdefined( plane.owner ) || plane.owner util::IsEnemyPlayer( attacker ) ) ) + { + challenges::destroyedAircraft( attacker, GetWeapon( "emp" ), false ); + scoreevents::processScoreEvent( "destroyed_plane_mortar", attacker, plane.owner, GetWeapon( "emp" ) ); + attacker challenges::addFlySwatterStat( GetWeapon( "emp" ), plane ); + } +} + +function Plane_CleanUpOnDeath() +{ + self Delete(); +} + + +function dropBomb( plane, bombPosition ) +{ + if ( !isdefined( plane.owner ) ) + return; + + targets = getplayers(); + foreach( target in targets ) + { + if( plane.owner util::IsEnemyPlayer(target) && Distance2DSquared( target.origin, bombPosition ) < 250000 ) + { + if( BulletTracePassed( (target.origin[0], target.origin[1], plane.origin[2]), target.origin, false, plane) ) + { + bombPosition = target.origin; + break; + } + } + } + + bombPosition = (bombPosition[0],bombPosition[1],plane.origin[2]); + bomb = self LaunchBomb( GetWeapon( "planemortar" ), bombPosition, (0,0,-5000) ); + bomb.soundmod = "heli"; + bomb playsound( "mpl_lightning_bomb_incoming" ); + bomb.killcamEnt = plane.killcamEnt; + plane.killcamEnt thread lookAtExplosion( bomb ); +} \ No newline at end of file diff --git a/mp/killstreaks/_qrdrone.csc b/mp/killstreaks/_qrdrone.csc new file mode 100644 index 0000000..92a2200 --- /dev/null +++ b/mp/killstreaks/_qrdrone.csc @@ -0,0 +1,515 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; + + + + +#using scripts\mp\_util; +#using scripts\mp\_vehicle; + + + +// _qrdrone.csc +// Sets up clientside behavior for the qrdrone + + + + + +#precache( "client_fx", "killstreaks/fx_drgnfire_light_red_3p" ); +#precache( "client_fx", "killstreaks/fx_drgnfire_light_green_3p" ); +#precache( "client_fx", "killstreaks/fx_drgnfire_light_green_1p" ); + +#namespace qrdrone; + +function autoexec __init__sytem__() { system::register("qrdrone",&__init__,undefined,undefined); } + +function __init__() +{ + type = "qrdrone_mp"; + + clientfield::register( "helicopter", "qrdrone_state", 1, 3, "int",&stateChange, !true, !true ); + clientfield::register( "vehicle", "qrdrone_state", 1, 3, "int",&stateChange, !true, !true ); + + level._effect["qrdrone_enemy_light"] = "killstreaks/fx_drgnfire_light_red_3p"; + level._effect["qrdrone_friendly_light"] = "killstreaks/fx_drgnfire_light_green_3p"; + level._effect["qrdrone_viewmodel_light"] = "killstreaks/fx_drgnfire_light_green_1p"; + + // vehicle flags + clientfield::register( "helicopter", "qrdrone_countdown", 1, 1, "int", &start_blink, !true, !true ); + clientfield::register( "helicopter", "qrdrone_timeout", 1, 1, "int", &final_blink, !true, !true ); + + clientfield::register( "vehicle", "qrdrone_countdown", 1, 1, "int", &start_blink, !true, !true ); + clientfield::register( "vehicle", "qrdrone_timeout", 1, 1, "int", &final_blink, !true, !true ); + clientfield::register( "vehicle", "qrdrone_out_of_range", 1, 1, "int", &out_of_range_update, !true, !true ); + + vehicle::add_vehicletype_callback( "qrdrone_mp",&spawned ); +} + +function spawned( localClientNum ) // self == qrdrone +{ + self util::waittill_dobj( localClientNum ); + + self thread restartFX( localClientNum, 0 ); + + self thread collisionHandler(localClientNum); + self thread engineStutterHandler(localClientNum); + self thread QRDrone_watch_distance(); +} + +//****************************************************************** +// * +// * +//****************************************************************** +function stateChange( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + self endon("entityshutdown"); + self util::waittill_dobj( localClientNum ); + + self restartFX( localClientNum, newVal ); +} + +//****************************************************************** +// * +// * +//****************************************************************** +function restartFX( localClientNum, blinkStage ) // self == qrdrone +{ + self notify( "restart_fx" ); + + /#println( "Restart QRDrone FX: stage " + blinkStage );#/ + + switch( blinkStage ) + { + case 0: + { + self spawn_solid_fx( localClientNum ); + break; + } + case 1: + { + self.fx_interval = 1.0; + self spawn_blinking_fx( localClientNum ); + break; + } + case 2: + { + self.fx_interval = .133; + self spawn_blinking_fx( localClientNum ); + break; + } + case 3: + { + self notify( "stopfx" ); + self notify( "fx_death" ); + return; + } + } + + self thread watchRestartFX( localClientNum ); +} + +//****************************************************************** +// * +// * +//****************************************************************** +function watchRestartFX( localClientNum ) +{ + self endon("entityshutdown"); + + level util::waittill_any( "demo_jump", "player_switch", "killcam_begin", "killcam_end" ); + + self restartFX( localClientNum, clientfield::get( "qrdrone_state" )); +} + +//****************************************************************** +// * +// * +//****************************************************************** +function spawn_solid_fx( localClientNum ) // self == qrdrone +{ + if ( self IsLocalClientDriver( localClientNum ) ) + { + fx_handle = playfxontag( localClientNum, level._effect["qrdrone_viewmodel_light"], self, "tag_body" ); + } + else if ( self util::friend_not_foe( localClientNum ) ) + { + fx_handle = playfxontag( localClientNum, level._effect["qrdrone_friendly_light"], self, "tag_body" ); + } + else + { + fx_handle = playfxontag( localClientNum, level._effect["qrdrone_enemy_light"], self, "tag_body" ); + } + + self thread cleanupFX( localClientNum, fx_handle ); +} + +//****************************************************************** +// * +// * +//****************************************************************** +function spawn_blinking_fx( localClientNum ) +{ + self thread blink_fx_and_sound( localClientNum, "wpn_qr_alert" ); +} + +//****************************************************************** +// * +// * +//****************************************************************** +function blink_fx_and_sound( localClientNum, soundAlias ) +{ + self endon( "entityshutdown" ); + self endon( "restart_fx" ); + self endon( "fx_death" ); + + if ( !isdefined( self.interval ) ) + { + self.interval = 1.0; + } + + while(1) + { + self PlaySound( localClientNum, soundAlias ); + + self spawn_solid_fx( localClientNum ); + util::server_wait( localClientNum, self.interval / 2); + + self notify( "stopfx" ); + + util::server_wait( localClientNum, self.interval / 2); + self.interval = (self.interval / 1.17); + + if (self.interval < .1) + { + self.interval = .1; + } + } +} + +//****************************************************************** +// * +// * +//****************************************************************** +function cleanupFX( localClientNum, handle ) +{ + self util::waittill_any( "entityshutdown", "blink", "stopfx", "restart_fx" ); + stopfx( localClientNum, handle ); +} + +function start_blink( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if (!newVal) + return; + + self notify("blink"); +} + +// this second state is necessary so killcams show the appropriate "fast blink" state +function final_blink( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if (!newVal) + return; + + self.interval = .133; +} + +function out_of_range_update( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + model = GetUIModel( GetUIModelForController( localClientNum ), "vehicle.outOfRange" ); + if ( isdefined( model ) ) + { + SetUIModelValue( model, newVal ); + } +} + +function loop_local_sound( localClientNum, alias, interval, fx ) +{ + self endon( "entityshutdown" ); + self endon( "stopfx" ); + + level endon( "demo_jump" ); + level endon( "player_switch" ); + + // also playing the blinking light fx with the sound + + if ( !isdefined( self.interval ) ) + { + self.interval = interval; + } + + while(1) + { + self PlaySound( localClientNum, alias ); + + self spawn_solid_fx( localClientNum ); + util::server_wait( localClientNum, self.interval / 2); + + self notify( "stopfx" ); + + util::server_wait( localClientNum, self.interval / 2); + self.interval = (self.interval / 1.17); + + if (self.interval < .1) + { + self.interval = .1; + } + } +} + +function check_for_player_switch_or_time_jump( localClientNum ) +{ + self endon("entityshutdown"); + + level util::waittill_any( "demo_jump", "player_switch", "killcam_begin" ); + self notify( "stopfx" ); + + waittillframeend; + + self thread blink_light( localClientNum ); + + if ( isdefined( self.blinkStartTime ) && self.blinkStartTime <= level.serverTime ) + { + self.interval = 1; + self thread start_blink( localClientNum, true ); + } + else + { + self spawn_solid_fx( localClientNum ); + } + + self thread check_for_player_switch_or_time_jump( localClientNum ); +} + +function blink_light( localClientNum ) +{ + self endon("entityshutdown"); + level endon( "demo_jump" ); + level endon( "player_switch" ); + level endon( "killcam_begin" ); + + self waittill("blink"); + + if ( !isdefined( self.blinkStartTime ) ) + { + self.blinkStartTime = level.serverTime; + } + + if ( self IsLocalClientDriver( localClientNum ) ) + { + self thread loop_local_sound( localClientNum, "wpn_qr_alert", 1, level._effect["qrdrone_viewmodel_light"] ); + } + else if ( self util::friend_not_foe( localClientNum ) ) + { + self thread loop_local_sound( localClientNum, "wpn_qr_alert", 1, level._effect["qrdrone_friendly_light"] ); + } + else + { + self thread loop_local_sound( localClientNum, "wpn_qr_alert", 1, level._effect["qrdrone_enemy_light"] ); + } +} + + +function collisionHandler( localClientNum ) +{ + self endon( "entityshutdown" ); + + while( 1 ) + { + self waittill( "veh_collision", hip, hitn, hit_intensity ); + + driver_local_client = self GetLocalClientDriver(); + + if( isdefined( driver_local_client ) ) + { + //println( "veh_collision " + hit_intensity ); + player = getlocalplayer( driver_local_client ); + + if( isdefined( player ) ) + { + // todo - play sound here also + if( hit_intensity > 15 ) + { + player PlayRumbleOnEntity( driver_local_client, "damage_heavy" ); + } + else + { + player PlayRumbleOnEntity( driver_local_client, "damage_light" ); + } + } + } + } +} + +function engineStutterHandler( localClientNum ) +{ + self endon( "entityshutdown" ); + + while( 1 ) + { + self waittill( "veh_engine_stutter" ); + if ( self IsLocalClientDriver( localClientNum ) ) + { + player = getlocalplayer( localClientNum ); + + if( isdefined( player ) ) + { + player PlayRumbleOnEntity( localClientNum, "rcbomb_engine_stutter" ); + } + } + } +} + +function getMinimumFlyHeight() +{ + if ( !isdefined( level.airsupportHeightScale ) ) + level.airsupportHeightScale = 1; + + airsupport_height = struct::get( "air_support_height", "targetname"); + if ( isdefined(airsupport_height) ) + { + planeFlyHeight = airsupport_height.origin[2]; + } + else + { +/# + PrintLn("WARNING: Missing air_support_height entity in the map. Using default height."); +#/ + // original system + planeFlyHeight = 850; + + if ( isdefined( level.airsupportHeightScale ) ) + { + level.airsupportHeightScale = GetDvarInt( "scr_airsupportHeightScale", level.airsupportHeightScale ); + planeFlyHeight *= GetDvarInt( "scr_airsupportHeightScale", level.airsupportHeightScale ); + } + + if ( isdefined( level.forceAirsupportMapHeight ) ) + { + planeFlyHeight += level.forceAirsupportMapHeight; + } + } + + return planeFlyHeight; +} + +function QRDrone_watch_distance() +{ + self endon ("entityshutdown" ); + + qrdrone_height = struct::get( "qrdrone_height", "targetname"); + if ( isdefined(qrdrone_height) ) + { + self.maxHeight = qrdrone_height.origin[2]; + } + else + { + self.maxHeight = int(getMinimumFlyHeight()); + } + + self.maxDistance = 12800; + + level.mapCenter = GetMapCenter(); + + self.minHeight = level.mapCenter[2] - 800; + + // shouldn't be possible to start out of range, but just in case + inRangePos = self.origin; + + soundent = spawn (0, self.origin, "script_origin" ); + soundent linkto(self); + + // end static on vehicle death + self thread QRDrone_staticStopOnDeath( soundent ); + + // loop + while ( true ) + { + if ( !self QRDrone_in_range() ) + { + // increase static with distance from exit point or distance to heli in proximity + staticAlpha = 0; + while ( !self QRDrone_in_range() ) + { + if ( isdefined( self.heliInProximity ) ) + { + dist = distance( self.origin, self.heliInProximity.origin ); + staticAlpha = 1 - ( (dist-150) / (300-150) ); + } + else + { + dist = distance( self.origin, inRangePos ); + staticAlpha = min( 1, dist/200 ); + } + + + // SOUND: put sound code here to change the volume of the static while the player is + // in static. staticAlpha will be 0 - 1. 0 being no static, 1 being full static. + + + sid = soundent playloopsound ( "veh_qrdrone_static_lp", .2 ); + self vehicle::set_static_amount( staticAlpha * 2 ); + + wait ( 0.05 ); + } + + + // fade out static + self thread QRDrone_staticFade( staticAlpha, soundent, sid ); + + } + inRangePos = self.origin; + wait ( 0.05 ); + } +} + + +function QRDrone_in_range() +{ + if ( self.origin[2] < self.maxHeight && self.origin[2] > self.minHeight ) + { + if ( self isInsideHeightLock() ) + { + return true; + } + } + return false; +} + + +function QRDrone_staticFade( staticAlpha, sndent, sid ) +{ + self endon ( "entityshutdown" ); + while( self QRDrone_in_range() ) + { + staticAlpha -= 0.05; + if ( staticAlpha <= 0 ) + { + // SOUND: Put call here to completely turn static sound off + sndent StopAllLoopSounds (.5); + //delete sid; + self vehicle::set_static_amount( 0 ); + break; + } + + // SOUND: Put call here to change volume of static based on staticAlpha + setsoundvolumerate( sid, .6 ); + setsoundvolume( sid, staticAlpha ); + + self vehicle::set_static_amount( staticAlpha * 2 ); + + + wait( 0.05 ); + } +} + +function QRDrone_staticStopOnDeath( sndent ) +{ + self waittill ( "entityshutdown" ); + sndent StopAllLoopSounds (.1); + sndent delete(); +} diff --git a/mp/killstreaks/_qrdrone.gsc b/mp/killstreaks/_qrdrone.gsc new file mode 100644 index 0000000..a5a3981 --- /dev/null +++ b/mp/killstreaks/_qrdrone.gsc @@ -0,0 +1,1667 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_heatseekingmissile; +#using scripts\shared\weapons\_weaponobjects; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_shellshock; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\killstreaks\_ai_tank; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_remote_weapons; + + + + + + + + + + + + + + + + + + + +#precache( "string", "MP_REMOTE_UAV_PLACE" ); +#precache( "string", "MP_REMOTE_UAV_CANNOT_PLACE" ); +#precache( "string", "SPLASHES_DESTROYED_REMOTE_UAV" ); +#precache( "string", "SPLASHES_MARKED_BY_REMOTE_UAV" ); +#precache( "string", "SPLASHES_REMOTE_UAV_MARKED" ); +#precache( "string", "SPLASHES_TURRET_MARKED_BY_REMOTE_UAV" ); +#precache( "string", "SPLASHES_REMOTE_UAV_ASSIST" ); +#precache( "string", "KILLSTREAK_EARNED_QRDRONE" ); +#precache( "string", "KILLSTREAK_QRDRONE_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_QRDRONE_INBOUND" ); +#precache( "string", "KILLSTREAK_QRDRONE_HACKED" ); +#precache( "eventstring", "mpl_killstreak_qrdrone" ); +#precache( "fx", "killstreaks/fx_drgnfire_light_green_3p" ); +#precache( "fx", "killstreaks/fx_drgnfire_light_red_3p" ); +#precache( "fx", "killstreaks/fx_drgnfire_light_green_1p" ); +#precache( "fx", "weapon/fx_muz_md_rifle_3p" ); +#precache( "fx", "killstreaks/fx_drgnfire_explosion" ); +#precache( "fx", "killstreaks/fx_drgnfire_impact_sparks" ); +#precache( "fx", "killstreaks/fx_drgnfire_damage_state" ); +#precache( "fx", "killstreaks/fx_drgnfire_rotor_wash_runner" ); + +#namespace qrdrone; + +function init() +{ + level.qrdrone_vehicle = "qrdrone_mp"; + + level.ai_tank_stun_fx = "killstreaks/fx_agr_emp_stun"; + + level.QRDrone_minigun_flash = "weapon/fx_muz_md_rifle_3p"; + level.QRDrone_fx["explode"] = "killstreaks/fx_drgnfire_explosion"; + +// level._effect[ "quadrotor_crash" ] = "_t6/destructibles/fx_quadrotor_crash01"; + level._effect[ "quadrotor_nudge" ] = "killstreaks/fx_drgnfire_impact_sparks"; + level._effect[ "quadrotor_damage" ] = "killstreaks/fx_drgnfire_damage_state"; +// level._effect[ "quadrotor_death" ] = "_t6/destructibles/fx_quadrotor_death01"; + + level.QRDrone_dialog["launch"][0] = "ac130_plt_yeahcleared"; + level.QRDrone_dialog["launch"][1] = "ac130_plt_rollinin"; + level.QRDrone_dialog["launch"][2] = "ac130_plt_scanrange"; + + level.QRDrone_dialog["out_of_range"][0] = "ac130_plt_cleanup"; + level.QRDrone_dialog["out_of_range"][1] = "ac130_plt_targetreset"; + + level.QRDrone_dialog["track"][0] = "ac130_fco_moreenemy"; + level.QRDrone_dialog["track"][1] = "ac130_fco_getthatguy"; + level.QRDrone_dialog["track"][2] = "ac130_fco_guymovin"; + level.QRDrone_dialog["track"][3] = "ac130_fco_getperson"; + level.QRDrone_dialog["track"][4] = "ac130_fco_guyrunnin"; + level.QRDrone_dialog["track"][5] = "ac130_fco_gotarunner"; + level.QRDrone_dialog["track"][6] = "ac130_fco_backonthose"; + level.QRDrone_dialog["track"][7] = "ac130_fco_gonnagethim"; + level.QRDrone_dialog["track"][8] = "ac130_fco_personnelthere"; + level.QRDrone_dialog["track"][9] = "ac130_fco_rightthere"; + level.QRDrone_dialog["track"][10] = "ac130_fco_tracking"; + + level.QRDrone_dialog["tag"][0] = "ac130_fco_nice"; + level.QRDrone_dialog["tag"][1] = "ac130_fco_yougothim"; + level.QRDrone_dialog["tag"][2] = "ac130_fco_yougothim2"; + level.QRDrone_dialog["tag"][3] = "ac130_fco_okyougothim"; + + level.QRDrone_dialog["assist"][0] = "ac130_fco_goodkill"; + level.QRDrone_dialog["assist"][1] = "ac130_fco_thatsahit"; + level.QRDrone_dialog["assist"][2] = "ac130_fco_directhit"; + level.QRDrone_dialog["assist"][3] = "ac130_fco_rightontarget"; + + level.QRDrone_lastDialogTime = 0; + + level.QRDrone_noDeployZones = GetEntArray( "no_vehicles", "targetname" ); + + level._effect["qrdrone_prop"] = "_t6/weapon/qr_drone/fx_qr_wash_3p"; + +/# + util::set_dvar_if_unset( "scr_QRDroneFlyTime", 60 ); +#/ + //killstreaks::register( "qrdrone", "killstreak_qrdrone", "killstreak_qrdrone", "qrdrone_used",&tryUseQRDrone ); + //killstreaks::register_alt_weapon( "qrdrone", "qrdrone_turret" ); + //killstreaks::register_strings( "qrdrone", &"KILLSTREAK_EARNED_QRDRONE", &"KILLSTREAK_QRDRONE_NOT_AVAILABLE", &"KILLSTREAK_QRDRONE_INBOUND", undefined, &"KILLSTREAK_QRDRONE_HACKED" ); + //killstreaks::register_dialog( "qrdrone", "mpl_killstreak_qrdrone", "kls_recondrone_used", "", "kls_recondrone_enemy", "", "kls_recondrone_ready" ); + //killstreaks::override_entity_camera_in_demo("qrdrone", true); + + clientfield::register( "helicopter", "qrdrone_state", 1, 3, "int" ); + clientfield::register( "helicopter", "qrdrone_timeout", 1, 1, "int" ); + clientfield::register( "helicopter", "qrdrone_countdown", 1, 1, "int" ); + + clientfield::register( "vehicle", "qrdrone_state", 1, 3, "int" ); + clientfield::register( "vehicle", "qrdrone_timeout", 1, 1, "int" ); + clientfield::register( "vehicle", "qrdrone_countdown", 1, 1, "int" ); + clientfield::register( "vehicle", "qrdrone_out_of_range", 1, 1, "int" ); + + + level.qrdroneOnBlowUp = &qrdrone::QRDrone_blowup; + level.qrdroneOnDamage = &qrdrone::QRDrone_damageWatcher; +} + +function tryUseQRDrone( lifeId ) +{ + if ( self util::isUsingRemote() || isdefined( level.nukeIncoming ) ) + { + return false; + } + + if (!self IsOnGround()) + { + self iPrintLnBold( &"KILLSTREAK_QRDRONE_NOT_PLACEABLE" ); + return false; + } + + streakName = "TODO"; + result = self giveCarryQRDrone( lifeId, streakName ); + + self.isCarrying = false; + return ( result ); +} + + +function giveCarryQRDrone( lifeId, streakName ) +{ + // create carry object + carryQRDrone = createCarryQRDrone( streakName, self ); + + // give carry object and wait for placement (blocking loop) + self setCarryingQRDrone( carryQRDrone ); + + // we're back, what happened? + if ( isAlive( self ) && isdefined( carryQRDrone ) ) + { + // if it placed, start the killstreak at that location + origin = carryQRDrone.origin; + angles = self.angles; + carryQRDrone.soundEnt delete(); + carryQRDrone delete(); + + result = self startQRDrone( lifeId, streakName, origin, angles ); + } + else + { + // cancelled placement or died + result = false; + } + + return result; +} + + +// Carry Remote UAV + + +function createCarryQRDrone( streakName, owner ) +{ + pos = owner.origin + ( anglesToForward( owner.angles ) * 4 ) + ( anglesToUp( owner.angles ) * 50 ); + + carryQRDrone = spawnTurret( "misc_turret", pos, GetWeapon( "auto_gun_turret" ) ); + carryQRDrone.turretType = "sentry"; + carryQRDrone SetTurretType(carryQRDrone.turretType); + carryQRDrone.origin = pos; + carryQRDrone.angles = owner.angles; + + carryQRDrone.canBePlaced = true; + carryQRDrone makeUnusable(); + carryQRDrone.owner = owner; + carryQRDrone SetOwner( carryQRDrone.owner ); + carryQRDrone.scale = 3; + carryQRDrone.inHeliProximity = false; + + carryQRDrone thread carryQRDrone_handleExistence(); + + carryQRDrone.rangeTrigger = GetEnt( "qrdrone_range", "targetname" ); + if ( !isdefined( carryQRDrone.rangeTrigger ) ) + { + carryQRDrone.maxHeight = int(airsupport::getMinimumFlyHeight()); + carryQRDrone.maxDistance = 3600; + } + carryQRDrone.minHeight = level.mapCenter[2] - 800; + + // apparently can't call playLoopSound on a turret? + carryQRDrone.soundEnt = spawn( "script_origin", carryQRDrone.origin ); + carryQRDrone.soundEnt.angles = carryQRDrone.angles; + carryQRDrone.soundEnt.origin = carryQRDrone.origin; + carryQRDrone.soundEnt linkTo( carryQRDrone ); + carryQRDrone.soundEnt playLoopSound( "recondrone_idle_high" ); + + return carryQRDrone; +} + +function watchForAttack( ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "place_carryQRDrone" ); + self endon ( "cancel_carryQRDrone" ); + + for ( ;; ) + { + {wait(.05);}; + + if ( self attackButtonPressed() ) + { + self notify( "place_carryQRDrone" ); + } + } +} + +function setCarryingQRDrone( carryQRDrone ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + carryQRDrone thread carryQRDrone_setCarried( self ); + + if ( !carryQRDrone.canBePlaced ) + { + if ( self.team != "spectator" ) + self iPrintLnBold( &"KILLSTREAK_QRDRONE_NOT_PLACEABLE" ); + if ( isdefined( carryQRDrone.soundEnt ) ) + carryQRDrone.soundEnt delete(); + carryQRDrone delete(); + return; + } + + self.isCarrying = false; + carryQRDrone.carriedBy = undefined; + + carryQRDrone playSound( "sentry_gun_plant" ); + carryQRDrone notify ( "placed" ); +} + +function carryQRDrone_setCarried( carrier ) +{ + self setCanDamage( false ); + self setContents( 0 ); + + self.carriedBy = carrier; + carrier.isCarrying = true; + + carrier thread updateCarryQRDronePlacement( self ); + self notify ( "carried" ); +} + + +function isInRemoteNoDeploy() +{ + if ( isdefined( level.QRDrone_noDeployZones ) && level.QRDrone_noDeployZones.size ) + { + foreach( zone in level.QRDrone_noDeployZones ) + { + if ( self isTouching( zone ) ) + return true; + } + } + return false; +} + + +function updateCarryQRDronePlacement( carryQRDrone ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + carryQRDrone endon ( "placed" ); + carryQRDrone endon ( "death" ); + + carryQRDrone.canBePlaced = true; + lastCanPlaceCarryQRDrone = -1; // force initial update + + for( ;; ) + { + heightOffset = 18; + switch( self getStance() ) + { + case "stand": + heightOffset = 40; + break; + case "crouch": + heightOffset = 25; + break; + case "prone": + heightOffset = 10; + break; + } + + placement = self CanPlayerPlaceVehicle( 22, 22, 50, heightOffset, 0, 0 ); + carryQRDrone.origin = placement[ "origin" ] + ( anglesToUp(self.angles) * ( 18 - -9 ) ); + carryQRDrone.angles = placement[ "angles" ]; + carryQRDrone.canBePlaced = self isOnGround() && placement[ "result" ] && carryQRDrone QRDrone_in_range() && !carryQRDrone isInRemoteNoDeploy(); + + if ( carryQRDrone.canBePlaced != lastCanPlaceCarryQRDrone ) + { + if ( carryQRDrone.canBePlaced ) + { + // if they're holding it in launch position just launch now + if ( self attackButtonPressed() ) + self notify( "place_carryQRDrone" ); + } + else + { + } + } + + lastCanPlaceCarryQRDrone = carryQRDrone.canBePlaced; + {wait(.05);}; + } +} + + +function carryQRDrone_handleExistence() +{ + level endon ( "game_ended" ); + self endon("death"); + self.owner endon ( "place_carryQRDrone" ); + self.owner endon ( "cancel_carryQRDrone" ); + + self.owner util::waittill_any( "death", "disconnect", "joined_team", "joined_spectators" ); + + if ( isdefined( self ) ) + { + self delete(); + } +} + +function removeRemoteWeapon() +{ + level endon( "game_ended" ); + self endon ( "disconnect" ); + + wait(0.7); + +} + + +function startQRDrone( lifeId, streakName, origin, angles ) +{ + self lockPlayerForQRDroneLaunch(); + self util::setUsingRemote( streakName ); + self util::freeze_player_controls( true ); + + // blocking function waiting for the tablet to be switched to + result = self killstreaks::init_ride_killstreak( "qrdrone" ); + + if ( result != "success" || level.gameEnded ) + { + if ( result != "disconnect" ) + { + self util::freeze_player_controls( false ); + self killstreakrules::isKillstreakAllowed( "qrdrone", self.team ); + self notify( "qrdrone_unlock" ); + self killstreaks::clear_using_remote(); + } + return false; + } + + team = self.team; + killstreak_id = self killstreakrules::killstreakStart( "qrdrone", team, false, true ); + if ( killstreak_id == -1 ) + { + self notify( "qrdrone_unlock" ); + self util::freeze_player_controls( false ); + self killstreaks::clear_using_remote(); + return false; + } + + self notify( "qrdrone_unlock" ); + QRDrone = createQRDrone( lifeId, self, streakName, origin, angles, killstreak_id ); + self util::freeze_player_controls( false ); + if ( isdefined( QRDrone ) ) + { + self thread QRDrone_Ride( lifeId, QRDrone, streakName ); + QRDrone waittill( "end_remote" ); + + killstreakrules::killstreakStop( "qrdrone", team, killstreak_id ); + return true; + } + else + { + self iPrintLnBold( &"MP_TOO_MANY_VEHICLES" ); + self killstreaks::clear_using_remote(); + killstreakrules::killstreakStop( "qrdrone", team, killstreak_id ); + return false; + } +} + +function lockPlayerForQRDroneLaunch() +{ + // lock + lockSpot = spawn( "script_origin", self.origin ); + lockSpot hide(); + self playerLinkTo( lockSpot ); + + // wait for unlock + self thread clearPlayerLockFromQRDroneLaunch( lockSpot ); +} + + +function clearPlayerLockFromQRDroneLaunch( lockSpot ) +{ + level endon( "game_ended" ); + + msg = self util::waittill_any_return( "disconnect", "death", "qrdrone_unlock" ); + + lockSpot delete(); +} + + +function createQRDrone( lifeId, owner, streakName, origin, angles, killstreak_id ) +{ + QRDrone = spawnHelicopter( owner, origin, angles, level.qrdrone_vehicle, "veh_t6_drone_quad_rotor_mp" ); + if ( !isdefined( QRDrone ) ) + return undefined; + + QRDrone.lifeId = lifeId; + QRDrone.team = owner.team; + QRDrone.pers["team"] = owner.team; + QRDrone.owner = owner; + QRDrone clientfield::set( "enemyvehicle", 1 ); + QRDrone.health = 999999; // keep it from dying anywhere in code + QRDrone.maxHealth = 250; // this is the health we'll check + QRDrone.damageTaken = 0; + QRDrone.destroyed = false; + QRDrone setCanDamage( true ); + QRDrone EnableAimAssist(); + + QRDrone.smoking = false; + QRDrone.inHeliProximity = false; + QRDrone.heliType = "qrdrone"; + QRDrone.markedPlayers = []; + QRDrone.isStunned = false; + QRDrone SetEnemyModel( "veh_t6_drone_quad_rotor_mp_alt" ); + QRDrone SetDrawInfrared( true ); + + QRDrone.killCamEnt = QRDrone.owner; + + owner weaponobjects::addWeaponObjectToWatcher( "qrdrone", QRDrone ); + QRDrone thread QRDrone_explode_on_notify(killstreak_id); + QRDrone thread QRDrone_explode_on_game_end(); + + QRDrone thread QRDrone_leave_on_timeout( streakName ); + QRDrone thread QRDrone_watch_distance(); + QRDrone thread QRDrone_watch_for_exit(); + + QRDrone thread deleteOnKillbrush( owner ); + + // make the qrdrone targetable + Target_Set( QRDrone, (0,0,0) ); + Target_SetTurretAquire( QRDrone, false ); + + QRDrone.numFlares = 0; + QRDrone.flareOffset = (0,0,-100); + QRDrone thread heatseekingmissile::MissileTarget_LockOnMonitor( self, "end_remote" ); // monitors missle lock-ons + QRDrone thread heatseekingmissile::MissileTarget_ProximityDetonateIncomingMissile( "crashing" ); + + QRDrone.emp_fx = spawn( "script_model", self.origin ); + QRDrone.emp_fx SetModel( "tag_origin" ); + QRDrone.emp_fx LinkTo( self, "tag_origin", (0,0,-20) + AnglesToForward(self.angles) * 6 ); + + // create the influencers + QRDrone spawning::create_entity_enemy_influencer( "small_vehicle", QRDrone.team ); + QRDrone spawning::create_entity_enemy_influencer( "qrdrone_cylinder", QRDrone.team ); + + return QRDrone; +} + + +function QRDrone_ride( lifeId, QRDrone, streakName ) +{ + QRDrone.playerLinked = true; + self.restoreAngles = self.angles; + + QRDrone usevehicle( self, 0 ); + self util::clientNotify( "qrfutz" ); + self killstreaks::play_killstreak_start_dialog( "qrdrone", self.pers["team"] ); + + self AddWeaponStat( GetWeapon( "killstreak_qrdrone" ), "used", 1 ); + + self.qrdrone_rideLifeId = lifeId; + self.QRDrone = QRDrone; + + self thread QRDrone_delayLaunchDialog( QRDrone ); + self thread QRDrone_fireGuns( QRDrone ); + QRDrone thread play_lockon_sounds( self ); + + if ( isdefined( level.qrdrone_vision ) ) + self setVisionsetWaiter(); +} + +function QRDrone_delayLaunchDialog( QRDrone ) +{ + level endon( "game_ended" ); + self endon ( "disconnect" ); + QRDrone endon ( "death" ); + QRDrone endon ( "end_remote" ); + QRDrone endon ( "end_launch_dialog" ); + + wait( 3 ); + self QRDrone_dialog( "launch" ); +} + +function QRDrone_Unlink( QRDrone ) +{ + if ( isdefined( QRDrone ) ) + { + QRDrone.playerLinked = false; + self destroyHud(); + + if ( isdefined( self.viewlockedentity ) ) + { + self Unlink(); + if ( isdefined(level.gameEnded) && level.gameEnded ) + { + self util::freeze_player_controls( true ); + } + } + } +} + + +function QRDrone_endride( QRDrone ) +{ + if ( isdefined( QRDrone ) ) + { + QRDrone notify( "end_remote" ); + + self killstreaks::clear_using_remote(); + + self setPlayerAngles( self.restoreAngles ); + + if ( isalive(self) ) + { + self killstreaks::switch_to_last_non_killstreak_weapon(); + } + + self thread QRDrone_freezeBuffer(); + } + self.QRDrone = undefined; +} + +function play_lockon_sounds( player ) +{ + player endon("disconnect"); + self endon( "death" ); + self endon ( "blowup" ); + self endon ( "crashing" ); + level endon ( "game_ended" ); + self endon ( "end_remote" ); + + self.lockSounds = spawn( "script_model", self.origin); + wait ( 0.1 ); + self.lockSounds LinkTo( self, "tag_player" ); + + while ( true ) + { + self waittill( "locking on" ); + + while ( true ) + { + if ( enemy_locking() ) + { + //self.lockSounds PlaySoundToPlayer( "uin_alert_lockon_start", player ); + //wait ( 0.3 ); + + self.lockSounds PlaySoundToPlayer( "uin_alert_lockon", player ); + wait ( 0.125 ); + } + + if ( enemy_locked() ) + { + self.lockSounds PlaySoundToPlayer( "uin_alert_lockon", player ); + wait ( 0.125 ); + } + + if ( !enemy_locking() && !enemy_locked() ) + { + self.lockSounds StopSounds(); + break; + } + } + } +} + +function enemy_locking() +{ + if ( isdefined(self.locking_on) && self.locking_on ) + return true; + + return false; +} + +function enemy_locked() +{ + if ( isdefined(self.locked_on) && self.locked_on ) + return true; + + return false; +} + + +function QRDrone_freezeBuffer() +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + self util::freeze_player_controls( true ); + wait( 0.5 ); + self util::freeze_player_controls( false ); +} + + +function QRDrone_playerExit( QRDrone ) +{ + level endon( "game_ended" ); + self endon ( "disconnect" ); + QRDrone endon ( "death" ); + QRDrone endon ( "end_remote" ); + + // delay exit for transition into remote + wait( 2 ); + + while( true ) + { + timeUsed = 0; + while( self UseButtonPressed() ) + { + timeUsed += 0.05; + if( timeUsed > 0.75 ) + { + QRDrone thread QRDrone_leave(); + return; + } + {wait(.05);}; + } + {wait(.05);}; + } +} + +function touchedKillbrush() +{ + if ( isdefined(self) ) + { + self clientfield::set( "qrdrone_state", 3 ); + watcher = self.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + watcher thread weaponobjects::waitAndDetonate( self, 0.0); + } +} + +function deleteOnKillbrush(player) +{ + player endon("disconnect"); + self endon("death"); + + killbrushes = []; + hurt = GetEntArray( "trigger_hurt","classname" ); + + foreach( trig in hurt ) + { + if ( trig.origin[2] <= player.origin[2] && ( !isDefined( trig.script_parameters ) || trig.script_parameters != "qrdrone_safe" ) ) + { + killbrushes[ killbrushes.size ] = trig; + } + } + + crate_triggers = GetEntArray( "crate_kill_trigger", "targetname" ); + + while(1) + { + for (i = 0; i < killbrushes.size; i++) + { + if (self istouching(killbrushes[i]) ) + { + self touchedKillbrush(); + return; + } + } + + foreach( trigger in crate_triggers ) + { + if ( trigger.active && self istouching(trigger) ) + { + self touchedKillbrush(); + return; + } + } + + if ( isdefined( level.levelKillbrushes ) ) + { + foreach( trigger in level.levelKillbrushes ) + { + if (self istouching(trigger) ) + { + self touchedKillbrush(); + return; + } + } + } + + if ( level.script == "mp_castaway" ) + { + origin = self.origin - ( 0, 0, 12 ); + water = GetWaterHeight( origin ); + + if ( water - origin[2] > 0 ) + { + self touchedKillbrush(); + return; + } + } + + wait( 0.1 ); + } +} + +function QRDrone_force_destroy() +{ + self clientfield::set( "qrdrone_state", 3 ); + watcher = self.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + watcher thread weaponobjects::waitAndDetonate( self, 0.0); +} + +function QRDrone_get_damage_effect( health_pct ) +{ + if( health_pct > .5 ) + { + return level._effect[ "quadrotor_damage" ]; + } + + return undefined; +} + +function QRDrone_play_single_fx_on_tag( effect, tag ) +{ + if( isdefined( self.damage_fx_ent ) ) + { + if( self.damage_fx_ent.effect == effect ) + { + // already playing + return; + } + self.damage_fx_ent delete(); + } + + +// ent = spawn( "script_model", ( 0, 0, 0 ) ); +// ent SetModel( "tag_origin" ); +// ent.origin = self GetTagOrigin( tag ); +// ent.angles = self GetTagAngles( tag ); +// ent NotSolid(); +// ent Hide(); +// ent LinkTo( self, tag ); +// ent.effect = effect; +// playfxontag( effect, ent, "tag_origin" ); +// ent playsound("veh_qrdrone_sparks"); +// +// +// self.damage_fx_ent = ent; + + playfxontag( effect, self, "tag_origin" ); + +} + +function QRDrone_update_damage_fx( health_percent ) +{ + effect = QRDrone_get_damage_effect( health_percent ); + if( isdefined( effect ) ) + { + QRDrone_play_single_fx_on_tag( effect, "tag_origin" ); + } + else + { + if( isdefined( self.damage_fx_ent ) ) + { + self.damage_fx_ent delete(); + } + } +} + +function QRDrone_damageWatcher() +{ + self endon( "death" ); + + self.maxhealth = 999999; + self.health = self.maxhealth; + self.maxhealth = 225; + + low_health = false; + damage_taken = 0; + + for ( ;; ) + { + self waittill( "damage", damage, attacker, dir, point, mod, model, tag, part, weapon, flags ); + + if( !isdefined( attacker ) || !isplayer( attacker ) ) + continue; + + self.owner playrumbleonentity("damage_heavy"); + +/# + self.damage_debug = ( damage + " (" + weapon.name + ")" ); +#/ + + if ( mod == "MOD_RIFLE_BULLET" || mod == "MOD_PISTOL_BULLET") + { + if ( isPlayer( attacker ) ) + { + if ( attacker HasPerk( "specialty_armorpiercing" ) ) + { + damage += int( damage * level.cac_armorpiercing_data ); + } + } + + if (weapon.weapClass == "spread") + damage = damage * 2; + } + + if ( weapon.isEmp && (mod == "MOD_GRENADE_SPLASH")) + { + damage_taken += ( 225 ); + damage = 0; + } + + if (!self.isStunned) + { + if ( weapon.isStun && (mod == "MOD_GRENADE_SPLASH" || mod == "MOD_GAS") ) + { + self.isStunned = true; + self QRDrone_stun( 2 ); + } + } + + self.attacker = attacker; + + self.owner SendKillstreakDamageEvent( int(damage) ); + + damage_taken += damage; + + + if ( damage_taken >= 225 ) + { + //this is for HUD screen scramble + self.owner SendKillstreakDamageEvent( 200 ); + + self QRDrone_death( attacker, weapon, dir, mod ); + return; + } + else + { + QRDrone_update_damage_fx( float(damage_taken) / 225 ); + } + } +} + +function QRDrone_stun( duration ) +{ + self endon( "death" ); + self notify( "stunned" ); + + //PlayFX( level.ai_tank_stun_fx, self.origin + (0,0,-20) + AnglesToForward(self.angles) * 6, AnglestoForward(self.angles) ); + + self.owner util::freeze_player_controls( true ); + + if (isdefined(self.owner.fullscreen_static)) + { + self.owner thread remote_weapons::stunStaticFX( duration ); + } + wait ( duration ); + + self.owner util::freeze_player_controls( false ); + + self.isStunned = false; +} + +function QRDrone_death( attacker, weapon, dir, damageType ) +{ + if( isdefined( self.damage_fx_ent ) ) + { + self.damage_fx_ent delete(); + } + + if ( isdefined(attacker) && IsPlayer(attacker) && attacker != self.owner) + { + level thread popups::DisplayTeamMessageToAll( &"SCORE_DESTROYED_QRDRONE", attacker ); + if ( self.owner util::IsEnemyPlayer( attacker ) ) + { + attacker challenges::destroyedQRDrone( damageType, weapon ); + //scoreevents::processScoreEvent( "destroyed_qrdrone", attacker, self.owner, weapon ); + attacker AddWeaponStat( weapon, "destroyed_qrdrone", 1 ); + attacker challenges::addFlySwatterStat( weapon, self ); + attacker AddWeaponStat( weapon, "destroyed_controlled_killstreak", 1 ); + } + else + { + //Destroyed Friendly Killstreak + } + } + + self thread QRDrone_crash_movement( attacker, dir ); + if ( weapon.isEmp ) + { + PlayFXOnTag( level.ai_tank_stun_fx, self.emp_fx, "tag_origin" ); + } + + self waittill( "crash_done" ); + + if ( isdefined(self.emp_fx) ) + { + self.emp_fx delete(); + } + // A dynEnt will be spawned in the collision thread when it hits the ground and "crash_done" notify will be sent + //self freeVehicle(); + //wait 20; + self clientfield::set( "qrdrone_state", 3 ); + watcher = self.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + watcher thread weaponobjects::waitAndDetonate( self, 0.0, attacker, weapon ); +} + +function death_fx() +{ + playfxontag( self.deathfx, self, self.deathfxtag ); + self playsound("veh_qrdrone_sparks"); +} + +function QRDrone_crash_movement( attacker, hitdir ) +{ + self endon( "crash_done" ); + self endon( "death" ); + self notify( "crashing" ); + + // take away driver control + self takeplayercontrol(); + + self SetMaxPitchRoll( 90, 180 ); + self SetPhysAcceleration( ( 0, 0, -800 ) ); + + side_dir = VectorCross( hitdir, (0,0,1) ); + side_dir_mag = RandomFloatRange( -100, 100 ); + side_dir_mag += math::sign( side_dir_mag ) * 80; + side_dir *= side_dir_mag; + + velocity = self GetVelocity(); + self SetVehVelocity( velocity + (0,0,100) + VectorNormalize( side_dir ) ); + + ang_vel = self GetAngularVelocity(); + ang_vel = ( ang_vel[0] * 0.3, ang_vel[1], ang_vel[2] * 0.3 ); + + yaw_vel = RandomFloatRange( 0, 210 ) * math::sign( ang_vel[1] ); + yaw_vel += math::sign( yaw_vel ) * 180; + + ang_vel += ( RandomFloatRange( -100, 100 ), yaw_vel, RandomFloatRange( -200, 200 ) ); + + self SetAngularVelocity( ang_vel ); + + self.crash_accel = RandomFloatRange( 75, 110 ); + + self thread QRDrone_crash_accel(); + self thread QRDrone_collision(); + + //drone death sounds JM - play 1 shot hit, turn off main loop, thread dmg loop + self playsound("veh_qrdrone_dmg_hit"); + self thread QRDrone_dmg_snd(); + + wait 0.1; + + if( RandomInt( 100 ) < 40 ) + { + self thread QRDrone_fire_for_time( RandomFloatRange( 0.7, 2.0 ) ); + } + + wait 2; + + // failsafe notify + self notify( "crash_done" ); +} + + +function QRDrone_dmg_snd() +{ + dmg_ent = spawn("script_origin", self.origin); + dmg_ent linkto (self); + dmg_ent PlayLoopSound ("veh_qrdrone_dmg_loop"); + self util::waittill_any("crash_done", "death"); + dmg_ent stoploopsound(.2); + wait (2); + dmg_ent delete(); +} + +function QRDrone_fire_for_time( totalFireTime ) +{ + self endon( "crash_done" ); + self endon( "change_state" ); + self endon( "death" ); + + weapon = self SeatGetWeapon( 0 ); + fireTime = weapon.fireTime; + time = 0; + + fireCount = 1; + + while( time < totalFireTime ) + { + self FireWeapon(); + fireCount++; + wait fireTime; + time += fireTime; + } +} + +function QRDrone_crash_accel() +{ + self endon( "crash_done" ); + self endon( "death" ); + + count = 0; + + while( 1 ) + { + velocity = self GetVelocity(); + self SetVehVelocity( velocity + AnglesToUp( self.angles ) * self.crash_accel ); + self.crash_accel *= 0.98; + + wait 0.1; + + count++; + if( count % 8 == 0 ) + { + if( RandomInt( 100 ) > 40 ) + { + if( velocity[2] > 150.0 ) + { + self.crash_accel *= 0.75; + } + else if( velocity[2] < 40.0 && count < 60 ) + { + if( Abs( self.angles[0] ) > 30 || Abs( self.angles[2] ) > 30 ) + { + self.crash_accel = RandomFloatRange( 160, 200 ); + } + else + { + self.crash_accel = RandomFloatRange( 85, 120 ); + } + } + } + } + } +} + +function QRDrone_collision() +{ + self endon( "crash_done" ); + self endon( "death" ); + + while( 1 ) + { + self waittill( "veh_collision", velocity, normal ); + ang_vel = self GetAngularVelocity() * 0.5; + self SetAngularVelocity( ang_vel ); + + velocity = self GetVelocity(); + + // bounce off walls + if( normal[2] < 0.7 ) + { + self SetVehVelocity( velocity + normal * 70 ); + self playsound ("veh_qrdrone_wall"); + PlayFX( level._effect[ "quadrotor_nudge" ], self.origin ); + } + else + { + //self.crash_accel *= 0.5; + //self SetVehVelocity( self.velocity * 0.8 ); +// CreateDynEntAndLaunch( self.deathmodel, self.origin, self.angles, self.origin, velocity * 0.03, level._effect[ "quadrotor_crash" ], 1 ); + self playsound ("veh_qrdrone_explo"); + self notify( "crash_done" ); + } + } +} + +function QRDrone_watch_distance( zoffset, minHeightOverride ) +{ + self endon ("death" ); + + self.owner inithud(); + + // Reset the ui model. Setting it dircetly to 0 doest seem to do it + self clientfield::set( "qrdrone_out_of_range", 1 ); + {wait(.05);}; + self clientfield::set( "qrdrone_out_of_range", 0 ); + + qrdrone_height = struct::get( "qrdrone_height", "targetname"); + if ( isdefined(qrdrone_height) ) + { + self.maxHeight = qrdrone_height.origin[2]; + } + else + { + self.maxHeight = int(airsupport::getMinimumFlyHeight()); + } + + if( isdefined( zoffset ) ) + self.maxHeight += zoffset; + + self.maxDistance = 12800; + + self.minHeight = level.mapCenter[2] - 800; + if( isdefined( minHeightOverride ) ) + self.minHeight = minHeightOverride; + + // ent to put headicon on for pointing to inside of map when they go out of range + self.centerRef = spawn( "script_model", level.mapCenter ); + + // shouldn't be possible to start out of range, but just in case + inRangePos = self.origin; + + self.rangeCountdownActive = false; + + // loop + while ( true ) + { + if ( !self QRDrone_in_range() ) + { + // increase static with distance from exit point or distance to heli in proximity + staticAlpha = 0; + while ( !self QRDrone_in_range() ) + { + if ( !self.rangeCountdownActive ) + { + self.rangeCountdownActive = true; + self thread QRDrone_rangeCountdown(); + } + if ( isdefined( self.heliInProximity ) ) + { + dist = distance( self.origin, self.heliInProximity.origin ); + staticAlpha = 1 - ( (dist-150) / (300-150) ); + } + else + { + dist = distance( self.origin, inRangePos ); + staticAlpha = min( .7, dist/200 ); + } + + self.owner set_static_alpha( staticAlpha, self ); + + {wait(.05);}; + } + + // end countdown + self notify( "in_range" ); + self.rangeCountdownActive = false; + + // fade out static + self thread QRDrone_staticFade( staticAlpha ); + } + inRangePos = self.origin; + {wait(.05);}; + } +} + + +function QRDrone_in_range() +{ + if ( self.origin[2] < self.maxHeight && self.origin[2] > self.minHeight && !self.inHeliProximity ) + { + if ( self isMissileInsideHeightLock() ) + { + return true; + } + } + return false; +} + + +function QRDrone_staticFade( staticAlpha ) +{ + self endon ( "death" ); + while( self QRDrone_in_range() ) + { + staticAlpha -= 0.05; + if ( staticAlpha < 0 ) + { + self.owner set_static_alpha( staticAlpha, self ); + break; + } + self.owner set_static_alpha( staticAlpha, self ); + + {wait(.05);}; + } +} + + +function QRDrone_rangeCountdown() +{ + self endon( "death" ); + self endon( "in_range" ); + + if ( isdefined( self.heliInProximity ) ) + countdown = 6.1; + else + countdown = 6.1; + + hostmigration::waitLongDurationWithHostMigrationPause( countdown ); + + self.owner notify( "stop_signal_failure" ); + + if( isdefined( self.distance_shutdown_override ) ) + { + return [[ self.distance_shutdown_override ]](); + } + + self clientfield::set( "qrdrone_state", 3 ); + watcher = self.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + watcher thread weaponobjects::waitAndDetonate(self,0); +} + + +function QRDrone_explode_on_notify( killstreak_id ) +{ + self endon ( "death" ); + self endon( "end_ride" ); + + self.owner util::waittill_any( "disconnect", "joined_team", "joined_spectators" ); + + if( isdefined( self.owner ) ) + { + self.owner killstreaks::clear_using_remote(); + self.owner destroyHud(); + self.owner QRDrone_endride( self ); + } + else + { + killstreakrules::killstreakStop( "qrdrone", self.team, killstreak_id ); + } + + self clientfield::set( "qrdrone_state", 3 ); + watcher = self.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + watcher thread weaponobjects::waitAndDetonate(self,0); +} + + +function QRDrone_explode_on_game_end() +{ + self endon ( "death" ); + + level waittill( "game_ended" ); + + self clientfield::set( "qrdrone_state", 3 ); + watcher = self.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + watcher weaponobjects::waitAndDetonate(self,0); + self.owner QRDrone_endride( self ); +} + + +function QRDrone_leave_on_timeout( killstreakName ) +{ + qrdrone = self; + qrdrone endon ( "death" ); + + if ( !level.vehiclesTimed ) + return; + + qrdrone.flyTime = 60.0; + waittime = self.flyTime - 10; +/# + util::set_dvar_int_if_unset( "scr_QRDroneFlyTime", qrdrone.flyTime ); + qrdrone.flyTime = GetDvarInt( "scr_QRDroneFlyTime" ); + waittime = self.flyTime - 10; + if( waittime < 0 ) + { + wait( qrdrone.flyTime ); + self clientfield::set( "qrdrone_state", 3 ); + watcher = qrdrone.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + watcher thread weaponobjects::waitAndDetonate(qrdrone,0); + return; + } +#/ + + qrdrone thread killstreaks::WaitForTimeout( killstreakName, waittime, &QRDrone_leave_on_timeout_callback, "death" ); +} + +function QRDrone_leave_on_timeout_callback() +{ + qrdrone = self; + + qrdrone clientfield::set( "qrdrone_state", 1 ); + qrdrone clientfield::set( "qrdrone_countdown", 1 ); + hostmigration::waitLongDurationWithHostMigrationPause( 6 ); + + qrdrone clientfield::set( "qrdrone_state", 2 ); + qrdrone clientfield::set( "qrdrone_timeout", 1 ); + hostmigration::waitLongDurationWithHostMigrationPause( 4 ); + + qrdrone clientfield::set( "qrdrone_state", 3 ); + watcher = self.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + watcher thread weaponobjects::waitAndDetonate(self,0); +} + + +function QRDrone_leave() +{ + level endon( "game_ended" ); + self endon( "death" ); + + // disengage player + self notify( "leaving" ); + self.owner QRDrone_Unlink( self ); + self.owner QRDrone_endride( self ); + + // remove + self notify( "death" ); +} + +function QRDrone_exit_button_pressed() +{ + return self UseButtonPressed(); +} + +function QRDrone_watch_for_exit() +{ + level endon( "game_ended" ); + self endon( "death" ); + self.owner endon( "disconnect" ); + + wait( 1 ); + + while( true ) + { + timeUsed = 0; + while( self.owner QRDrone_exit_button_pressed() ) + { + timeUsed += 0.05; + if ( timeUsed > 0.25 ) + { + self clientfield::set( "qrdrone_state", 3 ); + watcher = self.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + watcher thread weaponobjects::waitAndDetonate( self, 0.0, self.owner ); + return; + } + {wait(.05);}; + } + {wait(.05);}; + } +} + +function QRDrone_cleanup() +{ + if ( level.gameEnded ) + { + return; + } + + if( isdefined( self.owner ) ) + { + if ( self.playerLinked == true ) + self.owner QRDrone_Unlink( self ); + + self.owner QRDrone_endride( self ); + } + + if ( isdefined( self.scrambler ) ) + self.scrambler delete(); + + if ( isdefined(self) && isdefined( self.centerRef ) ) + self.centerRef delete(); + + Target_SetTurretAquire( self, false ); + + if( isdefined( self.damage_fx_ent ) ) + { + self.damage_fx_ent delete(); + } + + if ( isdefined( self.emp_fx ) ) + { + self.emp_fx delete(); + } + + self delete(); +} + + +function QRDrone_light_fx() +{ + playFXOnTag( level.chopper_fx["light"]["belly"], self, "tag_light_nose" ); + {wait(.05);}; + playFXOnTag( level.chopper_fx["light"]["tail"], self, "tag_light_tail1" ); +} + + +function QRDrone_dialog( dialogGroup ) +{ + if ( dialogGroup == "tag" ) + waitTime = 1000; + else + waitTime = 5000; + + if ( getTime() - level.QRDrone_lastDialogTime < waitTime ) + return; + + level.QRDrone_lastDialogTime = getTime(); + + randomIndex = randomInt( level.QRDrone_dialog[ dialogGroup ].size ); + soundAlias = level.QRDrone_dialog[ dialogGroup ][ randomIndex ]; + + self playLocalSound( soundAlias ); +} + +function QRDrone_watchHeliProximity() +{ + level endon( "game_ended" ); + self endon( "death" ); + self endon( "end_remote" ); + + while( true ) + { + inHeliProximity = false; + + if ( !self.inHeliProximity && inHeliProximity ) + self.inHeliProximity = true; + else if ( self.inHeliProximity && !inHeliProximity ) + { + self.inHeliProximity = false; + self.heliInProximity = undefined; + } + + {wait(.05);}; + } +} + + +function QRDrone_detonateWaiter() +{ + self.owner endon("disconnect"); + self endon("death"); + + while( self.owner attackbuttonpressed() ) + {wait(.05);}; + + watcher = self.owner weaponobjects::getWeaponObjectWatcher( "qrdrone" ); + + while( !self.owner attackbuttonpressed() ) + {wait(.05);}; + + self clientfield::set( "qrdrone_state", 3 ); + watcher thread weaponobjects::waitAndDetonate(self,0); + + self.owner thread hud::fade_to_black_for_x_sec( GetDvarfloat( "scr_rcbomb_fadeOut_delay" ), GetDvarfloat( "scr_rcbomb_fadeOut_timeIn" ), GetDvarfloat( "scr_rcbomb_fadeOut_timeBlack" ), GetDvarfloat( "scr_rcbomb_fadeOut_timeOut" ) ); +} + +function QRDrone_fireGuns( QRDrone ) +{ + self endon ( "disconnect" ); + QRDrone endon ( "death" ); + QRDrone endon ( "blowup" ); + QRDrone endon ( "crashing" ); + level endon ( "game_ended" ); + QRDrone endon ( "end_remote" ); + + // transition into remote + wait( 1 ); + + while ( true ) + { + if ( self AttackButtonPressed() ) + { + QRDrone FireWeapon(); + weapon = GetWeapon( "qrdrone_turret" ); + fireTime = weapon.fireTime; + + wait( fireTime ); + } + else + { + {wait(.05);}; + } + } +} + +function QRDrone_blowup(attacker, weapon) +{ + self.owner endon("disconnect"); + self endon ("death"); + + self notify("blowup"); + + explosionOrigin = self.origin; + explosionAngles = self.angles; + + if ( !isdefined( attacker ) ) + { + attacker = self.owner; + } + + origin = self.origin + (0,0,10); + radius = 256; + min_damage = 10; + max_damage = 35; + + if ( isdefined(attacker) ) + { + self radiusDamage( origin, radius, max_damage, min_damage, attacker, "MOD_EXPLOSIVE", self.weapon ); + } + PhysicsExplosionSphere( origin, radius, radius, 1, max_damage, min_damage ); + shellshock::rcbomb_earthquake( origin ); + + // CDC - play rc car exlposion sound TO DO replace with final explo sound after effects are in + playsoundatposition("veh_qrdrone_explo", self.origin); + + PlayFX( level.QRDrone_fx["explode"] , explosionOrigin, (0, 0, 1 )); + + self Hide(); + if( isdefined(self.owner)) + { + self.owner util::clientNotify("qrdrone_blowup"); + + if ( attacker != self.owner ) + { + level.globalKillstreaksDestroyed++; + attacker AddWeaponStat( self.weapon, "destroyed", 1 ); + } + self.owner remote_weapons::destroyRemoteHUD(); + + self.owner util::freeze_player_controls( true ); + self.owner SendKillstreakDamageEvent( 600 ); + wait(0.75); + self.owner thread hud::fade_to_black_for_x_sec( 0, 0.25, 0.1, 0.25 ); + wait(0.25); + self.owner QRDrone_Unlink( self ); + self.owner util::freeze_player_controls( false ); + + if ( isdefined( self.neverDelete ) && self.neverDelete ) + { + return; + } + } + + QRDrone_cleanup(); +} + +// self == player +function setVisionsetWaiter() +{ + self endon("disconnect"); + + self UseServerVisionset( true ); + self SetVisionSetForPlayer( level.qrdrone_vision, 1 ); + + self.QRDrone waittill("end_remote"); + + self UseServerVisionset( false ); +} + +function inithud() +{ + /*self.fullscreen_static = newclienthudelem( self ); + self.fullscreen_static.x = 0; + self.fullscreen_static.y = 0; + self.fullscreen_static.horzAlign = "fullscreen"; + self.fullscreen_static.vertAlign = "fullscreen"; + self.fullscreen_static.hidewhendead = false; + self.fullscreen_static.hidewheninmenu = true; + self.fullscreen_static.immunetodemogamehudsettings = true; + self.fullscreen_static.sort = 0; + self.fullscreen_static SetShader( "tow_filter_overlay_no_signal", 640, 480 ); + self.fullscreen_static.alpha = 0;*/ +} + +function destroyHud() +{ + if( isdefined(self) ) + { + self notify ( "stop_signal_failure" ); + self.flashingSignalFailure = false; + self clientfield::set_to_player( "static_postfx", 0 ); + + if ( isdefined( self.fullscreen_static ) ) + self.fullscreen_static destroy(); + + self remote_weapons::destroyRemoteHUD(); + self util::clientNotify( "nofutz" ); + } +} + +function set_static_alpha( alpha, drone ) +{ + if ( isdefined( self.fullscreen_static ) ) + { + self.fullscreen_static.alpha = alpha; + } + + if ( alpha > 0 ) + { + if( !isdefined( self.flashingSignalFailure ) || !self.flashingSignalFailure ) + { + self thread flash_signal_failure( drone ); + self.flashingSignalFailure = true; + if ( self IsRemoteControlling() ) + self clientfield::set_to_player( "static_postfx", 1 ); + } + } + else + { + self notify ( "stop_signal_failure" ); + drone clientfield::set( "qrdrone_out_of_range", 0 ); + self.flashingSignalFailure = false; + self clientfield::set_to_player( "static_postfx", 0 ); + } +} + +function flash_signal_failure( drone ) +{ + self endon( "stop_signal_failure" ); + drone endon( "death" ); + drone clientfield::set( "qrdrone_out_of_range", 1 ); + i = 0; + for ( ;; ) + { + drone PlaySoundToPlayer( "uin_alert_lockon", self ); + if ( i < 5 ) + wait ( .6 ); + else if ( i < 6 ) + wait ( 0.5 ); + else + wait ( .3 ); + i++; + } +} \ No newline at end of file diff --git a/mp/killstreaks/_raps.csc b/mp/killstreaks/_raps.csc new file mode 100644 index 0000000..934c7eb --- /dev/null +++ b/mp/killstreaks/_raps.csc @@ -0,0 +1,121 @@ + + + +#using scripts\codescripts\struct; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\vehicles\_raps; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; + + + +#precache( "client_fx", "killstreaks/fx_heli_raps_exp_trail" ); + +#namespace raps_mp; + + + + +function autoexec __init__sytem__() { system::register("raps_mp",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "vehicle", "monitor_raps_drop_landing", 1, 1, "int", &monitor__drop_landing_changed, !true, !true ); + clientfield::register( "vehicle", "raps_heli_low_health", 1, 1, "int", &heli_low_health_fx, !true, !true ); + clientfield::register( "vehicle", "raps_heli_extra_low_health", 1, 1, "int", &heli_extra_low_health_fx, !true, !true ); +} + + +function heli_low_health_fx( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( newVal == 0 ) + return; + + self endon( "entityshutdown" ); + + vehicle::wait_for_DObj( localClientNum ); + + PlayFxOnTag( localClientNum, "killstreaks/fx_heli_raps_exp_trail", self, "tag_fx_engine_left_front" ); +} + +function heli_extra_low_health_fx( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( newVal == 0 ) + return; + + self endon( "entityshutdown" ); + + vehicle::wait_for_DObj( localClientNum ); + + PlayFxOnTag( localClientNum, "killstreaks/fx_heli_raps_exp_trail", self, "tag_fx_engine_right_back" ); +} + +function monitor__drop_landing_changed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( !newVal ) + return; + + self thread monitor_drop_landing( localClientNum ); +} + +function monitor_drop_landing( localClientNum ) +{ + self endon( "entityshutdown" ); + + self notify( "monitor_drop_landing_entity_singleton" ); + self endon( "monitor_drop_landing_entity_singleton" ); + + a_trace = BulletTrace( self.origin + ( 0, 0, -200 ), self.origin + ( 0, 0, -5000 ), false, self, true ); + v_ground = a_trace[ "position" ]; + + wait 0.5; // gain some speed + whoosh_distance = 0; + + if( isdefined( v_ground ) ) + { + // there is a "whoosh" sound just before the actual impact, so we need to detect the distance + not_close_enough_to_ground = true; + + while( not_close_enough_to_ground ) + { + velocity = self GetVelocity(); + whoosh_distance = max( whoosh_distance, ( Abs( velocity[2] ) * ( 0.15 ) ) + ( 386.088 / 2.0 ) * ( 0.15 ) * ( 0.15 ) ); + whoosh_distance_squared = whoosh_distance * whoosh_distance; + + distance_squared = DistanceSquared( self.origin, v_ground ); + + not_close_enough_to_ground = ( distance_squared > whoosh_distance_squared ); + + if ( not_close_enough_to_ground ) + { + wait ( ( distance_squared > whoosh_distance_squared * 4 ) ? 0.1 : 0.05 ); + } + } + + self playsound( localClientNum, "veh_raps_first_land" ); + } + + // wait close enough to play fx, and rumble ( or z velocity hits zero ) + while( ( DistanceSquared( self.origin, v_ground ) > 24 * 24 ) || ( velocity[2] <= 0.0 ) ) + { + velocity = self GetVelocity(); + {wait(.016);}; + } + + bundle = struct::get_script_bundle( "killstreak", "killstreak_" + "raps" ); + + if ( isdefined( bundle ) && isdefined( bundle.ksDropDeployLandSurfaceFxTable ) && isdefined( a_trace[ "surfacetype" ] ) ) + { + fx_to_play = GetFXFromSurfaceTable( bundle.ksDropDeployLandSurfaceFxTable, a_trace[ "surfacetype" ] ); + if ( isdefined( fx_to_play ) ) + { + PlayFX( localClientNum, fx_to_play, self.origin ); + } + } + + if ( isdefined( bundle ) && isdefined( bundle.ksDropDeployLandFx ) ) + PlayFX( localClientNum, bundle.ksDropDeployLandFx, self.origin ); + + PlayRumbleOnPosition( localClientNum, "raps_land", self.origin ); +} diff --git a/mp/killstreaks/_raps.gsc b/mp/killstreaks/_raps.gsc new file mode 100644 index 0000000..9f9a57f --- /dev/null +++ b/mp/killstreaks/_raps.gsc @@ -0,0 +1,1556 @@ +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\array_shared; +#using scripts\shared\util_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\vehicle_ai_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\vehicles\_raps; +#using scripts\shared\weapons\_smokegrenade; + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\teams\_teams; +#using scripts\mp\killstreaks\_helicopter; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_airsupport; + + + + + + + +#namespace raps_mp; + + + + +#precache( "string", "KILLSTREAK_DESTROYED_RAPS_DEPLOY_SHIP"); +#precache( "string", "KILLSTREAK_EARNED_RAPS" ); +#precache( "string", "KILLSTREAK_RAPS_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_RAPS_NOT_PLACEABLE" ); +#precache( "string", "KILLSTREAK_RAPS_INBOUND" ); +#precache( "string", "KILLSTREAK_RAPS_HACKED" ); +#precache( "eventstring", "mpl_killstreak_raps" ); +#precache( "fx", "killstreaks/fx_heli_raps_exp_sm" ); +#precache( "fx", "killstreaks/fx_heli_raps_exp_trail" ); +#precache( "fx", "killstreaks/fx_heli_raps_exp_lg" ); + +function init() +{ + level.raps_settings = level.scriptbundles[ "vehiclecustomsettings" ][ "rapssettings_mp" ]; + assert( isdefined( level.raps_settings ) ); + + level.raps = []; + level.raps_helicopters = []; + + level.raps_force_get_enemies = &ForceGetEnemies; + + killstreaks::register( "raps", "raps", "killstreak_raps", "raps_used", &ActivateRapsKillstreak, true ); + killstreaks::register_strings( "raps", &"KILLSTREAK_EARNED_RAPS", &"KILLSTREAK_RAPS_NOT_AVAILABLE", &"KILLSTREAK_RAPS_INBOUND", undefined, &"KILLSTREAK_RAPS_HACKED" ); + killstreaks::register_dialog( "raps", "mpl_killstreak_raps", "rapsHelicopterDialogBundle", "rapsHelicopterPilotDialogBundle", "friendlyRaps", "enemyRaps", "enemyRapsMultiple", "friendlyRapsHacked", "enemyRapsHacked", "requestRaps", "threatRaps" ); + killstreaks::allow_assists( "raps", true ); + killstreaks::register_dev_debug_dvar( "raps" ); + + killstreak_bundles::register_killstreak_bundle( "raps_drone" ); + + InitHelicopterPositions(); + + callback::on_connect( &OnPlayerConnect ); + + clientfield::register( "vehicle", "monitor_raps_drop_landing", 1, 1, "int" ); + clientfield::register( "vehicle", "raps_heli_low_health", 1, 1, "int" ); + clientfield::register( "vehicle", "raps_heli_extra_low_health", 1, 1, "int" ); + + // level thread RapsHelicopterDynamicAvoidance(); // aku: disabling avoidance for now because it's avoidance technique does not "look right", going for different z heights for now + + level.raps_helicopter_drop_tag_names = []; + level.raps_helicopter_drop_tag_names[0] = "tag_raps_drop_left"; + level.raps_helicopter_drop_tag_names[1] = "tag_raps_drop_right"; +} + +function OnPlayerConnect() +{ + self.entNum = self getEntityNumber(); + level.raps[ self.entNum ] = spawnstruct(); + level.raps[ self.entNum ].killstreak_id = (-1); + level.raps[ self.entNum ].raps = []; + level.raps[ self.entNum ].helicopter = undefined; +} + +/* RapsHelicopterDynamicAvoidance + * + * This method supports a simple avoidance system for the RAPS Helicopter (RAPS deploy ship). + * + * The RAPS helicopters are required to fly at the same hight. To prevent overlapping, this system checks + * the helicopters relative to each other and changes driving behavior based on different distances. + * The system will choose another deploy point based on distance, last pick time, and other factors. + * + * Note: tuning vars in _killstreaks.gsh using RAPS_HELAV where HELAV is short for Helicopter Avoidance + * + * The RAPS helicopter avoidance has been designed to function with at most two RAPS helicopters for now. + * + * Key concepts in use: + * a. Forward Reference Point -- distances are measured relative to this forward reference point ( RAPS_HELAV_FORWARD_OFFSET ) + * b. Other Forward Ref Point -- this is the reference point used when testing distances from another helicopter ( RAPS_HELAV_OTHER_FORWARD_OFFSET ) + * c. Stop Distance -- the helicopter stops when another helicopter is within this distance + * d. Slow Down Distance -- the helicopter slows down when another helicopter is within this distance + * e. Pick New Goal Distance -- the helicopter selects a new drop point when the other helicopter is within this distance + * f. Backing Off -- if a helicopter stops and the other helicopter is in front of it, it will pick a random point opposite + * the direction behind it and can pick a new goal (drop point) to go to after it backs off + * g. Drive Mode -- there are four different drive modes: expedient, cautious, more cautious, and stop. + * Each has different speed, acceleration, and deceleration. + * + */ +function RapsHelicopterDynamicAvoidance() +{ + level endon( "game_ended" ); + + index_to_update = 0; + + while( true ) + { + RapsHelicopterDynamicAvoidanceUpdate( index_to_update ); + + index_to_update++; + if ( index_to_update >= level.raps_helicopters.size ) + index_to_update = 0; + + wait( ( 0.05 ) ); + } +} + +function RapsHelicopterDynamicAvoidanceUpdate( index_to_update ) +{ + helicopterRefOrigin = ( 0, 0, 0 ); + otherHelicopterRefOrigin = ( 0, 0, 0 ); + + ArrayRemoveValue( level.raps_helicopters, undefined ); + + if ( index_to_update >= level.raps_helicopters.size ) + index_to_update = 0; + + if( level.raps_helicopters.size >= 2 ) + { + helicopter = level.raps_helicopters[index_to_update]; + /# helicopter.__action_just_made = false; #/ + + for( i = 0; i < level.raps_helicopters.size; i++ ) + { + if ( i == index_to_update ) + continue; + + if ( helicopter.droppingRaps ) + continue; + + if ( !isdefined( helicopter.lastNewGoalTime ) ) + helicopter.lastNewGoalTime = GetTime(); + + helicopterForward = AnglesToForward( helicopter GetAngles() ); + helicopterRefOrigin = helicopter.origin + ( helicopterForward * ( 500 ) ); + otherHelicopterForward = AnglesToForward( level.raps_helicopters[i] GetAngles() ); + otherHelicopterRefOrigin = level.raps_helicopters[i].origin + ( otherHelicopterForward * ( 100 ) ); + deltaToOther = otherHelicopterRefOrigin - helicopterRefOrigin; + otherInFront = ( VectorDot( helicopterForward, VectorNormalize( deltaToOther ) ) > ( 0.707 ) ); + distanceSqr = Distance2DSquared( helicopterRefOrigin, otherHelicopterRefOrigin); + + if ( (distanceSqr < ( ( 200 + ( 1200 ) ) * ( 200 + ( 1200 ) ) ) || helicopter GetSpeed() == 0 ) + && (GetTime() - helicopter.lastNewGoalTime) > ( 5000 ) ) + { + // + // pick a new goal based on distance, speed, and the last time picked + // + /# helicopter.__last_dynamic_avoidance_action = 20; /* new goal */ #/ + /# helicopter.__action_just_made = true; #/ + + helicopter UpdateHelicopterSpeed(); + if ( helicopter.isLeaving ) + { + self.leaveLocation = GetRandomHelicopterLeaveOrigin( /*self.assigned_fly_height*/ 0, self.origin ); + helicopter setVehGoalPos( self.leaveLocation, 0 ); + } + else + { + self.targetDropLocation = GetRandomHelicopterPosition( self.lastDropLocation ); + helicopter setVehGoalPos( self.targetDropLocation, 1 ); + } + helicopter.lastNewGoalTime = GetTime(); + } + else if ( distanceSqr < ( ( 1200 ) * ( 1200 ) ) + && otherInFront + && (GetTime() - helicopter.lastStopTime) > ( 500 ) + ) + { + // + // do a full stop if the other helicopter is in front and is too close + // + /# helicopter.__last_dynamic_avoidance_action = 10; /* stop */ #/ + /# helicopter.__action_just_made = true; #/ + + helicopter StopHelicopter(); + } + else if ( helicopter GetSpeed() == 0 && otherInFront && distanceSqr < ( ( 1200 ) * ( 1200 ) ) ) + { + // + // after a full stop, have the helicopter back off if the other helicopter is in front and too close + // and a new drop location may be picked based on the tuning vars + // + /# helicopter.__last_dynamic_avoidance_action = 50; /* back off */ #/ + /# helicopter.__action_just_made = true; #/ + + delta = otherHelicopterRefOrigin - helicopterRefOrigin; + newGoalPosition = helicopter.origin - + ( deltaToOther[0] * RandomFloatRange( ( 0.7 ), ( 2.5 ) ), + deltaToOther[1] * RandomFloatRange( ( 0.7 ), ( 2.5 )), 0 ); + helicopter UpdateHelicopterSpeed(); + helicopter setVehGoalPos( newGoalPosition, 0 ); + + // pick a new drop location for use after the "back off" goal is reached + if ( ( true ) || (GetTime() - helicopter.lastNewGoalTime) > ( 5000 ) ) + { + /# helicopter.__last_dynamic_avoidance_action = 51; /* back off + new goal */ #/ + helicopter.targetDropLocation = GetClosestRandomHelicopterPosition( newGoalPosition, 8 ); + helicopter.lastNewGoalTime = GetTime(); + } + } + else if ( distanceSqr < ( ( 1000 + ( 200 + ( 1200 ) ) ) * ( 1000 + ( 200 + ( 1200 ) ) ) ) && helicopter.driveModeSpeedScale == 1.0 ) + { + // + // slow down the helicopter if within the configured distances and at full speed + // there is a cautious and a more cautious speed based on if the other helicopter is in front + // + /# helicopter.__last_dynamic_avoidance_action = (( otherInFront ) ? 31 : 30); /* cautious */ #/ + /# helicopter.__action_just_made = true; #/ + + helicopter UpdateHelicopterSpeed( ( (otherInFront) ? 2 : 1) ); + } + else if ( distanceSqr >= ( ( 1000 + ( 200 + ( 1200 ) ) ) * ( 1000 + ( 200 + ( 1200 ) ) ) ) && helicopter.driveModeSpeedScale < 1.0 ) + { + // + // speed the helicopter back up if we are beyond the slow down distance and set to drive at full speed + // + /# helicopter.__last_dynamic_avoidance_action = 40; /* expedient */ #/ + /# helicopter.__action_just_made = true; #/ + + helicopter UpdateHelicopterSpeed( 0 ); + } + else if ( helicopter GetSpeed() == 0 && (GetTime() - helicopter.lastStopTime) > ( 500 ) ) + { + // + // resume moving -- start mmoving again if we have stopped for too long + // + // devblock to report last action made intentionally left out. + + helicopter UpdateHelicopterSpeed(); + } + } + + /# + //================================================================================================ + // + // this code section is meant for visual debuggingof the RAPS Helicopter dynamic avoidance system + // + //------------------------------------------------------------------------------------------------ + // + if ( GetDvarInt( "scr_raps_helav_debug" ) ) + { + if ( isdefined( helicopter ) ) + { + server_frames_to_persist = INT( (( 0.05 ) * 2) / .05 ); + + Sphere( helicopterRefOrigin, 10, ( 0, 0, 1 ), 1, false, 10, server_frames_to_persist ); + Sphere( otherHelicopterRefOrigin, 10, ( 1, 0, 0 ), 1, false, 10, server_frames_to_persist ); + + circle( helicopterRefOrigin, ( 1000 + ( 200 + ( 1200 ) ) ), ( 1, 1, 0 ), true, true, server_frames_to_persist ); + circle( helicopterRefOrigin, ( 200 + ( 1200 ) ), ( 0, 0, 0 ), true, true, server_frames_to_persist ); + circle( helicopterRefOrigin, ( 1200 ), ( 1, 0, 0 ), true, true, server_frames_to_persist ); + + Print3d( helicopter.origin, "Speed: " + INT( helicopter GetSpeedMPH() ), (1,1,1), 1, 2.5, server_frames_to_persist ); + + action_debug_color = ( 0.8, 0.8, 0.8 ); + debug_action_string = ""; + if ( helicopter.__action_just_made ) + action_debug_color = ( 0, 1, 0 ); + + switch ( helicopter.__last_dynamic_avoidance_action ) + { + case 0: break; // do nothing + case 10: debug_action_string = "stop"; break; + case 20: debug_action_string = "new goal"; break; + case 30: debug_action_string = "cautious"; break; + case 31: debug_action_string = "more cautious"; break; + case 40: debug_action_string = "expedient"; break; + case 50: debug_action_string = "back off"; break; + case 51: debug_action_string = "back off + new goal"; break; + default: debug_action_string = "unknown action"; break; + } + + // display last action taken + Print3d( helicopter.origin + ( 0, 0, -50 ), debug_action_string, action_debug_color, 1, 2.5, server_frames_to_persist ); + + } + } + // + //------------------------------------------------------------------------------------------------ + // + // end of visual debug section + // + //================================================================================================ + #/ + } +} + +function ActivateRapsKillstreak( hardpointType ) +{ + player = self; + + if ( !player killstreakrules::isKillstreakAllowed( "raps", player.team ) ) + { + return false; + } + + if( game["raps_helicopter_positions"].size <= 0 ) + { + /# IPrintLnBold( "RAPS helicopter position error, check NavMesh." ); #/ + self iPrintLnBold( &"KILLSTREAK_RAPS_NOT_AVAILABLE" ); + return false; + } + + killstreakId = player killstreakrules::killstreakStart( "raps", player.team ); + if( killstreakId == (-1) ) + { + player iPrintLnBold( &"KILLSTREAK_RAPS_NOT_AVAILABLE" ); + return false; + } + + player thread teams::WaitUntilTeamChange( player, &OnTeamChanged, player.entNum, "raps_complete" ); + + level thread WatchRapsKillstreakEnd( killstreakId, player.entNum, player.team ); + + helicopter = player SpawnRapsHelicopter( killstreakId ); + helicopter.killstreakId = killstreakId; + + player killstreaks::play_killstreak_start_dialog( "raps", player.team, killstreakId ); + player AddWeaponStat( GetWeapon( "raps" ), "used", 1 ); + + helicopter killstreaks::play_pilot_dialog_on_owner( "arrive", "raps", killstreakId ); + + level.raps[ player.entNum ].helicopter = helicopter; + if ( !isdefined( level.raps_helicopters ) ) level.raps_helicopters = []; else if ( !IsArray( level.raps_helicopters ) ) level.raps_helicopters = array( level.raps_helicopters ); level.raps_helicopters[level.raps_helicopters.size]=level.raps[ player.entNum ].helicopter;; + level thread UpdateKillstreakOnHelicopterDeath( level.raps[ player.entNum ].helicopter, player.entNum ); + +/# + if ( GetDvarInt( "scr_raps_debug_auto_reactivate" ) ) + { + level thread AutoReactivateRapsKillstreak( player.entNum, player, hardpointType ); + } +#/ + + return true; +} + +/# +function AutoReactivateRapsKillstreak( ownerEntNum, player, hardpointType ) +{ + while( true ) + { + level waittill( "raps_updated_" + ownerEntNum ); + + if( isdefined( level.raps[ ownerEntNum ].helicopter ) ) + continue; + + wait ( RandomFloatRange( 2.0, 5.0 ) ); + player thread ActivateRapsKillstreak( hardpointType ); + + return; + } +} +#/ + +function WatchRapsKillstreakEnd( killstreakId, ownerEntNum, team ) +{ + while( true ) + { + level waittill( "raps_updated_" + ownerEntNum ); + + if( isdefined( level.raps[ ownerEntNum ].helicopter ) ) + { + continue; + } + + killstreakrules::killstreakStop( "raps", team, killstreakId ); + return; + } +} + +function UpdateKillstreakOnHelicopterDeath( helicopter, ownerEntEnum ) +{ + helicopter waittill( "death" ); + + level notify( "raps_updated_" + ownerEntEnum ); +} + +function OnTeamChanged( entNum, event ) +{ + abandoned = true; + DestroyAllRaps( entNum, abandoned ); +} + +function OnEMP( attacker, ownerEntNum ) +{ + DestroyAllRaps( ownerEntNum ); +} + +function NoVehicleFaceThread( mapCenter, radius ) +{ + level endon ("game_ended"); + wait 3; // wait arbitrary time so moving platform can be initialized + MarkNoVehicleNavMeshFaces( mapCenter, radius, 21 ); +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +//HELICOPTER +///////////////////////////////////////////////////////////////////////////////////////////////// +function InitHelicopterPositions() +{ + + // - - - - - + // + // -- try to find a reasonable center point on the nav mesh as a starting point to start querying for more points + // + startSearchPoint = airsupport::GetMapCenter(); + mapCenter = GetClosestPointOnNavMesh( startSearchPoint, ( 1024 ) ); + + if ( !isdefined( mapCenter ) ) + { + startSearchPoint = ( startSearchPoint[0], startSearchPoint[1], 0 ); + } + + remaining_attempts = 10; + while ( !isdefined( mapCenter ) && remaining_attempts > 0 ) + { + startSearchPoint += ( 100, 100, 0 ); + mapCenter = GetClosestPointOnNavMesh( startSearchPoint, ( 1024 ) ); + remaining_attempts -= 1; + } + + if( !isdefined( mapCenter ) ) + { + mapCenter = airsupport::GetMapCenter(); + } + + // - - - - - + // + // -- now query the nav mesh for some random, reasonably-spaced-out points + // + radius = airsupport::GetMaxMapWidth(); + if ( radius < 1 ) + radius = 1; + + // don't re-generate the points if they are already there + if ( IsDefined( game["raps_helicopter_positions"] ) ) + return; + + lots_of_height = 1024; + randomNavMeshPoints = util::PositionQuery_PointArray( mapCenter, ( 0 ), radius * 3, lots_of_height, ( 132 ) ); + // Hack fix for when the mapCenter cannot be found (mp_veiled / mp_sentosa) + if ( randomNavMeshPoints.size == 0 ) + { + mapCenter = ( 0, 0, 39 ); + randomNavMeshPoints = util::PositionQuery_PointArray( mapCenter, ( 0 ), radius, 70, ( 132 ) ); + } + + /# position_query_drop_location_count = randomNavMeshPoints.size; #/ + + // add level specific raps drop locations + if ( isdefined( level.add_raps_drop_locations ) ) + { + [[ level.add_raps_drop_locations ]]( randomNavMeshPoints ); + } + +/# + // debug draw level specific points + if ( GetDvarInt( "scr_raps_nav_point_trace_debug" ) ) + { + boxHalfWidth = ( 220 ) * 0.25; // draw a smaller box + + for( i = position_query_drop_location_count; i < randomNavMeshPoints.size; i++ ) + { + // shows a short orange box + Box( randomNavMeshPoints[ i ], (-boxHalfWidth, -boxHalfWidth, 0), (boxHalfWidth, boxHalfWidth, 8.88 ), 0, ( 1.0, 0.53, 0.0 ), 0.9, false, 9999999 ); + } + } +#/ + + // get any level specific omit points + omit_locations = []; + if ( isdefined( level.add_raps_omit_locations ) ) + { + [[ level.add_raps_omit_locations ]]( omit_locations ); // don't add too many of these. + } + +/# + // debug draw level specific omit points + if ( GetDvarInt( "scr_raps_nav_point_trace_debug" ) ) + { + debug_radius = ( 220 ) * 0.5; // draw a smaller box + + foreach( omit_location in omit_locations ) + { + // shows a few dark grey circles + Circle( omit_location, debug_radius, ( 0.05, 0.05, 0.05 ), false, true, 9999999 ); + Circle( omit_location + ( 0, 0, 4 ), debug_radius, ( 0.05, 0.05, 0.05 ), false, true, 9999999 ); + Circle( omit_location + ( 0, 0, 8 ), debug_radius, ( 0.05, 0.05, 0.05 ), false, true, 9999999 ); + } + } +#/ + + // - - - - - + // + // -- collect the random points that can be used to drop raps (test points using box traces, etc.) + // + game["raps_helicopter_positions"] = []; + minFlyHeight = INT( airsupport::getMinimumFlyHeight() + ( 1000 ) ); + test_point_radius = 12; + fit_radius = ( 220 ) * 0.5; + fit_radius_corner = fit_radius * 0.7071; + omit_radius = ( 220 ) * 0.5; + + foreach( point in randomNavMeshPoints ) + { + // skip points in water + start_water_trace = point + ( 0, 0, 6 ); + stop_water_trace = point + ( 0, 0, 8 ); + trace = physicstrace( start_water_trace, stop_water_trace, ( -2, -2, -2 ), ( 2, 2, 2 ) , undefined, (1 << 2) ); + if( trace["fraction"] < 1.0 ) + { + /# + if ( GetDvarInt( "scr_raps_nav_point_trace_debug" ) ) + { + DebugBoxWidth = ( 220 ) * 0.5; + DebugBoxHeight = 10; + + // draw a blue box where water was found + Box( start_water_trace, ( -DebugBoxWidth, -DebugBoxWidth, 0 ), ( DebugBoxWidth, DebugBoxWidth, DebugBoxHeight ), 0, ( 0.0, 0, 1.0 ), 0.9, false, 9999999 ); + Box( start_water_trace, ( -2, -2, -2 ), ( 2, 2, 2 ), 0, ( 0.0, 0, 1.0 ), 0.9, false, 9999999 ); + } + #/ + continue; + } + + // skip avoid points + should_omit = false; + + foreach( omit_location in omit_locations ) + { + if ( DistanceSquared( omit_location, point ) < ( omit_radius * omit_radius ) ) + { + should_omit = true; + + /# + if ( GetDvarInt( "scr_raps_nav_point_trace_debug" ) ) + { + DebugBoxWidth = ( 220 ) * 0.5; + DebugBoxHeight = 10; + + // draw a dark grey box for omitted boxes + Box( point, ( -DebugBoxWidth, -DebugBoxWidth, 0 ), ( DebugBoxWidth, DebugBoxWidth, DebugBoxHeight ), 0, ( 0.05, 0.05, 0.05 ), 1.0, false, 9999999 ); + } + #/ + + break; + } + } + + if (should_omit) + continue; + + // for each random nav mesh point, test a few points near it to see if it works as a drop point + randomTestPoints = util::PositionQuery_PointArray( point, 0, ( 128 ), lots_of_height, test_point_radius ); + max_attempts = 12; + point_added = false; + for ( i = 0; !point_added && i < max_attempts && i < randomTestPoints.size; i++ ) + { + test_point = randomTestPoints[ i ]; + + //can_fit_on_nav_mesh = IsPointOnNavMesh( test_point, RAPS_HELICOPTER_NAV_SPACIOUS_POINT_BOUNDARY ); // this line should "work", but it doesn't, so we test some points individually + can_fit_on_nav_mesh = ( IsPointOnNavMesh( test_point + ( 0, fit_radius, 0 ), 0 ) + && IsPointOnNavMesh( test_point + ( 0, -fit_radius, 0 ), 0 ) + && IsPointOnNavMesh( test_point + ( fit_radius, 0, 0 ), 0 ) + && IsPointOnNavMesh( test_point + ( -fit_radius, 0, 0 ), 0 ) + && IsPointOnNavMesh( test_point + ( fit_radius_corner, fit_radius_corner, 0 ), 0 ) // also include corners as there are cases where the above four are not sufficient for raps drones + && IsPointOnNavMesh( test_point + ( fit_radius_corner, -fit_radius_corner, 0 ), 0 ) + && IsPointOnNavMesh( test_point + ( -fit_radius_corner, fit_radius_corner, 0 ), 0 ) + && IsPointOnNavMesh( test_point + ( -fit_radius_corner, -fit_radius_corner, 0 ), 0 ) + ); + + if ( can_fit_on_nav_mesh ) + { + point_added = TryAddPointForHelicopterPosition( test_point, minFlyHeight ); + } + } + } + + if( game["raps_helicopter_positions"].size == 0 ) + { + /# IPrintLnBold( "Error Finding Valid RAPS Helicopter Positions, Using Default Random NavMesh Points" ); #/ + game["raps_helicopter_positions"] = randomNavMeshPoints; + } + + // find helicopter position closest to mapCenter to use as flood fill start point + flood_fill_start_point = undefined; + flood_fill_start_point_distance_squared = 9999999; + foreach( point in game["raps_helicopter_positions"] ) + { + if ( !isdefined( point ) ) + continue; + + distance_squared = DistanceSquared( point, mapCenter ); + if ( distance_squared < flood_fill_start_point_distance_squared ) + { + flood_fill_start_point_distance_squared = distance_squared; + flood_fill_start_point = point; + } + } + + if ( !isdefined( flood_fill_start_point ) ) + flood_fill_start_point = mapCenter; + + level thread NoVehicleFaceThread( flood_fill_start_point, radius * 2 ); + + + force_debug_draw = false; // NOTE: never check this in as true !!!!! + + /# + if ( killstreaks::should_draw_debug( "raps" ) || force_debug_draw ) // something is wrong with "scr_raps_debug", no time to troubleshoot this now, just force draw when needed + { + time = 9999999; + Sphere( mapCenter, 20, ( 1, 1, 0 ), 1, 0, 10, time ); + Circle( mapCenter, airsupport::GetMaxMapWidth(), ( 0, 1, 0 ), true, true, time ); + Box( mapCenter, (-4, -4, 0 ), ( 4, 4, 5000 ), 0, ( 1, 1, 0 ), 0.6, false, time ); + + // flood fill ceenter + Sphere( flood_fill_start_point, 20, ( 0, 1, 1 ), 1, 0, 10, time ); + Box( flood_fill_start_point, (-4, -4, 0 ), ( 4, 4, 4200 ), 0, ( 0, 1, 1 ), 0.6, false, time ); + + foreach( point in randomNavMeshPoints ) + { + Sphere( ( point + ( 0, 0, 950 ) ), 10, ( 0, 0, 1 ), 1, 0, 10, time ); + Circle( point, ( 128 ), ( 1, 0, 0 ), true, true, time ); + } + + foreach( point in game["raps_helicopter_positions"] ) + { + Sphere( ( point + ( 0, 0, 1000 ) ), 10, ( 0, 1, 0 ), 1, 0, 10, time ); + Circle( point + ( 0, 0, 2 ), ( 128 ), ( 0, 1, 0 ), true, true, time ); + airsupport::debug_cylinder( point, 8, 1000, ( 0, 0.8, 0 ), 16, time ); + Box( point, (-4, -4, 0 ), ( 4, 4, 1000 ), 0, ( 0, 0.7, 0 ), 0.6, false, time ); + + halfBoxWidth = ( 220 ) * 0.5; + Box( point, (-halfBoxWidth, -halfBoxWidth, 2), (halfBoxWidth, halfBoxWidth, 300), 0, ( 0, 0, 0.6 ), 0.6, false, time ); + } + } + #/ +} + +function TryAddPointForHelicopterPosition( spaciousPoint, minFlyHeight ) +{ + traceHeight = minFlyHeight + ( 500 ); + traceBoxHalfWidth = ( 220 ) * 0.5; + + if ( IsTraceSafeForRapsDroneDropFromHelicopter( spaciousPoint, traceHeight, traceBoxHalfWidth ) ) + { + if ( !isdefined( game["raps_helicopter_positions"] ) ) game["raps_helicopter_positions"] = []; else if ( !IsArray( game["raps_helicopter_positions"] ) ) game["raps_helicopter_positions"] = array( game["raps_helicopter_positions"] ); game["raps_helicopter_positions"][game["raps_helicopter_positions"].size]=spaciousPoint;; + return true; + } + + return false; +} + +function IsTraceSafeForRapsDroneDropFromHelicopter( spaciousPoint, traceHeight, traceBoxHalfWidth ) +{ + start = ( spaciousPoint[0], spaciouspoint[1], traceHeight ); + end = ( spaciousPoint[0], spaciouspoint[1], spaciouspoint[2] + ( 36 ) ); + + trace = PhysicsTrace( start, end, ( -traceBoxHalfWidth, -traceBoxHalfWidth, 0 ), ( traceBoxHalfWidth, traceBoxHalfWidth, traceBoxHalfWidth * 2.0 ), undefined, (1 << 0) ); + + +/# + if ( GetDvarInt( "scr_raps_nav_point_trace_debug" ) ) + { + if (trace["fraction"] < 1.0 ) + { + // shows the first trace hit, but from the end + Box( end, (-traceBoxHalfWidth, -traceBoxHalfWidth, 0), (traceBoxHalfWidth, traceBoxHalfWidth, (start[2] - end[2]) * (1.0 - trace["fraction"])), 0, ( 1.0, 0, 0.0 ), 0.6, false, 9999999 ); + } + else + { + // shows a small green box + Box( end, (-traceBoxHalfWidth, -traceBoxHalfWidth, 0), (traceBoxHalfWidth, traceBoxHalfWidth, 8.88), 0, ( 0.0, 1.0, 0.0 ), 0.6, false, 9999999 ); + } + } +#/ + + return ( trace["fraction"] == 1.0 && trace["surfacetype"] == "none" ); +} + +function GetRandomHelicopterStartOrigin( fly_height, firstDropLocation ) +{ + best_node = helicopter::getValidRandomStartNode( firstDropLocation ); + return best_node.origin + ( 0, 0, fly_height ); +} + +function GetRandomHelicopterLeaveOrigin( fly_height, startLocationToLeaveFrom ) +{ + best_node = helicopter::getValidRandomLeaveNode( startLocationToLeaveFrom ); + return best_node.origin + ( 0, 0, fly_height ); +} + +function GetInitialHelicopterFlyHeight() +{ + // Note A: call this only once for each helicopter when spawned + // Note B: this technique only works for two RAPS helicopters in play at any give time + // Note C: this works regardless of team based or not + + ArrayRemoveValue( level.raps_helicopters, undefined ); // clean up array first + + minimum_fly_height = airsupport::getMinimumFlyHeight(); + + if ( level.raps_helicopters.size > 0 ) + { + already_assigned_height = level.raps_helicopters[0].assigned_fly_height; + + if ( already_assigned_height == ( minimum_fly_height + INT( airsupport::getMinimumFlyHeight() + ( 1000 ) ) ) ) + return minimum_fly_height + INT( airsupport::getMinimumFlyHeight() + ( 1000 ) ) + ( 400 ); + } + + return minimum_fly_height + INT( airsupport::getMinimumFlyHeight() + ( 1000 ) ); +} + +function ConfigureChopperTeamPost( owner, isHacked ) +{ + helicopter = self; + helicopter thread WatchOwnerDisconnect( owner ); + helicopter thread CreateRapsHelicopterInfluencer(); +} + +function SpawnRapsHelicopter( killstreakId ) +{ + player = self; + + assigned_fly_height = GetInitialHelicopterFlyHeight(); + prePickedDropLocation = PickNextDropLocation( undefined, 0, player.origin, assigned_fly_height ); + spawnOrigin = GetRandomHelicopterStartOrigin( /*fly_height*/ 0, prePickedDropLocation ); // update this + + helicopter = SpawnHelicopter( player, spawnOrigin, ( 0, 0, 0 ), "heli_raps_mp", "veh_t7_mil_vtol_dropship_raps" ); + helicopter.prePickedDropLocation = prePickedDropLocation; + helicopter.assigned_fly_height = assigned_fly_height; + + helicopter killstreaks::configure_team( "raps", killstreakId, player, undefined, undefined, &ConfigureChopperTeamPost ); + helicopter killstreak_hacking::enable_hacking( "raps" ); + + helicopter.droppingRaps = false; + helicopter.isLeaving = false; + helicopter.droppedRaps = false; + helicopter.driveModeSpeedScale = 3.0; + helicopter.driveModeAccel = ( 20 ) * 5; + helicopter.driveModeDecel = ( 20 ) * 5; + helicopter.lastStopTime = 0; + helicopter.targetDropLocation = ( -9999999, -9999999, -9999999 ); + helicopter.lastDropLocation = ( -9999999, -9999999, -9999999 ); + helicopter.firstDropReferencePoint = ( player.origin[0], player.origin[1], INT( airsupport::getMinimumFlyHeight() + ( 1000 ) )); + /# helicopter.__last_dynamic_avoidance_action = 0; #/ + + helicopter clientfield::set( "enemyvehicle", 1 ); + + helicopter.health = 99999999; + helicopter.maxhealth = killstreak_bundles::get_max_health( "raps" ); + helicopter.lowhealth = killstreak_bundles::get_low_health( "raps" ); + helicopter.extra_low_health = helicopter.lowhealth * 0.5; // hand craft for ship (no need to be tunable now per design, it's locked in) + helicopter.extra_low_health_callback = &OnExtraLowHealth; + + helicopter SetCanDamage( true ); + helicopter thread killstreaks::MonitorDamage( "raps", helicopter.maxhealth, &OnDeath, helicopter.lowhealth, &OnLowHealth, 0, undefined, true ); + + helicopter.rocketDamage = helicopter.maxhealth / ( 4 ) + 1; + helicopter.remoteMissileDamage = helicopter.maxhealth / ( 1 ) + 1; + helicopter.hackerToolDamage = helicopter.maxhealth / ( 2 ) + 1; + helicopter.DetonateViaEMP = &raps::detonate_damage_monitored; + + Target_Set( helicopter, ( 0, 0, 100 ) ); + helicopter SetDrawInfrared( true ); + helicopter thread WaitForHelicopterShutdown(); + helicopter thread HelicopterThink(); + helicopter thread WatchGameEnded(); +/# helicopter thread HelicopterThinkDebugVisitAll(); #/ + + return helicopter; +} + +function WaitForHelicopterShutdown() +{ + helicopter = self; + helicopter waittill( "raps_helicopter_shutdown", killed ); + + level notify( "raps_updated_" + helicopter.ownerEntNum ); + + if ( Target_IsTarget( helicopter ) ) + { + Target_Remove( helicopter ); + } + + if( killed ) + { + wait( RandomFloatRange( 0.1, 0.2 ) ); + + helicopter FirstHeliExplo(); + helicopter HeliDeathTrails(); + + helicopter thread Spin(); + GoalX = RandomFloatRange( 650, 700 ); + GoalY = RandomFloatRange( 650, 700 ); + + if ( RandomIntRange ( 0, 2 ) > 0 ) + GoalX = -GoalX; + + if ( RandomIntRange ( 0, 2 ) > 0 ) + GoalY = -GoalY; + + helicopter setVehGoalPos( helicopter.origin + ( GoalX, GoalY, -RandomFloatRange( 285, 300 ) ), false ); + wait( RandomFloatRange( 3.0, 4.0 ) ); + + helicopter FinalHeliDeathExplode(); + + // fx will not work if we delete too soon + wait 0.1; // ghost only after fx has covered up the drop ship + helicopter ghost(); + self notify( "stop_death_spin" ); + wait 0.5; + } + else + { + helicopter HelicopterLeave(); + } + + helicopter delete(); +} + +function WatchOwnerDisconnect( owner ) +{ + self notify( "WatchOwnerDisconnect_singleton" ); + self endon ( "WatchOwnerDisconnect_singleton" ); + + helicopter = self; + helicopter endon( "raps_helicopter_shutdown" ); + owner util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); + helicopter notify( "raps_helicopter_shutdown", false ); +} + +function WatchGameEnded( ) +{ + helicopter = self; + helicopter endon( "raps_helicopter_shutdown" ); + helicopter endon( "death" ); + level waittill("game_ended"); + helicopter notify( "raps_helicopter_shutdown", false ); +} + +function OnDeath( attacker, weapon ) +{ + helicopter = self; + + if ( isdefined( attacker ) && ( !isdefined( helicopter.owner ) || helicopter.owner util::IsEnemyPlayer( attacker ) ) ) + { + challenges::destroyedAircraft( attacker, weapon, false ); + attacker challenges::addFlySwatterStat( weapon, self ); + scoreevents::processscoreevent( "destroyed_raps_deployship", attacker, helicopter.owner, weapon ); + if ( isdefined( helicopter.droppedRaps ) && helicopter.droppedRaps == false ) + { + attacker addplayerstat( "destroy_raps_before_drop", 1 ); + } + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_RAPS_DEPLOY_SHIP", attacker.entnum ); + helicopter notify( "raps_helicopter_shutdown", true ); + } + + if ( helicopter.isleaving !== true ) + { + helicopter killstreaks::play_pilot_dialog_on_owner( "destroyed", "raps" ); + helicopter killstreaks::play_destroyed_dialog_on_owner( "raps", self.killstreakId ); + } +} + +function OnLowHealth( attacker, weapon ) +{ + helicopter = self; + + helicopter killstreaks::play_pilot_dialog_on_owner( "damaged", "raps", helicopter.killstreakId ); + + helicopter HeliLowHealthFx(); +} + +function OnExtraLowHealth( attacker, weapon ) +{ + helicopter = self; + helicopter HeliExtraLowHealthFx(); +} + +function GetRandomHelicopterPosition( avoidPoint = ( -9999999, -9999999, -9999999 ), otherAvoidPoint = ( -9999999, -9999999, -9999999 ), avoidRadiusSqr = ( ( 1800 ) * ( 1800 ) ) ) +{ + flyHeight = INT( airsupport::getMinimumFlyHeight() + ( 1000 ) ); + found = false; + tries = 0; + + // try picking a location outside the avoid circle, if not possible, reduce the circle size and try again + for( i = 0; i <= ( 3 ); i++ ) // intentionally using "<=" to get N+1 attemmpts + { + // for the very last attempt, make radius negative to make any point valid as a fail safe + if ( i == ( 3 ) ) + avoidRadiusSqr = -1.0; + +/# if ( GetDvarInt( "scr_raps_hedeps_debug" ) > 0 ) + { + server_frames_to_persist = INT( 3.0 / .05 ); + circle( avoidPoint, ( 1800 ), ( 1, 0, 0 ), true, true, server_frames_to_persist ); + circle( avoidPoint, ( 1800 ) - 1, ( 1, 0, 0 ), true, true, server_frames_to_persist ); + circle( avoidPoint, ( 1800 ) - 2, ( 1, 0, 0 ), true, true, server_frames_to_persist ); + + circle( otherAvoidPoint, ( 1800 ), ( 1, 0, 0 ), true, true, server_frames_to_persist ); + circle( otherAvoidPoint, ( 1800 ) - 1, ( 1, 0, 0 ), true, true, server_frames_to_persist ); + circle( otherAvoidPoint, ( 1800 ) - 2, ( 1, 0, 0 ), true, true, server_frames_to_persist ); + } +#/ + + while( !found && tries < game["raps_helicopter_positions"].size ) + { + index = RandomIntRange( 0, game["raps_helicopter_positions"].size ); + randomPoint = ( game["raps_helicopter_positions"][ index ][0], game["raps_helicopter_positions"][ index ][1], flyHeight ); + found = ( ( Distance2DSquared( randomPoint, avoidPoint ) > avoidRadiusSqr ) && ( Distance2DSquared( randomPoint, otherAvoidPoint ) > avoidRadiusSqr ) ); + tries++; + } + + if (!found) + { + avoidRadiusSqr *= 0.25; + tries = 0; + } + } + + // note: the -1 avoid radius should force the selection of a point + Assert( found, "Failed to find a RAPS deploy point!" ); + + return randomPoint; +} + +function GetClosestRandomHelicopterPosition( refPoint, pickCount, avoidPoint = ( -9999999, -9999999, -9999999 ), otherAvoidPoint = ( -9999999, -9999999, -9999999 ) ) +{ + bestPosition = GetRandomHelicopterPosition( avoidPoint, otherAvoidPoint ); + bestDistanceSqr = Distance2DSquared( bestPosition, refPoint ); + + for ( i = 1; i < pickCount; i++ ) + { + candidatePosition = GetRandomHelicopterPosition( avoidPoint, otherAvoidPoint ); + candidateDistanceSqr = Distance2DSquared( candidatePosition, refPoint ); + + if ( candidateDistanceSqr < bestDistanceSqr ) + { + bestPosition = candidatePosition; + bestDistanceSqr = candidateDistanceSqr; + } + } + + return bestPosition; +} + +function WaitForStoppingMoveToExpire() +{ + elapsedTimeStopping = GetTime() - self.lastStopTime; + if ( elapsedTimeStopping < ( 2000 ) ) + { + wait ( (( 2000 ) - elapsedTimeStopping) * 0.001 ); + } +} + +function GetOtherHelicopterPointToAvoid() //self == raps helicopter +{ + avoid_point = undefined; + + ArrayRemoveValue( level.raps_helicopters, undefined ); // clean up array first + + foreach( heli in level.raps_helicopters ) + { + if ( heli != self ) + { + avoid_point = heli.targetDropLocation; + break; + } + } + + return avoid_point; +} + +function PickNextDropLocation( heli, drop_index, firstDropReferencePoint, assigned_fly_height, lastDropLocation ) +{ + avoid_point = self GetOtherHelicopterPointToAvoid(); + + // if we have a pre-picked drop location, use that first and reset it + if ( isdefined( heli ) && isdefined( heli.prePickedDropLocation ) ) + { + targetDropLocation = heli.prePickedDropLocation; + heli.prePickedDropLocation = undefined; + return targetDropLocation; + } + + targetDropLocation = ( ( drop_index == 0 ) ? GetClosestRandomHelicopterPosition( + firstDropReferencePoint, + INT(game["raps_helicopter_positions"].size * (( 66.6 ) / 100.0) + 1), + avoid_point ) + : GetRandomHelicopterPosition( lastDropLocation, avoid_point ) ); + + targetDropLocation = ( targetDropLocation[0], targetDropLocation[1], assigned_fly_height ); + + return targetDropLocation; +} + +function HelicopterThink() +{ +/# + if ( GetDvarInt( "scr_raps_debug_visit_all" ) ) + return; +#/ + + self endon( "raps_helicopter_shutdown" ); + + for( i = 0; i < ( 3 ); i++ ) + { + self.targetDropLocation = PickNextDropLocation( self, i, self.firstDropReferencePoint, self.assigned_fly_height, self.lastDropLocation ); + + while ( Distance2DSquared( self.origin, self.targetDropLocation ) > ( 5 * 5 ) ) + { + self WaitForStoppingMoveToExpire(); + self UpdateHelicopterSpeed(); + self setVehGoalPos( self.targetDropLocation, 1 ); + self waittill( "goal" ); + } + + if ( isdefined( self.owner ) ) + { + if ( ( i + 1 ) < ( 3 ) ) + { + self killstreaks::play_pilot_dialog_on_owner( "waveStart", "raps", self.killstreakId ); + } + else + { + self killstreaks::play_pilot_dialog_on_owner( "waveStartFinal", "raps", self.killstreakId ); + } + } + + enemy = self.owner battlechatter::get_closest_player_enemy( self.origin, true ); + enemyRadius = battlechatter::mpdialog_value( "rapsDropRadius", 0 ); + + if ( isdefined( enemy ) && Distance2DSquared( self.origin, enemy.origin ) < enemyRadius * enemyRadius ) + { + enemy battlechatter::play_killstreak_threat( "raps" ); + } + + self DropRaps(); + + wait( ( i + 1 >= ( 3 ) ) + ? ( 2.0 ) + RandomFloatRange( -( 1.0 ), ( 1.0 ) ) + : ( 2.0 ) + RandomFloatRange( -( 2.0 ) , ( 2.0 ) ) ); + } + + self notify( "raps_helicopter_shutdown", false ); +} + +/# +function HelicopterThinkDebugVisitAll() +{ + self endon( "death" ); + + if ( GetDvarInt( "scr_raps_debug_visit_all" ) == 0 ) + return; + + for( i = 0; i < 100; i++ ) + { + for( j = 0; j < game["raps_helicopter_positions"].size; j++ ) + { + self.targetDropLocation = ( game["raps_helicopter_positions"][ j ][0], game["raps_helicopter_positions"][ j ][1], self.assigned_fly_height ); + + while ( Distance2DSquared( self.origin, self.targetDropLocation ) > ( 5 * 5 ) ) + { + self WaitForStoppingMoveToExpire(); + self UpdateHelicopterSpeed(); + self setVehGoalPos( self.targetDropLocation, 1 ); + self waittill( "goal" ); + } + + self DropRaps(); + + wait( 1.0 ); + + if ( GetDvarInt( "scr_raps_debug_visit_all_fake_leave" ) > 0 ) + { + if ( (j+1) % 3 == 0 ) + { + + // fake a leave and then return + self.targetDropLocation = GetRandomHelicopterStartOrigin( self.assigned_fly_height, self.origin ); //TODO: make this debug function work at some point, not now, too close to ship + while ( Distance2DSquared( self.origin, self.targetDropLocation ) > ( 5 * 5 ) ) + { + self WaitForStoppingMoveToExpire(); + self UpdateHelicopterSpeed(); + self setVehGoalPos( self.targetDropLocation, 1 ); + self waittill( "goal" ); + } + } + } + } + } + + self notify( "raps_helicopter_shutdown", false ); +} +#/ + +function DropRaps() +{ + level endon( "game_ended" ); + self endon( "death" ); + + self.droppingRaps = true; + self.lastDropLocation = self.origin; + + // reposition raps to a more precise drap location + preciseDropLocation = 0.5 * ( self GetTagOrigin( level.raps_helicopter_drop_tag_names[0] ) + self GetTagOrigin( level.raps_helicopter_drop_tag_names[1] ) ); + preciseGoalLocation = self.targetDropLocation + (self.targetDropLocation - preciseDropLocation); + preciseGoalLocation = ( preciseGoalLocation[0], preciseGoalLocation[1], self.targetDropLocation[2] ); + self setVehGoalPos( preciseGoalLocation, 1 ); + self waittill( "goal" ); + self.droppedRaps = true; + for( i = 0; i < level.raps_settings.spawn_count; i++ ) + { + spawn_tag = level.raps_helicopter_drop_tag_names[ i % level.raps_helicopter_drop_tag_names.size ]; + + origin = self GetTagOrigin( spawn_tag ); + angles = self GetTagAngles( spawn_tag ); + + if ( !isdefined( origin ) || !isdefined( angles ) ) + { + origin = self.origin; + angles = self.angles; + } + + self.owner thread SpawnRaps( origin, angles ); + self playsound( "veh_raps_launch" ); + wait( ( 1 ) ); + } + + self.droppingRaps = false; +} + +function Spin() +{ + self endon( "stop_death_spin" ); + + speed = RandomIntRange( 180, 220 ); + self setyawspeed( speed, speed * 0.25, speed ); + + if ( RandomIntRange ( 0, 2 ) > 0 ) + speed = -speed; + + while ( isdefined( self ) ) + { + self settargetyaw( self.angles[1]+(speed*0.4) ); + wait ( 1 ); + } +} + +function FirstHeliExplo() +{ + PlayFxOnTag( "killstreaks/fx_heli_raps_exp_sm", self, "tag_fx_engine_exhaust_back" ); + self PlaySound( level.heli_sound["crash"] ); +} + +function HeliLowHealthFx() +{ + self clientfield::set( "raps_heli_low_health", 1 ); +} + +function HeliExtraLowHealthFx() +{ + self clientfield::set( "raps_heli_extra_low_health", 1 ); +} + +function HeliDeathTrails() +{ + PlayFxOnTag( "killstreaks/fx_heli_raps_exp_trail", self, "tag_fx_engine_exhaust_back" ); +} + +function FinalHeliDeathExplode() +{ + PlayFxOnTag( "killstreaks/fx_heli_raps_exp_lg", self, "tag_fx_death" ); + self PlaySound( level.heli_sound["crash"] ); +} + +function HelicopterLeave() +{ + self.isLeaving = true; + + self killstreaks::play_pilot_dialog_on_owner( "timeout", "raps" ); + self killstreaks::play_taacom_dialog_response_on_owner( "timeoutConfirmed", "raps" ); + + self.leaveLocation = GetRandomHelicopterLeaveOrigin( /* self.assigned_fly_height */ 0, self.origin ); + while ( Distance2DSquared( self.origin, self.leaveLocation ) > ( 600 * 600 ) ) + { + self UpdateHelicopterSpeed(); + self setVehGoalPos( self.leaveLocation, 0 ); + self waittill( "goal" ); + } +} + +function UpdateHelicopterSpeed( driveMode ) +{ + if ( isdefined( driveMode ) ) + { + switch ( driveMode ) + { + case 0: + self.driveModeSpeedScale = 1.0; + self.driveModeAccel = ( 20 ); + self.driveModeDecel = ( 20 ); + break; + + case 1: + case 2: + self.driveModeSpeedScale = ((driveMode == 2) ? ( 0.2 ) : ( 0.5 )); + self.driveModeAccel = ( 12 ); + self.driveModeDecel = ( 100 ); + break; + } + } + + desiredSpeed = (self GetMaxSpeed() / 17.6) * self.driveModeSpeedScale; + + // use Decel as Accel when the desired speed is less than the current speed; (it's a side effect of the system) + if ( desiredspeed < self GetSpeedMPH() ) + { + self SetSpeed( desiredSpeed, self.driveModeDecel, self.driveModeDecel ); + } + else + { + self SetSpeed( desiredSpeed, self.driveModeAccel, self.driveModeDecel ); + } +} + +function StopHelicopter() +{ + //self SetSpeed( 0, RAPS_HELAV_FULL_STOP_MODE_ACCEL, RAPS_HELAV_FULL_STOP_MODE_DECEL ); + self SetSpeed( 0, ( 500 ), ( 500 ) ); // using DECEL as accel due to way the current system works + self.lastStopTime = GetTime(); +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// RAPS +///////////////////////////////////////////////////////////////////////////////////////////////// +function SpawnRaps( origin, angles ) +{ + originalOwner = self; + originalOwnerEntNum = originalOwner.entNum; + + raps = SpawnVehicle( "spawner_bo3_raps_mp", origin, angles, "dynamic_spawn_ai" ); + + if ( !isdefined( raps ) ) + return; + + raps.forceOneMissile = true; + raps.drop_deploying = true; + raps.hurt_trigger_immune_end_time = GetTime() + (isdefined(level.raps_hurt_trigger_immune_duration_ms)?level.raps_hurt_trigger_immune_duration_ms:5000); + + if ( !isdefined( level.raps[ originalOwnerEntNum ].raps ) ) level.raps[ originalOwnerEntNum ].raps = []; else if ( !IsArray( level.raps[ originalOwnerEntNum ].raps ) ) level.raps[ originalOwnerEntNum ].raps = array( level.raps[ originalOwnerEntNum ].raps ); level.raps[ originalOwnerEntNum ].raps[level.raps[ originalOwnerEntNum ].raps.size]=raps;; + + raps killstreaks::configure_team( "raps", "raps", originalOwner, undefined, undefined, &ConfigureTeamPost ); + raps killstreak_hacking::enable_hacking( "raps" ); + raps clientfield::set( "enemyvehicle", 1 ); + raps.soundmod = "raps"; + raps.ignore_vehicle_underneath_splash_scalar = true; + raps.detonate_sides_disabled = true; + raps.treat_owner_damage_as_friendly_fire = true; + raps.ignore_team_kills = true; + + raps SetInvisibleToAll(); + raps thread AutoSetVisibleToAll(); + + raps vehicle::toggle_sounds( 0 ); + //raps thread sndAndRumbleWaitUntilLanding( originalOwner ); // now in client script as monitor_drop_landing + + raps thread WatchRapsKills( originalOwner ); + raps thread WatchRapsDeath( originalOwner ); + raps thread killstreaks::WaitForTimeout( "raps", raps.settings.max_duration * 1000, &OnRapsTimeout, "death" ); +} + + +function ConfigureTeamPost( owner, isHacked ) +{ + raps = self; + raps thread CreateRapsInfluencer(); + raps thread InitEnemySelection( owner ); + raps thread WatchRapsTippedOver( owner ); +} + + + +function AutoSetVisibleToAll() +{ + self endon( "death" ); + + // intent: hide the visual glitches when first spawning raps mid air + + {wait(.05);}; + {wait(.05);}; + + self SetVisibleToAll(); +} + +function OnRapsTimeout() +{ + self SelfDestruct( self.owner ); +} + +function SelfDestruct( attacker ) // self == raps +{ + self.selfDestruct = true; + self raps::detonate( attacker ); +} + +function WatchRapsKills( originalOwner ) +{ + originalOwner endon( "raps_complete" ); + self endon( "death" ); + + if( self.settings.max_kill_count == 0 ) + { + return; + } + + while( true ) + { + self waittill( "killed", victim ); + + if( isdefined( victim ) && IsPlayer( victim ) ) + { + if( !isdefined( self.killCount ) ) + { + self.killCount = 0; + } + + self.killCount++; + if( self.killCount >= self.settings.max_kill_count ) + { + self raps::detonate( self.owner ); + } + } + } +} + +function WatchRapsTippedOver( owner ) +{ + owner endon( "disconnect" ); + self endon( "death" ); + + // if the raps manage to tip over and get stuck, it should detonate + while( true ) + { + wait 3.5; + + if ( Abs( self.angles[2] ) > 75 ) + { + self raps::detonate( owner ); + } + } +} + +function WatchRapsDeath( originalOwner ) +{ + originalOwnerEntNum = originalOwner.entnum; + self waittill( "death", attacker, damageFromUnderneath, weapon ); + + attacker = self [[ level.figure_out_attacker ]]( attacker ); + + if( isdefined( attacker ) && isPlayer( attacker ) ) + { + if( isdefined( self.owner ) && self.owner != attacker && ( self.owner.team != attacker.team ) ) + { + scoreevents::processScoreEvent( "killed_raps", attacker ); + attacker challenges::destroyScoreStreak( weapon, true ); + attacker challenges::destroyNonAirScoreStreak_PostStatsLock( weapon ); + + if( isdefined( self.attackers ) ) + { + foreach( player in self.attackers ) + { + if( isPlayer( player ) && ( player != attacker ) && ( player != self.owner ) ) + { + scoreevents::processScoreEvent( "killed_raps_assist", player ); + } + } + } + } + } + + ArrayRemoveValue( level.raps[ originalOwnerEntNum ].raps, self ); +} + +function InitEnemySelection( owner ) //self == raps +{ + owner endon( "disconnect" ); + self endon( "death" ); + self endon( "hacked" ); + + self vehicle_ai::set_state( "off" ); + util::wait_network_frame(); // wait needed to get drop deploy mode to work + util::wait_network_frame(); // need two to make sure fast forward works + self SetVehicleForDropDeploy(); + self clientfield::set( "monitor_raps_drop_landing", 1 ); + wait( ( 3 ) ); + if ( self InitialWaitUntilSettled() ) + { + self ResetVehicleFromDropDeploy(); + self SetGoal( self.origin ); + self vehicle_ai::set_state( "combat" ); + self vehicle::toggle_sounds( 1 ); + + self.drop_deploying = undefined; + self.hurt_trigger_immune_end_time = undefined; + Target_Set( self ); + + // try not to target the same enemy + for( i = 0; i < level.raps[ owner.entNum ].raps.size; i++ ) + { + raps = level.raps[ owner.entNum ].raps[ i ]; + if( isdefined( raps ) && isdefined( raps.enemy ) && isdefined( self ) && isdefined( self.enemy ) && ( raps != self ) && ( raps.enemy == self.enemy ) ) + { + self SetPersonalThreatBias( self.enemy, -2000, 5.0 ); + } + } + } + else + { + // could not settle, then self destruct + self SelfDestruct( self.owner ); + } +} + + + + + + + + +function InitialWaitUntilSettled() +{ + // settle z speed first + waitTime = 0; + while ( Abs( self.velocity[2] ) > ( 0.1 ) && waitTime < ( 5.0 ) ) + { + wait ( 0.2 ); + waitTime += ( 0.2 ); + } + + // wait until settled on nav mesh + while( ( !IsPointOnNavMesh( self.origin, ( 36 ) ) || ( Abs( self.velocity[2] ) > ( 0.1 ) ) ) && waitTime < ( ( 5.0 ) + 5.0 ) ) + { + wait ( 0.2 ); + waitTime += ( 0.2 ); + } + +/# + if ( ( false ) ) + waitTime += ( ( 5.0 ) + 5.0 ); +#/ + + // return true if raps settled without timing out + return ( waitTime < ( ( 5.0 ) + 5.0 ) ); +} + + +function DestroyAllRaps( entNum, abandoned = false ) +{ + foreach( raps in level.raps[ entNum ].raps ) + { + if( IsAlive( raps ) ) + { + raps.owner = undefined; + raps.abandoned = abandoned; // note: abandoned vehicles do not cause damage radius damage + raps raps::detonate( raps ); + } + } +} + +//Override for scripts/shared/vehicles/_raps.gsc:force_get_enemies() +function ForceGetEnemies() +{ + foreach( player in level.players ) + { + if( isdefined( self.owner ) && self.owner util::IsEnemyPlayer( player ) && ( !player smokegrenade::IsInSmokeGrenade() ) && !player hasPerk( "specialty_nottargetedbyraps" ) ) + { + self GetPerfectInfo( player ); + return; + } + } +} + +function CreateRapsHelicopterInfluencer() +{ + level endon( "game_ended" ); + + helicopter = self; + + if ( isdefined( helicopter.influencerEnt ) ) + { + helicopter.influencerEnt Delete(); + } + + influencerEnt = spawn( "script_model", helicopter.origin - ( 0, 0, self.assigned_fly_height ) ); + helicopter.influencerEnt = influencerEnt; + helicopter.influencerEnt.angles = ( 0, 0, 0 ); + helicopter.influencerEnt LinkTo( helicopter ); + + preset = GetInfluencerPreset( "helicopter" ); + if( !IsDefined( preset ) ) + { + return; + } + + enemy_team_mask = helicopter spawning::get_enemy_team_mask( helicopter.team ); + helicopter.influencerEnt spawning::create_entity_influencer( "helicopter", enemy_team_mask ); + + helicopter waittill( "death" ); + if ( isdefined( influencerEnt ) ) + { + influencerEnt delete(); + } +} + +function CreateRapsInfluencer() +{ + raps = self; + + preset = GetInfluencerPreset( "raps" ); + if( !IsDefined( preset ) ) + { + return; + } + + enemy_team_mask = raps spawning::get_enemy_team_mask( raps.team ); + raps spawning::create_entity_influencer( "raps", enemy_team_mask ); +} diff --git a/mp/killstreaks/_rcbomb.csc b/mp/killstreaks/_rcbomb.csc new file mode 100644 index 0000000..0fcfe07 --- /dev/null +++ b/mp/killstreaks/_rcbomb.csc @@ -0,0 +1,303 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\math_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\vehicles\_driving_fx; + + + + + +#using scripts\mp\_callbacks; +#using scripts\mp\_util; +#using scripts\mp\_vehicle; + +// _rcbomb.csc +// Sets up clientside behavior for the rcbomb + +#precache( "client_fx", "killstreaks/fx_rcxd_lights_blinky" ); +#precache( "client_fx", "killstreaks/fx_rcxd_lights_solid" ); +#precache( "client_fx", "killstreaks/fx_rcxd_lights_red" ); +#precache( "client_fx", "killstreaks/fx_rcxd_lights_grn" ); +#precache( "client_fx", "_t6/weapon/grenade/fx_spark_disabled_rc_car" ); + +#namespace rcbomb; + + + + + + +function autoexec __init__sytem__() { system::register("rcbomb",&__init__,undefined,undefined); } + +function __init__() +{ + level._effect["rcbomb_enemy_light"] = "killstreaks/fx_rcxd_lights_blinky"; + level._effect["rcbomb_friendly_light"] = "killstreaks/fx_rcxd_lights_solid"; + level._effect["rcbomb_enemy_light_blink"] = "killstreaks/fx_rcxd_lights_red"; + level._effect["rcbomb_friendly_light_blink"] = "killstreaks/fx_rcxd_lights_grn"; + level._effect["rcbomb_stunned"] = "_t6/weapon/grenade/fx_spark_disabled_rc_car"; + + level.rcBombBundle = struct::get_script_bundle( "killstreak", "killstreak_rcbomb" ); + + // vehicle flags + clientfield::register( "vehicle", "rcbomb_stunned", 1, 1, "int", &callback::callback_stunned, !true, !true ); + + vehicle::add_vehicletype_callback( "rc_car_mp",&spawned ); +} + +function spawned(localClientNum) +{ + self thread demo_think( localClientNum ); + self thread stunnedHandler(localClientNum); + self thread boost_think( localClientNum ); + self thread shutdown_think( localClientNum ); + + self.driving_fx_collision_override = &OnDrivingFxCollision; + self.driving_fx_jump_landing_override = &OnDrivingFxJumpLanding; + + self.killstreakBundle = level.rcBombBundle; +} + +function demo_think( localClientNum ) +{ + self endon( "entityshutdown" ); + + if ( !IsDemoPlaying() ) + { + return; + } + + for ( ;; ) + { + level util::waittill_any( "demo_jump", "demo_player_switch" ); + self vehicle::lights_off( localClientNum ); + } +} + +function boost_blur( localClientNum ) +{ + self endon( "entityshutdown" ); + + if ( isdefined( self.owner ) && self.owner isLocalPlayer() ) + { + EnableSpeedBlur( localClientNum, GetDvarFloat( "scr_rcbomb_amount", 0.1 ), GetDvarFloat( "scr_rcbomb_inner_radius", 0.5 ), GetDvarFloat( "scr_rcbomb_outer_radius", 0.75 ), false, 0 ); + + wait( GetDvarFloat( "scr_rcbomb_duration", 1.0 ) ); + + DisableSpeedBlur( localClientNum ); + } +} + +function boost_think( localClientNum ) +{ + self endon( "entityshutdown" ); + + for ( ;; ) + { + self waittill( "veh_boost" ); + self boost_blur( localClientNum ); + } +} + +function shutdown_think( localClientNum ) +{ + self waittill( "entityshutdown" ); + DisableSpeedBlur( localClientNum ); +} + +function play_screen_fx_dirt(localClientNum) +{ + // support for this has been removed with the .menu system + /* + pick_one = RandomIntRange(0,4); + if ( pick_one == 0 ) + { + AnimateUI( localClientNum, "fullscreen_dirt", "dirt", "in", 0 ); + } + else if ( pick_one == 1 ) + { + AnimateUI( localClientNum, "fullscreen_dirt", "dirt_right_splash", "in", 0 ); + } + else if ( pick_one == 2 ) + { + AnimateUI( localClientNum, "fullscreen_dirt", "dirt_left_splash", "in", 0 ); + } + else + { + AnimateUI( localClientNum, "fullscreen_dirt", "blurred_dirt_random", "in", 0 ); + } + */ +} + +function play_screen_fx_dust(localClientNum) +{ + // support for this has been removed with the .menu system + /* + pick_one = RandomIntRange(0,4); + if ( pick_one == 0 ) + { + AnimateUI( localClientNum, "fullscreen_dust", "dust", "in", 0 ); + } + else if ( pick_one == 1 ) + { + AnimateUI( localClientNum, "fullscreen_dust", "dust_right_splash", "in", 0 ); + } + else if ( pick_one == 2 ) + { + AnimateUI( localClientNum, "fullscreen_dust", "dust_left_splash", "in", 0 ); + } + else + { + AnimateUI( localClientNum, "fullscreen_dust", "blurred_dust_random", "in", 0 ); + } + */ +} + +function play_driving_screen_fx( localClientNum ) +{ + speed_fraction = 0; + + while(1) + { + speed = self getspeed(); + maxspeed = self getmaxspeed(); + + if ( speed < 0 ) + { + maxspeed = self getmaxreversespeed(); + } + + if ( maxspeed > 0 ) + { + speed_fraction = Abs(speed) / maxspeed; + } + else + { + speed_fraction = 0; + } + if ( self iswheelcolliding( "back_left" ) || self iswheelcolliding( "back_right" ) ) + { + // probably need to fix this to work on spectators + if ( self IsLocalClientDriver( localClientNum ) ) + { + } + } + } +} + +function play_boost_fx( localClientNum ) +{ + self endon( "entityshutdown" ); + + while( 1 ) + { + speed = self GetSpeed(); + + if ( speed > 400 ) + { + self PlaySound( localClientNum, "mpl_veh_rc_boost" ); + return; + } + + util::server_wait( localClientNum, 0.1 ); + } +} + +function stunnedHandler( localClientNum ) +{ + self endon( "entityshutdown" ); + + self thread engineStutterHandler( localClientNum ); + + while( 1 ) + { + self waittill( "stunned" ); +///# +// PrintLn( "CLIENT ***************** stunned" ); +//#/ + + self setstunned( true ); + + self thread notStunnedHandler( localClientNum ); + + self thread play_stunned_fx_handler( localClientNum ); + } +} + +function notStunnedHandler( localClientNum ) +{ + self endon( "entityshutdown" ); + self endon( "stunned" ); + + self waittill( "not_stunned" ); +///# +// PrintLn( "CLIENT ***************** not stunned" ); +//#/ + + self setstunned( false ); +} + +function play_stunned_fx_handler( localClientNum ) // self == rc car +{ + self endon( "entityshutdown" ); + self endon( "stunned" ); + self endon( "not_stunned" ); + + // we need this so we can continue to play fx if being stunned by the jammer + while( true ) + { + playfxontag( localClientNum, level._effect["rcbomb_stunned"], self, "tag_origin" ); + wait( 0.5 ); + } +} + +function engineStutterHandler( localClientNum ) +{ + self endon( "entityshutdown" ); + + while( 1 ) + { + self waittill( "veh_engine_stutter" ); + if ( self IsLocalClientDriver( localClientNum ) ) + { + player = getlocalplayer( localClientNum ); + + if( isdefined( player ) ) + { + player PlayRumbleOnEntity( localClientNum, "rcbomb_engine_stutter" ); + } + } + } +} + +function OnDrivingFxCollision( localClientNum, player, hip, hitn, hit_intensity ) +{ + if( isdefined( hit_intensity ) && hit_intensity > 15 ) + { + volume = driving_fx::get_impact_vol_from_speed(); + + if (isdefined (self.sounddef)) + { + alias = self.sounddef + "_suspension_lg_hd"; + } + else + { + alias = "veh_default_suspension_lg_hd"; + } + + id = PlaySound( 0, alias, self.origin, volume); + player Earthquake( 0.7, 0.25, player.origin, 1500 ); + player PlayRumbleOnEntity( localClientNum, "damage_heavy" ); + } +} + +function OnDrivingFxJumpLanding( localClientNum, player ) +{ + // do nothing for now +} + + diff --git a/mp/killstreaks/_rcbomb.gsc b/mp/killstreaks/_rcbomb.gsc new file mode 100644 index 0000000..e8c30db --- /dev/null +++ b/mp/killstreaks/_rcbomb.gsc @@ -0,0 +1,681 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\_oob; +#using scripts\shared\array_shared; +#using scripts\shared\audio_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_flashgrenades; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\vehicle_shared; +#using scripts\shared\vehicle_death_shared; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_shellshock; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_remote_weapons; + + + + + + +#precache( "string", "KILLSTREAK_EARNED_RCBOMB" ); +#precache( "string", "KILLSTREAK_RCBOMB_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_RCBOMB_INBOUND" ); +#precache( "string", "KILLSTREAK_RCBOMB_HACKED" ); +#precache( "string", "KILLSTREAK_DESTROYED_RCBOMB" ); +#precache( "string", "mpl_killstreak_rcbomb" ); +#precache( "fx", "_t6/weapon/grenade/fx_spark_disabled_rc_car" ); +#precache( "fx", "killstreaks/fx_rcxd_lights_grn" ); +#precache( "fx", "killstreaks/fx_rcxd_lights_red" ); +#precache( "fx", "killstreaks/fx_rcxd_exp" ); + +#namespace rcbomb; + + + + + +function init() +{ + level._effect["rcbombexplosion"] = "killstreaks/fx_rcxd_exp"; + + killstreaks::register( "rcbomb", "rcbomb", "killstreak_rcbomb", "rcbomb_used",&ActivateRCBomb ); + killstreaks::register_strings( "rcbomb", &"KILLSTREAK_EARNED_RCBOMB", &"KILLSTREAK_RCBOMB_NOT_AVAILABLE", &"KILLSTREAK_RCBOMB_INBOUND", undefined, &"KILLSTREAK_RCBOMB_HACKED", false ); + killstreaks::register_dialog( "rcbomb", "mpl_killstreak_rcbomb", "rcBombDialogBundle", undefined, "friendlyRcBomb", "enemyRcBomb", "enemyRcBombMultiple", "friendlyRcBombHacked", "enemyRcBombHacked", "requestRcBomb" ); + killstreaks::allow_assists( "rcbomb", true); + killstreaks::register_alt_weapon( "rcbomb", "killstreak_remote" ); + killstreaks::register_alt_weapon( "rcbomb", "rcbomb_turret" ); + remote_weapons::RegisterRemoteWeapon( "rcbomb", &"", &StartRemoteControl, &EndRemoteControl, false ); + + vehicle::add_main_callback( "rc_car_mp", &InitRCBomb ); + + clientfield::register( "vehicle", "rcbomb_stunned", 1, 1, "int" ); +} + +function InitRCBomb() +{ + rcbomb = self; + + rcbomb clientfield::set( "enemyvehicle", 1 ); + rcbomb.allowFriendlyFireDamageOverride = &RCCarAllowFriendlyFireDamage; + rcbomb EnableAimAssist(); + rcbomb SetDrawInfrared( true ); + rcbomb.delete_on_death = true; + rcbomb.death_enter_cb = &waitRemoteControl; + rcbomb.disableRemoteWeaponSwitch = true; + rcbomb.overrideVehicleDamage = &OnDamage; + rcbomb.overrideVehicleDeath = &OnDeath; + //rcbomb.remoteWeaponShutdownDelay = RCBOMB_SHUTDOWN_DELAY; + rcbomb.watch_remote_weapon_death = true; + rcbomb.watch_remote_weapon_death_duration = ( 0.3 ); + + if ( IsSentient( rcbomb ) == false ) + rcbomb MakeSentient(); // so other sentients will consider this as a potential enemy +} + + +function waitRemoteControl() +{ + remote_controlled = ( isdefined( self.control_initiated ) && self.control_initiated ) || ( isdefined( self.controlled ) && self.controlled ); + + if( remote_controlled ) + { + notifyString = self util::waittill_any_return( "remote_weapon_end", "rcbomb_shutdown" ); + if( notifyString == "remote_weapon_end" ) + self waittill( "rcbomb_shutdown" ); + else + self waittill( "remote_weapon_end" ); + } + else + self waittill( "rcbomb_shutdown" ); +} + +function toggleLightsOnAfterTime( time ) +{ + self notify("toggleLightsOnAfterTime_singleton"); + self endon ("toggleLightsOnAfterTime_singleton"); + + rcbomb = self; + rcbomb endon( "death" ); + wait( time ); + rcbomb clientfield::set( "toggle_lights", 0 ); +} + +function HackedPreFunction( hacker ) +{ + rcbomb = self; + rcbomb clientfield::set( "toggle_lights", 1 ); + rcbomb.owner unlink(); + rcbomb clientfield::set( "vehicletransition", 0 ); + rcbomb.owner killstreaks::clear_using_remote(); + rcbomb MakeVehicleUnusable(); +} + +function HackedPostFunction( hacker ) +{ + rcbomb = self; + hacker remote_weapons::UseRemoteWeapon( rcbomb, "rcbomb", true, false ); + rcbomb MakeVehicleUnusable(); + hacker killstreaks::set_killstreak_delay_killcam( "rcbomb" ); + hacker killstreak_hacking::set_vehicle_drivable_time_starting_now( rcbomb ); +} + +function ConfigureTeamPost( owner, isHacked ) +{ + rcbomb = self; + rcbomb thread WatchOwnerGameEvents(); +} + + +function ActivateRCBomb( hardpointType ) +{ + assert( IsPlayer( self ) ); + player = self; + + if( !player killstreakrules::isKillstreakAllowed( hardpointType, player.team ) ) + { + return false; + } + + if ( player UseButtonPressed() ) + { + return false; + } + + placement = CalculateSpawnOrigin( self.origin, self.angles ); + if( !isdefined( placement ) || !self IsOnGround() || self util::isUsingRemote() || killstreaks::is_interacting_with_object() || self oob::IsTouchingAnyOOBTrigger() || self killstreaks::is_killstreak_start_blocked() ) + { + self iPrintLnBold( &"KILLSTREAK_RCBOMB_NOT_PLACEABLE" ); + return false; + } + + killstreak_id = player killstreakrules::killstreakStart( "rcbomb", player.team, false, true ); + if( killstreak_id == (-1) ) + { + return false; + } + + rcbomb = SpawnVehicle( "rc_car_mp", placement.origin, placement.angles, "rcbomb" ); + + rcbomb killstreaks::configure_team( "rcbomb", killstreak_id, player, "small_vehicle", undefined, &ConfigureTeamPost ); + rcbomb killstreak_hacking::enable_hacking( "rcbomb", &HackedPreFunction, &HackedPostFunction ); + rcbomb.damageTaken = 0; + rcbomb.abandoned = false; + rcbomb.killstreak_id = killstreak_id; + rcbomb.activatingKillstreak = true; + rcbomb SetInvisibleToAll(); + + rcbomb thread WatchShutdown(); + rcbomb.health = killstreak_bundles::get_max_health( hardpointType ); + rcbomb.maxhealth = killstreak_bundles::get_max_health( hardpointType ); + rcbomb.hackedhealth = killstreak_bundles::get_hacked_health( hardpointType ); + rcbomb.hackedHealthUpdateCallback = &rcbomb_hacked_health_update; + rcbomb.ignore_vehicle_underneath_splash_scalar = true; + + self thread killstreaks::play_killstreak_start_dialog( "rcbomb", self.team, killstreak_id ); + self AddWeaponStat( GetWeapon( "rcbomb" ) , "used", 1 ); + + remote_weapons::UseRemoteWeapon( rcbomb, "rcbomb", true, false ); + + if ( !isdefined( player ) || !isAlive( player ) || ( isdefined( player.laststand ) && player.laststand ) || player IsEMPJammed() ) + { + if ( isdefined( rcbomb ) ) + { + rcbomb notify( "remote_weapon_shutdown" ); + rcbomb notify( "rcbomb_shutdown" ); + } + return false; + } + + rcbomb SetVisibleToAll(); + rcbomb.activatingKillstreak = false; + Target_Set( rcbomb ); + + rcbomb thread WatchGameEnded(); + + return true; +} + +function rcbomb_hacked_health_update( hacker ) +{ + rcbomb = self; + if ( rcbomb.health > rcbomb.hackedhealth ) + { + rcbomb.health = rcbomb.hackedhealth; + } +} + + +function StartRemoteControl( rcbomb ) +{ + player = self; + + rcbomb UseVehicle( player, 0 ); + rcbomb clientfield::set( "vehicletransition", 1 ); + + rcbomb thread audio::sndUpdateVehicleContext(true); + + rcbomb thread WatchTimeout(); + rcbomb thread WatchDetonation(); + rcbomb thread WatchHurtTriggers(); + rcbomb thread WatchWater(); + + player vehicle::set_vehicle_drivable_time_starting_now( ( 40 * 1000 ) ); +} + +function EndRemoteControl( rcbomb, exitRequestedByOwner ) +{ + if ( exitrequestedbyowner == false ) + { + rcbomb notify( "rcbomb_shutdown" ); + rcbomb thread audio::sndUpdateVehicleContext(false); + } + rcbomb clientfield::set( "vehicletransition", 0 ); +} + +function WatchDetonation() +{ + rcbomb = self; + rcbomb endon( "rcbomb_shutdown" ); + rcbomb endon( "death" ); + + while( !rcbomb.owner attackbuttonpressed() ) + { + {wait(.05);}; + } + + rcbomb notify( "rcbomb_shutdown" ); +} + + + + + + +function WatchWater() +{ + self endon( "rcbomb_shutdown" ); + + inWater = false; + while( !inWater ) + { + wait ( 0.5 ); + trace = physicstrace( self.origin + ( 0, 0, 10 ), self.origin + ( 0, 0, 6 ), ( -2, -2, -2 ), ( 2, 2, 2 ), self, ( (1 << 2) )); + inWater = trace["fraction"] < 1.0; + } + + self.abandoned = true; + self notify( "rcbomb_shutdown" ); +} + +function WatchOwnerGameEvents() +{ + self notify("WatchOwnerGameEvents_singleton"); + self endon ("WatchOwnerGameEvents_singleton"); + + rcbomb = self; + rcbomb endon( "rcbomb_shutdown" ); + + rcbomb.owner util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); + + rcbomb.abandoned = true; + rcbomb notify( "rcbomb_shutdown" ); +} + +function WatchTimeout() +{ + rcbomb = self; + rcbomb thread killstreaks::WaitForTimeout( "rcbomb", ( 40 * 1000 ), &rc_shutdown, "rcbomb_shutdown" ); +} + +function rc_shutdown() +{ + rcbomb = self; + rcbomb notify( "rcbomb_shutdown" ); +} + +function WatchShutdown() +{ + rcbomb = self; + rcbomb endon( "death" ); + + rcbomb waittill( "rcbomb_shutdown" ); + + if ( isdefined( rcbomb.activatingKillstreak ) && rcbomb.activatingKillstreak ) + { + // we can delete since it should not have been made visible yet + killstreakrules::killstreakStop( "rcbomb", rcbomb.originalteam, rcbomb.killstreak_id ); + rcbomb notify( "rcbomb_shutdown" ); + rcbomb delete(); // still need to delete here + } + else + { + attacker = ( isdefined( rcbomb.owner ) ? rcbomb.owner : undefined ); + rcbomb DoDamage( rcbomb.health + 1, rcbomb.origin + (0, 0, 10), attacker, attacker, "none", "MOD_EXPLOSIVE", 0 ); + } +} + +function WatchHurtTriggers() +{ + rcbomb = self; + rcbomb endon( "rcbomb_shutdown" ); + + while( true ) + { + rcbomb waittill ( "touch", ent ); + if( isdefined( ent.classname ) && ( ent.classname == "trigger_hurt" || ent.classname == "trigger_out_of_bounds" ) ) + { + rcbomb notify( "rcbomb_shutdown" ); + } + } +} + +function OnDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) +{ + if ( self.activatingKillstreak ) + { + return 0.0; + } + + if ( !isdefined( eAttacker ) || eAttacker != self.owner ) + { + iDamage = killstreaks::OnDamagePerWeapon( "rcbomb", eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, self.maxhealth, undefined, self.maxhealth*0.4, undefined, 0, undefined, true, 1.0 ); + } + + if( isdefined( eAttacker ) && isdefined( eAttacker.team ) && eAttacker.team != self.team ) + { + if( weapon.isEmp ) + { + self.damage_on_death = false; + self.died_by_emp = true; + iDamage = self.health + 1; // destroy if hit by emp + //thread remote_weapons::do_static_fx(); + } + } + + // C4 destroys the HC-XD with any damage (TODO: consider creating a more robust solution if need be) + if ( weapon.name == "satchel_charge" && sMeansOfDeath == "MOD_EXPLOSIVE" ) + { + iDamage = self.health + 1; + } + + return iDamage; +} + + + +function OnDeath( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime ) +{ + rcbomb = self; + player = rcbomb.owner; + + player endon( "disconnect" ); + player endon( "joined_team" ); + player endon( "joined_spectators" ); + + killstreakrules::killstreakStop( "rcbomb", rcbomb.originalTeam, rcbomb.killstreak_id ); + + rcbomb clientfield::set( "enemyvehicle", 0 ); + + //if( isdefined( player ) && ( !isdefined( eAttacker ) || ( eAttacker.team != rcbomb.team ) ) && !level.gameEnded ) + //rcbomb remote_weapons::do_static_fx(); + + rcbomb Explode( eAttacker, weapon ); + + hide_after_wait_time = ( ( rcbomb.died_by_emp === true ) ? ( 0.2 ) : ( 0.1 ) ); + if( isdefined( player ) ) + { + player util::freeze_player_controls( true ); + rcbomb thread HideAfterWait( hide_after_wait_time ); + //rcbomb util::DeleteAfterTime( RCBOMB_SHUTDOWN_DELAY ); + wait( ( 0.2 ) ); + player util::freeze_player_controls( false ); + } + else + { + rcbomb thread HideAfterWait( hide_after_wait_time ); + //rcbomb util::DeleteAfterTime( RCBOMB_SHUTDOWN_DELAY_ABANDONED ); + } + + if ( isdefined( rcbomb ) ) + rcbomb notify( "rcbomb_shutdown" ); +} + +function WatchGameEnded() +{ + rcbomb = self; + rcbomb endon( "death" ); + + level waittill("game_ended"); + + rcbomb.abandoned = true; + rcbomb.selfDestruct = true; + rcbomb notify( "rcbomb_shutdown" ); +} + +function HideAfterWait( waitTime ) +{ + self endon( "death" ); + + wait waitTime; + self SetInvisibleToAll(); +} + +function Explode( attacker, weapon ) +{ + self endon ("death"); + owner = self.owner; + + if ( !isdefined( attacker ) && isdefined( self.owner ) ) + { + attacker = self.owner; + } + + self vehicle_death::death_fx(); + self thread vehicle_death::death_radius_damage(); + self thread vehicle_death::set_death_model( self.deathmodel, self.modelswapdelay ); + + self vehicle::toggle_tread_fx( false ); + self vehicle::toggle_exhaust_fx( false ); + self vehicle::toggle_sounds( false ); + self vehicle::lights_off(); + + self PlayRumbleOnEntity( "rcbomb_explosion" ); + + if ( !self.abandoned && attacker != self.owner && isPlayer( attacker ) ) + { + attacker challenges::destroyRCBomb( weapon ); + if ( self.owner util::IsEnemyPlayer( attacker ) ) + { + scoreevents::processScoreEvent( "destroyed_hover_rcxd", attacker, self.owner, weapon ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_RCBOMB", attacker.entnum ); + if ( isdefined( weapon ) && weapon.isValid ) + { + weaponStatName = "destroyed"; + level.globalKillstreaksDestroyed++; + // increment the destroyed stat for this, we aren't using the weaponStatName variable from above because it could be "kills" and we don't want that + weapon_rcbomb = GetWeapon( "rcbomb" ); + attacker AddWeaponStat( weapon_rcbomb, "destroyed", 1 ); + attacker AddWeaponStat( weapon, "destroyed_controlled_killstreak", 1 ); + } + + self killstreaks::play_destroyed_dialog_on_owner( "rcbomb", self.killstreak_id ); + } + } +} + +function RCCarAllowFriendlyFireDamage( eInflictor, eAttacker, sMeansOfDeath, weapon ) +{ + if ( isdefined( eAttacker ) && eAttacker == self.owner ) + return true; + + if ( isdefined( eInflictor ) && eInflictor islinkedto( self ) ) + return true; + + return false; +} + +function GetPlacementStartHeight() +{ + startheight = ( 50 ); + + switch( self GetStance() ) + { + case "crouch": + startheight = ( 30 ); + break; + case "prone": + startheight = ( 15 ); + break; + } + return startheight; +} + +function CalculateSpawnOrigin( origin, angles ) +{ + startheight = GetPlacementStartHeight(); + + mins = (-5,-5,0); // keep the min z 0 so the return point is the exact height of the collision + maxs = (5,5,10); + + startPoints = []; + startAngles = []; + wheelCounts = []; + testCheck = []; + largestCount = 0; + largestCountIndex = 0; + + testangles = []; + testangles[0] = (0,0,0); + testangles[1] = (0,20,0); + testangles[2] = (0,-20,0); + testangles[3] = (0,45,0); + testangles[4] = (0,-45,0); + + heightoffset = 5; + + for (i = 0; i < testangles.size; i++ ) + { + testCheck[i] = false; + + startAngles[i] = ( 0, angles[1], 0 ); + startPoint = origin + VectorScale( anglestoforward( startAngles[i] + testangles[i]), ( 70 ) ); + endPoint = startPoint - (0,0,100); + startPoint = startPoint + (0,0,startheight); + + // ignore water on this one + mask = (1 << 0) | (1 << 1); + + // using physicstrace so we dont slip through small cracks + trace = physicstrace( startPoint, endPoint, mins, maxs, self, mask); + + // if any player intersection then skip + if ( isdefined(trace["entity"]) && IsPlayer(trace["entity"])) + { + wheelCounts[i] = 0; + continue; + } + + startPoints[i] = trace["position"] + (0,0,heightoffset); + wheelCounts[i] = TestWheelLocations(startPoints[i],startAngles[i],heightoffset); + + if ( positionWouldTelefrag( startPoints[i] ) ) + continue; + + if ( largestCount < wheelCounts[i] ) + { + largestCount = wheelCounts[i]; + largestCountIndex = i; + } + + // going to early out on the first I find with valid tire positions + if ( wheelCounts[i] >= 3 ) + { + testCheck[i] = true; + + if ( TestSpawnOrigin( startPoints[i], startAngles[i] ) ) + { + placement = SpawnStruct(); + placement.origin = startPoints[i]; + placement.angles = startAngles[i]; + return placement; + } + } + } + + for (i = 0; i < testangles.size; i++ ) + { + if ( !testCheck[i] ) + { + if ( wheelCounts[i] >= 2 ) + { + if ( TestSpawnOrigin( startPoints[i], startAngles[i] ) ) + { + placement = SpawnStruct(); + placement.origin = startPoints[i]; + placement.angles = startAngles[i]; + return placement; + } + } + } + } + + return undefined; +} + +function TestWheelLocations( origin, angles, heightoffset ) +{ + forward = 13; + side = 10; + + wheels = []; + wheels[0] = ( forward, side, 0 ); + wheels[1] = ( forward, -1 * side, 0 ); + wheels[2] = ( -1 * forward, -1 * side, 0 ); + wheels[3] = ( -1 * forward, side, 0 ); + + height = 5; + touchCount = 0; + + yawangles = (0,angles[1],0); + + for (i = 0; i < 4; i++ ) + { + wheel = RotatePoint( wheels[i], yawangles ); + startPoint = origin + wheel; + endPoint = startPoint + (0,0,(-1 * height) - heightoffset); + startPoint = startPoint + (0,0,height - heightoffset) ; + + trace = bulletTrace( startPoint, endPoint, false, self ); + if ( trace["fraction"] < 1 ) + { + touchCount++; + } + } + + return touchCount; +} + +function TestSpawnOrigin( origin, angles ) +{ + liftedorigin = origin + (0,0,5); + size = 12; + height = 15; + mins = (-1 * size,-1 * size,0 ); + maxs = ( size,size,height ); + absmins = liftedorigin + mins; + absmaxs = liftedorigin + maxs; + + if( BoundsWouldTelefrag( absmins, absmaxs ) ) + { + return false; + } + + startheight = getPlacementStartHeight(); + + mask = (1 << 0) | (1 << 1) | (1 << 2); + + // test the volume where we are going to place the car + // note that this physics trace is not an oriented box. + trace = physicstrace( liftedorigin, (origin +(0,0,1)), mins, maxs, self, mask); + + if ( trace["fraction"] < 1 ) + { + return false; + } + + // swept trace of a small bounding box from head height to where we are placing the car + // to make sure there is no wall between us and the car + size = 2.5; + height = size * 2; + mins = (-1 * size,-1 * size,0 ); + maxs = ( size,size,height ); + + sweeptrace = physicstrace( (self.origin + (0,0,startheight)), liftedorigin, mins, maxs, self, mask); + + if ( sweeptrace["fraction"] < 1 ) + { + return false; + } + + return true; +} diff --git a/mp/killstreaks/_remote_weapons.gsc b/mp/killstreaks/_remote_weapons.gsc new file mode 100644 index 0000000..1416024 --- /dev/null +++ b/mp/killstreaks/_remote_weapons.gsc @@ -0,0 +1,529 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\hud_shared; +#using scripts\shared\util_shared; +#using scripts\shared\clientfield_shared; + +#using scripts\shared\lui_shared; +#using scripts\mp\killstreaks\_qrdrone; + +#using scripts\mp\_util; +#using scripts\mp\killstreaks\_ai_tank; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_turret; + + + +// Manager for all weapons that can be remotely controlled with the tablet + + +#namespace remote_weapons; + +function init() +{ + level.remoteWeapons = []; + level.remoteExitHint = &"MP_REMOTE_EXIT"; + callback::on_spawned( &on_player_spawned ); +} + +function on_player_spawned() +{ + self endon("disconnect"); + + self AssignRemoteControlTrigger(); +} + +function RemoveAndAssignNewRemoteControlTrigger( remoteControlTrigger ) // player = self +{ + ArrayRemoveValue( self.activeRemoteControlTriggers, remoteControlTrigger ); + self AssignRemoteControlTrigger( true ); +} + +function AssignRemoteControlTrigger( force_new_assignment = false ) // player = self +{ + if ( !isdefined( self.activeRemoteControlTriggers ) ) + self.activeRemoteControlTriggers = []; + + ArrayRemoveValue( self.activeRemoteControlTriggers, undefined ); + + if ( ( !isdefined( self.remoteControlTrigger ) || force_new_assignment ) && self.activeRemoteControlTriggers.size > 0 ) + { + self.remoteControlTrigger = self.activeRemoteControlTriggers[ self.activeRemoteControlTriggers.size - 1 ]; + } + + if ( isdefined( self.remoteControlTrigger ) ) + { + self.remoteControlTrigger.origin = self.origin; + self.remoteControlTrigger LinkTo( self ); + } +} + +function RegisterRemoteWeapon( weaponName, hintString, useCallback, endUseCallback, hideCompassOnUse = true ) +{ + assert( isdefined( level.remoteWeapons ) ); + + level.remoteWeapons[ weaponName ] = SpawnStruct(); + level.remoteWeapons[ weaponName ].hintString = hintString; + level.remoteWeapons[ weaponName ].useCallback = useCallback; + level.remoteWeapons[ weaponName ].endUseCallback = endUseCallback; + level.remoteWeapons[ weaponName ].hideCompassOnUse = hideCompassOnUse; +} + +function UseRemoteWeapon( weapon, weaponName, immediate, allowManualDeactivation = true, always_allow_ride = false ) +{ + player = self; + assert( IsPlayer( player ) ); + + weapon.remoteOwner = player; + weapon.initTime = GetTime(); + weapon.remoteName = weaponName; + weapon.remoteWeaponAllowManualDeactivation = allowManualDeactivation; + + weapon thread WatchRemoveRemoteControlledWeapon(); + + if( !immediate ) + { + weapon CreateRemoteWeaponTrigger(); + } + else + { + weapon thread WatchOwnerDisconnect(); + weapon UseRemoteControlWeapon( allowManualDeactivation, always_allow_ride ); + } +} + +function WatchForHack() +{ + weapon = self; + weapon endon( "death" ); + + weapon waittill( "killstreak_hacked", hacker ); + + if ( isdefined( weapon.remoteWeaponAllowManualDeactivation ) && weapon.remoteWeaponAllowManualDeactivation == true ) + { + weapon thread WatchRemoteControlDeactivate(); + } + weapon.remoteOwner = hacker; +} + +function WatchRemoveRemoteControlledWeapon() +{ + weapon = self; + weapon endon( "remote_weapon_end" ); + weapon util::waittill_any( "death", "remote_weapon_shutdown" ); + + weapon EndRemoteControlWeaponUse( false ); + + while( isdefined( weapon ) ) + { + {wait(.05);}; // Wait until any death animations and any other waits are finished + } + + //Check for queued remote weapon +} + +function CreateRemoteWeaponTrigger() +{ + weapon = self; + player = weapon.remoteOwner; + + if ( isdefined( weapon.useTrigger ) ) + { + weapon.useTrigger delete(); + } + + weapon.useTrigger = spawn( "trigger_radius_use", player.origin, 32, 32 ); + weapon.useTrigger EnableLinkTo(); + weapon.useTrigger LinkTo( player ); + weapon.useTrigger SetHintLowPriority( true ); + weapon.useTrigger SetCursorHint( "HINT_NOICON" ); + weapon.useTrigger SetHintString( level.remoteWeapons[ weapon.remoteName ].hintString ); + + weapon.useTrigger SetTeamForTrigger( player.team ); + weapon.useTrigger.team = player.team; + + player ClientClaimTrigger( weapon.useTrigger ); + player.remoteControlTrigger = weapon.useTrigger; + player.activeRemoteControlTriggers[ player.activeRemoteControlTriggers.size ] = weapon.useTrigger; + weapon.useTrigger.ClaimedBy = player; + + weapon thread WatchWeaponDeath(); + weapon thread WatchOwnerDisconnect(); + weapon thread WatchRemoteTriggerUse(); + weapon thread WatchRemoteTriggerDisable(); +} + +function WatchWeaponDeath() +{ + weapon = self; + weapon.useTrigger endon( "death" ); + + weapon util::waittill_any( "death", "remote_weapon_end" ); + + if ( isdefined( weapon.remoteOwner ) ) + { + weapon.remoteOwner RemoveAndAssignNewRemoteControlTrigger( weapon.useTrigger ); + } + + weapon.useTrigger delete(); +} + +function WatchOwnerDisconnect() +{ + weapon = self; + weapon endon( "remote_weapon_end" ); + weapon endon( "remote_weapon_shutdown" ); + + if( isdefined( weapon.useTrigger ) ) + weapon.useTrigger endon( "death" ); + + weapon.remoteOwner util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); + + EndRemoteControlWeaponUse( false ); + + if( isdefined( weapon ) && isdefined( weapon.useTrigger ) ) + weapon.useTrigger delete(); +} + +function WatchRemoteTriggerDisable() +{ + weapon = self; + weapon endon( "remote_weapon_end" ); + weapon endon( "remote_weapon_shutdown" ); + weapon.useTrigger endon( "death" ); + + while( true ) + { + weapon.useTrigger TriggerEnable( !( weapon.remoteOwner IsWallRunning() ) ); + wait( 0.1 ); + } +} + +function AllowRemoteStart() +{ + player = self; + if( player useButtonPressed() && !player.throwingGrenade && !player meleeButtonPressed() && !player util::isUsingRemote() && !( isdefined( player.carryObject ) && ( isdefined( player.carryObject.disallowRemoteControl ) && player.carryObject.disallowRemoteControl ) ) ) + { + return true; + } + else + { + return false; + } +} + +function WatchRemoteTriggerUse() +{ + weapon = self; + weapon endon( "death" ); + weapon endon( "remote_weapon_end" ); + + if( weapon.remoteOwner util::is_bot() ) + { + return; + } + + while( true ) + { + weapon.useTrigger waittill( "trigger", player ); + + if( weapon.remoteOwner IsUsingOffhand() || weapon.remoteOwner IsWallRunning() ) + { + continue; + } + + if( isdefined( weapon.hackertrigger ) && isdefined( weapon.hackertrigger.progressbar ) ) + { + if( weapon.remoteName == "killstreak_remote_turret" ) + { + weapon.remoteOwner iPrintLnBold( &"KILLSTREAK_AUTO_TURRET_NOT_AVAILABLE" ); + } + + continue; + } + if( weapon.remoteOwner AllowRemoteStart() ) + { + UseRemoteControlWeapon(); + } + } +} + +function UseRemoteControlWeapon( allowManualDeactivation = true, always_allow_ride = false ) +{ + self endon( "death" ); + + weapon = self; + + assert( isdefined( weapon.remoteOwner ) ); + + weapon.control_initiated = true; + weapon.endRemoteControlWeapon = false; + + weapon.remoteOwner endon( "disconnect" ); + weapon.remoteOwner endon( "joined_team" ); + + weapon.remoteOwner DisableOffhandWeapons(); + weapon.remoteOwner DisableWeaponCycling(); + + weapon.remoteOwner.dofutz = false; + + if( !isdefined( weapon.disableRemoteWeaponSwitch ) ) + { + remoteWeapon = GetWeapon( "killstreak_remote" ); + weapon.remoteOwner giveWeapon( remoteWeapon ); + weapon.remoteOwner switchToWeapon( remoteWeapon ); + + if ( always_allow_ride ) + { + weapon.remoteOwner util::waittill_any( "weapon_change", "death" ); + } + else + { + weapon.remoteOwner waittill( "weapon_change", newWeapon ); + } + } + + if( isdefined( newweapon ) ) + { + if( newweapon != remoteWeapon ) + { + weapon.remoteOwner killstreaks::clear_using_remote( true, true ); + return; + } + } + + weapon.remoteOwner thread killstreaks::watch_for_remove_remote_weapon(); + + weapon.remoteOwner util::setUsingRemote( weapon.remoteName ); + weapon.remoteOwner util::freeze_player_controls( true ); + + result = weapon.remoteOwner killstreaks::init_ride_killstreak( weapon.remoteName, always_allow_ride ); + + if( result != "success" ) + { + if( result != "disconnect" ) + { + weapon.remoteOwner killstreaks::clear_using_remote(); + weapon thread ResetControlInitiatedUponOwnerRespawn(); + } + } + else + { + weapon.controlled = true; + weapon.killCamEnt = self; + weapon notify("remote_start"); // shuts down the AI scripts for this weapon + + if ( allowManualDeactivation ) + { + weapon thread watchRemoteControlDeactivate(); + } + + weapon.remoteOwner thread [[level.remoteWeapons[ weapon.remoteName ].useCallback]]( weapon ); + + if ( level.remoteWeapons[ weapon.remoteName ].hideCompassOnUse ) + { + weapon.remoteOwner killstreaks::hide_compass(); + } + } + + weapon.remoteOwner util::freeze_player_controls( false ); +} + +function ResetControlInitiatedUponOwnerRespawn() +{ + self endon( "death" ); + + self.remoteOwner waittill( "spawned" ); + + self.control_initiated = false; +} + +function WatchRemoteControlDeactivate() +{ + self notify("WatchRemoteControlDeactivate_remoteWeapons"); + self endon ("WatchRemoteControlDeactivate_remoteWeapons"); + weapon = self; + weapon endon( "remote_weapon_end" ); + weapon endon( "death" ); + weapon.remoteOwner endon( "disconnect" ); + + while( true ) + { + timeUsed = 0; + while( weapon.remoteOwner UseButtonPressed() ) + { + timeUsed += 0.05; + if ( timeUsed > 0.25 ) + { + weapon thread EndRemoteControlWeaponUse( true ); + return; + } + {wait(.05);}; + } + {wait(.05);}; + } +} + +function EndRemoteControlWeaponUse( exitRequestedByOwner ) +{ + weapon = self; + + if( !isdefined( weapon ) || ( isdefined( weapon.endRemoteControlWeapon ) && weapon.endRemoteControlWeapon ) ) + return; + + weapon.endRemoteControlWeapon = true; + + remote_controlled = ( isdefined( weapon.control_initiated ) && weapon.control_initiated ) || ( isdefined( weapon.controlled ) && weapon.controlled ); + + while ( isdefined( weapon ) && ( weapon.forceWaitRemoteControl === true ) && ( remote_controlled == false ) ) + { + remote_controlled = ( ( isdefined( weapon.control_initiated ) && weapon.control_initiated ) || ( isdefined( weapon.controlled ) && weapon.controlled ) ); + {wait(.05);}; + } + + if ( !isdefined( weapon ) ) + return; + + if( isdefined( weapon.remoteOwner ) && remote_controlled ) + { + + if( isdefined( weapon.remoteWeaponShutdownDelay ) ) //when changing teams or disconnect, we can skip this + { + wait( weapon.remoteWeaponShutdownDelay ); + } + + player = weapon.remoteOwner; + + if( player.dofutz === true ) + { + + player clientfield::set_to_player( "static_postfx", 1 ); + + wait 1; + + if( isdefined( player ) ) + { + player clientfield::set_to_player( "static_postfx", 0 ); + player.dofutz = false; + } + } + else if ( !exitRequestedByOwner && weapon.watch_remote_weapon_death === true && !IsAlive( weapon ) ) + { + wait ( isdefined( weapon.watch_remote_weapon_death_duration ) ? weapon.watch_remote_weapon_death_duration : 1.0 ); + } + + if( isdefined( player ) ) + { + player thread FadeToBlackAndBackIn(); + player waittill( "fade2black" ); // we the prev call to be blocking, in the same time we dont want to be left with black screen if smth external kills this thread. + if( remote_controlled ) + player unlink(); + player killstreaks::clear_using_remote( true ); // must come before the end use callback, its notify clears all the AI threads on the remote weapons + cleared_killstreak_delay = 1; + player EnableUsability(); // there are cases where the typical path to enable usability is not called, (because of some endon) + } + } + + if( isdefined( weapon ) && isdefined( weapon.remoteName ) ) + { + self [[ level.remoteWeapons[ weapon.remoteName ].endUseCallback]]( weapon, exitRequestedByOwner ); + } + + if( isdefined( weapon )) + { + weapon.killCamEnt = weapon; + + if( isdefined( weapon.remoteOwner ) ) + { + if ( remote_controlled ) + { + weapon.remoteOwner unlink(); + if( !( isdefined( cleared_killstreak_delay ) && cleared_killstreak_delay ) ) + weapon.remoteOwner killstreaks::reset_killstreak_delay_killcam(); + weapon.remoteOwner util::clientNotify( "nofutz" ); + } + + if( isdefined( level.gameEnded ) && level.gameEnded ) + { + weapon.remoteOwner util::freeze_player_controls( true ); + } + } + } + + if( isdefined( weapon ) ) + { + weapon.control_initiated = false; + weapon.controlled = false; + if( isdefined( weapon.remoteOwner ) ) + weapon.remoteOwner killstreaks::unhide_compass(); + + if( !exitRequestedByOwner || ( isdefined( weapon.one_remote_use ) && weapon.one_remote_use ) ) + weapon notify( "remote_weapon_end" ); + } +} + +function FadeToBlackAndBackIn() +{ + self endon( "disconnect" ); + // self endon( "death" ); // never end on death as it may never fade back in + + lui::screen_fade_out( 0.1 ); + + self qrdrone::destroyHud(); + + {wait(.05);}; + + self notify( "fade2black" ); + + lui::screen_fade_in( .2 ); +} + +function stunStaticFX( duration ) +{ + self endon( "remove_remote_weapon" ); + self.fullscreen_static.alpha = 0.65; + wait ( duration - 0.5 ); + time = duration - 0.5; + while ( time < duration ) + { + {wait(.05);}; + time += 0.05; + self.fullscreen_static.alpha -= 0.05; + } + self.fullscreen_static.alpha = 0.15; +} + +function destroyRemoteHUD() +{ + self UseServerVisionset( false ); + self SetInfraredVision( false ); + if ( isdefined(self.fullscreen_static) ) + { + self.fullscreen_static destroy(); + } + + if ( isdefined( self.hud_prompt_exit )) + { + self.hud_prompt_exit destroy(); + } +} + +function set_static( val ) +{ + owner = self.owner; + if( isdefined( owner ) && owner.usingvehicle && isdefined( owner.viewlockedentity ) && ( owner.viewlockedentity == self ) ) + { + owner clientfield::set_to_player( "static_postfx", val ); + } +} + +function do_static_fx() +{ + self endon( "death" ); + self set_static( 1 ); + wait 2; + self set_static( 0 ); + self notify( "static_fx_done" ); +} \ No newline at end of file diff --git a/mp/killstreaks/_remote_weapons.gsh b/mp/killstreaks/_remote_weapons.gsh new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mp/killstreaks/_remote_weapons.gsh @@ -0,0 +1 @@ + diff --git a/mp/killstreaks/_remotemissile.csc b/mp/killstreaks/_remotemissile.csc new file mode 100644 index 0000000..7a97348 --- /dev/null +++ b/mp/killstreaks/_remotemissile.csc @@ -0,0 +1,140 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + + + + +#using scripts\mp\_util; + + + + +#namespace remotemissile; + +function autoexec __init__sytem__() { system::register("remotemissile",&__init__,undefined,undefined); } + +function __init__() +{ + clientfield::register( "missile", "remote_missile_bomblet_fired", 1, 1, "int",&bomblets_deployed, !true, !true ); + clientfield::register( "missile", "remote_missile_fired", 1, 2, "int",&missile_fired, !true, !true ); +} + +function missile_fired( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if( newVal == ( 1 ) ) + { + player = getlocalplayer( localClientNum ); + owner = self GetOwner( localClientNum ); + + clientObjID = util::getNextObjID( localClientNum ); + + objective_add( localClientNum, clientObjID, "invisible", self.origin, self.team, owner ); + Objective_OnEntity( localClientNum, clientObjID, self, true, false, true ); + self.hellfireObjId = clientObjID; + self thread destruction_watcher( localClientNum, clientObjID ); + + + objective_state( localClientNum, clientObjID, "active" ); + if( player hasPerk( localClientNum, "specialty_showenemyequipment" ) || self.team == player.team ) + { + objective_SetIcon( localClientNum, clientObjID, "remotemissile_target" ); + objective_SetIconSize( localClientNum, clientObjID, 50 ); + } + + self thread hud_update( localClientNum ); + } + else if ( newVal == ( 2 ) ) + { + if( isdefined( self.hellfireObjId ) ) + { + self notify( "hellfire_detonated" ); + objective_delete( localClientNum, self.hellfireObjId ); + util::releaseObjID( localClientNum, self.hellfireObjId ); + } + } + else + self notify( "cleanup_objectives" ); + + + ammo_ui_data_model = GetUIModel( GetUIModelForController( localClientNum ), "vehicle.ammo" ); + if ( isdefined( ammo_ui_data_model ) ) + SetUIModelValue( ammo_ui_data_model, 1 ); +} + +function bomblets_deployed( localClientNum, oldVal, newVal, bNewEnt, bInitialSnap, fieldName, bWasTimeJump ) +{ + if ( bNewEnt && oldVal == newVal ) + { + return; + } + + if( newVal == ( 1 ) ) + { + player = getlocalplayer( localClientNum ); + owner = self GetOwner( localClientNum ); + + clientObjID = util::getNextObjID( localClientNum ); + + objective_add( localClientNum, clientObjID, "invisible", self.origin, self.team, owner ); + Objective_OnEntity( localClientNum, clientObjID, self, true, false, true ); + self thread destruction_watcher( localClientNum, clientObjID ); + + + objective_state( localClientNum, clientObjID, "active" ); + if( player hasPerk( localClientNum, "specialty_showenemyequipment" ) || player.team == self.team ) + objective_SetIcon( localClientNum, clientObjID, "remotemissile_target" ); + } + else + self notify( "cleanup_objectives" ); + + ammo_ui_data_model = GetUIModel( GetUIModelForController( localClientNum ), "vehicle.ammo" ); + if ( isdefined( ammo_ui_data_model ) ) + SetUIModelValue( ammo_ui_data_model, 0 ); +} + +function destruction_watcher( localClientNum, clientObjID ) +{ + self util::waittill_any( "death", "entityshutdown", "cleanup_objectives" ); + wait( 0.1 ); + if( isdefined( clientObjID ) ) + { + objective_delete( localClientNum, clientObjID ); + util::releaseObjID( localClientNum, clientObjID ); + } +} + +function hud_update( localClientNum ) +{ + self endon( "entityshutdown" ); + self notify( "remote_missile_singeton"); + self endon( "remote_missile_singeton"); + missile = self; + + altitude_ui_data_model = GetUIModel( GetUIModelForController( localClientNum ), "vehicle.altitude" ); + speed_ui_data_model = GetUIModel( GetUIModelForController( localClientNum ), "vehicle.speed" ); + + if( !isdefined( altitude_ui_data_model ) || !isdefined( speed_ui_data_model ) ) + return; + + prev_z = missile.origin[2]; + + fps = 20.0; + delay = 1.0 / fps; + + while( 1 ) + { + cur_z = missile.origin[2]; + SetUIModelValue( altitude_ui_data_model, cur_z ); + + dist = ( prev_z - cur_z ) * fps; + val = dist / 17.6; + SetUIModelValue( speed_ui_data_model, val ); + + prev_z = cur_z; + + wait ( delay ); + } +} diff --git a/mp/killstreaks/_remotemissile.gsc b/mp/killstreaks/_remotemissile.gsc new file mode 100644 index 0000000..0cd059b --- /dev/null +++ b/mp/killstreaks/_remotemissile.gsc @@ -0,0 +1,1063 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\hud_shared; +#using scripts\shared\math_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\util_shared; + +#using scripts\mp\_util; +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\shared\visionset_mgr_shared; + + + +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; + + + + + + + + + + + + + +//#precache( "material","tow_filter_overlay_no_signal"); +#precache( "material", "hud_remote_missile_target" ); +#precache( "string", "KILLSTREAK_EARNED_REMOTE_MISSILE" ); +#precache( "string", "KILLSTREAK_REMOTE_MISSILE_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_REMOTE_MISSILE_INBOUND" ); +#precache( "string", "KILLSTREAK_REMOTE_MISSILE_HACKED" ); +#precache( "eventstring", "mpl_killstreak_cruisemissile" ); +#precache( "fx", "killstreaks/fx_predator_trigger" ); + +#namespace remotemissile; + + + +function init() +{ + level.rockets = []; + + killstreaks::register( "remote_missile", "remote_missile", "killstreak_remote_missile", "remote_missle_used",&tryUsePredatorMissile, true ); + killstreaks::register_alt_weapon( "remote_missile", "remote_missile_missile" ); + killstreaks::register_alt_weapon( "remote_missile", "remote_missile_bomblet" ); + killstreaks::register_strings( "remote_missile", &"KILLSTREAK_EARNED_REMOTE_MISSILE", &"KILLSTREAK_REMOTE_MISSILE_NOT_AVAILABLE", &"KILLSTREAK_REMOTE_MISSILE_INBOUND", undefined, &"KILLSTREAK_REMOTE_MISSILE_HACKED" ); + killstreaks::register_dialog( "remote_missile", "mpl_killstreak_cruisemissile", "remoteMissileDialogBundle", "remoteMissilePilotDialogBundle", "friendlyRemoteMissile", "enemyRemoteMissile", "enemyRemoteMissileMultiple", "friendlyRemoteMissileHacked", "enemyRemoteMissileHacked", "requestRemoteMissile" ); + killstreaks::set_team_kill_penalty_scale( "remote_missile", level.teamKillReducedPenalty ); + killstreaks::override_entity_camera_in_demo("remote_missile", true); + + clientfield::register( "missile", "remote_missile_bomblet_fired", 1, 1, "int" ); + clientfield::register( "missile", "remote_missile_fired", 1, 2, "int" ); + + level.missilesForSightTraces = []; + + level.missileRemoteDeployFX = "killstreaks/fx_predator_trigger"; + level.missileRemoteLaunchVert = 18000; + level.missileRemoteLaunchHorz = 7000; + level.missileRemoteLaunchTargetDist = 1500; + + visionset_mgr::register_info( "visionset", "remote_missile_visionset", 1, 110, 16, true, &visionset_mgr::ramp_in_out_thread_per_player, false ); +} + +function remote_missile_game_end_think( rocket, team, killstreak_id ) +{ + self endon( "Remotemissle_killstreak_done" ); + + level waittill( "game_ended" ); + + self thread player_missile_end( rocket, true, true, team, killstreak_id ); + + self notify( "Remotemissle_killstreak_done" ); +} + +function tryUsePredatorMissile( lifeId ) +{ + waterDepth = self depthofplayerinwater(); + + if( !self IsOnGround() || self util::isUsingRemote() || ( waterDepth > 2 ) || self killstreaks::is_killstreak_start_blocked() ) + { + self iPrintLnBold( &"KILLSTREAK_REMOTE_MISSILE_NOT_USABLE" ); + return false; + } + + team = self.team; + killstreak_id = self killstreakrules::killstreakStart( "remote_missile", team, false, true ); + if ( killstreak_id == -1 ) + { + return false; + } + + self.remoteMissilePilotIndex = killstreaks::get_random_pilot_index( "remote_missile" ); + + returnVar = _fire( lifeId, self, team, killstreak_id ); + + return returnVar; +} + + +function getBestSpawnPoint( remoteMissileSpawnPoints ) +{ + validEnemies = []; + + foreach ( spawnPoint in remoteMissileSpawnPoints ) + { + spawnPoint.validPlayers = []; + spawnPoint.spawnScore = 0; + } + + foreach ( player in level.players ) + { + if ( !isAlive( player ) ) + continue; + + if ( player.team == self.team ) + continue; + + if ( player.team == "spectator" ) + continue; + + bestDistance = 999999999; + bestSpawnPoint = undefined; + + foreach ( spawnPoint in remoteMissileSpawnPoints ) + { + //could add a filtering component here but i dont know what it would be. + spawnPoint.validPlayers[spawnPoint.validPlayers.size] = player; + + potentialBestDistance = Distance2DSquared( spawnPoint.targetent.origin, player.origin ); + + if ( potentialBestDistance <= bestDistance ) + { + bestDistance = potentialBestDistance; + bestSpawnpoint = spawnPoint; + } + } + + bestSpawnPoint.spawnScore += 2; + } + + bestSpawn = remoteMissileSpawnPoints[0]; + foreach ( spawnPoint in remoteMissileSpawnPoints ) + { + foreach ( player in spawnPoint.validPlayers ) + { + spawnPoint.spawnScore += 1; + + if ( bulletTracePassed( player.origin + (0,0,32), spawnPoint.origin, false, player ) ) + spawnPoint.spawnScore += 3; + + if ( spawnPoint.spawnScore > bestSpawn.spawnScore ) + { + bestSpawn = spawnPoint; + } + else if ( spawnPoint.spawnScore == bestSpawn.spawnScore ) // equal spawn weights so we toss a coin. + { + if ( math::cointoss() ) + bestSpawn = spawnPoint; + } + } + } + + return ( bestSpawn ); +} + +function drawLine( start, end, timeSlice, color ) +{ + /# + drawTime = int(timeSlice * 20); + for( time = 0; time < drawTime; time++ ) + { + line( start, end, color,false, 1 ); + {wait(.05);}; + } + #/ +} + +function _fire( lifeId, player, team, killstreak_id ) +{ + remoteMissileSpawnArray = getEntArray( "remoteMissileSpawn" , "targetname" ); + + foreach ( spawn in remoteMissileSpawnArray ) + { + if ( isdefined( spawn.target ) ) + spawn.targetEnt = getEnt( spawn.target, "targetname" ); + } + + if ( remoteMissileSpawnArray.size > 0 ) + remoteMissileSpawn = player getBestSpawnPoint( remoteMissileSpawnArray ); + else + remoteMissileSpawn = undefined; + + if ( isdefined( remoteMissileSpawn ) ) + { + startPos = remoteMissileSpawn.origin; + targetPos = remoteMissileSpawn.targetEnt.origin; + + vector = vectorNormalize( startPos - targetPos ); + startPos = ( vector * level.missileRemoteLaunchVert ) + targetPos; + } + else + { + upVector = (0, 0, level.missileRemoteLaunchVert ); + backDist = level.missileRemoteLaunchHorz; + targetDist = level.missileRemoteLaunchTargetDist; + + forward = AnglesToForward( player.angles ); + startpos = player.origin + upVector + forward * backDist * -1; + targetPos = player.origin + forward * targetDist; + } + + self util::setUsingRemote( "remote_missile" ); + self util::freeze_player_controls( true ); + player DisableWeaponCycling(); + + result = self killstreaks::init_ride_killstreak( "qrdrone" ); + + if ( result != "success" || level.gameended ) + { + if ( result != "disconnect" ) + { + player util::freeze_player_controls( false ); + player killstreaks::clear_using_remote(); + player EnableWeaponCycling(); + killstreakrules::killstreakStop( "remote_missile", team, killstreak_id ); + } + + return false; + } + + rocket = MagicBullet( GetWeapon( "remote_missile_missile" ), startpos, targetPos, player ); + rocket.forceOneMissile = true; + forceAngleVector = vectorNormalize( targetPos - startPos ); + rocket.angles = VectorToAngles( forceAngleVector ); + rocket.targetname = "remote_missile"; + + rocket killstreaks::configure_team( "remote_missile", killstreak_id, self, undefined, undefined, undefined ); + rocket killstreak_hacking::enable_hacking( "remote_missile", undefined, &HackedPostFunction ); + killstreak_detect::killstreakTargetSet( rocket ); + rocket.hackedHealthUpdateCallback = &hackedHealthUpdate; + rocket clientfield::set( "enemyvehicle", 1 ); + + rocket thread handleDamage(); + + player LinkToMissile( rocket, true, true ); + rocket.owner = player; + rocket.killcament = player; + player thread cleanupWaiter( rocket, player.team, killstreak_id ); + + visionset_mgr::activate( "visionset", "remote_missile_visionset", player, 0, 90000, 0 ); + player SetModelLodBias( (isdefined(level.remotemissile_lod_bias)?level.remotemissile_lod_bias:12) ); + self clientfield::set_to_player( "fog_bank_2", 1 ); + self clientfield::set( "operating_predator", 1 ); + self killstreaks::play_killstreak_start_dialog( "remote_missile", self.pers["team"], killstreak_id ); + + self AddWeaponStat( GetWeapon( "remote_missile" ), "used", 1 ); + + rocket thread setup_rockect_map_icon(); + rocket thread watch_missile_kill_z(); + rocket missile_sound_play( player ); + rocket thread missile_brake_timeout_watch(); + rocket thread missile_sound_impact( player, 3750 ); + player thread missile_sound_boost( rocket ); + player thread missile_deploy_watch( rocket ); + player thread remote_missile_game_end_think( rocket, player.team, killstreak_id ); + player thread watch_missile_death( rocket, player.team, killstreak_id ); + player thread sndWatchExplo(); + + rocket spawning::create_entity_enemy_influencer( "small_vehicle", rocket.team ); + + player util::freeze_player_controls( false ); + + player waittill( "Remotemissle_killstreak_done" ); + + return true; +} + +function hackedHealthUpdate( hacker ) +{ + // no need for health update +} + +function HackedPostFunction( hacker ) +{ + rocket = self; + hacker missile_deploy( rocket, true ); +} + +function setup_rockect_map_icon() +{ + self endon("death"); + wait( 0.1 ); + self clientfield::set( "remote_missile_fired", ( 1 ) ); +} + +function watch_missile_kill_z() +{ + if ( !isdefined( level.remotemissile_kill_z ) ) + return; + + rocket = self; + kill_z = level.remotemissile_kill_z; + + rocket endon( "Remotemissle_killstreak_done" ); + rocket endon( "death" ); + + while ( rocket.origin[2] > kill_z ) + { + wait 0.1; // need to see explosion, so don't wait too long + } + + rocket Detonate(); +} + +function watch_missile_death( rocket, team, killstreak_id ) +{ + self endon( "Remotemissle_killstreak_done" ); + + rocket waittill( "death" ); + + self thread player_missile_end( rocket, true, true, team, killstreak_id ); + + self thread remotemissile_bda_dialog(); + + self notify( "Remotemissle_killstreak_done" ); + +} + +function player_missile_end( rocket, performPlayerKillstreakEnd, unlink, team, killstreak_id ) +{ + self notify( "player_missile_end_singleton" ); + self endon ( "player_missile_end_singleton" ); + + if( isalive( rocket ) ) + { + rocket spawning::remove_influencers(); + rocket clientfield::set( "remote_missile_fired", 0 ); + rocket Delete(); + } + + self notify( "snd1stPersonExplo" ); + + if( isdefined( self ) ) + { + self thread destroy_missile_hud(); + + //Only do this if the killstreak is ending normally + if( ( isdefined( performPlayerKillstreakEnd ) && performPlayerKillstreakEnd ) ) + { + self PlayRumbleOnEntity( "grenade_rumble" ); + + if ( level.gameended == false ) + { + self SendKillstreakDamageEvent( 600 ); + //self thread hud::fade_to_black_for_x_sec( 0, 0.25, 0.1, 0.25 ); + } + + if( isdefined ( rocket ) ) + { + rocket hide(); + } + } + + self clientfield::set( "operating_predator", 0 ); + self clientfield::set_to_player( "fog_bank_2", 0 ); + visionset_mgr::deactivate( "visionset", "remote_missile_visionset", self ); + self SetModelLodBias( 0 ); + + if( unlink ) + self UnlinkFromMissile(); + + self notify( "remotemissile_done" ); + self util::freeze_player_controls( false ); + self killstreaks::clear_using_remote(); + self EnableWeaponCycling(); + + killstreakrules::killstreakStop( "remote_missile", team, killstreak_id ); + } +} + +function missile_brake_timeout_watch() +{ + rocket = self; + player = rocket.owner; + + self endon( "death" ); + + self waittill( "missile_brake" ); + + rocket PlaySound( "wpn_remote_missile_brake_npc" ); + player playlocalsound( "wpn_remote_missile_brake_plr" ); + + wait( 1.5 ); + + if( isdefined( self ) ) + { + self SetMissileBrake( false ); + } +} + +function stopOndeath( snd ) +{ + self waittill( "death" ); + if( isdefined( snd ) ) + snd delete(); +} + +function cleanupWaiter( rocket, team, killstreak_id ) +{ + self endon( "Remotemissle_killstreak_done" ); + + self util::waittill_any( "joined_team", "joined_spectators", "disconnect" ); + + self thread player_missile_end( rocket, false, false, team, killstreak_id ); + + self notify( "Remotemissle_killstreak_done" ); +} + +function handleDamage() +{ + self endon ( "death" ); + self endon ( "deleted" ); + + self setCanDamage( true ); + self.health = 99999; + for ( ;; ) + { + self waittill( "damage", damage, attacker, direction_vec, point, meansOfDeath, tagName, modelName, partName, weapon ); + + if ( isdefined ( attacker ) && isdefined( self.owner ) ) + { + if ( self.owner util::IsEnemyPlayer( attacker ) ) + { + challenges::destroyedAircraft( attacker, weapon, true ); + attacker challenges::addFlySwatterStat( weapon, self ); + scoreevents::processScoreEvent( "destroyed_remote_missile", attacker, self.owner, weapon ); + attacker AddWeaponStat( weapon, "destroyed_controlled_killstreak", 1 ); + } + else + { + //Destroyed Friendly Killstreak + } + self.owner SendKillstreakDamageEvent( int(damage) ); + } + self spawning::remove_influencers(); + self Detonate(); + } +} + +function staticEffect( duration ) +{ + self endon ( "disconnect" ); + + staticBG = newClientHudElem( self ); + staticBG.horzAlign = "fullscreen"; + staticBG.vertAlign = "fullscreen"; + staticBG setShader( "white", 640, 480 ); + staticBG.archive = true; + staticBG.sort = 10; + staticBG.immunetodemogamehudsettings = true; + + static = newClientHudElem( self ); + static.horzAlign = "fullscreen"; + static.vertAlign = "fullscreen"; + //static setShader( "tow_filter_overlay_no_signal", 640, 480 ); + static.archive = true; + static.sort = 20; + static.immunetodemogamehudsettings = true; + + self clientfield::set( "remote_killstreak_static", 1 ); + + wait ( duration ); + + self clientfield::set( "remote_killstreak_static", 0 ); + static destroy(); + staticBG destroy(); +} + +function Rocket_CleanupOnDeath() +{ + entityNumber = self getEntityNumber(); + level.rockets[ entityNumber ] = self; + self waittill( "death" ); + + level.rockets[ entityNumber ] = undefined; +} + +function missile_sound_play( player ) +{ + self.snd_first = spawn( "script_model", self.origin ); + self.snd_first SetModel( "tag_origin" ); + self.snd_first LinkTo( self ); + self.snd_first SetInvisibleToAll(); + self.snd_first SetVisibleToPlayer( player ); + self.snd_first PlayLoopSound( "wpn_remote_missile_loop_plr", .5 ); + + self thread stopOndeath( self.snd_first ); + + self.snd_third = spawn( "script_model", self.origin ); + self.snd_third SetModel( "tag_origin" ); + self.snd_third LinkTo( self ); + self.snd_third SetVisibleToAll(); + self.snd_third SetInvisibleToPlayer( player ); + self.snd_third PlayLoopSound( "wpn_remote_missile_loop_npc", .2 ); + + self thread stopOndeath( self.snd_third ); +} + +function missile_sound_boost( rocket ) +{ + self endon( "remotemissile_done" ); + self endon( "joined_team" ); + self endon( "joined_spectators" ); + self endon( "disconnect" ); + + self waittill( "missile_boost" ); + rocket PlaySound( "wpn_remote_missile_fire_boost_npc" ); + rocket.snd_third PlayLoopSound( "wpn_remote_missile_boost_npc" ); + + self playlocalsound( "wpn_remote_missile_fire_boost_plr" ); + rocket.snd_first PlayLoopSound( "wpn_remote_missile_boost_plr" ); + self PlayRumbleOnEntity( "sniper_fire" ); + + if ( rocket.origin[2] - self.origin[2] > 4000 ) + { + rocket notify( "stop_impact_sound" ); + rocket thread missile_sound_impact( self, 6250 ); + } +} + +function missile_sound_impact( player, distance ) +{ + self endon( "death" ); + self endon( "stop_impact_sound" ); + player endon( "disconnect" ); + player endon( "remotemissile_done" ); + player endon( "joined_team" ); + player endon( "joined_spectators" ); + + for ( ;; ) + { + if ( self.origin[2] - player.origin[2] < distance ) + { + self PlaySound( "wpn_remote_missile_inc" ); + return; + } + {wait(.05);}; + } +} + +function sndWatchExplo() +{ + self endon( "Remotemissle_killstreak_done" ); + self endon( "remotemissile_done" ); + self endon( "joined_team" ); + self endon( "joined_spectators" ); + self endon( "disconnect" ); + self endon( "bomblets_deployed" ); + + self waittill( "snd1stPersonExplo" ); + self playlocalsound( "wpn_remote_missile_explode_plr" ); +} + +function missile_sound_deploy_bomblets() +{ + self.snd_first PlayLoopSound( "wpn_remote_missile_loop_plr", .5 ); +} + +function getValidTargets( rocket, trace, max_targets ) +{ + pixbeginevent("remotemissile_getVTs_header"); + + targets = []; + + forward = AnglesToForward ( rocket.angles ); + + rocketZ = rocket.origin[2]; + mapCenterZ = level.mapCenter[2]; + diff = mapCenterZ - rocketZ; + + ratio = diff / forward[2]; + + aimTarget = rocket.origin + forward * ratio; + rocket.aimTarget = aimTarget; + +// /# +// circle( rocket.aimTarget, REMOTE_MISSILE_TARGETING_RADIUS, (0,1,0), true, true, 2000 ); +// #/ + + pixendevent(); + + pixbeginevent("remotemissile_getVTs_enemies"); + + enemies = self GetEnemies(); + + foreach( player in enemies ) + { + if( !IsPlayer( player ) ) + { + continue; + } + + if ( player.ignoreme === true ) + { + continue; + } + + if ( Distance2DSquared( player.origin, aimTarget) < 600 * 600 && !player HasPerk( "specialty_nokillstreakreticle" ) ) + { + if ( trace ) + { + if ( BulletTracePassed( player.origin + (0,0,60), player.origin + (0,0,180), false, player) ) + { + targets[targets.size] = player; + } + } + else + { + targets[targets.size] = player; + } + if( targets.size >= max_targets ) + return targets; + } + } + + dogs = GetEntArray( "attack_dog", "targetname" ); + + foreach( dog in dogs ) + { + if ( dog.team != self.team && Distance2DSquared( dog.origin, aimTarget) < 600 * 600 ) + { + if ( trace ) + { + if ( BulletTracePassed( dog.origin + (0,0,60), dog.origin + (0,0,180), false, dog) ) + { + targets[targets.size] = dog; + } + } + else + { + targets[targets.size] = dog; + } + if( targets.size >= max_targets ) + return targets; + } + } + + tanks = GetEntArray( "talon", "targetname" ); + + foreach( tank in tanks ) + { + if ( tank.team != self.team && Distance2DSquared( tank.origin, aimTarget) < 600 * 600 ) + { + if ( trace ) + { + if ( BulletTracePassed( tank.origin + (0,0,60), tank.origin + (0,0,180), false, tank) ) + { + targets[targets.size] = tank; + } + } + else + { + targets[targets.size] = tank; + } + if( targets.size >= max_targets ) + return targets; + } + } + + turrets = GetEntArray( "auto_turret", "classname" ); + foreach( turret in turrets ) + { + if ( turret.team != self.team && Distance2DSquared( turret.origin, aimTarget) < 600 * 600 ) + { + if ( trace ) + { + if ( BulletTracePassed( turret.origin + (0,0,60), turret.origin + (0,0,180), false, turret) ) + { + targets[targets.size] = turret; + } + } + else + { + targets[targets.size] = turret; + } + if( targets.size >= max_targets ) + return targets; + } + } + + pixendevent(); + + return targets; +} + +function create_missile_hud( rocket ) +{ + self.missile_target_icons = []; + + foreach (player in level.players) + { + if( player == self ) + continue; + + if (level.teamBased && player.team == self.team) + continue; + + index = player.clientId; + self.missile_target_icons[index] = newClientHudElem( self ); + self.missile_target_icons[index].x = 0; + self.missile_target_icons[index].y = 0; + self.missile_target_icons[index].z = 0; + self.missile_target_icons[index].alpha = 0; + self.missile_target_icons[index].archived = true; + self.missile_target_icons[index] setShader( "hud_remote_missile_target", 175, 175 ); + self.missile_target_icons[index] setWaypoint( false ); + self.missile_target_icons[index].hidewheninmenu = true; + self.missile_target_icons[index].immunetodemogamehudsettings = true; + } + + for(i=0; i<3; i++ ) + { + self.missile_target_other[i] = newClientHudElem( self ); + self.missile_target_other[i].x = 0; + self.missile_target_other[i].y = 0; + self.missile_target_other[i].z = 0; + self.missile_target_other[i].alpha = 0; + self.missile_target_other[i].archived = true; + self.missile_target_other[i] setShader( "hud_remote_missile_target", 175, 175 ); + self.missile_target_other[i] setWaypoint( false ); + self.missile_target_other[i].hidewheninmenu = true; + self.missile_target_other[i].immunetodemogamehudsettings = true; + } + rocket.iconIndexOther = 0; + + self thread targeting_hud_think( rocket ); +} + +function destroy_missile_hud() +{ + if (isdefined( self.missile_target_icons) ) + { + foreach (player in level.players) + { + if( player == self ) + { + continue; + } + + index = player.clientId; + if (isdefined(self.missile_target_icons[index])) + { + self.missile_target_icons[index] Destroy(); + } + } + } + if (isdefined( self.missile_target_other) ) + { + for( i=0; i<3; i++) + { + if( isdefined( self.missile_target_other[i] ) ) + self.missile_target_other[i] Destroy(); + } + } +} + +function targeting_hud_think( rocket ) +{ + self endon( "disconnect" ); + self endon("remotemissile_done"); + rocket endon("death"); + level endon ( "game_ended" ); + + targets = self getValidTargets( rocket, true, 6 ); + framesSinceTargetScan = 0; + + while( true ) + { + foreach (icon in self.missile_target_icons) + { + icon.alpha = 0; + } + + framesSinceTargetScan++; + + if ( framesSinceTargetScan > 5 ) + { + targets = self getValidTargets( rocket, true, 6 ); + framesSinceTargetScan = 0; + } + + if (targets.size > 0) + { + foreach (target in targets) + { + if ( isdefined( target ) == false ) + continue; + + if ( IsPlayer( target ) ) + { + if ( isalive( target ) ) + { + index = target.clientId; + assert( isdefined( index ) ); + + self.missile_target_icons[index].x = target.origin[0]; + self.missile_target_icons[index].y = target.origin[1]; + self.missile_target_icons[index].z = target.origin[2] + 47; + self.missile_target_icons[index].alpha = 1; + } + } + else + { + if( !isdefined(target.missileIconIndex)) + { + target.missileIconIndex = rocket.iconIndexOther; + rocket.iconIndexOther = (rocket.iconIndexOther + 1) % 3; + } + index = target.missileIconIndex; + self.missile_target_other[index].x = target.origin[0]; + self.missile_target_other[index].y = target.origin[1]; + self.missile_target_other[index].z = target.origin[2]; + self.missile_target_other[index].alpha = 1; + } + } + } + + wait (0.1); + } +} + +function missile_deploy_watch( rocket ) +{ + self endon( "disconnect" ); + self endon("remotemissile_done"); + rocket endon("remotemissile_bomblets_launched"); + rocket endon("death"); + level endon ( "game_ended" ); + + wait ( 0.25 ); + + self thread create_missile_hud( rocket ); + + while( true ) + { + if ( self attackbuttonpressed() ) + { + self thread missile_deploy( rocket, false ); + } + else + { + {wait(.05);}; + } + } +} + +function missile_deploy( rocket, hacked ) +{ + rocket notify ("remotemissile_bomblets_launched"); + waitFrames = 2; + explosionRadius = 0; + targets = self getValidTargets( rocket, false, 6 ); + if( targets.size > 0 ) + { + foreach( target in targets ) + { + self thread fire_bomblet( rocket, explosionRadius, target, waitFrames ); + waitFrames++; + } + } + + //bomblet = MagicBullet( GetWeapon( "remote_missile_bomblet" ), rocket.origin, rocket.origin + AnglesToForward ( rocket.angles ) * 1000, self); + //setup_bomblet( bomblet ); + + if( rocket.origin[2] - self.origin[2] > 4000 ) + { + //bomblet thread missile_sound_impact( self, 8000 ); + rocket notify( "stop_impact_sound" ); + } + if( hacked == true ) + { + rocket.originalOwner thread hud::fade_to_black_for_x_sec( 0, 0.15, 0, 0, "white" ); + self notify("remotemissile_done"); + } + + rocket clientfield::set( "remote_missile_fired", ( 2 ) ); + for( i = targets.size; i < 6; i++ ) + { + self thread fire_random_bomblet( rocket, explosionRadius, i % 6, waitFrames ); + waitFrames++; + } + + playfx( level.missileRemoteDeployFX, rocket.origin, anglestoForward( rocket.angles ) ); + self playLocalSound("mpl_rc_exp"); + self PlayRumbleOnEntity( "sniper_fire" ); + Earthquake( 0.2, 0.2, rocket.origin, 200 ); + + //still rocket for viewing the bomblets + rocket Hide(); + rocket setMissileCoasting( true ); + if ( hacked == false ) + { + self thread hud::fade_to_black_for_x_sec( 0, 0.15, 0, 0, "white" ); + } + + rocket missile_sound_deploy_bomblets(); + self thread bomblet_camera_waiter( rocket ); + self notify( "bomblets_deployed" ); + + if ( hacked == true ) + { + rocket notify( "death" ); + } + + return; +} + +function bomblet_camera_waiter( rocket ) +{ + self endon( "disconnect" ); + self endon("remotemissile_done"); + rocket endon("death"); + level endon ( "game_ended" ); + + delay = GetDvarFloat( "scr_rmbomblet_camera_delaytime", 1.0 ); + + self waittill( "bomblet_exploded" ); + + wait( delay ); + + rocket notify( "death" ); + self notify("remotemissile_done"); +} + +function setup_bomblet_map_icon() +{ + self endon("death"); + wait( 0.1 ); + self clientfield::set( "remote_missile_bomblet_fired", ( 1 ) ); + self clientfield::set( "enemyvehicle", 1 ); +} + +function setup_bomblet( bomb ) +{ + bomb.team = self.team; + + bomb setTeam( self.team ); + + //Send over for the compass icon + bomb thread setup_bomblet_map_icon(); + + bomb.killcamEnt = self; + + bomb thread bomblet_explostion_waiter( self ); +} + +function fire_bomblet( rocket, explosionRadius, target, waitFrames ) +{ + origin = rocket.origin; + + targetOrigin = target.origin + (0,0,50); + + wait( waitFrames * 0.05 ); + + if( isdefined( rocket ) ) + origin = rocket.origin; + + bomb = MagicBullet( GetWeapon( "remote_missile_bomblet" ), origin, targetOrigin, self, target, (0,0,30) ); + + setup_bomblet( bomb ); +} + +function fire_random_bomblet( rocket, explosionRadius, quadrant, waitFrames ) +{ + origin = rocket.origin; + angles = rocket.angles; + owner = rocket.owner; + aimTarget = rocket.aimtarget; + + wait ( waitFrames * 0.05 ); + + angle = randomIntRange( 10 + ( 60 * quadrant), 50 + ( 60 * quadrant ) ); + radius = randomIntRange( 200, 600 + 100 ); + x = min( radius, 600 - 50 ) * Cos( angle ); + y = min( radius, 600 - 50 ) * Sin( angle ); + + bomb = MagicBullet( GetWeapon( "remote_missile_bomblet" ), origin, aimtarget + ( x, y, 0 ), self); + + setup_bomblet( bomb ); +} + +function cleanup_bombs( bomb ) +{ + player = self; + bomb endon( "death" ); + + player util::waittill_any( "joined_team", "joined_spectators", "disconnect" ); + + if( isdefined( bomb ) ) + { + bomb clientfield::set( "remote_missile_bomblet_fired", 0 ); + bomb delete(); + } +} + +function bomblet_explostion_waiter( player ) +{ + player thread cleanup_bombs( self ); + + player endon( "disconnect" ); + player endon( "remotemissile_done" ); + player endon( "death" ); + level endon ( "game_ended" ); + + self waittill( "death" ); + + player notify( "bomblet_exploded" ); +} + +function remotemissile_bda_dialog() +{ + if ( isdefined( self.remotemissileBda ) ) + { + if (self.remotemissileBda === 1) + { + bdaDialog = "kill1"; + } + else if (self.remotemissileBda === 2) + { + bdaDialog = "kill2"; + } + else if (self.remotemissileBda === 3) + { + bdaDialog = "kill3"; + } + else if (isdefined( self.remotemissileBda ) && self.remotemissileBda > 3) + { + bdaDialog = "killMultiple"; + } + + self killstreaks::play_pilot_dialog( bdaDialog, "remote_missile", undefined, self.remoteMissilePilotIndex ); + + self globallogic_audio::play_taacom_dialog( "confirmHit" ); + + } + else + { + killstreaks::play_pilot_dialog( "killNone", "remote_missile", undefined, self.remoteMissilePilotIndex ); + globallogic_audio::play_taacom_dialog( "confirmMiss" ); + } + + self.remotemissileBda = undefined; +} diff --git a/mp/killstreaks/_satellite.gsc b/mp/killstreaks/_satellite.gsc new file mode 100644 index 0000000..cd1b61b --- /dev/null +++ b/mp/killstreaks/_satellite.gsc @@ -0,0 +1,372 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\weapons\_heatseekingmissile; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\math_shared; +#using scripts\shared\killstreaks_shared; + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\teams\_teams; +#using scripts\mp\_util; + + + + + + +#namespace satellite; + + + + +#precache( "string", "mpl_killstreak_satellite" ); +#precache( "string", "KILLSTREAK_EARNED_SATELLITE" ); +#precache( "string", "KILLSTREAK_SATELLITE_INBOUND" ); +#precache( "string", "KILLSTREAK_DESTROYED_SATELLITE" ); +#precache( "string", "KILLSTREAK_SATELLITE_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_SATELLITE_HACKED" ); + +function init() +{ + if ( level.teamBased ) + { + foreach( team in level.teams ) + { + level.activeSatellites[ team ] = 0; + } + } + else + { + level.activeSatellites = []; + } + + level.activePlayerSatellites = []; + + if ( tweakables::getTweakableValue( "killstreak", "allowradardirection" ) ) + { + killstreaks::register( "satellite", "satellite", "killstreak_satellite", "uav_used", &ActivateSatellite ); + killstreaks::register_strings( "satellite", &"KILLSTREAK_EARNED_SATELLITE", &"KILLSTREAK_SATELLITE_NOT_AVAILABLE", &"KILLSTREAK_SATELLITE_INBOUND", undefined, &"KILLSTREAK_SATELLITE_HACKED" ); + killstreaks::register_dialog( "satellite", "mpl_killstreak_satellite", "satelliteDialogBundle", undefined, "friendlySatellite", "enemySatellite", "enemySatelliteMultiple", "friendlySatelliteHacked", "enemySatelliteHacked", "requestSatellite", "threatSatellite" ); + } + + callback::on_connect( &OnPlayerConnect ); + callback::on_spawned( &OnPlayerSpawned ); + + level thread SatelliteTracker(); +} + +function OnPlayerConnect() +{ + self.entnum = self getEntityNumber(); + + if ( !level.teamBased ) + { + level.activeSatellites[ self.entnum ] = 0; + } + + level.activePlayerSatellites[ self.entnum ] = 0; // needed for satellite-related kill scores +} + +function OnPlayerSpawned( local_client_num ) +{ + if ( !level.teambased ) + { + UpdatePlayerSatelliteForDM( self ); + } +} + +function ActivateSatellite() +{ + if( self killstreakrules::isKillstreakAllowed( "satellite", self.team ) == false ) + { + return false; + } + + killstreak_id = self killstreakrules::killstreakStart( "satellite", self.team ); + if( killstreak_id == -1 ) + { + return false; + } + + minFlyHeight = int( airsupport::getMinimumFlyHeight() ); + zOffset = minFlyHeight + ( 5500 ); + + // pick a random start point from map center + travelAngle = RandomFloatRange( (isdefined(level.satellite_spawn_from_angle_min)?level.satellite_spawn_from_angle_min:( 90.0 )), (isdefined(level.satellite_spawn_from_angle_max)?level.satellite_spawn_from_angle_max:( 180.0 )) ); + travelRadius = airsupport::GetMaxMapWidth() * ( 1.5 ); + xOffset = sin( travelAngle ) * travelRadius; + yOffset = cos( travelAngle ) * travelRadius; + + satellite = spawn( "script_model", airsupport::GetMapCenter() + ( xOffset, yOffset, zOffset )); + satellite setModel( "veh_t7_drone_srv_blimp" ); + satellite SetScale( ( 1 ) ); + + satellite.killstreak_id = killstreak_id; + satellite.owner = self; + satellite.ownerEntNum = self GetEntityNumber(); + satellite.team = self.team; + satellite setTeam( self.team ); + satellite setOwner( self ); + satellite killstreaks::configure_team( "satellite", killstreak_id, self, undefined, undefined, &ConfigureTeamPost ); + satellite killstreak_hacking::enable_hacking( "satellite", &HackedPreFunction, undefined ); + satellite.targetname = "satellite"; + satellite.maxhealth = ( 700 ); + satellite.lowhealth = ( ( 700 ) * 0.5 ); + satellite.health = 99999; + satellite.leaving = false; + + satellite SetCanDamage( true ); + satellite thread killstreaks::MonitorDamage( "satellite", satellite.maxhealth, &DestroySatellite, satellite.lowhealth, &OnLowHealth, 0, undefined, false ); + satellite thread killstreaks::WaitTillEMP( &DestroySatelliteByEMP ); + // satellite.overrideVehicleDamage = &SatelliteDamageOverride; // satellite is not a vehicle right now + satellite.killstreakDamageModifier = &killstreakDamageModifier; + + satellite.rocketDamage = ( satellite.maxhealth / ( 3 ) ) + 1; + + /# + //Box( airsupport::GetMapCenter() + ( xOffset, yOffset, zOffset ), (-4, -4, 0 ), ( 4, 4, 5000 ), 0, ( 1, 0, 0 ), 0.6, false, 2000 ); + //Box( airsupport::GetMapCenter() + ( -xOffset, -yoffset, zOffset ), (-4, -4, 0 ), ( 4, 4, 5000 ), 0, ( 0, 1, 0 ), 0.6, false, 2000 ); + #/ + + satellite MoveTo( airsupport::GetMapCenter() + ( -xOffset, -yoffset, zOffset ), ( 40000 ) * 0.001 ); + + Target_Set( satellite ); + + satellite clientfield::set( "enemyvehicle", 1 ); + + satellite thread killstreaks::WaitForTimeout( "satellite", ( 40000 ), &OnTimeout, "death", "crashing" ); + + satellite thread heatseekingmissile::MissileTarget_ProximityDetonateIncomingMissile( "death", undefined, true ); + + satellite thread Rotate( ( 10 ) ); + + self killstreaks::play_killstreak_start_dialog( "satellite", self.team, killstreak_id ); + satellite thread killstreaks::player_killstreak_threat_tracking( "satellite" ); + self AddWeaponStat( GetWeapon( "satellite" ), "used", 1 ); + + return true; +} + +function HackedPreFunction( hacker ) +{ + satellite = self; + satellite ResetActiveSatellite(); +} + +function ConfigureTeamPost( owner, isHacked ) +{ + satellite = self; + + satellite thread teams::WaitUntilTeamChangeSingleTon( owner, "Satellite_watch_team_change", &OnTeamChange, self.entNum, "delete", "death", "leaving" ); + if ( isHacked == false ) + { + satellite teams::HideToSameTeam(); + } + else + { + satellite SetVisibleToAll(); + } + satellite AddActiveSatellite(); +} + +function Rotate( duration ) +{ + self endon( "death" ); + + while( true ) + { + self rotateyaw( -360, duration ); + wait( duration ); + } +} + +function OnLowHealth( attacker, weapon ) +{ +} + +function OnTeamChange( entNum, event ) +{ + DestroySatellite( undefined, undefined ); +} + +function OnTimeout() +{ + self killstreaks::play_pilot_dialog_on_owner( "timeout", "satellite" ); + + self.leaving = true; + self RemoveActiveSatellite(); + + airsupport::Leave( ( 10 ) ); + wait( ( 10 ) ); + + if( Target_IsTarget( self ) ) + Target_Remove( self ); + + self delete(); +} + +function DestroySatelliteByEMP( attacker, arg ) +{ + DestroySatellite( attacker, GetWeapon( "emp" ) ); +} + +function DestroySatellite( attacker = undefined, weapon = undefined ) +{ + attacker = self [[ level.figure_out_attacker ]]( attacker ); + if ( isdefined( attacker ) && ( !isdefined( self.owner ) || self.owner util::IsEnemyPlayer( attacker ) ) ) + { + challenges::destroyedAircraft( attacker, weapon, false ); + scoreevents::processScoreEvent( "destroyed_satellite", attacker, self.owner, weapon ); + attacker challenges::addFlySwatterStat( weapon, self ); + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_SATELLITE", attacker.entnum ); + if( !self.leaving ) + self killstreaks::play_destroyed_dialog_on_owner( "satellite", self.killstreak_id ); + } + + self notify( "crashing" ); + + params = level.killstreakBundle["satellite"]; + if( isdefined( params.ksExplosionFX ) ) + PlayFXOnTag( params.ksExplosionFX, self, "tag_origin" ); + + self setModel( "tag_origin" ); + if( Target_IsTarget( self ) ) + Target_Remove( self ); + + wait( 0.5 ); + + if( !self.leaving ) + self RemoveActiveSatellite(); + + self delete(); +} + +function HasSatellite( team_or_entnum ) +{ + return level.activeSatellites[ team_or_entnum ] > 0; +} + +function AddActiveSatellite() +{ + if ( level.teamBased ) + { + level.activeSatellites[ self.team ]++; + } + else + { + level.activeSatellites[ self.ownerEntNum ]++; + } + + level.activePlayerSatellites[ self.ownerEntNum ]++; + + level notify( "satellite_update" ); +} + +function RemoveActiveSatellite() +{ + self ResetActiveSatellite(); + + killstreakrules::killstreakStop( "satellite", self.originalteam, self.killstreak_id ); +} + +function ResetActiveSatellite() +{ + if( level.teamBased ) + { + level.activeSatellites[ self.team ]--; + + assert( level.activeSatellites[ self.team ] >= 0 ); + if( level.activeSatellites[ self.team ] < 0 ) + { + level.activeSatellites[ self.team ] = 0; + } + } + else if ( isdefined( self.ownerEntNum ) ) + { + level.activeSatellites[ self.ownerEntNum ]--; + + assert( level.activeSatellites[ self.ownerEntNum ] >= 0 ); + if( level.activeSatellites[ self.ownerEntNum ] < 0 ) + { + level.activeSatellites[ self.ownerEntNum ] = 0; + } + } + + assert( isdefined( self.ownerEntNum ) ); + level.activePlayerSatellites[ self.ownerEntNum ]--; + assert( level.activePlayerSatellites[ self.ownerEntNum ] >= 0 ); + + level notify( "satellite_update" ); +} + +function SatelliteTracker() +{ + level endon ( "game_ended" ); + + while( true ) + { + level waittill ( "satellite_update" ); + + // intentionally keeping both teambased and non-teambased logic for now + // TODO: one "might" be able to change it to teambased only; when trying to do so, watch for knock-on effects + + if( level.teamBased ) + { + foreach( team in level.teams ) + { + activeSatellites = level.activeSatellites[ team ]; + activeSatellitesAndUAVs = activeSatellites + ( ( isdefined( level.activeUAVs ) ) ? level.activeUAVs[ team ] : 0 ); + + SetTeamSatellite( team, ( activeSatellites > 0 ) ); + util::set_team_radar( team, ( activeSatellitesAndUAVs > 0 ) ); + } + } + else + { + for( i = 0; i < level.players.size; i++ ) + { + UpdatePlayerSatelliteForDM( level.players[ i ] ); + } + } + } +} + +function UpdatePlayerSatelliteForDM( player ) +{ + if( !isdefined( player.entnum ) ) + { + player.entnum = player getEntityNumber(); + } + + activeSatellites = level.activeSatellites[ player.entnum ]; + activeSatellitesAndUAVs = activeSatellites + ( ( isdefined( level.activeUAVs ) ) ? level.activeUAVs[ player.entnum ] : 0 ); + + player SetClientUIVisibilityFlag( "radar_client", ( activeSatellitesAndUAVs > 0 ) ); + player.hasSatellite = ( activeSatellites > 0 ); +} + +function killstreakDamageModifier( damage, attacker, direction, point, sMeansOfDeath, tagName, modelName, partname, weapon, flags, inflictor, chargeLevel ) +{ + if( ( sMeansOfDeath == "MOD_PISTOL_BULLET" ) || ( sMeansOfDeath == "MOD_RIFLE_BULLET" ) ) + return 0; + + if ( sMeansOfDeath == "MOD_PROJECTILE_SPLASH" ) + return 0; + + return damage; +} diff --git a/mp/killstreaks/_sentinel.gsc b/mp/killstreaks/_sentinel.gsc new file mode 100644 index 0000000..b294f05 --- /dev/null +++ b/mp/killstreaks/_sentinel.gsc @@ -0,0 +1,588 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\array_shared; +#using scripts\shared\audio_shared; +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\math_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\statemachine_shared; +#using scripts\shared\system_shared; +#using scripts\shared\turret_shared; +#using scripts\shared\util_shared; +#using scripts\shared\vehicle_ai_shared; +#using scripts\shared\vehicle_death_shared; +#using scripts\shared\vehicle_shared; +#using scripts\shared\vehicles\_wasp; +#using scripts\shared\visionset_mgr_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\weapons\_heatseekingmissile; + +#using scripts\mp\_util; +#using scripts\mp\gametypes\_shellshock; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_helicopter; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_qrdrone; +#using scripts\mp\killstreaks\_rcbomb; +#using scripts\mp\killstreaks\_remote_weapons; +#using scripts\mp\teams\_teams; + + + + + + + + + +#precache( "string", "mpl_killstreak_sentinel_strt" ); +#precache( "string", "KILLSTREAK_SENTINEL_HACKED" ); +#precache( "string", "KILLSTREAK_SENTINEL_INBOUND" ); +#precache( "string", "KILLSTREAK_SENTINEL_NOT_AVAILABLE" ); +#precache( "string", "KILLSTREAK_SENTINEL_EARNED" ); +#precache( "string", "KILLSTREAK_SENTINEL_NOT_PLACEABLE" ); +#precache( "string", "KILLSTREAK_DESTROYED_SENTINEL" ); + +#precache( "triggerstring", "KILLSTREAK_SENTINEL_USE_REMOTE" ); + +#namespace sentinel; + + + + +function init() +{ + killstreaks::register( "sentinel", "sentinel", "killstreak_" + "sentinel", "sentinel" + "_used", &ActivateSentinel, true ); + killstreaks::register_strings( "sentinel", &"KILLSTREAK_SENTINEL_EARNED", &"KILLSTREAK_SENTINEL_NOT_AVAILABLE", &"KILLSTREAK_SENTINEL_INBOUND", undefined, &"KILLSTREAK_SENTINEL_HACKED" ); + killstreaks::register_dialog( "sentinel", "mpl_killstreak_sentinel_strt", "sentinelDialogBundle", "sentinelPilotDialogBundle", "friendlySentinel", "enemySentinel", "enemySentinelMultiple", "friendlySentinelHacked", "enemySentinelHacked", "requestSentinel", "threatSentinel" ); + killstreaks::register_alt_weapon( "sentinel", "killstreak_remote" ); + killstreaks::register_alt_weapon( "sentinel", "sentinel_turret" ); + remote_weapons::RegisterRemoteWeapon( "sentinel", &"KILLSTREAK_SENTINEL_USE_REMOTE", &StartSentinelRemoteControl, &EndSentinelRemoteControl, false ); + + // TODO: Move to killstreak data + level.killstreaks["sentinel"].threatOnKill = true; + + vehicle::add_main_callback( "veh_sentinel_mp", &InitSentinel ); + + visionset_mgr::register_info( "visionset", "sentinel_visionset", 1, 100, 16, true, &visionset_mgr::ramp_in_out_thread_per_player_death_shutdown, false ); +} + +function InitSentinel() +{ + self.settings = struct::get_script_bundle( "vehiclecustomsettings", self.scriptbundlesettings ); + Target_Set( self, ( 0, 0, 0 ) ); + self.health = self.healthdefault; + self.numFlares = 1; + self.damageTaken = 0; + self vehicle::friendly_fire_shield(); + self EnableAimAssist(); + self SetNearGoalNotifyDist( ( 40 ) ); + self SetHoverParams( ( 50.0 ), ( 100.0 ), ( 100.0 ) ); + self.fovcosine = 0; // +/-90 degrees = 180 + self.fovcosinebusy = 0; //+/- 55 degrees = 110 fov + self.vehAirCraftCollisionEnabled = true; + + self thread vehicle_ai::nudge_collision(); + self thread heatseekingmissile::MissileTarget_ProximityDetonateIncomingMissile( "explode", "death" ); // fires chaff if needed + self thread helicopter::create_flare_ent( (0,0,-20) ); + self thread audio::vehicleSpawnContext(); + self.do_scripted_crash = false; + + self.overrideVehicleDamage = &SentinelDamageOverride; + self.selfDestruct = false; + + self.enable_target_laser = true; + self.aggresive_navvolume_recover = true; + + self vehicle_ai::init_state_machine_for_role( "default" ); + self vehicle_ai::get_state_callbacks( "combat" ).enter_func = &wasp::state_combat_enter; + self vehicle_ai::get_state_callbacks( "combat" ).update_func = &wasp::state_combat_update; + self vehicle_ai::get_state_callbacks( "death" ).update_func = &wasp::state_death_update; + self vehicle_ai::get_state_callbacks( "driving" ).enter_func = &driving_enter; + + wasp::init_guard_points(); + self vehicle_ai::add_state( "guard", + &wasp::state_guard_enter, + &wasp::state_guard_update, + &wasp::state_guard_exit ); + + vehicle_ai::add_utility_connection( "combat", "guard", &wasp::state_guard_can_enter ); + vehicle_ai::add_utility_connection( "guard", "combat" ); + vehicle_ai::add_interrupt_connection( "guard", "emped", "emped" ); + vehicle_ai::add_interrupt_connection( "guard", "surge", "surge" ); + vehicle_ai::add_interrupt_connection( "guard", "off", "shut_off" ); + vehicle_ai::add_interrupt_connection( "guard", "pain", "pain" ); + vehicle_ai::add_interrupt_connection( "guard", "driving", "enter_vehicle" ); + + self vehicle_ai::StartInitialState( "combat" ); +} + +function driving_enter( params ) +{ + vehicle_ai::defaultstate_driving_enter( params ); +} + +function drone_pain_for_time( time, stablizeParam, restoreLookPoint, weapon ) +{ + self endon( "death" ); + + self.painStartTime = GetTime(); + + if ( !( isdefined( self.inpain ) && self.inpain ) && isdefined( self.health ) && self.health > 0 ) + { + self.inpain = true; + + + while ( GetTime() < self.painStartTime + time * 1000 ) + { + self SetVehVelocity( self.velocity * stablizeParam ); + self SetAngularVelocity( self GetAngularVelocity() * stablizeParam ); + wait 0.1; + } + + if ( isdefined( restoreLookPoint ) && isdefined( self.health ) && self.health > 0 ) + { + restoreLookEnt = Spawn( "script_model", restoreLookPoint ); + restoreLookEnt SetModel( "tag_origin" ); + + self ClearLookAtEnt(); + self SetLookAtEnt( restoreLookEnt ); + self setTurretTargetEnt( restoreLookEnt ); + wait 1.5; + + self ClearLookAtEnt(); + self ClearTurretTarget(); + restoreLookEnt delete(); + } + + if( weapon.isEmp ) remote_weapons::set_static( 0 ); + + self.inpain = false; + } +} + +function drone_pain( eAttacker, damageType, hitPoint, hitDirection, hitLocationInfo, partName, weapon ) +{ + if ( !( isdefined( self.inpain ) && self.inpain ) ) + { + yaw_vel = math::randomSign() * RandomFloatRange( 280, 320 ); + + ang_vel = self GetAngularVelocity(); + ang_vel += ( RandomFloatRange( -120, -100 ), yaw_vel, RandomFloatRange( -200, 200 ) ); + self SetAngularVelocity( ang_vel ); + + self thread drone_pain_for_time( 0.8, 0.7, undefined, weapon ); + } +} + +function SentinelDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) +{ + if( sMeansOfDeath == "MOD_TRIGGER_HURT" ) + return 0; + + emp_damage = self.healthdefault * ( 0.5 ) + 0.5; + + iDamage = killstreaks::OnDamagePerWeapon( "sentinel", eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, self.maxhealth, &destroyed_cb, self.maxhealth*0.4, &low_health_cb, emp_damage, undefined, true, 1.0 ); + + if( isdefined( eAttacker ) && isdefined( eAttacker.team ) && eAttacker.team != self.team ) + { + drone_pain( eAttacker, sMeansOfDeath, vPoint, vDir, sHitLoc, partName, weapon ); + } + self.damageTaken += iDamage; + return iDamage; +} + +function destroyed_cb( attacker, weapon ) +{ + if( isdefined( attacker ) && isdefined( attacker.team ) && attacker.team != self.team ) + self.owner.dofutz = true; +} + +function low_health_cb( attacker, weapon ) +{ + if( self.playedDamaged == false ) + { + self killstreaks::play_pilot_dialog_on_owner( "damaged", "sentinel", self.killstreak_id ); + self.playedDamaged = true; + } +} + +function CalcSpawnOrigin( origin, angles ) +{ + heightOffset = rcbomb::GetPlacementStartHeight(); + + mins = ( -5, -5, 0 ); + maxs = ( 5, 5, 10 ); + + startPoints = []; + testangles = []; + + testangles[0] = ( 0, 0, 0 ); + testangles[1] = ( 0, 30, 0 ); + testangles[2] = ( 0, -30, 0 ); + testangles[3] = ( 0, 60, 0 ); + testangles[4] = ( 0, -60, 0 ); + testangles[3] = ( 0, 90, 0 ); + testangles[4] = ( 0, -90, 0 ); + + bestOrigin = origin; + bestAngles = angles; + bestFrac = 0; + + for( i = 0; i < testangles.size; i++ ) + { + startPoint = origin + ( 0, 0, heightOffset ); + endPoint = startPoint + VectorScale( anglestoforward( ( 0, angles[1], 0 ) + testangles[i] ), ( 70 ) ); + + mask = (1 << 0) | (1 << 1); + trace = physicstrace( startPoint, endPoint, mins, maxs, self, mask ); + + if( isdefined( trace["entity"] ) && IsPlayer( trace["entity"] ) ) + continue; + + if( trace["fraction"] > bestFrac ) + { + bestFrac = trace["fraction"]; + bestOrigin = trace["position"]; + bestAngles = ( 0, angles[1], 0 ) + testangles[i]; + if( bestFrac == 1 ) + break; + } + } + + if( bestFrac > 0 ) + { + if( Distance2DSquared( origin, bestOrigin ) < 20 * 20 ) + return undefined; + + trace = physicstrace( bestOrigin, bestOrigin + ( 0, 0, ( 25 ) ), mins, maxs, self, mask ); + + placement = SpawnStruct(); + placement.origin = trace["position"]; + placement.angles = bestAngles; + return placement; + } + else + return undefined; +} + +function ActivateSentinel( killstreakType ) +{ + assert( IsPlayer( self ) ); + player = self; + + if( !IsNavVolumeLoaded() ) + { + /# IPrintLnBold( "Error: NavVolume Not Loaded" ); #/ + self iPrintLnBold( &"KILLSTREAK_SENTINEL_NOT_AVAILABLE" ); + return false; + } + + if( player IsPlayerSwimming() ) + { + self iPrintLnBold( &"KILLSTREAK_SENTINEL_NOT_PLACEABLE" ); + return false; + } + + spawnPos = CalcSpawnOrigin( player.origin, player.angles ); + if( !isdefined( spawnPos ) ) + { + self iPrintLnBold( &"KILLSTREAK_SENTINEL_NOT_PLACEABLE" ); + return false; + } + + killstreak_id = player killstreakrules::killstreakStart( "sentinel", player.team, false, true ); + if( killstreak_id == (-1) ) + { + return false; + } + + player AddWeaponStat( GetWeapon( "sentinel" ), "used", 1 ); + + sentinel = SpawnVehicle( "veh_sentinel_mp", spawnPos.origin, spawnPos.angles, "dynamic_spawn_ai" ); + + sentinel killstreaks::configure_team( "sentinel", killstreak_id, player, "small_vehicle", undefined, &ConfigureTeamPost ); + sentinel killstreak_hacking::enable_hacking( "sentinel", &HackedCallbackPre, &HackedCallbackPost ); + sentinel.killstreak_id = killstreak_id; + sentinel.killstreak_end_time = GetTime() + ( 60000 ); + sentinel.original_vehicle_type = sentinel.vehicletype; + sentinel.ignore_vehicle_underneath_splash_scalar = true; + + sentinel clientfield::set( "enemyvehicle", 1 ); + sentinel.hardpointType = "sentinel"; + sentinel.soundmod = "player"; + + sentinel.maxhealth = killstreak_bundles::get_max_health( "sentinel" ); + sentinel.lowhealth = killstreak_bundles::get_low_health( "sentinel" ); + sentinel.health = sentinel.maxhealth; + sentinel.hackedhealth = killstreak_bundles::get_hacked_health( "sentinel" ); + sentinel.rocketDamage = ( sentinel.maxhealth / ( 1 ) ) + 1; + sentinel.playedDamaged = false; + sentinel.treat_owner_damage_as_friendly_fire = true; + sentinel.ignore_team_kills = true; + sentinel thread HealthMonitor(); + + sentinel.goalradius = ( 1200 ); + sentinel.goalHeight = 500; + //sentinel SetGoal( player, false, sentinel.goalRadius, sentinel.goalHeight ); + sentinel.enable_guard = true; + sentinel.always_face_enemy = true; + + sentinel thread killstreaks::WaitForTimeout( "sentinel", ( 60000 ), &OnTimeout, "sentinel_shutdown" ); + sentinel thread WatchWater(); + sentinel thread WatchDeath(); + sentinel thread WatchShutdown(); + + player remote_weapons::UseRemoteWeapon( sentinel, "sentinel", false ); + + sentinel killstreaks::play_pilot_dialog_on_owner( "arrive", "sentinel", killstreak_id ); + + sentinel vehicle::init_target_group(); + sentinel vehicle::add_to_target_group( sentinel ); + + self killstreaks::play_killstreak_start_dialog( "sentinel", self.team, killstreak_id ); + + sentinel thread WatchGameEnded(); + + return true; +} + +function HackedCallbackPre( hacker ) +{ + sentinel = self; + sentinel.owner unlink(); + sentinel clientfield::set( "vehicletransition", 0 ); + if( sentinel.controlled === true ) + visionset_mgr::deactivate( "visionset", "sentinel_visionset", sentinel.owner ); + sentinel.owner remote_weapons::RemoveAndAssignNewRemoteControlTrigger( sentinel.useTrigger ); + sentinel remote_weapons::EndRemoteControlWeaponUse( true ); + EndSentinelRemoteControl( sentinel, true ); +} + +function HackedCallbackPost( hacker ) +{ + sentinel = self; + + hacker remote_weapons::UseRemoteWeapon( sentinel, "sentinel", false ); + sentinel notify("WatchRemoteControlDeactivate_remoteWeapons"); + sentinel.killstreak_end_time = hacker killstreak_hacking::set_vehicle_drivable_time_starting_now( sentinel ); +} + +function ConfigureTeamPost( owner, isHacked ) +{ + sentinel = self; + sentinel thread WatchTeamChange(); +} + +function WatchGameEnded() +{ + sentinel = self; + sentinel endon( "death" ); + + level waittill("game_ended"); + + sentinel.abandoned = true; + sentinel.selfDestruct = true; + sentinel notify( "sentinel_shutdown" ); +} + +function StartSentinelRemoteControl( sentinel ) +{ + player = self; + assert( IsPlayer( player ) ); + + sentinel UseVehicle( player, 0 ); + + sentinel clientfield::set( "vehicletransition", 1 ); + sentinel thread audio::sndUpdateVehicleContext(true); + sentinel thread vehicle::monitor_missiles_locked_on_to_me( player ); + + sentinel.inHeliProximity = false; + sentinel.treat_owner_damage_as_friendly_fire = false; + sentinel.ignore_team_kills = false; + + minHeightOverride = undefined; + minz_struct = struct::get( "vehicle_oob_minz", "targetname"); + if( isdefined( minz_struct ) ) + minHeightOverride = minz_struct.origin[2]; + + sentinel thread qrdrone::QRDrone_watch_distance( ( 0 ), minHeightOverride ); + sentinel.distance_shutdown_override = &SentinelDistanceFailure; + + player vehicle::set_vehicle_drivable_time( ( 60000 ), sentinel.killstreak_end_time ); + visionset_mgr::activate( "visionset", "sentinel_visionset", player, 1, 90000, 1 ); + + if ( isdefined( sentinel.PlayerDrivenVersion ) ) + sentinel SetVehicleType( sentinel.PlayerDrivenVersion ); +} + +function EndSentinelRemoteControl( sentinel, exitRequestedByOwner ) +{ + sentinel.treat_owner_damage_as_friendly_fire = true; + sentinel.ignore_team_kills = true; + + if ( isdefined( sentinel.owner ) ) + { + sentinel.owner vehicle::stop_monitor_missiles_locked_on_to_me(); + if( sentinel.controlled === true ) + visionset_mgr::deactivate( "visionset", "sentinel_visionset", sentinel.owner ); + } + + if( exitRequestedByOwner ) + { + if ( isdefined( sentinel.owner ) ) + { + sentinel.owner qrdrone::destroyHud(); + sentinel.owner unlink(); + sentinel clientfield::set( "vehicletransition", 0 ); + } + sentinel thread audio::sndUpdateVehicleContext(false); + } + + if ( isdefined( sentinel.original_vehicle_type ) ) + sentinel SetVehicleType( sentinel.original_vehicle_type ); + +} + +function OnTimeout() +{ + sentinel = self; + + sentinel killstreaks::play_pilot_dialog_on_owner( "timeout", "sentinel" ); + + params = level.killstreakBundle["sentinel"]; + + if( isdefined( sentinel.owner ) ) + { + RadiusDamage( sentinel.origin, + params.ksExplosionOuterRadius, + params.ksExplosionInnerDamage, + params.ksExplosionOuterDamage, + sentinel.owner, + "MOD_EXPLOSIVE", + GetWeapon( "sentinel" ) ); + if( isdefined( params.ksExplosionRumble ) ) + sentinel.owner PlayRumbleOnEntity( params.ksExplosionRumble ); + } + + sentinel notify( "sentinel_shutdown" ); +} + +function HealthMonitor() +{ + self endon( "death" ); + + params = level.killstreakBundle["sentinel"]; + + if( isdefined( params.fxLowHealth ) ) + { + while( 1 ) + { + if( self.lowhealth > self.health ) + { + PlayFXOnTag( params.fxLowHealth, self, "tag_origin" ); + break; + } + {wait(.05);}; + } + } +} + +function SentinelDistanceFailure() +{ + sentinel = self; + + sentinel notify( "sentinel_shutdown" ); +} + +function WatchDeath() +{ + sentinel = self; + sentinel waittill( "death", attacker, damageFromUnderneath, weapon, point, dir, modType ); + sentinel notify( "sentinel_shutdown" ); + + attacker = self [[ level.figure_out_attacker ]]( attacker ); + if ( isdefined( attacker ) && ( !isdefined( self.owner ) || self.owner util::IsEnemyPlayer( attacker ) ) ) + { + if ( isPlayer( attacker ) ) + { + challenges::destroyedAircraft( attacker, weapon, sentinel.controlled === true ); + attacker challenges::addFlySwatterStat( weapon, self ); + attacker AddWeaponStat( weapon, "destroy_aitank_or_setinel", 1 ); + scoreevents::processScoreEvent( "destroyed_sentinel", attacker, sentinel.owner, weapon ); + if ( modType == "MOD_RIFLE_BULLET" || modType == "MOD_PISTOL_BULLET" ) + { + attacker addPlayerStat( "shoot_down_sentinel", 1 ); + } + LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_SENTINEL", attacker.entnum ); + } + + if ( isdefined( sentinel ) && isdefined( sentinel.owner ) ) + { + sentinel killstreaks::play_destroyed_dialog_on_owner( "sentinel", sentinel.killstreak_id ); + } + } +} + +function WatchTeamChange() +{ + self notify( "Sentinel_WatchTeamChange_Singleton" ); + self endon ( "Sentinel_WatchTeamChange_Singleton" ); + sentinel = self; + + sentinel endon( "sentinel_shutdown" ); + sentinel.owner util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); + sentinel notify( "sentinel_shutdown" ); +} + + + + + + +function WatchWater() +{ + sentinel = self; + sentinel endon( "sentinel_shutdown" ); + + while( true ) + { + wait ( 0.1 ); + trace = physicstrace( self.origin + ( 0, 0, 10 ), self.origin + ( 0, 0, 6 ), ( -2, -2, -2 ), ( 2, 2, 2 ), self, ( (1 << 2) )); + if( trace["fraction"] < 1.0 ) + break; + } + + sentinel notify( "sentinel_shutdown" ); +} + +function WatchShutdown() +{ + sentinel = self; + + sentinel waittill( "sentinel_shutdown" ); + + if( ( isdefined( sentinel.control_initiated ) && sentinel.control_initiated ) || ( isdefined( sentinel.controlled ) && sentinel.controlled ) ) + { + sentinel remote_weapons::EndRemoteControlWeaponUse( false ); + while( ( isdefined( sentinel.control_initiated ) && sentinel.control_initiated ) || ( isdefined( sentinel.controlled ) && sentinel.controlled ) ) + {wait(.05);}; + } + + if( isdefined( sentinel.owner ) ) + { + sentinel.owner qrdrone::destroyHud(); + } + + killstreakrules::killstreakStop( "sentinel", sentinel.originalTeam, sentinel.killstreak_id ); + + if( isalive( sentinel ) ) + sentinel Kill(); +} \ No newline at end of file diff --git a/mp/killstreaks/_supplydrop.gsc b/mp/killstreaks/_supplydrop.gsc new file mode 100644 index 0000000..d710f20 --- /dev/null +++ b/mp/killstreaks/_supplydrop.gsc @@ -0,0 +1,3463 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\entityheadicons_shared; +#using scripts\shared\gameobjects_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\popups_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\sound_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons\_hacker_tool; +#using scripts\shared\weapons\_smokegrenade; +#using scripts\shared\weapons\_tacticalinsertion; +#using scripts\shared\weapons\_weapons; +#using scripts\shared\weapons\_heatseekingmissile; +#using scripts\shared\weapons\_weaponobjects; +#using scripts\shared\vehicleriders_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\flagsys_shared; + +#using scripts\mp\_challenges; +#using scripts\mp\_util; +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\killstreaks\_ai_tank; +#using scripts\mp\killstreaks\_airsupport; +#using scripts\mp\killstreaks\_emp; +#using scripts\mp\killstreaks\_helicopter; +#using scripts\mp\killstreaks\_killstreak_bundles; +#using scripts\mp\killstreaks\_killstreak_detect; +#using scripts\mp\killstreaks\_killstreak_hacking; +#using scripts\mp\killstreaks\_killstreak_weapons; +#using scripts\mp\killstreaks\_killstreakrules; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\killstreaks\_supplydrop; +#using scripts\mp\killstreaks\_combat_robot; + + +