//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #include "cbase.h" #include "tf_weapon_jar.h" #include "decals.h" // Client specific. #ifdef CLIENT_DLL #include "c_tf_player.h" // Server specific. #else #include "soundent.h" #include "te_effect_dispatch.h" #include "tf_player.h" #include "func_break.h" #include "func_nogrenades.h" #include "Sprite.h" #include "tf_fx.h" #include "tf_team.h" #include "tf_gamestats.h" #include "tf_gamerules.h" #include "particle_parse.h" #include "bone_setup.h" #endif //============================================================================= // // Weapon Jar tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFJar, DT_TFWeaponJar ) BEGIN_NETWORK_TABLE( CTFJar, DT_TFWeaponJar ) END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFJar ) END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( tf_weapon_jar, CTFJar ); PRECACHE_WEAPON_REGISTER( tf_weapon_jar ); IMPLEMENT_NETWORKCLASS_ALIASED( TFJarMilk, DT_TFWeaponJarMilk ) BEGIN_NETWORK_TABLE( CTFJarMilk, DT_TFWeaponJarMilk ) END_NETWORK_TABLE() LINK_ENTITY_TO_CLASS( tf_weapon_jar_milk, CTFJarMilk ); PRECACHE_WEAPON_REGISTER( tf_weapon_jar_milk ); IMPLEMENT_NETWORKCLASS_ALIASED( TFCleaver, DT_TFWeaponCleaver ) BEGIN_NETWORK_TABLE( CTFCleaver, DT_TFWeaponCleaver ) END_NETWORK_TABLE() LINK_ENTITY_TO_CLASS( tf_weapon_cleaver, CTFCleaver ); PRECACHE_WEAPON_REGISTER( tf_weapon_cleaver ); // Projectile tables. IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Jar, DT_TFProjectile_Jar ) BEGIN_NETWORK_TABLE( CTFProjectile_Jar, DT_TFProjectile_Jar ) END_NETWORK_TABLE() LINK_ENTITY_TO_CLASS( tf_projectile_jar, CTFProjectile_Jar ); PRECACHE_WEAPON_REGISTER( tf_projectile_jar ); IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_JarMilk, DT_TFProjectile_JarMilk ) BEGIN_NETWORK_TABLE( CTFProjectile_JarMilk, DT_TFProjectile_JarMilk ) END_NETWORK_TABLE() LINK_ENTITY_TO_CLASS( tf_projectile_jar_milk, CTFProjectile_JarMilk ); PRECACHE_WEAPON_REGISTER( tf_projectile_jar_milk ); IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Cleaver, DT_TFProjectile_Cleaver ) BEGIN_NETWORK_TABLE( CTFProjectile_Cleaver, DT_TFProjectile_Cleaver ) END_NETWORK_TABLE() LINK_ENTITY_TO_CLASS( tf_projectile_cleaver, CTFProjectile_Cleaver ); PRECACHE_WEAPON_REGISTER( tf_projectile_cleaver ); #define TF_JAR_LAUNCH_SPEED 1000.f #define TF_CLEAVER_LAUNCH_SPEED 7000.f #define TF_WEAPON_PEEJAR_MODEL "models/weapons/c_models/urinejar.mdl" #define TF_WEAPON_FESTIVE_PEEJAR_MODEL "models/weapons/c_models/c_xms_urinejar.mdl" #ifdef STAGING_ONLY #define TF_WEAPON_MILKJAR_MODEL "models/workshop/weapons/c_models/c_madmilk/c_madmilk.mdl" #define TF_WEAPON_CLEAVER_MODEL "models/workshop_partner/weapons/c_models/c_sd_cleaver/c_sd_cleaver.mdl" #else #define TF_WEAPON_MILKJAR_MODEL "models/weapons/c_models/c_madmilk/c_madmilk.mdl" #define TF_WEAPON_CLEAVER_MODEL "models/weapons/c_models/c_sd_cleaver/c_sd_cleaver.mdl" #endif #define TF_WEAPON_PEEJAR_EXPLODE_SOUND "Jar.Explode" #define TF_WEAPON_CLEAVER_IMPACT_FLESH_SOUND "Cleaver.ImpactFlesh" #define TF_WEAPON_CLEAVER_IMPACT_WORLD_SOUND "Cleaver.ImpactWorld" #ifdef STAGING_ONLY #define TF_WEAPON_WATER_BALLOON_KILL_SOUND "Game.PenetrationKill" #define TF_WEAPON_WATER_BALLOON_HIT_SOUND "Weapon_waterbomb.hit" #define TF_WEAPON_WATER_BALLOON_SCORE_SOUND "Weapon_waterbomb.score" #define TF_BREAD_MODEL "models/props_gameplay/small_loaf.mdl" #define TF_WATERBALLOON_RADIUS 32 #define TF_WATERBALLOON_CHARGEDRADIUS 64 #define TF_WATERBALLOON_EXPLODE_SOUND "Weapon_waterbomb.explode" #endif //============================================================================= // // Weapon Jar functions. // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFJar::CTFJar() { } float CTFJar::GetProjectileSpeed( void ) { return TF_JAR_LAUNCH_SPEED; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFJar::PrimaryAttack( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; int iJarCount = pPlayer->GetAmmoCount( m_iPrimaryAmmoType ); if ( iJarCount == 0 ) return; if ( ( pPlayer->GetWaterLevel() == WL_Eyes ) && !CanThrowUnderWater() ) return; BaseClass::PrimaryAttack(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseEntity *CTFJar::FireJar( CTFPlayer *pPlayer ) { StartEffectBarRegen(); SetContextThink( &CTFJar::TossJarThink, gpGlobals->curtime + 0.1f, "TOSS_JAR_THINK" ); return NULL; } #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Jar *CTFJar::CreateJarProjectile( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo ) { return CTFProjectile_Jar::Create( position, angles, velocity, angVelocity, pOwner, weaponInfo ); } #endif #ifdef GAME_DLL Vector CTFJar::GetVelocityVector( const Vector &vecForward, const Vector &vecRight, const Vector &vecUp ) { return ( ( vecForward * GetProjectileSpeed() ) + ( vecUp * 200.0f ) + ( random->RandomFloat( -10.0f, 10.0f ) * vecRight ) + ( random->RandomFloat( -10.0f, 10.0f ) * vecUp ) ); } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFJar::TossJarThink( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; PlayWeaponShootSound(); #ifdef GAME_DLL Vector vecForward, vecRight, vecUp; AngleVectors( pPlayer->EyeAngles(), &vecForward, &vecRight, &vecUp ); float fRight = 8.f; if ( IsViewModelFlipped() ) { fRight *= -1; } Vector vecSrc = pPlayer->Weapon_ShootPosition(); vecSrc += vecForward * 16.0f + vecRight * fRight + vecUp * -6.0f; trace_t trace; Vector vecEye = pPlayer->EyePosition(); CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE ); UTIL_TraceHull( vecEye, vecSrc, -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); // If we started in solid, don't let them fire at all if ( trace.startsolid ) return; Vector vecVelocity = GetVelocityVector( vecForward, vecRight, vecUp ); CTFProjectile_Jar *pProjectile = CreateJarProjectile( trace.endpos, pPlayer->EyeAngles(), vecVelocity, GetAngularImpulse(), pPlayer, GetTFWpnData() ); if ( pProjectile ) { pProjectile->SetCritical( IsCurrentAttackACrit() ); pProjectile->SetLauncher( this ); } if ( ShouldSpeakWhenFiring() ) { pPlayer->SpeakWeaponFire( MP_CONCEPT_JARATE_LAUNCH ); } #endif } //----------------------------------------------------------------------------- void CTFJar::GetProjectileEntityName( CAttribute_String *attrProjectileEntityName ) { static CSchemaAttributeDefHandle pAttrDef_ProjectileEntityName( "projectile entity name" ); CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pAttrDef_ProjectileEntityName && pItem ) { //CAttribute_String attrProjectileEntityName; pItem->FindAttribute( pAttrDef_ProjectileEntityName, attrProjectileEntityName ); } } #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Jar::CTFProjectile_Jar() { m_vCollisionVelocity = Vector( 0,0,0 ); m_iProjectileType = TF_PROJECTILE_JAR; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Jar::Precache() { PrecacheModel( TF_WEAPON_PEEJAR_MODEL ); PrecacheModel( TF_WEAPON_FESTIVE_PEEJAR_MODEL ); PrecacheModel( "models/weapons/c_models/c_breadmonster/c_breadmonster.mdl" ); PrecacheScriptSound( TF_WEAPON_PEEJAR_EXPLODE_SOUND ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Jar::SetCustomPipebombModel() { // Check for Model Override int iProjectile = 0; CTFPlayer *pThrower = ToTFPlayer( GetThrower() ); if ( pThrower && pThrower->GetActiveWeapon() ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( pThrower->GetActiveWeapon(), iProjectile, override_projectile_type ); switch ( iProjectile ) { case TF_PROJECTILE_FESTIVE_JAR : m_iProjectileType = iProjectile; SetModel( TF_WEAPON_FESTIVE_PEEJAR_MODEL ); return; case TF_PROJECTILE_BREADMONSTER_JARATE: case TF_PROJECTILE_BREADMONSTER_MADMILK: m_iProjectileType = iProjectile; SetModel( "models/weapons/c_models/c_breadmonster/c_breadmonster.mdl" ); return; } } SetModel( TF_WEAPON_PEEJAR_MODEL ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Jar* CTFProjectile_Jar::Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo ) { CTFProjectile_Jar *pGrenade = static_cast( CBaseEntity::CreateNoSpawn( "tf_projectile_jar", position, angles, pOwner ) ); if ( pGrenade ) { // Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly. pGrenade->SetPipebombMode(); DispatchSpawn( pGrenade ); pGrenade->InitGrenade( velocity, angVelocity, pOwner, weaponInfo ); #ifdef _X360 if ( pGrenade->m_iType != TF_GL_MODE_REMOTE_DETONATE ) { pGrenade->SetDamage( TF_WEAPON_GRENADE_XBOX_DAMAGE ); } #endif pGrenade->m_flFullDamage = 0; pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity ); } return pGrenade; } extern void ExtinguishPlayer( CEconEntity *pExtinguisher, CTFPlayer *pOwner, CTFPlayer *pTarget, const char *pExtinguisherName ); void JarExplode( int iEntIndex, CTFPlayer *pAttacker, CBaseEntity *pOriginalWeapon, CBaseEntity *pWeapon, const Vector& vContactPoint, int iTeam, float flRadius, ETFCond cond, float flDuration, const char *pszImpactEffect ) { // Splash! CPVSFilter particleFilter( vContactPoint ); TE_TFParticleEffect( particleFilter, 0.0, pszImpactEffect, vContactPoint, vec3_angle ); // Explosion effect. CBroadcastRecipientFilter soundFilter; Vector vecOrigin = vContactPoint; CBaseEntity::EmitSound( soundFilter, iEntIndex, TF_WEAPON_PEEJAR_EXPLODE_SOUND, &vecOrigin ); // Treat this trace exactly like radius damage CTraceFilterIgnorePlayers traceFilter( pAttacker, COLLISION_GROUP_PROJECTILE ); // Splash pee on everyone nearby. CBaseEntity *pListOfEntities[32]; int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vContactPoint, flRadius, FL_CLIENT ); for ( int i = 0; i < iEntities; ++i ) { CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] ); if ( !pPlayer || !pPlayer->IsAlive() ) continue; // Do a quick trace to see if there's any geometry in the way. // Pee isn't stopped by other entities. Splishy splashy. trace_t trace; UTIL_TraceLine( vContactPoint, pPlayer->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &trace ); if ( trace.DidHitWorld() ) continue; // Drench the target. if ( pPlayer->GetTeamNumber() != iTeam ) { if ( pPlayer->m_Shared.IsInvulnerable() ) continue; if ( pPlayer->m_Shared.InCond( TF_COND_PHASE ) || pPlayer->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) ) continue; if ( !pPlayer->CanGetWet() ) continue; pPlayer->m_Shared.AddCond( cond, flDuration, pAttacker ); pPlayer->m_Shared.SetPeeAttacker( pAttacker ); pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_JARATE_HIT ); if ( pAttacker ) { if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) && pPlayer->m_Shared.GetPercentInvisible() == 1.0f ) { pAttacker->AwardAchievement( ACHIEVEMENT_TF_SNIPER_JARATE_REVEAL_SPY ); } float flStun = 1.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pAttacker, flStun, applies_snare_effect ); if ( flStun != 1.0f ) { pPlayer->m_Shared.StunPlayer( flDuration, flStun, TF_STUN_MOVEMENT, pAttacker ); } // Stats tracking? if ( cond == TF_COND_URINE || cond == TF_COND_MAD_MILK ) { if ( TFGameRules() && TFGameRules()->IsPVEModeActive() ) { // These if statements are intentionally split to avoid falling through to the normal kKillEaterEvent_PeeVictims event if we're in // IsPVEModeActive() but not a robot, or don't have the stun. if ( pPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS && flStun != 1.0f ) { EconEntity_OnOwnerKillEaterEvent( dynamic_cast( pWeapon ), pAttacker, pPlayer, kKillEaterEvent_RobotsSlowed ); } } else { EconEntity_OnOwnerKillEaterEvent( dynamic_cast( pWeapon ), pAttacker, pPlayer, kKillEaterEvent_PeeVictims ); } } // Tell the clients involved in the jarate CRecipientFilter involved_filter; involved_filter.AddRecipient( pPlayer ); involved_filter.AddRecipient( pAttacker ); UserMessageBegin( involved_filter, "PlayerJarated" ); WRITE_BYTE( pAttacker->entindex() ); WRITE_BYTE( pPlayer->entindex() ); MessageEnd(); const char *pszEvent = NULL; switch( cond ) { case TF_COND_URINE: pszEvent = "jarate_attack"; break; case TF_COND_MAD_MILK: pszEvent = "milk_attack"; break; } if ( pszEvent && pszEvent[0] ) { UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"%s\" against \"%s<%i><%s><%s>\" with \"%s\" (attacker_position \"%d %d %d\") (victim_position \"%d %d %d\")\n", pAttacker->GetPlayerName(), pAttacker->GetUserID(), pAttacker->GetNetworkIDString(), pAttacker->GetTeam()->GetName(), pszEvent, pPlayer->GetPlayerName(), pPlayer->GetUserID(), pPlayer->GetNetworkIDString(), pPlayer->GetTeam()->GetName(), "tf_weapon_jar", (int)pAttacker->GetAbsOrigin().x, (int)pAttacker->GetAbsOrigin().y, (int)pAttacker->GetAbsOrigin().z, (int)pPlayer->GetAbsOrigin().x, (int)pPlayer->GetAbsOrigin().y, (int)pPlayer->GetAbsOrigin().z ); } } } else { if ( pAttacker && pPlayer->m_Shared.InCond( TF_COND_BURNING ) ) { ExtinguishPlayer( dynamic_cast( pWeapon ), pAttacker, pPlayer, "tf_weapon_jar" ); // Return some percentage of the jar to the thrown weapon if extinguishing an ally auto pLauncher = dynamic_cast< CTFWeaponBase* >( pOriginalWeapon ); if ( pLauncher && pAttacker != pPlayer && pLauncher->HasEffectBarRegeneration() ) { float fCooldown = 1.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pLauncher, fCooldown, extinguish_reduces_cooldown ); fCooldown = 1.0f - fCooldown; if ( fCooldown > 0 ) { if ( pLauncher->GetEffectBarProgress() < fCooldown ) { float fDuration = pLauncher->GetEffectBarRechargeTime(); float fIncrement = fDuration * fCooldown; pLauncher->DecrementBarRegenTime( fIncrement ); } } } } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Jar::Explode( trace_t *pTrace, int bitsDamageType ) { SetModelName( NULL_STRING );//invisible AddSolidFlags( FSOLID_NOT_SOLID ); m_takedamage = DAMAGE_NO; // Pull out of the wall a bit. if ( pTrace->fraction != 1.0 ) { SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) ); } CTFPlayer *pThrower = ToTFPlayer( GetThrower() ); JarExplode( entindex(), pThrower, GetOriginalLauncher(), GetLauncher(), GetAbsOrigin(), GetTeamNumber(), GetDamageRadius(), GetEffectCondition(), 10.f, GetImpactEffect() ); // Debug radius draw. //DrawRadius( GetDamageRadius() ); SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" ); SetTouch( NULL ); AddEffects( EF_NODRAW ); SetAbsVelocity( vec3_origin ); } //----------------------------------------------------------------------------- void CTFProjectile_Jar::PipebombTouch( CBaseEntity *pOther ) { if ( pOther == GetThrower() ) return; if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) ) return; if ( !pOther->IsWorld() && !pOther->IsPlayer() ) return; // Don't collide with teammate if we're still in the grace period. if ( pOther->IsPlayer() && pOther->GetTeamNumber() == GetTeamNumber() && !CanCollideWithTeammates() ) { // Exception to this rule - if we're a jar or milk, and our potential victim is on fire, then allow collision after all. // If we're a jar or milk, then still allow collision if our potential victim is on fire. if (m_iProjectileType == TF_PROJECTILE_JAR || m_iProjectileType == TF_PROJECTILE_JAR_MILK) { auto victim = ToTFPlayer(pOther); if (!victim->m_Shared.InCond(TF_COND_BURNING)) { return; } } else { return; } } // Handle hitting skybox (disappear). trace_t pTrace; Vector velDir = GetAbsVelocity(); if ( velDir.IsZero() && pOther && pOther->IsPlayer() ) { velDir = pOther->WorldSpaceCenter() - GetAbsOrigin(); } VectorNormalize( velDir ); Vector vecSpot = GetAbsOrigin() - velDir * 32; UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace ); if ( pTrace.fraction < 1.0 && pTrace.surface.flags & SURF_SKY ) { UTIL_Remove( this ); return; } // If we already touched a surface then we're not exploding on contact anymore. if ( m_bTouched == true ) return; OnHit( pOther ); if ( m_iProjectileType == TF_PROJECTILE_BREADMONSTER_JARATE || m_iProjectileType == TF_PROJECTILE_BREADMONSTER_MADMILK ) { OnBreadMonsterHit( pOther, &pTrace ); } if ( ExplodesOnHit() ) { // Save this entity as enemy, they will take 100% damage if applicable m_hEnemy = pOther; Explode( &pTrace, GetDamageType() ); } } //----------------------------------------------------------------------------- void CTFProjectile_Jar::OnBreadMonsterHit( CBaseEntity *pOther, trace_t *pTrace ) { if ( m_iProjectileType != TF_PROJECTILE_BREADMONSTER_JARATE && m_iProjectileType != TF_PROJECTILE_BREADMONSTER_MADMILK ) return; CTFPlayer *pVictim = ToTFPlayer( pOther ); if ( !pVictim || pVictim->GetTeamNumber() == GetTeamNumber() ) return; // This is a player on the other team, attach a breadmonster CTFPlayer *pOwner = ToTFPlayer( GetThrower() ); // Attach Breadmonster to Victim CreateStickyAttachmentToTarget( pOwner, pVictim, pTrace ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Jar::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { BaseClass::VPhysicsCollision( index, pEvent ); int otherIndex = !index; CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex]; if ( !pHitEntity ) return; if ( pHitEntity->IsWorld() ) { OnHitWorld(); } // Break if we hit the world. bool bIsDynamicProp = ( NULL != dynamic_cast( pHitEntity ) ); if ( ExplodesOnHit() && pHitEntity && ( pHitEntity->IsWorld() || bIsDynamicProp ) ) { // Explode immediately next frame. (Can't explode in the collision callback.) m_vCollisionVelocity = pEvent->preVelocity[index]; SetContextThink( &CTFProjectile_Jar::VPhysicsCollisionThink, gpGlobals->curtime, "JarCollisionThink" ); } } //----------------------------------------------------------------------------- // Purpose: Handles exploding after a vphysics collision has happened. // This prevents changing collision properties during the vphysics callback itself. //----------------------------------------------------------------------------- void CTFProjectile_Jar::VPhysicsCollisionThink( void ) { if ( !ExplodesOnHit() ) return; trace_t pTrace; Vector velDir = m_vCollisionVelocity; VectorNormalize( velDir ); Vector vecSpot = GetAbsOrigin() - velDir * 16; UTIL_TraceLine( vecSpot, vecSpot + velDir * 32, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace ); Explode( &pTrace, GetDamageType() ); } //----------------------------------------------------------------------------- bool CTFProjectile_Jar::PositionArrowOnBone( mstudiobbox_t *pBox, CBaseAnimating *pOtherAnim ) { CStudioHdr *pStudioHdr = pOtherAnim->GetModelPtr(); if ( !pStudioHdr ) return false; mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pOtherAnim->GetHitboxSet() ); if ( !set ) return false; if ( !set->numhitboxes ) // Target must have hit boxes. return false; if ( pBox->bone < 0 || pBox->bone >= pStudioHdr->numbones() ) // Bone index must be valid. return false; CBoneCache *pCache = pOtherAnim->GetBoneCache(); if ( !pCache ) return false; matrix3x4_t *bone_matrix = pCache->GetCachedBone( pBox->bone ); if ( !bone_matrix ) return false; Vector vecBoxAbsMins, vecBoxAbsMaxs; TransformAABB( *bone_matrix, pBox->bbmin, pBox->bbmax, vecBoxAbsMins, vecBoxAbsMaxs ); // Adjust the arrow so it isn't exactly in the center of the box. Vector position; Vector vecDelta = vecBoxAbsMaxs - vecBoxAbsMins; float frand = (float)rand() / VALVE_RAND_MAX; position.x = vecBoxAbsMins.x + vecDelta.x*0.6f - vecDelta.x*frand*0.2f; frand = (float)rand() / VALVE_RAND_MAX; position.y = vecBoxAbsMins.y + vecDelta.y*0.6f - vecDelta.y*frand*0.2f; frand = (float)rand() / VALVE_RAND_MAX; position.z = vecBoxAbsMins.z + vecDelta.z*0.6f - vecDelta.z*frand*0.2f; SetAbsOrigin( position ); return true; } //----------------------------------------------------------------------------- void CTFProjectile_Jar::GetBoneAttachmentInfo( mstudiobbox_t *pBox, CBaseAnimating *pOtherAnim, Vector &bonePosition, QAngle &boneAngles, int &boneIndexAttached, int &physicsBoneIndex ) { // Find a bone to stick to. matrix3x4_t arrowWorldSpace; MatrixCopy( EntityToWorldTransform(), arrowWorldSpace ); // Get the bone info so we can follow the bone. boneIndexAttached = pBox->bone; physicsBoneIndex = pOtherAnim->GetPhysicsBone( boneIndexAttached ); matrix3x4_t boneToWorld; pOtherAnim->GetBoneTransform( boneIndexAttached, boneToWorld ); Vector attachedBonePos; QAngle attachedBoneAngles; pOtherAnim->GetBonePosition( boneIndexAttached, attachedBonePos, attachedBoneAngles ); // Transform my current position/orientation into the hit bone's space. matrix3x4_t worldToBone, localMatrix; MatrixInvert( boneToWorld, worldToBone ); ConcatTransforms( worldToBone, arrowWorldSpace, localMatrix ); MatrixAngles( localMatrix, boneAngles, bonePosition ); } //----------------------------------------------------------------------------- void CTFProjectile_Jar::CreateStickyAttachmentToTarget( CTFPlayer *pOwner, CTFPlayer *pVictim, trace_t *trace ) { // Dont stick to the sky! if ( trace->surface.flags & SURF_SKY ) { return; } // If I hit a player, remove the jar and replace with the face eater version CStudioHdr *pStudioHdr = NULL; mstudiohitboxset_t *set = NULL; pStudioHdr = pVictim->GetModelPtr(); if ( pStudioHdr ) { set = pStudioHdr->pHitboxSet( pVictim->GetHitboxSet() ); } // Look for nearest hitbox mstudiobbox_t *closest_box = NULL; if ( trace->m_pEnt && trace->m_pEnt->GetTeamNumber() != GetTeamNumber() ) { closest_box = set->pHitbox( trace->hitbox ); } if ( closest_box ) { if ( !PositionArrowOnBone( closest_box, pVictim ) ) return; // See if we're supposed to stick in the target. Vector bonePosition = vec3_origin; QAngle boneAngles = QAngle( 0, 0, 0 ); int boneIndexAttached = -1; int physicsBoneIndex = -1; GetBoneAttachmentInfo( closest_box, pVictim, bonePosition, boneAngles, boneIndexAttached, physicsBoneIndex ); IGameEvent * event = gameeventmanager->CreateEvent( "arrow_impact" ); if ( event ) { event->SetInt( "attachedEntity", pVictim->entindex() ); event->SetInt( "shooter", pOwner->entindex() ); event->SetInt( "attachedEntity", pVictim->entindex() ); event->SetInt( "boneIndexAttached", boneIndexAttached ); event->SetFloat( "bonePositionX", bonePosition.x ); event->SetFloat( "bonePositionY", bonePosition.y ); event->SetFloat( "bonePositionZ", bonePosition.z ); event->SetFloat( "boneAnglesX", boneAngles.x ); event->SetFloat( "boneAnglesY", boneAngles.y ); event->SetFloat( "boneAnglesZ", boneAngles.z ); event->SetInt( "projectileType", GetProjectileType() ); event->SetBool( "isCrit", IsCritical() ); gameeventmanager->FireEvent( event ); } } } #endif #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CTFProjectile_Jar::GetTrailParticleName( void ) { if ( GetTeamNumber() == TF_TEAM_BLUE ) { return "peejar_trail_blu"; } else { return "peejar_trail_red"; } } #endif #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Jar *CTFJarMilk::CreateJarProjectile( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo ) { return CTFProjectile_JarMilk::Create( position, angles, velocity, angVelocity, pOwner, weaponInfo ); } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_JarMilk::Precache() { PrecacheModel( TF_WEAPON_MILKJAR_MODEL ); BaseClass::Precache(); } #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_JarMilk* CTFProjectile_JarMilk::Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo ) { CTFProjectile_JarMilk *pGrenade = static_cast( CBaseEntity::CreateNoSpawn( "tf_projectile_jar_milk", position, angles, pOwner ) ); if ( pGrenade ) { // Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly. pGrenade->SetPipebombMode(); DispatchSpawn( pGrenade ); pGrenade->InitGrenade( velocity, angVelocity, pOwner, weaponInfo ); #ifdef _X360 if ( pGrenade->m_iType != TF_GL_MODE_REMOTE_DETONATE ) { pGrenade->SetDamage( TF_WEAPON_GRENADE_XBOX_DAMAGE ); } #endif pGrenade->m_flFullDamage = 0; pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity ); } return pGrenade; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_JarMilk::SetCustomPipebombModel() { // Check for Model Override int iProjectile = 0; CTFPlayer *pThrower = ToTFPlayer( GetThrower() ); if ( pThrower && pThrower->GetActiveWeapon() ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( pThrower->GetActiveWeapon(), iProjectile, override_projectile_type ); switch ( iProjectile ) { case TF_PROJECTILE_BREADMONSTER_JARATE: case TF_PROJECTILE_BREADMONSTER_MADMILK: m_iProjectileType = iProjectile; SetModel( "models/weapons/c_models/c_breadmonster/c_breadmonster_milk.mdl" ); return; } } SetModel( TF_WEAPON_MILKJAR_MODEL ); } #endif #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char* CTFJarMilk::ModifyEventParticles( const char* token ) { if ( FStrEq( token, "energydrink_splash") ) { CEconItemView *pItem = m_AttributeManager.GetItem(); int iSystems = pItem->GetStaticData()->GetNumAttachedParticles( GetTeamNumber() ); for ( int i = 0; i < iSystems; i++ ) { attachedparticlesystem_t *pSystem = pItem->GetStaticData()->GetAttachedParticleData( GetTeamNumber(),i ); if ( pSystem->iCustomType == 1 ) { return pSystem->pszSystemName; } } } return BaseClass::ModifyEventParticles( token ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFJarMilk::Holster( CBaseCombatWeapon *pSwitchingTo ) { CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() ); if ( pOwner && pOwner->IsLocalPlayer() ) { C_BaseEntity *pParticleEnt = pOwner->GetViewModel(0); if ( pParticleEnt ) { pOwner->StopViewModelParticles( pParticleEnt ); } } return BaseClass::Holster( pSwitchingTo ); } #endif #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Vector CTFCleaver::GetVelocityVector( const Vector &vecForward, const Vector &vecRight, const Vector &vecUp ) { Vector vecVelocity; // Calculate the initial impulse on the item. vecVelocity = Vector( 0.0f, 0.0f, 0.0f ); vecVelocity += vecForward * 10; vecVelocity += vecUp * 1; VectorNormalize( vecVelocity ); vecVelocity *= 3000; return vecVelocity; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Jar *CTFCleaver::CreateJarProjectile( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo ) { return CTFProjectile_Cleaver::Create( position, angles, velocity, angVelocity, pOwner, weaponInfo, GetSkin() ); } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFCleaver::GetProjectileSpeed( void ) { return TF_CLEAVER_LAUNCH_SPEED; } //----------------------------------------------------------------------------- void CTFCleaver::SecondaryAttack( void ) { PrimaryAttack(); } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char* CTFCleaver::ModifyEventParticles( const char* token ) { return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFCleaver::Holster( CBaseCombatWeapon *pSwitchingTo ) { return BaseClass::Holster( pSwitchingTo ); } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Cleaver::Precache() { PrecacheModel( TF_WEAPON_CLEAVER_MODEL ); PrecacheScriptSound( TF_WEAPON_CLEAVER_IMPACT_FLESH_SOUND ); PrecacheScriptSound( TF_WEAPON_CLEAVER_IMPACT_WORLD_SOUND ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Cleaver::SetCustomPipebombModel() { SetModel( TF_WEAPON_CLEAVER_MODEL ); } CTFProjectile_Cleaver::CTFProjectile_Cleaver() { #ifdef GAME_DLL m_bHitPlayer = false; m_bSoundPlayed = false; #endif } #ifdef GAME_DLL #define FLIGHT_TIME_TO_MAX_DMG 1.f //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Cleaver::OnHit( CBaseEntity *pOther ) { SetModelName( NULL_STRING );//invisible AddSolidFlags( FSOLID_NOT_SOLID ); CTFPlayer *pOwner = ToTFPlayer( GetThrower() ); if ( !pOwner ) return; if ( !pOther || !pOther->IsPlayer() ) return; CTFPlayer *pPlayer = ToTFPlayer( pOther ); if ( !pPlayer ) return; // Can't bleed an invul player. if ( pPlayer->m_Shared.IsInvulnerable() || pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) ) return; if ( pPlayer->GetTeamNumber() == pOwner->GetTeamNumber() ) return; if ( TFGameRules() && TFGameRules()->IsTruceActive() && pOwner->IsTruceValidForEnt() ) return; bool bIsCriticalHit = IsCritical(); bool bIsMiniCrit = false; float flBleedTime = 5.0f; float flLifeTime = gpGlobals->curtime - m_flCreationTime; if ( flLifeTime >= FLIGHT_TIME_TO_MAX_DMG ) { bIsMiniCrit = true; } // just do the bleed effect directly since the bleed // attribute comes from the inflictor, which is the cleaver. pPlayer->m_Shared.MakeBleed( pOwner, (CTFCleaver *)GetLauncher(), flBleedTime ); // Give 'em a love tap. const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); trace_t *pNewTrace = const_cast( pTrace ); CBaseEntity *pInflictor = GetLauncher(); CTakeDamageInfo info; info.SetAttacker( pOwner ); info.SetInflictor( pInflictor ); info.SetWeapon( pInflictor ); info.SetDamage( GetDamage() ); info.SetDamageCustom( bIsMiniCrit ? TF_DMG_CUSTOM_CLEAVER_CRIT : TF_DMG_CUSTOM_CLEAVER ); info.SetDamagePosition( GetAbsOrigin() ); int iDamageType = GetDamageType(); if ( bIsCriticalHit ) { iDamageType |= DMG_CRITICAL; } info.SetDamageType( iDamageType ); // Hurt 'em. Vector dir; AngleVectors( GetAbsAngles(), &dir ); pPlayer->DispatchTraceAttack( info, dir, pNewTrace ); ApplyMultiDamage(); // sound effects EmitSound_t params; params.m_flSoundTime = 0; params.m_pflSoundDuration = 0; params.m_pSoundName = TF_WEAPON_CLEAVER_IMPACT_FLESH_SOUND; CPASFilter filter( GetAbsOrigin() ); filter.RemoveRecipient( pOwner ); EmitSound( filter, entindex(), params ); CSingleUserRecipientFilter attackerFilter( pOwner ); EmitSound( attackerFilter, pOwner->entindex(), params ); AddEffects( EF_NODRAW ); SetAbsVelocity( vec3_origin ); SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime + 2, "RemoveThink" ); SetTouch( NULL ); m_bHitPlayer = true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Cleaver::Explode( trace_t *pTrace, int bitsDamageType ) { if ( !m_bHitPlayer ) { if ( !m_bSoundPlayed ) { EmitSound( TF_WEAPON_CLEAVER_IMPACT_WORLD_SOUND ); m_bSoundPlayed = true; } SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime + 2, "RemoveThink" ); SetTouch( NULL ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Cleaver::Detonate( void ) { trace_t tr; Vector vecSpot;// trace starts here! SetThink( NULL ); vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 ); UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -32 ), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, & tr); Explode( &tr, GetDamageType() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Cleaver* CTFProjectile_Cleaver::Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo, int nSkin ) { CTFProjectile_Cleaver *pGrenade = static_cast( CBaseEntity::CreateNoSpawn( "tf_projectile_cleaver", position, angles, pOwner ) ); if ( pGrenade ) { // Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly. pGrenade->SetPipebombMode(); DispatchSpawn( pGrenade ); pGrenade->m_nSkin = nSkin; pGrenade->InitGrenade( velocity, angVelocity, pOwner, weaponInfo ); pGrenade->m_flFullDamage = 0; pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity ); } return pGrenade; } #else //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CTFProjectile_Cleaver::GetTrailParticleName( void ) { if ( GetTeamNumber() == TF_TEAM_BLUE ) { return "peejar_trail_blu_glow"; } else { return "peejar_trail_red_glow"; } } #endif // GAME_DLL