diff options
Diffstat (limited to 'game/shared/tf/tf_weapon_jar.cpp')
| -rw-r--r-- | game/shared/tf/tf_weapon_jar.cpp | 1155 |
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 |