2023-04-13 17:30:38 +02:00

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;
}