#include common_scripts\utility; #using_animtree( "destructibles" ); // Car alarm constants MAX_SIMULTANEOUS_CAR_ALARMS = 2; CAR_ALARM_ALIAS = "car_alarm"; CAR_ALARM_OFF_ALIAS = "car_alarm_off"; NO_CAR_ALARM_MAX_ELAPSED_TIME = 120; CAR_ALARM_TIMEOUT = 25; DESTROYED_ATTACHMENT_SUFFIX = "_destroy"; SP_DAMAGE_BIAS = 0.5; SP_EXPLOSIVE_DAMAGE_BIAS = 9.0; MP_DAMAGE_BIAS = 1.0; MP_EXPLOSIVE_DAMAGE_BIAS = 13.0; SP_SHOTGUN_BIAS = 8.0; MP_SHOTGUN_BIAS = 4.0; init() { /# SetDevDvarIfUninitialized( "debug_destructibles", "0" ); SetDevDvarIfUninitialized( "destructibles_enable_physics", "1" ); SetDevDvarIfUninitialized( "destructibles_show_radiusdamage", "0" ); #/ level.destructibleSpawnedEntsLimit = 50; level.destructibleSpawnedEnts = []; level.currentCarAlarms = 0; level.commonStartTime = GetTime(); /# level.created_destructibles = []; #/ if ( !isdefined( level.func ) ) { // this array will be filled with code commands that SP or MP may use but doesn't exist in the other. level.func = []; } destructibles_enabled = true; /# destructibles_enabled = ( GetDvarInt( "destructibles_enabled", 1 ) == 1 ); #/ if ( destructibles_enabled ) find_destructibles(); deletables = GetEntArray( "delete_on_load", "targetname" ); foreach ( ent in deletables ) ent Delete(); /# SetDevDvarIfUninitialized( "scr_destructible_warning", "1" ); if ( GetDvarInt( "scr_destructible_warning", 1 ) == 1 ) thread warn_about_old_destructible(); #/ init_destroyed_count(); init_destructible_frame_queue(); } warn_about_old_destructible() { wait 1; // Find all old prefabs and print warning/errors about them so they get updated destructibles = GetEntArray( "destructible", "targetname" ); if ( destructibles.size != 0 ) { PrintLn( "This map contains old destructible vehicle prefabs which no longer work properly. Please update them to use the new vehicle prefabs in map_source\\prefabs\\destructible\\. See console for a list of vehicles that you need to updated." ); PrintLn( "^1###################################################^0" ); PrintLn( "^1###################################################^0" ); PrintLn( "^1###################################################^0" ); foreach ( vehicle in destructibles ) PrintLn( "^1Destructible vehicle at ( " + vehicle.origin + " ) uses an old prefab. Update it to use a prefab located in prefabs\destructible\^0" ); PrintLn( "^1###################################################^0" ); PrintLn( "^1###################################################^0" ); PrintLn( "^1###################################################^0" ); AssertMsg( "This map contains old destructible vehicle prefabs which no longer work properly. Please update them to use the new vehicle prefabs in map_source\\prefabs\\destructible\\. See console for a list of vehicles that you need to updated." ); } } find_destructibles() { //--------------------------------------------------------------------- // Find all destructibles by their targetnames and run the setup //--------------------------------------------------------------------- // array_thread( GetEntArray( "destructible_vehicle", "targetname" ), ::setup_destructibles ); //assuring orders -nate vehicles = GetEntArray( "destructible_vehicle", "targetname" ); foreach ( vehicle in vehicles ) vehicle setup_destructibles(); destructible_toy = GetEntArray( "destructible_toy", "targetname" ); foreach ( toy in destructible_toy ) toy setup_destructibles(); /# total = 0; if ( GetDvarInt( "destructibles_locate" ) > 0 ) { // Print out the destructibles we created and where they are all located PrintLn( "##################" ); PrintLn( "DESTRUCTIBLE LIST:" ); PrintLn( "##################" ); PrintLn( "" ); keys = GetArrayKeys( level.created_destructibles ); foreach ( key in keys ) { PrintLn( key + ": " + level.created_destructibles[ key ].size ); total += level.created_destructibles[ key ].size; } PrintLn( "" ); PrintLn( "Total: " + total ); PrintLn( "" ); PrintLn( "Locations:" ); foreach ( key in keys ) { foreach ( destructible in level.created_destructibles[ key ] ) { PrintLn( key + ": " + destructible.origin ); //destructible thread maps\_debug::drawOrgForever(); } } PrintLn( "" ); PrintLn( "##################" ); PrintLn( "##################" ); PrintLn( "##################" ); level.created_destructibles = undefined; } #/ } setup_destructibles( cached ) { if ( !isdefined( cached ) ) cached = false; //--------------------------------------------------------------------- // Figure out what destructible information this entity should use //--------------------------------------------------------------------- destuctableInfo = undefined; AssertEx( IsDefined( self.destructible_type ), "Destructible object with targetname 'destructible' does not have a 'destructible_type' key / value" ); self.modeldummyon = false;// - nate added for vehicle dummy stuff. This is so I can turn a destructible into a dummy and throw it around on jeepride. self add_damage_owner_recorder(); // Mackey added to track who is damaging the car self.destuctableInfo = common_scripts\_destructible_types::makeType( self.destructible_type ); //println( "### DESTRUCTIBLE ### assigned infotype index: " + self.destuctableInfo ); if ( self.destuctableInfo < 0 ) return; /# // Store what destructibles we create and where they are located so we can get a list in the console if ( !isdefined( level.created_destructibles[ self.destructible_type ] ) ) level.created_destructibles[ self.destructible_type ] = []; nextIndex = level.created_destructibles[ self.destructible_type ].size; level.created_destructibles[ self.destructible_type ][ nextIndex ] = self; #/ if ( !cached ) precache_destructibles(); add_destructible_fx(); //--------------------------------------------------------------------- // Attach all parts to the entity //--------------------------------------------------------------------- if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts ) ) { self.destructible_parts = []; for ( i = 0; i < level.destructible_type[ self.destuctableInfo ].parts.size; i++ ) { // create the struct where the info for each entity will be held self.destructible_parts[ i ] = SpawnStruct(); // set it's current state to 0 since it has never taken damage yet and will be on it's first state self.destructible_parts[ i ].v[ "currentState" ] = 0; // if it has a health value then store it's value if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "health" ] ) ) self.destructible_parts[ i ].v[ "health" ] = level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "health" ]; // find random attachements such as random advertisements on taxi cabs and attach them now if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "random_dynamic_attachment_1" ] ) ) { randAttachmentIndex = RandomInt( level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "random_dynamic_attachment_1" ].size ); attachTag = level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "random_dynamic_attachment_tag" ][ randAttachmentIndex ]; attach_model_1 = level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "random_dynamic_attachment_1" ][ randAttachmentIndex ]; attach_model_2 = level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "random_dynamic_attachment_2" ][ randAttachmentIndex ]; clipToRemove = level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "clipToRemove" ][ randAttachmentIndex ]; self thread do_random_dynamic_attachment( attachTag, attach_model_1, attach_model_2, clipToRemove ); } // continue if it's the base model since its not an attached part if ( i == 0 ) continue; // attach the part now modelName = level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "modelName" ]; tagName = level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "tagName" ]; stateIndex = 1; while ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ i ][ stateIndex ] ) ) { stateTagName = level.destructible_type[ self.destuctableInfo ].parts[ i ][ stateIndex ].v[ "tagName" ]; stateModelName = level.destructible_type[ self.destuctableInfo ].parts[ i ][ stateIndex ].v[ "modelName" ]; if ( IsDefined( stateTagName ) && stateTagName != tagName ) { self hideapart( stateTagName ); if ( self.modeldummyon ) self.modeldummy hideapart( stateTagName ); } stateIndex++; } } } // some destructibles have collision that needs to change due to the large change in the destructible when it blows pu if ( IsDefined( self.target ) ) thread destructible_handles_collision_brushes(); //--------------------------------------------------------------------- // Make this entity take damage and wait for events //--------------------------------------------------------------------- if ( self.code_classname != "script_vehicle" ) self SetCanDamage( true ); if ( isSP() ) self thread connectTraverses(); self thread destructible_think(); } destructible_create( type, tagName, health, validAttackers, validDamageZone, validDamageCause ) { //--------------------------------------------------------------------- // Creates a new information structure for a destructible object //--------------------------------------------------------------------- Assert( IsDefined( type ) ); if ( !isdefined( level.destructible_type ) ) level.destructible_type = []; destructibleIndex = level.destructible_type.size; destructibleIndex = level.destructible_type.size; level.destructible_type[ destructibleIndex ] = SpawnStruct(); level.destructible_type[ destructibleIndex ].v[ "type" ] = type; level.destructible_type[ destructibleIndex ].parts = []; level.destructible_type[ destructibleIndex ].parts[ 0 ][ 0 ] = SpawnStruct(); level.destructible_type[ destructibleIndex ].parts[ 0 ][ 0 ].v[ "modelName" ] = self.model; level.destructible_type[ destructibleIndex ].parts[ 0 ][ 0 ].v[ "tagName" ] = tagName; level.destructible_type[ destructibleIndex ].parts[ 0 ][ 0 ].v[ "health" ] = health; level.destructible_type[ destructibleIndex ].parts[ 0 ][ 0 ].v[ "validAttackers" ] = validAttackers; level.destructible_type[ destructibleIndex ].parts[ 0 ][ 0 ].v[ "validDamageZone" ] = validDamageZone; level.destructible_type[ destructibleIndex ].parts[ 0 ][ 0 ].v[ "validDamageCause" ] = validDamageCause; level.destructible_type[ destructibleIndex ].parts[ 0 ][ 0 ].v[ "godModeAllowed" ] = true; level.destructible_type[ destructibleIndex ].parts[ 0 ][ 0 ].v[ "rotateTo" ] = self.angles; level.destructible_type[ destructibleIndex ].parts[ 0 ][ 0 ].v[ "vehicle_exclude_anim" ] = false; } destructible_part( tagName, modelName, health, validAttackers, validDamageZone, validDamageCause, alsoDamageParent, physicsOnExplosion, grenadeImpactDeath, receiveDamageFromParent ) { //--------------------------------------------------------------------- // Adds a part to the last created destructible information structure //--------------------------------------------------------------------- destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts.size ) ); partIndex = level.destructible_type[ destructibleIndex ].parts.size; Assert( partIndex > 0 ); stateIndex = 0; destructible_info( partIndex, stateIndex, tagName, modelName, health, validAttackers, validDamageZone, validDamageCause, alsoDamageParent, physicsOnExplosion, grenadeImpactDeath, undefined, receiveDamageFromParent ); } destructible_state( tagName, modelName, health, validAttackers, validDamageZone, validDamageCause, grenadeImpactDeath, splashRotation ) { //--------------------------------------------------------------------- // Adds a new part that is a state of the last created part // When the previous part reaches zero health this part will show up // and the previous part will be removed //--------------------------------------------------------------------- destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size ); if ( !isdefined( tagName ) && partIndex == 0 ) tagName = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ 0 ].v[ "tagName" ]; destructible_info( partIndex, stateIndex, tagName, modelName, health, validAttackers, validDamageZone, validDamageCause, undefined, undefined, grenadeImpactDeath, splashRotation ); } destructible_fx( tagName, fxName, useTagAngles, damageType, groupNum, fxCost ) { //assert( IsDefined( tagName ) ); Assert( IsDefined( fxName ) ); if ( !isdefined( useTagAngles ) ) useTagAngles = true; if ( !isdefined( groupNum ) ) groupNum = 0; if ( !isdefined( fxCost ) ) fxCost = 0; destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); fx_size = 0; if ( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "fx_filename" ] ) ) if ( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "fx_filename" ][ groupNum ] ) ) fx_size = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "fx_filename" ][ groupNum ].size; if ( IsDefined( damageType ) ) level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "fx_valid_damagetype" ][ groupNum ][ fx_size ] = damageType; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "fx_filename" ][ groupNum ][ fx_size ] = fxName; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "fx_tag" ][ groupNum ][ fx_size ] = tagName; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "fx_useTagAngles" ][ groupNum ][ fx_size ] = useTagAngles; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "fx_cost" ][ groupNum ][ fx_size ] = fxCost; } destructible_loopfx( tagName, fxName, loopRate, fxCost ) { Assert( IsDefined( tagName ) ); Assert( IsDefined( fxName ) ); Assert( IsDefined( loopRate ) ); Assert( loopRate > 0 ); if ( !isdefined( fxCost ) ) fxCost = 0; destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); fx_size = 0; if ( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopfx_filename" ] ) ) fx_size = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopfx_filename" ].size; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopfx_filename" ][ fx_size ] = fxName; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopfx_tag" ][ fx_size ] = tagName; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopfx_rate" ][ fx_size ] = loopRate; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopfx_cost" ][ fx_size ] = fxCost; } destructible_healthdrain( amount, interval, badplaceRadius, badplaceTeam ) { Assert( IsDefined( amount ) ); destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "healthdrain_amount" ] = amount; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "healthdrain_interval" ] = interval; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "badplace_radius" ] = badplaceRadius; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "badplace_team" ] = badplaceTeam; } destructible_sound( soundAlias, soundCause, groupNum ) { Assert( IsDefined( soundAlias ) ); destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); if ( !isdefined( groupNum ) ) groupNum = 0; if ( !isdefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "sound" ] ) ) { level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "sound" ] = []; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "soundCause" ] = []; } if ( !isdefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "sound" ][ groupNum ] ) ) { level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "sound" ][ groupNum ] = []; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "soundCause" ][ groupNum ] = []; } index = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "sound" ][ groupNum ].size; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "sound" ][ groupNum ][ index ] = soundAlias; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "soundCause" ][ groupNum ][ index ] = soundCause; } destructible_loopsound( soundAlias, loopsoundCause ) { Assert( IsDefined( soundAlias ) ); destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); if ( !isdefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopsound" ] ) ) { level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopsound" ] = []; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopsoundCause" ] = []; } index = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopsound" ].size; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopsound" ][ index ] = soundAlias; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "loopsoundCause" ][ index ] = loopsoundCause; } destructible_anim( animName, animTree, animType, vehicle_exclude, groupNum, mpAnim, maxStartDelay, animRateMin, animRateMax ) { if ( !isdefined( vehicle_exclude ) ) vehicle_exclude = false; Assert( IsDefined( anim ) ); Assert( IsDefined( animName ) ); Assert( IsDefined( animtree ) ); if ( !isdefined( groupNum ) ) groupNum = 0; array = []; array[ "anim" ] = animName; array[ "animTree" ] = animtree; array[ "animType" ] = animType; array[ "vehicle_exclude_anim" ] = vehicle_exclude; array[ "groupNum" ] = groupNum; array[ "mpAnim" ] = mpAnim; array[ "maxStartDelay" ] = maxStartDelay; array[ "animRateMin" ] = animRateMin; array[ "animRateMax" ] = animRateMax; add_array_to_destructible( "animation", array ); } destructible_spotlight( tag ) { AssertEx( IsDefined( tag ), "Tag wasn't defined for destructible_spotlight" ); array = []; array[ "spotlight_tag" ] = tag; array[ "spotlight_fx" ] = "spotlight_fx"; array[ "spotlight_brightness" ] = 0.85; array[ "randomly_flip" ] = true; dvars = []; dvars[ "r_spotlightendradius" ] = 1200; dvars[ "r_spotlightstartradius" ] = 50; array[ "dvars" ] = dvars; add_keypairs_to_destructible( array ); } add_key_to_destructible( key, val ) { AssertEx( IsDefined( key ), "Key wasn't defined!" ); AssertEx( IsDefined( val ), "Val wasn't defined!" ); array = []; array[ key ] = val; add_keypairs_to_destructible( array ); } /* ============= ///ScriptDocBegin "Name: add_keypairs_to_destructible( )" "Summary: Goes through the array and adds each key/val to .v." "Module: Destructibles" "MandatoryArg: : Array of keypairs." "Example: add_keypairs_to_destructible( array );" "SPMP: singleplayer" ///ScriptDocEnd ============= */ add_keypairs_to_destructible( array ) { // add a single flat array to the destructible, overwriting any existing identical keys. destructibleIndex = level.destructible_type.size - 1; partIndex = level.destructible_type[ destructibleIndex ].parts.size - 1; stateIndex = level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1; Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); foreach ( key, val in array ) { level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ key ] = val; } } /* ============= ///ScriptDocBegin "Name: add_array_to_destructible( , )" "Summary: Goes through the array and adds each key/val to .v." "Module: Destructibles" "MandatoryArg: : Array of keypairs." "MandatoryArg: : Array of keypairs." "Example: add_array_to_destructible( array );" "SPMP: singleplayer" ///ScriptDocEnd ============= */ add_array_to_destructible( array_name, array ) { // add an array under a key name, so you can have multiple arrays under a given key name destructibleIndex = level.destructible_type.size - 1; partIndex = level.destructible_type[ destructibleIndex ].parts.size - 1; stateIndex = level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1; Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); v = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v; if ( !isdefined( v[ array_name ] ) ) { v[ array_name ] = []; } v[ array_name ][ v[ array_name ].size ] = array; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v = v; } destructible_car_alarm() { destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "triggerCarAlarm" ] = true; } destructible_lights_out( range ) { if ( !isdefined( range ) ) range = 256; destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "break_nearby_lights" ] = range; } random_dynamic_attachment( tagName, attachment_1, attachment_2, clipToRemove ) { // made so I can put random advertisements on the destructible taxi cabs without making lots of destructible types for each version Assert( IsDefined( tagName ) ); Assert( IsDefined( attachment_1 ) ); if ( !isdefined( attachment_2 ) ) attachment_2 = ""; destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); //stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); stateIndex = 0; Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); if ( !isdefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "random_dynamic_attachment_1" ] ) ) { level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "random_dynamic_attachment_1" ] = []; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "random_dynamic_attachment_2" ] = []; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "random_dynamic_attachment_tag" ] = []; } index = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "random_dynamic_attachment_1" ].size; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "random_dynamic_attachment_1" ][ index ] = attachment_1; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "random_dynamic_attachment_2" ][ index ] = attachment_2; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "random_dynamic_attachment_tag" ][ index ] = tagName; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "clipToRemove" ][ index ] = clipToRemove; } destructible_physics( physTagName, physVelocity ) { destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); if ( !isdefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "physics" ] ) ) { level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "physics" ] = []; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "physics_tagName" ] = []; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "physics_velocity" ] = []; } index = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "physics" ].size; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "physics" ][ index ] = true; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "physics_tagName" ][ index ] = physTagName; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "physics_velocity" ][ index ] = physVelocity; } destructible_splash_damage_scaler( damage_multiplier ) { destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "splash_damage_scaler" ] = damage_multiplier; } destructible_explode( force_min, force_max, rangeSP, rangeMP, mindamage, maxdamage, continueDamage, originOffset, earthQuakeScale, earthQuakeRadius ) { destructibleIndex = ( level.destructible_type.size - 1 ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); if ( isSP() ) level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "explode_range" ] = rangeSP; else level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "explode_range" ] = rangeMP; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "explode" ] = true; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "explode_force_min" ] = force_min; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "explode_force_max" ] = force_max; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "explode_mindamage" ] = mindamage; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "explode_maxdamage" ] = maxdamage; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "continueDamage" ] = continueDamage; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "originOffset" ] = originOffset; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "earthQuakeScale" ] = earthQuakeScale; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "earthQuakeRadius" ] = earthQuakeRadius; } destructible_info( partIndex, stateIndex, tagName, modelName, health, validAttackers, validDamageZone, validDamageCause, alsoDamageParent, physicsOnExplosion, grenadeImpactDeath, splashRotation, receiveDamageFromParent ) { Assert( IsDefined( partIndex ) ); Assert( IsDefined( stateIndex ) ); Assert( IsDefined( level.destructible_type ) ); Assert( level.destructible_type.size > 0 ); if ( IsDefined( modelName ) ) modelName = ToLower( modelName ); destructibleIndex = ( level.destructible_type.size - 1 ); level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] = SpawnStruct(); level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "modelName" ] = modelName; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "tagName" ] = tagName; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "health" ] = health; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "validAttackers" ] = validAttackers; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "validDamageZone" ] = validDamageZone; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "validDamageCause" ] = validDamageCause; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "alsoDamageParent" ] = alsoDamageParent; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "physicsOnExplosion" ] = physicsOnExplosion; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "grenadeImpactDeath" ] = grenadeImpactDeath; // sanity check please. I set this here so that I don't have to do isdefined on every part evertime it gets hit level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "godModeAllowed" ] = false; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "splashRotation" ] = splashRotation; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "receiveDamageFromParent" ] = receiveDamageFromParent; } precache_destructibles() { // I needed this to be seperate for vehicle scripts. //--------------------------------------------------------------------- // Precache referenced models and load referenced effects //--------------------------------------------------------------------- if ( !isdefined( level.destructible_type[ self.destuctableInfo ].parts ) ) return; //if ( !isdefined( level.precachedModels ) ) // level.precachedModels = []; for ( i = 0; i < level.destructible_type[ self.destuctableInfo ].parts.size; i++ ) { for ( j = 0; j < level.destructible_type[ self.destuctableInfo ].parts[ i ].size; j++ ) { if ( level.destructible_type[ self.destuctableInfo ].parts[ i ].size <= j ) continue; if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "modelName" ] ) ) { //model = level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "modelName" ]; //if ( !isdefined( level.precachedModels[ model ] ) ) //{ // level.precachedModels[ model ] = true; // println( "precachemodel( " + model + " )" ); //} PreCacheModel( level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "modelName" ] ); } // in MP we have to precache animations that will be used if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "animation" ] ) ) { animGroups = level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "animation" ]; foreach ( group in animGroups ) { if ( IsDefined( group[ "mpAnim" ] ) ) noself_func( "precacheMpAnim", group[ "mpAnim" ] ); } } // find random attachements such as random advertisements on taxi cabs and precache them now if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "random_dynamic_attachment_1" ] ) ) { foreach ( model in level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "random_dynamic_attachment_1" ] ) { if ( IsDefined( model ) && model != "" ) { PreCacheModel( model ); PreCacheModel( model + DESTROYED_ATTACHMENT_SUFFIX ); //if ( !isdefined( level.precachedModels[ model ] ) ) //{ // level.precachedModels[ model ] = true; // println( "precachemodel( " + model + " )" ); //} //if ( !isdefined( level.precachedModels[ model + DESTROYED_ATTACHMENT_SUFFIX ] ) ) //{ // level.precachedModels[ model + DESTROYED_ATTACHMENT_SUFFIX ] = true; // println( "precachemodel( " + model + DESTROYED_ATTACHMENT_SUFFIX + " )" ); //} } } foreach ( model in level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "random_dynamic_attachment_2" ] ) { if ( IsDefined( model ) && model != "" ) { PreCacheModel( model ); PreCacheModel( model + DESTROYED_ATTACHMENT_SUFFIX ); //if ( !isdefined( level.precachedModels[ model ] ) ) //{ // level.precachedModels[ model ] = true; // println( "precachemodel( " + model + " )" ); //} //if ( !isdefined( level.precachedModels[ model + DESTROYED_ATTACHMENT_SUFFIX ] ) ) //{ // level.precachedModels[ model + DESTROYED_ATTACHMENT_SUFFIX ] = true; // println( "precachemodel( " + model + DESTROYED_ATTACHMENT_SUFFIX + " )" ); //} } } } } } } add_destructible_fx() { // I needed this to be seperate for vehicle scripts. //--------------------------------------------------------------------- // Precache referenced models and load referenced effects //--------------------------------------------------------------------- if ( !isdefined( level.destructible_type[ self.destuctableInfo ].parts ) ) return; //if ( !isdefined( level.precachedFX ) ) // level.precachedFX = []; for ( i = 0; i < level.destructible_type[ self.destuctableInfo ].parts.size; i++ ) { for ( j = 0; j < level.destructible_type[ self.destuctableInfo ].parts[ i ].size; j++ ) { if ( level.destructible_type[ self.destuctableInfo ].parts[ i ].size <= j ) continue; part = level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ]; if ( IsDefined( part.v[ "fx_filename" ] ) ) { for ( g = 0; g < part.v[ "fx_filename" ].size; g++ ) { // for multiple checks on fx when doing conditional fx playing fx_filenames = part.v[ "fx_filename" ][ g ]; if ( IsDefined( fx_filenames ) ) { // has we already set this up? if ( IsDefined( part.v[ "fx" ] ) && IsDefined( part.v[ "fx" ][ g ] ) && part.v[ "fx" ][ g ].size == fx_filenames.size ) continue; foreach ( idx, fx_filename in fx_filenames ) { level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "fx" ][ g ][ idx ] = _loadfx( fx_filename ); //if ( !isdefined( level.precachedFX[ fx_filename ] ) ) //{ // level.precachedFX[ fx_filename ] = true; // println( "loadfx( " + fx_filename + " )" ); //} } } } } loopfx_filenames = level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "loopfx_filename" ]; if ( IsDefined( loopfx_filenames ) ) { // has we already set this up? if ( IsDefined( part.v[ "loopfx" ] ) && part.v[ "loopfx" ].size == loopfx_filenames.size ) continue; foreach ( idx, loopfx_filename in loopfx_filenames ) { level.destructible_type[ self.destuctableInfo ].parts[ i ][ j ].v[ "loopfx" ][ idx ] = _loadfx( loopfx_filename ); //if ( !isdefined( level.precachedFX[ loopfx_filename ] ) ) //{ // level.precachedFX[ loopfx_filename ] = true; // println( "loadfx( " + loopfx_filename + " )" ); //} } } } } } canDamageDestructible( testDestructible ) { foreach ( destructible in self.destructibles ) { if ( destructible == testDestructible ) return true; } return false; } destructible_think() { //--------------------------------------------------------------------- // Force it to run update part one time first so we can have parts with // 0 health that will start on level load instead of waiting for damage //--------------------------------------------------------------------- damage = 0; modelName = self.model; tagName = undefined; point = self.origin; direction_vec = undefined; attacker = undefined; damageType = undefined; self destructible_update_part( damage, modelName, tagName, point, direction_vec, attacker, damageType ); //--------------------------------------------------------------------- // Wait until this entity takes damage //--------------------------------------------------------------------- self endon( "stop_taking_damage" ); for ( ;; ) { // set these to undefined to clear them for each loop to save variables damage = undefined; attacker = undefined; direction_vec = undefined; point = undefined; type = undefined; modelName = undefined; tagName = undefined; partName = undefined; dflags = undefined; self waittill( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, dflags ); prof_begin( "_destructible" ); if ( !isdefined( damage ) ) continue; if ( IsDefined( attacker ) && IsDefined( attacker.type ) && attacker.type == "soft_landing" && !attacker canDamageDestructible( self ) ) continue; if ( isSP() ) damage *= SP_DAMAGE_BIAS; else damage *= MP_DAMAGE_BIAS; if ( damage <= 0 ) continue; if ( IsDefined( attacker ) && IsPlayer( attacker ) ) self.damageOwner = attacker; type = getDamageType( type ); Assert( IsDefined( type ) ); // shotguns only do one notify so we need to amp up the damage if ( is_shotgun_damage( attacker, type ) ) { if ( isSP() ) damage *= SP_SHOTGUN_BIAS; else damage *= MP_SHOTGUN_BIAS; } /# if ( GetDvarInt( "debug_destructibles" , 0 ) == 1 ) { Print3d( point, ".", ( 1, 1, 1 ), 1.0, 0.5, 100 ); if ( IsDefined( damage ) ) IPrintLn( "damage amount: " + damage ); if ( IsDefined( modelName ) ) IPrintLn( "hit model: " + modelName ); if ( IsDefined( tagName ) ) IPrintLn( "hit model tag: " + tagName ); else IPrintLn( "hit model tag: " ); } #/ // override for when base model is damaged. We dont want to pass in empty strings if ( !isdefined( modelName ) || ( modelName == "" ) ) { Assert( IsDefined( self.model ) ); modelName = self.model; } if ( IsDefined( tagName ) && tagName == "" ) { if ( IsDefined( partName ) && partName != "" && partName != "tag_body" && partName != "body_animate_jnt" ) tagName = partName; else tagName = undefined; baseModelTag = level.destructible_type[ self.destuctableInfo ].parts[ 0 ][ 0 ].v[ "tagName" ]; if ( IsDefined( baseModelTag ) && IsDefined( partName ) && ( baseModelTag == partName ) ) tagName = undefined; } prof_end( "_destructible" ); // special handling for splash and projectile damage if ( type == "splash" ) { /# if ( GetDvarInt( "debug_destructibles" , 0 ) == 1 ) IPrintLn( "type = splash" ); #/ if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ 0 ][ 0 ].v[ "splash_damage_scaler" ] ) ) damage *= level.destructible_type[ self.destuctableInfo ].parts[ 0 ][ 0 ].v[ "splash_damage_scaler" ]; else { if ( isSP() ) damage *= SP_EXPLOSIVE_DAMAGE_BIAS; else damage *= MP_EXPLOSIVE_DAMAGE_BIAS; } self destructible_splash_damage( Int( damage ), point, direction_vec, attacker, type ); continue; } self thread destructible_update_part( Int( damage ), modelName, tagName, point, direction_vec, attacker, type ); } } is_shotgun_damage( attacker, type ) { if ( type != "bullet" ) return false; if ( !isdefined( attacker ) ) return false; currentWeapon = undefined; if ( IsPlayer( attacker ) ) { currentweapon = attacker getCurrentWeapon(); } if ( !isdefined( currentweapon ) ) return false; class = weaponClass( currentweapon ); if ( isdefined( class ) && class == "spread" ) return true; return false; } getPartAndStateIndex( modelName, tagName ) { Assert( IsDefined( modelName ) ); info = SpawnStruct(); info.v = []; partIndex = -1; stateIndex = -1; Assert( IsDefined( self.model ) ); if ( ( ToLower( modelName ) == ToLower( self.model ) ) && ( !isdefined( tagName ) ) ) { modelName = self.model; tagName = undefined; partIndex = 0; stateIndex = 0; } for ( i = 0; i < level.destructible_type[ self.destuctableInfo ].parts.size; i++ ) { stateIndex = self.destructible_parts[ i ].v[ "currentState" ]; if ( level.destructible_type[ self.destuctableInfo ].parts[ i ].size <= stateIndex ) continue; if ( !isdefined( tagName ) ) continue; if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ i ][ stateIndex ].v[ "tagName" ] ) ) { partTagName = level.destructible_type[ self.destuctableInfo ].parts[ i ][ stateIndex ].v[ "tagName" ]; if ( partTagName == tagName ) { partIndex = i; break; } } } Assert( stateIndex >= 0 ); Assert( IsDefined( partIndex ) ); info.v[ "stateIndex" ] = stateindex; info.v[ "partIndex" ] = partindex; return info; } destructible_update_part( damage, modelName, tagName, point, direction_vec, attacker, damageType, partInfo ) { //--------------------------------------------------------------------- // Find what part this is, or is a child of. If the base model was // the entity that was damaged the part index will be -1 //--------------------------------------------------------------------- if ( !isdefined( self.destructible_parts ) ) return; if ( self.destructible_parts.size == 0 ) return; prof_begin( "_destructible" ); info = getPartAndStateIndex( modelName, tagName ); stateIndex = info.v[ "stateIndex" ]; partIndex = info.v[ "partIndex" ]; prof_end( "_destructible" ); if ( partIndex < 0 ) return; //--------------------------------------------------------------------- // Deduct the damage amount from the part's health // If the part runs out of health go to the next state //--------------------------------------------------------------------- state_before = stateIndex; updateHealthValue = false; delayModelSwap = false; prof_begin( "_destructible" ); for ( ;; ) { stateIndex = self.destructible_parts[ partIndex ].v[ "currentState" ]; // there isn't another state to go to when damaged if ( !isdefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ] ) ) break; // see if the model is also supposed to damage the parent if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ 0 ].v[ "alsoDamageParent" ] ) ) { if ( getDamageType( damageType ) != "splash" ) { ratio = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ 0 ].v[ "alsoDamageParent" ]; parentDamage = Int( damage * ratio ); self thread notifyDamageAfterFrame( parentDamage, attacker, direction_vec, point, damageType, "", "" ); } } // loop through all parts to see which ones also need to get this damage applied to them ( based on their "receiveDamageFromParent" value ) if ( getDamageType( damageType ) != "splash" ) { foreach ( part in level.destructible_type[ self.destuctableInfo ].parts ) { if ( !isdefined( part[ 0 ].v[ "receiveDamageFromParent" ] ) ) continue; if ( !isdefined( part[ 0 ].v[ "tagName" ] ) ) continue; ratio = part[ 0 ].v[ "receiveDamageFromParent" ]; Assert( ratio > 0 ); childDamage = Int( damage * ratio ); childTagName = part[ 0 ].v[ "tagName" ]; self thread notifyDamageAfterFrame( childDamage, attacker, direction_vec, point, damageType, "", childTagName ); } } if ( !isdefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ].v[ "health" ] ) ) break; if ( !isdefined( self.destructible_parts[ partIndex ].v[ "health" ] ) ) break; if ( updateHealthValue ) self.destructible_parts[ partIndex ].v[ "health" ] = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ].v[ "health" ]; updateHealthValue = false; /# if ( GetDvarInt( "debug_destructibles" , 0 ) == 1 ) { IPrintLn( "stateindex: " + stateIndex ); IPrintLn( "damage: " + damage ); IPrintLn( "health( before ): " + self.destructible_parts[ partIndex ].v[ "health" ] ); } #/ // Handle grenades hitting glass parts. Grenades should make the glass completely break instead of just doing 1 damage and shattering the glass if ( ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ].v[ "grenadeImpactDeath" ] ) ) && ( damageType == "impact" ) ) damage = 100000000; // apply the damage to the part if the attacker was a valid attacker savedHealth = self.destructible_parts[ partIndex ].v[ "health" ]; validAttacker = self isAttackerValid( partIndex, stateIndex, attacker ); if ( validAttacker ) { validDamageCause = self isValidDamageCause( partIndex, stateIndex, damageType ); if ( validDamageCause ) { if ( IsDefined( attacker ) ) { if ( IsPlayer( attacker ) ) { self.player_damage += damage; } else { if ( attacker != self ) self.non_player_damage += damage; } } // Chad - ask Brent why we think melee is worth 100000 damage if ( IsDefined( damageType ) ) { if ( damageType == "melee" || damageType == "impact" ) damage = 100000; } self.destructible_parts[ partIndex ].v[ "health" ] -= damage; } } /# if ( GetDvarInt( "debug_destructibles" , 0 ) == 1 ) IPrintLn( "health( after ): " + self.destructible_parts[ partIndex ].v[ "health" ] ); #/ // if the part still has health left then we're done if ( self.destructible_parts[ partIndex ].v[ "health" ] > 0 ) { prof_end( "_destructible" ); return; } // cap on the number of destructibles killed in a single frame if ( IsDefined( partInfo ) ) { partInfo.v[ "fxcost" ] = get_part_FX_cost_for_action_state( partIndex, self.destructible_parts[ partIndex ].v[ "currentState" ] ); add_destructible_to_frame_queue( self, partInfo, damage ); self.waiting_for_queue = true; self waittill( "queue_processed", success ); self.waiting_for_queue = undefined; if ( !success )// can we be destroyed this frame? { self.destructible_parts[ partIndex ].v[ "health" ] = savedHealth; return; } } // if the part ran out of health then carry over to the next part damage = Int( abs( self.destructible_parts[ partIndex ].v[ "health" ] ) ); // Brent asks - why is this condition here? It'll never trigger given that abs() does the following: // "fabs returns the absolute value of x. Absolute value is a number's distance from zero on the number line. The absolute value of -4 is 4; the absolute value of 4 is 4." // It should probably be removed if ( damage < 0 ) { prof_end( "_destructible" ); return; } self.destructible_parts[ partIndex ].v[ "currentState" ]++; stateIndex = self.destructible_parts[ partIndex ].v[ "currentState" ]; actionStateIndex = ( stateIndex - 1 ); // use these rather than re-getting them all the time. This insures that we do // not overwrite their values too. action_v = undefined; if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ] ) ) action_v = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v; state_v = undefined; if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ] ) ) state_v = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ].v; if ( !isdefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ] ) ) { prof_end( "_destructible" ); return; } //--------------------------------------------------------------------- // A state change is required so detach the old model or replace it if // it's the base model that took the damage. // Then attach the model ( if specified ) used for the new state // Only do this if there is another state to go to, some parts might have // fx or anims, or sounds but no next model to go to //--------------------------------------------------------------------- // if the part is meant to explode on this state set a flag. Actual explosion will be done down below if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "explode" ] ) ) self.exploding = true; // stop all previously looped sounds if ( IsDefined( self.loopingSoundStopNotifies ) && IsDefined( self.loopingSoundStopNotifies[ toString( partIndex ) ] ) ) { for ( i = 0; i < self.loopingSoundStopNotifies[ toString( partIndex ) ].size; i++ ) { self notify( self.loopingSoundStopNotifies[ toString( partIndex ) ][ i ] ); if ( isSP() && self.modeldummyon ) self.modeldummy notify( self.loopingSoundStopNotifies[ toString( partIndex ) ][ i ] ); } self.loopingSoundStopNotifies[ toString( partIndex ) ] = undefined; } // setup our destructible light if we want one and can find one if ( IsDefined( action_v[ "break_nearby_lights" ] ) ) { self destructible_get_my_breakable_light( action_v[ "break_nearby_lights" ] ); } // swap the model // this doesn't work when threaded off to another function if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ] ) ) { if ( partIndex == 0 ) // base model damaged { newModel = state_v[ "modelName" ]; if ( IsDefined( newModel ) && newModel != self.model ) { self SetModel( newModel ); if ( isSP() && self.modeldummyon ) self.modeldummy SetModel( newModel ); destructible_splash_rotatation( state_v ); } } else // part was damaged, not the base model { // handle a part getting damaged here - must be detached and reattached self hideapart( tagName ); if ( isSP() && self.modeldummyon ) self.modeldummy hideapart( tagName ); tagName = state_v[ "tagName" ]; if ( IsDefined( tagName ) ) { self showapart( tagName ); if ( isSP() && self.modeldummyon ) self.modeldummy showapart( tagName ); } } } eModel = get_dummy(); // If its exploding clear all previous animations on the destructible. The only animation that will play after this is an explosion animation if ( IsDefined( self.exploding ) ) self clear_anims( eModel ); // if the part has an anim then play it now groupNumber = destructible_animation_think( action_v, eModel, damageType, partIndex ); // if the part has fx then play it now groupNumber = destructible_fx_think( action_v, eModel, damageType, partIndex, groupNumber ); // if the part has a soundalias then play it now groupNumber = destructible_sound_think( action_v, eModel, damageType, groupNumber ); // if the part has a looping fx then play it now if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "loopfx" ] ) ) { loopfx_size = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "loopfx_filename" ].size; if ( loopfx_size > 0 ) self notify( "FX_State_Change" + partIndex ); for ( idx = 0; idx < loopfx_size; idx++ ) { Assert( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "loopfx_tag" ][ idx ] ) ); loopfx = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "loopfx" ][ idx ]; loopfx_tag = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "loopfx_tag" ][ idx ]; loopRate = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "loopfx_rate" ][ idx ]; self thread loopfx_onTag( loopfx, loopfx_tag, loopRate, partIndex ); } } // if the part has a looping soundalias then start looping it now if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "loopsound" ] ) ) { for ( i = 0; i < level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "loopsound" ].size; i++ ) { validSoundCause = self isValidSoundCause( "loopsoundCause", action_v, i, damageType ); if ( validSoundCause ) { loopsoundAlias = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "loopsound" ][ i ]; loopsoundTagName = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "tagName" ]; self thread play_loop_sound_on_destructible( loopsoundAlias, loopsoundTagName ); if ( !isdefined( self.loopingSoundStopNotifies ) ) self.loopingSoundStopNotifies = []; if ( !isdefined( self.loopingSoundStopNotifies[ toString( partIndex ) ] ) ) self.loopingSoundStopNotifies[ toString( partIndex ) ] = []; size = self.loopingSoundStopNotifies[ toString( partIndex ) ].size; self.loopingSoundStopNotifies[ toString( partIndex ) ][ size ] = "stop sound" + loopsoundAlias; } } } // if the part is supposed to trigger a car alarm if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "triggerCarAlarm" ] ) ) { self thread do_car_alarm(); } // if the part is supposed to trigger a car alarm if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "break_nearby_lights" ] ) ) { self thread break_nearest_light(); } // if the part should drain health then start the drain if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "healthdrain_amount" ] ) ) { self notify( "Health_Drain_State_Change" + partIndex ); healthdrain_amount = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "healthdrain_amount" ]; healthdrain_interval = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "healthdrain_interval" ]; healthdrain_modelName = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "modelName" ]; healthdrain_tagName = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "tagName" ]; badplaceRadius = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "badplace_radius" ]; badplaceTeam = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "badplace_team" ]; if ( healthdrain_amount > 0 ) { Assert( ( IsDefined( healthdrain_interval ) ) && ( healthdrain_interval > 0 ) ); self thread health_drain( healthdrain_amount, healthdrain_interval, partIndex, healthdrain_modelName, healthdrain_tagName, badplaceRadius, badplaceTeam ); } } // if the part is meant to explode on this state then do it now. Causes all attached models to become physics with the specified force if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "explode" ] ) ) { delayModelSwap = true; force_min = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "explode_force_min" ]; force_max = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "explode_force_max" ]; range = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "explode_range" ]; mindamage = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "explode_mindamage" ]; maxdamage = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "explode_maxdamage" ]; continueDamage = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "continueDamage" ]; originOffset = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "originOffset" ]; earthQuakeScale = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "earthQuakeScale" ]; earthQuakeRadius = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "earthQuakeRadius" ]; if ( IsDefined( attacker ) && attacker != self ) { // Achievement Hook self.attacker = attacker; // Only add .damage_type to script_vehicles that happen to be destructibles (ie UAZ) // This hook provides info so the vehicle can do _player_stat::register_kill() if ( self.code_classname == "script_vehicle" ) { self.damage_type = damageType; } } self thread explode( partIndex, force_min, force_max, range, mindamage, maxdamage, continueDamage, originOffset, earthQuakeScale, earthQuakeRadius, attacker ); } // if the part should do physics here then initiate the physics and velocity physTagOrigin = undefined; if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "physics" ] ) ) { for ( i = 0; i < level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "physics" ].size; i++ ) { physTagOrigin = undefined; physTagName = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "physics_tagName" ][ i ]; physVelocity = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v[ "physics_velocity" ][ i ]; initial_velocity = undefined; if ( IsDefined( physVelocity ) ) { physTagAngles = undefined; if ( IsDefined( physTagName ) ) physTagAngles = self GetTagAngles( physTagName ); else if ( IsDefined( tagName ) ) physTagAngles = self GetTagAngles( tagName ); Assert( IsDefined( physTagAngles ) ); physTagOrigin = undefined; if ( IsDefined( physTagName ) ) physTagOrigin = self GetTagOrigin( physTagName ); else if ( IsDefined( tagName ) ) physTagOrigin = self GetTagOrigin( tagName ); Assert( IsDefined( physTagOrigin ) ); phys_x = physVelocity[ 0 ] - 5 + RandomFloat( 10 ); phys_y = physVelocity[ 1 ] - 5 + RandomFloat( 10 ); phys_z = physVelocity[ 2 ] - 5 + RandomFloat( 10 ); forward = AnglesToForward( physTagAngles ) * phys_x * RandomFloatRange( 80, 110 ); right = AnglesToRight( physTagAngles ) * phys_y * RandomFloatRange( 80, 110 ); up = AnglesToUp( physTagAngles ) * phys_z * RandomFloatRange( 80, 110 ); initial_velocity = forward + right + up; /# if ( GetDvarInt( "debug_destructibles" , 0 ) == 1 ) { thread draw_line_for_time( physTagOrigin, physTagOrigin + initial_velocity, 1, 1, 1, 5.0 ); } #/ } else { initial_velocity = point; impactDir = ( 0, 0, 0 ); if ( IsDefined( attacker ) ) { impactDir = attacker.origin; initial_velocity = VectorNormalize( point - impactDir ); initial_velocity = vector_multiply( initial_velocity, 200 ); } } Assert( IsDefined( initial_velocity ) ); if ( IsDefined( physTagName ) ) { // Do physics on another part, and continue this thread since the current part is still unaffected by the physics // get the partIndex that cooresponds to what the tagname is physPartIndex = undefined; for ( j = 0; j < level.destructible_type[ self.destuctableInfo ].parts.size; j++ ) { if ( !isdefined( level.destructible_type[ self.destuctableInfo ].parts[ j ][ 0 ].v[ "tagName" ] ) ) continue; if ( level.destructible_type[ self.destuctableInfo ].parts[ j ][ 0 ].v[ "tagName" ] != physTagName ) continue; physPartIndex = j; break; } if ( IsDefined( physTagOrigin ) ) self thread physics_launch( physPartIndex, 0, physTagOrigin, initial_velocity ); else self thread physics_launch( physPartIndex, 0, point, initial_velocity ); } else { // Do physics on this part, therefore ending this thread if ( IsDefined( physTagOrigin ) ) self thread physics_launch( partIndex, actionStateIndex, physTagOrigin, initial_velocity ); else self thread physics_launch( partIndex, actionStateIndex, point, initial_velocity ); prof_end( "_destructible" ); return; } } } updateHealthValue = true; } prof_end( "_destructible" ); } destructible_splash_rotatation( v ) { // rotate model due to splash damage direction, optional model_rotation = v[ "splashRotation" ]; model_rotate_to = v[ "rotateTo" ]; if ( !isdefined( model_rotate_to ) ) return; if ( !isdefined( model_rotation ) ) return; if ( !model_rotation ) return; self.angles = ( self.angles[ 0 ], model_rotate_to[ 1 ], self.angles[ 2 ] ); } // parameter damageType can be single damage or multiple damages separated by spaces damage_not( damageType ) { toks = StrTok( damageType, " " ); damages_tok = StrTok( "splash melee bullet splash impact unknown", " " ); new_string = ""; foreach ( idx, tok in toks ) damages_tok = array_remove( damages_tok, tok ); foreach ( damages in damages_tok ) new_string += damages + " "; return new_string; } destructible_splash_damage( damage, point, direction_vec, attacker, damageType ) { if ( damage <= 0 ) return; if ( IsDefined( self.exploded ) ) return; //------------------------------------------------------------------------ // Fill an array of all possible parts that might have been splash damaged //------------------------------------------------------------------------ if ( !isdefined( level.destructible_type[ self.destuctableInfo ].parts ) ) return; damagedParts = self getAllActiveParts( direction_vec ); if ( damagedParts.size <= 0 ) return; damagedParts = self setDistanceOnParts( damagedParts, point ); closestPartDist = getLowestPartDistance( damagedParts ); Assert( IsDefined( closestPartDist ) ); //-------------------------------------------------------------------------- // Damage each part depending on how close it was to the splash damage point //-------------------------------------------------------------------------- prof_begin( "_destructible" ); foreach ( part in damagedParts ) { distanceMod = ( part.v[ "distance" ] * 1.4 ); damageAmount = ( damage - ( distanceMod - closestPartDist ) ); if ( damageAmount <= 0 ) continue; if ( IsDefined( self.exploded ) ) continue; /# if ( GetDvarInt( "debug_destructibles" , 0 ) == 1 ) { if ( IsDefined( part.v[ "tagName" ] ) ) Print3d( self GetTagOrigin( part.v[ "tagName" ] ), damageAmount, ( 1, 1, 1 ), 1.0, 0.5, 200 ); } #/ self thread destructible_update_part( damageAmount, part.v[ "modelName" ], part.v[ "tagName" ], point, direction_vec, attacker, damageType, part ); } prof_end( "_destructible" ); } getAllActiveParts( direction_vec ) { activeParts = []; Assert( IsDefined( self.destuctableInfo ) ); if ( !isdefined( level.destructible_type[ self.destuctableInfo ].parts ) ) return activeParts; prof_begin( "_destructible" ); for ( i = 0; i < level.destructible_type[ self.destuctableInfo ].parts.size; i++ ) { partIndex = i; currentState = self.destructible_parts[ partIndex ].v[ "currentState" ]; // Splash damage rotation, rotation angle only calculated for state that has this option enabled for ( j = 0; j < level.destructible_type[ self.destuctableInfo ].parts[ partIndex ].size; j++ ) { splash_rotation = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ j ].v[ "splashRotation" ]; if ( IsDefined( splash_rotation ) && splash_rotation ) { rotate_to_angle = VectorToAngles( direction_vec ); rotate_to_angle_y = rotate_to_angle[ 1 ] - 90; level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ j ].v[ "rotateTo" ] = ( 0, rotate_to_angle_y, 0 ); } } if ( !isdefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ currentState ] ) ) continue; tagName = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ currentState ].v[ "tagName" ]; if ( !isdefined( tagName ) ) tagName = ""; if ( tagName == "" ) continue; modelName = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ currentState ].v[ "modelName" ]; if ( !isdefined( modelName ) ) modelName = ""; activePartIndex = activeParts.size; activeParts[ activePartIndex ] = SpawnStruct(); activeParts[ activePartIndex ].v[ "modelName" ] = modelName; activeParts[ activePartIndex ].v[ "tagName" ] = tagName; } prof_end( "_destructible" ); return activeParts; } setDistanceOnParts( partList, point ) { prof_begin( "_destructible" ); for ( i = 0; i < partList.size; i++ ) { d = Distance( point, self GetTagOrigin( partList[ i ].v[ "tagName" ] ) ); partList[ i ].v[ "distance" ] = d; } prof_end( "_destructible" ); return partList; } getLowestPartDistance( partList ) { closestDist = undefined; prof_begin( "_destructible" ); foreach ( part in partList ) { Assert( IsDefined( part.v[ "distance" ] ) ); d = part.v[ "distance" ]; if ( !isdefined( closestDist ) ) closestDist = d; if ( d < closestDist ) closestDist = d; } prof_end( "_destructible" ); return closestDist; } isValidSoundCause( soundCauseVar, action_v, soundIndex, damageType, groupNum ) { if ( isdefined( groupNum ) ) soundCause = action_v[ soundCauseVar ][ groupNum ][ soundIndex ]; else soundCause = action_v[ soundCauseVar ][ soundIndex ]; if ( !isdefined( soundCause ) ) return true; if ( soundCause == damageType ) return true; return false; } isAttackerValid( partIndex, stateIndex, attacker ) { // return true if the vehicle is being force exploded if ( IsDefined( self.forceExploding ) ) return true; // return false if the vehicle is trying to explode but it's not allowed to if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ].v[ "explode" ] ) ) { if ( IsDefined( self.dontAllowExplode ) ) return false; } if ( !isdefined( attacker ) ) return true; if ( attacker == self ) return true; sType = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ].v[ "validAttackers" ]; if ( !isdefined( sType ) ) return true; if ( sType == "no_player" ) { if ( !isplayer( attacker ) ) return true; if ( !isdefined( attacker.damageIsFromPlayer ) ) return true; if ( attacker.damageIsFromPlayer == false ) return true; } else if ( sType == "player_only" ) { if ( IsPlayer( attacker ) ) return true; if ( IsDefined( attacker.damageIsFromPlayer ) && attacker.damageIsFromPlayer ) return true; } else if ( sType == "no_ai" && IsDefined( level.isAIfunc ) ) { if ( ![[ level.isAIfunc ]]( attacker ) ) return true; } else if ( sType == "ai_only" && IsDefined( level.isAIfunc ) ) { if ( [[ level.isAIfunc ]]( attacker ) ) return true; } else { AssertMsg( "Invalid attacker rules on destructible vehicle. Valid types are: ai_only, no_ai, player_only, no_player" ); } return false; } isValidDamageCause( partIndex, stateIndex, damageType ) { if ( !isdefined( damageType ) ) return true; godModeAllowed = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ].v[ "godModeAllowed" ]; if ( godModeAllowed && ( ( IsDefined( self.godmode ) && self.godmode ) || ( IsDefined( self.script_bulletshield ) && self.script_bulletshield ) && damageType == "bullet" ) ) return false; validType = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ].v[ "validDamageCause" ]; if ( !isdefined( validType ) ) return true; if ( ( validType == "splash" ) && damageType != "splash" ) return false; if ( ( validType == "no_melee" ) && damageType == "melee" || damageType == "impact" ) return false; return true; } getDamageType( type ) { //returns a simple damage type: melee, bullet, splash, or unknown if ( !isdefined( type ) ) return "unknown"; type = ToLower( type ); switch( type ) { case "mod_melee": case "mod_crush": case "melee": return "melee"; case "mod_pistol_bullet": case "mod_rifle_bullet": case "bullet": return "bullet"; case "mod_grenade": case "mod_grenade_splash": case "mod_projectile": case "mod_projectile_splash": case "mod_explosive": case "splash": return "splash"; case "mod_impact": return "impact"; case "unknown": return "unknown"; default: return "unknown"; } } damage_mirror( parent, modelName, tagName ) { self notify( "stop_damage_mirror" ); self endon( "stop_damage_mirror" ); parent endon( "stop_taking_damage" ); self SetCanDamage( true ); for ( ;; ) { self waittill( "damage", damage, attacker, direction_vec, point, type ); parent notify( "damage", damage, attacker, direction_vec, point, type, modelName, tagName ); damage = undefined; attacker = undefined; direction_vec = undefined; point = undefined; type = undefined; } } add_damage_owner_recorder() { // Mackey added to track who is damaging the car self.player_damage = 0; self.non_player_damage = 0; self.car_damage_owner_recorder = true; } loopfx_onTag( loopfx, loopfx_tag, loopRate, partIndex ) { self endon( "FX_State_Change" + partIndex ); self endon( "delete_destructible" ); level endon( "putout_fires" ); while( isdefined( self ) ) { eModel = get_dummy(); PlayFXOnTag( loopfx, eModel, loopfx_tag ); wait loopRate; } } health_drain( amount, interval, partIndex, modelName, tagName, badplaceRadius, badplaceTeam ) { self endon( "Health_Drain_State_Change" + partIndex ); level endon( "putout_fires" ); self endon( "destroyed" ); if( IsDefined( badplaceRadius ) && IsDefined( level.destructible_badplace_radius_multiplier ) ) { badplaceRadius *= level.destructible_badplace_radius_multiplier; } if( IsDefined( amount ) &&IsDefined( level.destructible_health_drain_amount_multiplier ) ) { amount *= level.destructible_health_drain_amount_multiplier; } wait interval; self.healthDrain = true; uniqueName = undefined; // disable the badplace radius call if level.disable_destructible_bad_places is true if ( IsDefined( level.disable_destructible_bad_places ) && level.disable_destructible_bad_places ) badplaceRadius = undefined; if ( IsDefined( badplaceRadius ) && IsDefined( badplaceTeam ) && isSP() ) { uniqueName = "" + GetTime(); if ( !isdefined( self.disableBadPlace ) ) { if ( IsDefined( self.script_radius ) ) { // overwrite the badplace radius from the map badplaceRadius = self.script_radius; } Assert( IsDefined( level.badplace_cylinder_func ) ); if ( badplaceTeam == "both" ) call [[ level.badplace_cylinder_func ]]( uniqueName, 0, self.origin, badplaceRadius, 128, "allies", "bad_guys" ); else call [[ level.badplace_cylinder_func ]]( uniqueName, 0, self.origin, badplaceRadius, 128, badplaceTeam ); self thread badplace_remove( uniqueName ); } } while ( isdefined( self ) && self.destructible_parts[ partIndex ].v[ "health" ] > 0 ) { /# if ( GetDvarInt( "debug_destructibles" , 0 ) == 1 ) { IPrintLn( "health before damage: " + self.destructible_parts[ partIndex ].v[ "health" ] ); IPrintLn( "doing " + amount + " damage" ); } #/ self notify( "damage", amount, self, ( 0, 0, 0 ), ( 0, 0, 0 ), "MOD_UNKNOWN", modelName, tagName ); wait interval; } self notify( "remove_badplace" ); } badplace_remove( uniqueName ) { self waittill_any( "destroyed", "remove_badplace" ); Assert( IsDefined( uniqueName ) ); Assert( IsDefined( level.badplace_delete_func ) ); call [[ level.badplace_delete_func ]]( uniqueName ); } physics_launch( partIndex, stateIndex, point, initial_velocity ) { modelName = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ].v[ "modelName" ]; tagName = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ stateIndex ].v[ "tagName" ]; self hideapart( tagName ); /# if ( GetDvarInt( "destructibles_enable_physics", 1 ) == 0 ) return; #/ // If we've reached the max number of spawned physics models for destructible vehicles then delete one before creating another if ( level.destructibleSpawnedEnts.size >= level.destructibleSpawnedEntsLimit ) physics_object_remove( level.destructibleSpawnedEnts[ 0 ] ); // Spawn a model to use for physics using the modelname and position of the part physicsObject = Spawn( "script_model", self GetTagOrigin( tagName ) ); physicsObject.angles = self GetTagAngles( tagName ); physicsObject SetModel( modelName ); // Keep track of the new part so it can be removed later if we reach the max level.destructibleSpawnedEnts[ level.destructibleSpawnedEnts.size ] = physicsObject; // Do physics on the model physicsObject PhysicsLaunchClient( point, initial_velocity ); } physics_object_remove( ent ) { newArray = []; for ( i = 0; i < level.destructibleSpawnedEnts.size; i++ ) { if ( level.destructibleSpawnedEnts[ i ] == ent ) continue; newArray[ newArray.size ] = level.destructibleSpawnedEnts[ i ]; } level.destructibleSpawnedEnts = newArray; if ( isdefined( ent ) ) ent Delete(); } explode( partIndex, force_min, force_max, range, mindamage, maxdamage, continueDamage, originOffset, earthQuakeScale, earthQuakeRadius, attacker ) { Assert( IsDefined( force_min ) ); Assert( IsDefined( force_max ) ); if( IsDefined( range ) && IsDefined( level.destructible_explosion_radius_multiplier ) ) { range *= level.destructible_explosion_radius_multiplier; } if ( !isdefined( originOffset ) ) originOffset = 80; if ( !isdefined( continueDamage ) || ( IsDefined( continueDamage ) && !continueDamage ) ) { if ( IsDefined( self.exploded ) ) return; self.exploded = true; } self notify( "exploded", attacker ); level notify( "destructible_exploded" ); if ( self.code_classname == "script_vehicle" ) self notify( "death", attacker, self.damage_type ); // check if there is a disconnect paths brush to disconnect any traverses if ( isSP() ) self thread disconnectTraverses(); wait 0.05; currentState = self.destructible_parts[ partIndex ].v[ "currentState" ]; Assert( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ] ) ); tagName = undefined; if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ currentState ] ) ) tagName = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ currentState ].v[ "tagName" ]; if ( IsDefined( tagName ) ) explosionOrigin = self GetTagOrigin( tagName ); else explosionOrigin = self.origin; self notify( "damage", maxdamage, self, ( 0, 0, 0 ), explosionOrigin, "MOD_EXPLOSIVE", "", "" ); self notify( "stop_car_alarm" ); waittillframeend; prof_begin( "_destructible" ); if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts ) ) { for ( i = ( level.destructible_type[ self.destuctableInfo ].parts.size - 1 ); i >= 0; i-- ) { if ( i == partIndex ) continue; stateIndex = self.destructible_parts[ i ].v[ "currentState" ]; if ( stateIndex >= level.destructible_type[ self.destuctableInfo ].parts[ i ].size ) stateIndex = level.destructible_type[ self.destuctableInfo ].parts[ i ].size - 1; modelName = level.destructible_type[ self.destuctableInfo ].parts[ i ][ stateIndex ].v[ "modelName" ]; tagName = level.destructible_type[ self.destuctableInfo ].parts[ i ][ stateIndex ].v[ "tagName" ]; if ( !isdefined( modelName ) ) continue; if ( !isdefined( tagName ) ) continue; // dont do physics on parts that are supposed to be removed on explosion if ( IsDefined( level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "physicsOnExplosion" ] ) ) { if ( level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "physicsOnExplosion" ] > 0 ) { velocityScaler = level.destructible_type[ self.destuctableInfo ].parts[ i ][ 0 ].v[ "physicsOnExplosion" ]; point = self GetTagOrigin( tagName ); initial_velocity = VectorNormalize( point - explosionOrigin ); initial_velocity = vector_multiply( initial_velocity, RandomFloatRange( force_min, force_max ) * velocityScaler ); self thread physics_launch( i, stateIndex, point, initial_velocity ); continue; } } //self.destructible_parts[ i ] Hide(); } } prof_end( "_destructible" ); stopTakingDamage = ( !isdefined( continueDamage ) || ( IsDefined( continueDamage ) && !continueDamage ) ); if ( stopTakingDamage ) self notify( "stop_taking_damage" ); wait 0.05; damageLocation = explosionOrigin + ( 0, 0, originOffset ); isVehicle = ( GetSubStr( level.destructible_type[ self.destuctableInfo ].v[ "type" ], 0, 7 ) == "vehicle" ); if ( isVehicle ) { anim.lastCarExplosionTime = GetTime(); anim.lastCarExplosionDamageLocation = damageLocation; anim.lastCarExplosionLocation = explosionOrigin; anim.lastCarExplosionRange = range; } // turn off friendly fire when they blow up so the player doesn't get accidental friendly fire mission failure level thread set_disable_friendlyfire_value_delayed( 1 ); if ( isSP() ) { if ( level.gameskill == 0 && !self player_touching_post_clip() ) self RadiusDamage( damageLocation, range, maxdamage, mindamage, self, "MOD_RIFLE_BULLET" ); else self RadiusDamage( damageLocation, range, maxdamage, mindamage, self ); if ( IsDefined( self.damageOwner ) && isVehicle ) { self.damageOwner notify( "destroyed_car" ); level notify( "player_destroyed_car", self.damageOwner, damageLocation ); } } else { if ( !isdefined( self.damageOwner ) ) { self RadiusDamage( damageLocation, range, maxdamage, mindamage, self ); } else { self RadiusDamage( damageLocation, range, maxdamage, mindamage, self.damageOwner ); if ( isVehicle ) { self.damageOwner notify( "destroyed_car" ); level notify( "player_destroyed_car", self.damageOwner, damageLocation ); } } } if ( IsDefined( earthQuakeScale ) && IsDefined( earthQuakeRadius ) ) Earthquake( earthQuakeScale, 2.0, damageLocation, earthQuakeRadius ); /# if ( GetDvarInt( "destructibles_show_radiusdamage" ) == 1 ) thread debug_radiusdamage_circle( damageLocation, range, maxdamage, mindamage ); #/ // explosion damage done, resume friendly fire if it was enabled level thread set_disable_friendlyfire_value_delayed( 0, 0.05 ); magnitudeScaler = 0.01; magnitude = range * magnitudeScaler; Assert( magnitude > 0 ); range *= .99; PhysicsExplosionSphere( damageLocation, range, 0, magnitude ); if ( stopTakingDamage ) { self SetCanDamage( false ); self thread cleanupVars(); } self notify( "destroyed" ); } cleanupVars() { // wait so we can make sure they are no longer needed wait 0.05; if ( !isdefined( self ) ) return; if ( isdefined( self.waiting_for_queue ) ) self waittill( "queue_processed" ); if ( !isdefined( self ) ) return; self.animsapplied = undefined; self.attacker = undefined; self.car_damage_owner_recorder = undefined; self.caralarm = undefined; self.damageowner = undefined; self.destructible_parts = undefined; self.destructible_type = undefined; self.destuctableinfo = undefined; self.healthdrain = undefined; self.non_player_damage = undefined; self.player_damage = undefined; } set_disable_friendlyfire_value_delayed( value, delay ) { level notify( "set_disable_friendlyfire_value_delayed" ); level endon( "set_disable_friendlyfire_value_delayed" ); Assert( IsDefined( value ) ); if ( IsDefined( delay ) ) wait delay; level.friendlyFireDisabledForDestructible = value; } /* arcadeMode_car_kill() { if ( !isSP() ) return false; if ( !arcadeMode() ) return false; if ( level.script == "ac130" ) return false; if ( IsDefined( level.allCarsDamagedByPlayer ) ) return true; return self maps\_gameskill::player_did_most_damage(); } */ connectTraverses() { clip = get_traverse_disconnect_brush(); if ( !isdefined( clip ) ) return; Assert( IsDefined( level.connectPathsFunction ) ); clip call [[ level.connectPathsFunction ]](); clip.origin -= ( 0, 0, 10000 ); } disconnectTraverses() { clip = get_traverse_disconnect_brush(); if ( !isdefined( clip ) ) return; clip.origin += ( 0, 0, 10000 ); Assert( IsDefined( level.disconnectPathsFunction ) ); clip call [[ level.disconnectPathsFunction ]](); clip.origin -= ( 0, 0, 10000 ); } get_traverse_disconnect_brush() { if ( !isdefined( self.target ) ) return undefined; targets = GetEntArray( self.target, "targetname" ); foreach ( target in targets ) { if ( IsSpawner( target ) ) continue; if ( IsDefined( target.script_destruct_collision ) ) continue; if ( target.code_classname == "light" ) continue; if ( !target.spawnflags & 1 ) continue; return target; } } hideapart( tagName ) { self HidePart( tagName ); } showapart( tagName ) { self ShowPart( tagName ); } disable_explosion() { self.dontAllowExplode = true; } force_explosion() { self.dontAllowExplode = undefined; self.forceExploding = true; self notify( "damage", 100000, self, self.origin, self.origin, "MOD_EXPLOSIVE", "", "" ); } get_dummy() { if ( !isSP() ) return self; if ( self.modeldummyon ) eModel = self.modeldummy; else eModel = self; return eModel; } play_loop_sound_on_destructible( alias, tag ) { eModel = get_dummy(); org = Spawn( "script_origin", ( 0, 0, 0 ) ); if ( IsDefined( tag ) ) org.origin = eModel GetTagOrigin( tag ); else org.origin = eModel.origin; org PlayLoopSound( alias ); eModel thread force_stop_sound( alias ); eModel waittill( "stop sound" + alias ); if ( !isdefined( org ) ) return; org StopLoopSound( alias ); org Delete(); } force_stop_sound( alias ) { self endon( "stop sound" + alias ); level waittill( "putout_fires" ); self notify( "stop sound" + alias ); } notifyDamageAfterFrame( damage, attacker, direction_vec, point, damageType, modelName, tagName ) { if ( IsDefined( level.notifyDamageAfterFrame ) ) return; level.notifyDamageAfterFrame = true; waittillframeend; if ( IsDefined( self.exploded ) ) { level.notifyDamageAfterFrame = undefined; return; } if ( isSP() ) damage /= SP_DAMAGE_BIAS; else damage /= MP_DAMAGE_BIAS; self notify( "damage", damage, attacker, direction_vec, point, damageType, modelName, tagName ); level.notifyDamageAfterFrame = undefined; } play_sound( alias, tag ) { if ( IsDefined( tag ) ) { org = Spawn( "script_origin", self GetTagOrigin( tag ) ); org Hide(); org LinkTo( self, tag, ( 0, 0, 0 ), ( 0, 0, 0 ) ); } else { org = Spawn( "script_origin", ( 0, 0, 0 ) ); org Hide(); org.origin = self.origin; org.angles = self.angles; org LinkTo( self ); } org PlaySound( alias ); wait( 5.0 ); if ( IsDefined( org ) ) org Delete(); } toString( num ) { return( "" + num ); } do_car_alarm() { if ( IsDefined( self.carAlarm ) ) return; self.carAlarm = true; if ( !should_do_car_alarm() ) return; self.car_alarm_org = Spawn( "script_model", self.origin ); self.car_alarm_org Hide(); self.car_alarm_org PlayLoopSound( CAR_ALARM_ALIAS ); level.currentCarAlarms++; Assert( level.currentCarAlarms <= MAX_SIMULTANEOUS_CAR_ALARMS ); self thread car_alarm_timeout(); self waittill( "stop_car_alarm" ); level.lastCarAlarmTime = GetTime(); level.currentCarAlarms--; self.car_alarm_org StopLoopSound( CAR_ALARM_ALIAS ); self.car_alarm_org Delete(); } car_alarm_timeout() { self endon( "stop_car_alarm" ); // Car alarm only lasts this long until it automatically shuts up wait CAR_ALARM_TIMEOUT; if ( !isdefined( self ) ) return; self thread play_sound( CAR_ALARM_OFF_ALIAS ); self notify( "stop_car_alarm" ); } should_do_car_alarm() { // If there is already car alarms going off then don't trigger another one if ( level.currentCarAlarms >= MAX_SIMULTANEOUS_CAR_ALARMS ) return false; // If the player hasn't heard a car alarm yet during this level timeElapsed = undefined; if ( !isdefined( level.lastCarAlarmTime ) ) { if ( cointoss() ) return true; timeElapsed = GetTime() - level.commonStartTime; } else { timeElapsed = GetTime() - level.lastCarAlarmTime; } Assert( IsDefined( timeElapsed ) ); // If the player hasn't heard a car alarm in a while then do one if ( level.currentCarAlarms == 0 && timeElapsed >= NO_CAR_ALARM_MAX_ELAPSED_TIME ) return true; if ( RandomInt( 100 ) <= 33 ) return true; return false; } do_random_dynamic_attachment( tagName, attach_model_1, attach_model_2, clipToRemove ) { Assert( IsDefined( tagName ) ); Assert( IsDefined( attach_model_1 ) ); spawnedModels = []; if ( isSP() ) { self Attach( attach_model_1, tagName, false ); if ( IsDefined( attach_model_2 ) && attach_model_2 != "" ) self Attach( attach_model_2, tagName, false ); } else { //attach doesn't work in MP so fake it spawnedModels[ 0 ] = Spawn( "script_model", self GetTagOrigin( tagName ) ); spawnedModels[ 0 ].angles = self GetTagAngles( tagName ); spawnedModels[ 0 ] SetModel( attach_model_1 ); spawnedModels[ 0 ] LinkTo( self, tagName ); if ( IsDefined( attach_model_2 ) && attach_model_2 != "" ) { spawnedModels[ 1 ] = Spawn( "script_model", self GetTagOrigin( tagName ) ); spawnedModels[ 1 ].angles = self GetTagAngles( tagName ); spawnedModels[ 1 ] SetModel( attach_model_2 ); spawnedModels[ 1 ] LinkTo( self, tagName ); } } // remove collision that might not be used for this attachment if ( isdefined( clipToRemove ) ) { tagOrg = self getTagOrigin( tagName ); clip = get_closest_with_targetname( tagOrg, clipToRemove ); if ( isdefined( clip ) ) clip delete(); } self waittill( "exploded" ); if ( isSP() ) { self Detach( attach_model_1, tagName ); self Attach( attach_model_1 + DESTROYED_ATTACHMENT_SUFFIX, tagName, false ); if ( IsDefined( attach_model_2 ) && attach_model_2 != "" ) { self Detach( attach_model_2, tagName ); self Attach( attach_model_2 + DESTROYED_ATTACHMENT_SUFFIX, tagName, false ); } } else { spawnedModels[ 0 ] SetModel( attach_model_1 + DESTROYED_ATTACHMENT_SUFFIX ); if ( IsDefined( attach_model_2 ) && attach_model_2 != "" ) spawnedModels[ 1 ] SetModel( attach_model_2 + DESTROYED_ATTACHMENT_SUFFIX ); } } get_closest_with_targetname( origin, targetname ) { closestDist = undefined; closestEnt = undefined; ents = getentarray( targetname, "targetname" ); foreach ( ent in ents ) { d = distanceSquared( origin, ent.origin ); if ( !isdefined( closestDist ) || ( d < closestDist ) ) { closestDist = d; closestEnt = ent; } } return closestEnt; } player_touching_post_clip() { post_clip = undefined; if ( !IsDefined( self.target ) ) { return false; } targets = GetEntArray( self.target, "targetname" ); foreach ( target in targets ) { if ( IsDefined( target.script_destruct_collision ) && target.script_destruct_collision == "post" ) { post_clip = target; break; } } if ( !IsDefined( post_clip ) ) { return false; } player = get_player_touching( post_clip ); if ( IsDefined( player ) ) { return true; } return false; } get_player_touching( ent ) { foreach ( player in level.players ) { if ( !IsAlive( player ) ) { continue; } if ( ent IsTouching( player ) ) { return player; } } return undefined; } is_so() { return GetDvar( "specialops" ) == "1"; } destructible_handles_collision_brushes() { targets = GetEntArray( self.target, "targetname" ); collision_funcs = []; collision_funcs[ "pre" ] = ::collision_brush_pre_explosion; collision_funcs[ "post" ] = ::collision_brush_post_explosion; foreach ( target in targets ) { if ( !isdefined( target.script_destruct_collision ) ) continue; self thread [[ collision_funcs[ target.script_destruct_collision ] ]]( target ); } } collision_brush_pre_explosion( clip ) { waittillframeend;// wait for same area post brushes to connect before we disconnect if ( isSP() ) clip call [[ level.disconnectPathsFunction ]](); self waittill( "exploded" ); if ( isSP() ) clip call [[ level.connectPathsFunction ]](); clip Delete(); } collision_brush_post_explosion( clip ) { clip NotSolid(); if ( isSP() ) clip call [[ level.connectPathsFunction ]](); self waittill( "exploded" ); waittillframeend;// wait for same area pre brushes to connect before we disconnect if ( isSP() ) { clip call [[ level.disconnectPathsFunction ]](); if ( is_so() ) { player = get_player_touching( clip ); if ( isdefined( player ) ) { assertex( isdefined( level.func_destructible_crush_player ), "Special Ops requires level.func_destructible_crush_player to be defined." ); self thread [[ level.func_destructible_crush_player ]]( player ); } } else { /# thread debug_player_in_post_clip( clip ); #/ } } clip Solid(); } debug_player_in_post_clip( clip ) { /# wait( 0.1 ); player = get_player_touching( clip ); if ( IsDefined( player ) ) { AssertEx( !IsAlive( player ), "Player is in a clip of a destructible, but is still alive. He's either in godmode or we're doing something wrong. Player will be stuck now." ); } #/ } destructible_get_my_breakable_light( range ) { AssertEx( !isdefined( self.breakable_light ), "Tried to define my breakable light twice" ); // light = getClosest( self.origin, GetEntArray("light_destructible","targetname") ); // beh getClosest is SP only.. to lazy to port right now. // find the nearest light with targetname light_destructible within range and turn it out. scripting stuff in prefabs is still hard. // lights = GetEntArray( "light_destructible", "targetname" ); if ( isSP() )// mp lacks noteworthy powers { lights2 = GetEntArray( "light_destructible", "script_noteworthy" ); lights = array_combine( lights, lights2 ); } if ( !lights.size ) return; shortest_distance = range * range; the_light = undefined; foreach ( light in lights ) { dist = DistanceSquared( self.origin, light.origin ); if ( dist < shortest_distance ) { the_light = light; shortest_distance = dist; } } if ( !isdefined( the_light ) ) return; self.breakable_light = the_light; } break_nearest_light( range ) { if ( !isdefined( self.breakable_light ) ) return; self.breakable_light SetLightIntensity( 0 ); } debug_radiusdamage_circle( center, radius, maxdamage, mindamage ) { circle_sides = 16; angleFrac = 360 / circle_sides; // Z circle circlepoints = []; for ( i = 0; i < circle_sides; i++ ) { angle = ( angleFrac * i ); xAdd = Cos( angle ) * radius; yAdd = Sin( angle ) * radius; x = center[ 0 ] + xAdd; y = center[ 1 ] + yAdd; z = center[ 2 ]; circlepoints[ circlepoints.size ] = ( x, y, z ); } thread debug_circle_drawlines( circlepoints, 5.0, ( 1, 0, 0 ), center ); // X circle circlepoints = []; for ( i = 0; i < circle_sides; i++ ) { angle = ( angleFrac * i ); xAdd = Cos( angle ) * radius; yAdd = Sin( angle ) * radius; x = center[ 0 ]; y = center[ 1 ] + xAdd; z = center[ 2 ] + yAdd; circlepoints[ circlepoints.size ] = ( x, y, z ); } thread debug_circle_drawlines( circlepoints, 5.0, ( 1, 0, 0 ), center ); // Y circle circlepoints = []; for ( i = 0; i < circle_sides; i++ ) { angle = ( angleFrac * i ); xAdd = Cos( angle ) * radius; yAdd = Sin( angle ) * radius; x = center[ 0 ] + yAdd; y = center[ 1 ]; z = center[ 2 ] + xAdd; circlepoints[ circlepoints.size ] = ( x, y, z ); } thread debug_circle_drawlines( circlepoints, 5.0, ( 1, 0, 0 ), center ); // draw center and range with values Print3d( center, maxdamage, ( 1, 1, 1 ), 1, 1, 100 ); Print3d( center + ( radius, 0, 0 ), mindamage, ( 1, 1, 1 ), 1, 1, 100 ); } debug_circle_drawlines( circlepoints, duration, color, center ) { Assert( IsDefined( center ) ); for ( i = 0; i < circlepoints.size; i++ ) { start = circlepoints[ i ]; if ( i + 1 >= circlepoints.size ) end = circlepoints[ 0 ]; else end = circlepoints[ i + 1 ]; thread debug_line( start, end, duration, color ); thread debug_line( center, start, duration, color ); } } debug_line( start, end, duration, color ) { if ( !isdefined( color ) ) color = ( 1, 1, 1 ); for ( i = 0; i < ( duration * 20 ); i++ ) { Line( start, end, color ); wait 0.05; } } spotlight_tag_origin_cleanup( tag_origin ) { tag_origin endon( "death" ); level waittill( "new_destructible_spotlight" ); tag_origin Delete(); } spotlight_fizzles_out( action_v, eModel, damageType, partIndex, tag_origin ) { level endon( "new_destructible_spotlight" ); thread spotlight_tag_origin_cleanup( tag_origin ); maxVal = action_v[ "spotlight_brightness" ]; noself_func( "setsaveddvar", "r_spotlightbrightness", maxVal ); wait( RandomFloatRange( 2, 5 ) ); steps = RandomIntRange( 5, 11 ); for ( i = 0; i < steps; i++ ) { noself_func( "setsaveddvar", "r_spotlightbrightness", maxVal * 0.65 ); wait( 0.05 ); noself_func( "setsaveddvar", "r_spotlightbrightness", maxVal ); wait( 0.05 ); } destructible_fx_think( action_v, eModel, damageType, partIndex ); level.destructible_spotlight Delete(); tag_origin Delete(); } destructible_spotlight_think( action_v, eModel, damageType, partIndex ) { // spotlights are MP only if ( !isSP() ) return; if ( !isdefined( self.breakable_light ) ) return; emodel self_func( "startignoringspotLight" ); // set all the dvars for this light type foreach ( dvar, val in action_v[ "dvars" ] ) { noself_func( "setsaveddvar", dvar, val ); } if ( !isdefined( level.destructible_spotlight ) ) { level.destructible_spotlight = spawn_tag_origin(); fx = getfx( action_v[ "spotlight_fx" ] ); PlayFXOnTag( fx, level.destructible_spotlight, "tag_origin" ); } //self.breakable_light thread maps\_debug::drawForwardForever( 200, (1,0,0) ); //level.destructible_spotlight thread maps\_debug::drawForwardForever( 200, (1,1,0) ); level notify( "new_destructible_spotlight" ); level.destructible_spotlight Unlink(); tag_origin = spawn_tag_origin(); tag_origin LinkTo( self, action_v[ "spotlight_tag" ], ( 0, 0, 0 ), ( 0, 0, 0 ) ); //eModel thread maps\_debug::drawTagForever( action_v[ "spotlight_tag" ] ); level.destructible_spotlight.origin = self.breakable_light.origin; level.destructible_spotlight.angles = self.breakable_light.angles; level.destructible_spotlight thread spotlight_fizzles_out( action_v, eModel, damageType, partIndex, tag_origin ); wait( 0.05 );// Wait for the spawned tag_origin to get to the right place before linking if ( IsDefined( tag_origin ) ) { // can be deleted during wait level.destructible_spotlight LinkTo( tag_origin ); } } is_valid_damagetype( damageType, v, idx, groupNum ) { valid_damagetype = undefined; if ( IsDefined( v[ "fx_valid_damagetype" ] ) ) valid_damagetype = v[ "fx_valid_damagetype" ][ groupNum ][ idx ]; if ( !isdefined( valid_damagetype ) ) return true; return IsSubStr( valid_damagetype, damageType ); } destructible_sound_think( action_v, eModel, damageType, groupNum ) { if ( isdefined( self.exploded ) ) return undefined; if ( !isDefined( action_v[ "sound" ] ) ) return undefined; if ( !isdefined( groupNum ) ) groupNum = 0; assert( isDefined( action_v[ "sound" ][ groupNum ] ) ); for ( i = 0; i < action_v[ "sound" ][ groupNum ].size; i++ ) { validSoundCause = self isValidSoundCause( "soundCause", action_v, i, damageType, groupNum ); if ( !validSoundCause ) continue; soundAlias = action_v[ "sound" ][ groupNum ][ i ]; soundTagName = action_v[ "tagName" ]; //chad - dont think I need a groupnum index here, but now we probably can't support playing sounds on multiple tags within one group eModel thread play_sound( soundAlias, soundTagName ); } return groupNum; } destructible_fx_think( action_v, eModel, damageType, partIndex, groupNum ) { if ( !isdefined( action_v[ "fx" ] ) ) return undefined; if ( !isdefined( groupNum ) ) groupNum = randomInt( action_v[ "fx_filename" ].size ); if ( !isDefined( action_v[ "fx" ][ groupNum ] ) ) { println( "^1destructible tried to use custom groupNum for FX but that group didn't exist" ); groupNum = randomInt( action_v[ "fx_filename" ].size ); } assert( isDefined( action_v[ "fx" ][ groupNum ] ) ); fx_size = action_v[ "fx_filename" ][ groupNum ].size; for ( idx = 0; idx < fx_size; idx++ ) { if ( !is_valid_damagetype( damageType, action_v, idx, groupNum ) ) continue; fx = action_v[ "fx" ][ groupNum ][ idx ]; if ( IsDefined( action_v[ "fx_tag" ][ groupNum ][ idx ] ) ) { fx_tag = action_v[ "fx_tag" ][ groupNum ][ idx ]; self notify( "FX_State_Change" + partIndex ); if ( action_v[ "fx_useTagAngles" ][ groupNum ][ idx ] ) { PlayFXOnTag( fx, eModel, fx_tag ); } else { fxOrigin = eModel GetTagOrigin( fx_tag ); forward = ( fxOrigin + ( 0, 0, 100 ) ) - fxOrigin; PlayFX( fx, fxOrigin, forward ); } } else { fxOrigin = eModel.origin; forward = ( fxOrigin + ( 0, 0, 100 ) ) - fxOrigin; PlayFX( fx, fxOrigin, forward ); } } return groupNum; } destructible_animation_think( action_v, eModel, damageType, partIndex ) { if ( IsDefined( self.exploded ) ) return undefined; if ( !isdefined( action_v[ "animation" ] ) ) return undefined; if ( IsDefined( action_v[ "randomly_flip" ] ) && !isdefined( self.script_noflip ) ) { if ( cointoss() ) { // flip it around for randomness self.angles += ( 0, 180, 0 ); } } // this stuff is SP only if ( IsDefined( action_v[ "spotlight_tag" ] ) ) { thread destructible_spotlight_think( action_v, eModel, damageType, partIndex ); wait( 0.05 ); } array = random( action_v[ "animation" ] ); animName = array[ "anim" ]; animTree = array[ "animTree" ]; groupNum = array[ "groupNum" ]; mpAnim = array[ "mpAnim" ]; maxStartDelay = array[ "maxStartDelay" ]; animRateMin = array[ "animRateMin" ]; animRateMax = array[ "animRateMax" ]; if ( !isdefined( animRateMin ) ) animRateMin = 1.0; if ( !isdefined( animRateMax ) ) animRateMax = 1.0; if ( animRateMin == animRateMax ) animRate = animRateMin; else animRate = RandomFloatRange( animRateMin, animRateMax ); vehicle_dodge_part_animation = array[ "vehicle_exclude_anim" ]; if ( self.code_classname == "script_vehicle" && vehicle_dodge_part_animation ) return undefined; eModel self_func( "useanimtree", animTree ); animType = array[ "animType" ]; if ( !isdefined( self.animsApplied ) ) self.animsApplied = []; self.animsApplied[ self.animsApplied.size ] = animName; if ( IsDefined( self.exploding ) ) self clear_anims( eModel ); if ( IsDefined( maxStartDelay ) && maxStartDelay > 0 ) wait RandomFloat( maxStartDelay ); // Multiplayer animations work now if ( !isSP() ) { if ( IsDefined( mpAnim ) ) self self_func( "scriptModelPlayAnim", mpAnim ); return groupNum; } if ( animType == "setanim" ) { eModel self_func( "setanim", animName, 1.0, 1.0, animRate ); return groupNum; } if ( animType == "setanimknob" ) { eModel self_func( "setanimknob", animName, 1.0, 0, animRate ); return groupNum; } AssertMsg( "Tried to play an animation on a destructible with an invalid animType: " + animType ); return undefined; } clear_anims( eModel ) { //clear all previously blended anims if the vehicle is exploding so the explosion doesn't have to blend with anything if ( IsDefined( self.animsApplied ) ) { foreach ( animation in self.animsApplied ) { if ( isSP() ) eModel self_func( "clearanim", animation, 0 ); else eModel self_func( "scriptModelClearAnim" ); } } } init_destroyed_count() { level.destroyedCount = 0; level.destroyedCountTimeout = 0.5; if ( isSP() ) level.maxDestructions = 20; else level.maxDestructions = 2; } add_to_destroyed_count() { level.destroyedCount++; wait( level.destroyedCountTimeout ); level.destroyedCount--; Assert( level.destroyedCount >= 0 ); } get_destroyed_count() { return( level.destroyedCount ); } get_max_destroyed_count() { return( level.maxDestructions ); } init_destructible_frame_queue() { level.destructibleFrameQueue = []; } add_destructible_to_frame_queue( destructible, partInfo, damage ) { entNum = self GetEntityNumber(); if ( !isDefined( level.destructibleFrameQueue[ entNum ] ) ) { level.destructibleFrameQueue[ entNum ] = SpawnStruct(); level.destructibleFrameQueue[ entNum ].entNum = entNum; level.destructibleFrameQueue[ entNum ].destructible = destructible; level.destructibleFrameQueue[ entNum ].totalDamage = 0; level.destructibleFrameQueue[ entNum ].nearDistance = 9999999; level.destructibleFrameQueue[ entNum ].fxCost = 0; } level.destructibleFrameQueue[ entNum ].fxCost += partInfo.v[ "fxcost" ]; level.destructibleFrameQueue[ entNum ].totalDamage += damage; if ( partInfo.v[ "distance" ] < level.destructibleFrameQueue[ entNum ].nearDistance ) level.destructibleFrameQueue[ entNum ].nearDistance = partInfo.v[ "distance" ]; thread handle_destructible_frame_queue(); } handle_destructible_frame_queue() { level notify( "handle_destructible_frame_queue" ); level endon( "handle_destructible_frame_queue" ); wait( 0.05 ); currentQueue = level.destructibleFrameQueue; level.destructibleFrameQueue = []; sortedQueue = sort_destructible_frame_queue( currentQueue ); for ( i = 0; i < sortedQueue.size; i++ ) { if ( get_destroyed_count() < get_max_destroyed_count() ) { if ( sortedQueue[ i ].fxCost ) thread add_to_destroyed_count(); sortedQueue[ i ].destructible notify( "queue_processed", true ); } else { sortedQueue[ i ].destructible notify( "queue_processed", false ); } } } sort_destructible_frame_queue( unsortedQueue ) { sortedQueue = []; foreach ( destructibleInfo in unsortedQueue ) sortedQueue[ sortedQueue.size ] = destructibleInfo; // insertion sort for ( i = 1; i < sortedQueue.size; i++ ) { queueStruct = sortedQueue[ i ]; for ( j = i - 1; j >= 0 && get_better_destructible( queueStruct, sortedQueue[ j ] ) == queueStruct; j-- ) sortedQueue[ j + 1 ] = sortedQueue[ j ]; sortedQueue[ j + 1 ] = queueStruct; } return sortedQueue; } get_better_destructible( destructibleInfo1, destructibleInfo2 ) { // this is very basic; we can also account for distance, fxcost, etc... if we need to if ( destructibleInfo1.totalDamage > destructibleInfo2.totalDamage ) return destructibleInfo1; else return destructibleInfo2; } get_part_FX_cost_for_action_state( partIndex, actionStateIndex ) { fxCost = 0; if ( !isDefined( level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ] ) ) return fxCost; action_v = level.destructible_type[ self.destuctableInfo ].parts[ partIndex ][ actionStateIndex ].v; if ( IsDefined( action_v[ "fx" ] ) ) { foreach ( fxCostObj in action_v[ "fx_cost" ] ) { foreach ( fxCostVal in fxCostObj ) fxCost += fxCostVal; } } return fxCost; }