#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(); if ( !IsDefined( level.fast_destructible_explode ) ) level.fast_destructible_explode = false; // want to isolate resulting bugs to hamburg. - Nate /# 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(); init_destroyed_count(); init_destructible_frame_queue(); } debgugPrintDestructibleList() { /# 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 ); } } PrintLn( "" ); PrintLn( "##################" ); level.created_destructibles = undefined; } #/ } find_destructibles() { //--------------------------------------------------------------------- // Find all destructibles by their targetnames and run the setup //--------------------------------------------------------------------- if( !IsDefined( level.destructible_functions ) ) level.destructible_functions = []; // Temporary store any dot refs in prefabs dots = []; foreach ( struct in level.struct ) if ( IsDefined( struct.script_noteworthy ) && struct.script_noteworthy == "destructible_dot" ) dots[ dots.size ] = struct; //assuring orders -nate vehicles = GetEntArray( "destructible_vehicle", "targetname" ); foreach ( vehicle in vehicles ) { vehicle thread setup_destructibles_thread( dots ); } destructible_toy = GetEntArray( "destructible_toy", "targetname" ); foreach ( toy in destructible_toy ) { toy thread setup_destructibles_thread( dots ); } debgugPrintDestructibleList(); } setup_destructibles_thread( dots ) { self setup_destructibles(); self setup_destructible_dots( dots ); } setup_destructible_dots( dots ) { destructibleInfo = self.destructibleInfo; AssertEx( IsDefined( destructibleInfo ), "Setup destructibles did not execute properly for this destructible" ); foreach ( dot in dots ) { if ( IsDefined( level.destructible_type[ destructibleInfo ].destructible_dots ) ) return; if ( IsDefined( dot.script_parameters ) && IsSubStr( dot.script_parameters, "destructible_type" ) && IsSubStr( dot.script_parameters, self.destructible_type ) ) { // This isnt the best solution. With how are prefabs system works I basically look for the dot reference closest to // the destructible origin if ( DistanceSquared( self.origin, dot.origin ) < 1 ) { triggers = GetEntArray( dot.target, "targetname" ); level.destructible_type[ destructibleInfo ].destructible_dots = []; // Find out which triggers are associated with this destructible and "precache" the info foreach ( trigger in triggers ) { script_index = trigger.script_index; AssertEx( IsDefined( script_index ), "Must specify a script_index for trigger being used as DOT in destructible prefab" ); if ( !IsDefined( level.destructible_type[ destructibleInfo ].destructible_dots[ script_index ] ) ) level.destructible_type[ destructibleInfo ].destructible_dots[ script_index ] = []; triggerIndex = level.destructible_type[ destructibleInfo ].destructible_dots[ script_index ].size; level.destructible_type[ destructibleInfo ].destructible_dots[ script_index ][ triggerIndex ][ "classname" ] = trigger.classname; level.destructible_type[ destructibleInfo ].destructible_dots[ script_index ][ triggerIndex ][ "origin" ] = trigger.origin; spawnflags = ter_op( IsDefined( trigger.spawnflags ), trigger.spawnflags, 0 ); level.destructible_type[ destructibleInfo ].destructible_dots[ script_index ][ triggerIndex ][ "spawnflags" ] = spawnflags; switch( trigger.classname ) { case "trigger_radius": level.destructible_type[ destructibleInfo ].destructible_dots[ script_index ][ triggerIndex ][ "radius" ] = trigger.height; level.destructible_type[ destructibleInfo ].destructible_dots[ script_index ][ triggerIndex ][ "height" ] = trigger.height; break; default: AssertMsg( "This class is not supported for DOTs" ); } trigger Delete(); } break; } } } } destructible_getInfoIndex( destructibleType ) { if ( !isdefined( level.destructible_type ) ) return - 1; if ( level.destructible_type.size == 0 ) return - 1; for ( i = 0 ; i < level.destructible_type.size ; i++ ) { if ( destructibleType == level.destructible_type[ i ].v[ "type" ] ) return i; } // didn't find it in the array, must not exist return - 1; } destructible_getType( destructibleType ) { // if it's already been created dont create it again infoIndex = destructible_getInfoIndex( destructibleType ); if ( infoIndex >= 0 ) return infoIndex; // This is the new stuff, Each Script describes this function. if ( !IsDefined( level.destructible_functions[ destructibleType ] ) ) AssertMsg( "Destructible object 'destructible_type' " + destructibleType + "' is not valid. Have you Repackaged Zone/Script? Sometimes you need to rebuild BSP ents." ); [[ level.destructible_functions[ destructibleType ] ]](); infoIndex = destructible_getInfoIndex( destructibleType ); assert( infoIndex >= 0 ); return infoIndex; } setup_destructibles() { //--------------------------------------------------------------------- // Figure out what destructible information this entity should use //--------------------------------------------------------------------- destructibleInfo = 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.destructibleInfo = destructible_getType( self.destructible_type ); if ( self.destructibleInfo < 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) ) level.created_destructibles = []; 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; #/ precache_destructibles(); add_destructible_fx(); if ( IsDefined( level.destructible_transient ) && IsDefined( level.destructible_transient[ self.destructible_type ] ) ) { flag_wait( level.destructible_transient[ self.destructible_type ] + "_loaded" ); } // Boon Oct2012. Allow models to be attached, so we don't have to export models with complex intact and destroyed variations all in one. // This should save some memory in cases where we also want static intact and/or destroyed models, since they can now reuse geo. if ( IsDefined( level.destructible_type[ self.destructibleInfo].attachedModels ) ) { foreach ( attachedModel in level.destructible_type[ self.destructibleInfo].attachedModels ) { if ( IsDefined( attachedModel.tag ) ) self Attach( attachedModel.model, attachedModel.tag ); else self Attach( attachedModel.model ); if ( self.modeldummyon ) if ( IsDefined( attachedModel.tag ) ) self.modeldummy Attach( attachedModel.model, attachedModel.tag ); else self.modeldummy Attach( attachedModel.model ); } } //--------------------------------------------------------------------- // Set up all parts on the entity //--------------------------------------------------------------------- if ( IsDefined( level.destructible_type[ self.destructibleInfo].parts ) ) { self.destructible_parts = []; for ( i = 0; i < level.destructible_type[ self.destructibleInfo].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.destructibleInfo].parts[ i ][ 0 ].v[ "health" ] ) ) self.destructible_parts[ i ].v[ "health" ] = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].parts[ i ][ 0 ].v[ "random_dynamic_attachment_1" ] ) ) { randAttachmentIndex = RandomInt( level.destructible_type[ self.destructibleInfo].parts[ i ][ 0 ].v[ "random_dynamic_attachment_1" ].size ); attachTag = level.destructible_type[ self.destructibleInfo].parts[ i ][ 0 ].v[ "random_dynamic_attachment_tag" ][ randAttachmentIndex ]; attach_model_1 = level.destructible_type[ self.destructibleInfo].parts[ i ][ 0 ].v[ "random_dynamic_attachment_1" ][ randAttachmentIndex ]; attach_model_2 = level.destructible_type[ self.destructibleInfo].parts[ i ][ 0 ].v[ "random_dynamic_attachment_2" ][ randAttachmentIndex ]; clipToRemove = level.destructible_type[ self.destructibleInfo].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; // (Boon Aug2012: The modelName stuff here appears to be dead code, left over from when parts were actually attached.) // Show and hide parts as appropriate modelName = level.destructible_type[ self.destructibleInfo].parts[ i ][ 0 ].v[ "modelName" ]; tagName = level.destructible_type[ self.destructibleInfo].parts[ i ][ 0 ].v[ "tagName" ]; stateIndex = 1; while ( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ i ][ stateIndex ] ) ) { stateTagName = level.destructible_type[ self.destructibleInfo].parts[ i ][ stateIndex ].v[ "tagName" ]; stateModelName = level.destructible_type[ self.destructibleInfo].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 up 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(); } /* ============= ///ScriptDocBegin "Name: destructible_create( , , , , , )" "Summary: Define a new type of destructible. This automatically creates the first 'part' in the destructible and the first 'state' for that part." "Module: Destructible" "CallOn: nothing" "MandatoryArg: : Each type of destructible needs a unique name. This is generally the same as the name of the gsc file and must match the 'destructible_type' keypair in Radiant." "MandatoryArg: : It is essential that this tag exists but I haven't yet figured out if there's a good reason for it." "MandatoryArg: : When this much damage is done, the destructible will do everything defined by the destructible_fx, destructible_anim, etc functions you put below this one, and transition to the next state." "OptionalArg: : See isAttackerValid() for good values." "OptionalArg: : This is always 32. As far as I can tell it's not used for anything." "OptionalArg: : See isValidDamageCause() for good values. Undefined means all damage is valid." "Example: destructible_create( "vehicle_pickup", "tag_body", 300, undefined, 32, "no_melee" );" "SPMP: both" ///ScriptDocEnd ============= */ 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; } /* ============= ///ScriptDocBegin "Name: destructible_part( , , , , , , , , , )" "Summary: Add a new 'part' to the last destructible type created. A part can be either a piece of the model that can be damaged (and hidden, shown, etc) separately from the rest, or a separate model that can be thrown using physics." "Module: Destructible" "CallOn: nothing" "MandatoryArg: : All geometry skinned to this tag will be considered to be this part." "OptionalArg: : The model fired off. This happens on a state change (if destructible_physics() is specified) or when the destructible explodes. The model is fired from "tagName", directly away from the origin of the entity." "OptionalArg: : How much damage this part can sustain before transitioning to its next state. Undefined means this part does not take damage." "OptionalArg: : See isAttackerValid() for good values." "OptionalArg: : This is always 32. As far as I can tell it's not used for anything though." "OptionalArg: : See isValidDamageCause() for good values. Undefined means all damage is valid." "OptionalArg: : Fraction of damage done to this part that is also passed on to the parent entity. Defaults to 0." "OptionalArg: : If set >0 and a state transition happens where destructible_explode() is used, launch "modelName" using physics with velocity scaled by this value." "OptionalArg: : If defined, transition to the next state if hit by a grenade. Good for windows." "OptionalArg: : Fraction of damage done to parent that will be passed on to this part. Must be >0. Defaults to 0. Appears to also receive damage from other child parts in the model." "Example: destructible_part( "tag_hood", "vehicle_pickup_hood", 800, undefined, undefined, undefined, 1.0, 2.5 );" "SPMP: both" ///ScriptDocEnd ============= */ 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 ); } /* ============= ///ScriptDocBegin "Name: destructible_state( , , , , , , , )" "Summary: Add a new 'state' to the last destructible part created. When the previous state reaches zero health, this state will be entered." "Module: Destructible" "CallOn: Nothing" "OptionalArg: : Any geometry skinned to this tag will be shown when in this state, and hidden otherwise (unless another state uses it)." "OptionalArg: : If this is the base part, model to swap the entity's model to when in this state (if undefined, previous model will be left alone). If non-base part, (unconfirmed!) model to be thrown using physics if the destructible explodes while this part is in this state." "OptionalArg: : How much damage this part can sustain before transitioning to its next state. Undefined means do not transition." "OptionalArg: : See isAttackerValid() for good values." "OptionalArg: : This is always 32. As far as I can tell it's not used for anything though." "OptionalArg: : See isValidDamageCause() for good values. Undefined means all damage is valid." "OptionalArg: : If defined, transition to the next state if hit by a grenade. Good for windows." "OptionalArg: : Rotate the model to face the damage when entering this state. For example, this is used by toy_mailbox2_yellow to make it bend away from the damage." "Example: destructible_state( undefined, "vehicle_pickup_destroyed", undefined, 32, "no_melee" );" "SPMP: both" ///ScriptDocEnd ============= */ 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 ); } /* ============= ///ScriptDocBegin "Name: destructible_fx( , , , , , )" "Summary: Play FX after the health of the previously defined destructible state reaches zero." "Module: Destructible" "CallOn: Nothing" "OptionalArg: : Tag to play the effects from. If undefined, play from the entity's origin, facing up." "MandatoryArg: : FX file to play, with path relative to game\share\raw directory, and no file extension." "OptionalArg: : If true or undefined, orient FX to tag's angles. If false, face FX up." "OptionalArg: : Only play this FX if the last state transition was caused by a damage type that is a substring of this string. The function damage_not() is interesting here." "OptionalArg: : If there are multiple groups defined in a state, a random group is chosen when the state is exited and only the FX, animations and sounds for that group are played. Toy_locker_double is the only example I have found." "OptionalArg: : If defined (to a number, generally 1), prevents too many destructibles entering a state on the same frame, ie prevents too many FX being started on the same frame." "Example: destructible_fx( "tag_death_fx", "fx/explosions/small_vehicle_explosion", false );" "SPMP: both" ///ScriptDocEnd ============= */ 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; } // Boon August2012: DOT is Damage Over Time? Like poison gas? destructible_createDOT_predefined( index ) { AssertEx( IsDefined( index ), "Must specify an index >= 0" ); destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); if ( !IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ] ) ) level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ] = []; dotIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ].size ); dot = createDOT(); dot.type = "predefined"; dot.index = index; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ][ dotIndex ] = dot; } destructible_createDOT_radius( tag, spawnflags, radius, height ) { AssertEx( IsDefined( tag ), "Must define tag where dot with be spawned for destructible" ); destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); if ( !IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ] ) ) level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ] = []; dotIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ].size ); dot = createDOT_radius( ( 0, 0, 0 ), spawnflags, radius, height ); dot.tag = tag; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ][ dotIndex ] = dot; } destructible_setDOT_onTick( delay, interval, duration, minDamage, maxDamage, falloff, type, affected ) { destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); dotIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ].size - 1 ); dot = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ][ dotIndex ]; Assert( IsDefined( dot ) ); dot setDOT_onTick( delay, interval, duration, minDamage, maxDamage, falloff, type, affected ); initDOT( type ); } destructible_setDOT_onTickFunc( onEnterFunc, onExitFunc, onDeathFunc ) { destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); dotIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ].size - 1 ); dot = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ][ dotIndex ]; Assert( IsDefined( dot ) ); tickIndex = dot.ticks.size; dot.ticks[ tickIndex ].onEnterFunc = onEnterFunc; dot.ticks[ tickIndex ].onExitFunc = onExitFunc; dot.ticks[ tickIndex ].onDeathFunc = onDeathFunc; } destructible_buildDOT_onTick( duration, affected ) { destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); dotIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ].size - 1 ); dot = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ][ dotIndex ]; Assert( IsDefined( dot ) ); dot buildDOT_onTick( duration, affected ); } destructible_buildDOT_startLoop( count ) { destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); dotIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ].size - 1 ); dot = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ][ dotIndex ]; Assert( IsDefined( dot ) ); dot buildDOT_startLoop( count ); } destructible_buildDOT_damage( minDamage, maxDamage, falloff, damageFlag, meansOfDeath, weapon ) { destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); dotIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ].size - 1 ); dot = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ][ dotIndex ]; Assert( IsDefined( dot ) ); dot buildDOT_damage( minDamage, maxDamage, falloff, damageFlag, meansOfDeath, weapon ); } destructible_buildDOT_wait( time ) { destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); dotIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ].size - 1 ); dot = level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "dot" ][ dotIndex ]; Assert( IsDefined( dot ) ); dot buildDOT_wait( time ); } /* ============= ///ScriptDocBegin "Name: destructible_loopfx( , , , )" "Summary: Play FX over and over after the health of the previously defined destructible state reaches zero. Calls loopfx_onTag()" "Module: Destructible" "CallOn: Nothing" "OptionalArg: : Tag to play the effects from. If undefined, play from the entity's origin, facing up." "MandatoryArg: : FX file to play, with path relative to game\share\raw directory." "OptionalArg: : Time to wait between FX." "OptionalArg: : If defined (to a number, generally 1), prevents too many destructibles entering a state on the same frame, ie prevents too many FX being started on the same frame." "Example: destructible_loopfx( "tag_hood_fx", "fx/smoke/car_damage_blacksmoke", 0.4 );" "SPMP: both" ///ScriptDocEnd ============= */ 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; } /* ============= ///ScriptDocBegin "Name: destructible_healthdrain( , , , )" "Summary: Drain health from this entity or part while in this state. Note that health drain continues in following states unless changed by another call to this function. See function health_drain() for actual workings." "Module: Destructible" "CallOn: An entity" "MandatoryArg: : Amount of damage to do." "MandatoryArg: : Interval between damage, in seconds." "OptionalArg: : Radius of the bad place cylinder to create. nb The cylinder is always 128 units high." "OptionalArg: : Any valid "team" parameter for the code function BadPlace_Cylinder(), or "both", which is translated to mean "allies" and "bad_guys"." "Example: destructible_healthdrain( 15, 0.25, 210, "allies" );" "SPMP: both" ///ScriptDocEnd ============= */ 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; } /* ============= ///ScriptDocBegin "Name: destructible_sound( , , )" "Summary: Play sound after the health of the previously defined destructible state reaches zero. Sound is played at the tag defined for the previously defined part/state." "Module: Destructible" "CallOn: Nothing" "MandatoryArg: : Sound alias!" "OptionalArg: : If defined, this sound will only be played if soundCause matches the damage type." "OptionalArg: : If there are multiple groups defined in a state, a random group is chosen when the state is exited and only the FX, animations and sounds for that group are played. Toy_locker_double is the only example I have found." "Example: destructible_sound( "veh_tire_deflate", "bullet" );" "SPMP: both" ///ScriptDocEnd ============= */ 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; } /* ============= ///ScriptDocBegin "Name: destructible_loopsound( , , )" "Summary: Play looping sound after the health of the previously defined destructible state reaches zero. Sound is played at the tag defined for the previously defined part/state." "Module: Destructible" "CallOn: Nothing" "MandatoryArg: : Sound alias!" "OptionalArg: : If defined, this sound will only be played if loopsoundCause matches the damage type." "Example: destructible_loopsound( "fire_vehicle_med" );" "SPMP: both" ///ScriptDocEnd ============= */ 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; } /* ============= ///ScriptDocBegin "Name: destructible_anim( , , , , , , , , )" "Summary: Play animation after the health of the previously defined destructible state reaches zero." "Module: Destructible" "CallOn: Nothing" "MandatoryArg: : The animation to play." "MandatoryArg: : The animtree that the animation belongs to. Commonly the animtree is set at the top of the script with "#using_animtree" and "#animtree" is used here." "MandatoryArg: : The type of anim call to make, either "setanim" (to play this animation and leave others alone) or "setanimknob" (to play this animation and stop playing others)." "OptionalArg: : If set to true, don't play this animation on vehicles in multiplayer?" "OptionalArg: : If there are multiple groups defined in a state, a random group is chosen when the state is exited and only the FX, animations and sounds for that group are played. Toy_locker_double is the only example I have found." "OptionalArg: : The animation to play, as a string, for MP" "OptionalArg: : Randomly wait up to this long (in seconds) before starting the animation." "OptionalArg: : Randomly scale animation playback speed to a value between this and animRateMax." "OptionalArg: : Randomly scale animation playback speed to a value between animRateMin and this." "Example: destructible_anim( %vehicle_80s_sedan1_destroy, #animtree, "setanimknob", undefined, undefined, "vehicle_80s_sedan1_destroy" );" "SPMP: both" ///ScriptDocEnd ============= */ 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 ); } /* ============= ///ScriptDocBegin "Name: destructible_spotlight( )" "Summary: Attach a spotlight to a tag, eg when a hanging light is shot." "Module: Destructible" "CallOn: Nothing" "MandatoryArg: : " "Example: destructible_spotlight( "tag_swing_r_far" );" "SPMP: both" ///ScriptDocEnd ============= */ 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; 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 of the most recently defined state." "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: Adds the array to the end of .v[ array_name ]" "Module: Destructibles" "MandatoryArg: : Name of array index in .v. May already exist; if so, array will be appended (not concatenated) to it." "MandatoryArg: : Array of keypairs to be added." "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; } /* ============= ///ScriptDocBegin "Name: destructible_car_alarm()" "Summary: Play a car alarm sound after the health of the previously defined destructible state reaches zero. Calls thread do_car_alarm()." "Module: Destructible" "CallOn: Nothing" "Example: destructible_car_alarm();" "SPMP: both" ///ScriptDocEnd ============= */ 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; } /* ============= ///ScriptDocBegin "Name: destructible_lights_out( )" "Summary: Switches off the nearest light if it is within range, after the health of the previously defined destructible state reaches zero." "Module: Destructible" "CallOn: Nothing" "MandatoryArg: : Maximum range in units to look for the light." "Example: destructible_lights_out( 16 );" "SPMP: both" ///ScriptDocEnd ============= */ 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; } // made so I can put random advertisements on the destructible taxi cabs without making lots of destructible types for each version random_dynamic_attachment( tagName, attachment_1, attachment_2, clipToRemove ) { 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; } /* ============= ///ScriptDocBegin "Name: destructible_physics( , )" "Summary: Throw this part's model using physics, after the health of the previously defined destructible state reaches zero." "Module: Destructible" "CallOn: An entity" "OptionalArg: : Tag of other part to throw using physics (useful when called on base part)." "OptionalArg: : Vector velocity, in the space of the tag. eg use (200,0,0) to fire along the tag's forward axis. Defaults to throwing away from impact." "Example: destructible_physics( "tag_cap", ( 50, 0, 0 ) );" "SPMP: both" ///ScriptDocEnd ============= */ 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; } /* ============= ///ScriptDocBegin "Name: destructible_splash_damage_scaler( )" "Summary: Scale splash damage done to destructibles of this type. Only works when called before any additional parts or states are defined." "Module: Destructible" "CallOn: Nothing" "MandatoryArg: " "Example: destructible_splash_damage_scaler( 15 );" "SPMP: both" ///ScriptDocEnd ============= */ 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; } /* ============= ///ScriptDocBegin "Name: destructible_explode( , , , , , , , , , , , )" "Summary: Create an explosion that throws physics parts and does splash damage to nearby objects, after the health of the previously defined destructible state reaches zero." "Module: Destructible" "CallOn: Nothing" "MandatoryArg: : Initial velocity of each thrown part will be multiplied by a random amount between this and force_max." "MandatoryArg: : Initial velocity of each thrown part will be multiplied by a random amount between force_min and this." "MandatoryArg: : Max distance over which objects are damaged by explosion, in SP." "MandatoryArg: : Max distance over which objects are damaged by explosion, in MP." "MandatoryArg: : Damage done to objects at maximum distance." "MandatoryArg: : Damage done to objects at zero distance." "OptionalArg: : Allow this destructible to continue taking damage after the explosion." "OptionalArg: : Vertical offset for explosion center. Added to originOffset3D. Defaults to 80." "OptionalArg: : Create a 2 second earthquake with this scale." "OptionalArg: : Create a 2 second earthquake with this radius." "OptionalArg: : 3D offset for explosion center. Added to originOffset. Defaults to (0,0,0)." "OptionalArg: : Wait before triggering the explosion." "Example: destructible_explode( 4000, 5000, 210, 250, 50, 300, undefined, undefined, 0.3, 500 );" "SPMP: both" ///ScriptDocEnd ============= */ destructible_explode( force_min, force_max, rangeSP, rangeMP, mindamage, maxdamage, continueDamage, originOffset, earthQuakeScale, earthQuakeRadius, originOffset3d, delaytime ) { 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; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "originOffset3d" ] = originOffset3d; level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "delaytime" ] = delaytime; } /* ============= ///ScriptDocBegin "Name: destructible_function( , )" "Summary: Call the specified function on the entity, after the health of the previously defined destructible state reaches zero." "Module: Destructible" "CallOn: Nothing" "MandatoryArg: : A pointer to the function to be called" "Example: destructible_function( ::removeScreen );" "SPMP: both" ///ScriptDocEnd ============= */ destructible_function( functionPtr ) { Assert( IsDefined( level.destructible_type ) ); destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "function" ] = functionPtr; } /* ============= ///ScriptDocBegin "Name: destructible_notify( )" "Summary: Notify the supplied string on the entity when the current part leaves this state." "Module: Destructible" "CallOn: Nothing" "MandatoryArg: : String to notify" "Example: destructible_notify( "stop flashing" );" "SPMP: both" ///ScriptDocEnd ============= */ destructible_notify( notifyStr ) { Assert( IsDefined( level.destructible_type ) ); destructibleIndex = ( level.destructible_type.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ] ) ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts ) ); partIndex = ( level.destructible_type[ destructibleIndex ].parts.size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ] ) ); stateIndex = ( level.destructible_type[ destructibleIndex ].parts[ partIndex ].size - 1 ); Assert( IsDefined( level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ] ) ); level.destructible_type[ destructibleIndex ].parts[ partIndex ][ stateIndex ].v[ "functionNotify" ] = notifyStr; } /* ============= ///ScriptDocBegin "Name: destructible_damage_threshold( )" "Summary: Minimum damage that will have any effect on the health of this state. If damage done in one hit is less than this amount, it will be ignored." "Module: Destructible" "CallOn: Nothing" "MandatoryArg: : A number" "Example: destructible_damage_threshold( 50 );" "SPMP: both" ///ScriptDocEnd ============= */ destructible_damage_threshold( damage_threshold ) { 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[ "damage_threshold" ] = damage_threshold; } /* ============= ///ScriptDocBegin "Name: destructible_attachmodel( , )" "Summary: Attach a model to this destructible, fusing them together to create a single entity." "Module: Destructible" "CallOn: Nothing." "OptionalArg: : Tag to attach the model to. If undefined, model will be merged with base model." "MandatoryArg: : Model to attach." "Example: destructible_attachmodel( undefined, "lv_slot_machine_destroyed" );" "SPMP: singleplayer" ///ScriptDocEnd ============= */ destructible_attachmodel( tagName, modelName ) { Assert( IsDefined( modelName ) ); Assert( IsDefined( level.destructible_type ) ); Assert( level.destructible_type.size > 0 ); modelName = ToLower( modelName ); destructibleIndex = ( level.destructible_type.size - 1 ); if ( !IsDefined( level.destructible_type[ destructibleIndex ].attachedModels ) ) level.destructible_type[ destructibleIndex ].attachedModels = []; attachedModel = SpawnStruct(); attachedModel.model = modelName; attachedModel.tag = tagName; level.destructible_type[ destructibleIndex ].attachedModels[ level.destructible_type[ destructibleIndex ].attachedModels.size ] = attachedModel; } /* "Worker function called by destructible_part() and destructible_state()." */ 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.destructibleInfo].parts ) ) return; //if ( !isdefined( level.precachedModels ) ) // level.precachedModels = []; if ( IsDefined( level.destructible_type[ self.destructibleInfo].attachedModels ) ) { foreach ( attachedModel in level.destructible_type[ self.destructibleInfo].attachedModels ) PreCacheModel( attachedModel.model ); } for ( i = 0; i < level.destructible_type[ self.destructibleInfo].parts.size; i++ ) { for ( j = 0; j < level.destructible_type[ self.destructibleInfo].parts[ i ].size; j++ ) { if ( level.destructible_type[ self.destructibleInfo].parts[ i ].size <= j ) continue; if ( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ i ][ j ].v[ "modelName" ] ) ) { PreCacheModel( level.destructible_type[ self.destructibleInfo].parts[ i ][ j ].v[ "modelName" ] ); } // in MP we have to precache animations that will be used if ( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ i ][ j ].v[ "animation" ] ) ) { animGroups = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].parts[ i ][ j ].v[ "random_dynamic_attachment_1" ] ) ) { foreach ( model in level.destructible_type[ self.destructibleInfo].parts[ i ][ j ].v[ "random_dynamic_attachment_1" ] ) { if ( IsDefined( model ) && model != "" ) { PreCacheModel( model ); PreCacheModel( model + DESTROYED_ATTACHMENT_SUFFIX ); } } foreach ( model in level.destructible_type[ self.destructibleInfo].parts[ i ][ j ].v[ "random_dynamic_attachment_2" ] ) { if ( IsDefined( model ) && model != "" ) { PreCacheModel( model ); 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.destructibleInfo].parts ) ) return; //if ( !isdefined( level.precachedFX ) ) // level.precachedFX = []; for ( i = 0; i < level.destructible_type[ self.destructibleInfo].parts.size; i++ ) { for ( j = 0; j < level.destructible_type[ self.destructibleInfo].parts[ i ].size; j++ ) { if ( level.destructible_type[ self.destructibleInfo].parts[ i ].size <= j ) continue; part = level.destructible_type[ self.destructibleInfo].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 ]; fx_tags = part.v[ "fx_tag" ][ g ]; if ( IsDefined( fx_filenames ) ) { assert( IsDefined( fx_tags ) ); // 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; for ( idx = 0; idx < fx_filenames.size; idx++ ) { fx_filename = fx_filenames[idx]; fx_tag = fx_tags[idx]; level.destructible_type[ self.destructibleInfo ].parts[ i ][ j ].v[ "fx" ][ g ][ idx ] = LoadFX( fx_filename, fx_tag ); //if ( !isdefined( level.precachedFX[ fx_filename ] ) ) //{ // level.precachedFX[ fx_filename ] = true; // println( "loadfx( " + fx_filename + " )" ); //} } } } } loopfx_filenames = level.destructible_type[ self.destructibleInfo].parts[ i ][ j ].v[ "loopfx_filename" ]; loopfx_tags = level.destructible_type[ self.destructibleInfo].parts[ i ][ j ].v[ "loopfx_tag" ]; if ( IsDefined( loopfx_filenames ) ) { assert( IsDefined( loopfx_tags ) ); // has we already set this up? if ( IsDefined( part.v[ "loopfx" ] ) && part.v[ "loopfx" ].size == loopfx_filenames.size ) continue; for ( idx = 0; idx < loopfx_filenames.size; idx++ ) { loopfx_filename = loopfx_filenames[idx]; loopfx_tag = loopfx_tags[idx]; level.destructible_type[ self.destructibleInfo ].parts[ i ][ j ].v[ "loopfx" ][ idx ] = LoadFX( loopfx_filename, loopfx_tag ); //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 ( isSP() ) { if ( IsDefined( attacker ) && IsPlayer( attacker ) ) self.damageOwner = attacker; } else { if ( IsDefined( attacker ) && IsPlayer( attacker ) ) self.damageOwner = attacker; // osprey gunner could shoot a destructible, or anything with a player as the gunner else if( IsDefined( attacker ) && IsDefined( attacker.gunner ) && IsPlayer( attacker.gunner ) ) self.damageOwner = attacker.gunner; } 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.destructibleInfo].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.destructibleInfo].parts[ 0 ][ 0 ].v[ "splash_damage_scaler" ] ) ) damage *= level.destructible_type[ self.destructibleInfo].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(); } else if( isdefined( level.enable_ai_shotgun_destructible_damage ) && level.enable_ai_shotgun_destructible_damage ) { if( isdefined( attacker.weapon ) ) currentweapon = attacker.weapon; } 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.destructibleInfo].parts.size; i++ ) { stateIndex = self.destructible_parts[ i ].v[ "currentState" ]; if ( level.destructible_type[ self.destructibleInfo].parts[ i ].size <= stateIndex ) continue; if ( !isdefined( tagName ) ) continue; if ( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ i ][ stateIndex ].v[ "tagName" ] ) ) { partTagName = level.destructible_type[ self.destructibleInfo].parts[ i ][ stateIndex ].v[ "tagName" ]; if ( tolower( partTagName ) == tolower( 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; if( level.fast_destructible_explode ) self endon ( "destroyed" ); // 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" ]; // We've finished with the last state already if ( !isdefined( level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ stateIndex ] ) ) break; // See if the model is also supposed to damage the parent if ( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ 0 ].v[ "alsoDamageParent" ] ) ) { if ( getDamageType( damageType ) != "splash" ) { ratio = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].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.destructibleInfo].parts[ partIndex ][ stateIndex ].v[ "health" ] ) ) break; if ( !isdefined( self.destructible_parts[ partIndex ].v[ "health" ] ) ) break; // If we entered a new state, reset the part's health to the starting value for that state if ( updateHealthValue ) self.destructible_parts[ partIndex ].v[ "health" ] = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].parts[ partIndex ][ stateIndex ].v[ "grenadeImpactDeath" ] ) ) && ( damageType == "impact" ) ) damage = 100000000; // Damage done is below the damage threshold for this part if ( IsDefined( level.destructible_type[ self.destructibleInfo ].parts[ partIndex ][ stateIndex ].v[ "damage_threshold" ] ) && level.destructible_type[ self.destructibleInfo ].parts[ partIndex ][ stateIndex ].v[ "damage_threshold" ] > damage ) damage = 0; // 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 ); if ( !IsDefined( self.waiting_for_queue ) ) self.waiting_for_queue = 1; else self.waiting_for_queue++; self waittill( "queue_processed", success ); self.waiting_for_queue--; if ( self.waiting_for_queue == 0 ) 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 ) { AssertEx( 0, "Logically, we should never get here, and I plan to remove this part of the script. Tell Boon at IW if you see this." ); // 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.destructibleInfo].parts[ partIndex ][ actionStateIndex ] ) ) action_v = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v; state_v = undefined; if ( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ stateIndex ] ) ) state_v = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ stateIndex ].v; if ( !isdefined( level.destructible_type[ self.destructibleInfo].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.destructibleInfo].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.destructibleInfo].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 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.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "loopfx" ] ) ) { loopfx_size = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "loopfx_tag" ][ idx ] ) ); loopfx = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "loopfx" ][ idx ]; loopfx_tag = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "loopfx_tag" ][ idx ]; loopRate = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "loopsound" ] ) ) { for ( i = 0; i < level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "loopsound" ].size; i++ ) { validSoundCause = self isValidSoundCause( "loopsoundCause", action_v, i, damageType ); if ( validSoundCause ) { loopsoundAlias = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "loopsound" ][ i ]; loopsoundTagName = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].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.destructibleInfo].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.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "healthdrain_amount" ] ) ) { self notify( "Health_Drain_State_Change" + partIndex ); healthdrain_amount = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "healthdrain_amount" ]; healthdrain_interval = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "healthdrain_interval" ]; healthdrain_modelName = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "modelName" ]; healthdrain_tagName = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "tagName" ]; badplaceRadius = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "badplace_radius" ]; badplaceTeam = level.destructible_type[ self.destructibleInfo].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 ); } } // DOT // - Usually this will happen the same time as explode. // - This is setup so it can theoretically happen at any given state dots = level.destructible_type[ self.destructibleInfo ].parts[ partIndex ][ actionStateIndex ].v[ "dot" ]; if ( IsDefined( dots ) ) { foreach ( dot in dots ) { dotIndex = dot.index; if ( dot.type == "predefined" && IsDefined( dotIndex ) ) { dot_group = []; foreach ( triggerIndex in level.destructible_type[ self.destructibleInfo ].destructible_dots[ dotIndex ] ) { classname = triggerIndex[ "classname" ]; _dot = undefined; switch ( classname ) { case "trigger_radius": origin = triggerIndex[ "origin" ]; spawnflags = triggerIndex[ "spawnflags" ]; radius = triggerIndex[ "radius" ]; height = triggerindex[ "height" ]; _dot = createDOT_radius( self.origin + origin, spawnflags, radius, height ); _dot.ticks = dot.ticks; dot_group[ dot_group.size ] = _dot; break; default: } } level thread startDOT_group( dot_group ); } else { if ( IsDefined( dot ) ) { if ( IsDefined( dot.tag ) ) dot setDOT_origin( self GetTagOrigin( dot.tag ) ); level thread startDOT_group( [ dot ] ); } } } dots = undefined; } // 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.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "explode" ] ) ) { delayModelSwap = true; force_min = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "explode_force_min" ]; force_max = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "explode_force_max" ]; range = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "explode_range" ]; mindamage = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "explode_mindamage" ]; maxdamage = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "explode_maxdamage" ]; continueDamage = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "continueDamage" ]; originOffset = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "originOffset" ]; earthQuakeScale = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "earthQuakeScale" ]; earthQuakeRadius = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "earthQuakeRadius" ]; originOffset3d = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "originOffset3d" ]; delaytime = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "delaytime" ]; 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, originOffset3d, delaytime ); } // if the part should do physics here then initiate the physics and velocity physTagOrigin = undefined; if ( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "physics" ] ) ) { for ( i = 0; i < level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "physics" ].size; i++ ) { physTagOrigin = undefined; physTagName = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "physics_tagName" ][ i ]; physVelocity = level.destructible_type[ self.destructibleInfo].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 *= 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.destructibleInfo].parts.size; j++ ) { if ( !isdefined( level.destructible_type[ self.destructibleInfo].parts[ j ][ 0 ].v[ "tagName" ] ) ) continue; if ( level.destructible_type[ self.destructibleInfo].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; } } } // if the part should notify, for a function on a previous state if ( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v ) && IsDefined( level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "functionNotify" ] ) ) { self notify( level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "functionNotify" ] ); } // if the part should call a function on its entity if ( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "function" ] ) ) { self thread [[ level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v[ "function" ] ]](); } 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.destructibleInfo].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" ); } // Called on a destructible entity, returns a list of all tags and models that are currently active. // - AFAIK models aren't attached to destructibles any more so it's probably just a list of tags // - Uses direction_vec to set an angle in the level's info for this destructible type, for later use by the destructible_splash_rotatation() function. getAllActiveParts( direction_vec ) { activeParts = []; Assert( IsDefined( self.destructibleInfo) ); if ( !isdefined( level.destructible_type[ self.destructibleInfo].parts ) ) return activeParts; // prof_begin( "_destructible" ); for ( i = 0; i < level.destructible_type[ self.destructibleInfo].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.destructibleInfo].parts[ partIndex ].size; j++ ) { splash_rotation = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].parts[ partIndex ][ j ].v[ "rotateTo" ] = ( 0, rotate_to_angle_y, 0 ); } } if ( !isdefined( level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ currentState ] ) ) continue; tagName = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ currentState ].v[ "tagName" ]; if ( !isdefined( tagName ) ) tagName = ""; if ( tagName == "" ) continue; modelName = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].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.destructibleInfo].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.destructibleInfo].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.destructibleInfo].parts[ partIndex ][ stateIndex ].v[ "validDamageCause" ]; if ( !isdefined( validType ) ) return true; if ( ( validType == "splash" ) && damageType != "splash" ) return false; if ( ( validType == "no_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( level.badplace_cylinder_func ) ) { uniqueName = "" + GetTime(); if ( !isdefined( self.disableBadPlace ) ) { if ( IsDefined( self.script_radius ) ) { // overwrite the badplace radius from the map badplaceRadius = self.script_radius; } if ( isSP() && IsDefined( badplaceTeam ) ) { 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 ); } else { call [[ level.badplace_cylinder_func ]]( uniqueName, 0, self.origin, badplaceRadius, 128 ); 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.destructibleInfo].parts[ partIndex ][ stateIndex ].v[ "modelName" ]; tagName = level.destructible_type[ self.destructibleInfo].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 ); /# // Give it a targetname that makes it easy to identify using g_entinfo physicsObject.targetname = modelName + "(thrown physics model)"; #/ // 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, originOffset3d, delaytime ) { 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( originOffset3d) ) originOffset3d = (0,0,0); if ( !isdefined( continueDamage ) || ( IsDefined( continueDamage ) && !continueDamage ) ) { if ( IsDefined( self.exploded ) ) return; self.exploded = true; } if (!isdefined( delaytime) ) delaytime = 0; self notify( "exploded", attacker ); level notify( "destructible_exploded", self, attacker ); 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(); if( ! level.fast_destructible_explode ) wait 0.05; // entity could be deleted during previous wait if ( !IsDefined( self ) ) return; currentState = self.destructible_parts[ partIndex ].v[ "currentState" ]; Assert( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ partIndex ] ) ); tagName = undefined; if ( IsDefined( level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ currentState ] ) ) tagName = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].parts ) ) { for ( i = ( level.destructible_type[ self.destructibleInfo].parts.size - 1 ); i >= 0; i-- ) { if ( i == partIndex ) continue; stateIndex = self.destructible_parts[ i ].v[ "currentState" ]; if ( stateIndex >= level.destructible_type[ self.destructibleInfo].parts[ i ].size ) stateIndex = level.destructible_type[ self.destructibleInfo].parts[ i ].size - 1; modelName = level.destructible_type[ self.destructibleInfo].parts[ i ][ stateIndex ].v[ "modelName" ]; tagName = level.destructible_type[ self.destructibleInfo].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.destructibleInfo].parts[ i ][ 0 ].v[ "physicsOnExplosion" ] ) ) { if ( level.destructible_type[ self.destructibleInfo].parts[ i ][ 0 ].v[ "physicsOnExplosion" ] > 0 ) { velocityScaler = level.destructible_type[ self.destructibleInfo].parts[ i ][ 0 ].v[ "physicsOnExplosion" ]; point = self GetTagOrigin( tagName ); initial_velocity = VectorNormalize( point - explosionOrigin ); 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" ); if( ! level.fast_destructible_explode ) wait 0.05; // entity could be deleted during previous wait if ( !IsDefined( self ) ) return; damageLocation = explosionOrigin + ( 0, 0, originOffset ) + originOffset3d; isVehicle = ( GetSubStr( level.destructible_type[ self.destructibleInfo].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 ( delaytime > 0 ) wait (delaytime); //using this in hamburg where player has no control and needs to be protected from cars blowing up nearby. It's not the players fault!! if( isdefined( level.destructible_protection_func ) ) thread [[level.destructible_protection_func]](); 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 { weapon = "destructible_toy"; if ( isVehicle ) weapon = "destructible_car"; if ( !isdefined( self.damageOwner ) ) { self RadiusDamage( damageLocation, range, maxdamage, mindamage, self, "MOD_EXPLOSIVE", weapon ); } else { self RadiusDamage( damageLocation, range, maxdamage, mindamage, self.damageOwner, "MOD_EXPLOSIVE", weapon ); 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; while ( isdefined( self ) && isdefined( self.waiting_for_queue ) ) { self waittill( "queue_processed" ); wait 0.05; } 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.destructibleInfo = undefined; self.healthdrain = undefined; self.non_player_damage = undefined; self.player_damage = undefined; if ( !IsDefined( level.destructible_cleans_up_more ) ) return; self.script_noflip = undefined; self.exploding = undefined; self.loopingsoundstopnotifies = undefined; self.car_alarm_org = 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 ) { // (Boon Oct2012: This function appears to be designed to transmit damage from child to/from parent parts. It can get // called several times for one damage event, and level.notifyDamageAfterFrame is usually not defined so it then generates // a cascade of damage notifies. Frankly, I can't understand how it works or how it is supposed to work, and I strongly // suspect it's very fragile.) 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 ); } } DYNAMICPATH = 1; collision_brush_pre_explosion( clip ) { waittillframeend;// wait for same area post brushes to connect before we disconnect if ( isSP() && clip.spawnflags & DYNAMICPATH ) clip call [[ level.disconnectPathsFunction ]](); self waittill( "exploded" ); if ( isSP() && clip.spawnflags & DYNAMICPATH ) clip call [[ level.connectPathsFunction ]](); clip Delete(); } collision_brush_post_explosion( clip ) { clip NotSolid(); if ( isSP() && clip.spawnflags & DYNAMICPATH ) clip call [[ level.connectPathsFunction ]](); self waittill( "exploded" ); waittillframeend;// wait for same area pre brushes to connect before we disconnect if ( isSP() ) { if( clip.spawnflags & DYNAMICPATH ) 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 ) ); //IW6 code to make light fizzle/blink/fade goes here. 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" ); 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; // Boon Nov 2012: This seems wrong. If there are no sounds defined, we lose our record of what group we're using? if ( !isDefined( action_v[ "sound" ] ) ) return undefined; if ( !isdefined( groupNum ) ) groupNum = 0; // Boon Nov 2012: I don't think it should be compulsory to have a sound for every effect. //assert( isDefined( action_v[ "sound" ][ groupNum ] ) ); if ( !IsDefined( action_v[ "sound" ][ groupNum ] ) ) return undefined; 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( self.no_destructible_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.destructibleInfo ].parts[ partIndex ][ actionStateIndex ] ) ) return fxCost; action_v = level.destructible_type[ self.destructibleInfo].parts[ partIndex ][ actionStateIndex ].v; if ( IsDefined( action_v[ "fx" ] ) ) { foreach ( fxCostObj in action_v[ "fx_cost" ] ) { foreach ( fxCostVal in fxCostObj ) fxCost += fxCostVal; } } return fxCost; } // ************************************* // DOT - **TODO for iw6 move into seperate .gsc // ************************************* initDOT( type ) { AssertEx( IsDefined( type ), "Must specify a type of 'poision'" ); if ( !flag_exist( "FLAG_DOT_init" ) ) { flag_init( "FLAG_DOT_init" ); //level._dots = []; flag_set( "FLAG_DOT_init" ); } type = ToLower( type ); switch ( type ) { case "poison": if ( !flag_exist( "FLAG_DOT_poison_init" ) ) { flag_init( "FLAG_DOT_poison_init" ); flag_set( "FLAG_DOT_poison_init" ); } break; default: AssertMsg( "Must specify a type of 'poison'" ); } } // Not sure if we will ever get support for this //createDOT_sphere(){} createDOT() { dot = SpawnStruct(); dot.ticks = []; return dot; } createDOT_radius( origin, spawnflags, radius, height ) { AssertEx( IsDefined( origin ), "Must specify a origin for the DOT" ); AssertEx( IsDefined( spawnflags ), "Must specify spawnflags for the DOT" ); AssertEx( IsDefined( radius ), "Must specify a radius for the DOT" ); AssertEx( IsDefined( height ), "Must specify a height for the DOT" ); dot = SpawnStruct(); dot.type = "trigger_radius"; dot.origin = origin; dot.spawnflags = spawnflags; dot.radius = radius; dot.minRadius = radius; dot.maxRadius = radius; dot.height = height; dot.ticks = []; return dot; } // There is a code request for this ... so stubbing this in for now /* createDOT_rect( origin, spawnflags, length, width, height ){} */ /* addDOT_radius( origin, spawnflags, radius, height ) { AssertEx( IsDefined( origin ), "Must specify a origin for the DOT" ); AssertEx( IsDefined( spawnflags ), "Must specify spawnflags for the DOT" ); AssertEx( IsDefined( radius ), "Must specify a radius for the DOT" ); AssertEx( IsDefined( height ), "Must specify a height for the DOT" ); dot = SpawnStruct(); dot.origin = origin; dot.spawnflags = spawnflags; dot.radius = radius; dot.minRadius = radius; dot.maxRadius = radius; dot.height = height; dot.ticks = []; self.dot = dot; dot.parent = self; return dot; } */ setDOT_origin( origin ) { AssertEx( IsDefined( origin ), "Must specify a origin" ); self.origin = origin; } setDOT_radius( minRadius, maxRadius ) { // Check if this is a valid call for self if ( IsDefined( self.classname ) && self.classname != "trigger_radius" ) AssertMsg( "You can only use setDOT_radius on trigger_radius" ); AssertEx( IsDefined( minRadius ), "Must define minRadius" ); if ( !IsDefined( maxRadius ) ) maxRadius = minRadius; AssertEx( maxRadius >= minRadius, "maxRadius must be greater than minRadius" ); AssertEx( self.radius >= maxRadius, "radius on trigger must be greater than or equal to maxRadius" ); self.minRadius = minRadius; self.maxRadius = maxRadius; } setDOT_height( minHeight, maxHeight ) { // Check if this is a valid call for self if ( IsDefined( self.classname ) && IsSubStr( self.classname, "trigger" ) ) AssertMsg( "You can only use setDOT_height on triggers" ); } setDOT_onTick( delay, interval, duration, minDamage, maxDamage, falloff, type, affected ) { if ( IsDefined( delay ) ) AssertEx( delay >= 0, "Must specify a delay >= 0" ); else delay = 0; AssertEx( IsDefined( interval ) && interval > 0 , "Must specify an interval > 0" ); AssertEx( IsDefined( duration ) && duration > 0, "Must specify a duration > 0" ); AssertEx( duration > interval, "duration must be > interval" ); AssertEx( IsDefined( minDamage ) && minDamage >= 0, "Must specify a minDamage >= 0" ); AssertEx( IsDefined( maxDamage ) && maxDamage > 0, "Must specify a maxDamage > 0" ); AssertEx( IsDefined( falloff ), "Must specify a falloff of 0 or 1" ); AssertEx( IsDefined( type ), "Must specify a type of 'normal' or 'poison'" ); AssertEx( IsDefined( affected ), "Must specify a type of 'player'" ); type = ToLower( type ); affected = ToLower( affected ); index = self.ticks.size; self.ticks[ index ] = SpawnStruct(); self.ticks[ index ].enable = 0; self.ticks[ index ].delay = delay; self.ticks[ index ].interval = interval; self.ticks[ index ].duration = duration; self.ticks[ index ].minDamage = minDamage; self.ticks[ index ].maxDamage = maxDamage; switch ( falloff ) { case 0: case 1: break; default: AssertMsg( "Must specify a falloff of 0 or 1" ); } self.ticks[ index ].falloff = falloff; self.ticks[ index ].startTime = 0; switch ( type ) { case "normal": break; case "poison": switch( affected ) { case "player": self.ticks[ index ].type = type; self.ticks[ index ].affected = affected; self.ticks[ index ].onEnterFunc = ::onEnterDOT_poisonDamagePlayer; self.ticks[ index ].onExitFunc = ::onExitDOT_poisonDamagePlayer; self.ticks[ index ].onDeathFunc = ::onDeathDOT_poisonDamagePlayer; break; default: AssertMsg( "Must specify the affected. Valid types are 'player'" ); } break; default: AssertMsg( "Must specify a type. Valid types are 'normal' or 'poision'" ); } } /* removeDOT_onTick( funcs ){} */ buildDOT_onTick( duration, affected ) { AssertEx( IsDefined( duration ), "Must specify a duration > 0" ); AssertEx( IsDefined( affected ), "Must specify a type of 'player'" ); affected = ToLower( affected ); index = self.ticks.size; self.ticks[ index ] = SpawnStruct(); self.ticks[ index ].duration = duration; self.ticks[ index ].delay = 0; self.ticks[ index ].onEnterFunc = ::onEnterDOT_buildFunc; self.ticks[ index ].onExitFunc = ::onExitDOT_buildFunc; self.ticks[ index ].onDeathFunc = ::onDeathDOT_buildFunc; switch( affected ) { case "player": self.ticks[ index ].affected = affected; break; default: AssertMsg( "Must specify the affected. Valid types are 'player'" ); } } buildDOT_startLoop( count ) { AssertEx( IsDefined( count ), "Must specify a count >= 0" ); AssertEx( IsDefined( self.ticks ) && self.ticks.size >= 0, "Must call buildDOT_onTick first" ); index = self.ticks.size - 1; if ( !IsDefined( self.ticks[ index ].statements ) ) self.ticks[ index ].statements = []; statementIndex = self.ticks[ index ].statements.size; self.ticks[ index ].statements = []; self.ticks[ index ].statements[ "vars" ] = []; self.ticks[ index ].statements[ "vars" ][ "count" ] = count; } buildDOT_damage( minDamage, maxDamage, falloff, damageFlag, meansOfDeath, weapon ) { AssertEx( IsDefined( minDamage ), "" ); AssertEx( IsDefined( maxDamage ), "" ); AssertEx( IsDefined( falloff ), "" ); AssertEx( IsDefined( damageFlag ), "" ); AssertEx( IsDefined( meansOfDeath ), "" ); AssertEx( IsDefined( weapon ), "" ); AssertEx( IsDefined( self.ticks ), "Must call buildDOT_startLoop first" ); tickIndex = self.ticks.size - 1; AssertEx( IsDefined( self.ticks[ tickIndex ] ) && IsDefined( self.ticks[ tickIndex ].statements ), "Must call buildDOT_startLoop first" ); if ( !IsDefined( self.ticks[ tickIndex ].statements[ "actions" ] ) ) self.ticks[ tickIndex ].statements[ "actions" ] = []; actionIndex = self.ticks[ tickIndex ].statements[ "actions" ].size; self.ticks[ tickIndex ].statements[ "actions" ][ actionIndex ] = []; self.ticks[ tickIndex ].statements[ "actions" ][ actionIndex ][ "vars" ] = [ minDamage, maxDamage, falloff, damageFlag, meansOfDeath, weapon ]; self.ticks[ tickIndex ].statements[ "actions" ][ actionIndex ][ "func" ] = ::doBuildDOT_damage; } buildDOT_wait( time ) { AssertEx( IsDefined( time ), "Must specify time >= 0" ); AssertEx( IsDefined( self.ticks ), "Must call buildDOT_startLoop first" ); tickIndex = self.ticks.size - 1; AssertEx( IsDefined( self.ticks[ tickIndex ] ) && IsDefined( self.ticks[ tickIndex ].statements ), "Must call buildDOT_startLoop first" ); if ( !IsDefined( self.ticks[ tickIndex ].statements[ "actions" ] ) ) self.ticks[ tickIndex ].statements[ "actions" ] = []; actionIndex = self.ticks[ tickIndex ].statements[ "actions" ].size; self.ticks[ tickIndex ].statements[ "actions" ][ actionIndex ] = []; self.ticks[ tickIndex ].statements[ "actions" ][ actionIndex ][ "vars" ] = [ time ]; self.ticks[ tickIndex ].statements[ "actions" ][ actionIndex ][ "func" ] = ::doBuildDOT_wait; } onEnterDOT_buildFunc( idx, trigger ) { AssertEx( IsDefined( idx ), "Must specify an index for this function" ); AssertEx( IsDefined( trigger ), "trying to run tick function on DOT that has been removed or is undefined" ); entNum = trigger GetEntityNumber(); trigger endon( "death" ); trigger endon( "LISTEN_kill_tick_" + idx + "_" + entNum ); self endon( "disconnect" ); self endon( "game_ended" ); self endon( "death" ); self endon( "LISTEN_exit_dot_" + entNum ); entNum = undefined; statements = trigger.ticks[ idx ].statements; if ( !IsDefined( statements ) || !IsDefined( statements[ "vars" ] ) || !IsDefined( statements[ "vars" ][ "count" ] ) || !IsDefined( statements[ "actions" ] ) ) return; count = statements[ "vars" ][ "count" ]; actions = statements[ "actions" ]; statements = undefined; // count = 0 runs loop forever // count >= 1 runs the loop count number of times for ( i = 1; i <= count || count == 0; i-- ) { foreach ( action in actions ) { vars = action[ "vars" ]; func = action[ "func" ]; self [[ func ]]( idx, trigger, vars ); } } } onExitDOT_buildFunc( idx, trigger ) { entNum = trigger GetEntityNumber(); playerNum = self GetEntityNumber(); trigger notify( "LISTEN_kill_tick_" + idx + "_" + entNum + "_" + playerNum ); } onDeathDOT_buildFunc( idx, trigger ){} doBuildDOT_damage( idx, trigger, vars ) { AssertEx( IsDefined( idx ), "Must specify an index >= 0" ); AssertEx( IsDefined( trigger ), "Must specify a trigger" ); AssertEx( IsDefined( vars ), "Must specify vars" ); minDamage = vars[ 0 ]; maxDamage = vars[ 1 ]; falloff = vars[ 2 ]; damageFlag = vars[ 3 ]; meansOfDeath = vars[ 4 ]; weapon = vars[ 5 ]; self thread [[ level.callbackPlayerDamage ]]( trigger, // eInflictor The entity that causes the damage.( e.g. a turret ) trigger, // eAttacker The entity that is attacking. maxDamage, // iDamage Integer specifying the amount of damage done damageFlag, // iDFlags Integer specifying flags that are to be applied to the damage meansOfDeath, // sMeansOfDeath Integer specifying the method of death weapon, // sWeapon The weapon number of the weapon used to inflict the damage trigger.origin, // vPoint The point the damage is from? ( 0,0,0 ) - trigger.origin,// vDir The direction of the damage "none", // sHitLoc The location of the hit 0 // psOffsetTime The time offset for the damage ); } doBuildDOT_wait( idx, trigger, vars ) { AssertEx( IsDefined( idx ), "Must specify an index >= 0" ); AssertEx( IsDefined( trigger ), "Must specify a trigger" ); AssertEx( IsDefined( vars ), "Must specify vars" ); entNum = trigger GetEntityNumber(); playerNum = self GetEntityNumber(); trigger endon( "death" ); trigger endon( "LISTEN_kill_tick_" + idx + "_" + entNum ); trigger notify( "LISTEN_kill_tick_" + idx + "_" + entNum + "_" + playerNum ); self endon( "disconnect" ); self endon( "game_ended" ); self endon( "death" ); self endon( "LISTEN_exit_dot_" + entNum ); entNum = undefined; playerNum = undefined; wait vars[ 0 ]; } startDOT_group( dots ) { AssertEx( IsDefined( dots ), "Must specify an array of dots to start" ); triggers = []; foreach ( dot in dots ) { // Spawn the appropriate dot.type aka trigger trigger = undefined; switch ( dot.type ) { case "trigger_radius": trigger = Spawn( "trigger_radius", dot.origin, dot.spawnflags, dot.radius, dot.height ); AssertEx( IsDefined( trigger ), "Could not spawn a trigger, too many entities" ); trigger.minRadius = dot.minRadius; trigger.maxRadius = dot.maxRadius; trigger.ticks = dot.ticks; triggers[ triggers.size ] = trigger; break; default: AssertMsg( ".type for DOT is not supported" ); } // Link to parent if it was defined if ( IsDefined( dot.parent ) ) { trigger LinkTo( dot.parent ); dot.parent.dot = trigger; } ticks = trigger.ticks; // Initialize ticks foreach ( tick in ticks ) tick.startTime = GetTime(); foreach ( tick in ticks ) if ( !tick.delay ) tick.enable = 1; // Check if there is a tick on player foreach ( tick in ticks ) { if ( IsSubStr( tick.affected, "player" ) ) { trigger.onPlayer = 1; break; } } } // Populate a group list for each trigger. Exclude self from list foreach ( trigger in triggers ) { trigger.DOT_group = []; foreach ( _trigger in triggers ) { if ( trigger == _trigger ) continue; trigger.DOT_group[ trigger.DOT_group.size ] = _trigger; } } // Start DOT on player foreach ( trigger in triggers ) if ( trigger.onPlayer ) trigger thread startDOT_player(); // Monitor DOTs foreach ( trigger in triggers ) trigger thread monitorDOT(); } startDOT_player() { self thread triggerTouchThink( ::onEnterDOT_player, ::onExitDOT_player ); } // Check to see when we should delete a DOT monitorDOT() { startTime = GetTime(); for ( ; IsDefined( self ); wait 0.05 ) { foreach ( i, tick in self.ticks ) { if ( IsDefined( tick ) && GetTime() - startTime >= tick.duration * 1000 ) { entNum = self GetEntityNumber(); self notify( "LISTEN_kill_tick_" + i + "_" + entNum ); self.ticks[ i ] = undefined; } } if ( !self.ticks.size ) break; } if ( IsDefined( self ) ) { foreach ( tick in self.ticks ) self [[ tick.onDeathFunc ]](); self notify( "death" ); self Delete(); } } onEnterDOT_player( trigger ) { Assert( IsDefined( trigger ) ); entNum = trigger GetEntityNumber(); self notify( "LISTEN_enter_dot_" + entNum ); foreach ( i, tick in trigger.ticks ) if ( !tick.enable ) self thread doDOT_delayFunc( i, trigger, tick.delay, tick.onEnterFunc ); foreach ( i, tick in trigger.ticks ) if ( tick.enable && tick.affected == "player" ) self thread [[ tick.onEnterFunc ]]( i, trigger ); } onExitDOT_player( trigger ) { Assert( IsDefined( trigger ) ); entNum = trigger GetEntityNumber(); self notify( "LISTEN_exit_dot_" + entNum ); foreach ( i, tick in trigger.ticks ) if ( tick.enable && tick.affected == "player" ) self thread [[ tick.onExitFunc ]]( i, trigger ); } doDOT_delayFunc( idx, trigger, delay, func ) { Assert( IsDefined( trigger ) ); entNum = trigger GetEntityNumber(); playerNum = self GetEntityNumber(); trigger endon( "LISTEN_kill_tick_" + idx + "_" + entNum + "_" + playerNum ); self endon( "disconnect" ); self endon( "game_ended" ); self endon( "death" ); self notify( "LISTEN_exit_dot_" + entNum ); entNum = undefined; playerNum = undefined; wait delay; self thread [[ func ]]( idx, trigger ); } /* soundWatcher( entNum ) { Assert( IsDefined( entNum ) ); self waittill_any( "death", "LISTEN_exit_dot_ + entNum ); self StopLoopSound(); } */ // This is a predefined DOT function onEnterDOT_poisonDamagePlayer( idx, trigger ) { AssertEx( IsDefined( idx ), "Must specify an index for this function" ); AssertEx( IsDefined( trigger ), "trying to run tick function on DOT that has been removed or is undefined" ); entNum = trigger GetEntityNumber(); playerNum = self GetEntityNumber(); trigger endon( "death" ); trigger endon( "LISTEN_kill_tick_" + idx + "_" + entNum ); trigger endon( "LISTEN_kill_tick_" + idx + "_" + entNum + "_" + playerNum ); self endon( "disconnect" ); self endon( "game_ended" ); self endon( "death" ); self endon( "LISTEN_exit_dot_" + entNum ); // Setup vars to keep track of number times player has been damaged by the trigger // For now this damage function is almost the same as the maps\mp\_radiatoin::radiationEffect if ( !IsDefined( self.onEnterDOT_poisonDamageCount ) ) self.onEnterDOT_poisonDamageCount = []; if ( !IsDefined( self.onEnterDOT_poisonDamageCount[ idx ] ) ) self.onEnterDOT_poisonDamageCount[ idx ] = []; self.onEnterDOT_poisonDamageCount[ idx ][ entNum ] = 0; //self thread soundWatcher( entNum ); damageMultiplier = ter_op( isSP(), 1.5, 1 ); for ( ; IsDefined( trigger ) && IsDefined( trigger.ticks[ idx ] ); wait trigger.ticks[ idx ].interval ) { self.onEnterDOT_poisonDamageCount[ idx ][ entNum ]++; switch ( self.onEnterDOT_poisonDamageCount[ idx ][ entNum ] ) { case 1: self ViewKick( 1, self.origin ); break; case 3: self ShellShock( "mp_radiation_low", 4 ); //self ViewKick( 3, self.origin ); self doDOT_poisonDamage( trigger, damageMultiplier * 2 ); break; case 4: self ShellShock( "mp_radiation_med", 5 ); //self ViewKick( 15, self.origin ); self thread doDOT_poisonBlackout( idx, trigger ); self doDOT_poisonDamage( trigger, damageMultiplier * 2 ); break; case 6: self ShellShock( "mp_radiation_high", 5 ); //self ViewKick( 75, self.origin ); self doDOT_poisonDamage( trigger, damageMultiplier * 2 ); break; case 8: self ShellShock( "mp_radiation_high", 5 ); //self ViewKick( 127, self.origin ); self doDOT_poisonDamage( trigger, damageMultiplier * 500 ); break; } } } onExitDOT_poisonDamagePlayer( idx, trigger ) { AssertEx( IsDefined( idx ), "Must specify an index for this function" ); AssertEx( IsDefined( trigger ), "trying to run tick function on DOT that has been removed or is undefined" ); entNum = trigger GetEntityNumber(); playerNum = self GetEntityNumber(); overlays = self.onEnterDOT_poisonDamageOverlay; if ( IsDefined( overlays ) ) { foreach ( i, _ in overlays ) { if ( IsDefined( overlays[ i ] ) && IsDefined( overlays[ i ][ entNum ] ) ) { overlays[ i ][ entNum ] thread doDOT_fadeOutBlackOut( 0.1, 0 ); } } } trigger notify( "LISTEN_kill_tick_" + idx + "_" + entNum + "_" + playerNum ); } onDeathDOT_poisonDamagePlayer() { entNum = self GetEntityNumber(); foreach ( player in level.players ) { overlays = player.onEnterDOT_poisonDamageOverlay; if ( IsDefined( overlays ) ) { foreach ( i, _ in overlays ) { if ( IsDefined( overlays[ i ] ) && IsDefined( overlays[ i ][ entNum ] ) ) { overlays[ i ][ entNum ] thread doDOT_fadeOutBlackOutAndDestroy(); } } } } } doDOT_poisonDamage( trigger, damage ) { if ( isSP() ) {/* self doDamage( damage, // iDamage Integer specifying the amount of damage done self.origin, // vPoint The point the damage is from? self, // eAttacker The entity that is attacking. self // eInflictor The entity that causes the damage.( e.g. a turret ) );*/ } else { self thread [[ level.callbackPlayerDamage ]]( trigger, // eInflictor The entity that causes the damage.( e.g. a turret ) trigger, // eAttacker The entity that is attacking. damage, // iDamage Integer specifying the amount of damage done 0, // iDFlags Integer specifying flags that are to be applied to the damage "MOD_SUICIDE", // sMeansOfDeath Integer specifying the method of death "claymore_mp", // sWeapon The weapon number of the weapon used to inflict the damage trigger.origin, // vPoint The point the damage is from? ( 0,0,0 ) - trigger.origin,// vDir The direction of the damage "none", // sHitLoc The location of the hit 0 // psOffsetTime The time offset for the damage ); } } doDOT_poisonBlackout( idx, trigger ) { AssertEx( IsDefined( idx ), "Must specify an index for this function" ); AssertEx( IsDefined( trigger ), "trying to run tick function on DOT that has been removed or is undefined" ); entNum = trigger GetEntityNumber(); playerNum = self GetEntityNumber(); trigger endon( "death" ); trigger endon( "LISTEN_kill_tick_" + idx + "_" + entNum ); trigger endon( "LISTEN_kill_tick_" + idx + "_" + entNum + "_" + playerNum ); self endon( "disconnect" ); self endon( "game_ended" ); self endon( "death" ); self endon( "LISTEN_exit_dot_" + entNum ); if ( !IsDefined( self.onEnterDOT_poisonDamageOverlay ) ) self.onEnterDOT_poisonDamageOverlay = []; if ( !IsDefined( self.onEnterDOT_poisonDamageOverlay[ idx ] ) ) self.onEnterDOT_poisonDamageOverlay[ idx ] = []; if ( !IsDefined( self.onEnterDOT_poisonDamageOverlay[ idx ][ entNum ] ) ) { overlay = NewClientHudElem( self ); overlay.x = 0; overlay.y = 0; overlay.alignX = "left"; overlay.alignY = "top"; overlay.horzAlign = "fullscreen"; overlay.vertAlign = "fullscreen"; overlay.alpha = 0; overlay SetShader( "black", 640, 480 ); self.onEnterDOT_poisonDamageOverlay[ idx ][ entNum ] = overlay; } overlay = self.onEnterDOT_poisonDamageOverlay[ idx ][ entNum ]; min_length = 1; max_length = 2; min_alpha = .25; max_alpha = 1; min_percent = 5; max_percent = 100; fraction = 0; for ( ;; ) { while ( self.onEnterDOT_poisonDamageCount[ idx ][ entNum ] > 1 ) { percent_range = max_percent - min_percent; fraction = ( self.onEnterDOT_poisonDamageCount[ idx ][ entNum ] - min_percent ) / percent_range; if ( fraction < 0 ) fraction = 0; else if ( fraction > 1 ) fraction = 1; length_range = max_length - min_length; length = min_length + ( length_range * ( 1 - fraction ) ); alpha_range = max_alpha - min_alpha; alpha = min_alpha + ( alpha_range * fraction ); end_alpha = fraction * 0.5; if ( fraction == 1 ) break; duration = length / 2; overlay doDOT_fadeInBlackOut( duration, alpha ); overlay doDOT_fadeOutBlackOut( duration, end_alpha ); wait( fraction * 0.5 ); } if ( fraction == 1 ) break; if ( overlay.alpha != 0 ) overlay doDOT_fadeOutBlackOut( 1, 0 ); wait 0.05; } overlay doDOT_fadeInBlackOut( 2, 0 ); } doDOT_fadeInBlackOut( duration, alpha ) { self fadeOverTime( duration ); self.alpha = alpha; alpha = undefined; wait duration; } doDOT_fadeOutBlackOut( duration, alpha ) { self fadeOverTime( duration ); self.alpha = alpha; alpha = undefined; wait duration; } doDOT_fadeOutBlackOutAndDestroy( duration, alpha ) { self fadeOverTime( duration ); self.alpha = alpha; alpha = undefined; wait duration; self Destroy(); } triggerTouchThink( enterFunc, exitFunc ) { level endon( "game_ended" ); self endon ( "death" ); self.entNum = self GetEntityNumber(); for ( ; ; ) { self waittill( "trigger", player ); if ( !isPlayer( player ) && !isDefined( player.finished_spawning ) ) continue; if ( !isAlive( player ) ) continue; if ( !isDefined( player.touchTriggers[ self.entNum ] ) ) player thread playerTouchTriggerThink( self, enterFunc, exitFunc ); } } // This is modified ver of _dynamic_world::playerTouchTriggerThink playerTouchTriggerThink( trigger, enterFunc, exitFunc ) { trigger endon( "death" ); if ( !isPlayer( self ) ) self endon( "death" ); if ( !isSP() ) touchName = self.guid; // generate GUID else touchName = "player" + gettime(); // generate GUID trigger.touchList[ touchName ] = self; if ( isDefined( trigger.moveTracker ) ) self.moveTrackers++ ; trigger notify( "trigger_enter", self ); self notify( "trigger_enter", trigger ); // Check if player is already touching a trigger belonging to a DOT group doEnterExitFunc = true; foreach ( trig in trigger.DOT_group ) foreach ( _trig in self.touchTriggers ) if ( trig == _trig ) doEnterExitFunc = false; if ( doEnterExitFunc && IsDefined( enterFunc ) ) self thread [[ enterFunc ]]( trigger ); self.touchTriggers[ trigger.entNum ] = trigger; // Do some extra checks to see if the player is touching any other trigger in the same group while ( IsAlive( self ) && ( isSP() || !level.gameEnded ) ) { touchingTrigger = true; if ( self IsTouching( trigger ) ) wait 0.05; else { if ( !trigger.DOT_group.size ) touchingTrigger = false; foreach ( trig in trigger.DOT_group ) { if ( self IsTouching( trig ) ) { wait 0.05; break; } else { touchingTrigger = false; } } } if ( !touchingTrigger ) break; } // disconnected player will skip this code if ( isDefined( self ) ) { self.touchTriggers[ trigger.entNum ] = undefined; if ( isDefined( trigger.moveTracker ) ) self.moveTrackers -- ; self notify( "trigger_leave", trigger ); if ( doEnterExitFunc && IsDefined( exitFunc ) ) self thread [[ exitFunc ]]( trigger ); } if ( !isSP() && level.gameEnded ) return; trigger.touchList[ touchName ] = undefined; trigger notify( "trigger_leave", self ); if ( !anythingTouchingTrigger( trigger ) ) trigger notify( "trigger_empty" ); } anythingTouchingTrigger( trigger ) { return( trigger.touchList.size ); } get_precached_anim( animname ) { println( animname ); assertEX( isdefined( level._destructible_preanims ) && isdefined( level._destructible_preanims[ animname ] ),"Can't find destructible anim: "+animname+" check the Build Precache Scripts and Repackage Zone boxes In launcher when you compile your map. " ); return level._destructible_preanims[ animname ]; } get_precached_animtree( animname ) { println( animname ); AssertEX( Isdefined( level._destructible_preanimtree ) && Isdefined( level._destructible_preanimtree[ animname ] ),"Can't find destructible anim tree: "+animname+" check the Build Precache Scripts and Repackage Zone boxes In launcher when you compile your map. " ); return level._destructible_preanimtree[ animname ]; }