untyped global function ScriptMovers_Init global function ScriptedSwitchDeactivate global function ScriptToyChangeStatusLights global function SetSwitchUseFunc global function ScriptedRotatorRotate global function CodeCallback_PreBuildAINFile const FX_BARREL_EXPLOSION = $"P_spectre_suicide" const FX_GENERATOR_EXPLOSION = $"xo_exp_death" const FX_CONSOLE_EXPLOSION = $"xo_exp_death" const FX_PANEL_EXPLOSION = $"P_drone_exp_md" const FX_BARREL_FIRE_SMOKE = $"P_fire_small_FULL" const FX_GENERATOR_FIRE_SMOKE = $"P_fire_med_FULL" const FX_CONSOLE_FIRE_SMOKE = $"P_fire_med_FULL" const FX_PANEL_FIRE_SMOKE = $"P_fire_small_FULL" const SOUND_BARREL_EXPLODE = "corporate_spectre_death_explode" const SOUND_GENERATOR_EXPLODE = "Goblin_Dropship_Explode" const SOUND_PANEL_EXPLODE = "AngelCity_Scr_DroneExplodes" const SOUND_CONSOLE_EXPLODE = "corporate_spectre_death_explode" const FAN_PUSH_RAMP_TIME = 0.8 const FAN_DEFAULT_PUSH_ACCEL = 25000 // units/sec^2 const FAN_PUSH_ANTI_GRAVITY = 1000 // units/sec^2 const FAN_PUSH_DECAY_SIDE_VELOCITY = false const FAN_DEBUG = false struct { table switchCallbacks //array entsInWindTunnel } file void function ScriptMovers_Init() { AddSpawnCallbackEditorClass( "prop_dynamic", "script_door", ScriptedDoorInit ) AddSpawnCallbackEditorClass( "prop_dynamic", "script_switch", ScriptedSwitchInit ) AddSpawnCallbackEditorClass( "script_mover_lightweight", "script_rotator", ScriptedRotatorThink ) AddSpawnCallbackEditorClass( "script_mover_lightweight", "script_seesaw", SeeSawThink ) AddSpawnCallbackEditorClass( "prop_dynamic", "shootable_clasp", ClaspInit ) AddSpawnCallback_ScriptName( "FanPusher", FanPusherThink ) PrecacheParticleSystem( FX_BARREL_EXPLOSION ) PrecacheParticleSystem( FX_GENERATOR_EXPLOSION ) PrecacheParticleSystem( FX_PANEL_EXPLOSION ) PrecacheParticleSystem( FX_BARREL_FIRE_SMOKE ) PrecacheParticleSystem( FX_GENERATOR_FIRE_SMOKE ) PrecacheParticleSystem( FX_PANEL_FIRE_SMOKE ) PrecacheParticleSystem( FX_CONSOLE_EXPLOSION ) PrecacheParticleSystem( FX_CONSOLE_FIRE_SMOKE ) AddSpawnCallback( "script_mover", MoverInit ) AddSpawnCallback( "script_mover_lightweight", MoverInit ) RegisterSignal( "OpenDoor" ) RegisterSignal( "CloseDoor" ) RegisterSignal( "OnDeactivate") RegisterSignal( "OnActivate") RegisterSignal( "StopRotating" ) } void function FlagControlsDoor( entity door, string flag ) { EndSignal( door, "OnDestroy" ) while ( true ) { WaitSignal( level, flag ) if ( Flag( flag ) ) Signal( door, "OpenDoor" ) else Signal( door, "CloseDoor" ) } } void function TriggerControlsDoor( entity door, entity trigger ) { EndSignal( door, "OpenDoor" ) EndSignal( door, "OnDestroy" ) EndSignal( trigger, "OnDestroy" ) WaitSignal( trigger, "OnTrigger" ) Signal( door, "OpenDoor" ) } void function MotionActivatedDoor( entity door ) { bool doorOpen = false if ( door.HasKey( "startOpen" ) ) doorOpen = (door.kv.startOpen == "1") EndSignal( door, "OnDestroy" ) while ( true ) { if ( doorOpen ) { while ( ArrayEntityWithinDistance( GetPlayerArray(), door.GetOrigin(), 300 ) ) wait 0.2 Signal( door, "CloseDoor" ) } else { while ( !ArrayEntityWithinDistance( GetPlayerArray(), door.GetOrigin(), 200 ) ) wait 0.2 Signal( door, "OpenDoor" ) } doorOpen = !doorOpen wait 1 } } void function CodeCallback_PreBuildAINFile() { array doors = GetEntArrayByClass_Expensive( "prop_dynamic" ) foreach ( entity door in doors ) { if ( GetEditorClass( door ) != "script_door" ) continue door.SetBoneFollowersSolid( false ) array linkedEnts = door.GetLinkEntArray() foreach ( entity ent in linkedEnts ) { if ( ent.GetClassName() == "func_brush" ) { ent.NotSolid() break } } } } void function ScriptedDoorInit( entity door ) { #if DEV array validModels = [ $"models/door/door_imc_interior_03_128_animated.mdl", $"models/door/pod_door_Hangar_IMC_01_animated.mdl", $"models/door/door_512x512x16_elevatorstyle01_animated.mdl", $"models/door/door_256x256x8_elevatorstyle01_animated.mdl", $"models/door/door_128x104x8_rolldownstyle01_animated.mdl", $"models/door/door_256x256x8_rolldownstyle01_animated.mdl", $"models/door/door_256_02_beacon_metal_door_animated.mdl", $"models/door/door_beacon_core_animated.mdl", $"models/door/door_128x104x8_elevatorstyle01_animated.mdl" $"models/door/door_marvin_animated.mdl" ] Assert( validModels.contains( door.GetModelName() ), "Door model at " + door.GetOrigin() + " is invalid: " + door.GetModelName() ) #endif EndSignal( door, "OnDestroy" ) door.SetBlocksLOS( true ) bool doorOpen = false if ( door.HasKey( "startOpen" ) ) doorOpen = (door.kv.startOpen == "1") bool initializing = true if ( door.HasKey( "script_flag" ) ) { string flag = expect string( door.kv.script_flag ) FlagInit( flag ) if ( doorOpen ) FlagSet( flag ) thread FlagControlsDoor( door, flag ) } string flagToggle if ( door.HasKey( "scr_flagToggle" ) ) { flagToggle = expect string( door.kv.scr_flagToggle ) FlagInit( flagToggle ) } if ( door.HasKey( "motionActivated" ) && door.kv.motionActivated == "1" ) thread MotionActivatedDoor( door ) // The door can link to a func_brush that is the collision of the door. The collision will be enabled/disabled based on the door state entity clipBrush array linkedEnts = door.GetLinkEntArray() foreach ( entity ent in linkedEnts ) { if ( ent.GetClassName() == "func_brush" ) { clipBrush = ent break } } if ( IsValid( clipBrush ) ) { clipBrush.Hide() clipBrush.NotSolid() WaitFrame() } // A trigger_multiple can link to the door, causing the door to open. It will only open the door once. entity trigger array linkParents = door.GetLinkParentArray() foreach ( entity ent in linkParents ) { if ( ent.GetClassName() == "trigger_multiple" ) { trigger = ent break } } if ( IsValid( trigger ) ) thread TriggerControlsDoor( door, trigger ) while ( IsValid( door ) ) { // UPDATE THE DOOR STATE if ( doorOpen ) door.Anim_Play("open") else if ( !initializing ) door.Anim_Play("close") if ( IsValid( clipBrush ) ) { ToggleNPCPathsForEntity( clipBrush, doorOpen ) if ( doorOpen ) { clipBrush.Hide() clipBrush.NotSolid() } else { clipBrush.Show() clipBrush.Solid() } } else { // door must be setup with expensive bone_follower collision for this to work ToggleNPCPathsForEntity( door, doorOpen ) } if ( flagToggle != "" ) { if ( doorOpen ) FlagSet( flagToggle ) else FlagClear( flagToggle ) } initializing = false // WAIT FOR STATE CHANGE if ( doorOpen ) WaitSignal( door, "CloseDoor" ) else WaitSignal( door, "OpenDoor" ) CreateShakeRumbleOnly( door.GetOrigin(), 15, 150, 1 ) doorOpen = !doorOpen } } void function ScriptedSwitchInit( entity button ) { #if DEV array< asset > validModels = [ $"models/domestic/light_switch_touchscreen.mdl", $"models/props/pressure_plates/pressure_plate_titan_industrial_01.mdl", $"models/domestic/elevator_switch_01.mdl", $"models/beacon/crane_room_monitor_console.mdl", $"models/props/global_access_panel_button/global_access_panel_button_wall.mdl", $"models/props/global_access_panel_button/global_access_panel_button_console.mdl" ] Assert( validModels.contains( button.GetModelName() ) ) #endif bool usesSkins int activeSkinID int inactiveSkinID switch( button.GetModelName() ) { case $"models/props/global_access_panel_button/global_access_panel_button_wall.mdl": case $"models/props/global_access_panel_button/global_access_panel_button_console.mdl": usesSkins = true activeSkinID = 0 inactiveSkinID = 1 break case $"models/beacon/crane_room_monitor_console.mdl": usesSkins = true activeSkinID = 1 inactiveSkinID = 2 break default: usesSkins = false break } int contextId = 0 button.Highlight_SetFunctions( contextId, 0, true, HIGHLIGHT_OUTLINE_INTERACT_BUTTON, 1, 0, false ) button.Highlight_SetParam( contextId, 0, HIGHLIGHT_COLOR_INTERACT ) button.Highlight_SetCurrentContext( contextId ) EndSignal( button, "OnDestroy" ) EndSignal( button, "OnDeactivate" ) OnThreadEnd( function() : ( button ) { // If we haven't destroyed the button, it must be inactive if ( IsValid( button ) ) { //ScriptToyChangeStatusLights( button, $"runway_light_red" ) Entity_StopFXArray( button ) button.UnsetUsable() button.Highlight_HideInside( 1.0 ) button.Highlight_HideOutline( 1.0 ) } } ) bool buttonActivated = false bool buttonIsSingleUse = false bool initialized = false bool buttonIsUsable = false float multiUseDelay = 0.2 bool isPressurePlate = button.GetModelName() == $"models/props/pressure_plates/pressure_plate_titan_industrial_01.mdl" if ( isPressurePlate ) button.kv.solid = 0 //hack until we can figure out why collision on this model kills titans when embarked if ( button.HasKey( "singleUse" ) ) buttonIsSingleUse = (button.kv.singleUse == "1" ) if ( button.HasKey( "usable" ) ) buttonIsUsable = (button.kv.usable == "1" ) if ( button.HasKey( "multiUseDelay" ) ) { multiUseDelay = float(button.kv.multiUseDelay) if ( multiUseDelay > 0.0 ) Assert( !buttonIsSingleUse, "script_switch at " + button.GetOrigin() + "has multiUseDelay set and is single use" ) } string flagToggle if ( button.HasKey( "scr_flagToggle" ) ) { flagToggle = expect string( button.kv.scr_flagToggle ) FlagInit( flagToggle ) } string flagRequired if ( button.HasKey( "scr_flagRequired" ) ) { flagRequired = expect string( button.kv.scr_flagRequired ) FlagInit( flagRequired ) } string hintString_hold = "#HOLD_TO_USE_GENERIC" if ( button.HasKey( "hintString_hold" ) && button.kv.hintString_hold != "" ) hintString_hold = string( button.kv.hintString_hold ) string hintString_press = "#PRESS_TO_USE_GENERIC" if ( button.HasKey( "hintString_press" ) && button.kv.hintString_press != "" ) hintString_press = string( button.kv.hintString_press ) entity trigger = GetLinkedTrigger( button ) //need a trigger for pressure plate unless it's just for show if ( ( isPressurePlate ) && ( buttonIsUsable ) ) Assert( IsValid( trigger ), "script_switch pressure plate at " + button.GetOrigin() + " needs to link to a trigger_multiple" ) if ( isPressurePlate && buttonIsUsable ) { Assert( IsValid( trigger ), "pressure plate switch at " + button.GetOrigin() + " requires a triggerTarget to activate" ) Assert( trigger.GetClassName() == "trigger_multiple", "pressure plate switch at " + button.GetOrigin() + " requires a trigger_multiple to activate" ) Assert( trigger.kv.spawnflags == "3", "Trigger for pressure plate at " + button.GetOrigin() + " needs spawnflags set to 3" ) } if ( !isPressurePlate ) { button.SetUsable() button.SetUsableByGroup( "pilot" ) button.SetUsePrompts( hintString_hold, hintString_press ) button.Highlight_ShowInside( 1.0 ) button.Highlight_ShowOutline( 1.0 ) } var player //hack: have to use "var" when waiting on a usable signal or trigger bool buttonUsedOnce = false while ( IsValid( button ) ) { //------------------------- // UPDATE EFFECTS //------------------------- if ( buttonActivated ) { //ScriptToyChangeStatusLights( button, $"runway_light_red" ) Entity_StopFXArray( button ) if ( usesSkins ) button.SetSkin( inactiveSkinID ) } else { ScriptToyChangeStatusLights( button, $"runway_light_green" ) if ( usesSkins ) button.SetSkin( activeSkinID ) } if ( !buttonIsUsable ) break //exit loop if we just want the pretty lights, but no player usability if ( buttonIsSingleUse && buttonUsedOnce ) break //exit loop if this is a single use button if ( isPressurePlate ) { //------------------------- // WAIT FOR STATE CHANGE (PRESSURE PLATE) //------------------------- if ( buttonActivated ) waitthread PressurePlateWaitSignal( trigger, "OnEndTouchAll" ) else waitthread PressurePlateWaitSignal( trigger, "OnTrigger" ) } else { if ( flagRequired != "" && !Flag( flagRequired ) ) { if ( !isPressurePlate ) { if ( button.HasKey( "disabledHintString" ) ) button.SetUsePrompts( button.kv.disabledHintString, button.kv.disabledHintString ) else button.UnsetUsable() } //ScriptToyChangeStatusLights( button, $"runway_light_red" ) Entity_StopFXArray( button ) if ( usesSkins ) button.SetSkin( inactiveSkinID ) FlagWait( flagRequired ) ScriptToyChangeStatusLights( button, $"runway_light_green" ) if ( usesSkins ) button.SetSkin( activeSkinID ) button.SetUsePrompts( hintString_hold , hintString_press ) button.SetUsable() } //------------------------- // WAIT FOR STATE CHANGE (SIMPLE PUSH BUTTON) //------------------------- if ( buttonActivated ) { wait multiUseDelay } else { player = button.WaitSignal( "OnPlayerUse" ).player if ( !IsValid( player ) ) continue if ( !player.IsPlayer() ) continue } } //-------------------------------------- // Player activated, switch button state //-------------------------------------- buttonUsedOnce = true buttonActivated = !buttonActivated EmitSoundOnEntity( button, "Switch_Activate" ) button.Signal( "OnActivate" ) if ( !isPressurePlate && buttonActivated ) { button.UnsetUsable() //make the button unusable right after clicking so player doesn't double hit it button.Highlight_HideInside( 1.0 ) button.Highlight_HideOutline( 1.0 ) // Run callbacks if ( button in file.switchCallbacks ) { foreach( table callbackTable in file.switchCallbacks[ button ] ) { if ( callbackTable.useEnt == null ) callbackTable.useFunc( button, player ) else callbackTable.useFunc( button, player, callbackTable.useEnt ) } } } //------------ // SET FLAGS //------------ // Button activated (green) if ( flagToggle != "" && buttonActivated ) { FlagSet( flagToggle ) } if ( buttonActivated ) SpawnSpawnersLinkedToButton( button, expect entity( player ) ) //else if ( buttonActivated && isPressurePlate ) // wait 1.5 //wait a bit before re-enabling the usability // Button deactivated (red) if ( flagToggle != "" && !buttonActivated ) FlagClear( flagToggle ) if ( !buttonActivated ) { button.SetUsable() button.Highlight_ShowInside( 1.0 ) button.Highlight_ShowOutline( 1.0 ) } } if ( ( isPressurePlate ) && ( IsValid( trigger ) ) ) trigger.Destroy() else { button.UnsetUsable() button.Highlight_HideInside( 1.0 ) button.Highlight_HideOutline( 1.0 ) } } void function SpawnSpawnersLinkedToButton( entity button, entity activator ) { foreach ( entity linkedEnt in button.GetLinkEntArray() ) { if ( IsStalkerRack( linkedEnt ) ) { thread SpawnFromStalkerRack( linkedEnt, activator ) } else if ( IsSpawner( linkedEnt ) ) { entity spawned = linkedEnt.SpawnEntity() DispatchSpawn( spawned ) } else { Signal( linkedEnt, "OpenDoor" ) } } } void function ScriptedSwitchDeactivate( entity button ) { Assert( IsValid( button ) ) button.Signal( "OnDeactivate" ) } void function ScriptToyChangeStatusLights( entity button, asset fxName ) { //-------------------------- // Kill any previous effects //-------------------------- Entity_StopFXArray( button ) //-------------------------- // Start new effects at tags //-------------------------- array newFxLights array fxTags entity newFx int index = 0 string tagName while (true) { tagName = "light" + index local id = button.LookupAttachment( tagName ) if ( id == 0 ) break newFx = PlayLoopFXOnEntity( fxName, button, tagName ) newFxLights.append( newFx ) index++ } button.e.fxArray = newFxLights } void function PressurePlateWaitSignal( entity trigger, string waitSignal ) { //waitSignal is either "OnTrigger" or "OnEndTouchAll" trigger.EndSignal( "OnDestroy" ) var result //hack. Result info from triggers while ( IsValid( trigger ) ) { result = trigger.WaitSignal( waitSignal ) if ( !IsValid( result.activator ) ) continue if ( !result.activator.IsTitan() ) continue if ( ( result.activator.IsPlayer() ) || ( IsPetTitan( result.activator ) ) ) break } } void function ScriptedRotatorThink( entity rotator ) { rotator.Hide() if ( rotator.HasKey( "use_local_rotation" ) && rotator.kv.use_local_rotation == "1" ) rotator.NonPhysicsSetRotateModeLocal( true ) EndSignal( rotator, "OnDestroy" ) vector baseAngles = rotator.GetAngles() // Linked entities get parented array linkedEnts = rotator.GetLinkEntArray() foreach ( entity ent in linkedEnts ) { ent.SetParent( rotator, "", true ) } if ( rotator.HasKey( "player_collides" ) && rotator.kv.player_collides == "1" ) rotator.SetPusher( true ) if ( rotator.HasKey( "change_navmesh" ) && rotator.kv.change_navmesh == "1" ) rotator.ChangeNPCPathsOnMove( true ) // script will custom rotate this one if ( rotator.HasKey( "scripted_rotator" ) && expect string( rotator.kv.scripted_rotator ) == "true" ) return if ( rotator.HasKey( "script_flag" ) ) { string flag = expect string( rotator.kv.script_flag ) FlagInit( flag ) while ( true ) { bool returnToBaseAngle = false if ( rotator.HasKey( "flag_clear_resets" ) ) returnToBaseAngle = rotator.kv.flag_clear_resets == "1" FlagWait( flag ) thread ScriptedRotatorRotate( baseAngles, rotator ) FlagWaitClear( flag ) Signal( rotator, "StopRotating" ) // Return when the flag is cleared if ( returnToBaseAngle ) { if ( !IsValid( rotator ) ) return float rotateTime = 1.0 if ( rotator.HasKey( "rotate_to_time" ) ) rotateTime = float( rotator.kv.rotate_to_time ) float easeTime = 0.0 if ( rotator.HasKey( "rotate_to_ease" ) && rotator.kv.rotate_to_ease == "1" ) easeTime = rotateTime * 0.33 rotator.NonPhysicsRotateTo( baseAngles, rotateTime, easeTime, easeTime ) } } } else { thread ScriptedRotatorRotate( baseAngles, rotator ) } } vector function GetRotationVector( entity rotator ) { string axis if ( rotator.HasKey( "rotation_axis" ) ) axis = expect string( rotator.kv.rotation_axis ) vector angles = rotator.GetAngles() switch ( axis ) { case "pitch": return AnglesToRight( angles ) case "yaw": return AnglesToUp( angles ) case "roll": default: return AnglesToForward( angles ) } unreachable } void function ScriptedRotatorRotate( vector baseAngles, entity rotator ) { Signal( rotator, "StopRotating" ) EndSignal( rotator, "OnDestroy" ) EndSignal( rotator, "StopRotating" ) OnThreadEnd( function() : ( rotator ) { if ( IsValid( rotator ) ) rotator.NonPhysicsRotate( Vector( 0, 0, 0), 0 ) } ) if ( rotator.HasKey( "start_delay" ) ) { float delay = float( rotator.kv.start_delay ) if ( delay > 0 ) wait delay } if ( rotator.kv.rotate_forever_speed != "0" ) { // Rotate forever float speed = float( rotator.kv.rotate_forever_speed ) Assert( speed != 0.0 ) vector rotateVec = GetRotationVector( rotator ) rotator.NonPhysicsRotate( rotateVec, speed ) WaitForever() } else { // Rotate specified amount Assert( rotator.HasKey( "rotate_to_degrees" ) ) Assert( rotator.HasKey( "rotate_to_time" ) ) string soundEffect = "" if ( rotator.HasKey( "script_sound" ) ) soundEffect = string( rotator.kv.script_sound ) float rotateTime = float( rotator.kv.rotate_to_time ) if ( rotateTime > 0.0 ) { vector rotateAngles = AnglesCompose( baseAngles, Vector( 0.0, 0.0, float( rotator.kv.rotate_to_degrees ) ) ) float easeTime = 0.0 if ( rotator.HasKey( "rotate_to_ease" ) && rotator.kv.rotate_to_ease == "1" ) easeTime = rotateTime * 0.33 while ( true ) { // Rotate to the goal angle rotator.NonPhysicsRotateTo( rotateAngles, rotateTime, easeTime, easeTime ) if ( soundEffect != "" ) EmitSoundOnEntity( rotator, soundEffect ) wait rotateTime // Rotate back to base angle if specified if ( !rotator.HasKey( "rotate_to_return_delay" ) || rotator.kv.rotate_to_return_delay == "-1" ) return Assert( float( rotator.kv.rotate_to_return_delay ) >= 0.0 ) wait float( rotator.kv.rotate_to_return_delay ) rotator.NonPhysicsRotateTo( baseAngles, rotateTime, easeTime, easeTime ) if ( soundEffect != "" ) EmitSoundOnEntity( rotator, soundEffect ) wait rotateTime // Wait a delay and repeat the rotation if specified if ( !rotator.HasKey( "rotate_to_loop_time" ) || rotator.kv.rotate_to_loop_time == "-1" ) return Assert( float( rotator.kv.rotate_to_loop_time ) >= 0.0 ) wait float( rotator.kv.rotate_to_loop_time ) } } } } void function MoverInit( entity mover ) { if ( !mover.HasKey( "leveledplaced" ) || mover.kv.leveledplaced != "1" ) return // Linked entities get parented if ( mover.HasKey( "parent_linked_ents" ) && mover.kv.parent_linked_ents == "1" ) { array linkedEnts = mover.GetLinkEntArray() foreach( entity ent in linkedEnts ) { if ( GetEditorClass( ent ) != "script_mover_path" ) ent.SetParent( mover, "", true ) } } if ( mover.HasKey( "player_collides" ) && mover.kv.player_collides == "1" ) mover.SetPusher( true ) if ( mover.HasKey( "change_navmesh" ) && mover.kv.change_navmesh == "1" ) mover.ChangeNPCPathsOnMove( true ) thread MoverThink( mover ) } void function MoverThink( entity mover ) { EndSignal( mover, "OnDestroy" ) if ( mover.GetModelName() == $"models/dev/editor_ref.mdl" ) mover.Hide() array pathNodes = GetNextMoverPathNodes( mover ) // Go down the path gathering the nodes and init any flags foreach( entity node in pathNodes ) InitMoverNodeFlagsAndErrorCheck( node ) Assert( mover.HasKey( "path_speed") && float( mover.kv.path_speed ) >= 0.0, "script_mover doesnt have a valid path speed" ) float pathSpeed = float( mover.kv.path_speed ) bool easeIn bool easeOut string startFlag = "" if ( mover.HasKey( "script_flag" ) && mover.kv.script_flag != "" ) { startFlag = mover.GetValueForKey( "script_flag" ) FlagInit( startFlag ) } if ( mover.HasKey( "dangerous_area_radius" ) ) mover.AllowNPCGroundEnt( false ) if ( pathNodes.len() == 0 ) return if ( startFlag != "" ) FlagWait( startFlag ) if ( mover.HasKey( "start_delay" ) && float( mover.kv.start_delay ) > 0.0 ) wait float( mover.kv.start_delay ) entity pathNode = pathNodes.getrandom() entity lastNode easeOut = pathNode.HasKey( "ease_from_node" ) && pathNode.GetValueForKey( "ease_from_node" ) == "1" bool isMoving = false while( IsValid( pathNode ) ) { bool teleport = false if ( pathNode.HasKey( "teleport_to_node" ) ) teleport = pathNode.GetValueForKey( "teleport_to_node" ) == "1" bool perfectRotation = false if ( IsValid( lastNode ) && lastNode.HasKey( "perfect_circular_rotation" ) ) perfectRotation = lastNode.GetValueForKey( "perfect_circular_rotation" ) == "1" float rotationTime = 0.0 if ( IsValid( lastNode ) && lastNode.HasKey( "circular_rotation_time" ) ) rotationTime = float( lastNode.GetValueForKey( "circular_rotation_time" ) ) float dist = Distance( pathNode.GetOrigin(), mover.GetOrigin() ) if ( !isMoving ) MoverPath_StartSound( mover, pathNode ) if ( dist > 0.0 && !teleport ) { easeIn = pathNode.HasKey( "ease_to_node" ) && pathNode.GetValueForKey( "ease_to_node" ) == "1" float moveTime = dist / pathSpeed float easeLeaving = easeOut ? moveTime * 0.5 : 0.0 float easeArriving = easeIn ? moveTime * 0.5 : 0.0 float angleChange = IsValid( lastNode ) ? MoverPath_GetAngleChange( lastNode, pathNode ) : 0.0 if ( perfectRotation && angleChange != 0 ) { string rotationSoundEvent = "" if ( mover.HasKey( "sound_circular_rotation" ) ) rotationSoundEvent = mover.GetValueForKey( "sound_circular_rotation" ) if ( rotationSoundEvent != "" ) EmitSoundOnEntity( mover, rotationSoundEvent ) vector turnAnchorPos = MoverPath_GetAngleAnchor( lastNode, pathNode ) // Create a new mover because as far as I know I can't get all the children of the mover and clearparent and reparent. entity curveMover = CreateScriptMover( turnAnchorPos, lastNode.GetAngles() ) curveMover.SetPusher( mover.GetPusher() ) mover.SetParent( curveMover, "", true ) // Find the circumference of the turn so we can calculate the rotation time based on the distance traveled around the bend float c = 2 * PI * Length(turnAnchorPos - lastNode.GetOrigin()) float frac = fabs(angleChange) / 360.0 moveTime = (c * frac) / pathSpeed isMoving = true curveMover.NonPhysicsRotateTo( pathNode.GetAngles(), moveTime, 0.0, 0.0 ) wait moveTime - 0.01 mover.ClearParent() curveMover.Destroy() if ( rotationSoundEvent != "" ) StopSoundOnEntity( mover, rotationSoundEvent ) } else { // Linear move/rotate isMoving = true if ( mover.HasKey( "dangerous_area_radius" ) ) thread CreateMoverDangrousAreas( mover, mover.GetOrigin(), pathNode.GetOrigin(), float( mover.GetValueForKey( "dangerous_area_radius" ) ), moveTime ) mover.NonPhysicsMoveTo( pathNode.GetOrigin(), moveTime, easeLeaving, easeArriving ) mover.NonPhysicsRotateTo( pathNode.GetAngles(), moveTime, easeLeaving, easeArriving ) wait moveTime - 0.01 } } else if ( dist == 0.0 && !teleport && rotationTime > 0.0 ) { // Rotation in place string rotationSoundEvent = "" if ( mover.HasKey( "sound_rotation" ) ) rotationSoundEvent = mover.GetValueForKey( "sound_rotation" ) if ( rotationSoundEvent != "" ) EmitSoundOnEntity( mover, rotationSoundEvent ) isMoving = false MoverPath_StopMoveSound( mover ) MoverPath_StopSound( mover, pathNode ) float easeIn = easeOut ? rotationTime * 0.5 : 0.0 float easeOut = easeIn ? rotationTime * 0.5 : 0.0 mover.NonPhysicsRotateTo( pathNode.GetAngles(), rotationTime, easeIn, easeOut ) wait rotationTime - 0.01 if ( rotationSoundEvent != "" ) StopSoundOnEntity( mover, rotationSoundEvent ) } else { mover.SetOrigin( pathNode.GetOrigin() ) mover.SetAngles( pathNode.GetAngles() ) } easeOut = pathNode.HasKey( "ease_from_node" ) && pathNode.GetValueForKey( "ease_from_node" ) == "1" if ( pathNode.HasKey( "scr_flag_set" ) ) FlagSet( pathNode.GetValueForKey( "scr_flag_set" ) ) if ( pathNode.HasKey( "scr_flag_clear" ) ) FlagClear( pathNode.GetValueForKey( "scr_flag_clear" ) ) if ( pathNode.HasKey( "scr_flag_wait" ) ) { string flag = pathNode.GetValueForKey( "scr_flag_wait" ) if ( !Flag( flag ) ) { isMoving = false MoverPath_StopMoveSound( mover ) MoverPath_StopSound( mover, pathNode ) FlagWait( flag ) } } if ( pathNode.HasKey( "scr_flag_wait_clear" ) ) { string flag = pathNode.GetValueForKey( "scr_flag_wait_clear" ) if ( Flag( flag ) ) { isMoving = false MoverPath_StopMoveSound( mover ) MoverPath_StopSound( mover, pathNode ) FlagWaitClear( flag ) } } if ( pathNode.HasKey( "path_wait" ) ) { float time = float( pathNode.GetValueForKey( "path_wait" ) ) if ( time > 0.0 ) { isMoving = false MoverPath_StopMoveSound( mover ) MoverPath_StopSound( mover, pathNode ) wait time } } pathNodes = GetNextMoverPathNodes( pathNode ) if ( pathNodes.len() == 0 ) { MoverPath_StopMoveSound( mover ) MoverPath_StopSound( mover, pathNode ) break } // Update speed based on the node if ( pathNode.HasKey( "path_speed" ) ) pathSpeed = float( pathNode.GetValueForKey( "path_speed" ) ) lastNode = pathNode pathNode = pathNodes.getrandom() } } void function CreateMoverDangrousAreas( entity mover, vector start, vector end, float radius, float duration ) { float d = Distance( start, end ) float spacing = radius * 1.5 int numDangerousSpots = int( ceil( d / spacing ) ) vector direction = Normalize( end - start ) vector pos for ( int i = 0 ; i < numDangerousSpots ; i++ ) { pos = start + ( direction * spacing * i ) thread CreateMoverDangrousAreaUntilMoverPasses( mover, start, end, pos, radius, duration ) } } void function CreateMoverDangrousAreaUntilMoverPasses( entity mover, vector start, vector end, vector pos, float radius, float maxDuration ) { // Create entity to link it to (lifetime) entity lifetimeEnt = CreateScriptRef( pos ) // Create the dangerous area AI_CreateDangerousArea_Static( lifetimeEnt, null, radius, TEAM_INVALID, true, true, pos ) // Wait for mover to go past the dangerous area, or timeout float endTime = Time() + maxDuration while( Time() <= endTime ) { if ( DotProduct( end - start, pos - mover.GetOrigin() ) < 0 ) break WaitFrame() } lifetimeEnt.Destroy() } void function MoverPath_StopMoveSound( entity mover ) { // Stops any move sounds playing on the mover // Stop playing a sound on the mover if one is specified & it is set to do so if ( mover.HasKey( "sound_move" ) && mover.kv.sound_move != "" ) { // "sound_move" sound continues to play after moving unless this is checked" if ( mover.HasKey( "stop_sound_move_on_stop" ) && mover.GetValueForKey( "stop_sound_move_on_stop" ) == "1" ) StopSoundOnEntity( mover, string( mover.kv.sound_move ) ) } } void function MoverPath_StopSound( entity mover, entity node ) { // Play sound on the node if one is specified if ( node.HasKey( "sound_stop_move" ) && node.kv.sound_stop_move != "" ) EmitSoundAtPosition( TEAM_UNASSIGNED, node.GetOrigin(), string( node.kv.sound_stop_move ) ) // Play sound on the mover if one is specified if ( mover.HasKey( "sound_stop_move" ) && mover.kv.sound_stop_move != "" ) EmitSoundOnEntity( mover, string( mover.kv.sound_stop_move ) ) } void function MoverPath_StartSound( entity mover, entity node ) { // Play sound on the node if one is specified if ( node.HasKey( "sound_start_move" ) && node.kv.sound_start_move != "" ) EmitSoundAtPosition( TEAM_UNASSIGNED, node.GetOrigin(), string( node.kv.sound_start_move ) ) // Play sound on mover if one is specified if ( mover.HasKey( "sound_move" ) && mover.kv.sound_move != "" ) EmitSoundOnEntity( mover, string( mover.kv.sound_move ) ) } float function MoverPath_GetAngleChange( entity node1, entity node2 ) { vector vec1 = node1.GetForwardVector() vector vec2 = node2.GetForwardVector() float angle = acos( DotProduct( vec1, vec2 ) ) * 180 / PI return angle } vector function MoverPath_GetAngleAnchor( entity node1, entity node2 ) { vector node1Origin = node1.GetOrigin() vector node2Origin = node2.GetOrigin() vector node1Angles = node1.GetAngles() vector node2Angles = node2.GetAngles() vector node1SideVec vector node2SideVec if ( node1Origin.z != node2Origin.z ) { // vertical turn node1SideVec = AnglesToUp( node1Angles ) node2SideVec = AnglesToUp( node2Angles ) } else { // horizontal turn node1SideVec = AnglesToRight( node1Angles ) node2SideVec = AnglesToRight( node2Angles ) } float angleChange = MoverPath_GetAngleChange( node1, node2 ) Assert( angleChange != 0 ) if ( angleChange > 0 ) { node1SideVec *= -1 node2SideVec *= -1 } float d = Distance( node1Origin, node2Origin ) vector intersect = GetClosestPointToLineSegments( node1Origin, node1Origin + node1SideVec * d, node2Origin, node2Origin + node2SideVec * d ) //DebugDrawLine( intersect, node1Origin, 255, 255, 0, true, 5.0 ) //DebugDrawLine( intersect, node2Origin, 0, 255, 255, true, 5.0 ) //DebugDrawLine( node1Origin, node1Origin + node1SideVec * 250, 100, 100, 100, true, 5.0 ) //DebugDrawLine( node2Origin, node2Origin + node2SideVec * 250, 100, 100, 100, true, 5.0 ) //DebugDrawLine( intersect, intersect - <0,0,128>, 100, 0, 0, true, 5.0 ) //DebugDrawText( intersect, angleChange.tostring(), true, 5.0 ) return intersect } array function GetNextMoverPathNodes( entity node, bool errorChecking = false ) { array nodes array linkedEnts = node.GetLinkEntArray() foreach( entity ent in linkedEnts ) { if ( GetEditorClass( ent ) == "script_mover_path" ) { if ( !errorChecking && ent.HasKey( "switchtrack_flag" ) && !Flag( ent.GetValueForKey( "switchtrack_flag" ) ) ) continue nodes.append( ent ) } } return nodes } void function InitMoverNodeFlagsAndErrorCheck( entity node ) { if ( node.e.moverPathPrecached ) return if ( node.HasKey( "path_speed" ) ) Assert( float( node.kv.path_speed ) > 0.0, "Node path_speed at " + node.GetOrigin() + " must be greater than 0." ) if ( node.HasKey( "path_wait" ) ) Assert( float( node.kv.path_wait ) >= 0.0, "Node path_wait at " + node.GetOrigin() + " must be greater than 0." ) if ( node.HasKey( "teleport_to_node" ) && node.kv.teleport_to_node == "1" ) { if ( node.HasKey( "ease_to_node" ) ) Assert( node.kv.ease_to_node == "0", "Node at " + node.GetOrigin() + " cant have both teleport_to_node and ease_to_node checked." ) } if ( node.HasKey( "scr_flag_set" ) ) FlagInit( node.GetValueForKey( "scr_flag_set" ) ) if ( node.HasKey( "scr_flag_clear" ) ) FlagInit( node.GetValueForKey( "scr_flag_clear" ) ) if ( node.HasKey( "scr_flag_wait" ) ) FlagInit( node.GetValueForKey( "scr_flag_wait" ) ) if ( node.HasKey( "switchtrack_flag" ) ) FlagInit( node.GetValueForKey( "switchtrack_flag" ), true ) node.e.moverPathPrecached = true array pathNodes = GetNextMoverPathNodes( node, true ) foreach( entity node in pathNodes ) InitMoverNodeFlagsAndErrorCheck( node ) } entity function GetLinkedTrigger( entity ent ) { array linkedEnts = ent.GetLinkEntArray() foreach ( entity ent in linkedEnts ) { if ( ent.GetClassName() == "trigger_multiple" ) return ent } return null } struct SeeSawThinkStruct // struct that is internal to seeSaw think logic { float speed bool touching bool wasTouched vector startAngles float oldSpeed bool playerIgnore float maxSpeed = 10 float acceleration = 0.425 } void function SeeSawThink( entity seeSaw ) { seeSaw.Hide() seeSaw.EndSignal( "OnDestroy" ) // seeSaw.NonPhysicsSetRotateModeLocal( true ) seeSaw.SetPusher( true ) array parents = seeSaw.GetLinkParentArray() array brushes foreach ( ent in parents ) { if ( ent.GetClassName() == "func_brush" ) brushes.append( ent ) } float minz = 0 float maxz = 0 foreach ( brush in brushes ) { vector mins = brush.GetBoundingMins() vector maxs = brush.GetBoundingMaxs() if ( mins.z < minz ) minz = mins.z if ( maxs.z > maxz ) maxz = maxs.z } float height = fabs( minz ) + maxz entity trigger = seeSaw.GetLinkEnt() trigger.EndSignal( "OnDestroy" ) SeeSawThinkStruct e e.startAngles = seeSaw.GetAngles() if ( seeSaw.HasKey( "script_start_moving" ) && int( seeSaw.kv.script_start_moving ) > 0 ) { e.wasTouched = true e.speed = 8 } thread SeeSawSpeedThink( seeSaw, e ) if ( seeSaw.HasKey( "script_player_ignore" ) && int( seeSaw.kv.script_player_ignore ) > 0 ) { e.playerIgnore = true } else { thread SeeSawTriggerThink( seeSaw, trigger, height, e ) } } void function SeeSawTriggerThink( entity seeSaw, entity trigger, float height, SeeSawThinkStruct e ) { for ( ;; ) { e.touching = false table results = trigger.WaitSignal( "OnTrigger" ) entity player = expect entity( results.activator ) if ( !IsAlive( player ) ) continue while ( trigger.IsTouching( player ) ) { PlayerNearSeeSaw( player, seeSaw, height, e ) WaitFrame() } } } float ornull function SeeSawPitchLimitOverride() { // return 60.0 return null } void function SeeSawSpeedThink( entity seeSaw, SeeSawThinkStruct e ) { seeSaw.EndSignal( "OnDestroy" ) float pitchLimit = 50 // 70.75 if ( seeSaw.HasKey( "script_pitch_limit" ) ) pitchLimit = float( seeSaw.kv.script_pitch_limit ) vector angles = seeSaw.GetAngles() vector forward = AnglesToRight( angles ) * -1 vector rotateDir = forward // < -1,0,0 > for ( ;; ) { WaitFrame() SeeSawSpeedThink_internal( seeSaw, e, rotateDir, pitchLimit ) } } void function SeeSawSpeedThink_internal( entity seeSaw, SeeSawThinkStruct e, vector rotateDir, float pitchLimit ) { float ornull pitchLimitOverride = SeeSawPitchLimitOverride() if ( pitchLimitOverride != null ) { pitchLimit = expect float( pitchLimitOverride ) } vector localAngles = seeSaw.GetLocalAngles() if ( ( !e.touching && e.wasTouched ) || e.playerIgnore ) { vector startForward = AnglesToForward( e.startAngles ) vector startUp = AnglesToUp( e.startAngles ) vector seeSawForward = AnglesToForward( seeSaw.GetAngles() ) // if ( ge(106)==seeSaw) // { // printt( "dot is " + DotProduct( startForward, seeSawForward ) + " speed " + e.speed ) // } //printt( "Dot " + DotProduct( startForward, seeSawForward ) ) //printt( "Dot " + //printt( DotProduct( startUp, seeSawForward ) ) // return to normal //printt( "start angles " + e.startAngles + " current angles " + seeSaw.GetAngles() ) if ( fabs( DotProduct( startForward, seeSawForward ) ) < 0.75 ) { if ( DotProduct( startUp, seeSawForward ) > 0 ) { if ( e.speed < e.maxSpeed ) e.speed += e.acceleration } else { if ( e.speed > -e.maxSpeed ) e.speed -= e.acceleration } } //DebugDrawText( seeSaw.GetOrigin(), "" + e.speed, true, 1 ) //DebugDrawLine( seeSaw.GetOrigin(), GetPlayerArray()[0].GetOrigin(), 255, 0, 0, true, 0.2 ) } // if ( ge(117) == seeSaw ) // return if ( e.speed < 0 ) { if ( localAngles.x < -pitchLimit ) { seeSaw.NonPhysicsRotate( rotateDir, 0 ) e.speed = 0 return } } else { if ( localAngles.x > pitchLimit ) { seeSaw.NonPhysicsRotate( rotateDir, 0 ) e.speed = 0 return } } if ( e.oldSpeed != e.speed || pitchLimitOverride != null ) { seeSaw.NonPhysicsRotate( rotateDir, e.speed ) e.oldSpeed = e.speed } } void function PlayerNearSeeSaw( entity player, entity seeSaw, float height, SeeSawThinkStruct e ) { vector playerOrigin = player.GetOrigin() vector seeSawOrigin = seeSaw.GetOrigin() //DebugDrawLine( playerOrigin, seeSawOrigin, 255, 0, 0, true, 0.2 ) vector originDif = playerOrigin - seeSawOrigin vector difNormal = Normalize( originDif ) vector seeSawAngles = seeSaw.GetAngles() vector seeSawUp = AnglesToUp( seeSawAngles ) bool onTop = DotProduct( difNormal, seeSawUp ) > 0 // may need to do something special for hands holding on if ( !onTop ) { e.touching = false return } float amountAbove = DotProduct( originDif, seeSawUp ) amountAbove -= height amountAbove += 2.5 // player is in the ground? // if ( ge(117) == seeSaw ) // { // printt( "amountAbove " + amountAbove ) // } if ( amountAbove < 0 || amountAbove > 15 ) { e.touching = false return } vector seeSawForward = AnglesToForward( seeSawAngles ) float amountForward = DotProduct( originDif, seeSawForward ) e.speed += Graph( amountForward, 0, 1000, 0, 2 ) float maxSpeed = 10 e.speed = min( maxSpeed, e.speed ) e.speed = max( -maxSpeed, e.speed ) e.touching = true e.wasTouched = true } void function ClaspInit(entity clasp) { //printt( "" ) //printt( "INIT SHOOTABLE CLASP" ) //printt( "" ) if ( clasp.HasKey( "scr_flag_set" ) ) FlagInit( clasp.GetValueForKey( "scr_flag_set" ) ) AddEntityCallback_OnDamaged( clasp, OnClaspDamaged ) } void function OnClaspDamaged( entity clasp, var damageInfo) { //printt( "CLASP HAS TAKEN DAMAGE" ) //if the attacker is not valid entity attacker = DamageInfo_GetAttacker( damageInfo ) if ( !IsValid( attacker ) || !attacker.IsPlayer() ) return if ( clasp.HasKey( "scr_flag_set") ) { //printt( clasp ) //printt( clasp.GetEncodedEHandle() ) //printt( clasp.GetTargetName() ) //printt( clasp.GetValueForKey( "scr_flag_set" ) ) FlagSet( clasp.GetValueForKey( "scr_flag_set" ) ) } //printt( "" ) //printt( "DESTROYING CLASP" ) //printt( "" ) //Destroy the clasp clasp.Destroy() } function SetSwitchUseFunc( button, func, ent = null ) { local Table = InitControlPanelUseFuncTable() Table.useFunc <- func Table.useEnt <- ent if ( !( button in file.switchCallbacks ) ) file.switchCallbacks[ button ] <- [] file.switchCallbacks[ button ].append( Table ) } void function FanPusherThink( entity fanPusher ) { float fanPushDist = 3000 if ( fanPusher.HasKey( "height" ) ) fanPushDist = float( fanPusher.kv.height ) if ( fanPusher.HasKey( "script_gravityscale" ) && fanPusher.kv.script_gravityscale != "" ) { fanPushDist *= string( fanPusher.kv.script_gravityscale ).tofloat() } else { float gravityScale = expect float( GetPlayerSettingsFieldForClassName( DEFAULT_PILOT_SETTINGS, "gravityscale" ) ) fanPushDist *= gravityScale // adjusted for new gravity scale } float radius = float( fanPusher.kv.script_radius ) vector forward = AnglesToForward( fanPusher.GetAngles() ) vector cylinderBottom = fanPusher.GetOrigin() vector cylinderTop = cylinderBottom + ( forward * fanPushDist ) if ( FAN_DEBUG ) DebugDrawCylinder( fanPusher.GetOrigin(), fanPusher.GetAngles(), radius, fanPushDist, 100, 0, 0, true, 120.0 ) string flag = "" if ( fanPusher.HasKey( "script_flag" ) ) { flag = string( fanPusher.kv.script_flag ) FlagInit( flag ) } bool lifterFan = DotProduct( forward, <0,0,1> ) >= 0.98 float pushAccel = FAN_DEFAULT_PUSH_ACCEL if ( fanPusher.HasKey( "strength" ) ) pushAccel = float( fanPusher.kv.strength ) array fanPushables table startTimes table startHeights // Play fan sound on entity instead of at position because some occluder bug with miles. Easiest fix for late in game. Next project fan pusher shoudln't be info_target that you can't play sounds on entity fanSoundEntity = CreateScriptMover( fanPusher.GetOrigin() ) fanSoundEntity.DisableHibernation() // Delay to fix code bug where audio wont play at map load wait 0.2 FanOnSoundEffects( fanPusher, radius, fanSoundEntity ) while( true ) { if ( flag != "" && !Flag( flag ) ) { foreach( entity ent, float time in startTimes ) ent.e.inWindTunnel = false startTimes.clear() startHeights.clear() FanOffSoundEffects( fanPusher, radius, fanSoundEntity ) FlagWait( flag ) FanOnSoundEffects( fanPusher, radius, fanSoundEntity ) } fanPushables.clear() fanPushables.extend( GetPlayerArray() ) fanPushables.extend( GetNPCArray() ) fanPushables.extend( GetProjectileArrayEx( "any", TEAM_ANY, TEAM_ANY, cylinderBottom, fanPushDist ) ) fanPushables.extend( GetEntArrayByClass_Expensive( "prop_physics" ) ) foreach( entity ent in fanPushables ) { array testPosArray = [ ent.GetWorldSpaceCenter(), ent.EyePosition() + <0,0,16>, ent.GetOrigin() - <0,0,16> ] bool isInFanCylinder = false vector testPos = ent.GetWorldSpaceCenter() if ( ent.e.windPushEnabled ) { foreach( vector pos in testPosArray ) { if ( !PointInCylinder( cylinderBottom, cylinderTop, radius, pos ) ) continue isInFanCylinder = true break } } if ( isInFanCylinder ) { if ( ent.IsPlayer() && ent.IsNoclipping() ) continue if ( !( ent in startTimes ) ) { ent.e.inWindTunnel = true ent.e.windTunnelDirection = forward if ( ent.IsPlayer() ) thread PlayerInWindTunnel( ent ) else ent.SetOrigin( ent.GetOrigin() + <0,0,48> ) startTimes[ ent ] <- Time() } float startTime = startTimes[ ent ] ent.e.windTunnelStartTime = startTime float startHeight if ( lifterFan ) { if ( !( ent in startHeights ) ) startHeights[ ent ] <- ent.GetOrigin().z startHeight = startHeights[ ent ] } // Figure out what force should be based on proximity to fan vector pointAlongTunnel = GetClosestPointOnLineSegment( cylinderBottom, cylinderTop, testPos ) float distanceFromFanAlongTunnel = Distance( pointAlongTunnel, cylinderBottom ) float fanStrength = GetFanStrengthWithGeoBlockage( ent, testPos, cylinderBottom, cylinderTop, forward ) if ( ent.IsPlayer() ) ent.SetPlayerNetFloatOverTime( "FanRumbleStrength", fanStrength, 0.0 ) if ( lifterFan ) fanStrength = GraphCapped( distanceFromFanAlongTunnel, 0.0, fanPushDist, 1.0, 0.0 ) if ( ent.IsProjectile() ) fanStrength *= 2.0 if ( ent.GetModelName() == $"models/containers/barrel.mdl" ) fanStrength = 0.0 // Ramp up the push over time when first entering float ramp = GraphCapped( Time(), startTime, startTime + FAN_PUSH_RAMP_TIME, 0.0, 1.0 ) float dt = 0.01666667 // old behavior vector velocity = ent.GetVelocity() // Apply push to the velocity velocity += forward * ( dt * pushAccel * fanStrength ) // Decay other directional movement on the vector if ( FAN_PUSH_DECAY_SIDE_VELOCITY ) { vector velInOtherDirs = velocity - forward * DotProduct( velocity, forward ) float decayFrac = pow( ramp * fanStrength, dt ) vector loseVelInOtherDirs = velInOtherDirs * (1 - decayFrac) velocity -= loseVelInOtherDirs } // Add some anti-gravity velocity.z += dt * FAN_PUSH_ANTI_GRAVITY * fanStrength ent.e.windTunnelStrength = fanStrength // Apply new force to ent ent.SetVelocity( velocity ) // Hack for drones. You can't set velocity on them yet so I'm doing this for now to test the gameplay if ( ent.GetClassName() == "npc_drone" ) { float zChange = dt * pushAccel * fanStrength * 1 ent.SetOrigin( ent.GetOrigin() + < 0, 0, zChange > ) } } else { if ( ent in startTimes ) { ent.e.inWindTunnel = false delete startTimes[ ent ] } if ( lifterFan && ent in startHeights ) { delete startHeights[ ent ] } if ( ent.IsPlayer() ) ent.SetPlayerNetFloatOverTime( "FanRumbleStrength", 0.0, 1.0 ) } } WaitFrame() } } void function PlayerInWindTunnel( entity player ) { //int poseIndex = player.LookupPoseParameterIndex( "windfrac" ) EndSignal( player, "OnDestroy" ) OnThreadEnd( function() : ( player ) { if ( FAN_DEBUG ) printt( "OUT!" ) if ( IsValid( player ) ) { player.SetOneHandedWeaponUsageOff() FadeOutSoundOnEntity( player, "Beacon_WindBuffet_Player", 1.0 ) player.kv.airSpeed = player.GetPlayerSettingsField( "airSpeed" ) player.kv.airAcceleration = player.GetPlayerSettingsField( "airAcceleration" ) } } ) if ( FAN_DEBUG ) printt( "IN!" ) bool playingWindBuffet = false player.kv.airSpeed = 150 player.kv.airAcceleration = 650 while( player.e.inWindTunnel ) { //player.GetViewModelEntity().SetPoseParameter( poseIndex, player.e.windTunnelStrength ) if ( player.e.windTunnelStrength > 0 ) { player.SetOneHandedWeaponUsageOn() if ( !playingWindBuffet ) { EmitSoundOnEntityOnlyToPlayerWithFadeIn( player, player, "Beacon_WindBuffet_Player", 1.0 ) playingWindBuffet = true } } else { player.SetOneHandedWeaponUsageOff() if ( playingWindBuffet ) { FadeOutSoundOnEntity( player, "Beacon_WindBuffet_Player", 1.0 ) playingWindBuffet = false } } WaitFrame() } } float function GetFanStrengthWithGeoBlockage( entity ent, vector testPos, vector cylinderBottom, vector cylinderTop, vector fanDirection ) { vector pointAlongFan = GetClosestPointOnLineSegment( cylinderBottom, cylinderTop, testPos ) vector vecFromFanCenter = testPos - pointAlongFan vector traceEnd = cylinderBottom + vecFromFanCenter // Trace from the entity towards the fan along the fan axis to see if we are getting blocked TraceResults result = TraceLine( testPos, traceEnd, ent, TRACE_MASK_NPCSOLID, TRACE_COLLISION_GROUP_NONE ) if ( FAN_DEBUG ) { DebugDrawLine( testPos, result.endPos, 255, 255, 0, true, 0.1 ) DebugDrawLine( result.endPos, traceEnd, 255, 255, 255, true, 0.1 ) //DebugDrawLine( <0,0,0>, result.endPos, 255, 0, 0, true, 0.1 ) } float distFromCover = Distance( testPos, result.endPos ) float strength = GraphCapped( distFromCover, 256, 1024, 0.0, 1.0 ) if ( result.fraction == 1.0 ) strength = 1.0 //if ( FAN_DEBUG ) //{ // printt( "strength:", strength ) // printt( "fraction:", result.fraction ) // printt( "dist from fan:", Distance( testPos, cylinderBottom ) ) // printt( "distFromCover:", distFromCover ) //} Assert( strength >= 0.0 && strength <= 1.0 ) return strength } void function FanOnSoundEffects( entity fanPusher, float radius, entity fanSoundEntity ) { // Turn on sound if ( radius >= 350 ) { EmitSoundOnEntity( fanSoundEntity, "Beacon_VerticalFanControl_On" ) if ( fanPusher.HasKey( "fan_loop_sound" ) ) EmitSoundOnEntity( fanSoundEntity, string( fanPusher.kv.fan_loop_sound ) ) } else { EmitSoundOnEntity( fanSoundEntity, "Beacon_MediumBlueFan_On" ) string loopAlias = fanPusher.HasKey( "fan_loop_sound" ) ? string( fanPusher.kv.fan_loop_sound ) : "Beacon_MediumBlueFan_Loop_01" EmitSoundOnEntity( fanSoundEntity, loopAlias ) //DebugDrawText( fanPusher.GetOrigin(), loopAlias, true, 90.0 ) } } void function FanOffSoundEffects( entity fanPusher, float radius, entity fanSoundEntity ) { // Turn off sound if ( radius >= 350 ) { string alias = "Beacon_VerticalFanControl_Off" if ( fanPusher.HasKey( "shutoff_sound" ) ) alias = string( fanPusher.kv.shutoff_sound ) EmitSoundOnEntity( fanSoundEntity, alias ) if ( fanPusher.HasKey( "fan_loop_sound" ) ) StopSoundOnEntity( fanSoundEntity, string( fanPusher.kv.fan_loop_sound ) ) } else { EmitSoundOnEntity( fanSoundEntity, "Beacon_MediumBlueFan_Off" ) string loopAlias = fanPusher.HasKey( "fan_loop_sound" ) ? string( fanPusher.kv.fan_loop_sound ) : "Beacon_MediumBlueFan_Loop_01" StopSoundOnEntity( fanSoundEntity, loopAlias ) } }