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
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 ¤tList, 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
|
|
|