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

691 lines
20 KiB
Plaintext

#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\_art;
CONST_SWIMMING_DEPTH = 48;
init()
{
/*---HOW TO SET UP----/
In Script:
* add this line to your map.gsc main() function: maps\mp\_water::init();
* Add this to your level.csv file:
* include,_water
In Radiant:
* In your level in radiant add a trigger_multiple with a KVP of: "targetname" / "trigger_underwater". Place the trigger where you want the player to be considered touching the water.
* give the trigger_multiple a KVP of: "script_noteworthy" / "<name>". Name can be anything.
* link the trigger_multiple to a script struct. Place the script struct at the waterline.
* In your level in radiant add a client_trigger that encompasses the water (about 8 units below the ground and about 64 units above the waterline). Give it a KVP of "script_water" / <name>. the name needs to match the script_noteworthy of the trigger_multiple.
/----OPTIONAL----/
Customize: the lightset, fogset, vfx, sound fx, visionset, and depth of field. To do this:
* Settings for the water triggers should be added to the <mapname>_water.csv file. That csv file should be included in <mapname>.csv as a stringtable type.
* stringtable,maps/createart/<mapname>_water.csv
* You can copy maps/createart/default_water.csv to start with and add rows as needed. Please make a copy and do not edit the default as it would effect other levels. The name column should be filled in with whatever you used for the script_water KVP in the client trigger.
* Columns:
* plunge vfx/sfx - The vfx/sfx to play when going under the water.
* emerge vfx/sfx - The vfx/sfx to play when coming out of the water.
* bubble vfx - The vfx to play while underwater.
* bubble update ms - The bubble vfx play over and over, this is the time in mS between successive plays of the vfx.
* breath sfx - The "player breathing heavily" sound to play when emerging.
* underwater vision set - The name of the vision set to apply when underwater. (from <mapname>_fog.gsc and <mapname>_fog_hdr.gsc files).
* underwater light set - The name of the light set to apply when underwater. ( from <mapname>_lightsets_hdr.gsc )
* dof* - The depth of field settings to apply when underwater.
Moving water:
* If your water is animated or moves you can link the waterline ent(s) to the animated or moving water.
* The water ents are stored in level.waterline_ents which is an array of tag_origin script models that are created in script based on the location of the script_struct you place in radiant.
* To identify a specific one use the .script_noteworthy of one of the waterline_ents in the array. The script noteworthy of it will match the script noteworthy of the trigger_multiple you placed in radiant in step 2 above.
*TODO: make swimming better, and add dolphin death! (for map boundaries or map killstreak)
/---------------------------------------------------------------------------------------------------------------------*/
level._effect[ "water_wake" ] = loadfx( "vfx/treadfx/body_wake_water" );
level._effect[ "water_wake_stationary" ] = loadfx( "vfx/treadfx/body_wake_water_stationary" );
level._effect[ "water_splash_emerge" ] = loadfx( "vfx/water/body_splash_exit" );
level._effect[ "water_splash_enter" ] = loadfx( "vfx/water/body_splash" );
PreCacheShellShock( "underwater" );
if( !isDefined( level.waterline_ents ) )
level.waterline_ents = [];
if( !isDefined( level.waterline_offset ) )
level.waterline_offset = 0;
if( !isDefined( level.shallow_water_weapon ) )
SetShallowWaterWeapon( "iw5_combatknife_mp" );
if( !isDefined( level.deep_water_weapon ) )
SetDeepWaterWeapon( "iw5_underwater_mp" );
if( !isDefined( level.allow_swimming ) )
level.allow_swimming = true;
if( level.deep_water_weapon == level.shallow_water_weapon )
level.allow_swimming = false;
if( !isDefined( level.swimming_depth ) )
level.swimming_depth = CONST_SWIMMING_DEPTH;
triggers = getentarray( "trigger_underwater", "targetname" );
level.water_triggers = triggers;
foreach( trig in triggers )
{
trig create_clientside_water_ents();
trig thread watchPlayerEnterWater();
level thread clearWaterVarsOnSpawn( trig );
}
level thread OnPlayerConnectFunctions();
}
player_set_in_water( in_water )
{
if ( in_water )
{
self.inWater = true;
if ( IsAIGameParticipant( self ) )
self BotSetFlag("in_water",true);
}
else
{
self.inWater = undefined;
if ( IsAIGameParticipant( self ) )
self BotSetFlag("in_water",false);
}
}
OnPlayerConnectFunctions()
{
level endon( "game_ended" );
while( true )
{
level waittill( "connected", player );
foreach( waterline_ent in level.waterline_ents )
{
player InitWaterClientTrigger( waterline_ent.script_noteworthy, waterline_ent );
}
}
}
create_clientside_water_ents()
{
water_ent_struct = getstruct( self.target, "targetname" );
AssertEx( isDefined( water_ent_struct ), "waterline needs to be defined. Place a script_struct at the height of the waterline and link your underwater trigger to it." );
water_ent_struct.origin = water_ent_struct.origin + (0,0,level.waterline_offset);
waterline_ent = water_ent_struct spawn_tag_origin();
waterline_ent show();
if( isDefined( self.script_noteworthy ) )
{
waterline_ent.script_noteworthy = self.script_noteworthy;
level.waterline_ents = array_add( level.waterline_ents, waterline_ent );
}
}
clearWaterVarsOnSpawn( underWater )
{
level endon ( "game_ended" );
while( true )
{
level waittill( "player_spawned", player );
if ( !player IsTouching( underWater ) )
{
player player_set_in_water(false);
player.underWater = undefined;
player.inThickWater = undefined;
player.isSwimming = undefined;
player.isWading = undefined;
player.water_last_weapon = undefined;
player.isShocked = undefined;
player notify( "out_of_water" );
}
}
}
watchPlayerEnterWater()
{
level endon( "game_ended" );
while( true )
{
self waittill( "trigger", ent );
if( IsDefined(level.isHorde) && level.isHorde && IsAgent( ent ) && IsDefined( ent.horde_type ) && ent.horde_type == "Quad" && !isDefined(ent.inWater))
ent thread hordeDogInWater( self );
if ( !IsPlayer( ent ) && !IsAI(ent) )
continue;
if ( !isAlive( ent ) )
continue;
if ( !isDefined( ent.inWater ) )
{
ent player_set_in_water(true);
ent thread playerInWater( self );
}
}
}
hordeDogInWater(trig)
{
level endon( "game_ended" );
self endon( "death" );
self endon( "disconnect" );
self player_set_in_water(true);
while( 1 )
{
if (!self inShallowWater( trig, 40 ))
{
wait 2.5;
if( !self inShallowWater( trig, 20 ))
self DoDamage( self.health, self.origin );
}
waitframe();
}
}
playerInWater( underWater )
{
level endon( "game_ended" );
self endon( "death" );
self endon( "disconnect" );
self thread inWaterWake(underWater);
self thread playerWaterClearWait();
self.eyeHeightLastFrame = 0;
self.eye_velocity = 0;
//note: go from out of the water to in for the order in the if statements below
while( true )
{
if ( self maps\mp\_utility::isUsingRemote() )
{
if ( isDefined( self.underWater ) && isDefined( self.isShocked ) )
{
self StopShellShock();
self.isShocked = undefined;
}
}
else
{
if ( isDefined( self.underWater ) && !IsDefined( self.isShocked ) )
{
self shellShock( "underwater", 19 );
self.isShocked = true;
}
}
//player is not touching the water
if ( !self IsTouching( underWater ) )
{
self player_set_in_water(false);
self.underWater = undefined;
self.inThickWater = undefined;
self.isSwimming = undefined;
self.moveSpeedScaler = level.basePlayerMoveScale;
self maps\mp\gametypes\_weapons::updateMoveSpeedScale();
self notify( "out_of_water" );
break;
}
//player is walking in shallow water after being in thick water
if( isDefined( self.inThickWater ) && self inShallowWater( underwater, 32 ) )
{
self.inThickWater = undefined;
self.moveSpeedScaler = level.basePlayerMoveScale;
self maps\mp\gametypes\_weapons::updateMoveSpeedScale();
}
//player is walking in thick water
if( !isDefined( self.inThickWater ) && !self inShallowWater( underwater, 32 ) )
{
self.inThickWater = true;
self.moveSpeedScaler = .7*level.basePlayerMoveScale;
self maps\mp\gametypes\_weapons::updateMoveSpeedScale();
}
//player's eye is under water
if ( !isDefined( self.underWater ) && !self isAboveWaterLine( underWater, 0 ) )
{
self.underWater = true;
self thread playerHandleDamage();
if( isAugmentedGameMode() )
self DisableExo();
if ( !self maps\mp\_utility::isUsingRemote() )
{
self shellShock( "underwater", 19 );
self.isShocked = true;
}
currentWeapon = self GetCurrentWeapon();
if ( currentWeapon != "none" && WeaponInventoryType( currentWeapon ) == "primary" )
{
self.water_last_weapon = currentWeapon;
}
if(IsDefined(level.gamemodeOnUnderWater))
self [[level.gamemodeOnUnderWater]](underWater);
self maps\mp\killstreaks\_coop_util::playerStopPromptForStreakSupport();
}
//player's eye is underwater and player's in shallow water or prone or is not allowed to swim
if ( isDefined( self.underWater ) && ( isDefined( self.isSwimming ) || !isDefined( self.isWading ) ) && ( self inShallowWater( underwater, level.swimming_depth ) || self GetStance() == "prone" || !level.allow_swimming ) )
{
self.isWading = true;
self.isSwimming = undefined;
self PlayerDisableUnderwater();
if ( IsDefined( self.isJuggernaut ) && self.isJuggernaut == true )
{
self PlayerEnableUnderwater( "none" ); // Special case for when the player is using the Goliath Scorestreak.
self AllowFire( false ); // Disable main weapons and lethal offhands. Does not include second offhand weapons.
self DisableOffhandSecondaryWeapons();
}
else
{
self PlayerEnableUnderwater( "shallow" );
}
}
//player's eye is underwater and player's in deep water and the player is allowed to swim
if ( isDefined( self.underWater ) && ( isDefined( self.isWading ) || !isDefined( self.isSwimming ) ) && ( !self inShallowWater( underwater, level.swimming_depth ) && self GetStance() != "prone" && level.allow_swimming ) )
{
self.isSwimming = true;
self.isWading = undefined;
self PlayerDisableUnderwater();
if ( IsDefined( self.isJuggernaut ) && self.isJuggernaut == true )
{
self PlayerEnableUnderwater( "none" ); // Special case for when the player is using the Goliath Scorestreak.
self AllowFire( false ); // Disable main weapons and lethal offhands. Does not include second offhand weapons.
self DisableOffhandSecondaryWeapons();
}
else
{
self PlayerEnableUnderwater( "deep" );
}
}
//player's eye above the waterline
if ( isDefined( self.underWater ) && self isAboveWaterLine( underWater, 0 ) )
{
self.underWater = undefined;
self.isSwimming = undefined;
self.isWading = undefined;
self notify( "above_water" );
speed = Distance( self GetVelocity(), ( 0, 0, 0 ) );
point = (self.origin[0], self.origin[1], getWaterLine(underwater));
playFX( level._effect[ "water_splash_emerge" ], point, AnglesToForward((0, self.angles[1], 0) + (270, 180, 0) ) );
if ( !self maps\mp\_utility::isUsingRemote() )
{
self StopShellshock();
self.isShocked = undefined;
}
self PlayerDisableUnderwater();
if( isAugmentedGameMode() )
self EnableExo();
self maps\mp\killstreaks\_coop_util::playerStartPromptForStreakSupport();
}
wait ( 0.05 );
}
}
IsActiveKillstreakWaterRestricted( player )//TODO: move this check into the killstreak functions
{
if ( IsDefined( player.killstreakIndexWeapon ) )
{
streakName = self.pers["killstreaks"][self.killstreakIndexWeapon].streakName;
if ( IsDefined( streakName ) )
{
if( string_find( streakName, "turret" ) > 0 || string_find( streakName, "sentry" ) > 0 )
{
return true;
}
}
}
return false;
}
playerWaterClearWait()
{
msg = self waittill_any_return( "death", "out_of_water" );
self.underwaterMotionType = undefined;
self.dont_give_or_take_weapon = undefined;
self player_set_in_water(false);
self.underWater = undefined;
self.inThickWater = undefined;
self.water_last_weapon = undefined;
self.moveSpeedScaler = level.basePlayerMoveScale;
self maps\mp\gametypes\_weapons::updateMoveSpeedScale();
}
inWaterWake(underwater)
{
level endon( "game_ended" );
self endon( "death" );
self endon( "disconnect" );
self endon( "out_of_water" );
speed = Distance( self GetVelocity(), ( 0, 0, 0 ) );
if (speed > 90)
{
point = (self.origin[0], self.origin[1], getWaterLine(underwater));
playFX( level._effect[ "water_splash_enter" ], point, AnglesToForward((0, self.angles[1], 0) + (270, 180, 0) ) );
}
while( true )
{
//loop fx based off of player speed
velocity = self GetVelocity();
speed = Distance( velocity, ( 0, 0, 0 ) );
if (speed > 0)
wait(max(( 1 - (speed / 120)),0.1) );
else
wait (0.3);
if (speed > 5)
{
movementDir = VectorNormalize((velocity[0], velocity[1], 0));
forwardVec = AnglesToForward(VectorToAngles(movementDir) + (270, 180, 0));
point = (self.origin[0], self.origin[1], getWaterLine(underwater)) + ((speed / 4) * movementDir);
playFX( level._effect[ "water_wake" ], point, forwardVec );
}
else
{
point = (self.origin[0], self.origin[1], getWaterLine(underwater));
playFX( level._effect[ "water_wake_stationary" ], point, AnglesToForward((0, self.angles[1], 0) + (270, 180, 0) ) );
}
}
}
playerhandleDamage()
{
level endon( "game_ended" );
self endon( "death" );
self endon( "stopped_using_remote" );
self endon( "disconnect" );
self endon( "above_water" );
self thread onPlayerDeath();
wait( 13 );
while( true )
{
if ( !IsDefined( self.isJuggernaut ) || self.isJuggernaut == false )
{
RadiusDamage( self.origin + anglesToForward( self.angles ) * 5, 1, 20, 20, undefined, "MOD_TRIGGER_HURT" );
}
wait( 1 );
}
}
onPlayerDeath()
{
level endon( "game_ended" );
self endon( "disconnect" );
self endon( "above_water" );
self waittill( "death" );
self player_set_in_water(false);
self.underWater = undefined;
self.inThickWater = undefined;
self.isSwimming = undefined;
self.isWading = undefined;
self.water_last_weapon = undefined;
self.underwaterMotionType = undefined;
self.eye_velocity = 0;
self.eyeHeightLastFrame = 0;
self maps\mp\killstreaks\_coop_util::playerStartPromptForStreakSupport();
}
/////////////////////////
//water check functions
/////////////////////////
inShallowWater( trig, depth )
{
if( !isDefined( depth ) )
depth = 32;
if( level GetWaterLine( trig ) - self.origin[2] <= depth )
return true;
return false;
}
isAboveWaterLine( trig, offset )
{
if( (self GetPlayerEyeHeight() + offset ) >= level GetWaterLine( trig ))
return true;
else
return false;
}
GetPlayerEyeHeight()
{
player_eye = self geteye();
self.eye_velocity = player_eye[2] - self.eyeHeightLastFrame;
self.eyeHeightLastFrame = player_eye[2];
return player_eye[2];
}
GetWaterLine( trig )
{
water_line_struct = getstruct( trig.target, "targetname" );//TODO: assert if the struct is not defined!
water_line = water_line_struct.origin[2];
return water_line;
}
////////////////////////
//SWIMMING
////////////////////////
PlayerEnableUnderwater( type )
{
level endon( "game_ended" );
self endon( "death" );
self endon( "disconnect" );
self endon( "end_swimming" );
if( !isDefined( type ) )
type = "shallow";
/* Taking this out since it causes a bug where the player could switch weapons before the disableWeaponSwitch() call causing the player to get a weapon underwater;
if( ( !self IsSwitchingWeapon() && ( type == "shallow" && CurrentWeapon == level.shallow_water_weapon ) || ( type == "deep" && CurrentWeapon == level.deep_water_weapon ) ) )
type = "none";
*/
if( ( type == "shallow" && self HasWeapon( level.shallow_water_weapon ) ) || ( type == "deep" && self HasWeapon( level.deep_water_weapon ) ) )
self.dont_give_or_take_weapon = true;
switch( type )
{
case "deep":
self give_water_weapon( level.deep_water_weapon );
self SwitchToWeaponImmediate( level.deep_water_weapon );
self.underwaterMotionType = "deep";
break;
case "shallow":
self give_water_weapon( level.shallow_water_weapon );
self SwitchToWeaponImmediate( level.shallow_water_weapon );
self.underwaterMotionType = "shallow";
break;
case "none":
self.underwaterMotionType = "none";
break;
default:
self give_water_weapon( level.shallow_water_weapon );
self SwitchToWeaponImmediate( level.shallow_water_weapon );
self.underwaterMotionType = "shallow";
break;
}
self DisableWeaponPickup();
self _disableWeaponSwitch();
self _disableOffhandWeapons();
}
playerDisableUnderwater()
{
level endon( "game_ended" );
self endon( "death" );
self endon( "disconnect" );
if( isDefined( self.underwaterMotionType ) )
{
type = self.underwaterMotionType;
self notify( "end_swimming" );
self EnableWeaponPickup();
self _enableWeaponSwitch();
self _enableOffhandWeapons();
if ( IsDefined( self.isJuggernaut ) && self.isJuggernaut == true && IsDefined( self.heavyExoData ) )
{
self AllowFire( true ); // Re-enable main weapon and lethal offhands (does not include secondary offhands).
// Disable lethals if the Goliath doesn't have the underbarrel rocket launcher module.
if ( !IsDefined( self.heavyExoData.hasLongPunch ) || self.heavyExoData.hasLongPunch == false )
{
self DisableOffhandWeapons();
}
// Disable tacticals if the Goliath doesn't have the swarm rockets module.
if ( !IsDefined( self.heavyExoData.hasRockets ) || self.heavyExoData.hasRockets == false )
{
self DisableOffhandSecondaryWeapons();
}
else
{
self EnableOffhandSecondaryWeapons();
}
}
if( isDefined( self.water_last_weapon ) )
{
self switch_to_last_weapon( self.water_last_weapon );
}
switch( type )
{
case "deep":
self take_water_weapon( level.deep_water_weapon );
break;
case "shallow":
self take_water_weapon( level.shallow_water_weapon );
break;
case "none":
break;
default:
self take_water_weapon( level.shallow_water_weapon );
break;
}
self.underwaterMotionType = undefined;
self.dont_give_or_take_weapon = undefined;
}
}
give_water_weapon( weapon )
{
if( !isDefined( self.dont_give_or_take_weapon ) || !self.dont_give_or_take_weapon )
self giveWeapon( weapon );
}
take_water_weapon( weapon )
{
if( !isDefined( self.dont_give_or_take_weapon ) || !self.dont_give_or_take_weapon )
self takeWeapon( weapon );
}
EnableExo()
{
self playerAllowHighJump(true);
self playerAllowHighJumpDrop(true);
self playerAllowBoostJump(true);
self playerAllowPowerSlide(true);
self playerAllowDodge(true);
}
DisableExo()
{
self playerAllowHighJump(false);
self playerAllowHighJumpDrop(false);
self playerAllowBoostJump(false);
self playerAllowPowerSlide(false);
self playerAllowDodge(false);
}
/*
=============
///ScriptDocBegin
"Name: SetShallowWaterWeapon( weapon )"
"Summary: forces the player to use this weapon when underwater in shallow water"
"Module: Water"
"MandatoryArg: <weapon> : the weapon to use when underwater in shallow water"
"CallOn: Level"
"Example: Level SetUnderWaterWeapon( "iw5_combatknife_mp" )"
"SPMP: MP"
///ScriptDocEnd
=============
*/
SetShallowWaterWeapon( weapon )
{
assertEx( isValidUnderWaterWeapon( weapon ), "weapon is not a valid underwater weapon" );
level.shallow_water_weapon = weapon;
}
/*
=============
///ScriptDocBegin
"Name: SetDeepWaterWeapon( weapon )"
"Summary: forces the player to use this weapon when underwater in deep water"
"Module: Water"
"MandatoryArg: <weapon> : the weapon to use when underwater in deep water"
"CallOn: Level"
"Example: Level SetUnderWaterWeapon( "iw5_underwater_mp" )"
"SPMP: MP"
///ScriptDocEnd
=============
*/
SetDeepWaterWeapon( weapon )
{
assertEx( isValidUnderWaterWeapon( weapon ), "weapon is not a valid underwater weapon" );
level.deep_water_weapon = weapon;
}
isValidUnderWaterWeapon( weapon )
{
switch( weapon )
{
case "iw5_underwater_mp": //swim
case "iw5_combatknife_mp": //knife
case "none": //no weapon
return true;
default:
return false;
}
}