#include maps\mp\_utility; #include common_scripts\utility; //time that the ugv will hang out in the level UGV_TIMEOUT_SEC = 60; //ugv will exit the level if it has stopped this many times UGV_MAX_STOPS = 50; //easy tweaking of the hud UGV timer UGV_HUD_TIMER_POS_X = 0; UGV_HUD_TIMER_POS_Y = -35; init() { //can remove these once we have completley shifted to using the nx ugv PrecacheVehicle( "bmp_mp" ); precacheModel( "vehicle_bmp" ); precacheModel( "vehicle_bmp_dsty" ); PreCacheTurret( "abrams_minigun_mp" ); precacheTurret( "pavelow_minigun_mp" ); /# setDevDvar( "tankDir", "" ); setDevDvar( "tankForceTrigger", 0 ); if ( getDvar( "tankDebug" ) == "" ) setDevDvar( "tankDebug", 0 ); #/ level._killstreakFuncs["tank"] = ::tryUseTank; level._tankFire = loadfx( "explosions/large_vehicle_explosion" ); level._tankCover = loadfx( "props/american_smoke_grenade_mp" ); level._otherDir["forward"] = "reverse"; level._otherDir["reverse"] = "forward"; tankSpawners = Vehicle_GetSpawnerArray(); if ( !tankSpawners.size ) return; ugvStartNodes = getVehicleNodeArray( "ugvstartnode", "script_noteworthy" ); if ( !ugvStartNodes.size ) { assertEx ( !ugvStartNodes.size, "Vehicle spawn is setup but, tank path is not setup in this level bug your friendly neighborhood LD." ); return false; } level._tankSpawner["allies"] = tankSpawners[0]; level._tankSpawner["axis"] = tankSpawners[0]; level._pathCount = 0; foreach ( spawner in tankSpawners ) { if ( isSubStr( spawner.model, "bradley" ) ) level._tankSpawner["allies"] = spawner; if ( isSubStr( spawner.model, "bmp" ) ) level._tankSpawner["axis"] = spawner; } //this is from old IW code, looks like it could be useful, but i'm not using it for now. //level setupPaths(); //a generic tank timeOut in sec. level._tankTimeOut = 8; //number of stops allowed before the ugv will move to the exit path. level._maxUGVStops = 4; } tryUseTank( lifeId ) { println( "tryUseTank" ); if ( isDefined( level._tankInUse ) && level._tankInUse ) { self iPrintLnBold( "Armor support unavailable. A UGV is already in use, future task: support multiple UGVs" ); return false; } ugvSpawnNodes = getVehicleNodeArray( "ugvstartnodespawn", "targetname" ); if ( !ugvSpawnNodes.size ) { self iPrintLnBold( "Tank is currently not supported in this level, bug your friendly neighborhood LD, Failed to locate valid spawn node." ); return false; } ugvStartNodes = getVehicleNodeArray( "ugvstartnode", "script_noteworthy" ); if ( !ugvStartNodes.size ) { self iPrintLnBold( "Tank is currently not supported in this level, bug your friendly neighborhood LD, Failed to locate valid start node." ); return false; } ugvExitNodes = getVehicleNodeArray( "ugvexitnode", "script_noteworthy" ); if ( !ugvExitNodes.size ) { println( "^1Warning: UGV Exit path is undefined" ); } ugvBackNodes = getVehicleNodeArray( "ugvstartnodeback", "script_noteworthy" ); if ( !ugvBackNodes.size ) { println( "^1Warning: UGV Backward paths are undefined" ); } if ( !Vehicle_GetSpawnerArray().size ) { self iPrintLnBold( "Vehicle spawn array empty" ); return false; } //picks UGV Location and kicks off the UGV Killstreak. result = self selectTankLocation( lifeId ); if ( !isDefined( result ) || !result ) return false; return true; } selectTankLocation( lifeId ) { //if we want to use the directional on the selector chooseDirection = false; targetSize = level._mapSize / 5.625; // 138 in 720 if ( level._splitscreen ) targetSize *= 1.5; self createUGVNodeHudElements(); self beginLocationSelection( "map_artillery_selector", chooseDirection, true, targetSize ); self.selectingLocation = true; self setblurforplayer( 4.0, 0.3 ); self thread waitForTankCancel(); self thread endSelectionOn( "cancel_location" ); self thread endSelectionOn( "death" ); self thread endSelectionOn( "disconnect" ); self thread endSelectionOn( "used" ); // so that this thread doesn't kill itself when we use a tank self thread endSelectionOnGameEnd(); self thread endSelectionOnEMP(); self endon( "stop_location_selection" ); self waittill( "confirm_location", location, directionYaw ); if ( !chooseDirection ) directionYaw = randomint(360); self setblurforplayer( 0, 0.3 ); self thread finishTankSelectLocation( lifeId, location, directionYaw ); return true; } createUGVNodeHudElements() { //get a list of UGV drop nodes, stuff the data in hud elements so the client can draw them on the mini map selector. //startNodes = GetVehicleNodeArray( "ugvstartnode", "script_noteworthy" ); spawnNodes = GetVehicleNodeArray( "ugvstartnodespawn", "targetname" ); self.ugvhudElements = []; loopcount = 0; foreach( node in spawnNodes ) { self.ugvhudElements[loopcount] = newClientHudElem( self ); self.ugvhudElements[loopcount].x = node.origin[0]; self.ugvhudElements[loopcount].y = node.origin[1]; self.ugvhudElements[loopcount] SetWorldPosition( node.origin[0], node.origin[1] ); self.ugvhudElements[loopcount].alpha = 0.0; loopcount = loopcount + 1; } } destroyUGVNodeHudElements() { //destroy the drop nodes if( isDefined( self.ugvhudElements )) { for( i = 0; i < self.ugvhudElements.size; i++ ) { elem = self.ugvhudElements[i]; elem Destroy(); } } } waitForTankCancel() { self waittill( "cancel_location" ); self setblurforplayer( 0, 0.3 ); } endSelectionOn( waitfor ) { self endon( "stop_location_selection" ); self waittill( waitfor ); self thread stopTankLocationSelection( (waitfor == "disconnect") ); } endSelectionOnGameEnd() { self endon( "stop_location_selection" ); level waittill( "game_ended" ); self thread stopTankLocationSelection( false ); } endSelectionOnEMP() { self endon( "stop_location_selection" ); for ( ;; ) { level waittill( "emp_update" ); if ( !self isEMPed() ) continue; self thread stopTankLocationSelection( false ); return; } } stopTankLocationSelection( disconnected ) { if ( !disconnected ) { self setblurforplayer( 0, 0.3 ); self endLocationSelection(); self.selectingLocation = undefined; } self destroyUGVNodeHudElements(); self notify( "stop_location_selection" ); } finishTankSelectLocation( lifeId, location, directionYaw ) { self notify( "used" ); //thread useTank( lifeId, location, directionYaw, self, self.pers["team"] ); //println( "chosen location is ( " + location[0] + ", " + location[1] + ", " + location[2] + " )" ); thread useTank( lifeId, location ); } useTank( lifeId, location ) { println( "useTank" ); if ( self.team == "allies" ) tank = level._tankSpawner["allies"] spawnArmor( self, "nx_vehicle_ugv" ); else tank = level._tankSpawner["axis"] spawnArmor( self, "nx_vehicle_ugv" ); tank startTank( location ); return true; } spawnArmor( owner, vehicletype, model ) { armor = self Vehicle_DoSpawn( "tank", owner ); armor setModel( vehicletype ); armor.health = 3000; armor.targeting_delay = 1; armor.team = owner.team; armor.pers["team"] = armor.team; armor.owner = owner; armor setCanDamage( true ); armor.standardSpeed = 10; armor.evadeSpeed = 50; armor.dangerSpeed = 15; armor.miniEngagementSpeed = 15; armor.engagementSpeed = 15; armor thread deleteOnZ(); armor addToTankList(); armor.damageCallback = ::Callback_VehicleDamage; return armor; } 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 ); } } startTank( location ) { //give the UGV a list of all vehicle nodes self.nodes = GetVehicleNodeArray( "info_vehicle_node", "classname" ); //seems like these lists should just be set once when the level loads but for some reason that was not working. level._ugvstartnodes = GetVehicleNodeArray( "ugvstartnode", "script_noteworthy" ); level._ugvbacknodes = GetVehicleNodeArray( "ugvstartnodeback", "script_noteworthy" ); level._ugvexitnodes = GetVehicleNodeArray( "ugvexitnode", "script_noteworthy" ); level._ugvspawnnodes = GetVehicleNodeArray( "ugvstartnodespawn", "targetname" ); //A little debug info //println( " ================================================== " ); //println( "found " + level._ugvstartnodes.size + " ugvstartnodes" ); //println( "found " + level._ugvbacknodes.size + " ugvbacknodes" ); //println( "found " + level._ugvexitnodes.size + " ugvexitnodes" ); //println( "found " + level._ugvspawnnodes.size + " ugvspawnnodes" ); //println( " ================================================== " ); //find the start node nearest to the location the player selected on the mini map startNode = getNearestUGVNode( location, level._ugvspawnnodes ); //init num stops self._numstops = 0; self._time_is_up = 0; level._tankInUse = true; //self thread TankUpdate( startNode, waitNode ); //self thread tankUpdateReverse( startNode, waitNode ); self thread ugvUpdate( startNode ); self.owner hudUGVTimer( UGV_TIMEOUT_SEC, self ); self thread tankDamageMonitor(); level._tank = self; if ( level._teamBased ) { /* objIDAllies = maps\mp\gametypes\_gameobjects::getNextObjID(); objective_add( objIDAllies, "invisible", (0,0,0) ); objective_team( objIDAllies, "allies" ); level._tank.objID["allies"] = objIDAllies; objIDAxis = maps\mp\gametypes\_gameobjects::getNextObjID(); objective_add( objIDAxis, "invisible", (0,0,0) ); objective_team( objIDAxis, "axis" ); level._tank.objID["axis"] = objIDAxis; */ team = self.team; level._tank.team = team; level._tank.pers[ "team" ] = team; } //NOT USING VEHICLE MAIN WEAPON ANYMORE, model was split into vehicle and turret components //equip the vehicle with harrier 20mm bullets //self setVehWeapon( "harrier_20mm_mp" ); //TagZP removing ugv mini gun for now. mgTurret = spawnTurret( "misc_turret", self.origin, "ugv_main_turret_mp" ); //mgTurret = spawnTurret( "misc_turret", self.origin, "abrams_minigun_mp" ); //TagZP attach the turret to the UGV mgTurret linkTo( self, "TAG_TURRET", (0,0,0), (0,0,0) ); //mgTurret setModel( "weapon_minigun" ); mgTurret setModel( "nx_ugv_main_turret" ); mgTurret.angles = self.angles; mgTurret.owner = self.owner; mgTurret makeTurretInoperable(); self.mgTurret = mgTurret; self.mgTurret SetDefaultDropPitch( 0 ); oldangles = self.angles; self.angles = (0,0,0); tankTurretPoint = self getTagOrigin( "TAG_TURRET" ); self.angles = oldangles; offset = tankTurretPoint - self.origin; self thread waitForChangeTeams(); self thread waitForDisco(); self.timeLastFired = getTime(); neutralTargetEnt = spawn("script_origin", self.mgTurret getTagOrigin("tag_flash") ); neutralTargetEnt linkTo( self, "tag_origin", offset, (0,0,0) ); neutralTargetEnt hide(); self.neutralTarget = neutralTargetEnt; //TagZP not using UGV built in gun anymore, removed the turret and broke the the model into components //self thread tankGetTargets(); self thread destroyTank(); self thread tankGetMiniTargets(); self thread checkDanger(); //self thread watchForThreat(); //reacts to players about to fire with rockets /# self thread forceDirection(); #/ } hudUGVTimer( duration, tank ) { tank endon ( "death" ); self.ugvTimer = newClientHudElem( self ); self.ugvTimer.x = UGV_HUD_TIMER_POS_X; self.ugvTimer.y = UGV_HUD_TIMER_POS_Y; self.ugvTimer.alignX = "center"; self.ugvTimer.alignY = "bottom"; self.ugvTimer.horzAlign = "center_adjustable"; self.ugvTimer.vertAlign = "bottom_adjustable"; self.ugvTimer.fontScale = 2.5; self.ugvTimer setTimer( 1.0 ); self.ugvTimer.alpha = 1.0; self.ugvTimer setTimer( duration ); println( "done setting hud timer" ); tank thread UGVWaitForTimeout( duration, "ugv_time_is_up" ); } UGVWaitForTimeout( duration, msg ) { self endon( "death" ); wait duration; self.owner notify( msg ); self._time_is_up = 1; } destroyUGVTimer() { self.ugvTimer Destroy(); } moveTank( pathnode ) { println( "starting ugv killstreak path" ); self startPath( pathnode ); self waittill( "reached_end_node" ); println( "reached ugv killstreak path end" ); } ugvUpdate( startNode ) { println( "Starting UGV Killstreak Update Loop" ); total_time = 0; //allows the player to advance the tank when it is waiting at a path node self.owner NotifyOnPlayerCommand( "advance_tank_forwards", "+actionslot 1" ); self.owner NotifyOnPlayerCommand( "advance_tank_backwards", "+actionslot 2" ); self endon( "death" ); self attachPath( startNode ); wait( 0.1 ); //main tank update loop for ( ;; ) { self moveTank( startnode ); self._numstops = self._numstops + 1; if( self._time_is_up == 1 ) { break; } if( !isDefined( self.owner )) { continue; wait 0.05; } startnode = getNearestUGVNode( self.origin, level._ugvstartnodes ); /* msg = self.owner waittill_any_return( "advance_tank_forwards", "advance_tank_backwards", "ugv_time_is_up", "death" ); if( msg == "death" ) { //when a player dies, self.owner is temporarily undefined. So I am catching the player death, and reinstating the input command wait. continue; } //println( "msg = " + msg ); //move to next node, or to exit path if( self._numstops < UGV_MAX_STOPS && self._time_is_up == 0 ) { if( msg == "advance_tank_forwards" ) { //make sure we have at least one startnode in the list if( level._ugvstartnodes.size ) { startnode = getNearestUGVNode( self.origin, level._ugvstartnodes ); } } else if( msg == "advance_tank_backwards" ) { if( level._ugvbacknodes.size ) { startnode = getNearestUGVNode( self.origin, level._ugvbacknodes ); } } else { if( msg == "ugv_time_is_up" ) { break; } } } else { break; } */ } //if exit nodes are not defined blow up the tank. if( !level._ugvexitnodes.size ) { //destroy the ugv println( "^1Warning: UGV Exit path is undefined SELF DESTRUCT" ); self notify( "death" ); return; } //move to exit location exitnode = getNearestUGVNode( self.origin, level._ugvexitnodes ); self moveTank( exitnode ); //destroy the ugv self notify( "death" ); } //returns the node in nodelist that is the closest to pos getNearestUGVNode( pos, nodelist ) { num_nodes = nodelist.size; mindist = Distance2d( pos, nodelist[0].origin ); //println( "distance = " + mindist ); nodeidx = 0; cnt = 1; while( cnt < num_nodes ) { dist = Distance2d( pos, nodelist[cnt].origin ); //println( "distance = " + dist ); if( dist < mindist ) { nodeidx = cnt; mindist = dist; } cnt++; } //println( "closest node index is " + nodelist[nodeidx].targetname ); return nodelist[nodeidx]; } //given a node on the ugv path, this retunrs the last node in that path getPathEnd( pathnode ) { node = pathnode; while( IsDefined( node )) { if( !IsDefined( node.target )) return node; temp = GetVehicleNode( node.target, "targetname" ); if( !IsDefined( temp )) break; //return node; node = temp; } return node; } waitForChangeTeams() { self endon ( "death" ); self.owner endon ( "disconnect" ); self.owner waittill ( "joined_team" ); self.health = 0; self notify( "death" ); } waitForDisco() { self endon ( "death" ); self.owner waittill ( "disconnect" ); self.health = 0; self notify( "death" ); } /# forceDirection() { for ( ;; ) { if ( getDvar( "tankDir" ) != "" ) { forceDir = getDvar( "tankDir" ); if ( self.veh_pathdir != forceDir ) { if ( forceDir == "forward" ) self stopToForward(); else self stopToReverse(); } } wait ( 0.05 ); } } #/ //================================================================= // // Movement/Update Functions // //================================================================= setDirection( direction ) { if ( self.veh_pathdir != direction ) { if ( direction == "forward" ) self stopToForward(); else self stopToReverse(); } } setEngagementSpeed() { self endon( "death" ); self notify ( "path_abandoned" ); while ( isDefined( self.changingDirection ) ) wait ( 0.05 ); self vehicle_SetSpeed( self.engagementSpeed, 10, 10 ); self.speedType = "engage"; } setMiniEngagementSpeed() { self endon( "death" ); self notify ( "path_abandoned" ); while ( isDefined( self.changingDirection ) ) wait ( 0.05 ); self vehicle_SetSpeed( self.miniEngagementSpeed, 10, 10 ); self.speedType = "engage"; } setStandardSpeed() { self endon( "death" ); while ( isDefined( self.changingDirection ) ) wait ( 0.05 ); self vehicle_SetSpeed( self.standardSpeed, 10, 10 ); self.speedType = "standard"; } setEvadeSpeed() { self endon( "death" ); while ( isDefined( self.changingDirection ) ) wait ( 0.05 ); self vehicle_setSpeed( self.evadeSpeed, 15, 15 ); self.speedType = "evade"; wait(1.5); self vehicle_setSpeed( self.standardSpeed, 10, 10); } setDangerSpeed() { self endon( "death" ); while ( isDefined( self.changingDirection ) ) wait ( 0.05 ); self vehicle_SetSpeed( self.dangerSpeed, 5, 5 ); self.speedType = "danger"; } stopToReverse() { debugPrintLn2( "tank changing direction at " + getTime() ); self vehicle_setSpeed( 0, 5, 6 ); self.changingDirection = true; while ( self.veh_speed > 0 ) wait ( 0.05 ); wait( 0.25 ); self.changingDirection = undefined; debugPrintLn2( "tank done changing direction" ); self.veh_transmission = "reverse"; self.veh_pathDir = "reverse"; self vehicle_setSpeed( self.standardSpeed, 5, 6 ); } stopToForward() { debugPrintLn2( "tank changing direction at " + getTime() ); self vehicle_setSpeed( 0, 5, 6 ); self.changingDirection = true; while ( self.veh_speed > 0 ) wait ( 0.05 ); wait( 0.25 ); self.changingDirection = undefined; debugPrintLn2( "tank done changing direction" ); self.veh_transmission = "forward"; self.veh_pathDir = "forward"; self vehicle_setSpeed( self.standardSpeed, 5, 6 ); } checkDanger() { self endon( "death" ); targets = []; players = level._players; self.numEnemiesClose = 0; for( ;; ) { foreach (potentialTarget in players) { if ( !isDefined( potentialTarget ) ) continue; if ( potentialTarget.team == self.team ) { wait( .05 ); continue; } dist = Distance2d( potentialTarget.origin, self.origin ); if ( dist < 2048 ) { self.numEnemiesClose++; } wait( .05 ); } if ( isDefined( self.speedType ) && ( self.speedType == "evade" || self.speedType == "engage" ) ) { self.numEnemiesClose = 0; continue; } if ( self.numEnemiesClose > 1 ) self thread setDangerSpeed(); else self thread setStandardSpeed(); self.numEnemiesClose = 0; wait( .05 ); } } tankUpdate( startNode, waitNode ) { self endon( "tankDestroyed" ); self endon( "death" ); if ( !isDefined( level._graphNodes ) ) { println( "bailing from tankupdate no graphnodes" ); self startPath( startNode ); return; } self attachPath( startNode ); self startPath( startNode ); startNode notify ( "trigger", self, true ); wait ( 0.05 ); for ( ;; ) { /# while ( getDvar( "tankDir" ) != "" ) wait ( 0.05 ); #/ while ( isDefined( self.changingDirection ) ) wait ( 0.05 ); endNode = self getNodeNearEnemies(); if ( isDefined( endNode ) ) self.endNode = endNode; else self.endNode = undefined; wait ( 0.65 ); } } Callback_VehicleDamage( inflictor, attacker, damage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName ) { if ( ( attacker == self || attacker == self.mgTurret || ( isDefined( attacker.pers ) && attacker.pers["team"] == self.team ) ) && ( attacker != self.owner || meansOfDeath == "MOD_MELEE" ) ) return; tankDamage = modifyDamage( meansOfDeath, damage, attacker ); self Vehicle_FinishDamage( inflictor, attacker, tankDamage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName ); } // accumulate damage and react tankDamageMonitor() { self endon( "death" ); self.damageTaken = 0; speed = self vehicle_GetSpeed(); maxHealth = self.health; stage1 = false; stage2 = false; stage3 = false; for( ;; ) { self waittill( "damage", amount, attacker, direction_vec, point, damageType ); if ( isDefined( attacker.classname ) && attacker.classname == "script_vehicle" ) { if ( isDefined( self.bestTarget ) && self.bestTarget != attacker ) { self.forcedTarget = attacker; println( "Abandoning Target due to vehicle attacker" ); self thread explicitAbandonTarget(); } } else { if ( isPlayer( attacker ) ) { attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "hitHelicopter" ); if ( attacker _hasPerk( "specialty_armorpiercing" ) ) { damageAdd = amount*level._armorPiercingMod; self.health -= int(damageAdd); } } } //stages will be used to effect effeciency of the tank //accuracy, speed, smoke emitters etc.... if ( self.health <= 0 ) { self notify( "death" ); print("sent death notify via script"); return; } else if (self.health < (maxHealth/4) && stage3 == false ) { //newSpeed = 4; //self vehicle_SetSpeed( newSpeed, 10, 10 ); //self.standardSpeed = newSpeed; stage3 = true; } else if (self.health < (maxHealth/2) && stage2 == false ) { //newSpeed = 6; //self vehicle_SetSpeed( newSpeed, 10, 10 ); //self.standardSpeed = newSpeed; stage2 = true; } else if (self.health < (maxHealth/1.5) && stage1 == false ) { //newSpeed = 10; //self vehicle_SetSpeed( newSpeed, 10, 10 ); //self.standardSpeed = newSpeed; stage1 = true; } if ( amount > 1000 ) { self handleThreat( attacker ); } } } handleThreat( attacker ) { self endon( "death" ); rand = randomInt(100); if ( isDefined( self.bestTarget) && self.bestTarget != attacker && rand > 30 ) { targ = []; targ[0] = self.bestTarget; explicitAbandonTarget( true, self.bestTarget ); self thread acquireTarget( targ ); } else if ( !isDefined( self.bestTarget ) && rand > 30 ) { targ = []; targ[0] = attacker; self thread acquireTarget( targ ); } else if ( rand < 30 ) { // all we know here is that it didnt hit the 70% playFX( level._tankCover, self.origin); self thread setEvadeSpeed(); } else { self fireWeapon(); self playSound( "bmp_fire" ); } } handlePossibleThreat( attacker ) { self endon( "death" ); position = relativeAngle( attacker ); distance = distance( self.origin, attacker.origin ); if ( RandomInt( 4 ) < 3 ) return; if( position == "front" && distance < 768 ) //attempts to crush player { self thread setEvadeSpeed(); } else if ( position == "rear_side" || ( position == "rear" && distance >= 768 ) ) { playFX( level._tankCover, self.origin); self thread setEvadeSpeed(); } else if( position == "rear" && distance < 768 ) //attempts to crush player { self stopToReverse(); self setEvadeSpeed(); wait( 4 ); self stopToForward(); } else if( position == "front_side" || position == "front" ) { playFX( level._tankCover, self.origin); self stopToReverse(); self setEvadeSpeed(); wait( 8 ); self stopToForward(); } } relativeAngle( ent1 ) { self endon( "death" ); ent1 endon( "death" ); ent1 endon( "disconnect" ); tankForwardVector = anglesToForward( self.angles ); tankToEnt = ent1.origin - self.origin; tankForwardVector *= (1,1,0); tankToEnt *= (1,1,0 ); tankToEnt = VectorNormalize( tankToEnt ); TankForwardVector = VectorNormalize( tankForwardVector ); targetCosine = VectorDot( tankToEnt, tankForwardVector ); if ( targetCosine > 0 ) { if ( targetCosine > .9 ) return "front"; else return "front_side"; } else { if ( targetCosine < -.9 ) return "rear"; else return "rear_side"; } ent1 iPrintLnBold( targetCosine ); } watchForThreat() { self endon( "death" ); for ( ;; ) { targets = []; players = level._players; foreach (player in players) { if ( !isDefined( player ) ) { wait( .05 ); continue; } if ( !isTarget( player ) ) { wait ( .05 ); continue; } currentWeapon = player GetCurrentWeapon(); if ( isSubStr( currentWeapon, "at4" ) || isSubStr( currentWeapon, "stinger" ) || isSubStr( currentWeapon, "javelin" ) ) { self thread handlePossibleThreat( player ); wait( 8 ); } wait( .15 ); } } } //================================================================= // // Accessory Functions // //================================================================= // checks if owner is valid, returns false if not valid checkOwner() { if ( !isdefined( self.owner ) || !isdefined( self.owner.pers["team"] ) || self.owner.pers["team"] != self.team ) { self notify ( "abandoned" ); return false; } return true; } drawLine( start, end, timeSlice, color ) { drawTime = int(timeSlice * 20); for( time = 0; time < drawTime; time++ ) { line( start, end, color,false, 1 ); wait ( 0.05 ); } } modifyDamage( damageType, amount, attacker ) { if ( damageType == "MOD_RIFLE_BULLET" ) return ( amount ); else if ( damageType == "MOD_PISTOL_BULLET" ) return ( amount ); else if ( damageType == "MOD_IMPACT" ) return ( amount ); else if (damageType == "MOD_MELEE" ) return ( 0 ); else if (damageType == "MOD_EXPLOSIVE_BULLET" ) return ( amount ); else if (damageType == "MOD_GRENADE" ) return ( amount * 5 ); else if (damageType == "MOD_GRENADE_SPLASH" ) return ( amount * 5 ); else return amount * 10; } destroyTank() { self waittill ( "death" ); //remove hud timer if the player has not disconnected if( isDefined( self.owner )) { self.owner destroyUGVTimer(); } /* if ( level._teamBased ) { team = level._tank.team; objective_state( level._tank.objID[team], "invisible" ); objective_state( level._tank.objID[level._otherTeam[team]], "invisible" ); if( level._multiTeamBased ) { for( i = 0; i < level._teamNameList.size; i++ ) { if( level._teamNameList[i] != team ) { objective_state( level._tank.objID[level._teamNameList[i]], "invisible" ); } } } } */ // award attacker self notify( "tankDestroyed" ); self Vehicle_SetSpeed( 0,10,10 ); level._tankInUse = false; playFX( level._spawnFire, self.origin); playFX( level._tankFire, self.origin); self removeFromTankList(); destroyedTank = Spawn( "script_model", self.origin ); // set model to current destroyed model. // if ( self.team == "allies" ) // destroyedTank setModel( "vehicle_bradley_destroyed" ); // else destroyedTank setModel( "vehicle_bmp_dsty" ); destroyedTank.angles = self.angles; self.mgTurret delete(); self delete(); wait(4); destroyedTank delete(); } //================================================================= // // Main Weapon Targeting Functions // //================================================================= onHitPitchClamp() { self notify( "onTargOrTimeOut" ); self endon( "onTargOrTimeOut" ); self endon( "turret_on_target" ); self waittill( "turret_pitch_clamped" ); println( "Abandoning Target due to turret not being able to reach target" ); self thread explicitAbandonTarget( false, self.bestTarget ); } fireOnTarget() { self endon( "abandonedTarget" ); self endon( "killedTarget" ); self endon( "death" ); self endon( "targetRemoved" ); self endon( "lostLOS" ); for ( ;; ) { self onHitPitchClamp(); if ( !isDefined( self.bestTarget ) ) continue; flashOrigin = self.mgTurret GetTagOrigin( "tag_flash" ); trace = BulletTrace( self.origin, flashOrigin, false, self ); if ( trace["position"] != flashOrigin ) { println( "Abandoning Target due to turret not being able to reach target without clipping" ); self thread explicitAbandonTarget( false, self.bestTarget ); } trace = BulletTrace( flashOrigin, self.bestTarget.origin, true, self ); distance = Distance(self.origin, trace["position"] ); realDistance = Distance( self.bestTarget.origin, self.origin ); //hitting somthing not even close if ( distance < 384 || distance + 256 < realDistance ) { wait( .5 ); if ( distance > 384 ) { self waitForTurretReady(); self FireWeapon(); self playSound( "bmp_fire" ); self.timeLastFired = getTime(); } println( "Abandoning due to not hitting intended space" ); // Adjust forward or backward to hit target... // check angle of target position = relativeAngle( self.bestTarget ); //if ( position == "rear_side" ) // backup //if ( position == "front_side" ) self thread explicitAbandonTarget( false, self.bestTarget ); return; } self waitForTurretReady(); self FireWeapon(); self playSound( "bmp_fire" ); self.timeLastFired = getTime(); } } waitForTurretReady() { self endon( "abandonedTarget" ); self endon( "killedTarget" ); self endon( "death" ); self endon( "targetRemoved" ); self endon( "lostLOS" ); timeWaited = getTime() - self.timeLastFired; if ( timeWaited < 1499 ) wait( 1.5 - timeWaited/1000 ); } tankGetTargets( badTarget ) { self endon( "death" ); self endon( "leaving" ); targets = []; prof_begin( "tankTargets" ); for ( ;; ) { targets = []; players = level._players; if ( isDefined( self.forcedTarget ) ) { targets = []; targets[0] = self.ForcedTarget; self acquireTarget( targets ); self.forcedTarget = undefined; } if ( isDefined( level._harrier ) && level._harrier.team != self.team && isAlive( level._harrier ) ) { if( isVehicleTarget( level._tank ) ) targets[targets.size] = level._tank; } if ( isDefined( level._chopper ) && level._chopper.team != self.team && isAlive( level._chopper ) ) { if( isVehicleTarget( level._chopper ) ) targets[targets.size] = level._chopper; } foreach ( potentialTarget in players ) { if (!isDefined( potentialTarget ) ) { wait(.05); continue; } if ( isDefined( badTarget ) && potentialTarget == badTarget ) { continue; } if ( isTarget( potentialTarget ) ) { if( isDefined( potentialTarget ) ) { targets[targets.size] = potentialTarget; } } else { continue; } } if ( targets.size > 0 ) { self acquireTarget( targets ); } else wait( 1 ); } prof_end( "tankTargets" ); } acquireTarget( targets ) { self endon( "death" ); if ( targets.size == 1 ) self.bestTarget = targets[0]; else self.bestTarget = self getBestTarget( targets ); self thread setEngagementSpeed(); // slows tank down to fire on target // checks to abandon target //self thread lostTarget(); // sets lost LOS and time of lost target //self thread abandonTarget(); // if target is lost for 3+ seconds drops target and gets new one self thread watchTargetDeath( targets ); //abandons target when target killed self SetTurretTargetEnt( self.bestTarget, ( 0, 0, 48 )); // sets turret to target entity self fireOnTarget(); // fires on current target. self thread setNoTarget(); } setNoTarget() { self endon( "death" ); self setStandardSpeed(); self removeTarget(); self setTurretTargetEnt( self.neutralTarget ); } getBestTarget( targets ) { self endon( "death" ); mainGunPointOrigin = self.mgTurret getTagOrigin( "tag_flash" ); tankOrigin = self.origin; bestYaw = undefined; bestTarget = undefined; targetHasRocket = false; foreach ( targ in targets ) { angle = abs ( vectorToAngles ( ( targ.origin - self.origin ) )[1] ); cannonAngle = abs( self.mgTurret getTagAngles( "tag_flash" )[1] ); angle = abs ( angle - cannonAngle ); //vehicle priorities if ( isDefined( level._chopper ) && targ == level._chopper ) return targ; if ( isDefined( level._harrier ) && targ == level._harrier ) return targ; // in this calculation having a rocket removes 40d of rotation cost from best target calculation // to prioritize targeting dangerous targets. weaponsArray = targ GetWeaponsListItems(); foreach ( weapon in weaponsArray ) { if ( isSubStr( weapon, "at4" ) || isSubStr( weapon, "jav" ) || isSubStr( weapon, "c4" ) ) angle -= 40; } if ( !isDefined( bestYaw ) ) { bestYaw = angle; bestTarget = targ; } else if ( bestYaw > angle ) { bestYaw = angle; bestTarget = targ; } } return ( bestTarget ); } watchTargetDeath( targets ) { self endon( "abandonedTarget" ); self endon( "lostLOS" ); self endon( "death" ); self endon( "targetRemoved" ); bestTarg = self.bestTarget; bestTarg endon ( "disconnect" ); bestTarg waittill( "death" ); self notify( "killedTarget" ); self removeTarget(); self setStandardSpeed(); self thread setNoTarget(); } explicitAbandonTarget( noNewTarget, targ ) { self endon( "death" ); self notify( "abandonedTarget" ); println("ABANDON TARGET EXPLICIT"); self setStandardSpeed(); self thread Setnotarget(); self removeTarget(); if ( isDefined(targ) ) { self.badTarget = targ; badTargetReset(); } if ( isDefined(noNewTarget) && noNewTarget ) return; return; } badTargetReset() { self endon("death"); wait (1.5); self.badTarget = undefined; } removeTarget() { self notify( "targetRemoved" ); self.bestTarget = undefined; self.lastLostTime = undefined; } isVehicleTarget( potentialTarget ) { if ( distance2D( potentialTarget.origin, self.origin ) > 4096 ) return false; if ( distance( potentialTarget.origin , self.origin ) < 512 ) return false; return turretSightTrace( potentialTarget, false ); } isTarget( potentialTarget ) { self endon( "death" ); dist = distanceSquared( potentialTarget.origin, self.origin ); if ( !level._teamBased && isDefined( self.owner ) && potentialTarget == self.owner ) return false; if ( !isalive( potentialTarget ) || potentialTarget.sessionstate != "playing" ) return false; if ( dist > 4096*4096 ) return false; if ( dist < 512*512 ) return false; if ( !isdefined( potentialTarget.pers["team"] ) ) return false; if ( potentialTarget == self.owner ) 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 ( potentialTarget _hasPerk( "specialty_blindeye" ) ) return false; return self Vehicle_CanTurretTargetPoint( potentialTarget.origin, 1, self ); //return self turretSightTrace( potentialTarget, false ); } turretSightTrace( targ, debug ) { turretCanSeeTarget = targ sightConeTrace( self getTagOrigin( "tag_turret" ), self ); if ( turretCanSeeTarget < .7 ) { return false; } if ( isDefined(debug) && debug ) self thread drawLine( targ.origin, self getTagOrigin( "tag_turret" ), 10, (1,0,0) ); return true; } //================================================================= // // Secondary Weapon Targeting Functions // //================================================================= 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; } tankGetMiniTargets() { self endon( "death" ); self endon( "leaving" ); miniTargets = []; println( "Geting Mini Targets" ); for ( ;; ) { miniTargets = []; players = level._players; for (i = 0; i <= players.size; i++) { if ( isMiniTarget( players[i] ) ) { if( isdefined( players[i] ) ) miniTargets[miniTargets.size] = players[i]; } else { continue; } wait( .05 ); } if ( miniTargets.size > 0 ) { self acquireMiniTarget( miniTargets ); return; } else { wait( .5 ); } } } getBestMiniTarget( targets ) { self endon( "death" ); tankOrigin = self.origin; closest = undefined; bestTarget = undefined; foreach ( targ in targets ) { curDist = Distance( self.origin, targ.origin ); // in this calculation having a rocket javelin or c4 increases mini turret priority // to prioritize targeting dangerous targets. 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 ); } acquireMiniTarget( targets ) { self endon( "death" ); if ( targets.size == 1 ) self.bestMiniTarget = targets[0]; else self.bestMiniTarget = self getBestMiniTarget( targets ); if ( distance2D( self.origin, self.bestMiniTarget.origin) > 768 ) self thread setMiniEngagementSpeed(); self notify( "acquiringMiniTarget" ); self.mgTurret SetTargetEntity( self.bestMiniTarget, ( 0,0,42 ) ); // sets turret to target entity wait( .15 ); self thread fireMiniOnTarget(); // fires on current target. self thread watchMiniTargetDeath( targets ); //abandons target when target killed self thread watchMiniTargetDistance( targets ); self thread watchMiniTargetThreat( self.bestMiniTarget ); } 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 on best target"); while( 1 ) { if ( !isDefined ( self.mgTurret getTurretTarget( true ) ) ) { if ( !isDefined( noTargTime ) ) noTargTime = getTime(); curTime = getTime(); if ( noTargTime - curTime > 1 ) { noTargTime = undefined; self thread explicitAbandonMiniTarget(); return; } println("Waiting because the turret doesnt have a target" ); wait ( .5 ); continue; } // if ( getTime() > miniAcquiredTime + 1000 && !isDefined( self.bestTarget ) ) // { // if ( distance2D(self.origin, self.bestMiniTarget.origin ) > 768 ) // { // targets[0] = self.bestMiniTarget; // self acquireTarget( targets ); // } // } numShots = randomIntRange( 2, 6 ); for ( i = 0; i < numShots; i++ ) { println( "actually shooting turret" ); self.mgTurret ShootTurret(); wait ( .25 ); } wait ( randomFloatRange( 0.5, 3.0 ) ); } } watchMiniTargetDeath( targets ) { self endon( "abandonedMiniTarget" ); self endon( "death" ); if ( ! isDefined( self.bestMiniTarget ) ) return; self.bestMiniTarget waittill( "death" ); self notify( "killedMiniTarget" ); println( "Killed Mini Target" ); self.bestMiniTarget = undefined; self.mgTurret ClearTargetEntity(); self tankGetMiniTargets(); } 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 ); } } 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 ); } } explicitAbandonMiniTarget( noNewTarget ) { self notify( "abandonedMiniTarget" ); println( "ABANDONED MINI TARGET" ); self.bestMiniTarget = undefined; self.mgTurret ClearTargetEntity(); if ( isDefined(noNewTarget) && noNewTarget ) return; self thread tankGetMiniTargets(); return; } addToTankList() { level._tanks[self getEntityNumber()] = self; } removeFromTankList() { level._tanks[self getEntityNumber()] = undefined; } /************************************************************************* * * PATHFINDING AND PATH NODE FUNCTIONS * ***************************************************************************/ getNodeNearEnemies() { validEnemies = []; foreach ( player in level._players ) { if ( player.team == "spectator" ) continue; if ( player.team == self.team ) continue; if ( !isAlive( player ) ) continue; player.dist = 0; validEnemies[validEnemies.size] = player; } if ( !validEnemies.size ) return undefined; for ( i = 0; i < validEnemies.size; i++ ) { for ( j = i + 1; j < validEnemies.size; j++ ) { dist = distanceSquared( validEnemies[i].origin, validEnemies[j].origin ); validEnemies[i].dist += dist; validEnemies[j].dist += dist; } } bestPlayer = validEnemies[0]; foreach ( player in validEnemies ) { if ( player.dist < bestPlayer.dist ) bestPlayer = player; } bestOrigin = bestPlayer.origin; sortedNodes = sortByDistance( level._graphNodes, bestOrigin ); //thread drawLine( bestOrigin, sortedNodes[0].origin, 10.0, (1,0,1) ); return ( sortedNodes[0] ); } setupPaths() { tankNodes = []; startNodes = []; endNodes = []; aStarGraphNodes = []; // setup the start node tankNode = GetVehicleNode( "startnode", "targetname" ); tankNodes[tankNodes.size] = tankNode; startNodes[startNodes.size] = tankNode; while ( isDefined( tankNode.target ) ) { lastNode = tankNode; tankNode = GetVehicleNode( tankNode.target, "targetname" ); tankNode.prev = lastNode; // case for connected path if ( tankNode == tankNodes[0] ) break; tankNodes[tankNodes.size] = tankNode; // case for disconnected path if ( !isDefined( tankNode.target ) ) return; } tankNodes[0].branchNodes = []; tankNodes[0] thread handleBranchNode( "forward" ); aStarGraphNodes[aStarGraphNodes.size] = tankNodes[0]; // find the start and end nodes of the branches branchNodes = GetVehicleNodeArray( "branchnode", "targetname" ); foreach ( branchNode in branchNodes ) { tankNode = branchNode; tankNodes[tankNodes.size] = tankNode; startNodes[startNodes.size] = tankNode; while ( isDefined( tankNode.target ) ) { lastNode = tankNode; tankNode = GetVehicleNode( tankNode.target, "targetname" ); tankNodes[tankNodes.size] = tankNode; tankNode.prev = lastNode; if ( !isDefined( tankNode.target ) ) { endNodes[endNodes.size] = tankNode; } } } // detect and initialize the branch nodes. These will be used for the aStar node graph foreach ( tankNode in tankNodes ) { isBranchNode = false; foreach ( startNode in startNodes ) { if ( startNode == tankNode ) continue; if ( startNode.target == tankNode.targetname ) continue; if ( isDefined( tankNode.target ) && tankNode.target == startNode.targetname ) continue; if ( distance2d( tankNode.origin, startNode.origin ) > 80 ) continue; startNode thread handleCapNode( tankNode, "reverse" ); startNode.prev = tankNode; if ( !isDefined( tankNode.branchNodes ) ) tankNode.branchNodes = []; tankNode.branchNodes[tankNode.branchNodes.size] = startNode; isBranchNode = true; } if ( isBranchNode ) { tankNode thread handleBranchNode( "forward" ); } isJoinNode = false; foreach ( endNode in endNodes) { if ( endNode == tankNode ) continue; if ( !isDefined( tankNode.target ) ) continue; if ( tankNode.target == endNode.targetname ) continue; if ( isDefined( endNode.target ) && endNode.target == tankNode.targetname ) continue; if ( distance2d( tankNode.origin, endNode.origin ) > 80 ) continue; endNode thread handleCapNode( tankNode, "forward" ); endNode.next = getVehicleNode( tankNode.targetname, "targetname" ); //endNode.target = tankNode.targetname; // READ-ONLY field... endNode.length = distance( endNode.origin, tankNode.origin ); if ( !isDefined( tankNode.branchNodes ) ) tankNode.branchNodes = []; tankNode.branchNodes[tankNode.branchNodes.size] = endNode; isJoinNode = true; } if ( isJoinNode ) { assert( !isBranchNode ); tankNode thread handleBranchNode( "reverse" ); } if ( isJoinNode || isBranchNode ) aStarGraphNodes[aStarGraphNodes.size] = tankNode; } if ( aStarGraphNodes.size < 3 ) { level notify ( "end_tankPathHandling" ); return; } // subdivide the path a bit... segmentNodes = []; foreach( tankNode in tankNodes ) { if ( !isDefined( tankNode.branchNodes ) ) continue; segmentNodes[segmentNodes.size] = tankNode; } foreach ( segmentNode in segmentNodes ) { tankNode = segmentNode; pathLength = 0; while ( isDefined( tankNode.target ) ) { prevNode = tankNode; tankNode = GetVehicleNode( tankNode.target, "targetname" ); pathLength += distance( tankNode.origin, prevNode.origin ); if ( tankNode == segmentNode ) break; if ( isDefined( tankNode.branchNodes ) ) break; } if ( pathLength > 1000 ) { tankNode = segmentNode; curLength = 0; while ( isDefined( tankNode.target ) ) { prevNode = tankNode; tankNode = GetVehicleNode( tankNode.target, "targetname" ); curLength += distance( tankNode.origin, prevNode.origin ); if ( curLength < pathLength / 2 ) continue; tankNode.branchNodes = []; // necessary? tankNode thread handleBranchNode( "forward" ); aStarGraphNodes[aStarGraphNodes.size] = tankNode; break; } } } level._graphNodes = initNodeGraph( aStarGraphNodes ); if( !isDefined(level._graphNodes) ) { println("level.graphnodes undefined"); } foreach ( tankNode in tankNodes ) { if ( !isDefined( tankNode.graphId ) ) tankNode thread nodeTracker(); } } getRandomBranchNode( direction ) { branchNodes = []; foreach ( graphId, linkNode in self.links ) { // pick a branch in the direction we're already heading if ( self.linkDirs[graphId] != direction ) continue; branchNodes[branchNodes.size] = linkNode; } return ( branchNodes[randomInt( branchNodes.size )] ); } getNextNodeForEndNode( endNode, direction ) { graphNode = level._graphNodes[self.graphId]; continuePath = generatePath( graphNode, endNode, undefined, direction ); continueG = continuePath[0].g; changePath = generatePath( graphNode, endNode, undefined, level._otherDir[direction] ); changeG = changePath[0].g; // temporarily force the tank to only go forward if ( !getDvarInt( "tankDebug" ) ) changeG = 9999999; if ( continueG <= changeG ) return ( continuePath[1] ); } handleBranchNode( direction ) { level endon ( "end_tankPathHandling" ); for ( ;; ) { self waittill( "trigger", tank, wasForced ); graphNode = level._graphNodes[self.graphId]; tank.node = self; nextGraphNode = undefined; if ( isDefined( tank.endNode ) && tank.endNode != graphNode ) { nextGraphNode = getNextNodeForEndNode( tank.endNode, tank.veh_pathdir ); if ( !isDefined( nextGraphNode ) ) tank thread setDirection( level._otherDir[tank.veh_pathdir] ); } if ( !isDefined( nextGraphNode ) || nextGraphNode == graphNode ) { nextGraphNode = graphNode getRandomBranchNode( tank.veh_pathdir ); } goalNode = graphNode.linkStartNodes[nextGraphNode.graphId]; if ( tank.veh_pathdir == "forward" ) nextLinkNode = self getNextNode(); else nextLinkNode = self getPrevNode(); // if we're already on this path, just keep going if ( nextLinkNode != goalNode ) tank startPath( goalNode ); } } handleCapNode( joinNode, direction ) { for ( ;; ) { self waittill( "trigger", tank ); if ( tank.veh_pathdir != direction ) continue; debugPrintLn2( "tank starting path at join node: " + joinNode.graphId ); tank startPath( joinNode ); } } nodeTracker() { self.forwardGraphId = getForwardGraphNode().graphId; self.reverseGraphId = getReverseGraphNode().graphId; for ( ;; ) { self waittill ( "trigger", tank, wasForced ); tank.node = self; /# if ( getDvarInt( "tankForceTrigger" ) ) { if ( tank.veh_pathdir == "forward" ) tank thread forceTrigger( self, self getNextNode(), tank ); else tank thread forceTrigger( self, self getPrevNode(), tank ); } #/ tank.forwardGraphId = self.forwardGraphId; tank.reverseGraphId = self.reverseGraphId; if ( !isDefined( self.target ) || self.targetname == "branchnode" ) nodeType = "TRANS"; else nodeType = "NODE"; if ( isDefined( wasForced ) ) debugPrint3D( self.origin, nodeType, (1,0.5,0), 1, 2, 100 ); else debugPrint3D( self.origin, nodeType, (0,1,0), 1, 2, 100 ); } } forceTrigger( prevNode, nextNode, tank ) { nextNode endon ( "trigger" ); prevNode endon ( "trigger" ); tank endon ( "death" ); minDist = distanceSquared( tank.origin, nextNode.origin ); tankDir = tank.veh_pathdir; debugPrint3D( prevNode.origin+(0,0,30), "LAST", (0,0,1), 0.5, 1, 100 ); debugPrint3D( nextNode.origin+(0,0,60), "NEXT", (0,1,0), 0.5, 1, 100 ); timeOutNextFrame = false; for ( ;; ) { wait ( 0.05 ); // tank changed direction if ( tankDir != tank.veh_pathdir ) { debugPrintLn2( "tank missed node: reversing direction" ); tank thread forceTrigger( nextNode, prevNode, tank ); return; } if ( timeOutNextFrame ) { debugPrintLn2( "... sending notify." ); nextNode notify ( "trigger", tank, true ); return; } curDist = distanceSquared( tank.origin, nextNode.origin ); if ( curDist > minDist ) { timeOutNextFrame = true; debugPrintLn2( "tank missed node: forcing notify in one frame..." ); } minDist = curDist; } } getForwardGraphNode() { assert( !isDefined( self.graphId ) ); checkNode = self; while ( !isDefined( checkNode.graphId ) ) checkNode = checkNode getNextNode(); return checkNode; } getReverseGraphNode() { assert( !isDefined( self.graphId ) ); checkNode = self; while ( !isDefined( checkNode.graphId ) ) checkNode = checkNode getPrevNode(); return checkNode; } getNextNode() { if ( isDefined( self.target ) ) return ( GetVehicleNode( self.target, "targetname" ) ); else return ( self.next ); } getPrevNode() { return self.prev; } // Builds the aStar node graph initNodeGraph( astarBaseNodes ) { graphNodes = []; foreach ( pathNode in aStarBaseNodes ) { graphNode = spawnStruct(); graphNode.linkInfos = []; graphNode.links = []; graphNode.linkLengths = []; graphNode.linkDirs = []; graphNode.linkStartNodes = []; graphNode.node = pathNode; graphNode.origin = pathNode.origin; graphNode.graphId = graphNodes.size; pathNode.graphId = graphNodes.size; debugPrint3D( graphNode.origin + (0,0,80), graphNode.graphId, (1,1,1), 0.65, 2, 100000 ); graphNodes[graphNodes.size] = graphNode; } foreach ( pathNode in aStarBaseNodes ) { graphId = pathNode.graphId; checkNode = GetVehicleNode( pathNode.target, "targetname" ); linkLength = distance( pathNode.origin, checkNode.origin ); linkStartNode = checkNode; while ( !isDefined( checkNode.graphId ) ) { linkLength += distance( checkNode.origin, checkNode.prev.origin ); if ( isDefined( checkNode.target ) ) checkNode = GetVehicleNode( checkNode.target, "targetname" ); else checkNode = checkNode.next; } assert( checkNode != pathNode ); graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "forward", linkStartNode ); checkNode = pathNode.prev; linkLength = distance( pathNode.origin, checkNode.origin ); linkStartNode = checkNode; while ( !isDefined( checkNode.graphId ) ) { linkLength += distance( checkNode.origin, checkNode.prev.origin ); checkNode = checkNode.prev; } assert( checkNode != pathNode ); graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "reverse", linkStartNode ); foreach ( branchNode in pathNode.branchNodes ) { checkNode = branchNode; linkLength = distance( pathNode.origin, checkNode.origin ); linkStartNode = checkNode; if ( checkNode.targetname == "branchnode" ) { while ( !isDefined( checkNode.graphId ) ) { if ( isDefined( checkNode.target ) ) nextNode = GetVehicleNode( checkNode.target, "targetname" ); else nextNode = checkNode.next; linkLength += distance( checkNode.origin, nextNode.origin ); checkNode = nextNode; } assert( checkNode != pathNode ); graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "forward", linkStartNode ); } else { while ( !isDefined( checkNode.graphId ) ) { linkLength += distance( checkNode.origin, checkNode.prev.origin ); checkNode = checkNode.prev; } assert( checkNode != pathNode ); graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "reverse", linkStartNode ); } } } return graphNodes; } addLinkNode( graphNode, linkLength, linkDir, linkStartNode ) { assert( self.graphId != graphNode.graphId ); assert( !isDefined( self.links[graphNode.graphId] ) ); self.links[graphNode.graphId] = graphNode; self.linkLengths[graphNode.graphId] = linkLength; self.linkDirs[graphNode.graphId] = linkDir; self.linkStartNodes[graphNode.graphId] = linkStartNode; linkInfo = spawnStruct(); linkInfo.toGraphNode = graphNode; linkInfo.toGraphId = graphNode.graphId; linkInfo.length = linkLength; linkInfo.direction = linkDir; linkInfo.startNode = linkStartNode; self.linkInfos[graphNode.graphId] = linkInfo; } // call function as generatePath(startNode, destNode), otherwise paths will be reversed generatePath( destNode, startNode, blockedNodes, direction ) { level._openList = []; level._closedList = []; foundPath = false; pathNodes = []; if ( !isDefined( blockedNodes ) ) blockedNodes = []; startNode.g = 0; startNode.h = getHValue( startNode, destNode ); startNode.f = startNode.g + startNode.h; addToClosedList( startNode ); curNode = startNode; for ( ;; ) { foreach ( linkId, checkNode in curNode.links ) { if ( is_in_array( blockedNodes, checkNode ) ) continue; if ( is_in_array( level._closedList, checkNode ) ) continue; if ( isDefined( direction ) && checkNode.linkDirs[curNode.graphId] != direction ) continue; if ( !is_in_array( level._openList, checkNode ) ) { addToOpenList( checkNode ); checkNode.parentNode = curNode; checkNode.g = getGValue( checkNode, curNode ); checkNode.h = getHValue( checkNode, destNode ); checkNode.f = checkNode.g + checkNode.h; if ( checkNode == destNode ) foundPath = true; } else { if ( checkNode.g < getGValue( curNode, checkNode ) ) continue; checkNode.parentNode = curNode; checkNode.g = getGValue( checkNode, curNode ); checkNode.f = checkNode.g + checkNode.h; } } if ( foundPath ) break; addToClosedList( curNode ); bestNode = level._openList[0]; foreach ( testNode in level._openList ) { if ( testNode.f > bestNode.f ) continue; bestNode = testNode; } assert( isDefined( bestNode ) ); // the tank should always have a path addToClosedList( bestNode ); curNode = bestNode; } assert( isDefined( destNode.parentNode ) ); curNode = destNode; while (curNode != startNode) { pathNodes[pathNodes.size] = curNode; curNode = curNode.parentNode; } pathNodes[pathNodes.size] = curNode; return pathNodes; } addToOpenList( node ) { node.openListID = level._openList.size; level._openList[level._openList.size] = node; node.closedListID = undefined; } addToClosedList( node ) { if (isdefined (node.closedListID)) return; node.closedListID = level._closedList.size; level._closedList[level._closedList.size] = node; if (!is_in_array (level._openList, node)) return; level._openList[node.openListID] = level._openList[level._openList.size - 1]; level._openList[node.openListID].openListID = node.openListID; level._openList[level._openList.size - 1] = undefined; node.openListID = undefined; } getHValue (node1, node2) { return (distance (node1.node.origin, node2.node.origin)); } getGValue(node1, node2) { return ( node1.parentNode.g + node1.linkLengths[node2.graphId] ); } is_in_array( aeCollection, eFindee ) { for ( i = 0; i < aeCollection.size; i++ ) { if ( aeCollection[ i ] == eFindee ) return( true ); } return( false ); } drawPath( pathNodes ) { for ( i = 1; i < pathNodes.size; i++ ) { startNode = pathNodes[i-1]; endNode = pathNodes[i]; if ( startNode.linkDirs[endNode.graphId] == "reverse" ) level thread drawLink( startNode.node.origin, endNode.node.origin, (1,0,0) ); else level thread drawLink( startNode.node.origin, endNode.node.origin, (0,1,0) ); vehNode = startNode.linkStartNodes[endNode.graphId]; level thread drawLink( startNode.node.origin + (0,0,4), vehNode.origin + (0,0,4), (0,0,1) ); if ( startNode.linkDirs[endNode.graphId] == "reverse" ) { while ( !isDefined( vehNode.graphId ) ) { lastVehNode = vehNode; vehNode = vehNode.prev; level thread drawLink( lastVehNode.origin + (0,0,4), vehNode.origin + (0,0,4), (0,1,1) ); } } else { while ( !isDefined( vehNode.graphId ) ) { lastVehNode = vehNode; if ( isDefined( vehNode.target ) ) vehNode = GetVehicleNode( vehNode.target, "targetname" ); else vehNode = vehNode.next; level thread drawLink( lastVehNode.origin + (0,0,4), vehNode.origin + (0,0,4), (0,1,1) ); } } } } drawGraph( pathNodes ) { /* level.pathZOffset = 0; foreach ( node in pathNodes ) { println( node.links.size ); foreach ( linkId, graphNode in node.links ) { if ( node.linkDirs[linkId] == "reverse" ) level thread drawLink( node.node.origin, graphNode.node.origin, (0,1,0) ); else level thread drawLink( node.node.origin, graphNode.node.origin, (1,0,0) ); //if ( node.linkDirs[linkId] == "reverse" ) // continue; //level thread drawLink( pathNodes[graphId].node.origin, pathNodes[node.graphId].node.origin, (randomFloat( 2 ), randomFloat( 2 ), randomFloat( 2 )) ); } } */ } drawLink( start, end, color ) { level endon ( "endpath" ); for ( ;; ) { line(start, end, color, true); wait 0.05; } } debugPrintLn2( printString ) { /# if ( getDvarInt( "tankDebug" ) ) printLn( printString ); #/ } debugPrint( printString ) { /# if ( getDvarInt( "tankDebug" ) ) print( printString ); #/ } debugPrint3D( origin, printString, color, alpha, scale, duration ) { /# if ( getDvarInt( "tankDebug" ) ) { print3d( origin, printString, color, alpha, scale, duration ); println( "3D: " + printString ); } #/ } drawTankGraphIds() { /# if ( getDvarInt( "tankDebug" ) ) { self notify ( "drawTankGraphIds" ); self endon ( "drawTankGraphIds" ); for ( ;; ) { print3d( self.origin + (0,0,128), "FW: " + self.forwardGraphId + " RV: " + self.reverseGraphId, (0,1,0), 1, 3, 1 ); wait ( 0.05 ); } } #/ } main( model ) { } //******************************************************************* // * // * //******************************************************************* /*QUAKED script_vehicle_nx_chinese_ugv_mp_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER maps\mp\killstreaks\_tank::main( "nx_vehicle_chinese_ugv" ); include,nx_vehicle_chinese_ugv_mp sound,nx_ugv,,all_sp defaultmdl="nx_vehicle_chinese_ugv" default:"vehicletype" "nx_ugv" default:"script_team" "allies" */