diff --git a/Northstar.Custom/mod/scripts/vscripts/melee/sh_melee_human.gnut b/Northstar.Custom/mod/scripts/vscripts/melee/sh_melee_human.gnut index b73eb25c7..031acacfa 100644 --- a/Northstar.Custom/mod/scripts/vscripts/melee/sh_melee_human.gnut +++ b/Northstar.Custom/mod/scripts/vscripts/melee/sh_melee_human.gnut @@ -1,490 +1,490 @@ -untyped - -global function MeleeHumanShared_Init - -global function HumanUnsyncedMelee -global function HumanMeleeAttack - -function MeleeHumanShared_Init() -{ - PrecacheParticleSystem( $"P_melee_player" ) - RegisterSignal( "StopSlowMoMelee" ) - RegisterSignal( "StopHighlightValidMeleeEnemy" ) -} - -function HumanUnsyncedMelee( entity player, bool movestunBlocked ) -{ - entity activeWeapon = player.GetActiveWeapon() - if ( !IsValid( activeWeapon ) ) - { -#if SERVER - print( "SERVER: " + player + " has no valid active weapon\n" ) -#else - print( "CLIENT: " + player + " has no valid active weapon\n" ) -#endif - return - } - - entity meleeWeapon = player.GetMeleeWeapon() - if ( !IsValid( meleeWeapon ) ) - { -#if SERVER - print( "SERVER: " + player + " has no valid melee weapon\n" ) -#else - print( "CLIENT: " + player + " has no valid melee weapon\n" ) -#endif - return - } - - local meleeAttackType = PLAYER_MELEE_STATE_HUMAN_KICK_ATTACK - if ( activeWeapon.GetWeaponClassName() == "mp_weapon_dash_melee" ) - meleeAttackType = PLAYER_MELEE_STATE_HUMAN_EVISCERATE_ATTACK - - player.PlayerMelee_StartAttack( meleeAttackType ) - - if ( player.PlayerMelee_GetState() == PLAYER_MELEE_STATE_HUMAN_EVISCERATE_ATTACK ) - { - vector lungeTargetPos = (player.GetOrigin() + (player.GetViewVector() * 300)) - player.Lunge_SetTargetPosition( lungeTargetPos ) - player.Lunge_EnableFlying() - } - else - { - entity lungeTarget = GetLungeTargetForPlayer( player ) - if ( IsAlive( lungeTarget ) ) - { - if ( !movestunBlocked ) - { - if ( player.Lunge_SetTargetEntity( lungeTarget, true ) ) - { - if ( lungeTarget.IsTitan() ) - { - player.Lunge_EnableFlying() - vector oldOffset = player.Lunge_GetEndPositionOffset() - player.Lunge_SetEndPositionOffset( oldOffset + <0, 0, 128> ) - } - else - { - if ( player.IsOnGround() ) - player.Lunge_LockPitch( true ) - } - } - } - } -#if SERVER - // if we don't lunge at anything stop slowmo - else if ( IsSingleplayer() && PROTO_IsSlowMoWeapon( meleeWeapon ) ) - { - player.Signal( "StopSlowMoMelee" ) - } -#endif // #if SERVER - } - -#if SERVER - meleeWeapon.EmitWeaponNpcSound_DontUpdateLastFiredTime( 200, 0.2 ) -#endif // #if SERVER - - //player.Weapon_StartCustomActivity( meleeActivity1p, false ) - player.SetSelectedOffhandToMelee() -} - -function DoReactionForTitanHit( entity player, entity titan ) -{ - player.Lunge_SetTargetEntity( titan, true ) - if ( player.Lunge_IsLungingToEntity() ) - player.Lunge_EnableFlying() - - vector titanCenter = titan.EyePosition() - vector delta = (player.EyePosition() - titanCenter) - vector dir = Normalize( delta ) - player.Lunge_SetEndPositionOffset( dir * 350 ) -} - -function HumanMeleeAttack( entity player ) -{ - if ( player.IsPhaseShifted() ) - return - if ( player.PlayerMelee_GetAttackHitEntity() ) - return - if ( IsInExecutionMeleeState( player ) ) - return - - entity meleeWeapon = player.GetMeleeWeapon() - float attackRange = meleeWeapon.GetMeleeAttackRange() - - if ( player.Lunge_IsGroundExecute() ) - attackRange = 150 - - table traceResult = PlayerMelee_AttackTrace( player, attackRange, CodeCallback_IsValidMeleeAttackTarget ) - - entity hitEnt = expect entity( traceResult.ent ) - if ( !IsValid( hitEnt ) ) - return - - if ( PlayerMelee_IsServerSideEffects() ) - { -#if SERVER - vector hitNormal = Normalize( traceResult.startPosition - traceResult.position ) - player.DispatchImpactEffects( hitEnt, traceResult.startPosition, traceResult.position, hitNormal, traceResult.surfaceProp, traceResult.staticPropIndex, traceResult.damageType, meleeWeapon.GetImpactTableIndex(), player, traceResult.impactEffectFlags | IEF_SERVER_SIDE_EFFECT ) -#endif - } - else - { - vector hitNormal = Normalize( traceResult.startPosition - traceResult.position ) - player.DispatchImpactEffects( hitEnt, traceResult.startPosition, traceResult.position, hitNormal, traceResult.surfaceProp, traceResult.staticPropIndex, traceResult.damageType, meleeWeapon.GetImpactTableIndex(), player, traceResult.impactEffectFlags ) - } - - player.PlayerMelee_SetAttackHitEntity( hitEnt ) - if ( !hitEnt.IsWorld() ) - player.PlayerMelee_SetAttackRecoveryShouldBeQuick( true ) - - if ( hitEnt.IsTitan() ) - DoReactionForTitanHit( player, hitEnt ) - - if ( hitEnt.IsBreakableGlass() ) - { -#if SERVER - hitEnt.BreakSphere( traceResult.position, 50 ) -#endif // #if SERVER - } - else - { - if ( player.IsInputCommandHeld( IN_MELEE ) && AttemptHumanMeleeExecution( player, hitEnt, meleeWeapon, traceResult ) ) - return - -#if CLIENT - //MeleeImpactFX( player, meleeWeapon, hitEnt ) -#else - HumanMeleeAttack_DoImpact( player, meleeWeapon, traceResult ) -#endif - const float SCALE_WHEN_ENEMY = 1.0 - const float SCALE_WHEN_NOT_ENEMY = 0.5 - float severityScale = IsEnemyTeam( player.GetTeam(), hitEnt.GetTeam() ) ? SCALE_WHEN_ENEMY : SCALE_WHEN_NOT_ENEMY - meleeWeapon.DoMeleeHitConfirmation( severityScale ) - } -} - -#if 0 //CLIENT -function MeleeImpactFX( entity player, entity meleeWeapon, entity target ) -{ - if ( !target.IsWorld() ) - { - entity cockpit = player.GetCockpit() - if ( IsValid( cockpit ) ) - StartParticleEffectOnEntity( cockpit, GetParticleSystemIndex( $"P_melee_player" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) //P_MFD works well too - } -} -#endif // CLIENT - -#if SERVER -function HumanMeleeAttack_DoImpact( entity player, entity meleeWeapon, traceResult ) -{ - local angles = player.EyeAngles() - entity target = expect entity( traceResult.ent ) - player.PlayerMelee_SetAttackHitEntity( target ) - - string weaponName = meleeWeapon.GetWeaponClassName() - local damageSource = eDamageSourceId[weaponName] - int damageAmount = GetDamageAmountForTarget( meleeWeapon, target ) - - if ( IsHumanSized( target ) ) - { - if ( target.IsPlayer() ) //Strip away rodeo protection - { - entity titanBeingRodeoed = GetTitanBeingRodeoed( target ) - if ( IsValid( titanBeingRodeoed ) ) - TakeAwayFriendlyRodeoPlayerProtection( titanBeingRodeoed ) - } - - // ?? - target.SetContinueAnimatingAfterRagdoll( true ) - } - - vector oldVelocity = target.GetVelocity() - vector damageForce = AnglesToForward( angles ) * meleeWeapon.GetWeaponDamageForce() - - if ( target.IsNPC() && target.CanBeGroundExecuted() ) - target.TakeDamage( target.GetHealth(), player, player, { scriptType = DF_RAGDOLL | meleeWeapon.GetWeaponDamageFlags(), damageType = DMG_MELEE_ATTACK, damageSourceId = damageSource, origin = traceResult.position, force = Vector( 0, 0, 0 ) } ) - else - target.TakeDamage( damageAmount, player, player, { scriptType = meleeWeapon.GetWeaponDamageFlags(), damageType = DMG_MELEE_ATTACK, damageSourceId = damageSource, origin = traceResult.position, force = damageForce } ) - - // PROTO DEV - if ( IsSingleplayer() ) - { - if ( PROTO_ShouldActivateSlowMo( target, meleeWeapon ) ) - { - thread PROTO_SlowMoMelee( player, target, meleeWeapon ) - } - } - - // triggers: - { - local triggerTraceDir = Normalize( traceResult.position - traceResult.startPosition ) - player.TraceAttackToTriggers( damageAmount, player, player, { scriptType = meleeWeapon.GetWeaponDamageFlags(), damageType = DMG_MELEE_ATTACK, damageSourceId = damageSource, force = damageForce }, traceResult.startPosition, traceResult.position, triggerTraceDir ) - } - - if ( target.IsPlayerDecoy() ) - { - player.PlayerMelee_EndAttack() - } -} - -int function GetDamageAmountForTarget( entity meleeWeapon, entity target ) -{ - // special case - if ( IsTurret( target ) && IsHumanSized( target ) ) - return target.GetMaxHealth() + 1 - - // default - return meleeWeapon.GetDamageAmountForArmorType( target.GetArmorType() ) -} - - -// HACK - testing linked slow mo melee -void function PROTO_SlowMoMelee( entity player, entity currentEnemy, entity meleeWeapon ) -{ - player.EndSignal( "OnDeath" ) - player.EndSignal( "OnDestroy" ) - player.EndSignal( "StopSlowMoMelee" ) - - float duration = 1.75 //1.75 - float timescale = 0.4 - float lastKillTimescale = 0.2 - - var SlowMoTimeRemaining = player.s.meleeSlowMoEndTime - Time() - - meleeWeapon.SetMods( [ "SlowMoLinked" ] ) // need to switch to the other mod to get the longer lunge range - - // find an enemy close enough that we can melee him next - entity nextEnemy = PROTO_GetNextMeleeEnemy( player, meleeWeapon, currentEnemy ) - - if ( !IsValid( nextEnemy ) ) - { - meleeWeapon.SetMods( [ "SlowMo" ] ) - if ( SlowMoTimeRemaining > 0 ) - { - // do extra slowdown for the last kill in a linked slow-mo melee chain. - ServerCommand( "host_timescale " + string( lastKillTimescale ) ) - wait 0.2 - player.Signal( "StopSlowMoMelee" ) // this will also end this thread - } - - return - } - - if ( player.s.meleeSlowMoEndTime > Time() ) - { - // if we are already in slow-mo just turn towards the next enemy and extend the duration - thread PROTO_TurnViewTowardsClosestEnemy( player, nextEnemy ) - player.s.meleeSlowMoEndTime = Time() + duration // += duration - return - } - - // require a 5 second cool down between leaving and reentering slow mo. - if ( SlowMoTimeRemaining > -5 ) - return - - thread PROTO_TurnViewTowardsClosestEnemy( player, nextEnemy ) - - // enter slow mo - ServerCommand( "host_timescale " + string( timescale ) ) - player.s.meleeSlowMoEndTime = Time() + duration - meleeWeapon.SetMods( [ "SlowMoLinked" ] ) - - float range = meleeWeapon.GetMeleeLungeTargetRange() - array enemyArray = PROTO_GetMeleeEnemiesWithinRange( player.GetOrigin(), player.GetTeam(), range ) - foreach( enemy in enemyArray ) - thread PROTO_HighlightValidMeleeEnemy( player, enemy, meleeWeapon ) - - player.SetInvulnerable() - - OnThreadEnd( - function() : ( player, meleeWeapon ) - { - if ( IsValid( meleeWeapon ) ) - meleeWeapon.SetMods( [ "SlowMo" ] ) - - if ( IsValid( player ) ) - { - player.ClearInvulnerable() - player.s.meleeSlowMoEndTime = 0 - } - - thread PROTO_EaseOutSlowMo() - } - ) - - while( Time() <= player.s.meleeSlowMoEndTime ) - { - var waitTime = player.s.meleeSlowMoEndTime - Time() - wait waitTime - } - - player.Signal( "StopSlowMoMelee" ) -} - -void function PROTO_EaseOutSlowMo() -{ - ServerCommand( "host_timescale 0.4" ) - wait 0.1 - ServerCommand( "host_timescale 0.7" ) - wait 0.1 - ServerCommand( "host_timescale 1.0" ) -} - -bool function PROTO_IsSlowMoWeapon( entity meleeWeapon ) -{ - return ( meleeWeapon.HasMod( "SlowMo" ) || meleeWeapon.HasMod( "SlowMoLinked" ) ) -} - -bool function PROTO_ShouldActivateSlowMo( entity enemy, entity meleeWeapon ) -{ - if ( !PROTO_IsSlowMoWeapon( meleeWeapon ) ) - return false - - if ( !IsHumanSized( enemy ) ) - return false - - return true -} - -void function PROTO_TurnViewTowardsClosestEnemy( entity player, entity nextEnemy ) -{ - player.EndSignal( "OnDeath" ) - - OnThreadEnd( - function() : ( player ) - { - if ( IsValid( player ) ) - { - player.ClearParent() - player.PlayerCone_Disable() - } - } - ) - - // turn player view towards next enemy - vector vec = nextEnemy.GetOrigin() - player.GetOrigin() - vector newAngles = VectorToAngles( vec ) - - entity scriptMover = CreateScriptMover( player.GetOrigin(), player.GetAngles() ) - player.SetParent( scriptMover ) - - player.PlayerCone_SetLerpTime( 0.15 ) - - player.PlayerCone_FromAnim() - player.PlayerCone_SetMinYaw( -15 ) - player.PlayerCone_SetMaxYaw( 15 ) - player.PlayerCone_SetMinPitch( -5 ) - player.PlayerCone_SetMaxPitch( 15 ) - - wait 0.2 - - scriptMover.NonPhysicsRotateTo( newAngles, 0.4, 0.2, 0.2 ) - wait 0.4 -} - -entity function PROTO_GetNextMeleeEnemy( entity player, entity meleeWeapon, entity lastEnemy ) -{ - float range = meleeWeapon.GetMeleeLungeTargetRange() - array enemyArray = PROTO_GetMeleeEnemiesWithinRange( player.GetOrigin(), player.GetTeam(), range ) - entity nextEnemy = null - - foreach ( enemy in enemyArray ) - { - float heightDif = enemy.GetOrigin().z - player.GetOrigin().z - if ( heightDif < -96 || heightDif > 48 ) - continue - - float frac = TraceLineSimple( player.EyePosition(), enemy.EyePosition(), enemy ) - if ( frac < 1 ) - continue - - if ( enemy == lastEnemy ) - continue - - nextEnemy = enemy - break - } - - return nextEnemy -} - -array function PROTO_GetMeleeEnemiesWithinRange( vector playerOrigin, int playerTeam, float range ) -{ - array enemyArray = GetNPCArrayEx( "npc_soldier", TEAM_ANY, playerTeam, playerOrigin, range ) - enemyArray.extend( GetNPCArrayEx( "npc_spectre", TEAM_ANY, playerTeam, playerOrigin, range ) ) - - return enemyArray -} - -void function PROTO_HighlightValidMeleeEnemy( entity player, entity enemy, entity meleeWeapon ) -{ - enemy.Signal( "StopHighlightValidMeleeEnemy" ) - enemy.EndSignal( "StopHighlightValidMeleeEnemy" ) - - player.EndSignal( "StopSlowMoMelee" ) - player.EndSignal( "OnDeath" ) - player.EndSignal( "OnDestroy" ) - - enemy.EndSignal( "OnDestroy" ) - - OnThreadEnd( - function() : ( enemy ) - { - if ( IsValid( enemy ) ) - Highlight_ClearEnemyHighlight( enemy ) - } - ) - - float range = meleeWeapon.GetMeleeLungeTargetRange() - float minDot = AngleToDot( meleeWeapon.GetMeleeLungeTargetAngle() ) - - while( true ) - { - vector viewVector = player.GetViewVector() - vector enemyVector = enemy.GetCenter() - player.EyePosition() - float dist = expect float( enemyVector.Norm() ) - - if ( DotProduct( enemyVector, viewVector ) > minDot && dist < range ) - Highlight_SetEnemyHighlight( enemy, "enemy_sur_base" ) // enemy_sur_base, enemy_sonar, map_scan - else - Highlight_ClearEnemyHighlight( enemy ) - - wait 0.1 - } -} - -#endif // #if SERVER - -bool function AttemptHumanMeleeExecution( entity player, entity syncedTarget, entity meleeWeapon, table traceResult ) -{ - if ( player.PlayerMelee_GetState() == PLAYER_MELEE_STATE_NONE ) - return false - - if ( !IsAlive( player ) ) - return false - - if ( player.IsPhaseShifted() ) - return false - - if ( !CodeCallback_IsValidMeleeExecutionTarget( player, syncedTarget ) ) - return false - - #if SERVER - player.Anim_StopGesture( 0 ) - #endif - - thread PlayerTriesSyncedMelee_FallbackToHumanMeleeAttack( player, syncedTarget, meleeWeapon, traceResult ) - return true -} - -void function PlayerTriesSyncedMelee_FallbackToHumanMeleeAttack( entity player, entity target, entity meleeWeapon, table traceResult ) -{ - if ( !PlayerTriesSyncedMelee( player, target ) ) - { -#if SERVER - HumanMeleeAttack_DoImpact( player, meleeWeapon, traceResult ) -#endif - } -} +untyped + +global function MeleeHumanShared_Init + +global function HumanUnsyncedMelee +global function HumanMeleeAttack + +function MeleeHumanShared_Init() +{ + PrecacheParticleSystem( $"P_melee_player" ) + RegisterSignal( "StopSlowMoMelee" ) + RegisterSignal( "StopHighlightValidMeleeEnemy" ) +} + +function HumanUnsyncedMelee( entity player, bool movestunBlocked ) +{ + entity activeWeapon = player.GetActiveWeapon() + if ( !IsValid( activeWeapon ) ) + { +#if SERVER + print( "SERVER: " + player + " has no valid active weapon\n" ) +#else + print( "CLIENT: " + player + " has no valid active weapon\n" ) +#endif + return + } + + entity meleeWeapon = player.GetMeleeWeapon() + if ( !IsValid( meleeWeapon ) ) + { +#if SERVER + print( "SERVER: " + player + " has no valid melee weapon\n" ) +#else + print( "CLIENT: " + player + " has no valid melee weapon\n" ) +#endif + return + } + + local meleeAttackType = PLAYER_MELEE_STATE_HUMAN_KICK_ATTACK + if ( activeWeapon.GetWeaponClassName() == "mp_weapon_dash_melee" ) + meleeAttackType = PLAYER_MELEE_STATE_HUMAN_EVISCERATE_ATTACK + + player.PlayerMelee_StartAttack( meleeAttackType ) + + if ( player.PlayerMelee_GetState() == PLAYER_MELEE_STATE_HUMAN_EVISCERATE_ATTACK ) + { + vector lungeTargetPos = (player.GetOrigin() + (player.GetViewVector() * 300)) + player.Lunge_SetTargetPosition( lungeTargetPos ) + player.Lunge_EnableFlying() + } + else + { + entity lungeTarget = GetLungeTargetForPlayer( player ) + if ( IsAlive( lungeTarget ) ) + { + if ( !movestunBlocked ) + { + if ( player.Lunge_SetTargetEntity( lungeTarget, true ) ) + { + if ( lungeTarget.IsTitan() ) + { + player.Lunge_EnableFlying() + vector oldOffset = player.Lunge_GetEndPositionOffset() + player.Lunge_SetEndPositionOffset( oldOffset + <0, 0, 128> ) + } + else + { + if ( player.IsOnGround() ) + player.Lunge_LockPitch( true ) + } + } + } + } +#if SERVER + // if we don't lunge at anything stop slowmo + else if ( IsSingleplayer() && PROTO_IsSlowMoWeapon( meleeWeapon ) ) + { + player.Signal( "StopSlowMoMelee" ) + } +#endif // #if SERVER + } + +#if SERVER + meleeWeapon.EmitWeaponNpcSound_DontUpdateLastFiredTime( 200, 0.2 ) +#endif // #if SERVER + + //player.Weapon_StartCustomActivity( meleeActivity1p, false ) + player.SetSelectedOffhandToMelee() +} + +function DoReactionForTitanHit( entity player, entity titan ) +{ + player.Lunge_SetTargetEntity( titan, true ) + if ( player.Lunge_IsLungingToEntity() ) + player.Lunge_EnableFlying() + + vector titanCenter = titan.EyePosition() + vector delta = (player.EyePosition() - titanCenter) + vector dir = Normalize( delta ) + player.Lunge_SetEndPositionOffset( dir * 350 ) +} + +function HumanMeleeAttack( entity player ) +{ + if ( player.IsPhaseShifted() ) + return + if ( player.PlayerMelee_GetAttackHitEntity() ) + return + if ( IsInExecutionMeleeState( player ) ) + return + + entity meleeWeapon = player.GetMeleeWeapon() + float attackRange = meleeWeapon.GetMeleeAttackRange() + + if ( player.Lunge_IsGroundExecute() ) + attackRange = 150 + + table traceResult = PlayerMelee_AttackTrace( player, attackRange, CodeCallback_IsValidMeleeAttackTarget ) + + entity hitEnt = expect entity( traceResult.ent ) + if ( !IsValid( hitEnt ) ) + return + + if ( PlayerMelee_IsServerSideEffects() ) + { +#if SERVER + vector hitNormal = Normalize( traceResult.startPosition - traceResult.position ) + player.DispatchImpactEffects( hitEnt, traceResult.startPosition, traceResult.position, hitNormal, traceResult.surfaceProp, traceResult.staticPropIndex, traceResult.damageType, meleeWeapon.GetImpactTableIndex(), player, traceResult.impactEffectFlags | IEF_SERVER_SIDE_EFFECT ) +#endif + } + else + { + vector hitNormal = Normalize( traceResult.startPosition - traceResult.position ) + player.DispatchImpactEffects( hitEnt, traceResult.startPosition, traceResult.position, hitNormal, traceResult.surfaceProp, traceResult.staticPropIndex, traceResult.damageType, meleeWeapon.GetImpactTableIndex(), player, traceResult.impactEffectFlags ) + } + + player.PlayerMelee_SetAttackHitEntity( hitEnt ) + if ( !hitEnt.IsWorld() ) + player.PlayerMelee_SetAttackRecoveryShouldBeQuick( true ) + + if ( hitEnt.IsTitan() ) + DoReactionForTitanHit( player, hitEnt ) + + if ( hitEnt.IsBreakableGlass() ) + { +#if SERVER + hitEnt.BreakSphere( traceResult.position, 50 ) +#endif // #if SERVER + } + else + { + if ( player.IsInputCommandHeld( IN_MELEE ) && AttemptHumanMeleeExecution( player, hitEnt, meleeWeapon, traceResult ) ) + return + +#if CLIENT + //MeleeImpactFX( player, meleeWeapon, hitEnt ) +#else + HumanMeleeAttack_DoImpact( player, meleeWeapon, traceResult ) +#endif + const float SCALE_WHEN_ENEMY = 1.0 + const float SCALE_WHEN_NOT_ENEMY = 0.5 + float severityScale = IsEnemyTeam( player.GetTeam(), hitEnt.GetTeam() ) ? SCALE_WHEN_ENEMY : SCALE_WHEN_NOT_ENEMY + meleeWeapon.DoMeleeHitConfirmation( severityScale ) + } +} + +#if 0 //CLIENT +function MeleeImpactFX( entity player, entity meleeWeapon, entity target ) +{ + if ( !target.IsWorld() ) + { + entity cockpit = player.GetCockpit() + if ( IsValid( cockpit ) ) + StartParticleEffectOnEntity( cockpit, GetParticleSystemIndex( $"P_melee_player" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) //P_MFD works well too + } +} +#endif // CLIENT + +#if SERVER +function HumanMeleeAttack_DoImpact( entity player, entity meleeWeapon, traceResult ) +{ + local angles = player.EyeAngles() + entity target = expect entity( traceResult.ent ) + player.PlayerMelee_SetAttackHitEntity( target ) + + string weaponName = meleeWeapon.GetWeaponClassName() + local damageSource = eDamageSourceId[weaponName] + int damageAmount = GetDamageAmountForTarget( meleeWeapon, target ) + + if ( IsHumanSized( target ) ) + { + if ( target.IsPlayer() ) //Strip away rodeo protection + { + entity titanBeingRodeoed = GetTitanBeingRodeoed( target ) + if ( IsValid( titanBeingRodeoed ) ) + TakeAwayFriendlyRodeoPlayerProtection( titanBeingRodeoed ) + } + + // ?? + target.SetContinueAnimatingAfterRagdoll( true ) + } + + vector oldVelocity = target.GetVelocity() + vector damageForce = AnglesToForward( angles ) * meleeWeapon.GetWeaponDamageForce() + + if ( target.IsNPC() && target.CanBeGroundExecuted() ) + target.TakeDamage( target.GetHealth(), player, player, { scriptType = DF_RAGDOLL | meleeWeapon.GetWeaponDamageFlags(), damageType = DMG_MELEE_ATTACK, damageSourceId = damageSource, origin = traceResult.position, force = Vector( 0, 0, 0 ) } ) + else + target.TakeDamage( damageAmount, player, player, { scriptType = meleeWeapon.GetWeaponDamageFlags(), damageType = DMG_MELEE_ATTACK, damageSourceId = damageSource, origin = traceResult.position, force = damageForce } ) + + // PROTO DEV + if ( IsSingleplayer() ) + { + if ( PROTO_ShouldActivateSlowMo( target, meleeWeapon ) ) + { + thread PROTO_SlowMoMelee( player, target, meleeWeapon ) + } + } + + // triggers: + { + local triggerTraceDir = Normalize( traceResult.position - traceResult.startPosition ) + player.TraceAttackToTriggers( damageAmount, player, player, { scriptType = meleeWeapon.GetWeaponDamageFlags(), damageType = DMG_MELEE_ATTACK, damageSourceId = damageSource, force = damageForce }, traceResult.startPosition, traceResult.position, triggerTraceDir ) + } + + if ( target.IsPlayerDecoy() ) + { + player.PlayerMelee_EndAttack() + } +} + +int function GetDamageAmountForTarget( entity meleeWeapon, entity target ) +{ + // special case + if ( IsTurret( target ) && IsHumanSized( target ) ) + return target.GetMaxHealth() + 1 + + // default + return meleeWeapon.GetDamageAmountForArmorType( target.GetArmorType() ) +} + + +// HACK - testing linked slow mo melee +void function PROTO_SlowMoMelee( entity player, entity currentEnemy, entity meleeWeapon ) +{ + player.EndSignal( "OnDeath" ) + player.EndSignal( "OnDestroy" ) + player.EndSignal( "StopSlowMoMelee" ) + + float duration = 1.75 //1.75 + float timescale = 0.4 + float lastKillTimescale = 0.2 + + var SlowMoTimeRemaining = player.s.meleeSlowMoEndTime - Time() + + meleeWeapon.SetMods( [ "SlowMoLinked" ] ) // need to switch to the other mod to get the longer lunge range + + // find an enemy close enough that we can melee him next + entity nextEnemy = PROTO_GetNextMeleeEnemy( player, meleeWeapon, currentEnemy ) + + if ( !IsValid( nextEnemy ) ) + { + meleeWeapon.SetMods( [ "SlowMo" ] ) + if ( SlowMoTimeRemaining > 0 ) + { + // do extra slowdown for the last kill in a linked slow-mo melee chain. + ServerCommand( "host_timescale " + string( lastKillTimescale ) ) + wait 0.2 + player.Signal( "StopSlowMoMelee" ) // this will also end this thread + } + + return + } + + if ( player.s.meleeSlowMoEndTime > Time() ) + { + // if we are already in slow-mo just turn towards the next enemy and extend the duration + thread PROTO_TurnViewTowardsClosestEnemy( player, nextEnemy ) + player.s.meleeSlowMoEndTime = Time() + duration // += duration + return + } + + // require a 5 second cool down between leaving and reentering slow mo. + if ( SlowMoTimeRemaining > -5 ) + return + + thread PROTO_TurnViewTowardsClosestEnemy( player, nextEnemy ) + + // enter slow mo + ServerCommand( "host_timescale " + string( timescale ) ) + player.s.meleeSlowMoEndTime = Time() + duration + meleeWeapon.SetMods( [ "SlowMoLinked" ] ) + + float range = meleeWeapon.GetMeleeLungeTargetRange() + array enemyArray = PROTO_GetMeleeEnemiesWithinRange( player.GetOrigin(), player.GetTeam(), range ) + foreach( enemy in enemyArray ) + thread PROTO_HighlightValidMeleeEnemy( player, enemy, meleeWeapon ) + + player.SetInvulnerable() + + OnThreadEnd( + function() : ( player, meleeWeapon ) + { + if ( IsValid( meleeWeapon ) ) + meleeWeapon.SetMods( [ "SlowMo" ] ) + + if ( IsValid( player ) ) + { + player.ClearInvulnerable() + player.s.meleeSlowMoEndTime = 0 + } + + thread PROTO_EaseOutSlowMo() + } + ) + + while( Time() <= player.s.meleeSlowMoEndTime ) + { + var waitTime = player.s.meleeSlowMoEndTime - Time() + wait waitTime + } + + player.Signal( "StopSlowMoMelee" ) +} + +void function PROTO_EaseOutSlowMo() +{ + ServerCommand( "host_timescale 0.4" ) + wait 0.1 + ServerCommand( "host_timescale 0.7" ) + wait 0.1 + ServerCommand( "host_timescale 1.0" ) +} + +bool function PROTO_IsSlowMoWeapon( entity meleeWeapon ) +{ + return ( meleeWeapon.HasMod( "SlowMo" ) || meleeWeapon.HasMod( "SlowMoLinked" ) ) +} + +bool function PROTO_ShouldActivateSlowMo( entity enemy, entity meleeWeapon ) +{ + if ( !PROTO_IsSlowMoWeapon( meleeWeapon ) ) + return false + + if ( !IsHumanSized( enemy ) ) + return false + + return true +} + +void function PROTO_TurnViewTowardsClosestEnemy( entity player, entity nextEnemy ) +{ + player.EndSignal( "OnDeath" ) + + OnThreadEnd( + function() : ( player ) + { + if ( IsValid( player ) ) + { + player.ClearParent() + player.PlayerCone_Disable() + } + } + ) + + // turn player view towards next enemy + vector vec = nextEnemy.GetOrigin() - player.GetOrigin() + vector newAngles = VectorToAngles( vec ) + + entity scriptMover = CreateScriptMover( player.GetOrigin(), player.GetAngles() ) + player.SetParent( scriptMover ) + + player.PlayerCone_SetLerpTime( 0.15 ) + + player.PlayerCone_FromAnim() + player.PlayerCone_SetMinYaw( -15 ) + player.PlayerCone_SetMaxYaw( 15 ) + player.PlayerCone_SetMinPitch( -5 ) + player.PlayerCone_SetMaxPitch( 15 ) + + wait 0.2 + + scriptMover.NonPhysicsRotateTo( newAngles, 0.4, 0.2, 0.2 ) + wait 0.4 +} + +entity function PROTO_GetNextMeleeEnemy( entity player, entity meleeWeapon, entity lastEnemy ) +{ + float range = meleeWeapon.GetMeleeLungeTargetRange() + array enemyArray = PROTO_GetMeleeEnemiesWithinRange( player.GetOrigin(), player.GetTeam(), range ) + entity nextEnemy = null + + foreach ( enemy in enemyArray ) + { + float heightDif = enemy.GetOrigin().z - player.GetOrigin().z + if ( heightDif < -96 || heightDif > 48 ) + continue + + float frac = TraceLineSimple( player.EyePosition(), enemy.EyePosition(), enemy ) + if ( frac < 1 ) + continue + + if ( enemy == lastEnemy ) + continue + + nextEnemy = enemy + break + } + + return nextEnemy +} + +array function PROTO_GetMeleeEnemiesWithinRange( vector playerOrigin, int playerTeam, float range ) +{ + array enemyArray = GetNPCArrayEx( "npc_soldier", TEAM_ANY, playerTeam, playerOrigin, range ) + enemyArray.extend( GetNPCArrayEx( "npc_spectre", TEAM_ANY, playerTeam, playerOrigin, range ) ) + + return enemyArray +} + +void function PROTO_HighlightValidMeleeEnemy( entity player, entity enemy, entity meleeWeapon ) +{ + enemy.Signal( "StopHighlightValidMeleeEnemy" ) + enemy.EndSignal( "StopHighlightValidMeleeEnemy" ) + + player.EndSignal( "StopSlowMoMelee" ) + player.EndSignal( "OnDeath" ) + player.EndSignal( "OnDestroy" ) + + enemy.EndSignal( "OnDestroy" ) + + OnThreadEnd( + function() : ( enemy ) + { + if ( IsValid( enemy ) ) + Highlight_ClearEnemyHighlight( enemy ) + } + ) + + float range = meleeWeapon.GetMeleeLungeTargetRange() + float minDot = AngleToDot( meleeWeapon.GetMeleeLungeTargetAngle() ) + + while( true ) + { + vector viewVector = player.GetViewVector() + vector enemyVector = enemy.GetCenter() - player.EyePosition() + float dist = expect float( enemyVector.Norm() ) + + if ( DotProduct( enemyVector, viewVector ) > minDot && dist < range ) + Highlight_SetEnemyHighlight( enemy, "enemy_sur_base" ) // enemy_sur_base, enemy_sonar, map_scan + else + Highlight_ClearEnemyHighlight( enemy ) + + wait 0.1 + } +} + +#endif // #if SERVER + +bool function AttemptHumanMeleeExecution( entity player, entity syncedTarget, entity meleeWeapon, table traceResult ) +{ + if ( player.PlayerMelee_GetState() == PLAYER_MELEE_STATE_NONE ) + return false + + if ( !IsAlive( player ) ) + return false + + if ( player.IsPhaseShifted() ) + return false + + if ( !CodeCallback_IsValidMeleeExecutionTarget( player, syncedTarget ) ) + return false + + #if SERVER + player.Anim_StopGesture( 0 ) + #endif + + thread PlayerTriesSyncedMelee_FallbackToHumanMeleeAttack( player, syncedTarget, meleeWeapon, traceResult ) + return true +} + +void function PlayerTriesSyncedMelee_FallbackToHumanMeleeAttack( entity player, entity target, entity meleeWeapon, table traceResult ) +{ + if ( !PlayerTriesSyncedMelee( player, target ) ) + { +#if SERVER + HumanMeleeAttack_DoImpact( player, meleeWeapon, traceResult ) +#endif + } +}