#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( 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( 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( 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( 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(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(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 ASW_Melee_Attack_List_t; void BuildAttackList_r( CUtlVector &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 &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 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