874 lines
31 KiB
Plaintext
874 lines
31 KiB
Plaintext
#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;
|
|
}
|