//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: This is the brickbat weapon // // $Workfile: $ // $Date: $ // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "npcevent.h" #include "basehlcombatweapon.h" #include "basecombatcharacter.h" #include "ai_basenpc.h" #include "AI_Memory.h" #include "player.h" #include "gamerules.h" // For g_pGameRules #include "weapon_brickbat.h" #include "grenade_brickbat.h" #include "ammodef.h" #include "in_buttons.h" #include "game.h" #include "IEffects.h" #include "vstdlib/random.h" #include "baseviewmodel.h" #include "movevars_shared.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern ConVar sk_npc_dmg_brickbat; extern ConVar sk_plr_dmg_brickbat; struct BrickbatAmmo_s { const char *m_sClassName; int m_nAmmoType; int m_nMaxCarry; const char *m_sViewModel; const char *m_sWorldModel; }; BrickbatAmmo_s BrickBatAmmoArray[NUM_BRICKBAT_AMMO_TYPES] = { { "grenade_rockbb", BRICKBAT_ROCK, 5, "models/weapons/v_bb_bottle.mdl", "models/props_junk/Rock001a.mdl" }, { "grenade_beerbottle", BRICKBAT_BOTTLE, 3, "models/weapons/v_bb_bottle.mdl", "models/weapons/w_bb_bottle.mdl" }, }; IMPLEMENT_SERVERCLASS_ST(CWeaponBrickbat, DT_WeaponBrickbat) END_SEND_TABLE() //LINK_ENTITY_TO_CLASS( weapon_brickbat, CWeaponBrickbat ); //PRECACHE_WEAPON_REGISTER(weapon_brickbat); acttable_t CWeaponBrickbat::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_THROW, true }, }; IMPLEMENT_ACTTABLE(CWeaponBrickbat); BEGIN_DATADESC( CWeaponBrickbat ) DEFINE_FIELD( m_bNeedDraw, FIELD_BOOLEAN ), DEFINE_FIELD( m_bNeedThrow, FIELD_BOOLEAN ), DEFINE_FIELD( m_iThrowBits, FIELD_INTEGER ), DEFINE_FIELD( m_fNextThrowCheck, FIELD_TIME ), DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ), DEFINE_ARRAY( m_nAmmoCount, FIELD_INTEGER, NUM_BRICKBAT_AMMO_TYPES ), DEFINE_KEYFIELD( m_iCurrentAmmoType, FIELD_INTEGER, "BrickbatType" ), // Function Pointers DEFINE_FUNCTION( BrickbatTouch ), END_DATADESC() //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CWeaponBrickbat::Precache( void ) { for (int i=0;iUseTriggerBounds( false ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CWeaponBrickbat::GetViewModel( int viewmodelindex /*=0*/ ) { return BrickBatAmmoArray[m_iCurrentAmmoType].m_sViewModel; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CWeaponBrickbat::GetWorldModel( void ) { return BrickBatAmmoArray[m_iCurrentAmmoType].m_sWorldModel; } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ bool CWeaponBrickbat::Deploy( void ) { SetModel( GetViewModel() ); m_bNeedDraw = false; m_bNeedThrow = false; return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_VM_DRAW, (char*)GetAnimPrefix() ); } //------------------------------------------------------------------------------ // Purpose : Override to use brickbats pickup touch function // Input : // Output : //------------------------------------------------------------------------------ void CWeaponBrickbat::SetPickupTouch( void ) { SetTouch( BrickbatTouch ); } //----------------------------------------------------------------------------- // Purpose: Override so give correct ammo // Input : pOther - the entity that touched me // Output : //----------------------------------------------------------------------------- void CWeaponBrickbat::BrickbatTouch( CBaseEntity *pOther ) { // --------------------------------------------------- // First give weapon to touching entity if allowed // Skip ammo given portion by setting clips to zero // and handle ammo giving here // --------------------------------------------------- BaseClass::DefaultTouch(pOther); //FIXME: This ammo handling code is a bit bogus, need a real solution if brickbats are going to live /* // ---------------------------------------------------- // Give brickbat ammo if touching client // ---------------------------------------------------- if (pOther->GetFlags() & FL_CLIENT) { CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pOther ); // Exit if game rules say I can't have any more of this ammo type. if ( g_pGameRules->CanHaveAmmo( pBCC, m_iPrimaryAmmoType ) == false ) return; // ------------------------------------------------ // If already owned weapon of this type remove me // ------------------------------------------------ CWeaponBrickbat* oldWeapon = (CWeaponBrickbat*)pBCC->Weapon_OwnsThisType( GetClassname() ); // Remove physics object if is one VPhysicsDestroyObject(); if ( ( oldWeapon != NULL ) && ( oldWeapon != this ) ) { // Only pick up if not at max ammo amount if (oldWeapon->m_nAmmoCount[m_iCurrentAmmoType] < BrickBatAmmoArray[m_iCurrentAmmoType].m_nMaxCarry) { oldWeapon->m_nAmmoCount[m_iCurrentAmmoType]++; pBCC->GiveAmmo( 1, oldWeapon->m_iPrimaryAmmoType ); UTIL_Remove( this ); } } else { // Only pick up if not at max ammo amount if (m_nAmmoCount[m_iCurrentAmmoType] < BrickBatAmmoArray[m_iCurrentAmmoType].m_nMaxCarry) { m_nAmmoCount[m_iCurrentAmmoType]++; pBCC->GiveAmmo( 1, m_iPrimaryAmmoType ); SetThink (NULL); } } // ----------------------------------------------------- // Switch to this weapon if the only weapon I own // ----------------------------------------------------- if (!pBCC->GetActiveWeapon() && pBCC->GetActiveWeapon() != this) { pBCC->Weapon_Switch(oldWeapon); } } */ } //----------------------------------------------------------------------------- // Purpose: Gets event from anim stream and throws the object // Input : // Output : //----------------------------------------------------------------------------- void CWeaponBrickbat::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) { switch( pEvent->event ) { case EVENT_WEAPON_THROW: { CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); if (!pNPC) { return; } Vector vec_target = pNPC->GetEnemyLKP(); // ----------------------------------------------------- // Get position of throw // ----------------------------------------------------- // If owner has a hand, set position to the hand bone position Vector launchPos; int iBIndex = pNPC->LookupBone("Bip01 R Hand"); if (iBIndex != -1) { Vector origin; QAngle angles; pNPC->GetBonePosition( iBIndex, launchPos, angles); } // Otherwise just set to in front of the owner else { Vector vFacingDir = pNPC->BodyDirection2D( ); vFacingDir = vFacingDir * 60.0; launchPos = pNPC->GetLocalOrigin()+vFacingDir; } ThrowBrickbat( launchPos, m_vecTossVelocity, sk_npc_dmg_brickbat.GetFloat()); // Drop the weapon and remove as no more ammo pNPC->Weapon_Drop( this ); UTIL_Remove( this ); } break; default: BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); break; } } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- bool CWeaponBrickbat::ObjectInWay( void ) { CBaseCombatCharacter *pOwner = GetOwner(); if (!pOwner) { return false; } Vector vecSrc = pOwner->Weapon_ShootPosition( ); Vector vecAiming = pOwner->BodyDirection2D( ); trace_t tr; Vector vecEnd = vecSrc + (vecAiming * 32); UTIL_TraceLine( vecSrc, vecEnd, MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr ); if (tr.fraction < 1.0) { // Don't block on a living creature if (tr.m_pEnt) { CBaseEntity *pEntity = tr.m_pEnt; CBaseCombatCharacter *pBCC = ToBaseCombatCharacter( pEntity ); if (pBCC) { return false; } } return true; } else { return false; } } //----------------------------------------------------------------------------- // Purpose: Override to allow throw w/o LOS // Input : // Output : //----------------------------------------------------------------------------- bool CWeaponBrickbat::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos,bool bSetConditions) { // <> should test if can throw from present location here... return true; } //----------------------------------------------------------------------------- // Purpose: Override to check throw // Input : // Output : //----------------------------------------------------------------------------- int CWeaponBrickbat::WeaponRangeAttack1Condition( float flDot, float flDist ) { // If things haven't changed too much since last time // just return that previously calculated value if (gpGlobals->curtime < m_fNextThrowCheck ) { return m_iThrowBits; } if ( flDist < m_fMinRange1) { m_iThrowBits = COND_TOO_CLOSE_TO_ATTACK; } else if (flDist > m_fMaxRange1) { m_iThrowBits = COND_TOO_FAR_TO_ATTACK; } else if (flDot < 0.5) { m_iThrowBits = COND_NOT_FACING_ATTACK; } // If moving, can't throw. else if ( m_flGroundSpeed != 0 ) { m_iThrowBits = COND_NONE; } else { // Ok we should check again as some time has passed // This function is only used by NPC's so we can cast to a Base Monster CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); CBaseEntity *pEnemy = pNPC->GetEnemy(); if (!pEnemy) { return COND_NONE; } // Get Enemy Position Vector vecTarget; pEnemy->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &vecTarget ); // Get Toss Vector Vector throwStart = pNPC->Weapon_ShootPosition(); Vector vecToss; CBaseEntity* pBlocker = NULL; float throwDist = (throwStart - vecTarget).Length(); float fGravity = GetCurrentGravity(); float throwLimit = pNPC->ThrowLimit(throwStart, vecTarget, fGravity, 35, WorldAlignMins(), WorldAlignMaxs(), pEnemy, &vecToss, &pBlocker); // If I can make the throw (or most of the throw) if (!throwLimit || (throwLimit != throwDist && throwLimit > 0.8*throwDist)) { m_vecTossVelocity = vecToss; m_iThrowBits = COND_CAN_RANGE_ATTACK1; } else { m_iThrowBits = COND_NONE; } } // don't check again for a while. m_fNextThrowCheck = gpGlobals->curtime + 0.33; // 1/3 second. return m_iThrowBits; } //----------------------------------------------------------------------------- // Purpose: // // //----------------------------------------------------------------------------- void CWeaponBrickbat::ThrowBrickbat( Vector vecSrc, Vector vecVelocity, float damage) { CGrenade_Brickbat *pBrickbat = (CGrenade_Brickbat*)Create( BrickBatAmmoArray[m_iCurrentAmmoType].m_sClassName, vecSrc, vec3_angle, GetOwner() ); if (!pBrickbat) { Msg("Brickbat type (%s) not defined!\n",BrickBatAmmoArray[m_iCurrentAmmoType].m_sClassName); return; } AngularImpulse vecAngVel; // Tumble through the air vecAngVel.x = random->RandomFloat ( -100, -500 ); vecAngVel.z = random->RandomFloat ( -100, -500 ); vecAngVel.y = random->RandomFloat ( -100, -500 ); // If physically simulated IPhysicsObject *pPhysicsObject = pBrickbat->VPhysicsGetObject(); if ( pPhysicsObject ) { pPhysicsObject->AddVelocity( &vecVelocity, &vecAngVel ); } // Otherwise else { pBrickbat->SetAbsVelocity( vecVelocity ); QAngle angVel; AngularImpulseToQAngle( vecAngVel, angVel ); pBrickbat->SetLocalAngularVelocity( angVel ); } pBrickbat->SetThrower( GetOwner() ); pBrickbat->SetOwnerEntity( ((CBaseEntity*)GetOwner()) ); pBrickbat->SetDamage(damage); m_nAmmoCount[m_iCurrentAmmoType]--; m_bNeedThrow = false; } //----------------------------------------------------------------------------- // Purpose: // // //----------------------------------------------------------------------------- void CWeaponBrickbat::PrimaryAttack( void ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if (!pPlayer) { return; } SendWeaponAnim(ACT_VM_PULLBACK); // Don't fire again until fire animation has completed float flSequenceEndTime = gpGlobals->curtime + SequenceDuration(); pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = flSequenceEndTime; m_bNeedThrow = true; } //----------------------------------------------------------------------------- // Purpose: // // //----------------------------------------------------------------------------- void CWeaponBrickbat::Throw( void ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if (!pPlayer) { return; } Vector vecSrc = pPlayer->WorldSpaceCenter(); Vector vecFacing = pPlayer->BodyDirection3D( ); vecSrc = vecSrc + vecFacing * 18.0; vecSrc.z += 24.0f; // Player may have turned to face a wall during the throw anim in which case // we don't want to throw the SLAM into the wall if (ObjectInWay()) { vecSrc = pPlayer->WorldSpaceCenter() + vecFacing * 5.0; } Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); vecAiming.z += 0.20; // Raise up so passes through reticle ThrowBrickbat(vecSrc, vecAiming*800, sk_plr_dmg_brickbat.GetFloat()); pPlayer->RemoveAmmo( 1, m_iPrimaryAmmoType ); SendWeaponAnim(ACT_VM_THROW); // Don't fire again until fire animation has completed float flSequenceEndTime = gpGlobals->curtime + SequenceDuration(); pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = flSequenceEndTime; m_bNeedThrow = false; m_bNeedDraw = true; } //----------------------------------------------------------------------------- // Purpose: // // //----------------------------------------------------------------------------- void CWeaponBrickbat::SecondaryAttack( void ) { int counter = 0; while (counter < NUM_BRICKBAT_AMMO_TYPES) { m_iCurrentAmmoType = ((++m_iCurrentAmmoType)%NUM_BRICKBAT_AMMO_TYPES); // If I've found a category with ammo stop looking if (m_nAmmoCount[m_iCurrentAmmoType] > 0) { DrawAmmo(); return; } counter++; } // I'm out of all ammo types } //----------------------------------------------------------------------------- // Purpose: // // //----------------------------------------------------------------------------- void CWeaponBrickbat::DrawAmmo( void ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); // ------------------------------------------- // Make sure I have ammo of the current type // ------------------------------------------- int counter = 0; while (m_nAmmoCount[m_iCurrentAmmoType] <=0) { m_iCurrentAmmoType = ((++m_iCurrentAmmoType)%NUM_BRICKBAT_AMMO_TYPES); counter++; // ---------------------------------------------------- // No ammo of any types so drop the weapon and destroy // ---------------------------------------------------- if (counter >= NUM_BRICKBAT_AMMO_TYPES) { pOwner->Weapon_Drop( this, NULL, NULL ); UTIL_Remove(this); return; } } SetModel( BrickBatAmmoArray[m_iCurrentAmmoType].m_sViewModel); CBaseViewModel *vm = pOwner->GetViewModel(); if ( vm ) { vm->SetModel( BrickBatAmmoArray[m_iCurrentAmmoType].m_sViewModel ); } //Msg("Drawing %s...\n",BrickBatAmmoArray[m_iCurrentAmmoType].m_sClassName); m_bNeedDraw = false; SendWeaponAnim(ACT_VM_DRAW); // Don't fire again until fire animation has completed float flSequenceEndTime = gpGlobals->curtime + SequenceDuration(); pOwner->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = flSequenceEndTime; } //----------------------------------------------------------------------------- // Purpose: Override so shotgun can do mulitple reloads in a row // Input : // Output : //----------------------------------------------------------------------------- void CWeaponBrickbat::ItemPostFrame( void ) { /* HANDY FOR DEBUG for (int i=0;im_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime)) { SecondaryAttack(); } else if ((pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) { // Uses secondary ammo only if (pOwner->GetAmmoCount(m_iPrimaryAmmoType)) { PrimaryAttack(); } } else if (m_bNeedDraw) { DrawAmmo(); } else { SendWeaponAnim( ACT_VM_IDLE ); //pOwner->m_flNextAttack = gpGlobals->curtime + SequenceDuration(); } } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- void CWeaponBrickbat::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) { if ( info.GetDamageType() & DMG_BULLET) { if ( BrickBatAmmoArray[m_iCurrentAmmoType].m_nAmmoType == BRICKBAT_ROCK ) { g_pEffects->Ricochet(ptr->endpos,ptr->plane.normal); } } BaseClass::TraceAttack( info, vecDir, ptr ); } //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CWeaponBrickbat::CWeaponBrickbat( void ) { #ifdef _DEBUG m_vecTossVelocity.Init(); #endif m_fMinRange1 = 200; m_fMaxRange1 = 1000; }