1 #using scripts\codescripts\struct;
3 #using scripts\shared\callbacks_shared;
4 #using scripts\shared\clientfield_shared;
5 #using scripts\shared\flagsys_shared;
6 #using scripts\shared\visionset_mgr_shared;
7 #using scripts\shared\spawner_shared;
8 #using scripts\shared\util_shared;
9 #using scripts\shared\ai_shared;
10 #using scripts\shared\ai_puppeteer_shared;
11 #using scripts\shared\ai\systems\blackboard;
12 #using scripts\shared\ai\systems\shared;
13 #using scripts\shared\abilities\_ability_player;
14 #using scripts\shared\abilities\_ability_util;
15 #using scripts\shared\array_shared;
16 #using scripts\shared\_oob;
17 #using scripts\shared\scoreevents_shared;
19 #insert scripts\shared\ai\systems\blackboard.gsh;
20 #insert scripts\shared\shared.gsh;
21 #insert scripts\shared\version.gsh;
22 #insert scripts\shared\abilities\_ability_util.gsh;
23 #insert scripts\shared\statstable_shared.gsh;
25 #using scripts\shared\system_shared;
30 #define CLONE_HEIGHT 72
31 #define CLONE_MAX_CLONES_ALLOWED 20 // don't change this.
32 #define CLONE_HEALTH_MULTIPLIER 1.5
33 #define CLONE_SPAWN_DISTANCE 60
34 #define CLONE_SPAWN_HEIGHT_OFFSET 5
35 #define CLONE_SPAWN_VELOCITY_OFFSET 17
36 #define CLONE_SPAWN_FACING_OFFSET 17
37 #define CLONE_SPAWN_FROM_PLAYER_MAX 500
38 #define CLONE_FIRST_POINT_DISTANCE 1000
39 #define CLONE_MAX_SEARCH_RADIUS_INITIAL 450
40 #define CLONE_MIN_SEARCH_RADIUS 500
41 #define CLONE_MAX_SEARCH_RADIUS 750
42 #define CLONE_PATH_DISTANCE_THRESHOLD 120
43 #define CLONE_PATH_DISTANCE_THRESHOLD_SQ CLONE_PATH_DISTANCE_THRESHOLD * CLONE_PATH_DISTANCE_THRESHOLD
44 #define FAKEFIRE_MAX_RANGE 750
45 #define FAKEFIRE_MAX_RANGE_SQR ( FAKEFIRE_MAX_RANGE * FAKEFIRE_MAX_RANGE )
46 #define FAKEFIRE_MIN_SHOTS 1
47 #define FAKEFIRE_MAX_SHOTS 4
48 #define FAKEFIRE_MIN_WAIT_DURATION .5
49 #define FAKEFIRE_MAX_WAIT_DURATION 3
50 #define ORB_TRAVEL_VELOCITY 800 // ( Max time is equivalent to CLONE_SPAWN_FROM_PLAYER_MAX / ORB_TRAVEL_VELOCITY )
51 #define ORB_HEIGHT_OFFSET ( 40 - CLONE_SPAWN_HEIGHT_OFFSET )
52 #define CLONE_ORB_FX "player/fx_plyr_clone_reaper_orb"
53 #define CLONE_VANISH_FX "player/fx_plyr_clone_vanish"
54 #define CLONE_APPEAR_FX "player/fx_plyr_clone_reaper_appear"
57 #precache( "fx", CLONE_VANISH_FX );
58 #precache( "fx", CLONE_ORB_FX );
59 #precache( "fx", CLONE_APPEAR_FX );
80 return self GadgetIsActive( slot );
86 return self GadgetFlickering( slot );
112 if( isDefined( player._clone ) )
114 foreach( clone
in player._clone )
116 if( isDefined( clone ) )
118 clone notify(
"clone_shutdown" );
127 ground_ent =
self GetGroundEnt();
128 return (!isdefined(ground_ent));
138 testangles[0] = ( 0, 0, 0);
139 testangles[1] = ( 0, -30, 0);
140 testangles[2] = ( 0, 30, 0);
141 testangles[3] = ( 0, -60, 0);
142 testangles[4] = ( 0, 60, 0);
143 testangles[5] = ( 0, 90, 0);
144 testangles[6] = ( 0, -90, 0);
145 testangles[7] = ( 0, 120, 0);
146 testangles[8] = ( 0, -120, 0);
147 testangles[9] = ( 0, 150, 0);
148 testangles[10] = ( 0, -150, 0);
149 testangles[11] = ( 0, 180, 0);
151 validSpawns = spawnStruct();
162 foreach( zoff
in zoffests )
164 for( i = 0; i < testangles.size; i++ )
166 startAngles[i] = ( 0, angles[1], 0 );
167 startPoint = origin + VectorScale( anglestoforward( startAngles[i] + testangles[i]), cloneDistance );
168 startPoint += ( 0, 0, zoff );
170 if( PlayerPositionValidIgnoreEnt( startPoint,
self ) )
173 if( isDefined( closestNavMeshPoint ) )
175 startPoint = closestNavMeshPoint;
178 const height_diff = 24;
179 trace = GroundTrace( startPoint + (0, 0, height_diff), startPoint - ( 0, 0, height_diff ),
false,
false,
false );
181 if ( IsDefined(
trace[
"position" ] ) )
183 startPoint =
trace[
"position" ];
186 validpositions[ validpositions.size ] = startPoint;
187 validAngles[ validAngles.size ] = startAngles[i] + testangles[i];
202 validspawns.validPositions = validpositions;
203 validspawns.validAngles = validAngles;
209 insertedClone =
false;
212 if( !isDefined( level._clone[i] ) )
214 level._clone[i] = clone;
215 insertedClone =
true;
218 PrintLn(
"inserted at index: " + i +
" clone count is: " + level._clone.size );
223 assert( insertedClone );
230 if( isDefined( level._clone[i] ) && ( level._clone[i] == clone ) )
232 level._clone[i] = undefined;
236 PrintLn(
"removed clone at index: " + i +
" clone count is: " + level._clone.size );
247 oldestClone = undefined;
250 if( !isDefined( oldestClone ) && isDefined( level._clone[i] ) )
252 oldestClone = level._clone[i];
255 else if( isDefined( level._clone[i] ) && ( level._clone[i].spawnTime < oldestClone.spawnTime ) )
257 oldestClone = level._clone[i];
264 PrintLn(
"Exceeded max clones: removing clone at index: " + i +
" clone count is: " + level._clone.size );
267 level._clone[oldestIndex] notify (
"clone_shutdown" );
268 level._clone[oldestIndex] = undefined;
275 self endon(
"death" );
280 velocity =
self getvelocity();
281 velocity = velocity + ( 0, 0, -velocity[2] );
282 velocity = Vectornormalize( velocity );
291 for ( index = 0; index < validExtendedSpawns.validPositions.size && validSpawns.validPositions.size <
CLONE_COUNT; index++ )
293 validSpawns.validPositions[ validSpawns.validPositions.size ] = validExtendedSpawns.validPositions[ index ];
294 validSpawns.validAngles[ validSpawns.validAngles.size ] = validExtendedSpawns.validAngles[ index ];
298 for( i = 0; i < validSpawns.validpositions.size; i++ )
300 travelDistance = Distance( validSpawns.validpositions[i],
self.origin );
302 self thread
_CloneOrbFx( validSpawns.validpositions[i], validspawns.spawnTimes[i] );
304 for( i = 0; i < validSpawns.validpositions.size; i++ )
316 "spawner_bo3_human_male_reaper_mp",
317 validSpawns.validpositions[i],
318 validSpawns.validAngles[i],
322 /# RecordCircle( validSpawns.validpositions[i], 2,
ORANGE,
"Animscript", clone ); #/
324 _ConfigureClone( clone,
self, AnglesToForward( validSpawns.validAngles[i] ), validspawns.spawnTimes[i] );
325 self._clone[
self._clone.size] = clone;
329 self notify(
"reveal_clone" );
346 #define CLONE_NOT_MOVING_DIST_SQ SQR( 24 )
347 #define CLONE_NOT_MOVING_POLL_TIME 2000
350 self endon(
"death" );
353 if ( GetDvarInt(
"tu1_gadgetCloneSwimming", 1 ) )
355 if ( (
self.origin[2] +
CLONE_HEIGHT / 2.0 ) <= GetWaterHeight(
self.origin ) )
359 self SetGoal(
self.origin,
true );
366 if ( GetDvarInt(
"tu1_gadgetCloneCrouching", 1 ) )
368 if ( !IsDefined(
self.lastKnownPos ) )
370 self.lastKnownPos =
self.origin;
371 self.lastKnownPosTime = GetTime();
386 self.lastKnownPos =
self.origin;
387 self.lastKnownPosTime = GetTime();
392 if( isDefined(
self._clone_goal ) )
394 distance = DistanceSquared(
self._clone_goal,
self.origin );
399 forward = AnglesToForward(
self getangles() );
401 self._goal_center_point = searchOrigin;
403 queryResult = PositionQuery_Source_Navigation(
404 self._goal_center_point,
411 if( queryResult.data.size == 0 )
413 queryResult = PositionQuery_Source_Navigation(
422 if( queryResult.data.size > 0 )
424 randIndex = RandomIntRange( 0, queryResult.data.size );
426 self setgoalpos( queryResult.data[ randIndex ].origin,
true );
427 self._clone_goal = queryresult.data[ randIndex ].origin;
439 spawnPos =
self GetTagOrigin(
"j_spine4" );
440 fxOrg =
spawn(
"script_model", spawnPos );
441 fxOrg SetModel(
"tag_origin" );
447 fxOrg MoveTo( fxEndPos, travelTime );
455 if ( GetDvarInt(
"tu1_gadgetCloneCopyLook", 1 ) )
457 if ( IsPlayer( player ) && IsAI( clone ) )
459 bodyModel = player GetCharacterBodyModel();
460 if ( IsDefined( bodyModel ) )
462 clone SetModel( bodyModel );
465 headModel = player GetCharacterHeadModel();
466 if ( IsDefined( headModel ) )
468 if ( IsDefined( clone.head ) )
470 clone Detach( clone.head );
473 clone Attach( headModel );
476 helmetModel = player GetCharacterHelmetModel();
477 if ( IsDefined( helmetModel ) )
479 clone Attach( helmetModel );
487 clone.isAiClone =
true;
488 clone.properName =
"";
489 clone.ignoreTriggerDamage =
true;
490 clone.minWalkDistance = 125;
492 clone.spawnTime = GetTime();
495 if ( GetDvarInt(
"tu1_aiPathableMaterials", 0 ) )
497 if ( IsDefined( clone.pathablematerial ) )
502 clone PushActors(
true );
503 clone PushPlayer(
true );
505 clone SetAvoidanceMask(
"avoid none" );
507 clone ASMSetAnimationRate( RandomFloatRange( 0.98, 1.02 ) );
519 clone._goal_center_point = GetClosestPointOnNavMesh( clone._goal_center_point, 600 );
521 queryResult = undefined;
523 if ( IsDefined( clone._goal_center_point ) &&
524 clone FindPath( clone.origin, clone._goal_center_point,
true,
false ) )
526 queryResult = PositionQuery_Source_Navigation(
527 clone._goal_center_point,
536 queryResult = PositionQuery_Source_Navigation(
545 if( queryResult.data.size > 0 )
547 clone setgoalpos( queryResult.data[0].origin,
true );
548 clone._clone_goal = queryresult.data[0].origin;
553 clone._goal_center_point = clone.origin;
558 clone thread
_show( spawnTime );
564 if( isDefined(
self ) )
568 PlaySoundAtPosition(
"mpl_clone_holo_death",
self.origin );
574 self waittill(
"death" );
576 if( isDefined(
self ) )
578 self stoploopsound();
589 clone.originalteam = player.team;
591 clone.ignoreall =
true;
592 clone.owner = player;
593 clone SetTeam( player.team );
594 clone.team = player.team;
595 clone SetEntityOwner( player );
600 self endon(
"death" );
606 self playloopsound(
"mpl_clone_gadget_loop_npc" );
616 if ( IsAlive(
self ) && isdefined( level.playGadgetSuccess ) )
618 self [[ level.playGadgetSuccess ]]( weapon,
"cloneSuccessDelay" );
624 self endon (
"death" );
632 if ( isdefined( attacker ) && isplayer( attacker ) )
634 if ( !level.teamBased || (clone.team != attacker.pers[
"team"]) )
636 if( isDefined( clone.isAiClone ) && clone.isAiClone )
644 function cloneDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, timeOffset, boneIndex, modelIndex, surfaceType, surfaceNormal )
648 if( weapon.isEmp && sMeansOfDeath ==
"MOD_GRENADE_SPLASH" )
651 self notify(
"clone_shutdown" );
654 if( isDefined( level.weaponLightningGun ) && weapon == level.weaponLightningGun )
657 self notify(
"clone_shutdown" );
660 supplyDrop = GetWeapon(
"supplydrop" );
661 if( isDefined( supplyDrop ) && supplyDrop == weapon )
664 self notify(
"clone_shutdown" );
673 clone notify(
"WatchCloneOwnerDisconnect" );
674 clone endon(
"WatchCloneOwnerDisconnect" );
675 clone endon(
"clone_shutdown" );
678 if( isDefined( clone ) )
680 clone notify(
"clone_shutdown" );
687 clone waittill(
"clone_shutdown" );
691 if( isdefined( clone ) )
693 if( !level.gameEnded )
699 clone stoploopsound();
709 clone endon(
"clone_shutdown" );
710 clone endon(
"death" );
723 clone endon(
"clone_shutdown" );
724 clone endon(
"death" );
731 if( isDefined( clone.fakeFireWeapon ) && ( clone.fakeFireWeapon != level.weaponnone ) )
733 players = GetPlayers();
734 foreach( player
in players )
736 if( isDefined( player ) && isAlive( player ) && ( player getteam() != clone.team ) )
740 if( clone cansee( player ) )
742 clone FakeFire( clone.owner, clone.origin, clone.fakeFireWeapon, shotsFired );
750 wait( shotsFired / 2 );
751 clone SetFakeFire(
false );
759 playerWeapon = player GetCurrentWeapon();
760 ball = GetWeapon(
"ball" );
761 if( IsDefined( playerWeapon ) && IsDefined( ball ) && ( playerWeapon == ball ) )
765 else if( IsDefined( playerWeapon.worldModel ) && (
_TestPlayerWeapon( playerWeapon, items[
"primary"] ) ) )
767 weapon = playerWeapon;
774 if( isDefined( weapon ) )
777 clone.fakeFireWeapon = weapon;
784 pixbeginevent(
"clone_build_item_list" );
803 if ( player IsItemLocked( number ) )
810 if ( allocation < 0 )
817 if ( !isdefined( items[slot] ) )
822 items[ slot ][ items[slot].size ] =
name;
832 classNum = RandomInt( 10 );
833 for( i = 0; i < 10; i++ )
835 weapon = player GetLoadoutWeapon( ( i + classNum ) % 10,
"primary" );
836 if( weapon != level.weaponnone )
846 if ( !isdefined( items ) || !items.size || !isdefined( playerweapon ) )
851 for( i = 0; i < items.size; i++ )
853 displayName = items[ i ];
855 if ( playerweapon.displayname == displayName )