Skip to content

An increased chance of same bullet hitting same target twice? (maybe "shotgunning") on sv_fps > 20 #453

@gerardommilan

Description

@gerardommilan

Hi there.

rtcwPro/src/game/g_weapon.c

Lines 1961 to 2171 in 8fe7081

void Bullet_Fire_Extended(gentity_t* source, gentity_t* attacker, vec3_t start, vec3_t end, float spread, int damage) {
trace_t tr;
vec3_t initial_start;
gentity_t* tent;
gentity_t* traceEnt;
gentity_t* head = NULL;
// since start is overwritten halfway through this function.
VectorCopy(start, initial_start);
damage *= s_quadFactor;
// (SA) changed so player could shoot his own dynamite.
// (SA) whoops, but that broke bullets going through explosives...
trap_Trace(&tr, start, NULL, NULL, end, source->s.number, MASK_SHOT);
// trap_Trace (&tr, start, NULL, NULL, end, ENTITYNUM_NONE, MASK_SHOT);
traceEnt = &g_entities[tr.entityNum];
if (traceEnt->is_head) {
head = traceEnt;
traceEnt = head->parent;
}
if (LogAccuracyShot(traceEnt, source) && g_gamestate.integer == GS_PLAYING)
{
source->client->pers.life_acc_shots++;
source->client->sess.acc_shots++;
}
// DHM - Nerve :: only in single player
if (g_gametype.integer == GT_SINGLE_PLAYER)
AICast_ProcessBullet(attacker, start, tr.endpos);
// bullet debugging using Q3A's railtrail
if (g_debugBullets.integer & 1) {
tent = G_TempEntity(start, EV_RAILTRAIL);
VectorCopy(tr.endpos, tent->s.origin2);
tent->s.otherEntityNum2 = attacker->s.number;
}
//----(SA) commented out
// if ( tr.surfaceFlags & SURF_NOIMPACT ) {
// if (attacker->s.weapon == WP_MAUSER && attacker->r.svFlags & SVF_CASTAI)
// SniperSoundEFX (tr.endpos);
//
// return;
// }
//----(SA) end
RubbleFlagCheck(attacker, tr);
EmitterCheck(traceEnt, attacker, &tr);
// snap the endpos to integers, but nudged towards the line
SnapVectorTowards(tr.endpos, start);
//----(SA) commented out
// if (attacker->s.weapon == WP_MAUSER && attacker->r.svFlags & SVF_CASTAI)
// {
// SniperSoundEFX (tr.endpos);
// }
//----(SA) end
// send bullet impact
if (traceEnt->takedamage && (traceEnt->client) && !(traceEnt->flags & FL_DEFENSE_GUARD)) {
tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_FLESH);
tent->s.eventParm = traceEnt->s.number;
if (LogAccuracyHit(traceEnt, attacker) && g_gamestate.integer == GS_PLAYING) {
attacker->client->ps.persistant[PERS_ACCURACY_HITS]++;
// L0 - Stats
attacker->client->pers.life_acc_hits++;
attacker->client->sess.acc_hits++;
}
//----(SA) added
if (g_debugBullets.integer >= 2) { // show hit player bb
gentity_t* bboxEnt;
vec3_t b1, b2;
VectorCopy(traceEnt->r.currentOrigin, b1);
VectorCopy(traceEnt->r.currentOrigin, b2);
VectorAdd(b1, traceEnt->r.mins, b1);
VectorAdd(b2, traceEnt->r.maxs, b2);
bboxEnt = G_TempEntity(b1, EV_RAILTRAIL);
VectorCopy(b2, bboxEnt->s.origin2);
bboxEnt->s.dmgFlags = 1; // ("type")
bboxEnt->s.otherEntityNum2 = attacker->s.number;
}
//----(SA) end
}
else if (traceEnt->takedamage && traceEnt->s.eType == ET_BAT) {
tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_FLESH);
tent->s.eventParm = traceEnt->s.number;
}
else {
// Ridah, bullet impact should reflect off surface
vec3_t reflect;
float dot;
if (g_debugBullets.integer >= 4) { // show hit thing bb
gentity_t* bboxEnt;
vec3_t b1, b2;
VectorCopy(traceEnt->r.currentOrigin, b1);
VectorCopy(traceEnt->r.currentOrigin, b2);
VectorAdd(b1, traceEnt->r.mins, b1);
VectorAdd(b2, traceEnt->r.maxs, b2);
bboxEnt = G_TempEntity(b1, EV_RAILTRAIL);
VectorCopy(b2, bboxEnt->s.origin2);
bboxEnt->s.dmgFlags = 1; // ("type")
bboxEnt->s.otherEntityNum2 = attacker->s.number;
}
if (traceEnt->flags & FL_DEFENSE_GUARD) {
// reflect off sheild
VectorSubtract(tr.endpos, traceEnt->r.currentOrigin, reflect);
VectorNormalize(reflect);
VectorMA(traceEnt->r.currentOrigin, 15, reflect, reflect);
tent = G_TempEntity(reflect, EV_BULLET_HIT_WALL);
}
else {
tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_WALL);
}
dot = DotProduct(forward, tr.plane.normal);
VectorMA(forward, -2 * dot, tr.plane.normal, reflect);
VectorNormalize(reflect);
tent->s.eventParm = DirToByte(reflect);
if (traceEnt->flags & FL_DEFENSE_GUARD) {
tent->s.otherEntityNum2 = traceEnt->s.number; // force sparks
}
else {
tent->s.otherEntityNum2 = ENTITYNUM_NONE;
}
// done.
}
tent->s.otherEntityNum = attacker->s.number;
if (traceEnt->takedamage) {
qboolean reflectBool = qfalse;
vec3_t trDir;
if (traceEnt->flags & FL_DEFENSE_GUARD) {
// if we are facing the direction the bullet came from, then reflect it
AngleVectors(traceEnt->s.apos.trBase, trDir, NULL, NULL);
if (DotProduct(forward, trDir) < 0.6) {
reflectBool = qtrue;
}
}
if (reflectBool) {
vec3_t reflect_end;
// reflect this bullet
G_AddEvent(traceEnt, EV_GENERAL_SOUND, level.bulletRicochetSound);
CalcMuzzlePoints(traceEnt, traceEnt->s.weapon);
//----(SA) modified to use extended version so attacker would pass through
// Bullet_Fire( traceEnt, 1000, damage );
Bullet_Endpos(traceEnt, spread, &reflect_end);
Bullet_Fire_Extended(traceEnt, attacker, muzzleTrace, reflect_end, spread, damage);
//----(SA) end
}
else {
vec3_t backwards;
// Ridah, don't hurt team-mates
// DHM - Nerve :: Only in single player
if (attacker->client && traceEnt->client && g_gametype.integer == GT_SINGLE_PLAYER && (traceEnt->r.svFlags & SVF_CASTAI) && (attacker->r.svFlags & SVF_CASTAI) && AICast_SameTeam(AICast_GetCastState(attacker->s.number), traceEnt->s.number)) {
// AI's don't hurt members of their own team
return;
}
// done.
VectorSubtract(initial_start, tr.endpos, backwards);
VectorNormalize(backwards);
VectorMA(tr.endpos, 1, backwards, initial_start);
traceEnt->headshot = IsHeadshot(traceEnt, &head, initial_start, forward, ammoTable[attacker->s.weapon].mod);
if (head && g_debugBullets.integer >= 3) // show hit player head bb
{
gentity_t* bboxEnt;
vec3_t b1, b2;
VectorCopy(head->r.currentOrigin, b1);
VectorCopy(head->r.currentOrigin, b2);
VectorAdd(b1, head->r.mins, b1);
VectorAdd(b2, head->r.maxs, b2);
bboxEnt = G_TempEntity(b1, EV_RAILTRAIL);
VectorCopy(b2, bboxEnt->s.origin2);
bboxEnt->s.dmgFlags = 1;
bboxEnt->s.otherEntityNum2 = attacker->s.number;
}
G_Damage(traceEnt, attacker, attacker, forward, tr.endpos, damage, 0, ammoTable[attacker->s.weapon].mod);
traceEnt->headshot = qfalse;
// allow bullets to "pass through" func_explosives if they break by taking another simultanious shot
if (Q_stricmp(traceEnt->classname, "func_explosive") == 0) {
if (traceEnt->health <= damage) {
// start new bullet at position this hit the bmodel and continue to the end position (ignoring shot-through bmodel in next trace)
// spread = 0 as this is an extension of an already spread shot
Bullet_Fire_Extended(traceEnt, attacker, tr.endpos, end, 0, damage);
}
}
}
}
}

I've been trying to look into the shotgunning effect as is something reported by many, noticed more in a close range/slow movement fights.

Looking at bullet fire extended I don't think there is a check to detect if a bullet already hit X entity, probably makes no sense as the times moves forward yes, I know but involving lag compensation, bullet traces calc, overlapping hitboxes, applied spread, laggy moments, combined with sv_40fps there is a chance that bullet hitscan detection, which might not be perfect, a single bullet hits a target twice? Hard to explain as the time moves but somehow can see it happining, everything is possible.

Any chance to add into debug bullet to see if this attacker/bullet has hit this entity already and logs warning? Or by frame.

Entity has been hit by X attacker this frame | Might mess with explosions damage.
Entity has been hit by X bullet from this X attacker this frame | Might work better but probably harder to track.

If for any reason it happens would explain "shotgunning" and can be "easily" corrected (at that point the same check would just before G_Damage)

Correct me is this has been already issued somewhere else or makes no sense at all.

I'll be glad to chat about it more or attempt to help

-Elver

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions