From 6ff307c15ef5c5797f5a0d2b2ed26ff4aa018ddd Mon Sep 17 00:00:00 2001 From: Respawn Date: Sat, 24 Jun 2023 00:32:27 +0200 Subject: Add sh_melee.gnut from englishclient_mp_common --- .../mod/scripts/vscripts/melee/sh_melee.gnut | 1212 ++++++++++++++++++++ 1 file changed, 1212 insertions(+) create mode 100644 Northstar.Custom/mod/scripts/vscripts/melee/sh_melee.gnut (limited to 'Northstar.Custom/mod/scripts/vscripts/melee') diff --git a/Northstar.Custom/mod/scripts/vscripts/melee/sh_melee.gnut b/Northstar.Custom/mod/scripts/vscripts/melee/sh_melee.gnut new file mode 100644 index 000000000..dfd957e1d --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/melee/sh_melee.gnut @@ -0,0 +1,1212 @@ +untyped + +global function MeleeShared_Init + +global function CodeCallback_OnMeleePressed +//global function CodeCallback_OnMeleeHeld +//global function CodeCallback_OnMeleeReleased +global function CodeCallback_IsValidMeleeExecutionTarget +global function CodeCallback_IsValidMeleeAttackTarget +global function CodeCallback_OnMeleeAttackAnimEvent +global function AddSyncedMeleeServerCallback +global function AddSyncedMeleeServerThink + +global function GetSyncedMeleeChooser +global function CreateSyncedMeleeChooser +global function PlayerTriesSyncedMelee +global function FindBestSyncedMelee +global function GetSyncedMeleeChooserForPlayerVsTarget +global function AddSyncedMelee +global function GetEyeOrigin +global function SetObjectCanBeMeleed +global function ObjectCanBeMeleed +global function ShouldClampTargetVelocity +global function ClampVerticalVelocity +global function IsInExecutionMeleeState + +global function GetLungeTargetForPlayer +global function Melee_IsAllowed +global function IsAttackerRef + +#if SERVER + global function Melee_Enable + global function Melee_Disable + global function SyncedMelee_Enable + global function SyncedMelee_Disable + global function InitMeleeAnimEventCallbacks + global function GetRefAnglesBetweenEnts + global function CreateMeleeScriptMoverBetweenEnts + global function ShouldHolsterWeaponForMelee + global function ShouldHolsterWeaponForSyncedMelee + global function NPCTriesSyncedMeleeVsPlayer +#endif + +const SMOOTH_TIME = 0.2 +const INSTA_KILL_TIME_THRESHOLD = 0.35 +const BUG_REPRO_MOVEMELEE = 19114 + +global struct SyncedMelee +{ + string ref + bool enabled = true + vector direction = < 1, 0, 0 > + float distance + float distanceSqr + string attackerAnimation1p + string attackerAnimation3p +// void function AddAnimEvent( entity ent, string eventName, void functionref( entity ent ) func, var optionalVar = null ) + array attacker3pAnimEvents + array target3pAnimEvents + string targetAnimation1p + string targetAnimation3p + string thirdPersonCameraAttachment + asset attachModel1p + string attachTag1p + float minDot = -1.0 // always happens + string animRefPos = "target" + bool canTargetNPCs = true + float percentDamageDealtPerHit = 1.0 + bool usableByPlayers = true + + float targetMinHealthRatio = 0.0 // target health ratio must be at least this high + float targetMaxHealthRatio = 1.0 // target health ratio must be at or below this + bool onlyIfLethal // only if the strike would be lethal + bool isAttackerRef = true + +} + +global struct SyncedMeleeChooser +{ + vector functionref( entity ) attackerOriginFunc + vector functionref( entity ) targetOriginFunc + array syncedMelees + bool displayMeleePrompt = true +} + +struct +{ + table > syncedMeleeChoosers + table > syncedMeleeServerCallbacks + table syncedMeleeServerThink + + string lastExecutionUsed = "" +} file + +function MeleeShared_Init() +{ + FlagInit( "ForceSyncedMelee" ) + + level.HUMAN_VS_TITAN_MELEE <- 1 + level.titan_attack_anim_event_count <- 0 + level.titan_attack_push_button_count <- 0 + + MeleeHumanShared_Init() + MeleeTitanShared_Init() + MeleeSyncedHumanShared_Init() + MeleeSyncedTitanShared_Init() + + #if SERVER + VerifySyncedMelee() + MeleeSyncedServer_Init() + #endif + + RegisterSignal( "SyncedMeleeComplete" ) + RegisterSignal( "OnSyncedMelee" ) + RegisterSignal( "OnSyncedMeleeVictim" ) + RegisterSignal( "OnSyncedMeleeAttacker" ) +} + +int function GetPlayerMeleeDamage( entity player ) +{ + Assert( player.IsPlayer() ) + foreach ( weapon in player.GetMainWeapons() ) + { + switch ( weapon.GetWeaponInfoFileKeyField( "fire_mode" ) ) + { + case "offhand_melee": + return expect int( weapon.GetWeaponInfoFileKeyField( "melee_damage" ) ) + } + } + + return 0 +} + +void function AddSyncedMeleeServerCallback( SyncedMeleeChooser chooser, void functionref( SyncedMeleeChooser actions, SyncedMelee action, entity player, entity target ) func ) +{ + if ( !( chooser in file.syncedMeleeServerCallbacks ) ) + file.syncedMeleeServerCallbacks[ chooser ] <- [] + + file.syncedMeleeServerCallbacks[ chooser ].append( func ) +} + +void function AddSyncedMeleeServerThink( SyncedMeleeChooser chooser, bool functionref( SyncedMelee action, entity player, entity target ) func ) +{ + file.syncedMeleeServerThink[ chooser ] <- func +} + + +function VerifySyncedMelee() +{ + foreach ( attackerChoosers in file.syncedMeleeChoosers ) + { + foreach ( chooser in attackerChoosers ) + { + //Assert( chooser in file.syncedMeleeServerCallbacks, "Need to add synced server melee callback for synced melee chooser" ) + //Assert( file.syncedMeleeServerCallbacks[ chooser ].len() > 0, "Need to create a callback for chooser" ) + Assert( chooser in file.syncedMeleeServerThink, "Need to add synced server melee callback for synced melee chooser" ) + } + } +} + +SyncedMeleeChooser function GetSyncedMeleeChooser( string attackerType, string victimType ) +{ + return file.syncedMeleeChoosers[ attackerType ][ victimType ] +} + +SyncedMeleeChooser function CreateSyncedMeleeChooser( string attackerType, string victimType ) +{ + SyncedMeleeChooser chooser + + chooser.attackerOriginFunc = GetEyeOrigin + chooser.targetOriginFunc = GetEyeOrigin + + if ( !( attackerType in file.syncedMeleeChoosers ) ) + file.syncedMeleeChoosers[ attackerType ] <- {} + + Assert( !( victimType in file.syncedMeleeChoosers[ attackerType ] ), "Already has " + victimType ) + file.syncedMeleeChoosers[ attackerType ][ victimType ] <- chooser + return chooser +} + +vector function GetEyeOrigin( entity ent ) +{ + return ent.EyePosition() +} + +//Called after pressing the melee button to recheck for targets +bool function CodeCallback_IsValidMeleeExecutionTarget( entity attacker, entity target ) +{ + if ( attacker == target ) + return false + + if ( !ShouldPlayerExecuteTarget( attacker, target ) ) + return false + + if ( !attacker.IsOnGround() && attacker.IsHuman() ) + return false + + if ( !IsAlive( target ) ) + return false + + if ( target.IsInvulnerable() ) + return false + + if ( !CanBeMeleed( target ) ) + return false + + if ( target.IsNPC() && !target.CanBeMeleeExecuted() ) + return false + + // Disallow executing someone that is already in execution. That road leads to script errors and asserts. + if ( target.ContextAction_IsMeleeExecution() ) + return false + + if ( attacker.IsTitan() && target.IsTitan() ) + { + // no melee execute for berserker + if ( PlayerHasPassive( attacker, ePassives.PAS_BERSERKER ) ) + return false + + if ( PlayerHasPassive( attacker, ePassives.PAS_SHIFT_CORE ) ) + return false + + if ( HasSoul( target ) && target.GetTitanSoul().IsEjecting() ) + return false + + if ( attacker.ContextAction_IsActive() ) + return false + + if ( target.ContextAction_IsActive() ) + return false + + if ( GetCurrentPlaylistVarInt( "vortex_blocks_melee", 0 ) == 1 ) + { + vector traceStartPos = attacker.EyePosition() + vector traceEndPos = target.EyePosition() + VortexBulletHit ornull vortexHit = VortexBulletHitCheck( attacker, traceStartPos, traceEndPos ) + if ( vortexHit != null ) + { + return false + } + } + } + + if ( !CheckVerticallyCloseEnough( attacker, target ) ) + return false + + //No necksnaps while wall running or mantling + if ( attacker.IsWallRunning() ) + return false + + if ( attacker.IsTraversing() ) + return false + + if ( target.IsPlayer() ) //Disallow execution on a bunch of player-only actions + { + + if ( target.IsHuman() ) + { + if ( target.IsWallRunning() ) + return false + + if ( target.IsTraversing() ) + return false + + if ( !target.IsOnGround() ) //disallow mid-air necksnaps. Can't really do that for Titan executions since dash puts them in mid air... will have visual glitches unfortunately. + return false + + if ( target.IsCrouched() ) + return false + + if ( Rodeo_IsAttached( target ) ) + return false + } + } + + if ( target.IsPhaseShifted() ) + return false + + //Disallow executions on contextActions marked Busy. Note that this allows + //execution on melee and leeching context actions! + if ( target.ContextAction_IsBusy() ) + return false + + if ( target.IsNPC() ) //NPC only checks + { + if ( target.ContextAction_IsActive() ) + return false + + if ( !target.IsInterruptable() ) + return false + } + + if ( attacker.GetTeam() == target.GetTeam() ) + return false + +#if SERVER + if ( "syncedMeleeAttacker" in target.s ) //Don't allow necksnap on a guy who'se already getting necksnapped + return false +#endif // #if SERVER + + SyncedMeleeChooser ornull actions = GetSyncedMeleeChooserForPlayerVsTarget( attacker, target ) + if ( actions == null ) + return false + expect SyncedMeleeChooser( actions ) + + SyncedMelee ornull action = FindBestSyncedMelee( attacker, target, actions ) + if ( action == null ) + return false + + if ( !PlayerMelee_IsExecutionReachable( attacker, target, 0.3 ) ) + return false + + return true +} + +bool function CodeCallback_IsValidMeleeAttackTarget( entity attacker, entity target ) +{ + if ( attacker == target ) + return false + + if ( target.IsBreakableGlass() ) + return true + + if ( !CanBeMeleed( target ) ) + return false + + if ( attacker.GetTeam() == target.GetTeam() ) + return false + +#if SERVER + if ( target.IsPlayer() ) + { + //Make titans not able to melee the pilot who is doing the embark animation + if ( GetTitanBeingRodeoed( target ) == attacker ) + return false + } +#endif // #if SERVER + + if ( target.IsPhaseShifted() ) + return false + + if ( target.GetParent() == attacker ) + return false + + #if SERVER //Awkward, needed because it's CBaseCombatCharacter on server and C_BaseCombatCharacter on client, and because we allow melee on non BaseCombatCharacters like props that don't have ContextActions defined + if ( target instanceof CBaseCombatCharacter && target.ContextAction_IsMeleeExecutionTarget() ) //Don't lunge towards a victim that is already being executed ) + return false + #elseif CLIENT + if ( target instanceof C_BaseCombatCharacter && target.ContextAction_IsMeleeExecutionTarget() ) //Don't lunge towards a victim that is already being executed ) + return false + #endif + + entity meleeWeapon = attacker.GetMeleeWeapon() + if ( !IsValid( meleeWeapon ) ) + return false; + + if ( !meleeWeapon.GetMeleeCanHitHumanSized() && IsHumanSized( target ) ) + return false; + if ( !meleeWeapon.GetMeleeCanHitTitans() && target.IsTitan() ) + return false; + + return true +} + +void function CodeCallback_OnMeleePressed( entity player ) +{ +#if SERVER + print( "SERVER: " + player + " pressed melee\n" ) +#else + print( "CLIENT: " + player + " pressed melee\n" ) +#endif + + if ( !Melee_IsAllowed( player ) ) + { +#if SERVER + print( "SERVER: Melee_IsAllowed() for " + player + " is false\n" ) +#else + print( "CLIENT: Melee_IsAllowed() for " + player + " is false\n" ) +#endif + return + } + +#if SERVER + if ( svGlobal.cloakBreaksOnMelee && IsCloaked( player ) ) + player.SetCloakFlicker( 1.0, 2.0 ) +#endif // #if SERVER + + if ( player.IsWeaponDisabled() ) + { +#if SERVER + print( "SERVER: IsWeaponDisabled() for " + player + " is true\n" ) +#else + print( "CLIENT: IsWeaponDisabled() for " + player + " is true\n" ) +#endif + return + } + + if ( player.PlayerMelee_GetState() != PLAYER_MELEE_STATE_NONE ) + { +#if SERVER + print( "SERVER: PlayerMelee_GetState() for " + player + " is " + player.PlayerMelee_GetState() + "\n" ) +#else + print( "CLIENT: PlayerMelee_GetState() for " + player + " is " + player.PlayerMelee_GetState() + "\n" ) +#endif + return + } + + if ( !IsAlive( player ) ) + { +#if SERVER + print( "SERVER: " + player + " is dead\n" ) +#else + print( "CLIENT: " + player + " is dead\n" ) +#endif + return + } + + thread CodeCallback_OnMeleePressed_InternalThread( player ) +} + +void function CodeCallback_OnMeleePressed_InternalThread( entity player ) +{ + if ( player.IsTitan() ) + { + TitanUnsyncedMelee( player ) + } + else if ( player.IsHuman() ) + { + const float STUN_EFFECT_CUTOFF = 0.05 + float movestunEffect = StatusEffect_Get( player, eStatusEffect.move_slow ) + bool movestunBlocked = (movestunEffect > STUN_EFFECT_CUTOFF) + + HumanUnsyncedMelee( player, movestunBlocked ) + } +} + +//void function CodeCallback_OnMeleeHeld( entity player ) +//{ +//} + +//void function CodeCallback_OnMeleeReleased( entity player ) +//{ +//} + +bool function ShouldHolsterWeaponForSyncedMelee( entity player ) +{ + if ( player.GetPlayerSettings() == "titan_ogre_minigun" ) + return false + + return ShouldHolsterWeaponForMelee( player ) +} + +bool function ShouldHolsterWeaponForMelee( entity player ) +{ + #if !SERVER + return true + #endif + + if ( !player.IsTitan() ) + return true + + return Time() - player.s.startDashMeleeTime > 1.0 //Fix issue with gun being out when it shouldn't, according to Mackey... +} + +#if SERVER +bool function NPCTriesSyncedMeleeVsPlayer( entity npc, entity player ) +{ + Assert( npc.IsNPC() ) + Assert( player.IsPlayer() ) + Assert( IsAlive( player ) ) + Assert( player.IsPlayer() ) + Assert( IsPilot( player ) ) + if ( player.ContextAction_IsBusy() ) + return false + + //#if SERVER + //player.PlayerMelee_SetState( PLAYER_MELEE_STATE_HUMAN_EXECUTION ) + //#else + //player.PlayerMelee_SetState( PLAYER_MELEE_STATE_HUMAN_EXECUTION_PREDICTED ) + //#endif + + return DoSyncedMelee( npc, player ) +} +#endif + + + +bool function PlayerTriesSyncedMelee( entity player, entity target ) +{ + if ( !target ) + return false + if ( !IsAlive( target ) ) + return false + + if ( target.ContextAction_IsBusy() ) + return false + + if ( player.IsTitan() ) + { +#if SERVER + player.PlayerMelee_SetState( PLAYER_MELEE_STATE_TITAN_EXECUTION ) +#else + player.PlayerMelee_SetState( PLAYER_MELEE_STATE_TITAN_EXECUTION_PREDICTED ) +#endif + } + else + { +#if SERVER + player.PlayerMelee_SetState( PLAYER_MELEE_STATE_HUMAN_EXECUTION ) +#else + player.PlayerMelee_SetState( PLAYER_MELEE_STATE_HUMAN_EXECUTION_PREDICTED ) +#endif + } + + if ( !player.Lunge_IsActive() || !player.Lunge_IsGroundExecute() || !player.Lunge_IsLungingToEntity() || (player.Lunge_GetTargetEntity() != target) ) + { +#if SERVER + print( "SERVER: " + player + " is calling Lunge_SetTargetEntity() from PlayerTriesSyncedMelee()\n" ) +#else + print( "CLIENT: " + player + " is calling Lunge_SetTargetEntity() from PlayerTriesSyncedMelee()\n" ) +#endif + player.Lunge_SetTargetEntity( target, false ) + } + +#if SERVER + OnThreadEnd( + function() : ( player, target ) + { + if ( IsValid( player ) && player.IsPlayer() ) + { + RemoveCinematicFlag( player, CE_FLAG_TITAN_3P_CAM ) + RemoveCinematicFlag( player, CE_FLAG_EXECUTION ) + } + if ( IsValid( target ) && target.IsPlayer() ) + { + RemoveCinematicFlag( target, CE_FLAG_TITAN_3P_CAM ) + RemoveCinematicFlag( target, CE_FLAG_EXECUTION ) + } + } + ) + + if ( player.IsTitan() ) + TransferDamageHistoryToTarget( target ) + if ( player.IsPlayer() ) + { + AddCinematicFlag( player, CE_FLAG_TITAN_3P_CAM ) + AddCinematicFlag( player, CE_FLAG_EXECUTION ) + } + if ( IsValid( target ) && target.IsPlayer() ) + { + AddCinematicFlag( target, CE_FLAG_TITAN_3P_CAM ) + AddCinematicFlag( player, CE_FLAG_EXECUTION ) + } +#endif + + bool success = DoSyncedMelee( player, target ) + if ( !success ) + { + player.Lunge_ClearTarget() + } + + return success +} + +function TransferDamageHistoryToTarget( entity target ) +{ + entity titanSoul = target.GetTitanSoul() + target.e.recentDamageHistory = titanSoul.e.recentDamageHistory +} + +bool function DoSyncedMelee( entity player, entity target ) +{ + SyncedMeleeChooser ornull actions = GetSyncedMeleeChooserForPlayerVsTarget( player, target ) + + Assert( actions != null, "No melee action for " + player + " vs " + target ) + expect SyncedMeleeChooser( actions ) + +#if SERVER + if ( player.IsPlayer() ) + { + PlayerMelee_StartLagCompensateTargetForLunge( player, target ) + } +#endif // #if SERVER + + SyncedMelee ornull action = FindBestSyncedMelee( player, target, actions ) + +#if SERVER + if ( player.IsPlayer() ) + { + + player.ForceStand() + player.UnforceStand() + PlayerMelee_FinishLagCompensateTarget( player ) + } +#endif // #if SERVER + + if ( action == null ) + return false + + expect SyncedMelee( action ) + + + + player.Signal( "OnSyncedMelee" ) + target.Signal( "OnSyncedMelee" ) + player.Signal( "OnSyncedMeleeAttacker" ) + target.Signal( "OnSyncedMeleeVictim" ) + +#if SERVER + player.p.lastExecutionUsed = action.ref + + if ( actions in file.syncedMeleeServerCallbacks ) + thread SyncedMeleeServerCallbacks( actions, action, player, target ) + bool functionref( SyncedMelee action, entity player, entity target ) think = file.syncedMeleeServerThink[ actions ] + return think( action, player, target ) +#endif // #if SERVER + + return true +} + +void function SyncedMeleeServerCallbacks( SyncedMeleeChooser actions, SyncedMelee action, entity player, entity target ) +{ + // Added via AddSyncedMeleeServerCallback + foreach ( index, _ in file.syncedMeleeServerCallbacks[ actions ] ) + { + void functionref( SyncedMeleeChooser actions, SyncedMelee action, entity player, entity target ) item = file.syncedMeleeServerCallbacks[ actions ][ index ] + item( actions, action, player, target ) + } +} + +/* +void function CodeCallback_OnMeleeReleased( entity player ) +{ +} +*/ + +function TextDebug( string msg ) +{ + wait 0.5 + printt( msg ) +} + +bool function ShouldClampTargetVelocity( vector targetVelocity, vector pushBackVelocity, float clampRatio ) +{ + float dot = DotProduct( targetVelocity, pushBackVelocity ) + if ( dot < 0 ) + return true + + if ( dot <= 0 ) + return false + + float velRatio = LengthSqr( targetVelocity ) / LengthSqr( pushBackVelocity ) + + return velRatio < clampRatio +} + +bool function CanBeMeleed( entity target ) +{ + if ( target.IsPlayer() ) + return true + if ( target.IsNPC() ) + return true + + if ( ObjectCanBeMeleed( target ) ) + return true + + return false +} + +// IMPORTANT: Only used for non-player, non-living special cases like prop_dynamics we want to be able to melee (drones, etc) +bool function ObjectCanBeMeleed( entity ent ) +{ + if ( !( "canBeMeleed" in ent.s ) ) + return false + + return expect bool( ent.s.canBeMeleed ) +} + +// IMPORTANT: Only used for non-player, non-living special cases like prop_dynamics we want to be able to melee (drones, etc) +function SetObjectCanBeMeleed( entity ent, bool value ) +{ + Assert( !ent.IsPlayer(), ent + " should not be a player. This is for non-player, non-NPC entities.") + Assert( !ent.IsNPC(), ent + " should not be an NPC. This is for non-player, non-NPC entities.") + + if ( !( "canBeMeleed" in ent.s ) ) + ent.s.canBeMeleed <- false + + ent.s.canBeMeleed = value +} + +//function TitanExposionDeath( entity titan, entity attacker ) +//{ +// if ( !IsAlive( titan ) ) +// return +// +// ExplodeTitanBits( titan ) +// // and your pretty titan too! +// +// //TitanEjectExplosion +// table deathTable = { scriptType = damageTypes.titanMelee, forceKill = true, damageType = DMG_MELEE_EXECUTION, damageSourceId = eDamageSourceId.titan_execution } +// titan.TakeDamage( titan.GetMaxHealth() + 1, attacker, attacker, deathTable ) +//} + +#if SERVER +vector function GetRefAnglesBetweenEnts( entity attacker, entity target ) +{ + vector endOrigin = target.GetOrigin() + vector startOrigin = attacker.GetOrigin() + vector refVec = endOrigin - startOrigin + vector refAng = VectorToAngles( refVec ) + if ( fabs( AngleNormalize( refAng.x ) ) > 35 ) //If pitch is too much, use angles from either attacker or target + { + if ( attacker.IsTitan() ) + refAng = attacker.GetAngles() //Doing titan synced kill from front, so use attacker's origin + else + refAng = target.GetAngles() // Doing rear necksnap, so use target's angles + } + return refAng +} + +entity function CreateMeleeScriptMoverBetweenEnts( entity attacker, entity target ) +{ + vector refAng = GetRefAnglesBetweenEnts( attacker, target ) + + vector endOrigin = target.GetOrigin() + vector startOrigin = attacker.GetOrigin() + vector refVec = endOrigin - startOrigin + vector refPos = endOrigin - refVec * 0.5 + + entity ref = CreateOwnedScriptMover( attacker ) + ref.SetOrigin( refPos ) + ref.SetAngles( refAng ) + + return ref +} +#endif // SERVER + +void function AddSyncedMelee( SyncedMeleeChooser chooser, SyncedMelee melee ) +{ + // sqr the distance + melee.distanceSqr = melee.distance * melee.distance + + chooser.syncedMelees.append( melee ) +} + +SyncedMelee ornull function FindBestSyncedMelee( entity attacker, entity target, SyncedMeleeChooser actions ) +{ + #if CLIENT + Assert( attacker == GetLocalViewPlayer() ) + #endif // CLIENT + + vector absTargetToPlayerDir + if ( attacker.IsPlayer() && attacker.Lunge_IsActive() && (attacker.Lunge_GetTargetEntity() == target) ) + { + absTargetToPlayerDir = attacker.Lunge_GetStartPositionOffset() + absTargetToPlayerDir = Normalize( absTargetToPlayerDir ) + } + else + { + vector attackerPos = actions.attackerOriginFunc( attacker ) // + ( attacker.GetVelocity() * SMOOTH_TIME ) + vector targetPos = actions.targetOriginFunc( target ) + + if ( attackerPos == targetPos ) + { + absTargetToPlayerDir = < 1, 0, 0 > + } + else + { + absTargetToPlayerDir = Normalize( attackerPos - targetPos ) + } + } + + vector angles = attacker.EyeAngles() + vector forward = AnglesToForward( angles ) + + vector relTargetToPlayerDir = CalcRelativeVector( < 0, target.EyeAngles().y, 0 >, absTargetToPlayerDir ) + + array bestActions + float bestDot = -2.0 + float distSqr = LengthSqr( actions.attackerOriginFunc( attacker ) - actions.targetOriginFunc( target ) ) + + bool npcTarget = target.IsNPC() + bool playerAttacker = attacker.IsPlayer() + + int health = target.GetHealth() + float healthRatio = HealthRatio( target ) + int meleeDamage + if ( attacker.IsNPC() ) + { + meleeDamage = attacker.GetMeleeDamageMaxForTarget( target ) + } + else if ( attacker.IsPlayer() ) + { + meleeDamage = GetPlayerMeleeDamage( attacker ) + } + + SyncedMelee ornull returnVal = null + +#if MP + if ( IsPilot( attacker ) ) + { + PilotLoadoutDef loadout = GetActivePilotLoadout( attacker ) + + foreach ( action in actions.syncedMelees ) + { + if ( action.ref != loadout.execution ) + continue + + if ( npcTarget && !action.canTargetNPCs ) + break + + if ( playerAttacker && !action.usableByPlayers ) + break + + if ( healthRatio < action.targetMinHealthRatio ) + break + + if ( healthRatio > action.targetMaxHealthRatio ) + break + + if ( action.onlyIfLethal && health > meleeDamage ) + break + + if ( distSqr > action.distanceSqr ) + break + + float dot = relTargetToPlayerDir.Dot( action.direction ) + if ( dot < action.minDot ) + break + +#if SERVER + //Random Execution + if ( string( attacker.GetPersistentVar( "activePilotLoadout.execution" )) == "execution_random") + { + returnVal = PickRandomExecution(actions, attacker) + break + } +#endif + + returnVal = action + break + } + } + else + { +#endif + foreach ( action in actions.syncedMelees ) + { + if ( !action.enabled ) + continue + + if ( npcTarget && !action.canTargetNPCs ) + continue + + if ( playerAttacker && !action.usableByPlayers ) + continue + + if ( healthRatio < action.targetMinHealthRatio ) + continue + + if ( healthRatio > action.targetMaxHealthRatio ) + continue + + if ( action.onlyIfLethal && health > meleeDamage ) + continue + + if ( distSqr > action.distanceSqr ) + continue + + float dot = relTargetToPlayerDir.Dot( action.direction ) + + //printt( "Dot: " + dot ) + + if ( dot < action.minDot ) + continue + + if ( dot == bestDot ) + { + bestActions.append( action ) + continue + } + + if ( dot > bestDot ) + { + // found new best dot + bestActions.clear() + bestDot = dot + bestActions.append( action ) + } + } + + if ( bestActions.len() ) + returnVal = bestActions.getrandom() +#if MP + } +#endif + + return returnVal +} + +string function GetAttackerSyncedMelee( entity ent ) +{ + if ( ent.IsPlayer() ) + { + // TODO: for MP, change this to be based on loadout choice + string bodyType = GetPlayerBodyType( ent ) + if ( bodyType == "human" ) + { + entity weapon = ent.GetActiveWeapon() + var weaponSyncedMelee + + if ( IsValid( weapon ) ) + weaponSyncedMelee = weapon.GetWeaponInfoFileKeyField( "synced_melee_action" ) + + if ( weaponSyncedMelee ) + return string( weaponSyncedMelee ) + } + + return bodyType + + } + else if ( IsProwler( ent ) ) + { + return "prowler" + } + else if ( IsPilotElite( ent ) ) + { + return "pilotelite" + } + else if ( IsSpectre( ent ) ) + { + return "spectre" + } + else if ( ent.IsNPC() ) + { + return ent.GetBodyType() + } + else if ( ent.IsTitan() ) + { + return "titan" + } + + unreachable +} + +string function GetVictimSyncedMeleeTargetType( entity ent ) +{ + string targetType + + if ( ent.IsPlayer() && GetPlayerBodyType( ent ) == "human" ) + { + targetType = "human" + } + else if ( IsProwler( ent ) ) + { + targetType = "prowler" + } + else if ( IsPilotElite( ent ) ) + { + targetType = "pilotelite" + } + else if ( ent.IsNPC() ) + { + targetType = ent.GetBodyType() + } + else if ( ent.IsTitan() ) + { + targetType = "titan" + } + else + { + Assert( 0, "Unknown ent type" ) + } + + return targetType +} + +SyncedMeleeChooser ornull function GetSyncedMeleeChooserForPlayerVsTarget( entity attacker, entity target ) +{ + string attackerType = GetAttackerSyncedMelee( attacker ) + string targetType = GetVictimSyncedMeleeTargetType( target ) + + if ( !( attackerType in file.syncedMeleeChoosers ) ) + return null + + if ( !( targetType in file.syncedMeleeChoosers[ attackerType ] ) ) + return null + + return file.syncedMeleeChoosers[ attackerType ][ targetType ] +} + +void function CodeCallback_OnMeleeAttackAnimEvent( entity player ) +{ + Assert( IsValid( player ) ) +#if SERVER + print( "SERVER: " + player + " is calling CodeCallback_OnMeleeAttackAnimEvent()\n" ) +#else + print( "CLIENT: " + player + " is calling CodeCallback_OnMeleeAttackAnimEvent()\n" ) +#endif + if ( player.PlayerMelee_IsAttackActive() ) + { + if ( player.IsTitan() ) + TitanMeleeAttack( player ) + else if ( player.IsHuman() ) + HumanMeleeAttack( player ) + } +} + +bool function IsInExecutionMeleeState( entity player ) +{ + local meleeState = player.PlayerMelee_GetState() + switch ( meleeState ) + { + case PLAYER_MELEE_STATE_HUMAN_EXECUTION_PREDICTED: + case PLAYER_MELEE_STATE_HUMAN_EXECUTION: + case PLAYER_MELEE_STATE_TITAN_EXECUTION_PREDICTED: + case PLAYER_MELEE_STATE_TITAN_EXECUTION: + return true + + default: + return false + } + + unreachable +} + +#if SERVER +void function InitMeleeAnimEventCallbacks( entity player ) +{ + AddAnimEvent( player, "screen_blackout", MeleeBlackoutScreen_AE ) +} + +void function MeleeBlackoutScreen_AE( entity player ) +{ + ScreenFadeToBlack( player, 0.7, 1.2 ) +} +#endif + +bool function ShouldPlayerExecuteTarget( entity player, entity target ) +{ + if ( player.IsTitan() ) + { + if ( !target.IsTitan() ) + return false + + if ( Flag( "ForceSyncedMelee" ) ) + return true + + if ( !GetDoomedState( target ) ) + return false + + entity soul = target.GetTitanSoul() + if ( soul != null ) + { + if ( soul.GetShieldHealth() > 0 && GetCurrentPlaylistVarInt( "titan_shield_blocks_execution", 0 ) != 0 ) + return false + } + + if ( !SyncedMelee_IsAllowed( player ) ) + return false + + return true + } + + if ( player.IsHuman() ) + { + if ( !IsHumanSized( target ) ) + return false + +#if SERVER + if ( Flag( "ForceSyncedMelee" ) ) + return true +#endif // #if SERVER + + if ( !SyncedMelee_IsAllowed( player ) ) + return false + } + + return true +} + +vector function ClampVerticalVelocity( vector targetVelocity, float maxVerticalVelocity ) +{ + vector clampedVelocity = targetVelocity + if ( clampedVelocity.z > maxVerticalVelocity ) + { + printt( "clampedVelocity.z: " + clampedVelocity.z +", maxVerticalVelocity:" + maxVerticalVelocity ) + clampedVelocity = Vector( targetVelocity.x, targetVelocity.y, maxVerticalVelocity ) + } + + return clampedVelocity +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +bool function CheckVerticallyCloseEnough( entity attacker, entity target ) +{ + vector attackerOrigin = attacker.GetOrigin() + vector targetOrigin = target.GetOrigin() + + float verticalDistance = fabs( attackerOrigin.z - targetOrigin.z ) + float halfHeight = 0 + + if ( attacker.IsTitan() ) + halfHeight = 92.5 + else if ( attacker.IsHuman() ) + halfHeight = 30 + + Assert( halfHeight, "Attacker is neither Titan nor Human" ) + + //printt( "vertical distance: " + verticalDistance ) + return verticalDistance < halfHeight +} + + +entity function GetLungeTargetForPlayer( entity player ) +{ + // Titan melee does not lunge + if ( player.IsTitan() ) + return null + + if ( player.IsPhaseShifted() ) + return null + + entity lungeTarget = PlayerMelee_LungeConeTrace( player, SHARED_CB_IS_VALID_MELEE_ATTACK_TARGET ) + return lungeTarget +} + +#if SERVER +void function Melee_Enable( entity player ) +{ + player.SetPlayerNetBool( "playerAllowedToMelee", true ) +} + +void function Melee_Disable( entity player ) +{ + player.SetPlayerNetBool( "playerAllowedToMelee", false ) +} + +void function SyncedMelee_Enable( entity player ) +{ + player.SetPlayerNetBool( "playerAllowedToSyncedMelee", true ) +} + +void function SyncedMelee_Disable( entity player ) +{ + player.SetPlayerNetBool( "playerAllowedToSyncedMelee", false ) +} +#endif + +bool function Melee_IsAllowed( entity player ) +{ + return player.GetPlayerNetBool( "playerAllowedToMelee" ) +} + +bool function SyncedMelee_IsAllowed( entity player ) +{ + return player.GetPlayerNetBool( "playerAllowedToSyncedMelee" ) +} + +bool function IsAttackerRef( SyncedMelee ornull action, entity target ) +{ + if ( action != null ) + { + expect SyncedMelee( action ) + if ( action.isAttackerRef ) + { + return true + } + } + + if ( !target ) + return true + + if ( !IsValid( target ) ) + return true + + if ( !target.IsPlayer() ) + return true + + return false +} + +#if MP +#if SERVER +SyncedMelee ornull function PickRandomExecution( SyncedMeleeChooser actions, entity attacker ) +{ + array possibleExecutions = [] + + SyncedMelee neckSnap + + foreach ( action in actions.syncedMelees ) + { + if (action.ref == "execution_neck_snap") + neckSnap = action + + if(!IsItemLocked( attacker, action.ref ) && action.ref != "execution_random" && action.ref != attacker.p.lastExecutionUsed) + + possibleExecutions.append(action) + } + + if (possibleExecutions.len() == 0) + return neckSnap + + possibleExecutions.randomize() + + return possibleExecutions[0] +} +#endif +#endif \ No newline at end of file -- cgit v1.2.3