boiii-scripts/shared/vehicleriders_shared.gsc
2023-04-13 17:30:38 +02:00

859 lines
30 KiB
Plaintext

#using scripts\codescripts\struct;
#using scripts\shared\ai_shared;
#using scripts\shared\animation_shared;
#using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\colors_shared;
#using scripts\shared\flag_shared;
#using scripts\shared\flagsys_shared;
#using scripts\shared\spawner_shared;
#using scripts\shared\system_shared;
#using scripts\shared\trigger_shared;
#using scripts\shared\util_shared;
#using scripts\shared\ai\systems\gib;
#using scripts\shared\hostmigration_shared;
#using_animtree( "generic" );
#namespace vehicle;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TODO:
//
// * Implement death anims
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
function autoexec __init__sytem__() { system::register("vehicleriders",&__init__,undefined,undefined); }
function __init__()
{
level.vehiclerider_groups = [];
level.vehiclerider_groups[ "all" ] = "all";
level.vehiclerider_groups[ "driver" ] = "driver";
level.vehiclerider_groups[ "passengers" ] = "passenger";
level.vehiclerider_groups[ "crew" ] = "crew";
level.vehiclerider_groups[ "gunners" ] = "gunner";
a_registered_fields = [];
foreach ( bundle in struct::get_script_bundles( "vehicleriders" ) )
{
foreach ( object in bundle.objects )
{
if ( IsString( object.VehicleEnterAnim ) )
{
array::add( a_registered_fields, object.position + "_enter", false );
}
if ( IsString( object.VehicleExitAnim ) )
{
array::add( a_registered_fields, object.position + "_exit", false );
}
if ( IsString( object.VehicleRiderDeathAnim ) )
{
array::add( a_registered_fields, object.position + "_death", false );
}
}
}
foreach ( str_clientfield in a_registered_fields )
{
clientfield::register( "vehicle", str_clientfield, 1, 1, "counter" );
}
level.vehiclerider_use_index = [];
level.vehiclerider_use_index[ "driver" ] = 0;
// Set up gunnner use indices.
const MAX_USE_INDEX = 4;
for ( i = 1; i <= MAX_USE_INDEX; i++ )
{
level.vehiclerider_use_index[ "gunner" + i ] = i;
}
// Setup passenger use indices
const MAX_PASSENGER_INDEX = 10;
passengerIndex = 1;
for ( i = MAX_USE_INDEX + 1; i <= MAX_PASSENGER_INDEX; i++ )
{
level.vehiclerider_use_index[ "passenger" + passengerIndex ] = i;
passengerIndex++;
}
/* FIX UP STUFF FROM THE GDT */
foreach ( s in struct::get_script_bundles( "vehicleriders" ) )
{
if(!isdefined(s.LowExitHeight))s.LowExitHeight=0;
if(!isdefined(s.HighExitLandHeight))s.HighExitLandHeight=32;
}
callback::on_vehicle_spawned( &on_vehicle_spawned );
callback::on_ai_spawned( &on_ai_spawned );
callback::on_vehicle_killed(&on_vehicle_killed);
}
function seat_position_to_index( str_position )
{
return level.vehiclerider_use_index[ str_position ];
}
function on_vehicle_spawned()
{
spawn_riders();
}
function on_ai_spawned()
{
if ( IsVehicle( self ) )
{
self spawn_riders();
}
}
function claim_position( vh, str_pos )
{
array::add( vh.riders, self, false );
vh flagsys::set( str_pos + "occupied" );
self flagsys::set( "vehiclerider" );
self thread _unclaim_position_on_death( vh, str_pos );
}
function unclaim_position( vh, str_pos )
{
ArrayRemoveValue( vh.riders, self );
vh flagsys::clear( str_pos + "occupied" );
self flagsys::clear( "vehiclerider" );
}
function private _unclaim_position_on_death( vh, str_pos )
{
vh endon( "death" );
vh endon( str_pos + "occupied" );
self waittill( "death" );
unclaim_position( vh, str_pos );
}
function find_next_open_position( ai )
{
foreach ( s_rider in get_bundle_for_ai( ai ).objects )
{
seat_index = seat_position_to_index(s_rider.position);
if( seat_index <= 4 ) // IsVehicleSeatOccupied only works on code seats and not passenger seats
{
if( self IsVehicleSeatOccupied( seat_index ) )
continue;
}
if ( !flagsys::get( s_rider.position + "occupied" ) )
{
return s_rider.position;
}
}
}
function spawn_riders()
{
self endon("death");
self.riders = [];
if ( isdefined( self.script_vehicleride ) )
{
a_spawners = GetSpawnerArray( self.script_vehicleride, "script_vehicleride" );
foreach ( sp in a_spawners )
{
ai_rider = sp spawner::spawn( true );
//ai_rider = util::spawn_anim_model( "c_hro_hendricks_base_fb" );
if ( isdefined( ai_rider ) )
{
ai_rider get_in( self, ai_rider.script_startingposition, true );
}
}
}
}
function get_bundle_for_ai( ai )
{
vh = self;
if ( (isdefined(ai.archetype) && ( ai.archetype == "robot" )) )
{
bundle = vh get_robot_bundle();
}
else
{
bundle = vh get_bundle();
}
return bundle;
}
function get_rider_info( vh, str_pos = "driver" )
{
ai = self;
bundle = undefined;
bundle = vh get_bundle_for_ai( ai );
foreach ( s_rider in bundle.objects )
{
if ( s_rider.position == str_pos )
{
return s_rider;
}
}
}
/@
"Summary: Makes an AI get in a vehicle (with animation)"
"Name: get_in( vh, str_pos, b_teleport = false )"
"CallOn: AI"
"MandatoryArg: <vh> The vehicle to ride in"
"OptionalArg: [str_pos] The position to ride in. If undefined, will choose next available position."
"OptionalArg: [b_teleport] If true, teleport to rider position and don't play get in animation."
"Example: ai vehicle::get_in( vh_truck, "driver" );"
@/
function get_in( vh, str_pos, b_teleport = false )
{
self endon( "death" );
vh endon( "death" );
if ( !isdefined( str_pos ) )
{
str_pos = vh find_next_open_position( self );
}
Assert( isdefined( str_pos ), "No unoccupied seats for vehicle rider." );
if( !isdefined( str_pos ) )
{
return;
}
//return if the seat is not available
if ( !isdefined( vh.ignore_seat_check ) || !vh.ignore_seat_check )
{
seat_index = level.vehiclerider_use_index[ str_pos ];
if( seat_index <= 4 ) // IsVehicleSeatOccupied only works on code seats and not passenger seats
{
seat_available = !(vh IsVehicleSeatOccupied( seat_index ) );
Assert( seat_available, "This seat is already occupied." );
if(!seat_available)
{
return;
}
}
}
claim_position( vh, str_pos );
if ( !b_teleport && self flagsys::get( "in_vehicle" ) )
{
get_out();
}
if ( colors::is_color_ai() )
{
colors::disable();
}
_init_rider( vh, str_pos );
if ( !b_teleport )
{
self animation::set_death_anim( self.rider_info.EnterDeathAnim );
animation::reach( self.rider_info.EnterAnim, self.vehicle, self.rider_info.AlignTag );
// Animate the door on the client.
if ( isdefined( self.rider_info.VehicleEnterAnim ) )
{
vh clientfield::increment( self.rider_info.position + "_enter", 1 );
self SetAnim( self.rider_info.VehicleEnterAnim, 1, 0, 1 );
}
self animation::play( self.rider_info.EnterAnim, self.vehicle, self.rider_info.AlignTag );
}
if ( isdefined(self.rider_info) && isdefined( self.rider_info.RideAnim ) )
{
//disable unlink after the animation is done since this will be handled by UseVehicle
self thread animation::play( self.rider_info.RideAnim, self.vehicle, self.rider_info.AlignTag, 1, 0.2, 0.2, 0, 0, false, false );
}
else if ( !isdefined( level.vehiclerider_use_index[ str_pos ] ) )
{
assert( "Rider is missing ride animation for seat: " + str_pos );
}
else if ( isdefined(self.rider_info) )
{
// HACK: teleport to align tag in the case of no ride animation being available (mainly for gunners)
// Since UseVehicle doesn't put them on the vehicle properly.
v_tag_pos = vh GetTagOrigin( self.rider_info.aligntag );
v_tag_ang = vh GetTagAngles( self.rider_info.aligntag );
if (isdefined(v_tag_pos))
self ForceTeleport( v_tag_pos, v_tag_ang );
}
else
{
/# ErrorMsg( "Missing rider_info" ); #/
}
if ( IsActor( self ) )
{
// Disable pathing while inside a vehicle.
self PathMode( "dont move" );
// Disable dropping ammo/weapon inside a vehicle.
self.disableAmmoDrop = true;
self.dontDropWeapon = true;
}
// If there is an associated use index, call UseVehicle.
//
if ( isdefined( level.vehiclerider_use_index[ str_pos ] ) )
{
//double check that the seat is still available
if ( !isdefined( self.vehicle.ignore_seat_check ) || !self.vehicle.ignore_seat_check )
{
seat_index = level.vehiclerider_use_index[ str_pos ];
if( seat_index <= 4 ) // IsVehicleSeatOccupied only works on code seats and not passenger seats
{
if( self.vehicle IsVehicleSeatOccupied( seat_index ) )
{
get_out();
return;
}
}
}
self.vehicle UseVehicle( self, level.vehiclerider_use_index[ str_pos ] );
}
self flagsys::set( "in_vehicle" );
self thread handle_rider_death();
}
function handle_rider_death()
{
self endon( "exiting_vehicle" );
self.vehicle endon( "death" );
if ( IsDefined( self.rider_info.RideDeathAnim ) )
{
self animation::set_death_anim( self.rider_info.RideDeathAnim );
}
self waittill( "death" );
if ( !IsDefined( self ) )
{
return;
}
if ( IsDefined( self.vehicle ) && IsDefined( self.rider_info ) && IsDefined( self.rider_info.VehicleRiderDeathAnim ) )
{
self.vehicle clientfield::increment( self.rider_info.position + "_death", 1 );
self.vehicle SetAnimKnobRestart( self.rider_info.VehicleRiderDeathAnim, 1, 0, 1 );
}
}
function delete_rider_asap( entity )
{
wait .05;
if ( IsDefined( entity ) )
{
entity Delete();
}
}
function kill_rider( entity )
{
if( IsDefined( entity ) )
{
if( IsAlive( entity ) && !GibServerUtils::IsGibbed( entity, 2 ) )
{
if ( entity IsPlayingAnimScripted() )
{
entity StopAnimScripted();
}
if ( GetDvarInt( "tu1_vehicleRidersInvincibility", 1 ) )
{
// TU1 crash fix when killing a vehicle that had invincible riders.
util::stop_magic_bullet_shield( entity );
}
GibServerUtils::GibLeftArm( entity );
GibServerUtils::GibRightArm( entity );
GibServerUtils::GibLegs( entity );
GibServerUtils::Annihilate( entity );
entity Unlink();
entity Kill();
}
// Ragdoll typically goes insane, hide and delete the entity.
entity Ghost();
level thread delete_rider_asap( entity );
}
}
function on_vehicle_killed( params )
{
// "self" is a removed entity in this case and IsDefined( self ) will always return false, even though
// script variables on that entity are still available.
if ( /* IsDefined( self ) && */ IsDefined( self.riders ) )
{
foreach ( rider in self.riders )
{
kill_rider( rider );
}
}
}
function is_seat_available( vh, str_pos )
{
if ( vh flagsys::get( str_pos + "occupied" ) )
{
return false;
}
if ( AnglesToUp( vh.angles )[2] < 0.3 )
{
// Vehicle is flipped
return false;
}
seat_index = seat_position_to_index( str_pos );
if( seat_index <= 4 ) // IsVehicleSeatOccupied only works on code seats and not passenger seats
{
if( vh IsVehicleSeatOccupied( seat_index ) )
{
return false;
}
}
return true;
}
function can_get_in( vh, str_pos )
{
if ( !is_seat_available( vh, str_pos ) )
{
return false;
}
rider_info = self get_rider_info( vh, str_pos );
v_tag_org = vh GetTagOrigin( rider_info.AlignTag );
v_tag_ang = vh GetTagAngles( rider_info.AlignTag );
v_enter_pos = GetStartOrigin( v_tag_org, v_tag_ang, rider_info.EnterAnim );
if ( !self FindPath( self.origin, v_enter_pos ) )
{
return false;
}
return true;
}
/@
"Summary: Makes an AI get out of a vehicle (with animation)"
"Name: get_out()"
"CallOn: AI"
"Example: ai vehicle::get_out();"
@/
function get_out( str_mode )
{
ai = self;
self endon( "death" );
self notify( "exiting_vehicle" );
Assert( IsAlive( self ), "Dead or undefined vehicle rider." );
Assert( isdefined( self.vehicle ), "AI is not on vehicle." );
if ( ( isdefined(self.vehicle.vehicleclass) && (self.vehicle.vehicleclass == "helicopter" ) ) || ( isdefined(self.vehicle.vehicleclass) && (self.vehicle.vehicleclass == "plane") ) )
{
if(!isdefined(str_mode))str_mode="variable";
}
else
{
if(!isdefined(str_mode))str_mode="ground";
}
bundle = self.vehicle get_bundle_for_ai( ai );
n_hover_height = bundle.LowExitHeight;
// Animate the door on the client.
if ( isdefined( self.rider_info.VehicleExitAnim ) )
{
self.vehicle clientfield::increment( self.rider_info.position + "_exit", 1 );
self.vehicle SetAnim( self.rider_info.VehicleExitAnim, 1, 0, 1 );
}
switch ( str_mode )
{
case "ground":
exit_ground();
break;
case "low":
exit_low();
break;
case "variable":
exit_variable();
break;
default: AssertMsg( "Invalid mode for vehicle unload." );
}
if ( IsActor( self ) )
{
// Re-enable pathing once an AI leaves a vehicle.
self PathMode( "move allowed" );
// Allow dropping ammo/weapon after leaving the vehicle.
self.disableAmmoDrop = false;
self.dontDropWeapon = false;
}
if( isdefined( self.vehicle ) )
{
unclaim_position( self.vehicle, self.rider_info.position );
// If there is an associated use index, call UseVehicle.
//
if ( isdefined( level.vehiclerider_use_index[ self.rider_info.position ] ) && (self flagsys::get( "in_vehicle" )) )
{
self.vehicle UseVehicle( self, level.vehiclerider_use_index[ self.rider_info.position ] );
}
}
self flagsys::clear( "in_vehicle" );
self.vehicle = undefined;
self.rider_info = undefined;
self animation::set_death_anim( undefined );
set_goal();
self notify( "exited_vehicle" );
}
function set_goal()
{
if ( colors::is_color_ai() )
{
colors::enable();
}
else if ( !isdefined( self.target ) )
{
self SetGoal( self.origin );
}
}
/@
"Summary: Unload riders from a vehicle"
"Name: unload( str_group )"
"CallOn: Vehicle"
"OptionalArg: [str_group] Unload a specific group instead of all riders (all, driver, passengers, crew, gunners)."
"Example: vh_truck vehicle::unload( "all" );"
"Example: vh_truck vehicle::unload( "gunners" );"
@/
function unload( str_group = "all", str_mode, remove_rider_before_unloading, remove_riders_wait_time )
{
self notify( "unload", str_group );
Assert( isdefined( level.vehiclerider_groups[ str_group ] ), str_group + " is not a valid unload group." );
str_group = level.vehiclerider_groups[ str_group ]; // look up position subtring to use
a_ai_unloaded = [];
foreach ( ai_rider in self.riders )
{
if ( ( str_group == "all" ) || IsSubStr( ai_rider.rider_info.position, str_group ) )
{
ai_rider thread get_out( str_mode );
if ( !isdefined( a_ai_unloaded ) ) a_ai_unloaded = []; else if ( !IsArray( a_ai_unloaded ) ) a_ai_unloaded = array( a_ai_unloaded ); a_ai_unloaded[a_ai_unloaded.size]=ai_rider;;
}
}
if ( a_ai_unloaded.size > 0 )
{
if ( remove_rider_before_unloading === true )
{
remove_riders_after_wait( remove_riders_wait_time, a_ai_unloaded );
}
array::flagsys_wait_clear( a_ai_unloaded, "in_vehicle", (isdefined(self.unloadTimeout)?self.unloadTimeout:4) );
self notify( "unload", a_ai_unloaded );
}
}
function remove_riders_after_wait( wait_time, a_riders_to_remove )
{
wait wait_time;
if ( isdefined( a_riders_to_remove ) )
{
foreach( ai in a_riders_to_remove )
{
ArrayRemoveValue( self.riders, ai );
}
}
}
function ragdoll_dead_exit_rider()
{
self endon( "exited_vehicle" );
self waittill( "death" );
if ( IsActor( self ) && !self IsRagdoll() )
{
self Unlink();
self StartRagdoll();
}
}
function exit_ground()
{
self animation::set_death_anim( self.rider_info.ExitGroundDeathAnim );
if ( !IsDefined( self.rider_info.ExitGroundDeathAnim ) )
{
self thread ragdoll_dead_exit_rider();
}
Assert( IsString( self.rider_info.ExitGroundAnim ), "No exit animation for '" + self.rider_info.position + "'. Can't unload specified group." );
if ( IsString( self.rider_info.ExitGroundAnim ) )
{
animation::play( self.rider_info.ExitGroundAnim, self.vehicle, self.rider_info.AlignTag );
}
}
function exit_low()
{
self animation::set_death_anim( self.rider_info.ExitLowDeathAnim );
Assert( isdefined( self.rider_info.ExitLowAnim ), "No exit animation for '" + self.rider_info.position + "'. Can't unload specified group." );
animation::play( self.rider_info.ExitLowAnim, self.vehicle, self.rider_info.AlignTag );
}
function private handle_falling_death()
{
self endon( "landed" );
self waittill( "death" );
if ( IsActor( self ) )
{
self Unlink();
self StartRagdoll();
}
}
function private forward_euler_integration( e_move, v_target_landing, n_initial_speed )
{
// Sympletic euler integration.
landed = false;
integrationStep = 0.1; // Seconds
position = self.origin;
velocity = (0, 0, -n_initial_speed);
gravity = (0, 0, -385.8); // Gravity in inches/second^2 moving downward
while ( !landed )
{
previousPosition = position;
// Update the velocity and position based on the integration step.
velocity = velocity + gravity * integrationStep;
// Calculating velocity before position ensure sympletic integration.
position = position + velocity * integrationStep;
// If the next integration step will take us past our landing, just move to that position instead.
if ( ( position[2] + velocity[2] * integrationStep ) <= v_target_landing[2] )
{
landed = true;
position = v_target_landing;
}
/#
recordLine( previousPosition, position, ( 1, .5, 0 ), "Animscript", self );
#/
hostmigration::waitTillHostMigrationDone();
e_move MoveTo( position, integrationStep );
if ( !landed )
{
wait integrationStep;
}
}
}
function exit_variable()
{
ai = self;
self endon( "death" );
self notify( "exiting_vehicle" );
self thread handle_falling_death();
self animation::set_death_anim( self.rider_info.ExitHighDeathAnim );
Assert( isdefined( self.rider_info.ExitHighAnim ), "No exit animation for '" + self.rider_info.position + "'. Can't unload specified group." );
animation::play( self.rider_info.ExitHighAnim, self.vehicle, self.rider_info.AlignTag, 1, 0, 0 );
self animation::set_death_anim( self.rider_info.ExitHighLoopDeathAnim );
n_cur_height = get_height( self.vehicle );
bundle = self.vehicle get_bundle_for_ai( ai );
n_target_height = bundle.HighExitLandHeight;
if( ( isdefined( self.rider_info.DropUnderVehicleOrigin ) && self.rider_info.DropUnderVehicleOrigin ) || ( isdefined( self.DropUnderVehicleOriginOverride ) && self.DropUnderVehicleOriginOverride ) )
v_target_landing = ( self.vehicle.origin[0], self.vehicle.origin[1], self.origin[2] - n_cur_height + n_target_height );
else
v_target_landing = ( self.origin[0], self.origin[1], self.origin[2] - n_cur_height + n_target_height );
if( isdefined( self.overrideDropPosition ) )
v_target_landing = ( self.overrideDropPosition[0], self.overrideDropPosition[1], v_target_landing[2] );
if( isdefined( self.targetAngles ) )
angles = self.targetAngles;
else
angles = self.angles;
// Create the tag origin
e_move = util::spawn_model( "tag_origin", self.origin, angles );
self thread exit_high_loop_anim( e_move );
// Move the tag origin downward.
distance = n_target_height - n_cur_height;
initialSpeed = bundle.DropSpeed;
acceleration = 385.8; // Gravity in inches/second^2
// Kinematic Equation to compute distance given, time, velocity, and acceleration.
// distance = velocity(init) * time + (1/2) * acceleration * time^2;
// Use the quadratic equation to solve for time.
n_fall_time = ( -initialSpeed + Sqrt( Pow( initialSpeed, 2 ) - 2 * acceleration * distance ) ) / acceleration;
self notify( "falling", n_fall_time );
forward_euler_integration( e_move, v_target_landing, bundle.DropSpeed );
e_move waittill( "movedone" );
self notify( "landing" );
self animation::set_death_anim( self.rider_info.ExitHighLandDeathAnim );
animation::play( self.rider_info.ExitHighLandAnim, e_move, "tag_origin" );
self notify( "landed" );
self Unlink();
{wait(.05);};
// Detach from tag origin and delete.
e_move Delete();
}
function exit_high_loop_anim( e_parent )
{
self endon( "death" );
self endon( "landing" );
while ( true )
{
animation::play( self.rider_info.ExitHighLoopAnim, e_parent, "tag_origin" );
}
}
function get_height( e_ignore )
{
if(!isdefined(e_ignore))e_ignore=self;
const height_diff = 10;
trace = GroundTrace( self.origin + (0,0,height_diff), self.origin + ( 0, 0, -10000 ), false, e_ignore, false );
/#
recordLine( self.origin + (0,0,height_diff), trace[ "position" ], ( 1, .5, 0 ), "Animscript", self );
#/
return Distance( self.origin, trace[ "position" ] );
}
function get_bundle()
{
Assert( isdefined( self.vehicleridersbundle ), "No vehicleriders bundle specified for this vehicle (in the vehiclesettings gdt)." );
return struct::get_script_bundle( "vehicleriders", self.vehicleridersbundle );
}
function get_robot_bundle()
{
Assert( isdefined( self.vehicleridersrobotbundle ), "No vehicleriders robot bundle specified for this vehicle (in the vehiclesettings gdt)." );
return struct::get_script_bundle( "vehicleriders", self.vehicleridersrobotbundle );
}
/@
"Summary: Get a specific rider from a vehicle. Look at vehiclerider GDT for possible positions."
"Name: get_rider( str_pos )"
"CallOn: Vehicle"
"MandatoryArg: <str_group> The rider position to unload."
"Example: ai_rider = vh_truck vehicle::get_rider( "passenger1" );"
@/
function get_rider( str_pos ) // self = vehicle
{
if ( isdefined( self.riders ) )
{
foreach ( ai in self.riders )
{
if ( IsDefined( ai ) && ( ai.rider_info.position == str_pos ) )
{
return ai;
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function private _init_rider( vh, str_pos )
{
Assert( isdefined( self.vehicle ) || isdefined( vh ), "No vehicle specified for rider." );
Assert( isdefined( self.rider_info ) || isdefined( str_pos ), "No position specified for rider." );
if ( isdefined( vh ) )
{
self.vehicle = vh;
}
if(!isdefined(str_pos))str_pos=self.rider_info.position;
self.rider_info = self get_rider_info( self.vehicle, str_pos );
}