@@ -538,32 +538,199 @@ localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir,
538538=================
539539CG_Bleed
540540
541- This is the spurt of blood when a character gets hit
541+ This is the spurt of blood when a character gets hit.
542+ Spawns multiple blood droplet particles that spray along the impact direction.
543+ dir = direction the projectile was traveling (NULL for omnidirectional spray)
544+ weapon = weapon type for determining particle count
542545=================
543546*/
544- void CG_Bleed ( vec3_t origin , int entityNum ) {
545- localEntity_t * ex ;
547+ #define BLOOD_PARTICLE_SPEED_EXIT 600
548+ #define BLOOD_PARTICLE_SPEED_ENTRY 50
549+
550+ static int CG_BloodParticleCount ( int weapon ) {
551+ // Returns droplet count (reduced by 1 since we also spawn a puff)
552+ switch ( weapon ) {
553+ case WP_PLASMAGUN :
554+ return 4 ;
555+ case WP_BFG :
556+ return 7 ;
557+ case WP_ROCKET_LAUNCHER :
558+ case WP_GRENADE_LAUNCHER :
559+ return 9 ;
560+ // Weapons that trigger CG_Bleed via EV_MISSILE_HIT or EV_BULLET_HIT_FLESH:
561+ // Gauntlet, machine gun, shotgun, lightning gun, or even grappling hook
562+ // Railgun doesn't trigger a blood event.
563+ default :
564+ return 2 ;
565+ }
566+ }
567+
568+ void CG_Bleed ( vec3_t origin , vec3_t dir , int entityNum , int weapon ) {
569+ localEntity_t * le ;
570+ refEntity_t * re ;
571+ int i ;
572+ int particleCount ;
573+ vec3_t velocity ;
574+ vec3_t baseDir ;
575+ vec3_t perpA , perpB ;
576+ float spread , forwardBias ;
577+ float speed ;
578+ qboolean isPlayer ;
546579
547580 if ( !cg_blood .integer ) {
548581 return ;
549582 }
550583
551- ex = CG_AllocLocalEntity ();
552- ex -> leType = LE_EXPLOSION ;
584+ isPlayer = ( entityNum == cg .snap -> ps .clientNum );
553585
554- ex -> startTime = cg .time ;
555- ex -> endTime = ex -> startTime + 500 ;
556-
557- VectorCopy ( origin , ex -> refEntity .origin );
558- ex -> refEntity .reType = RT_SPRITE ;
559- ex -> refEntity .rotation = rand () % 360 ;
560- ex -> refEntity .radius = 24 ;
586+ // If particles disabled, use original sprite-based blood effect
587+ if ( !cg_bloodParticles .integer ) {
588+ le = CG_AllocLocalEntity ();
589+ le -> leType = LE_EXPLOSION ;
590+ le -> startTime = cg .time ;
591+ le -> endTime = le -> startTime + 500 ;
592+
593+ VectorCopy ( origin , le -> refEntity .origin );
594+ le -> refEntity .reType = RT_SPRITE ;
595+ le -> refEntity .rotation = rand () % 360 ;
596+ le -> refEntity .radius = 24 ;
597+ le -> refEntity .customShader = cgs .media .bloodExplosionShader ;
561598
562- ex -> refEntity .customShader = cgs .media .bloodExplosionShader ;
599+ // don't show player's own blood in view
600+ if ( isPlayer ) {
601+ le -> refEntity .renderfx |= RF_THIRD_PERSON ;
602+ }
603+ return ;
604+ }
563605
564- // don't show player's own blood in view
565- if ( entityNum == cg .snap -> ps .clientNum ) {
566- ex -> refEntity .renderfx |= RF_THIRD_PERSON ;
606+ qboolean inLiquid = ( CG_PointContents ( origin , -1 ) & MASK_WATER ) != 0 ;
607+
608+ if ( inLiquid ) {
609+ // Single puff underwater
610+ float puffRadius = 2 + random () * 3 ; // 2-5 units
611+ int puffDuration = 300 + random () * 200 ; // 300-500ms
612+
613+ le = CG_SmokePuff ( origin , vec3_origin ,
614+ puffRadius ,
615+ 1 , 1 , 1 , 1 ,
616+ puffDuration ,
617+ cg .time , 0 , 0 ,
618+ cgs .media .bloodTrailShader );
619+ le -> leType = LE_FALL_SCALE_FADE ;
620+ le -> pos .trDelta [2 ] = -2 ; // Slow rise
621+
622+ if ( isPlayer ) {
623+ le -> refEntity .renderfx |= RF_THIRD_PERSON ;
624+ }
625+ return ;
626+ }
627+
628+ particleCount = CG_BloodParticleCount ( weapon );
629+
630+ // Set up directional basis if we have a direction
631+ if ( dir && ( dir [0 ] != 0 || dir [1 ] != 0 || dir [2 ] != 0 ) ) {
632+ VectorNormalize2 ( dir , baseDir );
633+ // Create perpendicular vectors for spray spread
634+ PerpendicularVector ( perpA , baseDir );
635+ CrossProduct ( baseDir , perpA , perpB );
636+ } else {
637+ // No direction - use upward as default
638+ VectorSet ( baseDir , 0 , 0 , 1 );
639+ VectorSet ( perpA , 1 , 0 , 0 );
640+ VectorSet ( perpB , 0 , 1 , 0 );
641+ }
642+
643+ // Spawn the blood droplet particles
644+ for ( i = 0 ; i < particleCount ; i ++ ) {
645+ le = CG_AllocLocalEntity ();
646+ re = & le -> refEntity ;
647+
648+ le -> leFlags = LEF_PUFF_DONT_SCALE ;
649+ le -> leType = LE_BLOOD_PARTICLE ;
650+ le -> startTime = cg .time ;
651+ le -> endTime = cg .time + 800 + random () * 400 ;
652+ le -> lifeRate = 1.0f / ( le -> endTime - le -> startTime );
653+
654+ // Directional spray along bullet path
655+ // 80% exit wound (away from shooter), 20% entry (random horizontal splash)
656+ qboolean isExitWound = ( random () < 0.8f );
657+ if ( isExitWound ) {
658+ speed = BLOOD_PARTICLE_SPEED_EXIT * (0.4f + random () * 0.6f );
659+ forwardBias = 0.8f + random () * 0.4f ;
660+ spread = (random () - 0.5f ) * 0.4f ; // Tight perpendicular spread
661+
662+ velocity [0 ] = baseDir [0 ] * speed * forwardBias
663+ + perpA [0 ] * speed * spread
664+ + perpB [0 ] * speed * (random () - 0.5f ) * 0.6f ;
665+ velocity [1 ] = baseDir [1 ] * speed * forwardBias
666+ + perpA [1 ] * speed * spread
667+ + perpB [1 ] * speed * (random () - 0.5f ) * 0.6f ;
668+ velocity [2 ] = baseDir [2 ] * speed * forwardBias
669+ + perpA [2 ] * speed * spread
670+ + perpB [2 ] * speed * (random () - 0.5f ) * 0.6f
671+ + speed * 0.2f ; // Slight upward bias
672+ } else {
673+ // Entry wound - spray in the plane perpendicular to projectile direction
674+ float angle = random () * M_PI * 2 ;
675+ float perpSpeed ;
676+ speed = BLOOD_PARTICLE_SPEED_ENTRY * (0.4f + random () * 0.6f );
677+ perpSpeed = speed * (0.8f + random () * 0.4f );
678+
679+ velocity [0 ] = perpA [0 ] * cos ( angle ) * perpSpeed + perpB [0 ] * sin ( angle ) * perpSpeed ;
680+ velocity [1 ] = perpA [1 ] * cos ( angle ) * perpSpeed + perpB [1 ] * sin ( angle ) * perpSpeed ;
681+ velocity [2 ] = perpA [2 ] * cos ( angle ) * perpSpeed + perpB [2 ] * sin ( angle ) * perpSpeed ;
682+ }
683+
684+ le -> pos .trType = TR_GRAVITY ;
685+ le -> pos .trTime = cg .time ;
686+ VectorCopy ( origin , le -> pos .trBase );
687+ VectorCopy ( velocity , le -> pos .trDelta );
688+
689+ // Use bloodTrail shader for particles
690+ re -> reType = RT_SPRITE ;
691+ re -> rotation = rand () % 360 ;
692+ re -> radius = 3 + random () * 5 ; // Varied sizes 3-8
693+ re -> customShader = cgs .media .bloodTrailShader ;
694+ re -> shaderTime = cg .time / 1000.0f ;
695+
696+ VectorCopy ( le -> pos .trBase , re -> origin );
697+
698+ // Set color (shader handles actual blood color)
699+ le -> color [0 ] = 1.0f ;
700+ le -> color [1 ] = 1.0f ;
701+ le -> color [2 ] = 1.0f ;
702+ le -> color [3 ] = 1.0f ;
703+
704+ re -> shaderRGBA [0 ] = 0xff ;
705+ re -> shaderRGBA [1 ] = 0xff ;
706+ re -> shaderRGBA [2 ] = 0xff ;
707+ re -> shaderRGBA [3 ] = 0xff ;
708+
709+ le -> radius = re -> radius ;
710+
711+ // don't show player's own blood in view
712+ if ( isPlayer ) {
713+ re -> renderfx |= RF_THIRD_PERSON ;
714+ }
715+ }
716+
717+ // Add a blood mist at entry or exit wound
718+ {
719+ float puffRadius = 2 + random () * 3 ; // 2-5 units
720+ int puffDuration = 300 + random () * 200 ; // 300-500ms
721+
722+ le = CG_SmokePuff ( origin , vec3_origin ,
723+ puffRadius ,
724+ 1 , 1 , 1 , 1 ,
725+ puffDuration ,
726+ cg .time , 0 , 0 ,
727+ cgs .media .bloodTrailShader );
728+ le -> leType = LE_FALL_SCALE_FADE ;
729+ le -> pos .trDelta [2 ] = 4 ; // Slow fall
730+
731+ if ( isPlayer ) {
732+ le -> refEntity .renderfx |= RF_THIRD_PERSON ;
733+ }
567734 }
568735}
569736
0 commit comments