#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: 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: 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 ); }