#using scripts\codescripts\struct; #using scripts\shared\callbacks_shared; #using scripts\shared\clientfield_shared; #using scripts\shared\flagsys_shared; #using scripts\shared\visionset_mgr_shared; #using scripts\shared\spawner_shared; #using scripts\shared\util_shared; #using scripts\shared\ai_shared; #using scripts\shared\ai_puppeteer_shared; #using scripts\shared\ai\systems\blackboard; #using scripts\shared\ai\systems\shared; #using scripts\shared\abilities\_ability_player; #using scripts\shared\abilities\_ability_util; #using scripts\shared\array_shared; #using scripts\shared\_oob; #using scripts\shared\scoreevents_shared; #using scripts\shared\system_shared; function autoexec __init__sytem__() { system::register("gadget_clone",&__init__,undefined,undefined); } // don't change this. // ( Max time is equivalent to CLONE_SPAWN_FROM_PLAYER_MAX / ORB_TRAVEL_VELOCITY ) #precache( "fx", "player/fx_plyr_clone_vanish" ); #precache( "fx", "player/fx_plyr_clone_reaper_orb" ); #precache( "fx", "player/fx_plyr_clone_reaper_appear" ); function __init__() { ability_player::register_gadget_activation_callbacks( 42, &gadget_clone_on, &gadget_clone_off ); ability_player::register_gadget_possession_callbacks( 42, &gadget_clone_on_give, &gadget_clone_on_take ); ability_player::register_gadget_flicker_callbacks( 42, &gadget_clone_on_flicker ); ability_player::register_gadget_is_inuse_callbacks( 42, &gadget_clone_is_inuse ); ability_player::register_gadget_is_flickering_callbacks( 42, &gadget_clone_is_flickering ); callback::on_connect( &gadget_clone_on_connect ); clientfield::register( "actor", "clone_activated", 1, 1, "int" ); clientfield::register( "actor", "clone_damaged", 1, 1, "int" ); clientfield::register( "allplayers", "clone_activated", 1, 1, "int" ); level._clone = []; } function gadget_clone_is_inuse( slot ) { return self GadgetIsActive( slot ); } function gadget_clone_is_flickering( slot ) { // returns true when the gadget is flickering return self GadgetFlickering( slot ); } function gadget_clone_on_flicker( slot, weapon ) { // excuted when the gadget flickers } function gadget_clone_on_give( slot, weapon ) { } function gadget_clone_on_take( slot, weapon ) { } //self is the player function gadget_clone_on_connect() { // setup up stuff on player connect } function killClones( player ) { if( isDefined( player._clone ) ) { foreach( clone in player._clone ) { if( isDefined( clone ) ) { clone notify( "clone_shutdown" ); } } } } function is_jumping() { // checking PMF_JUMPING in code would give more accurate results ground_ent = self GetGroundEnt(); return (!isdefined(ground_ent)); } function CalculateSpawnOrigin( origin, angles, cloneDistance ) { player = self; startAngles = []; 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[5] = ( 0, 90, 0); testangles[6] = ( 0, -90, 0); testangles[7] = ( 0, 120, 0); testangles[8] = ( 0, -120, 0); testangles[9] = ( 0, 150, 0); testangles[10] = ( 0, -150, 0); testangles[11] = ( 0, 180, 0); validSpawns = spawnStruct(); validPositions = []; validAngles = []; zoffests = []; zoffests[0] = 5; zoffests[1] = 0; if( player is_jumping() ) zoffests[2] = -5; foreach( zoff in zoffests ) { for( i = 0; i < testangles.size; i++ ) { startAngles[i] = ( 0, angles[1], 0 ); startPoint = origin + VectorScale( anglestoforward( startAngles[i] + testangles[i]), cloneDistance ); startPoint += ( 0, 0, zoff ); if( PlayerPositionValidIgnoreEnt( startPoint, self ) ) { closestNavMeshPoint = GetClosestPointOnNavMesh( startpoint, 500 ); if( isDefined( closestNavMeshPoint ) ) { startPoint = closestNavMeshPoint; // Trace downward to find out the actual position on the terrain to spawn the clone. const height_diff = 24; trace = GroundTrace( startPoint + (0, 0, height_diff), startPoint - ( 0, 0, height_diff ), false, false, false ); if ( IsDefined( trace[ "position" ] ) ) { startPoint = trace[ "position" ]; } } validpositions[ validpositions.size ] = startPoint; validAngles[ validAngles.size ] = startAngles[i] + testangles[i]; if( validAngles.size == 3 ) { break; } } } if( validAngles.size == 3 ) { break; } } validspawns.validPositions = validpositions; validspawns.validAngles = validAngles; return validspawns; } function insertClone( clone ) { insertedClone = false; for( i = 0; i < 20; i++ ) { if( !isDefined( level._clone[i] ) ) { level._clone[i] = clone; insertedClone = true; /# PrintLn( "inserted at index: " + i + " clone count is: " + level._clone.size ); #/ break; } } assert( insertedClone ); } function removeClone( clone ) { for( i = 0; i < 20; i++ ) { if( isDefined( level._clone[i] ) && ( level._clone[i] == clone ) ) { level._clone[i] = undefined; array::remove_undefined( level._clone ); /# PrintLn( "removed clone at index: " + i + " clone count is: " + level._clone.size ); #/ break; } } } function removeOldestClone() { assert( level._clone.size == 20 ); oldestClone = undefined; for( i = 0; i < 20; i++ ) { if( !isDefined( oldestClone ) && isDefined( level._clone[i] ) ) { oldestClone = level._clone[i]; oldestIndex = i; } else if( isDefined( level._clone[i] ) && ( level._clone[i].spawnTime < oldestClone.spawnTime ) ) { oldestClone = level._clone[i]; oldestIndex = i; } } /# PrintLn( "Exceeded max clones: removing clone at index: " + i + " clone count is: " + level._clone.size ); #/ level._clone[oldestIndex] notify ( "clone_shutdown" ); level._clone[oldestIndex] = undefined; array::remove_undefined( level._clone ); } function spawnClones() // self is player { self endon( "death" ); self killClones( self ); self._clone = []; velocity = self getvelocity(); velocity = velocity + ( 0, 0, -velocity[2] ); velocity = Vectornormalize( velocity ); origin = self.origin + velocity * 17 + VectorScale( anglestoforward( self getangles() ), 17 ); validSpawns = CalculateSpawnOrigin( origin, self getangles(), 60 ); // If there weren't enough valid spawn positions, try extending the spawn distance to find additional spawn points. if ( validSpawns.validPositions.size < 3 ) { validExtendedSpawns = CalculateSpawnOrigin( origin, self getangles(), 60 * 3 ); for ( index = 0; index < validExtendedSpawns.validPositions.size && validSpawns.validPositions.size < 3; index++ ) { validSpawns.validPositions[ validSpawns.validPositions.size ] = validExtendedSpawns.validPositions[ index ]; validSpawns.validAngles[ validSpawns.validAngles.size ] = validExtendedSpawns.validAngles[ index ]; } } for( i = 0; i < validSpawns.validpositions.size; i++ ) { travelDistance = Distance( validSpawns.validpositions[i], self.origin ); validspawns.spawnTimes[i] = travelDistance / 800; self thread _CloneOrbFx( validSpawns.validpositions[i], validspawns.spawnTimes[i] ); } for( i = 0; i < validSpawns.validpositions.size; i++ ) { if( level._clone.size < 20 ) { // do nothing here } else { removeOldestClone(); } clone = SpawnActor( "spawner_bo3_human_male_reaper_mp", validSpawns.validpositions[i], validSpawns.validAngles[i], "", true ); /# RecordCircle( validSpawns.validpositions[i], 2, ( 1, .5, 0 ), "Animscript", clone ); #/ _ConfigureClone( clone, self, AnglesToForward( validSpawns.validAngles[i] ), validspawns.spawnTimes[i] ); self._clone[self._clone.size] = clone; insertClone( clone ); {wait(.05);}; } self notify( "reveal_clone" ); if( self oob::IsOutOfBounds() ) { gadget_clone_off( self, undefined ); } } function gadget_clone_on( slot, weapon ) { self clientfield::set( "clone_activated", 1 ); self flagsys::set( "clone_activated" ); fx = PlayFx( "player/fx_plyr_clone_reaper_appear", self.origin, AnglesToForward( self getangles() )); fx.team = self.team; thread spawnClones(); } function private _UpdateClonePathing() // self is clone { self endon( "death" ); while( true ) { if ( GetDvarInt( "tu1_gadgetCloneSwimming", 1 ) ) { if ( ( self.origin[2] + 72 / 2.0 ) <= GetWaterHeight( self.origin ) ) { Blackboard::SetBlackBoardAttribute( self, "_stance", "swim" ); self SetGoal( self.origin, true ); wait ( 0.5 ); continue; } } if ( GetDvarInt( "tu1_gadgetCloneCrouching", 1 ) ) { if ( !IsDefined( self.lastKnownPos ) ) { self.lastKnownPos = self.origin; self.lastKnownPosTime = GetTime(); } // If the clone hasn't moved by atleast CLONE_NOT_MOVING_DIST_SQ within // CLONE_NOT_MOVING_POLL_TIME then make the clone go crouched. if ( DistanceSquared( self.lastKnownPos, self.origin ) < ( (24) * (24) ) && !self HasPath() ) { Blackboard::SetBlackBoardAttribute( self, "_stance", "crouch" ); wait ( 0.5 ); continue; } if ( ( self.lastKnownPosTime + 2000 ) <= GetTime() ) { self.lastKnownPos = self.origin; self.lastKnownPosTime = GetTime(); } } distance = 0; if( isDefined( self._clone_goal ) ) { distance = DistanceSquared( self._clone_goal, self.origin ); } if( distance < 120 * 120 || !self HasPath() ) { forward = AnglesToForward( self getangles() ); searchOrigin = self.origin + forward * 750; self._goal_center_point = searchOrigin; 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 _CloneOrbFx( endPos, travelTime ) //self is player { spawnPos = self GetTagOrigin( "j_spine4" ); fxOrg = spawn( "script_model", spawnPos ); fxOrg SetModel( "tag_origin" ); fx = PlayFxOnTag( "player/fx_plyr_clone_reaper_orb", fxOrg, "tag_origin" ); fx.team = self.team; fxEndPos = endPos + ( 0, 0, ( 40 - 5 ) ); fxOrg MoveTo( fxEndPos, travelTime ); self util::waittill_any_timeout( traveltime, "death", "disconnect" ); fxOrg delete(); } function private _CloneCopyPlayerLook( clone, player ) { if ( GetDvarInt( "tu1_gadgetCloneCopyLook", 1 ) ) { if ( IsPlayer( player ) && IsAI( clone ) ) { bodyModel = player GetCharacterBodyModel(); if ( IsDefined( bodyModel ) ) { clone SetModel( bodyModel ); } headModel = player GetCharacterHeadModel(); if ( IsDefined( headModel ) ) { if ( IsDefined( clone.head ) ) { clone Detach( clone.head ); } clone Attach( headModel ); } helmetModel = player GetCharacterHelmetModel(); if ( IsDefined( helmetModel ) ) { clone Attach( helmetModel ); } } } } function private _ConfigureClone( clone, player, forward, spawnTime ) // self is player { clone.isAiClone = true; clone.properName = ""; clone.ignoreTriggerDamage = true; clone.minWalkDistance = 125; clone.overrideActorDamage = &cloneDamageOverride; clone.spawnTime = GetTime(); clone setmaxhealth( int(1.5 * level.playerMaxHealth) ); if ( GetDvarInt( "tu1_aiPathableMaterials", 0 ) ) { if ( IsDefined( clone.pathablematerial ) ) { clone.pathablematerial = clone.pathablematerial & ~(1 << 1); // Don't path through water. } } 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 ASMSetAnimationRate( RandomFloatRange( 0.98, 1.02 ) ); clone setclone(); clone _CloneCopyPlayerLook( clone, player ); clone _CloneSelectWeapon( player ); clone thread _CloneWatchDeath(); clone thread _CloneWatchOwnerDisconnect( player ); clone thread _CloneWatchShutdown(); clone thread _CloneFakeFire(); clone thread _CloneBreakGlass(); clone._goal_center_point = forward * 1000 + clone.origin; clone._goal_center_point = GetClosestPointOnNavMesh( clone._goal_center_point, 600 ); 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 ghost(); clone thread _show( spawnTime ); _ConfigureCloneTeam( clone, player, false ); } function private _PlayDematerialization() { if( isDefined( self ) ) { fx = PlayFx( "player/fx_plyr_clone_vanish", self.origin ); fx.team = self.team; PlaySoundAtPosition( "mpl_clone_holo_death", self.origin ); } } function private _CloneWatchDeath() { self waittill( "death" ); if( isDefined( self ) ) { self stoploopsound(); self _PlayDematerialization(); removeClone( self ); self delete(); } } function private _ConfigureCloneTeam( clone, player, isHacked ) { if ( isHacked == false ) { clone.originalteam = player.team; } clone.ignoreall = true; clone.owner = player; clone SetTeam( player.team ); clone.team = player.team; clone SetEntityOwner( player ); } function private _show( spawnTime ) { self endon( "death" ); wait( spawnTime ); self show(); self clientfield::set( "clone_activated", 1 ); fx = PlayFx( "player/fx_plyr_clone_reaper_appear", self.origin, AnglesToForward( self getangles() )); fx.team = self.team; self playloopsound( "mpl_clone_gadget_loop_npc" ); } function gadget_clone_off( slot, weapon ) { self clientfield::set( "clone_activated", 0 ); self flagsys::clear( "clone_activated" ); self killClones( self ); self _PlayDematerialization(); if ( IsAlive( self ) && isdefined( level.playGadgetSuccess ) ) { self [[ level.playGadgetSuccess ]]( weapon, "cloneSuccessDelay" ); } } function private _cloneDamaged() { self endon ( "death" ); self clientfield::set( "clone_damaged", 1 ); util::wait_network_frame(); self clientfield::set( "clone_damaged", 0 ); } function ProcessCloneScoreEvent( clone, attacker, weapon ) { if ( isdefined( attacker ) && isplayer( attacker ) ) { if ( !level.teamBased || (clone.team != attacker.pers["team"]) ) { if( isDefined( clone.isAiClone ) && clone.isAiClone ) { scoreevents::processScoreEvent( "killed_clone_enemy", attacker, clone, weapon ); } } } } function cloneDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, timeOffset, boneIndex, modelIndex, surfaceType, surfaceNormal ) { self thread _cloneDamaged(); if( weapon.isEmp && sMeansOfDeath == "MOD_GRENADE_SPLASH" ) { ProcessCloneScoreEvent( self, eAttacker, weapon ); self notify( "clone_shutdown" ); } if( isDefined( level.weaponLightningGun ) && weapon == level.weaponLightningGun ) { ProcessCloneScoreEvent( self, eAttacker, weapon ); self notify( "clone_shutdown" ); } supplyDrop = GetWeapon( "supplydrop" ); if( isDefined( supplyDrop ) && supplyDrop == weapon ) { ProcessCloneScoreEvent( self, eAttacker, weapon ); self notify( "clone_shutdown" ); } return iDamage; } function _CloneWatchOwnerDisconnect( player ) { clone = self; clone notify( "WatchCloneOwnerDisconnect" ); clone endon( "WatchCloneOwnerDisconnect" ); clone endon( "clone_shutdown" ); player util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); if( isDefined( clone ) ) { clone notify( "clone_shutdown" ); } } function _CloneWatchShutdown() { clone = self; clone waittill( "clone_shutdown" ); removeClone( clone ); if( isdefined( clone ) ) { if( !level.gameEnded ) // kill and do damage do nothing after game end { clone Kill(); } else { clone stoploopsound(); self _PlayDematerialization(); clone hide(); } } } function _CloneBreakGlass() { clone = self; clone endon( "clone_shutdown" ); clone endon( "death" ); while( true ) { clone util::break_glass(); wait 0.25; } } function _CloneFakeFire() { clone = self; clone endon( "clone_shutdown" ); clone endon( "death" ); while( true ) { waitTime = RandomFloatRange( .5, 3 ); wait( waitTime ); shotsFired = RandomIntRange( 1, 4 ); if( isDefined( clone.fakeFireWeapon ) && ( clone.fakeFireWeapon != level.weaponnone ) ) { players = GetPlayers(); foreach( player in players ) { if( isDefined( player ) && isAlive( player ) && ( player getteam() != clone.team ) ) { if( DistanceSquared( player.origin, clone.origin ) < ( 750 * 750 ) ) { if( clone cansee( player ) ) { clone FakeFire( clone.owner, clone.origin, clone.fakeFireWeapon, shotsFired ); break; } } } } } //clone SetFakeFire( true ); wait( shotsFired / 2 ); // Either fire number of shots or simulate firing that duration. May need to adjust this to get it even clone SetFakeFire( false ); } } function _CloneSelectWeapon( player ) { clone = self; items = _CloneBuildItemList( player ); playerWeapon = player GetCurrentWeapon(); ball = GetWeapon( "ball" ); if( IsDefined( playerWeapon ) && IsDefined( ball ) && ( playerWeapon == ball ) ) { weapon = ball; } else if( IsDefined( playerWeapon.worldModel ) && ( _TestPlayerWeapon( playerWeapon, items["primary"] ) ) ) { weapon = playerWeapon; } else { if ( isdefined( level.onCloneSelectWeapon ) ) { weapon = [[level.onCloneSelectWeapon]]( player ); } else { weapon = undefined; } if ( !isdefined( weapon ) ) { weapon = _ChooseWeapon( player ); } } if( isDefined( weapon ) ) { clone shared::placeWeaponOn( weapon, "right" ); clone.fakeFireWeapon = weapon; } } function _CloneBuildItemList( player ) { pixbeginevent( "clone_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 ( player IsItemLocked( number ) ) { continue; } allocation = Int( tableLookupColumnForRow( level.statsTableID, row, 12 ) ); if ( allocation < 0 ) { continue; } name = tableLookupColumnForRow( level.statsTableID, row, 3 ); if ( !isdefined( items[slot] ) ) { items[slot] = []; } items[ slot ][ items[slot].size ] = name; } } pixendevent(); return items; } function private _ChooseWeapon( player ) { classNum = RandomInt( 10 ); for( i = 0; i < 10; i++ ) { weapon = player GetLoadoutWeapon( ( i + classNum ) % 10, "primary" ); if( weapon != level.weaponnone ) { break; } } return weapon; } function private _TestPlayerWeapon( playerweapon, items ) { if ( !isdefined( items ) || !items.size || !isdefined( playerweapon ) ) { return false; } for( i = 0; i < items.size; i++ ) { displayName = items[ i ]; if ( playerweapon.displayname == displayName ) { return true; } } return false; }