#include maps\mp\_utility; #include common_scripts\utility; //**************************************************************************** // ** // Confidential - (C) Activision Publishing, Inc. 2010 ** // ** //**************************************************************************** // ** // Module: Prototype killstreak - Roboturret ** // Once the killstreak is deployed, it will follow the player ** // around the map and kill enemies. ** // ** // Created: May 31th, 2011 - James Chen ** // ** //***************************************************************************/ //tagJC: The current implementation consists of three components: one very small joint as vehicle serving as the connecting // base for the turret and the legs. //tagJC: Known Issues (bugs) // (1) The animation for the turret is controlled by the another underlying (turret) script. Currently, the turret // does not play the proper animation when tracking/shooting enemies. // (2) The robo legs is 90 degree from the correct orientation of the vehicle. //tagJC: Determine a better pathing logic under the current vehicle pathing limitation so the roboturret can really follow // the players around the level. init() { //tagJC: Precache all the assets needed. //tagJC: nx_vehicle_roboturret_legs_vehicle is a very small joint which has all the required TAG for a vehicle. // It is used so the robo legs and the roboturrets can be attached to it and the game will handle it properly // according to the default vehicle animation. PrecacheVehicle( "proto_nx_vehicle_roboturret_legs_vehicle" ); precacheModel( "nx_vehicle_roboturret_legs_vehicle" ); //tagJC: nx_vehicle_spider is the robo legs. PrecacheVehicle( "nx_spider_mp" ); precacheModel( "nx_vehicle_spider" ); //tagJC: nx_ugv_robo_turret is the robo turret. precacheTurret( "ugv_robo_turret_mp" ); precacheModel( "nx_ugv_robo_turret" ); //tagJC: These are the animations for the robo legs. precacheMpAnim( "nx_vh_roboturret_run" ); precacheMpAnim( "nx_vh_roboturret_walk" ); precacheMpAnim( "nx_vh_roboturret_idle" ); //level._spiderSpawners = Vehicle_GetSpawnerArray(); //tagJC: This is the killstreak activation callback for the roboturret. level._killstreakFuncs["spider"] = ::try_use_killstreak; //tagJC: Use this script to update/initialize players as they connect to the game. level thread onPlayerConnect(); //tagJC: In order for the roboturret to work correctly, the level needs to have at least one "spidernode". level._spiderNodes = getVehicleNodeArray( "spidernode", "script_noteworthy" ); StartNodeMappingTable(); } //******************************************************************* // * // * //******************************************************************* //tagJC: Checking whether the level is properly set up for the roboturret killstreak. try_use_killstreak( lifeId ) { if ( ! level._spiderNodes.size ) { self iPrintLnBold( "Spider is currently not supported in this level, bug your friendly neighborhood Level Designer, Failed to locate valid spider node." ); return false; } //tagJC: Test script of which any desired testing can be performed before integrated into working prototype. //testscript(); killstreak_template_use( lifeId ); return true; } //******************************************************************* // * // * //******************************************************************* //tagJC: This function creates a mapping table for all the start nodes. It uses the coordinate of the start node as the // the index for the first dimension of the array. The second dimension of the array stores all the start nodes that // the current start node can travel to. StartNodeMappingTable() { println( "StartNodeMappingTable" ); level._StartNodeMapping = []; println( "################################" ); for ( i = 0; i < level._spiderNodes.size; i++ ) { StartNode = level._spiderNodes[i]; //tagJC: Getting the end node of the path whose start node is StartNode EndNode = GetVehicleNode( StartNode.target, "targetname" ); //tagJC: Creating the index using the StartNode's coordinate index = "X" + StartNode.origin[0] + "Y" + StartNode.origin[1] + "Z" + StartNode.origin[2]; println( "The index is " + index ); for ( j = 0; j < level._spiderNodes.size; j++ ) { TargetStartNode = level._spiderNodes[j]; //tagJC: Store the TargetStartNode into the array if its distance is less than 250 units from the EndNode if ( distance( EndNode.origin, TargetStartNode.origin ) < 250 ) { if ( ! IsDefined (level._StartNodeMapping[index]) ) { level._StartNodeMapping[index][0] = TargetStartNode; } else { ArraySize = level._StartNodeMapping[index].size; level._StartNodeMapping[index][ArraySize] = TargetStartNode; } } } //tagJC: Debugging output to look at the array println( "There are " + level._StartNodeMapping[index].size + " startnodes connected" ); for ( r = 0; r < level._StartNodeMapping[index].size; r++) { println( " The coordinate is " + level._StartNodeMapping[index][r].origin ); } } } //******************************************************************* // * // * //******************************************************************* //tagJC: Test script for any desired functionality, such as loading model, playing certain animation etc.. // This is to make sure things are working properly before they are integrated into the working prototype. testscript() { } //******************************************************************* // * // * //******************************************************************* // tagJC: This is the callback that executes when the killstreak is activated by a player pressing on the dpad. killstreak_template_use( lifeId ) { assert( isDefined( self ) ); //tagJC: spawning the small joint as the vehicle and setting the associated attributes. level._spider = spawnVehicle( "nx_vehicle_roboturret_legs_vehicle", "roboturret", "proto_nx_vehicle_roboturret_legs_vehicle", (0, 0, 0), (0, 0, 0) ); level._spider.health = 3000; level._spider.targeting_delay = 1; level._spider.team = self.team; level._spider.pers["team"] = level._spider.team; level._spider.owner = self; level._spider setCanDamage( true ); level._spider.standardSpeed = 10; level._spider.evadeSpeed = 50; level._spider.dangerSpeed = 15; level._spider.miniEngagementSpeed = 15; level._spider.engagementSpeed = 15; //tagJC: Kill the vehicle if it is dropped for more than 2048 units level._spider thread deleteOnZ(); //tagJC: Spawning the robo legs spiderScriptModel = spawn ( "script_model", level._spider.origin ); spiderScriptModel setModel ( "nx_vehicle_spider" ); spiderScriptModel ScriptModelPlayAnim ( "nx_vh_roboturret_idle" ); level._spiderCurrentAnimation = "nx_vh_roboturret_idle"; //tagJC: Linking the robo legs to the vehicle. spiderScriptModel linkTo ( level._spider, "TAG_ORIGIN" , (0, 0, 0), (0, 0, 0) ); spiderScriptModel thread loopOnLegs ( level._spider ); //tagJC: Spawning the roboturret and setting its attributes. spiderTurret = spawnTurret( "misc_turret", level._spider.origin, "ugv_main_turret_mp" ); //tagJC: Linking the turret to the scripted robo legs model. spiderTurret linkTo( spiderScriptModel, "TAG_TURRET", (0,0,0), (0,0,0) ); spiderTurret setModel( "nx_ugv_robo_turret" ); spiderTurret.angles = level._spider.angles; spiderTurret.owner = level._spider.owner; spiderTurret makeTurretInoperable(); level._spider.mgTurret = spiderTurret; level._spider.mgTurret SetDefaultDropPitch( 0 ); //tagJC: Start the turret to track and shoot enemies. level._spider thread tankGetMiniTargets(); //tagJC: Finding the spider start node that is closest to the player. closest_spidernode = find_closest_spidernode( self, level._spiderNodes ); //tagJC: Start moving the roboturret according to player's position. level._spider thread move_spider ( self, closest_spidernode ); return true; } //******************************************************************* // * // * //******************************************************************* //tagJC: There are three animations that the robo legs can play: idel, walk and run loopOnLegs ( vehicle ) { timer = 0; while ( 1 ) { self.angle = vehicle.angle; speed = vehicle Vehicle_GetSpeed(); //tagJC: If the vehicle speed is faster than 10 MPH, the legs will run if ( speed > 10 ) { timer = 0; if ( level._spiderCurrentAnimation != "nx_vh_roboturret_run" ) { self ScriptModelPlayAnim ( "nx_vh_roboturret_run" ); level._spiderCurrentAnimation = "nx_vh_roboturret_run"; self stoploopsound("robot_sentry_walk"); self playloopsound("robot_sentry_run"); } } //tagJC: If the vehicle speed is between 10 and 0 MPH, the legs will walk else if ( speed < 10 && speed > 0 ) { timer = 0; if ( level._spiderCurrentAnimation != "nx_vh_roboturret_walk" ) { self ScriptModelPlayAnim ( "nx_vh_roboturret_walk" ); level._spiderCurrentAnimation = "nx_vh_roboturret_walk"; self stoploopsound("robot_sentry_run"); self playloopsound("robot_sentry_walk"); } } //tagJC: If the vehicle speed is 0 MPH, the legs will be idel else { timer = timer + 1; //tagJC: If the roboturret has been idle for 15 seconds if ( timer == 30 ) { vehicle playsound ( "roboturret_idle_vo" ); //vehicle playsound ( "roboturret_kill_vo" ); timer = 0; } if ( level._spiderCurrentAnimation != "nx_vh_roboturret_idle" ) { self stoploopsound("robot_sentry_run"); self stoploopsound("robot_sentry_walk"); self ScriptModelPlayAnim ( "nx_vh_roboturret_idle" ); level._spiderCurrentAnimation = "nx_vh_roboturret_idle"; } } println ( "timer is: " + timer ); wait ( 0.5 ); } } //******************************************************************* // * // * //******************************************************************* //tagJC: Currently, in order for this implementation to work, designers would have to place many pairs of bi-directional vehicle nodes // in the level. Given the limitation that vehicle seems to be only moving when it is on a vehicle path, the roboturret // cannot quite follow the players perfectly yet. Players would have to get close to the roboturret and then guide // it to a different position. move_spider ( owner, startNode ) { //self playsound( "roboturret_deploy_vo" ); self Vehicle_SetSpeed( 30, 5, 5 ); //tagJC: Starting the roboturret along the path where its starting node is the roboturret's spawning node self AttachPath( startNode ); self StartPath( startNode ); self waittill( "reached_end_node" ); self playsound( "roboturret_deploy_vo" ); prev_node = startNode; while ( 1 ) { index = "X" + prev_node.origin[0] + "Y" + prev_node.origin[1] + "Z" + prev_node.origin[2]; best_node = find_best_spidernode( owner, level._StartNodeMapping[index] ); //if ( distance ( owner.origin, level._spider.origin ) > 50 ) if ( isGettingCloserToPlayer ( owner, best_node ) ) { //if ( prev_node != best_node ) //{ self StartPath( best_node ); self waittill( "reached_end_node" ); prev_node = best_node; //println( "This is after subsequent reached_end_node" ); //} } else { wait 0.5; } } } //******************************************************************* // * // * //******************************************************************* //tagJC: This function checks whether the turret will be moving away from the player or not isGettingCloserToPlayer ( owner, StartNode ) { EndNode = GetVehicleNode( StartNode.target, "targetname" ); if ( distance ( owner.origin, EndNode.origin ) > distance ( owner.origin, StartNode.origin ) ) { return false; } else { return true; } } //******************************************************************* // * // * //******************************************************************* //tagJC: From the ReachableNodeList, determine the node that is will lead the turret to the player find_best_spidernode( owner, ReachableNodeList ) { target_vector = VectorNormalize ( owner.origin - level._spider.origin ); //tagJC: Creating an arbitrary base line using the first node in the node awway best_node = ReachableNodeList [0]; best_dot_product = VectorDot ( VectorNormalize( best_node.origin - level._spider.origin ), target_vector ); for ( i = 1; i < ReachableNodeList.size; i++) { //tagJC: If another node in the list can produce a larger dot product, that node is the best node dot_product = VectorDot ( VectorNormalize( ReachableNodeList[i].origin - level._spider.origin ), target_vector ); if ( dot_product > best_dot_product ) { best_node = ReachableNodeList[i]; } } return best_node; } //******************************************************************* // * // * //******************************************************************* tankGetMiniTargets() { self endon( "death" ); self endon( "leaving" ); miniTargets = []; println( "Geting Mini Targets" ); for ( ;; ) { miniTargets = []; players = level._players; //tagJC: Putting all the players on the oppositing team into a array. for (i = 0; i <= players.size; i++) { if ( isMiniTarget( players[i] ) ) { if( isdefined( players[i] ) ) miniTargets[miniTargets.size] = players[i]; } else { continue; } wait( .05 ); } //tagJC: If there is at least one enemy, start acquiring the target if ( miniTargets.size > 0 ) { self acquireMiniTarget( miniTargets ); return; } else wait( 0.5 ); } } //******************************************************************* // * // * //******************************************************************* //tagJC: From the potential target list, determine whether there is any target that is feasible/practical for the turret // to target. isMiniTarget( potentialTarget ) { self endon( "death" ); if ( !isalive( potentialTarget ) || potentialTarget.sessionstate != "playing" ) return false; if ( !isdefined( potentialTarget.pers["team"] ) ) return false; if ( potentialTarget == self.owner ) return false; if ( distanceSquared( potentialTarget.origin , self.origin ) > 1024*1024 ) return false; if ( level._teamBased && potentialTarget.pers["team"] == self.team ) return false; if ( potentialTarget.pers["team"] == "spectator" ) return false; if ( isdefined( potentialTarget.spawntime ) && ( gettime() - potentialTarget.spawntime )/1000 <= 5 ) return false; if ( isDefined( self ) ) { minTurretEye = self.mgTurret.origin + ( 0, 0, 64 ); minTurretCanSeeTarget = potentialTarget sightConeTrace( minTurretEye, self ); if ( minTurretCanSeeTarget < 1 ) { return false; } } return true; } //******************************************************************* // * // * //******************************************************************* //tagJC: From the target list, determine which one is the best target and shoot it. acquireMiniTarget( targets ) { self endon( "death" ); //tagJC: If there is only one target in the list, it is the best target. if ( targets.size == 1 ) { self.bestMiniTarget = targets[0]; } //tagJC: Else, determine the best target in the list. else { self.bestMiniTarget = self getBestMiniTarget( targets ); } self notify( "acquiringMiniTarget" ); //tagJC: Set turret to target the best target. self.mgTurret SetTargetEntity( self.bestMiniTarget, ( 0,0,42 ) ); wait( .15 ); //tagJC: Set turret to fire at the best target. self thread fireMiniOnTarget(); //tagJC: Abandon the target when it is killed. self thread watchMiniTargetDeath( targets ); self thread watchMiniTargetDistance( targets ); self thread watchMiniTargetThreat( self.bestMiniTarget ); } //******************************************************************* // * // * //******************************************************************* //tagJC: Determine the best target from the target list getBestMiniTarget( targets ) { self endon( "death" ); tankOrigin = self.origin; closest = undefined; bestTarget = undefined; foreach ( targ in targets ) { curDist = Distance( self.origin, targ.origin ); //tagJC: If the target is holding or using explosives, push the target to a higher priority curWeaon = targ GetCurrentWeapon(); if ( isSubStr( curWeaon, "at4" ) || isSubStr( curWeaon, "jav" ) || isSubStr( curWeaon, "c4" ) || isSubStr( curWeaon, "smart" ) || isSubStr( curWeaon, "grenade" ) ) { curDist -= 200; } if ( !isDefined( closest ) ) { closest = curDist; bestTarget = targ; } else if ( closest > curDist ) { closest = curDist; bestTarget = targ; } } return ( bestTarget ); } //******************************************************************* // * // * //******************************************************************* //tagJC: Firing the turret at the target. fireMiniOnTarget() { self endon( "death" ); self endon( "abandonedMiniTarget" ); self endon( "killedMiniTarget" ); noTargTime = undefined; miniAcquiredTime = getTime(); if ( !isDefined( self.bestMiniTarget ) ) { println( "No Targ to fire on" ); return; } println( "firing at best target" ); while( 1 ) { if ( !isDefined ( self.mgTurret getTurretTarget( true ) ) ) { if ( !isDefined( noTargTime ) ) { noTargTime = getTime(); } curTime = getTime(); //tagJC: If there has been more than 1 milliseconds without a target, abandon the target. if ( noTargTime - curTime > 1 ) { noTargTime = undefined; self thread explicitAbandonMiniTarget(); return; } println("Waiting because the turret doesnt have a target" ); wait ( .5 ); continue; } //tagJC: Firing a random number of shots between 2 to 6. numShots = randomIntRange( 2, 6 ); for ( i = 0; i < numShots; i++ ) { println( "actually shooting turret" ); self.mgTurret ShootTurret(); wait ( .25 ); } wait ( randomFloatRange( 0.05, 0.1 ) ); } } //******************************************************************* // * // * //******************************************************************* //tagJC: Clearing the current target list for the turret and starting to get more targets. explicitAbandonMiniTarget( noNewTarget ) { self notify( "abandonedMiniTarget" ); println( "ABANDONED MINI TARGET" ); self.bestMiniTarget = undefined; self.mgTurret ClearTargetEntity(); if ( isDefined(noNewTarget) && noNewTarget ) { return; } self thread tankGetMiniTargets(); return; } //******************************************************************* // * // * //******************************************************************* //tagJC: Wait for the target's death. Once the target is killed, clear the target list and get more targets. watchMiniTargetDeath( targets ) { self endon( "abandonedMiniTarget" ); self endon( "death" ); if ( ! isDefined( self.bestMiniTarget ) ) return; self.bestMiniTarget waittill( "death" ); //self playsound ( "roboturret_idle_vo" ); self playsound ( "roboturret_kill_vo" ); self notify( "killedMiniTarget" ); println( "Killed Mini Target" ); self.bestMiniTarget = undefined; self.mgTurret ClearTargetEntity(); self tankGetMiniTargets(); } //******************************************************************* // * // * //******************************************************************* //tagJC: If the target is more than 1024 units away, abandon the target watchMiniTargetDistance( targets ) { self endon( "abandonedMiniTarget" ); self endon( "death" ); for ( ;; ) { if (! isDefined( self.bestMiniTarget ) ) return; trace = BulletTrace( self.mgTurret.origin, self.bestMiniTarget.origin, false, self ); traceDistance = Distance(self.origin, trace["position"] ); if ( traceDistance > 1024 ) { println( "MINI TARGET DIST TOO FAR!!!" ); self thread explicitAbandonMiniTarget(); return; } println( traceDistance ); wait ( 2 ); } } //******************************************************************* // * // * //******************************************************************* //tagJC: If there is a target that is closer to the current target, abandon the curret target watchMiniTargetThreat( curTarget ) { self endon( "abandonedMiniTarget" ); self endon( "death" ); self endon( "killedMiniTarget" ); for ( ;; ) { miniTargets = []; players = level._players; for (i = 0; i <= players.size; i++) { if ( isMiniTarget( players[i] ) ) { if( !isdefined( players[i] ) ) { continue; } if( !isdefined(curTarget) ) { return; } traceOldTarg = Distance(self.origin, CurTarget.origin ); traceNewTarg = Distance(self.origin, players[i].origin ); if ( traceNewTarg < traceOldTarg ) { self thread explicitAbandonMiniTarget(); return; } } wait( .05 ); } wait( .25 ); } } //******************************************************************* // * // * //******************************************************************* //tagJC: From the entity_list, return the one that is closest to the entity find_closest_spidernode ( target, entity_list ) { //tagJC: Use an arbitrary distance as the base for comparision closest_distance = distance( target.origin , entity_list[0].origin ); closest_entity = entity_list[0]; for ( i = 1; i < entity_list.size; i++ ) { //tagJC: If another entity on the list results in a shorter distance, update the results accordingly if ( distance( target.origin , entity_list[i].origin ) < closest_distance ) { closest_distance = distance( target.origin , entity_list[i].origin ); closest_entity = entity_list[i]; } } return closest_entity; } //******************************************************************* // * // * //******************************************************************* //tagJC: If a vehicle is dropped more than 2048 units, kill it. deleteOnZ() { self endon ( "death" ); originalZ = self.origin[2]; for ( ;; ) { if ( originalZ - self.origin[2] > 2048 ) { self.health = 0; self notify( "death" ); return; } wait ( 1.0 ); } } //******************************************************************* // * // * //******************************************************************* //tagJC: This function tracks player's movement and put the closest node to the player into a queue TrackingPlayers( owner ) { self.PlayerLocations = []; while ( 1 ) { close_node = find_closest_spidernode( owner, level._spiderNodes ); if ( self.PlayerLocations.size == 0 ) { self.PlayerLocations[0] = close_node; } else { size = self.PlayerLocations.size; Player_last_location = self.PlayerLocations[ size - 1 ]; if ( close_node != Player_last_location ) { self.PlayerLocations[ size ] = close_node; //println ( "========== This is inside Tracking Player ============" ); //println ( "The location of the node being added is: " + self.PlayerLocations[ size ].origin); //println ( "The size of PlayerLocations is: " + self.PlayerLocations.size ); //println ( "======================================================" ); } } wait( 0.5 ); } } //******************************************************************* // * // * //******************************************************************* //tagJC: Remove the first item in the list by shifting entities in the list forward by one slot in the list RemoveFirstItem ( list ) { if ( list.size > 0 ) { for ( i = 0 ; i < list.size - 1 ; i ++ ) { list[ i ] = list[ i + 1 ]; } list[ list.size - 1 ] = undefined; } } //******************************************************************* // * // * //******************************************************************* // tagJC: This script is running on the global level object, it monitors players connecting to the game. // Its main purpose is to apply the onPlayerSpawned script to each player as they connect to the game. onPlayerConnect() { for(;;) { level waittill("connected", player); player thread onPlayerSpawned(); } } //******************************************************************* // * // * //******************************************************************* // tagJC: This script is running on each player in the game, it recieves a notification each time the player it is running on spawns in the game // Its main purpose is to initialize any per player data, as well as update the player subject to any global killstreak data when that player spawns. onPlayerSpawned() { self endon("disconnect"); for(;;) { self waittill( "spawned_player" ); println( "player spwaned" ); // init/manage any per player killstreak data here } } main ( vehicle, model ) { } //******************************************************************* // * // * //******************************************************************* /* QUAKED script_vehicle_nx_proto_roboturret (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER maps\mp\killstreaks\_spider::main( "nx_vehicle_roboturret_legs_vehicle", "proto_nx_vehicle_roboturret_legs_vehicle" ); include,prototype_nx_vehicle_roboturret defaultmdl="nx_vehicle_roboturret_legs_vehicle" default:"vehicletype" "proto_nx_vehicle_roboturret_legs_vehicle" default:"script_team" "allies" */