/* * G_PointContents * returns the CONTENTS_* value from the world at the given point. * Quake 2 extends this to also check entities, to allow moving liquids */ static int GClip_PointContents( vec3_t p, int timeDelta ) { c4clipedict_t *clipEnt; int touch[MAX_EDICTS]; int i, num; int contents, c2; struct cmodel_s *cmodel; float *angles; // get base contents from world contents = trap_CM_TransformedPointContents( p, NULL, NULL, NULL ); // or in contents from all the other entities num = GClip_AreaEdicts( p, p, touch, MAX_EDICTS, AREA_SOLID, timeDelta ); for( i = 0; i < num; i++ ) { clipEnt = GClip_GetClipEdictForDeltaTime( touch[i], timeDelta ); // might intersect, so do an exact clip cmodel = GClip_CollisionModelForEntity( &clipEnt->s, &clipEnt->r ); if( !ISBRUSHMODEL( clipEnt->s.modelindex ) ) angles = vec3_origin; // boxes don't rotate else angles = clipEnt->s.angles; c2 = trap_CM_TransformedPointContents( p, cmodel, clipEnt->s.origin, clipEnt->s.angles ); contents |= c2; } return contents; }
/* * GClip_ClipMoveToEntities */ /*static*/ void GClip_ClipMoveToEntities( moveclip_t *clip, int timeDelta ) { int i, num; c4clipedict_t *touch; int touchlist[MAX_EDICTS]; trace_t trace; struct cmodel_s *cmodel; float *angles; num = GClip_AreaEdicts( clip->boxmins, clip->boxmaxs, touchlist, MAX_EDICTS, AREA_SOLID, timeDelta ); // be careful, it is possible to have an entity in this // list removed before we get to it (killtriggered) for( i = 0; i < num; i++ ) { touch = GClip_GetClipEdictForDeltaTime( touchlist[i], timeDelta ); if( clip->passent >= 0 ) { // when they are offseted in time, they can be a different pointer but be the same entity if( touch->s.number == clip->passent ) continue; if( touch->r.owner && ( touch->r.owner->s.number == clip->passent ) ) continue; if( game.edicts[clip->passent].r.owner && ( game.edicts[clip->passent].r.owner->s.number == touch->s.number ) ) continue; // wsw : jal : never clipmove against SVF_PROJECTILE entities if( touch->r.svflags & SVF_PROJECTILE ) continue; } if( ( touch->r.svflags & SVF_CORPSE ) && !( clip->contentmask & CONTENTS_CORPSE ) ) continue; // might intersect, so do an exact clip cmodel = GClip_CollisionModelForEntity( &touch->s, &touch->r ); if( ISBRUSHMODEL( touch->s.modelindex ) ) angles = touch->s.angles; else angles = vec3_origin; // boxes don't rotate trap_CM_TransformedBoxTrace( &trace, clip->start, clip->end, clip->mins, clip->maxs, cmodel, clip->contentmask, touch->s.origin, angles ); if( trace.allsolid || trace.fraction < clip->trace->fraction ) { trace.ent = touch->s.number; *( clip->trace ) = trace; } else if( trace.startsolid ) clip->trace->startsolid = qtrue; if( clip->trace->allsolid ) return; } }
/* * W_Touch_Grenade */ static void W_Touch_Grenade( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags ) { int hitType; vec3_t dir; if( surfFlags & SURF_NOIMPACT ) { G_FreeEdict( ent ); return; } hitType = G_Projectile_HitStyle( ent, other ); if( hitType == PROJECTILE_TOUCH_NOT ) return; // don't explode on doors and plats that take damage if( !other->takedamage || ISBRUSHMODEL( other->s.modelindex ) ) { G_AddEvent( ent, EV_GRENADE_BOUNCE, ( ent->s.effects & EF_STRONG_WEAPON ) ? FIRE_MODE_STRONG : FIRE_MODE_WEAK, true ); return; } if( other->takedamage ) { int directHitDamage = ent->projectileInfo.maxDamage; VectorNormalize2( ent->velocity, dir ); if( hitType == PROJECTILE_TOUCH_DIRECTSPLASH ) // use hybrid direction from splash and projectile { G_SplashFrac4D( ENTNUM( other ), ent->s.origin, ent->projectileInfo.radius, dir, NULL, NULL, ent->timeDelta ); } else { VectorNormalize2( ent->velocity, dir ); // no direct hit bonuses for grenades /* if( hitType == PROJECTILE_TOUCH_DIRECTAIRHIT ) directHitDamage += DIRECTAIRTHIT_DAMAGE_BONUS; else if( hitType == PROJECTILE_TOUCH_DIRECTHIT ) directHitDamage += DIRECTHIT_DAMAGE_BONUS; */ } G_Damage( other, ent, ent->r.owner, dir, ent->velocity, ent->s.origin, directHitDamage, ent->projectileInfo.maxKnockback, ent->projectileInfo.stun, 0, ent->style ); } ent->enemy = other; W_Grenade_ExplodeDir( ent, plane ? plane->normal : NULL ); }
/* * W_Touch_GunbladeBlast */ static void W_Touch_GunbladeBlast( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags ) { vec3_t dir; int hitType; if( surfFlags & SURF_NOIMPACT ) { G_FreeEdict( ent ); return; } hitType = G_Projectile_HitStyle( ent, other ); if( hitType == PROJECTILE_TOUCH_NOT ) return; if( other->takedamage ) { VectorNormalize2( ent->velocity, dir ); if( hitType == PROJECTILE_TOUCH_DIRECTSPLASH ) // use hybrid direction from splash and projectile { G_SplashFrac4D( ENTNUM( other ), ent->s.origin, ent->projectileInfo.radius, dir, NULL, NULL, ent->timeDelta ); } else { VectorNormalize2( ent->velocity, dir ); } G_Damage( other, ent, ent->r.owner, dir, ent->velocity, ent->s.origin, ent->projectileInfo.maxDamage, ent->projectileInfo.maxKnockback, ent->projectileInfo.stun, 0, ent->style ); } G_RadiusDamage( ent, ent->r.owner, plane, other, MOD_GUNBLADE_S ); // add explosion event if( ( !other->takedamage || ISBRUSHMODEL( other->s.modelindex ) ) ) { edict_t *event; event = G_SpawnEvent( EV_GUNBLADEBLAST_IMPACT, DirToByte( plane ? plane->normal : NULL ), ent->s.origin ); event->s.weapon = ( ( ent->projectileInfo.radius * 1/8 ) > 127 ) ? 127 : ( ent->projectileInfo.radius * 1/8 ); event->s.skinnum = ( ( ent->projectileInfo.maxKnockback * 1/8 ) > 255 ) ? 255 : ( ent->projectileInfo.maxKnockback * 1/8 ); } // free at next frame G_FreeEdict( ent ); }
/* * GClip_CollisionModelForEntity * * Returns a collision model that can be used for testing or clipping an * object of mins/maxs size. */ static struct cmodel_s *GClip_CollisionModelForEntity( entity_state_t *s, entity_shared_t *r ) { struct cmodel_s *model; if( ISBRUSHMODEL( s->modelindex ) ) { // explicit hulls in the BSP model model = trap_CM_InlineModel( s->modelindex ); if( !model ) G_Error( "MOVETYPE_PUSH with a non bsp model" ); return model; } // create a temp hull from bounding box sizes if( s->type == ET_PLAYER || s->type == ET_CORPSE ) return trap_CM_OctagonModelForBBox( r->mins, r->maxs ); else return trap_CM_ModelForBBox( r->mins, r->maxs ); }
/* * GClip_EntityContact */ static qboolean GClip_EntityContact( vec3_t mins, vec3_t maxs, edict_t *ent ) { trace_t tr; struct cmodel_s *model; if( !mins ) mins = vec3_origin; if( !maxs ) maxs = vec3_origin; if( ISBRUSHMODEL( ent->s.modelindex ) ) { model = trap_CM_InlineModel( ent->s.modelindex ); if( !model ) G_Error( "MOVETYPE_PUSH with a non bsp model" ); trap_CM_TransformedBoxTrace( &tr, vec3_origin, vec3_origin, mins, maxs, model, MASK_ALL, ent->s.origin, ent->s.angles ); return tr.startsolid || tr.allsolid; } return ( BoundsIntersect( mins, maxs, ent->r.absmin, ent->r.absmax ) ); }
void GClip_LinkEntity( edict_t *ent ) { areanode_t *node; int leafs[MAX_TOTAL_ENT_LEAFS]; int clusters[MAX_TOTAL_ENT_LEAFS]; int num_leafs; int i, j, k; int area; int topnode; if( ent->r.area.prev ) GClip_UnlinkEntity( ent ); // unlink from old position if( ent == game.edicts ) return; // don't add the world if( !ent->r.inuse ) return; // set the size VectorSubtract( ent->r.maxs, ent->r.mins, ent->r.size ); if( ent->r.solid == SOLID_NOT || ( ent->r.svflags & SVF_PROJECTILE ) ) { ent->s.solid = 0; } else if( ISBRUSHMODEL( ent->s.modelindex ) ) { // the only predicted SOLID_TRIGGER entity is ET_PUSH_TRIGGER if( ent->r.solid != SOLID_TRIGGER || ent->s.type == ET_PUSH_TRIGGER ) ent->s.solid = SOLID_BMODEL; else ent->s.solid = 0; } else // encode the size into the entity_state for client prediction { if( ent->r.solid == SOLID_TRIGGER ) { ent->s.solid = 0; } else { // assume that x/y are equal and symetric i = ent->r.maxs[0]/8; clamp( i, 1, 31 ); // z is not symetric j = ( -ent->r.mins[2] )/8; clamp( j, 1, 31 ); // and z maxs can be negative... k = ( ent->r.maxs[2]+32 )/8; clamp( k, 1, 63 ); ent->s.solid = ( k<<10 ) | ( j<<5 ) | i; } } // set the abs box if( ISBRUSHMODEL( ent->s.modelindex ) && ( ent->s.angles[0] || ent->s.angles[1] || ent->s.angles[2] ) ) { // expand for rotation float radius; radius = RadiusFromBounds( ent->r.mins, ent->r.maxs ); for( i = 0; i < 3; i++ ) { ent->r.absmin[i] = ent->s.origin[i] - radius; ent->r.absmax[i] = ent->s.origin[i] + radius; } } else // axis aligned { VectorAdd( ent->s.origin, ent->r.mins, ent->r.absmin ); VectorAdd( ent->s.origin, ent->r.maxs, ent->r.absmax ); } // because movement is clipped an epsilon away from an actual edge, // we must fully check even when bounding boxes don't quite touch ent->r.absmin[0] -= 1; ent->r.absmin[1] -= 1; ent->r.absmin[2] -= 1; ent->r.absmax[0] += 1; ent->r.absmax[1] += 1; ent->r.absmax[2] += 1; // link to PVS leafs ent->r.num_clusters = 0; ent->r.areanum = ent->r.areanum2 = -1; // get all leafs, including solids num_leafs = trap_CM_BoxLeafnums( ent->r.absmin, ent->r.absmax, leafs, MAX_TOTAL_ENT_LEAFS, &topnode ); // set areas for( i = 0; i < num_leafs; i++ ) { clusters[i] = trap_CM_LeafCluster( leafs[i] ); area = trap_CM_LeafArea( leafs[i] ); if( area > -1 ) { // doors may legally straggle two areas, // but nothing should ever need more than that if( ent->r.areanum > -1 && ent->r.areanum != area ) { if( ent->r.areanum2 > -1 && ent->r.areanum2 != area ) { if( developer->integer ) G_Printf( "Object %s touching 3 areas at %f %f %f\n", ( ent->classname ? ent->classname : "" ), ent->r.absmin[0], ent->r.absmin[1], ent->r.absmin[2] ); } ent->r.areanum2 = area; } else ent->r.areanum = area; } } if( num_leafs >= MAX_TOTAL_ENT_LEAFS ) { // assume we missed some leafs, and mark by headnode ent->r.num_clusters = -1; ent->r.headnode = topnode; } else { ent->r.num_clusters = 0; for( i = 0; i < num_leafs; i++ ) { if( clusters[i] == -1 ) continue; // not a visible leaf for( j = 0; j < i; j++ ) if( clusters[j] == clusters[i] ) break; if( j == i ) { if( ent->r.num_clusters == MAX_ENT_CLUSTERS ) { // assume we missed some leafs, and mark by headnode ent->r.num_clusters = -1; ent->r.headnode = topnode; break; } ent->r.clusternums[ent->r.num_clusters++] = clusters[i]; } } } // if first time, make sure old_origin is valid if( !ent->r.linkcount && !( ent->r.svflags & SVF_TRANSMITORIGIN2 ) ) { VectorCopy( ent->s.origin, ent->s.old_origin ); ent->olds = ent->s; } ent->r.linkcount++; ent->linked = qtrue; if( ent->r.solid == SOLID_NOT ) return; // find the first node that the ent's box crosses node = sv_areanodes; while( 1 ) { if( node->axis == -1 ) break; if( ent->r.absmin[node->axis] > node->dist ) node = node->children[0]; else if( ent->r.absmax[node->axis] < node->dist ) node = node->children[1]; else break; // crosses the node } // link it in if( ent->r.solid == SOLID_TRIGGER ) GClip_InsertLinkBefore( &ent->r.area, &node->trigger_edicts, NUM_FOR_EDICT( ent ) ); else GClip_InsertLinkBefore( &ent->r.area, &node->solid_edicts, NUM_FOR_EDICT( ent ) ); }
/* * * - We will consider direct impacts as splash when the player is on the ground and the hit very close to the ground */ int G_Projectile_HitStyle( edict_t *projectile, edict_t *target ) { trace_t trace; vec3_t end; qboolean atGround = qfalse; edict_t *attacker; #define AIRHIT_MINHEIGHT 64 // don't hurt owner for the first second if( target == projectile->r.owner && target != world ) { if( !g_projectile_touch_owner->integer || ( g_projectile_touch_owner->integer && projectile->timeStamp + 1000 > level.time ) ) return PROJECTILE_TOUCH_NOT; } if( !target->takedamage || ISBRUSHMODEL( target->s.modelindex ) ) return PROJECTILE_TOUCH_DIRECTHIT; if( target->waterlevel > 1 ) return PROJECTILE_TOUCH_DIRECTHIT; // water hits are direct but don't count for awards attacker = ( projectile->r.owner && projectile->r.owner->r.client ) ? projectile->r.owner : NULL; // see if the target is at ground or a less than a step of height if( target->groundentity ) atGround = qtrue; else { VectorCopy( target->s.origin, end ); end[2] -= STEPSIZE; G_Trace4D( &trace, target->s.origin, target->r.mins, target->r.maxs, end, target, MASK_DEADSOLID, 0 ); if( ( trace.ent != -1 || trace.startsolid ) && ISWALKABLEPLANE( &trace.plane ) ) atGround = qtrue; } if( atGround ) { // when the player is at ground we will consider a direct hit only when // the hit is 16 units above the feet if( projectile->s.origin[2] <= 16 + target->s.origin[2] + target->r.mins[2] ) return PROJECTILE_TOUCH_DIRECTSPLASH; } else { // it's direct hit, but let's see if it's airhit VectorCopy( target->s.origin, end ); end[2] -= AIRHIT_MINHEIGHT; G_Trace4D( &trace, target->s.origin, target->r.mins, target->r.maxs, end, target, MASK_DEADSOLID, 0 ); if( ( trace.ent != -1 || trace.startsolid ) && ISWALKABLEPLANE( &trace.plane ) ) { // add directhit and airhit to awards counter if( attacker && !GS_IsTeamDamage( &attacker->s, &target->s ) && G_ModToAmmo( projectile->style ) != AMMO_NONE ) { projectile->r.owner->r.client->level.stats.accuracy_hits_direct[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; teamlist[projectile->r.owner->s.team].stats.accuracy_hits_direct[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; projectile->r.owner->r.client->level.stats.accuracy_hits_air[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; teamlist[projectile->r.owner->s.team].stats.accuracy_hits_air[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; } return PROJECTILE_TOUCH_DIRECTAIRHIT; } } // add directhit to awards counter if( attacker && !GS_IsTeamDamage( &attacker->s, &target->s ) && G_ModToAmmo( projectile->style ) != AMMO_NONE ) { projectile->r.owner->r.client->level.stats.accuracy_hits_direct[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; teamlist[projectile->r.owner->s.team].stats.accuracy_hits_direct[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; } return PROJECTILE_TOUCH_DIRECTHIT; #undef AIRHIT_MINHEIGHT }
/* * W_Fire_Instagun_Strong */ void W_Fire_Instagun( edict_t *self, vec3_t start, vec3_t angles, float damage, int knockback, int stun, int radius, int range, int mod, int timeDelta ) { vec3_t from, end, dir; trace_t tr; edict_t *ignore, *event; int mask; qboolean missed = qtrue; int dmgflags = 0; if( GS_Instagib() ) damage = 9999; AngleVectors( angles, dir, NULL, NULL ); VectorMA( start, range, dir, end ); VectorCopy( start, from ); ignore = self; mask = MASK_SHOT; if( GS_RaceGametype() ) mask = MASK_SOLID; tr.ent = -1; while( ignore ) { G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta ); VectorCopy( tr.endpos, from ); ignore = NULL; if( tr.ent == -1 ) break; // some entity was touched if( tr.ent == world->s.number || game.edicts[tr.ent].movetype == MOVETYPE_NONE || game.edicts[tr.ent].movetype == MOVETYPE_PUSH ) { if( g_instajump->integer && self && self->r.client ) { // create a temporary inflictor entity edict_t *inflictor; inflictor = G_Spawn(); inflictor->s.solid = SOLID_NOT; inflictor->timeDelta = 0; VectorCopy( tr.endpos, inflictor->s.origin ); inflictor->s.ownerNum = ENTNUM( self ); inflictor->projectileInfo.maxDamage = 0; inflictor->projectileInfo.minDamage = 0; inflictor->projectileInfo.maxKnockback = knockback; inflictor->projectileInfo.minKnockback = 1; inflictor->projectileInfo.stun = 0; inflictor->projectileInfo.radius = radius; G_RadiusDamage( inflictor, self, &tr.plane, NULL, mod ); G_FreeEdict( inflictor ); } break; } // allow trail to go through SOLID_BBOX entities (players, gibs, etc) if( !ISBRUSHMODEL( game.edicts[tr.ent].s.modelindex ) ) ignore = &game.edicts[tr.ent]; if( ( &game.edicts[tr.ent] != self ) && ( game.edicts[tr.ent].takedamage ) ) { G_Damage( &game.edicts[tr.ent], self, self, dir, dir, tr.endpos, damage, knockback, stun, dmgflags, mod ); // spawn a impact event on each damaged ent event = G_SpawnEvent( EV_INSTA_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos ); event->s.firemode = FIRE_MODE_STRONG; if( game.edicts[tr.ent].r.client ) missed = qfalse; } } if( missed && self->r.client ) G_AwardPlayerMissedElectrobolt( self, mod ); // send the weapon fire effect event = G_SpawnEvent( EV_INSTATRAIL, ENTNUM( self ), start ); event->r.svflags = SVF_TRANSMITORIGIN2; VectorScale( dir, 1024, event->s.origin2 ); }
void W_Fire_Electrobolt_FullInstant( edict_t *self, vec3_t start, vec3_t angles, float maxdamage, float mindamage, int maxknockback, int minknockback, int stun, int range, int minDamageRange, int mod, int timeDelta ) { vec3_t from, end, dir; trace_t tr; edict_t *ignore, *event, *hit, *damaged; int mask; qboolean missed = qtrue; int dmgflags = 0; #define FULL_DAMAGE_RANGE g_projectile_prestep->value if( GS_Instagib() ) maxdamage = mindamage = 9999; AngleVectors( angles, dir, NULL, NULL ); VectorMA( start, range, dir, end ); VectorCopy( start, from ); ignore = self; hit = damaged = NULL; mask = MASK_SHOT; if( GS_RaceGametype() ) mask = MASK_SOLID; clamp_high( mindamage, maxdamage ); clamp_high( minknockback, maxknockback ); clamp_high( minDamageRange, range ); if( minDamageRange <= FULL_DAMAGE_RANGE ) minDamageRange = FULL_DAMAGE_RANGE + 1; if( range <= FULL_DAMAGE_RANGE + 1 ) range = FULL_DAMAGE_RANGE + 1; tr.ent = -1; while( ignore ) { G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta ); VectorCopy( tr.endpos, from ); ignore = NULL; if( tr.ent == -1 ) break; // some entity was touched hit = &game.edicts[tr.ent]; if( hit == world ) // stop dead if hit the world break; if( hit->movetype == MOVETYPE_NONE || hit->movetype == MOVETYPE_PUSH ) break; // allow trail to go through BBOX entities (players, gibs, etc) if( !ISBRUSHMODEL( hit->s.modelindex ) ) ignore = hit; if( ( hit != self ) && ( hit->takedamage ) ) { float frac, damage, knockback, dist; dist = DistanceFast( tr.endpos, start ); if( dist <= FULL_DAMAGE_RANGE ) frac = 0.0f; else { frac = ( dist - FULL_DAMAGE_RANGE ) / (float)( minDamageRange - FULL_DAMAGE_RANGE ); clamp( frac, 0.0f, 1.0f ); } damage = maxdamage - ( ( maxdamage - mindamage ) * frac ); knockback = maxknockback - ( ( maxknockback - minknockback ) * frac ); //G_Printf( "mindamagerange %i frac %.1f damage %i\n", minDamageRange, 1.0f - frac, (int)damage ); G_Damage( hit, self, self, dir, dir, tr.endpos, damage, knockback, stun, dmgflags, mod ); // spawn a impact event on each damaged ent event = G_SpawnEvent( EV_BOLT_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos ); event->s.firemode = FIRE_MODE_STRONG; if( hit->r.client ) missed = qfalse; damaged = hit; } } if( missed && self->r.client ) G_AwardPlayerMissedElectrobolt( self, mod ); // send the weapon fire effect event = G_SpawnEvent( EV_ELECTROTRAIL, ENTNUM( self ), start ); event->r.svflags = SVF_TRANSMITORIGIN2; VectorScale( dir, 1024, event->s.origin2 ); event->s.firemode = FIRE_MODE_STRONG; #undef FULL_DAMAGE_RANGE }
/* * W_Fire_Electrobolt_Combined */ void W_Fire_Electrobolt_Combined( edict_t *self, vec3_t start, vec3_t angles, float maxdamage, float mindamage, float maxknockback, float minknockback, int stun, int range, int mod, int timeDelta ) { vec3_t from, end, dir; trace_t tr; edict_t *ignore, *event, *hit, *damaged; int mask; qboolean missed = qtrue; int dmgflags = 0; int fireMode; #ifdef ELECTROBOLT_TEST fireMode = FIRE_MODE_WEAK; #else fireMode = FIRE_MODE_STRONG; #endif if( GS_Instagib() ) maxdamage = mindamage = 9999; AngleVectors( angles, dir, NULL, NULL ); VectorMA( start, range, dir, end ); VectorCopy( start, from ); ignore = self; hit = damaged = NULL; mask = MASK_SHOT; if( GS_RaceGametype() ) mask = MASK_SOLID; clamp_high( mindamage, maxdamage ); clamp_high( minknockback, maxknockback ); tr.ent = -1; while( ignore ) { G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta ); VectorCopy( tr.endpos, from ); ignore = NULL; if( tr.ent == -1 ) break; // some entity was touched hit = &game.edicts[tr.ent]; if( hit == world ) // stop dead if hit the world break; if( hit->movetype == MOVETYPE_NONE || hit->movetype == MOVETYPE_PUSH ) break; // allow trail to go through BBOX entities (players, gibs, etc) if( !ISBRUSHMODEL( hit->s.modelindex ) ) ignore = hit; if( ( hit != self ) && ( hit->takedamage ) ) { float frac, damage, knockback; frac = DistanceFast( tr.endpos, start ) / (float)range; clamp( frac, 0.0f, 1.0f ); damage = maxdamage - ( ( maxdamage - mindamage ) * frac ); knockback = maxknockback - ( ( maxknockback - minknockback ) * frac ); G_Damage( hit, self, self, dir, dir, tr.endpos, damage, knockback, stun, dmgflags, mod ); // spawn a impact event on each damaged ent event = G_SpawnEvent( EV_BOLT_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos ); event->s.firemode = fireMode; if( hit->r.client ) missed = qfalse; damaged = hit; } } if( missed && self->r.client ) G_AwardPlayerMissedElectrobolt( self, mod ); // send the weapon fire effect event = G_SpawnEvent( EV_ELECTROTRAIL, ENTNUM( self ), start ); event->r.svflags = SVF_TRANSMITORIGIN2; VectorScale( dir, 1024, event->s.origin2 ); event->s.firemode = fireMode; if( !GS_Instagib() && tr.ent == -1 ) // didn't touch anything, not even a wall { edict_t *bolt; gs_weapon_definition_t *weapondef = GS_GetWeaponDef( self->s.weapon ); // fire a weak EB from the end position bolt = W_Fire_Electrobolt_Weak( self, end, angles, weapondef->firedef_weak.speed, mindamage, minknockback, minknockback, stun, weapondef->firedef_weak.timeout, mod, timeDelta ); bolt->enemy = damaged; } }