s1-scripts-dev/raw/maps/mp/_riotshield.gsc
2025-05-21 16:23:17 +02:00

814 lines
21 KiB
Plaintext

#include common_scripts\utility;
#include maps\mp\_utility;
init()
{
level.riot_shield_names = [];
level.riot_shield_names[level.riot_shield_names.size] = "riotshield_mp";
level.riot_shield_names[level.riot_shield_names.size] = "iw5_riotshieldt6_mp";
level.riot_shield_names[level.riot_shield_names.size] = "iw5_riotshieldt6loot0_mp";
level.riot_shield_names[level.riot_shield_names.size] = "iw5_riotshieldt6loot1_mp";
level.riot_shield_names[level.riot_shield_names.size] = "iw5_riotshieldt6loot2_mp";
level.riot_shield_names[level.riot_shield_names.size] = "iw5_riotshieldt6loot3_mp";
level.riot_shield_names[level.riot_shield_names.size] = "iw5_riotshieldjugg_mp";
precacheAnims();
// loadfx( "weapon/riotshield/fx_riotshield_depoly_lights" );
// loadfx( "weapon/riotshield/fx_riotshield_depoly_dust" );
level.riot_shield_collision = GetEnt( "riot_shield_collision", "targetname" );
level._effect[ "riot_shield_shock_fx" ] = LoadFX( "vfx/explosion/riotshield_stun" );
level._effect[ "riot_shield_deploy_smoke" ] = LoadFX( "vfx/smoke/riotshield_deploy_smoke" );
level._effect[ "riot_shield_deploy_lights" ] = LoadFX( "vfx/lights/riotshield_deploy_lights" );
}
#using_animtree ( "mp_riotshield" );
precacheAnims()
{
PrecacheMpAnim( "npc_deployable_riotshield_stand_deploy" );
PrecacheMpAnim( "npc_deployable_riotshield_stand_destroyed" );
PrecacheMpAnim( "npc_deployable_riotshield_stand_shot" );
PrecacheMpAnim( "npc_deployable_riotshield_stand_shot_back" );
PrecacheMpAnim( "npc_deployable_riotshield_stand_melee_front" );
PrecacheMpAnim( "npc_deployable_riotshield_stand_melee_back" );
}
hasRiotShield() // self == player
{
return ( IsDefined( self.frontShieldModel ) || IsDefined( self.backShieldModel ) );
}
hasRiotShieldEquipped() // self == player
{
return ( IsDefined( self.frontShieldModel ) );
}
weaponIsRiotShield( inWeaponName )
{
inBaseWeaponName = GetWeaponBaseName( inWeaponName );
if ( !IsDefined( inBaseWeaponName ) )
inBaseWeaponName = inWeaponName;
foreach( weaponName in level.riot_shield_names )
{
if ( weaponName == inBaseWeaponName )
return true;
}
return false;
}
weaponIsShockPlantRiotShield( inWeaponName )
{
if ( !weaponIsRiotShield( inWeaponName ) )
return false;
return IsSubStr( inWeaponName, "shockplant" );
}
getOtherRiotShieldName( inWeaponName )
{
foundInputWeapon = false;
weapons = self GetWeaponsListPrimaries();
foreach( weapon in weapons )
{
if ( weaponIsRiotShield( weapon ) )
{
if ( ( weapon == inWeaponName ) && !foundInputWeapon )
foundInputWeapon = true;
else
return weapon;
}
}
return undefined;
}
updateFrontAndBackShields( newWeapon ) // self == player
{
// note this function must play nice with _detachAll().
self.frontShieldModel = undefined;
self.backShieldModel = undefined;
if ( !IsDefined( newWeapon ) )
newWeapon = self GetCurrentPrimaryWeapon();
if ( weaponIsRiotShield( newWeapon ) )
{
self.frontShieldModel = GetWeaponModel( newWeapon );
}
otherShield = getOtherRiotShieldName( newWeapon );
if ( IsDefined( otherShield ) )
{
assert( weaponIsRiotShield( otherShield ) );
self.backShieldModel = GetWeaponModel( otherShield );
}
self RefreshShieldModels( newWeapon );
}
riotShield_clear()
{
self.frontShieldModel = undefined;
self.backShieldModel = undefined;
}
entIsStuckToShield()
{
if ( !self IsLinked() )
return false;
tagName = self GetLinkedTagName();
if ( !IsDefined( tagName ) )
return false;
switch( tagName )
{
case "tag_weapon_left": // Front shield
case "tag_shield_back": // Back shield
case "tag_inhand": // Exo shield
return true;
}
return false;
}
watchRiotShieldUse() // self == player
{
self endon( "death" );
self endon( "disconnect" );
self endon ( "faux_spawn" );
// watcher for attaching the model to correct player bones
self thread trackRiotShield();
for ( ;; )
{
self waittill( "raise_riotshield" );
self thread startRiotshieldDeploy();
}
}
riotshield_watch_for_change_weapon() // self == player
{
self endon( "death" );
self endon( "disconnect" );
self endon( "faux_spawn" );
self endon( "riotshield_change_weapon" );
newWeapon = undefined;
self waittill( "weapon_change", newWeapon );
self notify ( "riotshield_change_weapon", newWeapon );
}
riotshield_watch_for_start_change_weapon() // self == player
{
self endon( "death" );
self endon( "disconnect" );
self endon( "faux_spawn" );
self endon( "riotshield_change_weapon" );
newWeapon = undefined;
// SetDvarIfUninitialized( "scr_twoshield_wait", 1.0 );
while( 1 )
{
self waittill( "weapon_switch_started", newWeapon );
// Climbing a ladder instantly switches
if ( self IsOnLadder() )
{
self thread riotshield_watch_for_ladder_early_exit();
break;
}
// If you have two shields, handle the swap time manually
if ( IsDefined( self.frontShieldModel ) && IsDefined( self.backShieldModel ) )
{
// wait( GetDvarFloat( "scr_twoshield_wait", 1.0 ) );
wait( 0.5 );
break;
}
}
self notify ( "riotshield_change_weapon", newWeapon );
}
riotshield_watch_for_ladder_early_exit() // self == player
{
self endon( "death" );
self endon( "disconnect" );
self endon( "faux_spawn" );
self endon( "weapon_change" ); // Stop worrying once the ladder weapon change completes
while( self IsOnLadder() )
waitframe();
self notify ( "riotshield_change_weapon", self GetCurrentPrimaryWeapon() );
}
riotshield_watch_for_exo_shield_pullback() // self == player
{
self endon( "death" );
self endon( "disconnect" );
self endon( "faux_spawn" );
self endon( "riotshield_change_weapon" );
newWeapon = undefined;
exo_shield_weapon = maps\mp\_exo_shield::get_exo_shield_weapon();
self waittillmatch( "grenade_pullback", exo_shield_weapon );
// Allow time for the flag to get set
while( !IsDefined( self.exo_shield_on ) || !self.exo_shield_on )
waitframe();
self notify ( "riotshield_change_weapon", exo_shield_weapon );
}
riotshield_watch_for_exo_shield_release() // self == player
{
self endon( "death" );
self endon( "disconnect" );
self endon( "faux_spawn" );
self endon( "riotshield_change_weapon" );
if ( !IsDefined( self.exo_shield_on ) || !self.exo_shield_on )
return;
newWeapon = undefined;
exo_shield_weapon = maps\mp\_exo_shield::get_exo_shield_weapon();
self waittillmatch( "battery_discharge_end", exo_shield_weapon );
// Allow time for the flag to clear
while( IsDefined( self.exo_shield_on ) && self.exo_shield_on )
waitframe();
self notify ( "riotshield_change_weapon", self getCurrentWeapon() );
}
trackRiotShield() // self == player
{
self endon ( "death" );
self endon ( "disconnect" );
self endon ( "faux_spawn" );
self notify( "track_riot_shield" );
self endon ( "track_riot_shield" );
self updateFrontAndBackShields( self.currentWeaponAtSpawn );
self.lastNonShieldWeapon = "none";
for ( ;; )
{
self thread watchRiotshieldPickup();
prevWeapon = self getCurrentWeapon();
if ( IsDefined( self.exo_shield_on ) && self.exo_shield_on )
prevWeapon = maps\mp\_exo_shield::get_exo_shield_weapon();
self thread riotshield_watch_for_change_weapon();
self thread riotshield_watch_for_start_change_weapon();
self thread riotshield_watch_for_exo_shield_pullback();
self thread riotshield_watch_for_exo_shield_release();
self waittill ( "riotshield_change_weapon", newWeapon );
if ( weaponIsRiotShield( newWeapon ) )
{
if ( self hasRiotShield() )
{
if ( IsDefined( self.riotshieldTakeWeapon ))
{
self TakeWeapon( self.riotshieldTakeWeapon );
self.riotshieldTakeWeapon = undefined;
}
}
if( isValidNonShieldWeapon( prevWeapon ) )
{
self.lastNonShieldWeapon = prevWeapon;
}
}
updateRiotShieldAttachForNewWeapon( newWeapon );
}
}
updateRiotShieldAttachForNewWeapon( newWeapon ) // self == player
{
// Do nothing, we want to keep that weapon on their arm. Will get another changeweapon when finished mantling
if ( ( self IsMantling() ) && ( newWeapon == "none" ) )
return;
updateFrontAndBackShields( newWeapon );
}
watchRiotshieldPickup() // self == player
{
self endon ( "death" );
self endon ( "disconnect" );
self endon ( "track_riot_shield" );
self notify ( "watch_riotshield_pickup" );
self endon ( "watch_riotshield_pickup" );
// tagTMR<NOTE>: fix for rare case when riotshield is given by the server
// but the client fails to equip because of prone
self waittill ( "pickup_riotshield" );
self endon ( "weapon_change" );
/#println( "Picked up riotshield, expecting weapon_change notify..." );#/
wait 0.5;
/#println( "picked up shield but didn't change weapons, attach it!" );#/
updateRiotShieldAttachForNewWeapon( self getCurrentWeapon() );
}
isValidNonShieldWeapon( weapon )
{
if( maps\mp\_utility::isKillstreakWeapon( weapon ) )
return false;
if( weapon == "none" )
return false;
if ( maps\mp\gametypes\_class::isValidEquipment( weapon, true ) ||
maps\mp\gametypes\_class::isValidEquipment( weapon, false ) )
return false;
if ( weaponIsRiotShield( weapon ) )
return false;
if ( WeaponClass(weapon) == "ball" )
return false;
return true;
}
startRiotshieldDeploy() // self == player
{
self thread watchRiotshieldDeploy();
}
handleRiotShieldShockPlant() // self == player
{
shield_ent = self.riotshieldEntity;
assert( IsDefined( shield_ent ) );
min_damage = 10;
max_damage = 50;
radius = 150;
radius_sq = ( radius * radius );
event_origin = self.riotshieldEntity.origin + ( 0, 0, -25 );
self RadiusDamage( event_origin, radius, max_damage, min_damage, self, "MOD_EXPLOSIVE" );
PlayFX( level._effect[ "riot_shield_shock_fx" ], event_origin, AnglesToForward( self.riotshieldEntity.angles + ( -90, 0, 0 )) );
foreach( player in level.players )
{
if ( isReallyAlive( player ) && !IsAlliedSentient( player, self ) )
{
if ( DistanceSquared( event_origin, player.origin ) < radius_sq )
{
player ShellShock( "concussion_grenade_mp", 1 );
}
}
}
}
watchRiotshieldDeploy() // self == player
{
self endon( "death" );
self endon( "disconnect" );
self notify( "start_riotshield_deploy" );
self endon( "start_riotshield_deploy" );
self waittill( "startdeploy_riotshield" );
self PlaySound( "wpn_riot_shield_plant_mech" );
self waittill( "deploy_riotshield", deploy_attempt );
// if we have a deployed riotshield in the world, delete if we attempt to deploy another
if ( IsDefined( self.riotshieldEntity ))
{
self.riotshieldEntity thread damageThenDestroyRiotshield();
waitframe(); // give it some time to clean up
}
curWeapon = self GetCurrentWeapon();
self SetWeaponModelVariant( curWeapon, 0 );
shockVersion = weaponIsShockPlantRiotShield( curWeapon );
self PlaySound( "wpn_riot_shield_plant_punch" );
if ( shockVersion )
self PlaySound( "wpn_riot_shield_blast_punch" );
//self SetPlacementHint( 1 );
placement_hint = false;
if ( deploy_attempt )
{
placement = self CanPlaceRiotshield();
if ( placement["result"] && riotshieldDistanceTest( placement["origin"] ) )
{
zoffset = 28;
shield_ent = self spawnRiotshieldCover( placement["origin"] + ( 0, 0, zoffset ), placement["angles"] );
coll_ent = self spawnRiotshieldCollision( placement["origin"] + ( 0, 0, zoffset ), placement["angles"], shield_ent );
item_ent = DeployRiotShield( self, shield_ent );
primaries = self GetWeaponsListPrimaries();
/#
assert( IsDefined( item_ent ) );
assert( !IsDefined( self.riotshieldRetrieveTrigger ) );
assert( !IsDefined( self.riotshieldEntity ) );
assert( !IsDefined( self.riotshieldCollisionEntity ) );
#/
self.riotshieldRetrieveTrigger = item_ent;
self.riotshieldEntity = shield_ent;
self.riotshieldCollisionEntity = coll_ent;
if ( shockVersion )
self thread handleRiotShieldShockPlant();
else
PlayFXOnTag( getfx( "riot_shield_deploy_smoke" ), shield_ent, "tag_weapon" );
//shield_ent SetClientField( "riotshield_state", RIOTSHIELD_STATE_DEPLOYED );
// TODO: spawn thread to: play deploy anim on shield, play dust fx at shield origin, wait 0.8, playfxontag light fx at tag_fx on shield
shield_ent ScriptModelPlayAnimDeltaMotion( "npc_deployable_riotshield_stand_deploy" );
// Play Dust FX at shield origin
// spawn thread:
thread spawnShieldLights( shield_ent );
switchToKnife = false;
if( self.lastNonShieldWeapon != "none" && self hasWeapon( self.lastNonShieldWeapon ) )
self SwitchToWeaponImmediate( self.lastNonShieldWeapon );
else if ( primaries.size > 0 )
self SwitchToWeaponImmediate( primaries[0] );
else
switchToKnife = true;
if ( !self HasWeapon( "iw5_combatknife_mp" ) )
{
self GiveWeapon( "iw5_combatknife_mp" );
self.riotshieldTakeWeapon = "iw5_combatknife_mp";
}
if ( switchToKnife )
self SwitchToWeaponImmediate( "iw5_combatknife_mp" );
// Moving platforms.
data = SpawnStruct();
data.deathOverrideCallback = ::damageThenDestroyRiotshield;
shield_ent thread maps\mp\_movers::handle_moving_platforms( data );
self thread watchDeployedRiotshieldEnts();
self thread deleteShieldOnTriggerDeath( self.riotshieldRetrieveTrigger );
self thread deleteShieldOnTriggerPickup( self.riotshieldRetrieveTrigger, self.riotshieldEntity );
self thread deleteShieldOnPlayerDeathOrDisconnect( shield_ent );
self.riotshieldEntity thread watchDeployedRiotshieldDamage();
level notify( "riotshield_planted", self );
}
else
{
placement_hint = true;
clip_max_ammo = WeaponClipSize( curWeapon );
self setWeaponAmmoClip( curWeapon, clip_max_ammo );
}
}
else
{
// tagTMR<NOTE>: just lowering the shield not trying to deploy
placement_hint = true;
}
if ( placement_hint )
{
self SetRiotshieldFailHint();
}
}
spawnShieldLights(ent)
{
level endon( "game_ended" );
ent endon( "death" );
wait 0.6;
PlayFXOnTag( getfx( "riot_shield_deploy_lights" ), ent, "tag_weapon" );
}
riotshieldDistanceTest( origin )
{
/#
assert ( IsDefined( origin ) );
#/
min_dist_squared = GetDvarFloat( "riotshield_deploy_limit_radius" );
min_dist_squared *= min_dist_squared;
foreach( player in level.players )
{
if ( IsDefined( player.riotshieldEntity ) )
{
dist_squared = DistanceSquared( player.riotshieldEntity.origin, origin );
if ( min_dist_squared > dist_squared )
{
/#
println( "Shield placement denied! Failed distance check to other riotshields." );
#/
return false;
}
}
}
return true;
}
spawnRiotshieldCover( origin, angles ) // self == player
{
shield_ent = Spawn( "script_model", origin );
shield_ent.targetname = "riotshield_mp";
shield_ent.angles = angles;
model = undefined;
curWeapon = self GetCurrentPrimaryWeapon();
if ( weaponIsRiotShield( curWeapon ) )
model = GetWeaponModel( curWeapon );
if ( !IsDefined( model ) )
model = "npc_deployable_riot_shield_base";
shield_ent SetModel( model );
// shield_ent SetEntityOwner( self ); // Removing this as it causes traces from the player to pass through it unintentionally (grenades and missiles especially)
shield_ent.owner = self;
shield_ent.team = self.team;
// shield_ent SetTeam( self.team );
// shield_ent UseAnimTree( #animtree );
return shield_ent;
}
spawnRiotshieldCollision( origin, angles, shield_ent ) // self == player
{
coll_ent = Spawn( "script_model", origin, 1 ); // third param is spawn flag for dynamic pathing
coll_ent.targetname = "riotshield_coll_mp";
coll_ent.angles = angles;
coll_ent SetModel( "tag_origin" );
coll_ent.owner = self;
coll_ent.team = self.team;
coll_ent CloneBrushmodelToScriptModel( level.riot_shield_collision );
coll_ent DisconnectPaths();
return coll_ent;
}
watchDeployedRiotshieldEnts() // self == player
{
/#
assert( IsDefined( self.riotshieldRetrieveTrigger ) );
assert( IsDefined( self.riotshieldEntity ) );
assert( IsDefined( self.riotshieldCollisionEntity ) );
#/
self waittill( "destroy_riotshield" );
if ( IsDefined( self.riotshieldRetrieveTrigger ) )
{
self.riotshieldRetrieveTrigger delete();
}
if ( IsDefined( self.riotshieldCollisionEntity ) )
{
self.riotshieldCollisionEntity ConnectPaths();
self.riotshieldCollisionEntity delete();
}
if ( IsDefined( self.riotshieldEntity ) )
{
self.riotshieldEntity delete();
}
}
deleteShieldOnTriggerPickup( shield_trigger, shield_ent ) // self == player
{
level endon( "game_ended" );
shield_trigger endon( "death" );
shield_trigger waittill( "trigger", player );
// Transfer any linked entities from the deployed shield to the player's arm at the same offset
HandlePickupDeployedRiotshield( player, shield_ent );
self notify( "destroy_riotshield" );
}
deleteShieldOnTriggerDeath( shield_trigger ) // self == player
{
level endon( "game_ended" );
shield_trigger waittill( "death" );
self notify( "destroy_riotshield" );
}
deleteShieldOnPlayerDeathOrDisconnect( shield_ent ) // self == player
{
shield_ent endon( "death" );
shield_ent endon( "damageThenDestroyRiotshield" );
self waittill_any( "death", "disconnect", "remove_planted_weapons" );
shield_ent thread damageThenDestroyRiotshield();
}
watchDeployedRiotshieldDamage() // self == riotshield script_model ent
{
self endon( "death" );
damageMax = GetDvarInt( "riotshield_deployed_health" );
self.damageTaken = 0;
nextDamageAnimTime = 0;
while( true )
{
self.maxhealth = 100000;
self.health = self.maxhealth;
self waittill( "damage", damage, attacker, direction, point, type, modelName, tagName, partname, iDFlags, weaponName );
if( !isdefined( attacker ) )
{
continue;
}
/#
assert( isDefined( self.owner ) && isDefined( self.owner.team ));
#/
if ( isplayer( attacker ) )
{
if ( ( level.teamBased ) && ( attacker.team == self.owner.team ) && ( attacker != self.owner ) )
{
continue;
}
}
isMeleeDamage = false;
isBulletDamage = false;
if ( isMeleeMOD( type ) )
{
isMeleeDamage = true;
damage *= GetDvarfloat( "riotshield_melee_damage_scale" );
}
else if ( type == "MOD_PISTOL_BULLET" || type == "MOD_RIFLE_BULLET" )
{
isBulletDamage = true;
damage *= GetDvarfloat( "riotshield_bullet_damage_scale" );
}
else if ( type == "MOD_GRENADE" || type == "MOD_GRENADE_SPLASH" || type == "MOD_EXPLOSIVE" || type == "MOD_EXPLOSIVE_SPLASH" || type == "MOD_PROJECTILE" || type == "MOD_PROJECTILE_SPLASH")
{
damage *= GetDvarfloat( "riotshield_explosive_damage_scale" );
}
else if ( type == "MOD_IMPACT" )
{
damage *= GetDvarFloat( "riotshield_projectile_damage_scale" );
}
else if ( type == "MOD_CRUSH" )
{
damage = damageMax;
}
self.damageTaken += damage;
if( self.damageTaken >= damageMax )
{
self thread damageThenDestroyRiotshield( attacker, weaponName );
break;
}
else if ( ( isMeleeDamage || isBulletDamage ) && ( GetTime() >= nextDamageAnimTime ) )
{
nextDamageAnimTime = GetTime() + 500;
fromBack = false;
shield_fwd = AnglesToForward( self.angles );
if ( VectorDot( direction, shield_fwd ) > 0 )
fromBack = true;
if ( isMeleeDamage )
{
if ( fromBack )
self ScriptModelPlayAnimDeltaMotion( "npc_deployable_riotshield_stand_melee_back" );
else
self ScriptModelPlayAnimDeltaMotion( "npc_deployable_riotshield_stand_melee_front" );
}
else
{
Assert( isBulletDamage );
if ( fromBack )
self ScriptModelPlayAnimDeltaMotion( "npc_deployable_riotshield_stand_shot_back" );
else
self ScriptModelPlayAnimDeltaMotion( "npc_deployable_riotshield_stand_shot" );
}
}
}
}
damageThenDestroyRiotshield(attacker, weaponName) // self == riotshield script_model ent
{
self notify( "damageThenDestroyRiotshield" );
self endon( "death" );
if ( IsDefined( self.owner.riotshieldRetrieveTrigger ) )
{
self.owner.riotshieldRetrieveTrigger delete();
}
if ( IsDefined( self.owner.riotshieldCollisionEntity ) )
{
self.owner.riotshieldCollisionEntity ConnectPaths();
self.owner.riotshieldCollisionEntity delete();
}
self.owner.riotshieldEntity = undefined;
self NotSolid();
// self SetClientField( "riotshield_state", RIOTSHIELD_STATE_DESTROYED );
// TODO: stop fx, play sound, play destroyed anim, wait, force not simple dobj...?
// Stop FX on shield
// Play destroyed sound
self ScriptModelPlayAnimDeltaMotion( "npc_deployable_riotshield_stand_destroyed" );
// Wait
// Force not simple dobj?
// if (isdefined (attacker) && isdefined (weaponName) && attacker != self.owner && isplayer( attacker ) )
// {
// maps\mp\_scoreevents::processScoreEvent( "destroyed_shield", attacker, self.owner, weaponName );
// }
wait( GetDvarFloat( "riotshield_destroyed_cleanup_time" ) );
self delete();
}
watchRiotshieldStuckEntityDeath( grenade, owner ) // self == entity stuck with nade
{
grenade endon( "death" );
self waittill_any( "damageThenDestroyRiotshield", "death", "disconnect", "weapon_change", "deploy_riotshield" );
grenade Detonate( owner );
}