Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1546 lines
51 KiB

#include "cbase.h"
#ifdef CLIENT_DLL
#include "c_asw_player.h"
#include "c_asw_marine.h"
#include "c_asw_game_resource.h"
#include "c_asw_alien.h"
#include "c_asw_weapon.h"
#include "engine/IVDebugOverlay.h"
#include "shake.h"
#include "ivieweffects.h"
#include "asw_input.h"
#include "prediction.h"
#define CASW_Player C_ASW_Player
#define CASW_Marine C_ASW_Marine
#define CASW_Game_Resource C_ASW_Game_Resource
#else
#include "asw_player.h"
#include "asw_marine.h"
#include "asw_game_resource.h"
#include "asw_weapon.h"
#include "util.h"
#include "asw_achievements.h"
#include "asw_missile_round_shared.h"
#include "asw_marine_resource.h"
#endif
#include "asw_gamerules.h"
#include "in_buttons.h"
#include "npcevent.h"
#include "eventlist.h"
#include "asw_shareddefs.h"
#include "asw_util_shared.h"
#include "asw_marine_profile.h"
#include "basecombatweapon_shared.h"
#include "igamemovement.h"
#include "filesystem.h"
#include "asw_melee_system.h"
#include "asw_marine_skills.h"
#include "asw_gamerules.h"
#include "asw_movedata.h"
#include "datacache/imdlcache.h"
#include "asw_weapon_blink.h"
#include "asw_weapon_jump_jet.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
BEGIN_DEFINE_LOGGING_CHANNEL( LOG_ASW_Melee, "ASWMelee", 0, LS_MESSAGE );
ADD_LOGGING_CHANNEL_TAG( "AlienSwarm" );
ADD_LOGGING_CHANNEL_TAG( "Melee" );
END_DEFINE_LOGGING_CHANNEL();
// TODO: Some parts of the clientside prediction are firing multiple times when you have lag. Filter them out if it isn't the first time predicting?
// (Sounds at least)
#define MELEE_FACING_THRESHOLD 0.7071f
// MELEE_CHARGE_SWITCH_START - time after starting a heavy attack that we begin allowing a transition to a charge attack if fire is still pressed
#define MELEE_CHARGE_SWITCH_START 0.25f
CASW_Melee_System* g_pMeleeSystem = NULL;
CASW_Melee_System* ASWMeleeSystem()
{
if ( !g_pMeleeSystem )
{
g_pMeleeSystem = new CASW_Melee_System;
}
return g_pMeleeSystem;
}
ConVar asw_melee_debug( "asw_melee_debug", "0", FCVAR_REPLICATED, "Debugs the melee system. Set to 2 for position updates" );
ConVar asw_melee_require_contact( "asw_melee_require_contact", "0", FCVAR_REPLICATED, "Melee requires contact to transition to the next combo" );
ConVar asw_melee_lock( "asw_melee_lock", "0", FCVAR_REPLICATED, "Marine is moved to the nearest enemy when melee attacking" );
ConVar asw_melee_require_key_release( "asw_melee_require_key_release", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Melee requires key release between attacks" );
ConVar asw_melee_base_damage( "asw_melee_base_damage", "12.0", FCVAR_REPLICATED | FCVAR_CHEAT, "The melee damage that marines do at level 1 (scales up with level)" );
ConVar asw_marine_rolls( "asw_marine_rolls", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "If set, marine will do rolls when jump is pressed" );
#ifdef CLIENT_DLL
ConVar asw_melee_lock_distance( "asw_melee_lock_distance", "35", FCVAR_CHEAT, "Dist marine slides to when locked onto a melee target" );
ConVar asw_melee_lock_slide_speed( "asw_melee_lock_slide_speed", "200", FCVAR_CHEAT, "Speed at which marine slides into place when target locked in melee" );
#endif
mstudioevent_for_client_server_t *GetEventIndexForSequence( mstudioseqdesc_t &seqdesc );
void SetEventIndexForSequence( mstudioseqdesc_t &seqdesc );
int CASW_Melee_System::s_nRollAttackID = -1;
int CASW_Melee_System::s_nKnockdownForwardAttackID = -1;
int CASW_Melee_System::s_nKnockdownBackwardAttackID = -1;
CASW_Melee_System::CASW_Melee_System()
{
LoadMeleeAttacks();
m_bAllowNormalAnimEvents = false;
m_bAttacksValidated = false;
}
CASW_Melee_System::~CASW_Melee_System()
{
m_MeleeAttacks.PurgeAndDeleteElements();
}
void CASW_Melee_System::Reload()
{
m_MeleeAttacks.PurgeAndDeleteElements();
LoadMeleeAttacks();
m_bAttacksValidated = false;
}
void CASW_Melee_System::LoadMeleeAttacks()
{
KeyValues *kv = new KeyValues( "resource/melee_attacks.txt" );
if ( kv->LoadFromFile( g_pFullFileSystem, "resource/melee_attacks.txt" ) )
{
KeyValues *pKeys = kv;
while ( pKeys )
{
CASW_Melee_Attack* pAttack = new CASW_Melee_Attack;
pAttack->ApplyKeyValues( pKeys );
m_MeleeAttacks.AddToTail( pAttack );
pKeys = pKeys->GetNextKey();
}
}
kv->deleteThis();
LinkUpCombos();
CASW_Melee_System::s_nRollAttackID = GetMeleeAttackByName( "Roll" ) ? GetMeleeAttackByName( "Roll" )->m_nAttackID : -1;
CASW_Melee_System::s_nKnockdownForwardAttackID = GetMeleeAttackByName( "KnockdownForward" ) ? GetMeleeAttackByName( "KnockdownForward" )->m_nAttackID : -1;
CASW_Melee_System::s_nKnockdownBackwardAttackID = GetMeleeAttackByName( "KnockdownBackward" ) ? GetMeleeAttackByName( "KnockdownBackward" )->m_nAttackID : -1;
}
void CASW_Melee_System::LinkUpCombos()
{
m_candidateMeleeAttacks.RemoveAll();
// link up the combos
for ( int i = 0 ; i < m_MeleeAttacks.Count() ; i++ )
{
m_MeleeAttacks[i]->m_nAttackID = i + 1;
m_MeleeAttacks[i]->m_CombosFromAttacks.RemoveAll();
m_MeleeAttacks[i]->m_pOnCollisionDoAttack = NULL;
m_MeleeAttacks[i]->m_pForceComboAttack = NULL;
if ( m_MeleeAttacks[i]->m_CombosFromAttackNames.Count() )
{
for ( int j = 0; j < m_MeleeAttacks[i]->m_CombosFromAttackNames.Count(); j++ )
{
for ( int k = 0 ; k < m_MeleeAttacks.Count() ; k++ )
{
if ( k == i )
continue;
if ( !Q_stricmp( m_MeleeAttacks[i]->m_CombosFromAttackNames[j], m_MeleeAttacks[k]->m_szAttackName ) )
{
m_MeleeAttacks[i]->m_CombosFromAttacks.AddToTail( m_MeleeAttacks[k] );
}
}
}
}
if ( m_MeleeAttacks[i]->m_szOnCollisionDoAttackName )
{
for ( int k = 0 ; k < m_MeleeAttacks.Count() ; k++ )
{
if ( k == i )
continue;
if ( !Q_stricmp( m_MeleeAttacks[i]->m_szOnCollisionDoAttackName, m_MeleeAttacks[k]->m_szAttackName ) )
{
m_MeleeAttacks[i]->m_pOnCollisionDoAttack = m_MeleeAttacks[k];
}
}
}
if ( m_MeleeAttacks[i]->m_szForceComboAttackName )
{
for ( int k = 0; k < m_MeleeAttacks.Count() ; k++ )
{
if ( k == i )
continue;
if ( !Q_stricmp( m_MeleeAttacks[i]->m_szForceComboAttackName, m_MeleeAttacks[k]->m_szAttackName ) )
{
m_MeleeAttacks[i]->m_pForceComboAttack = m_MeleeAttacks[k];
}
}
}
}
}
void CASW_Melee_System::ValidateMeleeAttacks( CASW_Marine *pMarine )
{
for ( int i = m_MeleeAttacks.Count() - 1; i >= 0; --i )
{
if ( pMarine->LookupSequence( m_MeleeAttacks[i]->m_szSequenceName ) == -1 )
{
ASW_MEL_WAR( "Sequence %s not found for melee attack %s\n", m_MeleeAttacks[i]->m_szSequenceName, m_MeleeAttacks[i]->m_szAttackName );
// TODO: Fix this - not all marine models have the same attacks, so removing entries from the list when they're invalid will result in mismatched lists
//CASW_Melee_Attack *pInvalidAttack = m_MeleeAttacks[i];
//m_MeleeAttacks.Remove( i );
//delete pInvalidAttack;
}
}
LinkUpCombos();
}
static Animevent s_PredictedAnimEvents[]={
AE_MELEE_DAMAGE,
AE_MELEE_START_COLLISION_DAMAGE,
AE_MELEE_STOP_COLLISION_DAMAGE,
AE_START_DETECTING_COMBO,
AE_STOP_DETECTING_COMBO,
AE_COMBO_TRANSITION,
AE_ALLOW_MOVEMENT,
AE_CL_CREATE_PARTICLE_EFFECT,
AE_CL_STOP_PARTICLE_EFFECT,
AE_CL_ADD_PARTICLE_EFFECT_CP,
AE_CL_PLAYSOUND,
AE_SKILL_EVENT,
};
// player has pressed melee attack key
void CASW_Melee_System::ProcessMovement( CASW_Marine *pMarine, CMoveData *pMoveData )
{
if ( !pMarine )
{
return;
}
CASW_MoveData *pASWMove = static_cast<CASW_MoveData*>( pMoveData );
if ( !m_bAttacksValidated )
{
ValidateMeleeAttacks( pMarine );
m_bAttacksValidated = true;
}
//if ( pMarine->IsMeleeInhibited() )
//{
//return;
//}
//CASW_Weapon *pWeapon = pMarine->GetActiveASWWeapon();
#ifdef MELEE_CHARGE_ATTACKS
int nMeleeButton = MELEE_BUTTON;
int nAltPressed = nMeleeButton & ((pMoveData->m_nOldButtons ^ pMoveData->m_nButtons) & pMoveData->m_nButtons);
if ( nAltPressed || !(pMoveData->m_nOldButtons & nMeleeButton) )
{
pMarine->m_flMeleeHeavyKeyHoldStart = gpGlobals->curtime;
}
if ( (pMoveData->m_nButtons & nMeleeButton) &&
gpGlobals->curtime >= (pMarine->m_flMeleeHeavyKeyHoldStart + MELEE_CHARGE_SWITCH_START) )
{
pMarine->m_bMeleeHeavyKeyHeld = true;
}
else
{
pMarine->m_bMeleeHeavyKeyHeld = false;
}
int nButtonsActivated = (pMoveData->m_nButtons ^ pMoveData->m_nOldButtons) & pMoveData->m_nOldButtons; // in charge attack mode, we have to initiate melee on mouse up
#else
int nButtonsActivated = (pMoveData->m_nButtons ^ pMoveData->m_nOldButtons) & pMoveData->m_nButtons;
#endif
CASW_Melee_Attack *pAttack = pMarine->GetCurrentMeleeAttack();
bool bStumbling = ( pAttack &&
( !Q_stricmp( pAttack->m_szAttackName, "StumbleForward" ) ||
!Q_stricmp( pAttack->m_szAttackName, "StumbleRightward" ) ||
!Q_stricmp( pAttack->m_szAttackName, "StumbleBackward" ) ||
!Q_stricmp( pAttack->m_szAttackName, "StumbleLeftward" ) ||
!Q_stricmp( pAttack->m_szAttackName, "StumbleShortForward" ) ||
!Q_stricmp( pAttack->m_szAttackName, "StumbleShortRightward" ) ||
!Q_stricmp( pAttack->m_szAttackName, "StumbleShortLeftward" ) ||
!Q_stricmp( pAttack->m_szAttackName, "StumbleShortBackward" )
)
);
bool bKnockedDown = ( pAttack &&
( !Q_stricmp( pAttack->m_szAttackName, "KnockdownForward" ) ||
!Q_stricmp( pAttack->m_szAttackName, "KnockdownBackward" ) ) );
bool bTryForcedAction = ( !bKnockedDown && ( !bStumbling || pASWMove->m_iForcedAction == FORCED_ACTION_KNOCKDOWN_FORWARD || pASWMove->m_iForcedAction == FORCED_ACTION_KNOCKDOWN_BACKWARD ) );
if ( bTryForcedAction && pASWMove->m_iForcedAction != 0 )
{
if ( pMarine->CanDoForcedAction( pASWMove->m_iForcedAction ) )
{
pAttack = NULL;
switch( pASWMove->m_iForcedAction )
{
case FORCED_ACTION_STUMBLE_FORWARD: pAttack = GetMeleeAttackByName( "StumbleForward" ); break;
case FORCED_ACTION_STUMBLE_RIGHT: pAttack = GetMeleeAttackByName( "StumbleRightward" ); break;
case FORCED_ACTION_STUMBLE_LEFT: pAttack = GetMeleeAttackByName( "StumbleLeftward" ); break;
case FORCED_ACTION_STUMBLE_BACKWARD: pAttack = GetMeleeAttackByName( "StumbleBackward" ); break;
case FORCED_ACTION_STUMBLE_SHORT_FORWARD: pAttack = GetMeleeAttackByName( "StumbleShortForward" ); break;
case FORCED_ACTION_STUMBLE_SHORT_RIGHT: pAttack = GetMeleeAttackByName( "StumbleShortRightward" ); break;
case FORCED_ACTION_STUMBLE_SHORT_LEFT: pAttack = GetMeleeAttackByName( "StumbleShortLeftward" ); break;
case FORCED_ACTION_STUMBLE_SHORT_BACKWARD: pAttack = GetMeleeAttackByName( "StumbleShortBackward" ); break;
case FORCED_ACTION_KNOCKDOWN_FORWARD: pAttack = GetMeleeAttackByName( "KnockdownForward" ); break;
case FORCED_ACTION_KNOCKDOWN_BACKWARD: pAttack = GetMeleeAttackByName( "KnockdownBackward" ); break;
case FORCED_ACTION_CHAINSAW_SYNC_KILL: pAttack = GetMeleeAttackByName( "SyncKillChainsaw" ); break;
case FORCED_ACTION_BLINK:
{
CASW_Weapon *pWeapon = pMarine->GetASWWeapon( ASW_INVENTORY_SLOT_EXTRA );
if ( pWeapon && pWeapon->Classify() == CLASS_ASW_BLINK )
{
CASW_Weapon_Blink *pBlink = assert_cast<CASW_Weapon_Blink*>( pWeapon );
pBlink->DoBlink();
}
}
break;
case FORCED_ACTION_JUMP_JET:
{
CASW_Weapon *pWeapon = pMarine->GetASWWeapon( ASW_INVENTORY_SLOT_EXTRA );
if ( pWeapon && pWeapon->Classify() == CLASS_ASW_JUMP_JET )
{
CASW_Weapon_Jump_Jet *pJet = assert_cast<CASW_Weapon_Jump_Jet*>( pWeapon );
pJet->DoJumpJet();
}
}
break;
}
if ( pAttack )
{
#ifdef CLIENT_DLL
ScreenShake_t shake;
shake.command = SHAKE_START;
shake.amplitude = 20.0f;
shake.frequency = 750.f;
shake.duration = 0.5f;
if ( pASWMove->m_iForcedAction >= FORCED_ACTION_STUMBLE_SHORT_FORWARD
&& pASWMove->m_iForcedAction <= FORCED_ACTION_STUMBLE_SHORT_BACKWARD )
{
shake.amplitude = 10.0f;
shake.duration = 0.25f;
}
//HACK_GETLOCALPLAYER_GUARD( "ASW_ShakeAnimEvent" );
static float s_flLastShakeTime = 0.0f;
if ( Plat_FloatTime() > s_flLastShakeTime + 3.0f )
{
if ( !( prediction && prediction->InPrediction() && !prediction->IsFirstTimePredicted() ) )
{
GetViewEffects()->Shake( shake );
}
s_flLastShakeTime = Plat_FloatTime();
}
#endif
StartMeleeAttack( pAttack, pMarine, pMoveData );
if ( pASWMove->m_iForcedAction == FORCED_ACTION_KNOCKDOWN_FORWARD )
{
pMarine->m_flMeleeYaw = pMarine->m_flKnockdownYaw;
pMarine->m_bFaceMeleeYaw = true;
}
else if ( pASWMove->m_iForcedAction == FORCED_ACTION_KNOCKDOWN_BACKWARD )
{
pMarine->m_flMeleeYaw = pMarine->m_flKnockdownYaw + 180;
pMarine->m_bFaceMeleeYaw = true;
}
}
}
pMarine->ClearForcedActionRequest();
}
else if ( gpGlobals->curtime >= pMarine->m_fNextMeleeTime )
{
int nMeleeButton = MELEE_BUTTON;
bool bMeleePressed = ( ( pMoveData->m_nButtons & nMeleeButton ) || ( nButtonsActivated & nMeleeButton ) );
//bool bAttackPressedWithMeleeWeapon = ( ( pMoveData->m_nButtons & IN_ATTACK ) && pWeapon && pWeapon->ASWIsMeleeWeapon() && !pMarine->IsFiringInhibited() );
bool bAttackPressedWithMeleeWeapon = false;
if ( ( pMoveData->m_nButtons & nMeleeButton ) && !( pMoveData->m_nOldButtons & nMeleeButton ) )
{
CASW_Player *pPlayer = pMarine->GetCommander();
if ( pPlayer )
{
pMarine->m_iUsableItemsOnMeleePress = pPlayer->GetNumUseEntities();
}
}
// if we're using the USE key to melee, then don't melee when there are usable entities nearby
if ( bMeleePressed && nMeleeButton == IN_USE )
{
CASW_Player *pPlayer = pMarine->GetCommander();
if ( pPlayer && pPlayer->GetNumUseEntities() > 0 )
{
bMeleePressed = false;
}
}
if ( bMeleePressed || bAttackPressedWithMeleeWeapon )
{
OnMeleePressed( pMarine, pMoveData );
}
if ( pMoveData->m_nButtons & IN_JUMP )
{
OnJumpPressed( pMarine, pMoveData );
}
}
if ( pMarine && pMarine->GetCurrentMeleeAttack() )
{
SetupMeleeMovement( pMarine, pMoveData );
}
}
void CASW_Melee_System::OnMeleePressed( CASW_Marine *pMarine, CMoveData *pMoveData )
{
if ( !pMarine || !pMoveData || !pMarine->GetMarineProfile() )
return;
CASW_Melee_Attack *pCurrentAttack = pMarine->GetCurrentMeleeAttack();
if ( pCurrentAttack )
{
if ( pMarine->m_bMeleeComboKeypressAllowed && ( !asw_melee_require_key_release.GetBool() || pMarine->m_bMeleeKeyReleased ) )
{
// the player has pressed melee attack again at the right time, when the combo transition event occurs, the next attack will start
if ( asw_melee_debug.GetInt() == 3 )
{
Msg( "%s:%f m_bMeleeComboKeyPressed set to true\n", pMarine->IsServer() ? "s" : " c", gpGlobals->curtime );
}
pMarine->m_bMeleeComboKeyPressed = true;
}
return;
}
// Check to see if we should transition to a charge combo
#ifdef MELEE_CHARGE_ATTACKS
if ( pMarine->m_bMeleeHeavyKeyHeld )
{
if ( asw_melee_debug.GetInt() == 3 )
{
Msg( "%s:%f m_bMeleeChargeActivate set to true\n", pMarine->IsServer() ? "s" : " c", gpGlobals->curtime );
}
pMarine->m_bMeleeChargeActivate = true;
}
#endif
UpdateCandidateMeleeAttacks( pMarine, pMoveData );
if ( m_candidateMeleeAttacks.Count() <= 0 )
return;
// count how many attacks at the end of the list have the same priority
float flHighestPriority = m_candidateMeleeAttacks.Tail()->m_flPriority;
int iCount = 0;
for ( int i = m_candidateMeleeAttacks.Count() - 1; i >= 0; i-- )
{
if ( m_candidateMeleeAttacks[i]->m_flPriority == flHighestPriority )
{
iCount++;
}
}
StartMeleeAttack( m_candidateMeleeAttacks[ SharedRandomInt( "MeleeChoice", m_candidateMeleeAttacks.Count() - iCount, m_candidateMeleeAttacks.Count() - 1 ) ], pMarine, pMoveData );
}
// do rolls when jump is pressed
void CASW_Melee_System::OnJumpPressed( CASW_Marine *pMarine, CMoveData *pMoveData )
{
if ( !pMarine || !pMoveData || !pMarine->GetMarineProfile() || !ASWGameRules() )
return;
if ( !asw_marine_rolls.GetBool() )
return;
// no rolling if in the middle of an attack
if ( pMarine->GetCurrentMeleeAttack() )
return;
CASW_Weapon *pWeapon = pMarine->GetActiveASWWeapon();
if ( pWeapon )
{
pWeapon->OnStartedRoll();
}
StartMeleeAttack( GetMeleeAttackByID( CASW_Melee_System::s_nRollAttackID ), pMarine, pMoveData );
QAngle angRollDir = vec3_angle;
if ( pMoveData->m_flSideMove == 0.0f && pMoveData->m_flForwardMove == 0.0f )
{
pMarine->m_flMeleeYaw = pMarine->ASWEyeAngles()[ YAW ];
}
else
{
pMarine->m_flMeleeYaw = RAD2DEG(atan2(-pMoveData->m_flSideMove, pMoveData->m_flForwardMove)) + ASWGameRules()->GetTopDownMovementAxis()[YAW]; // assumes 45 degree cam!
}
pMarine->m_bFaceMeleeYaw = true;
// see if we just dodged any ranger shots
#ifdef GAME_DLL
CASW_Player *pPlayer = pMarine->GetCommander();
if ( pPlayer && pMarine->IsInhabited() )
{
const float flNearby = 150.0f;
const float flNearbySqr = flNearby * flNearby;
int nCount = g_vecMissileRounds.Count();
for ( int i = 0; i < nCount; i++ )
{
if ( pMarine->GetAbsOrigin().DistToSqr( g_vecMissileRounds[i]->GetAbsOrigin() ) <= flNearbySqr )
{
pPlayer->AwardAchievement( ACHIEVEMENT_ASW_DODGE_RANGER_SHOT );
if ( pMarine->GetMarineResource() )
{
pMarine->GetMarineResource()->m_bDodgedRanger = true;
}
}
}
}
#endif
}
void CASW_Melee_System::UpdateCandidateMeleeAttacks( CASW_Marine *pMarine, CMoveData *pMoveData )
{
// build a list of melee attacks that can be triggered given our conditions and controls
m_candidateMeleeAttacks.RemoveAll();
int iMeleeSkill = 0;
CASW_Melee_Attack *pCurrentAttack = pMarine->GetCurrentMeleeAttack();
#ifdef MELEE_CHARGE_ATTACKS
int nButtonsActivated = (pMoveData->m_nButtons ^ pMoveData->m_nOldButtons) & pMoveData->m_nOldButtons; // in charge attack mode, we have to initiate melee on mouse up
#else
int nButtonsActivated = (pMoveData->m_nButtons ^ pMoveData->m_nOldButtons) & pMoveData->m_nButtons;
#endif
for ( int i = 0 ; i < m_MeleeAttacks.Count() ; i++ )
{
CASW_Melee_Attack *pAttack = m_MeleeAttacks[i];
if ( pAttack->m_iMinMeleeSkill > iMeleeSkill )
continue;
if ( pAttack->m_iMaxMeleeSkill < iMeleeSkill )
continue;
// Require melee-weapon specific attacks if using a melee weapon
//if ( pCurrentMeleeWeapon && !pAttack->m_szActiveWeapon )
//continue;
#ifdef MELEE_CHARGE_ATTACKS
// Allow heavy->charge transitions to override currently playing melee attacks
if ( !pAttack->CanComboFrom( pCurrentAttack ) &&
(!pMarine->m_bMeleeHeavyKeyHeld || (pCurrentAttack && pCurrentAttack->m_AttackType != ASW_MA_HEAVY)) )
continue;
#else
if ( !pAttack->CanComboFrom( pCurrentAttack ) )
continue;
#endif
if ( pAttack->m_MarineClass != MARINE_CLASS_UNDEFINED && pAttack->m_MarineClass != pMarine->GetMarineProfile()->GetMarineClass() )
continue;
if ( pAttack->m_AttackType == ASW_MA_LIGHT && !(pMoveData->m_nButtons & IN_ATTACK) )
continue;
int nMeleeButton = MELEE_BUTTON;
if ( pAttack->m_AttackType == ASW_MA_HEAVY )
{
if ( !(nButtonsActivated & nMeleeButton) )
continue;
if ( pMarine->m_iUsableItemsOnMeleePress > 0 && nMeleeButton == IN_USE ) // don't do regular melee if there was a usable item on the ground when we pressed the key down
continue;
}
#ifdef MELEE_CHARGE_ATTACKS
if ( pAttack->m_AttackType == ASW_MA_CHARGE && !pMarine->m_bMeleeChargeActivate )
continue;
// Check if charge release attacks have charged enough
if ( pAttack->m_AttackType == ASW_MA_CHARGE_RELEASE &&
( (gpGlobals->curtime < (pMarine->m_flMeleeHeavyKeyHoldStart + pAttack->m_flRequiredChargeTime)) || !pCurrentAttack || pCurrentAttack->m_AttackType != ASW_MA_CHARGE) )
{
continue;
}
#endif
//if ( pAttack->m_AttackType == ASW_MA_CHARGE_RELEASE &&
// (!pCurrentAttack || pCurrentAttack->m_AttackType != ASW_MA_CHARGE ||
// (gpGlobals->curtime < (pMarine->m_flMeleeHeavyKeyHoldStart + pAttack->m_flRequiredChargeTime)) ) )
//{
// continue;
//}
if ( pAttack->m_ControlDirection != ASW_CD_ANY )
{
if ( pAttack->m_ControlDirection == ASW_CD_NONE && ( pMoveData->m_flForwardMove != 0 || pMoveData->m_flSideMove != 0 ) )
continue;
// build a vector pointing in the direction of our keypresses
Vector vecKeyDir = vec3_origin;
vecKeyDir.y += pMoveData->m_flForwardMove;
vecKeyDir.x += pMoveData->m_flSideMove;
VectorNormalize( vecKeyDir );
// find dot product of key direction and marine facing
QAngle angFacing = vec3_angle;
angFacing.y = pMoveData->m_vecViewAngles.y;
Vector vecFacing;
AngleVectors( angFacing, &vecFacing );
float flFacingDot = vecFacing.Dot( vecKeyDir );
if ( pAttack->m_ControlDirection == ASW_CD_FORWARD && flFacingDot <= MELEE_FACING_THRESHOLD )
continue;
if ( pAttack->m_ControlDirection == ASW_CD_BACK && flFacingDot >= -MELEE_FACING_THRESHOLD )
continue;
// find sideways dot product to check strafing
angFacing.y -= 90;
AngleVectors( angFacing, &vecFacing );
float flSideDot = vecFacing.Dot( vecKeyDir );
if ( pAttack->m_ControlDirection == ASW_CD_LEFT && flSideDot >= -MELEE_FACING_THRESHOLD )
continue;
if ( pAttack->m_ControlDirection == ASW_CD_RIGHT && flSideDot <= MELEE_FACING_THRESHOLD )
continue;
}
if ( pAttack->m_szActiveWeapon )
{
CBaseCombatWeapon *pWeapon = pMarine->GetActiveWeapon();
if ( !pWeapon )
continue;
if ( Q_stricmp( pWeapon->GetClassname(), pAttack->m_szActiveWeapon ) )
continue;
}
m_candidateMeleeAttacks.Insert( pAttack );
}
//if ( pCurrentMeleeWeapon )
//{
//pCurrentMeleeWeapon->OnMeleeAttackAvailable( m_candidateMeleeAttacks );
//}
}
void CASW_Melee_System::StartMeleeAttack( CASW_Melee_Attack *pAttack, CASW_Marine *pMarine, CMoveData *pMoveData, float flBaseMeleeDamage )
{
if ( !pMarine )
return;
if ( !pAttack )
return;
if ( flBaseMeleeDamage == -1 )
{
//CASW_Weapon *pWeapon = pMarine->GetActiveASWWeapon();
bool bIsMeleeWeapon = false; //pWeapon ? pWeapon->ASWIsMeleeWeapon() : false;
if ( !bIsMeleeWeapon )
{
pMarine->m_flBaseMeleeDamage = asw_melee_base_damage.GetFloat();
CASW_Marine_Profile *pProfile = pMarine->GetMarineProfile();
if ( pProfile )
{
int iMarineLevel = 1; //pProfile->GetLevel();
pMarine->m_flBaseMeleeDamage = pMarine->m_flBaseMeleeDamage + pMarine->m_flBaseMeleeDamage * 0.15f * ( iMarineLevel - 1 ); // +15% damage for every level above 1, similar to drone health
}
}
else
{
//pMarine->m_flBaseMeleeDamage = pWeapon->m_flDamage * g_pGameRules->GetDamageMultiplier();
}
}
else
{
pMarine->m_flBaseMeleeDamage = flBaseMeleeDamage;
}
// CASW_Weapon *pWeapon = pMarine->GetActiveASWWeapon();
// if ( pWeapon && pWeapon->GetAttributeContainer()->GetItem() )
// {
// CASWScriptCreatedItem *pItem = static_cast<CASWScriptCreatedItem*>( pWeapon->GetAttributeContainer()->GetItem() );
// if ( pItem->IsMeleeWeapon() )
// {
// pMarine->m_flBaseMeleeDamage += pWeapon->GetWeaponDamage();
// }
//
// // mod the damage if the weapon has a melee damage increasing attribute
// float flModdedDamage = pMarine->m_flBaseMeleeDamage;
// if ( asw_melee_debug.GetBool() )
// Msg( "%s:%f Pre-modded melee damage: %d\n", IsServerDll() ? "S" : "C", gpGlobals->curtime, flModdedDamage );
//
// CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flModdedDamage, mod_melee_damage );
// CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flModdedDamage, mod_melee_damage_scale );
//
// if ( asw_melee_debug.GetBool() )
// Msg( "%s:%f Post-modded melee damage: %d\n", IsServerDll() ? "S" : "C", gpGlobals->curtime, flModdedDamage );
//
// pMarine->m_flBaseMeleeDamage = flModdedDamage;
// }
if ( asw_melee_debug.GetBool() )
{
Msg( "%s:%f Playing melee attack %s (seq: %s)\n", IsServerDll() ? "S" : "C", gpGlobals->curtime, pAttack->m_szAttackName, pAttack->m_szSequenceName );
}
int iSequence = pMarine->LookupSequence( pAttack->m_szSequenceName );
if ( iSequence < 0 )
{
Assert( false );
return;
}
//CASW_Melee_Attack *pPrevAttack = GetMeleeAttackByID( pMarine->m_iMeleeAttackID );
pMarine->m_iMeleeAttackID = pAttack->m_nAttackID;
if ( pMoveData && pMoveData->m_bFirstRunOfFunctions )
{
pMarine->DoAnimationEvent( (PlayerAnimEvent_t) ( PLAYERANIMEVENT_MELEE + pAttack->m_nAttackID - 1 ) );
}
// set m_fNextMeleeTime (prevents attacking again too soon)
//float flDuration = pMarine->SequenceDuration( iSequence );
//pMarine->m_fNextMeleeTime = gpGlobals->curtime + flDuration; // TODO: Revisit to allow combos
pMarine->m_vecMeleeStartPos = pMarine->GetAbsOrigin();
pMarine->m_bFaceMeleeYaw = false;
pMarine->m_flMeleeStartTime = gpGlobals->curtime;
pMarine->m_flMeleeYaw = pMoveData ? pMoveData->m_vecViewAngles.y : pMarine->GetAbsAngles()[ YAW ];
pMarine->m_flMeleeLastCycle = 0;
pMarine->m_bMeleeCollisionDamage = false;
pMarine->m_bMeleeComboKeypressAllowed = false;
pMarine->m_bMeleeComboKeyPressed = false;
pMarine->m_bMeleeComboTransitionAllowed = false;
pMarine->m_bMeleeMadeContact = false;
pMarine->m_iMeleeAllowMovement = pAttack->m_iAllowMovement;
pMarine->m_bMeleeKeyReleased = false;
pMarine->m_bMeleeChargeActivate = false;
pMarine->m_RecentMeleeHits.RemoveAll();
pMarine->m_bPlayedMeleeHitSound = false;
//pMarine->m_bReflectingProjectiles = true;
// search through the chosen sequence for events we want to predict (such as combo timing and damage)
pMarine->m_iNumPredictedEvents = 0;
CStudioHdr *pStudioHdr = pMarine->GetModelPtr();
if ( !pStudioHdr )
return;
mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence );
if ( seqdesc.numevents > 0 )
{
SetEventIndexForSequence( seqdesc );
mstudioevent_t *pEvent = GetEventIndexForSequence( seqdesc );
int num_events = NELEMS( s_PredictedAnimEvents );
for ( int i = 0 ; i < (int) seqdesc.numevents ; i++ )
{
if ( ! (pEvent[i].type & AE_TYPE_NEWEVENTSYSTEM ) )
continue;
int nEvent = pEvent[i].Event();
for ( int k = 0 ; k < num_events ; k++ )
{
if ( nEvent == s_PredictedAnimEvents[k] )
{
if ( pEvent[i].cycle == 0 ) // if the animation event is on the first frame, then trigger it now
{
if ( pMoveData->m_bFirstRunOfFunctions ||
nEvent == AE_START_DETECTING_COMBO ||
nEvent == AE_STOP_DETECTING_COMBO ||
nEvent == AE_COMBO_TRANSITION ||
nEvent == AE_ALLOW_MOVEMENT )
{
m_bAllowNormalAnimEvents = true;
pMarine->HandlePredictedAnimEvent( nEvent, pEvent[i].pszOptions() );
m_bAllowNormalAnimEvents = false;
}
break;
}
pMarine->m_iPredictedEvent[pMarine->m_iNumPredictedEvents] = nEvent;
pMarine->m_flPredictedEventTime[pMarine->m_iNumPredictedEvents] = pEvent[i].cycle;
pMarine->m_szPredictedEventOptions[pMarine->m_iNumPredictedEvents] = pEvent[i].pszOptions();
pMarine->m_iNumPredictedEvents++;
break;
}
}
}
}
//CASW_Weapon_Melee *pMeleeWeapon = dynamic_cast<CASW_Weapon_Melee*>(pMarine->GetActiveASWWeapon());
//if ( pMeleeWeapon )
//{
//pMeleeWeapon->OnMeleeAttackBegin( pAttack, pPrevAttack );
//}
#ifdef CLIENT_DLL
pMarine->m_hMeleeLockTarget = NULL;
FindMeleeLockTarget( pMarine );
#endif
}
extern ConVar sv_maxvelocity;
void CASW_Melee_System::SetupMeleeMovement( CASW_Marine *pMarine, CMoveData *pMoveData )
{
CASW_Melee_Attack *pAttack = pMarine->GetCurrentMeleeAttack();
if ( !pAttack )
return;
#ifdef CLIENT_DLL
if ( !pMarine->m_hMeleeLockTarget.Get() )
{
FindMeleeLockTarget( pMarine );
}
if ( pMarine->m_hMeleeLockTarget.Get() && asw_melee_debug.GetBool() )
{
debugoverlay->AddLineOverlay( pMarine->GetAbsOrigin(), pMarine->m_hMeleeLockTarget->GetAbsOrigin(), 255, 128, 0, true, 0.05f );
}
#endif
int nMeleeButton = MELEE_BUTTON;
if ( !( pMoveData->m_nButtons & nMeleeButton ) )
{
pMarine->m_bMeleeKeyReleased = true;
}
if ( !pMarine->m_bMeleeMadeContact && pMoveData->m_nButtons & IN_MELEE_CONTACT )
{
pMarine->m_bMeleeMadeContact = true;
}
// find out the position we should be in by now, relative to our melee animation
Vector vecDeltaPos;
QAngle vecDeltaAngles;
int iMiscSequence = pMarine->LookupSequence( pAttack->m_szSequenceName );
//CASW_Weapon *pWeapon = pMarine->GetActiveASWWeapon();
float flSpeedScale = pAttack->m_flSpeedScale;
// if ( pWeapon )
// {
// CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flSpeedScale, mod_melee_speed );
// }
// else
// {
// CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pMarine, flSpeedScale, mod_melee_speed );
// }
float flMiscDuration = pMarine->SequenceDuration( iMiscSequence ) * flSpeedScale;
float flMiscCycle = clamp( ( gpGlobals->curtime - pMarine->m_flMeleeStartTime ) / flMiscDuration, 0.0f, 1.0f );
static int iOutputNum = 0;
iOutputNum++;
if ( pMarine->m_PlayerAnimState )
{
pMarine->m_PlayerAnimState->SetMiscPlaybackRate( 1.0f / pAttack->m_flSpeedScale );
}
if ( asw_melee_debug.GetInt() == 2 )
{
Msg( "%s %d iMiscSequence = %d %f %f yaw %f\n", pMarine->IsServer() ? "s" : " c", iOutputNum, iMiscSequence, flMiscCycle, flMiscDuration, pMarine->m_flMeleeYaw );
}
if ( iMiscSequence <= 0 )
{
Msg( "Aborting melee attack as couldn't find sequence for it\n" );
OnMeleeAttackFinished( pMarine );
return;
}
if ( pMarine->m_iMeleeAllowMovement == MELEE_MOVEMENT_ANIMATION_ONLY && gpGlobals->frametime > 0 )
{
pMarine->GetSequenceMovement( iMiscSequence, pMarine->m_flMeleeLastCycle, flMiscCycle, vecDeltaPos, vecDeltaAngles );
VectorYawRotate( vecDeltaPos, pMarine->m_flMeleeYaw, vecDeltaPos );
Vector vecTargetPos = pMarine->GetAbsOrigin();
if ( asw_melee_lock.GetInt() < 2 ) // ignore animation movement when melee locked?
{
vecTargetPos += vecDeltaPos;
}
// add in target lock sliding
if ( asw_melee_lock.GetBool() && pMoveData->m_nButtons & IN_MELEE_LOCK )
{
QAngle facing = vec3_angle;
facing[ YAW ] = pMarine->ASWEyeAngles()[ YAW ]; //m_flMeleeYaw;
Vector vecForward;
AngleVectors( facing, &vecForward );
vecTargetPos += vecForward * pMoveData->m_flForwardMove;
}
// set our velocity such that we move to the target position
float flVerticalSpeed = pMoveData->m_vecVelocity.z;
pMoveData->m_vecVelocity = ( vecTargetPos - pMarine->GetAbsOrigin() ) / gpGlobals->frametime;
if ( pMoveData->m_vecVelocity.z == 0 )
{
pMoveData->m_vecVelocity.z = flVerticalSpeed;
}
if ( asw_melee_debug.GetBool() && ( fabs( pMoveData->m_vecVelocity[0] ) > sv_maxvelocity.GetFloat() || fabs( pMoveData->m_vecVelocity[1] ) > sv_maxvelocity.GetFloat() ) )
{
Msg( "%s high velocity %d. Targetpos is %f units away\n", IsServerDll() ? "S" : "C", ( vecTargetPos - pMarine->GetAbsOrigin() ).Length() );
Msg( " forwardmove is %f frametime is %f\n", pMoveData->m_flForwardMove, gpGlobals->frametime );
#ifdef CLIENT_DLL
debugoverlay->AddLineOverlay( pMarine->GetAbsOrigin(), vecTargetPos, 255, 0, 0, true, 10.0f );
debugoverlay->AddTextOverlay( vecTargetPos, 10.0f, "vecTargetPos" );
debugoverlay->AddTextOverlay( pMarine->GetAbsOrigin(), 10.0f, "marine origin" );
#endif
}
//pMoveData->m_vecVelocity += ( vecDeltaPos / gpGlobals->frametime );
if ( asw_melee_debug.GetInt() == 2 )
{
Msg("%s %d set velocity to %f %f %f\n", pMarine->IsServer() ? "s" : " c", iOutputNum, VectorExpand( pMoveData->m_vecVelocity ) );
Msg("%s %d pos = %f %f %f\n", pMarine->IsServer() ? "s" : " c", iOutputNum, VectorExpand( pMoveData->GetAbsOrigin() ) );
Msg("%s %d start = %f %f %f\n", pMarine->IsServer() ? "s" : " c", iOutputNum, VectorExpand( pMarine->m_vecMeleeStartPos ) );
Msg("%s %d delta = %f %f %f\n", pMarine->IsServer() ? "s" : " c", iOutputNum, VectorExpand( vecDeltaPos ) );
Msg("%s %d target = %f %f %f\n", pMarine->IsServer() ? "s" : " c", iOutputNum, VectorExpand( vecTargetPos ) );
}
}
// check for any predicted anim events
for ( int i = 0 ; i < pMarine->m_iNumPredictedEvents ; i++ )
{
if ( pMarine->m_iPredictedEvent[i] != -1 && pMarine->m_flPredictedEventTime[i] > pMarine->m_flMeleeLastCycle && pMarine->m_flPredictedEventTime[i] <= flMiscCycle )
{
if ( pMoveData->m_bFirstRunOfFunctions ||
pMarine->m_iPredictedEvent[i] == AE_START_DETECTING_COMBO ||
pMarine->m_iPredictedEvent[i] == AE_STOP_DETECTING_COMBO ||
pMarine->m_iPredictedEvent[i] == AE_COMBO_TRANSITION ||
pMarine->m_iPredictedEvent[i] == AE_ALLOW_MOVEMENT )
{
m_bAllowNormalAnimEvents = true;
pMarine->HandlePredictedAnimEvent( pMarine->m_iPredictedEvent[i], pMarine->m_szPredictedEventOptions[i] );
m_bAllowNormalAnimEvents = false;
}
}
}
// Check for a regular combo transition
if ( pMarine->m_bMeleeComboKeyPressed && pMarine->m_bMeleeComboTransitionAllowed )
{
if ( asw_melee_debug.GetInt() == 3 )
{
Msg( "%s:%f Checking for m_bMeleeComboKeyPressed combo\n", pMarine->IsServer() ? "s" : " c", gpGlobals->curtime );
}
bool bMadeContact = !asw_melee_require_contact.GetBool() || pMarine->m_bMeleeMadeContact;
if ( bMadeContact && ComboTransition( pMarine, pMoveData ) )
return;
}
#ifdef MELEE_CHARGE_ATTACKS
// check for transitions from charge to charge-released attacks
if ( pAttack->m_AttackType == ASW_MA_CHARGE )
{
// See if any attacks are avaialble, to play ready effects, etc
UpdateCandidateMeleeAttacks( pMarine, pMoveData );
if ( !pMarine->m_bMeleeHeavyKeyHeld )
{
// actually transition to
if ( asw_melee_debug.GetInt() == 3 )
{
Msg( "%s:%f Checking for !m_bMeleeHeavyKeyHeld combo\n", pMarine->IsServer() ? "s" : " c", gpGlobals->curtime );
}
if ( ComboTransition( pMarine, pMoveData, false ) )
{
return;
}
else
{
// If we released our button, but didn't pick up any combo transitions, it means we didn't hold long enough,
// so force our cycle to end so we exit the current charge attack
flMiscCycle = 1.0f;
}
}
}
// looping
if ( pAttack->m_bIsLooping && flMiscCycle >= 1.00f && pMarine->m_bMeleeHeavyKeyHeld )
{
StartMeleeAttack( pAttack, pMarine, pMoveData );
return;
}
#endif
// finished melee attack
if ( flMiscCycle >= 1.0f )
{
if ( pAttack->m_pForceComboAttack )
{
StartMeleeAttack( pAttack->m_pForceComboAttack, pMarine, pMoveData );
return;
}
OnMeleeAttackFinished( pMarine );
}
else
{
pMarine->m_flMeleeLastCycle = flMiscCycle;
}
}
bool CASW_Melee_System::ComboTransition( CASW_Marine *pMarine, CMoveData *pMoveData, bool bUpdateCandidateAttacks /* = true */ )
{
if ( bUpdateCandidateAttacks )
{
UpdateCandidateMeleeAttacks( pMarine, pMoveData );
}
if ( m_candidateMeleeAttacks.Count() <= 0 )
{
return false;
}
if ( asw_melee_debug.GetBool() )
{
Msg( "%s doing ComboTransition\n", pMarine->IsServer() ? "s" : " c" );
}
// count how many attacks at the end of the list have the same priority
float flHighestPriority = m_candidateMeleeAttacks.Tail()->m_flPriority;
int iCount = 0;
for ( int i = m_candidateMeleeAttacks.Count() - 1; i >= 0; i-- )
{
if ( m_candidateMeleeAttacks[i]->m_flPriority == flHighestPriority )
{
iCount++;
}
}
// set the attack and play the animation for it
StartMeleeAttack( m_candidateMeleeAttacks[ SharedRandomInt( "MeleeChoice", m_candidateMeleeAttacks.Count() - iCount, m_candidateMeleeAttacks.Count() - 1 ) ], pMarine, pMoveData );
return true;
}
void CASW_Melee_System::OnMeleeAttackFinished( CASW_Marine *pMarine )
{
Assert( pMarine );
//CASW_Weapon_Melee *pWeapon = dynamic_cast<CASW_Weapon_Melee*>(pMarine->GetActiveASWWeapon());
//if ( pWeapon )
//{
//pWeapon->OnMeleeAttackEnd( pMarine->GetCurrentMeleeAttack() );
//}
pMarine->m_bMeleeCollisionDamage = false;
pMarine->m_bMeleeComboKeypressAllowed = false;
pMarine->m_bMeleeComboKeyPressed = false;
pMarine->m_bMeleeComboTransitionAllowed = false;
pMarine->m_iMeleeAllowMovement = MELEE_MOVEMENT_ANIMATION_ONLY;
pMarine->m_bMeleeChargeActivate = false;
#ifdef MELEE_CHARGE_ATTACKS
if ( pMarine->m_bMeleeHeavyKeyHeld )
{
pMarine->m_bMeleeHeavyKeyHeld = false;
pMarine->m_flMeleeHeavyKeyHoldStart = gpGlobals->curtime;
}
#endif
if ( asw_melee_debug.GetBool() )
{
Msg( "%s:%f OnMeleeFinished() for attack %s\n", pMarine->IsServer() ? "s" : " c", gpGlobals->curtime, pMarine->GetCurrentMeleeAttack() ? pMarine->GetCurrentMeleeAttack()->m_szAttackName : "(null)" );
}
pMarine->m_iMeleeAttackID = 0;
pMarine->m_bReflectingProjectiles = false;
}
// ==================================================================================================
CASW_Melee_Attack::CASW_Melee_Attack()
{
m_nAttackID = 0;
m_iAllowMovement = MELEE_MOVEMENT_ANIMATION_ONLY;
m_pOnCollisionDoAttack = NULL;
m_pForceComboAttack = NULL;
m_ControlDirection = ASW_CD_ANY;
}
CASW_Melee_Attack::~CASW_Melee_Attack()
{
m_CombosFromAttackNames.PurgeAndDeleteElements();
m_CombosFromAttacks.Purge();
}
void CASW_Melee_Attack::ApplyKeyValues( KeyValues *pKeys )
{
m_szAttackName = ASW_AllocString( pKeys->GetString( "name", "Unnamed Attack" ) );
m_szSequenceName = ASW_AllocString( pKeys->GetString( "sequence", "Melee sequence not specified" ) );
m_flPriority = pKeys->GetFloat( "Priority", 1.0f );
m_iMinMeleeSkill = pKeys->GetInt( "MinMeleeSkill", 0 );
m_iMaxMeleeSkill = pKeys->GetInt( "MaxMeleeSkill", 5 );
m_flDamageScale = pKeys->GetFloat( "DamageScale", 1.0f );
m_flForceScale = pKeys->GetFloat( "ForceScale", 1.0f );
m_flTraceDistance = pKeys->GetFloat( "TraceDistance", 0.0f );
m_flTraceHullSize = pKeys->GetFloat( "TraceHullSize", 0.0f );
m_iAllowMovement = (ASW_Melee_Movement_t) pKeys->GetInt( "AllowMovement", 0 );
m_bAllowRotation = pKeys->GetBool( "AllowRotation", true );
m_vTraceAttackOffset.x = pKeys->GetFloat( "TraceAttackOffsetRight", 0.0f );
m_vTraceAttackOffset.y = pKeys->GetFloat( "TraceAttackOffsetForward", 0.0f );
m_vTraceAttackOffset.z = pKeys->GetFloat( "TraceAttackOffsetUp", 0.0f );
m_flRequiredChargeTime = pKeys->GetFloat( "RequiredChargeTime", 0.0f );
m_flSpeedScale = pKeys->GetFloat( "SpeedScale", 1.0f );
m_flBlendIn = pKeys->GetFloat( "BlendIn", 0.0f );
m_flBlendOut = pKeys->GetFloat( "BlendOut", 0.1f );
m_flKnockbackForce = pKeys->GetFloat( "KnockbackForce", 0.0f );
m_bHoldAtEnd = pKeys->GetBool( "HoldAtEnd", false );
m_bAllowHitsBehindMarine = pKeys->GetBool( "AllowHitsBehindMarine", false );
const char *szAttackType = pKeys->GetString( "AttackType", "Heavy" );
if ( !Q_stricmp( szAttackType, "Light" ) )
{
m_AttackType = ASW_MA_LIGHT;
}
else if ( !Q_stricmp( szAttackType, "Heavy" ) )
{
m_AttackType = ASW_MA_HEAVY;
}
else if ( !Q_stricmp( szAttackType, "Charge" ) )
{
m_AttackType = ASW_MA_CHARGE;
}
else if ( !Q_stricmp( szAttackType, "ChargeRelease" ) )
{
m_AttackType = ASW_MA_CHARGE_RELEASE;
}
m_bIsLooping = pKeys->GetBool( "Loop", false );
const char *szMarineClass = pKeys->GetString( "MarineClass" );
m_MarineClass = MARINE_CLASS_UNDEFINED;
if ( !Q_stricmp( szMarineClass, "Officer" ) )
{
m_MarineClass = MARINE_CLASS_NCO;
}
else if ( !Q_stricmp( szMarineClass, "SpecialWeapons" ) )
{
m_MarineClass = MARINE_CLASS_SPECIAL_WEAPONS;
}
else if ( !Q_stricmp( szMarineClass, "Medic" ) )
{
m_MarineClass = MARINE_CLASS_MEDIC;
}
else if ( !Q_stricmp( szMarineClass, "Tech" ) )
{
m_MarineClass = MARINE_CLASS_TECH;
}
for ( KeyValues *pKey = pKeys->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() )
{
if ( !Q_stricmp( pKey->GetName(), "CombosFrom" ) )
{
const char *szCombosFromAttackName = ASW_AllocString( pKey->GetString() );
m_CombosFromAttackNames.AddToTail( szCombosFromAttackName );
}
}
m_szOnCollisionDoAttackName = ASW_AllocString( pKeys->GetString( "OnCollisionDoAttack" ) );
m_szForceComboAttackName = ASW_AllocString( pKeys->GetString( "ForceCombo" ) );
const char *szControlDirection = pKeys->GetString( "ControlDirection", "any" );
if ( !szControlDirection || !Q_stricmp( szControlDirection, "any" ) )
{
m_ControlDirection = ASW_CD_ANY;
}
else if ( !Q_stricmp( szControlDirection, "forward" ) )
{
m_ControlDirection = ASW_CD_FORWARD;
}
else if ( !Q_stricmp( szControlDirection, "back" ) )
{
m_ControlDirection = ASW_CD_BACK;
}
else if ( !Q_stricmp( szControlDirection, "left" ) )
{
m_ControlDirection = ASW_CD_LEFT;
}
else if ( !Q_stricmp( szControlDirection, "right" ) )
{
m_ControlDirection = ASW_CD_RIGHT;
}
else if ( !Q_stricmp( szControlDirection, "none" ) )
{
m_ControlDirection = ASW_CD_NONE;
}
m_szActiveWeapon = ASW_AllocString( pKeys->GetString( "ActiveWeapon" ) );
}
CASW_Melee_Attack* CASW_Melee_System::GetMeleeAttackByID( int iMeleeID )
{
// melee attack IDs start at 1
if ( iMeleeID <= 0 || iMeleeID > m_MeleeAttacks.Count() )
{
return NULL;
}
return m_MeleeAttacks[ iMeleeID - 1 ];
}
CASW_Melee_Attack* CASW_Melee_System::GetMeleeAttackByName( const char *szAttackName )
{
for ( int i = 0; i < m_MeleeAttacks.Count(); i++ )
{
if ( !Q_stricmp( m_MeleeAttacks[i]->m_szAttackName, szAttackName ) )
{
return m_MeleeAttacks[i];
}
}
return NULL;
}
void CASW_Melee_System::ComputeTraceOffset( CASW_Marine *pMarine, Vector &vecTraceOffset )
{
CASW_Melee_Attack *pAttack = pMarine->GetCurrentMeleeAttack();
if ( !pAttack )
{
return;
}
QAngle angles = pMarine->GetAbsAngles();
Vector right, up, forward;
AngleVectors( angles, &forward, &right, &up );
vecTraceOffset += right * pAttack->m_vTraceAttackOffset.x;
vecTraceOffset += forward * pAttack->m_vTraceAttackOffset.y;
vecTraceOffset += up * pAttack->m_vTraceAttackOffset.z;
}
#ifdef CLIENT_DLL
void CASW_Melee_System::FindMeleeLockTarget( CASW_Marine *pMarine )
{
CBaseEntity *pBest = NULL;
float flBestScore = -1;
for ( int i = 0; i < IASW_Client_Aim_Target::AutoList().Count(); i++ )
{
IASW_Client_Aim_Target *pAimTarget = static_cast< IASW_Client_Aim_Target* >( IASW_Client_Aim_Target::AutoList()[ i ] );
C_BaseEntity *pEnt = pAimTarget->GetEntity();
if ( !pEnt || !pAimTarget->IsAimTarget() )
continue;
Vector dir = pEnt->GetAbsOrigin() - pMarine->GetAbsOrigin();
float flDist = dir.NormalizeInPlace();
if ( flDist > 200.0f )
continue;
Vector vecForward;
AngleVectors( pMarine->ASWEyeAngles(), &vecForward );
float flDot = dir.Dot( vecForward );
if ( flDot < 0 )
continue;
float flScore = ( 200.0f - flDist ) * flDot;
if ( flScore > flBestScore )
{
flBestScore = flScore;
pBest = pEnt;
}
}
pMarine->m_hMeleeLockTarget = pBest;
}
void CASW_Melee_System::CreateMove( float flInputSampleTime, CUserCmd *pCmd, CASW_Marine *pMarine )
{
// don't replace the move if our current melee attack lets us move about freely
if ( pMarine->m_iMeleeAllowMovement != MELEE_MOVEMENT_ANIMATION_ONLY )
return;
if ( pMarine->m_bFaceMeleeYaw )
{
pCmd->viewangles[ YAW ] = pMarine->m_flMeleeYaw;
}
if ( asw_melee_lock.GetBool() )
{
// TODO: This breaks direction detection once we start melee'ing.
pCmd->forwardmove = 0;
pCmd->sidemove = 0;
// set forwardmove to the number of units we wish to move this movement tick
C_BaseEntity *pEnemy = pMarine->m_hMeleeLockTarget.Get();
if ( pEnemy )
{
Vector dir = pEnemy->GetAbsOrigin() - pMarine->GetAbsOrigin();
float flDist = dir.NormalizeInPlace();
Vector vecForward;
AngleVectors( pMarine->ASWEyeAngles(), &vecForward );
float flDot = dir.Dot( vecForward );
if ( flDot > 0.7f )
{
float flIdealDist = pEnemy->CollisionProp()->BoundingRadius2D() + asw_melee_lock_distance.GetFloat();
flDist -= flIdealDist; // distance to our ideal spot
float flSlideAmount = asw_melee_lock_slide_speed.GetFloat() * flInputSampleTime;
if ( flDist > 0 )
{
flSlideAmount = MIN( flDist, flSlideAmount );
}
else
{
flSlideAmount = MAX( flDist, -flSlideAmount );
}
pCmd->forwardmove = flSlideAmount;
}
pCmd->buttons |= IN_MELEE_LOCK;
}
}
}
#endif
// ======================================================================================================
#ifdef CLIENT_DLL
// this does a trace with the current melee attack's size/dimensions and reports if we hit anything
bool CASW_Melee_Attack::CheckContact( CASW_Marine *pAttacker )
{
Vector forward;
AngleVectors( pAttacker->GetAbsAngles(), &forward );
Vector vStart = pAttacker->GetAbsOrigin();
Vector mins = -Vector( m_flTraceHullSize, m_flTraceHullSize, 32);
Vector maxs = Vector( m_flTraceHullSize, m_flTraceHullSize, 32 );
float flDist = m_flTraceDistance;
// The ideal place to start the trace is in the center of the attacker's bounding box.
// however, we need to make sure there's enough clearance. Some of the smaller monsters aren't
// as big as the hull we try to trace with. (SJB)
float flVerticalOffset = pAttacker->WorldAlignSize().z * 0.5;
if( flVerticalOffset < maxs.z )
{
// There isn't enough room to trace this hull, it's going to drag the ground.
// so make the vertical offset just enough to clear the ground.
flVerticalOffset = maxs.z + 1.0;
}
vStart.z += flVerticalOffset;
Vector vEnd = vStart + (forward * flDist );
// asw - make melee attacks trace below us too, so it's possible to hit things just below you on a slope
Vector low_mins = mins;
low_mins.z -= 30;
Ray_t ray;
ray.Init( vStart, vEnd, mins, maxs );
trace_t tr;
CTraceFilterSimple traceFilter( pAttacker, COLLISION_GROUP_NONE );
enginetrace->TraceRay( ray, MASK_SHOT_HULL, &traceFilter, &tr );
return tr.DidHit();
}
#endif
// marine skips anim events that are predicted by the melee system
bool CASW_Melee_Attack::AllowNormalAnimEvent( CASW_Marine *pMarine, int event )
{
if ( ASWMeleeSystem()->m_bAllowNormalAnimEvents )
return true;
#ifdef CLIENT_DLL
// we don't predict other players' animation events, so allow them to fire the regular way
if ( pMarine && pMarine->GetPredictionOwner() != C_ASW_Player::GetLocalASWPlayer() )
return true;
// in singleplayer there's no prediction, so allow events
if ( gpGlobals->maxClients <= 1 )
return true;
#endif
int num_events = NELEMS( s_PredictedAnimEvents );
for ( int k = 0 ; k < num_events ; k++ )
{
if ( event == s_PredictedAnimEvents[k] )
return false;
}
return true;
}
void CASW_Melee_Attack::MovementCollision( CASW_Marine *pMarine, CMoveData *pMoveData, trace_t *tr )
{
if ( !tr->m_pEnt )
{
//Msg( "%s: Melee attack collided with nothing\n", IsServerDll() ? "S" : "C" );
return;
}
//Msg( "%s: Melee attack collided with %d %s\n", IsServerDll() ? "S" : "C", tr->m_pEnt->entindex(), tr->m_pEnt->GetClassname() );
if ( m_pOnCollisionDoAttack )
{
// jms: Temp for playtest, this assumes we're using the Charge skill. Make this data driven
CASW_Marine_Profile *pProfile = pMarine->GetMarineProfile();
if ( !pProfile )
return;
pMarine->EmitSound( "ASW_Charge.ImpactBlast" );
int iBaseDamage = 10; //pSkill->GetValue( CASW_Skill_Details::Damage, pProfile->GetMarineSkill( pSkill->m_iSkillIndex ) );
ASWMeleeSystem()->StartMeleeAttack( m_pOnCollisionDoAttack, pMarine, pMoveData, iBaseDamage );
//#ifdef GAME_DLL
//float flRadius = pSkill->GetValue( CASW_Skill_Details::Radius, pProfile->GetMarineSkill( pSkill->m_iSkillIndex ) );
//ASWGameRules()->StumbleAliensInRadius( pMarine, pMarine->GetAbsOrigin(), flRadius );
//#endif
}
}
bool CASW_Melee_Attack::CanComboFrom( CASW_Melee_Attack *pAttack )
{
return (m_CombosFromAttacks.Count() == 0 && pAttack == NULL) || (m_CombosFromAttacks.Find( pAttack ) != -1);
}
#ifdef GAME_DLL
typedef CCopyableUtlVector<CASW_Melee_Attack*> ASW_Melee_Attack_List_t;
void BuildAttackList_r( CUtlVector<ASW_Melee_Attack_List_t> &attackList, ASW_Melee_Attack_List_t &currentList, int nStartIndex )
{
CASW_Melee_Attack *pAttack = ASWMeleeSystem()->GetMeleeAttackByID( nStartIndex );
// Check for loops
int nSelfIndex = currentList.Find( pAttack );
if ( nSelfIndex != -1 )
{
return;
}
bool bIsLeaf = true;
// Add ourselves to the list we're currently building
currentList.AddToTail( pAttack );
// Melee attack indexing starts at 1
for ( int k = 1; k <= ASWMeleeSystem()->m_MeleeAttacks.Count(); k++ )
{
if ( nStartIndex == k )
{
continue;
}
CASW_Melee_Attack *pIterAttack = ASWMeleeSystem()->GetMeleeAttackByID( k );
if( pIterAttack->CanComboFrom( pAttack ) )
{
bIsLeaf = false;
BuildAttackList_r( attackList, currentList, k );
}
}
if ( bIsLeaf )
{
// Once at a leaf node, actually add the complete attack list
attackList.AddToTail( currentList );
}
currentList.Remove( currentList.Count() - 1 );
}
void BuildAttackList( CUtlVector<ASW_Melee_Attack_List_t> &attackList )
{
// Melee attack indexing starts at 1
for( int i = 1; i <= ASWMeleeSystem()->m_MeleeAttacks.Count(); i++ )
{
CASW_Melee_Attack *pAttack = ASWMeleeSystem()->GetMeleeAttackByID( i );
// Recurse down starting at each root melee attack
if ( pAttack->m_CombosFromAttacks.Count() == 0 )
{
ASW_Melee_Attack_List_t emptyList;
BuildAttackList_r( attackList, emptyList, i );
}
}
}
int GetNumberOfAttacks( CStudioHdr *pStudioHdr, int nSequence )
{
int nNumAttacks = 0;
MDLCACHE_CRITICAL_SECTION();
mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( nSequence );
if ( seqdesc.numevents > 0 )
{
SetEventIndexForSequence( seqdesc );
mstudioevent_t *pEvent = GetEventIndexForSequence( seqdesc );
for ( int i = 0 ; i < (int) seqdesc.numevents ; i++ )
{
if ( pEvent[i].Event() == AE_MELEE_DAMAGE )
{
nNumAttacks++;
}
}
}
return nNumAttacks;
}
void cc_asw_melee_list_dps_f()
{
CUtlVector<ASW_Melee_Attack_List_t> attackList;
BuildAttackList( attackList );
CASW_Player *pPlayer = ToASW_Player( UTIL_GetCommandClient() );
if ( !pPlayer )
{
Warning( "Cannot get local player\n" );
return;
}
CASW_Marine *pMarine = pPlayer->GetMarine();
CStudioHdr *pStudioHdr = pMarine? pMarine->GetModelPtr() : NULL;
if ( !pMarine || !pStudioHdr )
{
Warning( "Cannot get local marine\n" );
return;
}
for ( int i = 0; i < attackList.Count(); i++ )
{
// This will take damage scale into account, so a melee attack that does 3 attacks with a damage scale of 0.5 will count as 1.5 attacks
float flTotalAttacks = 0;
float flAttackDuration = 0;
if ( attackList[i].Count() == 0 )
{
continue;
}
ASW_MEL_MSG_SIMPLE( "Melee Attack Sequence (%s):\n", attackList[i][0]->m_szActiveWeapon );
for( int j = 0; j < attackList[i].Count(); j++ )
{
if ( j == 0 )
{
ASW_MEL_MSG_SIMPLE( "\t\t" );
}
else
{
ASW_MEL_MSG_SIMPLE( " -> " );
}
ASW_MEL_MSG_SIMPLE( "%s", attackList[i][j]->m_szAttackName );
flTotalAttacks += attackList[i][j]->m_flDamageScale *
GetNumberOfAttacks( pStudioHdr, pMarine->LookupSequence( attackList[i][j]->m_szSequenceName ) );
flAttackDuration += pMarine->SequenceDuration( pMarine->LookupSequence( attackList[i][j]->m_szSequenceName ) );
}
ASW_MEL_MSG_SIMPLE( "\n\tThis sequence performs %f attacks over %f seconds, DPS modifier: %f\n\n", flTotalAttacks, flAttackDuration, flTotalAttacks / flAttackDuration );
}
}
ConCommand cc_asw_melee_list_dps( "asw_melee_list_dps", cc_asw_melee_list_dps_f, "Lists DPS for melee weapons" );
#endif
void cc_asw_melee_reload_f()
{
ASWMeleeSystem()->Reload();
#ifdef CLIENT_DLL
engine->ClientCmd( "asw_melee_reload_server_only" );
#endif
}
#ifdef GAME_DLL
ConCommand cc_asw_melee_reload( "asw_melee_reload_server_only", cc_asw_melee_reload_f, "Reloads melee_attacks.txt" );
#else // #ifdef GAME_DLL
ConCommand cc_cl_asw_melee_reload( "asw_melee_reload", cc_asw_melee_reload_f, "Reloads melee_attacks.txt" );
#endif // #ifdef GAME_DLL