summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_weapon_jar.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/tf_weapon_jar.cpp')
-rw-r--r--game/shared/tf/tf_weapon_jar.cpp1155
1 files changed, 1155 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_jar.cpp b/game/shared/tf/tf_weapon_jar.cpp
new file mode 100644
index 0000000..419c240
--- /dev/null
+++ b/game/shared/tf/tf_weapon_jar.cpp
@@ -0,0 +1,1155 @@
+//========= 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<CTFProjectile_Jar*>( 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<CEconEntity *>( pWeapon ), pAttacker, pPlayer, kKillEaterEvent_RobotsSlowed );
+ }
+ }
+ else
+ {
+ EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( 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<CEconEntity *>( 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<CDynamicProp *>( 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<CTFProjectile_JarMilk*>( 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<trace_t*>( 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<CTFProjectile_Cleaver*>( 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 \ No newline at end of file