//----------------------------------------------------------------------------- // // $Id$ // //----------------------------------------------------------------------------- // // $Log$ // Revision 1.93 2005/02/15 16:33:39 makro // Tons of updates (entity tree attachment system, UI vectors) // // Revision 1.92 2003/09/19 21:25:10 makro // Flares (again!). Doors that open away from players. // // Revision 1.91 2003/09/01 15:09:49 jbravo // Cleanups, crashbug fix and version bumped to 3.2 // // Revision 1.90 2003/07/30 16:05:47 makro // no message // // Revision 1.89 2003/04/26 22:33:07 jbravo // Wratted all calls to G_FreeEnt() to avoid crashing and provide debugging // // Revision 1.88 2003/03/28 10:36:02 jbravo // Tweaking the replacement system a bit. Reactionmale now the default model // // Revision 1.87 2003/03/22 20:29:26 jbravo // wrapping linkent and unlinkent calls // // Revision 1.86 2003/03/09 21:30:38 jbravo // Adding unlagged. Still needs work. // // Revision 1.85 2002/12/20 14:29:47 jbravo // Activated the second barrel on the HC // // Revision 1.84 2002/10/26 22:03:43 jbravo // Made TeamDM work RQ3 style. // // Revision 1.83 2002/10/26 00:37:18 jbravo // New multiple item code and added PB support to the UI // // Revision 1.82 2002/09/24 05:06:17 blaze // fixed spectating so ref\'s can now use all the chasecam modes. // // Revision 1.81 2002/09/08 03:31:36 niceass // accuracy improved // // Revision 1.80 2002/09/02 02:23:45 niceass // removed spherical head detection and may have helped the ssg accuracy bug // // Revision 1.79 2002/08/28 23:10:06 jbravo // Added cg_RQ3_SuicideLikeARealMan, timestamping to server logs and // fixed stats for non-TP modes. // // Revision 1.78 2002/08/23 23:07:01 blaze // Should have fixed the unkickable thing breaking explosive breakables. // // Revision 1.77 2002/07/22 07:27:02 niceass // better fog laser support // // Revision 1.76 2002/07/22 06:32:15 niceass // cleaned up the powerup code // // Revision 1.75 2002/07/09 05:41:23 niceass // fix to kicking in CTB // // Revision 1.74 2002/07/04 00:59:25 blaze // moved the kick counter for stats to a more accurate spot. // // Revision 1.73 2002/07/01 19:48:56 makro // Fixed a crash bug // // Revision 1.72 2002/06/29 20:58:10 niceass // shoot through teammates // // Revision 1.71 2002/06/29 02:41:34 niceass // m4 kick fix // // Revision 1.70 2002/06/26 03:27:37 niceass // handcannon crash bug fixed // // Revision 1.69 2002/06/24 05:51:51 jbravo // CTF mode is now semi working // // Revision 1.68 2002/06/24 01:21:26 blaze // changed HC to be more like aq2. Probably more work needed // // Revision 1.67 2002/06/23 21:44:08 jbravo // Fixed shots fired stats for non TP modes and some cleanups // // Revision 1.66 2002/06/16 20:06:14 jbravo // Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap" // // Revision 1.65 2002/06/16 17:38:00 jbravo // Removed the MISSIONPACK ifdefs and missionpack only code. // // Revision 1.64 2002/06/03 00:40:25 blaze // some more breakables fixes(ssg chips) // // Revision 1.63 2002/06/02 22:04:38 blaze // breakables act proper when triggered(explode if explosive, etc) also, spawnflags 8 will make the breakable so you cant kick it // // Revision 1.62 2002/05/27 06:54:06 niceass // new reflection code // // Revision 1.61 2002/05/25 10:40:31 makro // Loading screen // // Revision 1.60 2002/05/24 18:47:02 makro // Jump kicks // // Revision 1.59 2002/05/20 16:25:48 makro // Triggerable cameras // // Revision 1.58 2002/05/20 16:23:44 jbravo // Fixed spec problem when noone is alive. Fixed kicking teammates bug // // Revision 1.57 2002/05/18 14:52:16 makro // Bot stuff. Other stuff. Just... stuff :p // // Revision 1.56 2002/05/12 22:14:13 makro // Impact sounds // // Revision 1.55 2002/05/12 14:40:28 makro // Wood, brick & ceramic impact sounds // // Revision 1.54 2002/05/12 13:37:25 makro // Bugs with entities // // Revision 1.53 2002/05/05 15:18:03 makro // Fixed some crash bugs. Bot stuff. Triggerable func_statics. // Made flags only spawn in CTF mode // // Revision 1.52 2002/05/03 18:09:20 makro // Bot stuff. Jump kicks // // Revision 1.51 2002/05/02 23:05:25 makro // Loading screen. Jump kicks. Bot stuff // // Revision 1.50 2002/05/02 03:06:09 blaze // Fixed breakables crashing on vashes // // Revision 1.49 2002/04/29 06:16:10 niceass // small change to pressure system // // Revision 1.48 2002/04/26 03:57:51 niceass // took some old stuff out + small pressure stuff // // Revision 1.47 2002/04/23 06:00:32 niceass // pressure stuff // // Revision 1.46 2002/04/21 00:31:27 blaze // ssg now properly breaks breakables // // Revision 1.45 2002/04/20 15:03:48 makro // More footstep sounds, a few other things // // Revision 1.44 2002/04/07 19:23:41 jbravo // Yet another crashbug fixed. // // Revision 1.43 2002/04/06 21:42:20 makro // Changes to bot code. New surfaceparm system. // // Revision 1.42 2002/04/03 09:26:47 jbravo // New FF system. Warns and then finally kickbans teamwounders and // teamkillers // // Revision 1.41 2002/04/02 00:28:10 slicer // Reduced the "arc" knife does // // Revision 1.40 2002/03/31 19:15:21 makro // Door kicking - ignore doors with targetnames // // Revision 1.39 2002/03/31 03:31:24 jbravo // Compiler warning cleanups // // Revision 1.38 2002/03/30 15:25:57 makro // Fixed door kicking // // Revision 1.37 2002/03/12 04:55:31 blaze // stats should only be recored when the round is in progress // // Revision 1.36 2002/03/03 02:20:58 jbravo // No kicking teammates in TP // // Revision 1.35 2002/01/27 14:02:51 jbravo // Make the right effect when rounds hit the new SURF_HARDMETAL surface. // // Revision 1.34 2002/01/11 20:20:58 jbravo // Adding TP to main branch // // Revision 1.33 2002/01/11 19:48:30 jbravo // Formatted the source in non DOS format. // // Revision 1.32 2001/12/31 16:28:42 jbravo // I made a Booboo with the Log tag. // // //----------------------------------------------------------------------------- // Copyright (C) 1999-2000 Id Software, Inc. // // g_weapon.c // perform the server side effects of a weapon firing #include "g_local.h" // JBravo: for warnings void Use_BinaryMover(gentity_t * ent, gentity_t * other, gentity_t * activator); static vec3_t forward, right, up; static vec3_t muzzle; //Elder: used for shell damage - we have no more malloc function so make it static? int tookShellHit[MAX_CLIENTS]; #define NUM_NAILSHOTS 15 /* ================ G_BounceProjectile ================ */ void G_BounceProjectile(vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout) { vec3_t v, newv; float dot; VectorSubtract(impact, start, v); dot = DotProduct(v, dir); VectorMA(v, -2 * dot, dir, newv); VectorNormalize(newv); VectorMA(impact, 8192, newv, endout); } /* ====================================================================== RQ3_JumpKick Moved from g_active.c to g_weapon.c Because it is a weapon! ====================================================================== */ qboolean JumpKick(gentity_t * ent) { trace_t tr; vec3_t end; gentity_t *tent, *traceEnt; int damage; //Elder: for kick sound qboolean kickSuccess; if (g_gametype.integer == GT_TEAMPLAY && level.lights_camera_action) { return qfalse; // JBravo: No kicking during LCA } //Makro - added if (ent == NULL || ent->client == NULL) { return qfalse; } // set aiming directions AngleVectors(ent->client->ps.viewangles, forward, right, up); CalcMuzzlePoint(ent, forward, right, up, muzzle); // Elder: AQ2 offset muzzle[2] -= (ent->client->ps.viewheight - 20); VectorMA(muzzle, 25, forward, end); // the muzzle really isn't the right point to test the jumpkick from trap_Trace(&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT); if (tr.surfaceFlags & SURF_NOIMPACT) { return qfalse; } kickSuccess = DoorKick(&tr, ent, muzzle, forward); traceEnt = &g_entities[tr.entityNum]; // JBravo: some sanity checks on the traceEnt // Makro - this check made the sound only play when a client is hit if (traceEnt == NULL) return qfalse; //Makro - removed //if (!traceEnt->takedamage) // return qfalse; //Makro - this was a few lines below damage = 20; if ((traceEnt->s.eType == ET_BREAKABLE || traceEnt->s.eType == ET_MOVER) && traceEnt->unkickable == qtrue ) { return qfalse; } if (traceEnt->s.eType == ET_BREAKABLE || traceEnt->client) kickSuccess = qtrue; // JBravo: no kicking teammates while rounds are going if (g_gametype.integer >= GT_TEAM) { //Makro - client check here if (traceEnt->client != NULL && ent->client != NULL) { // TP if (ent->client->sess.sessionTeam == traceEnt->client->sess.sessionTeam && level.team_round_going && g_gametype.integer == GT_TEAMPLAY) { return qfalse; } // CTF if (ent->client->sess.sessionTeam == traceEnt->client->sess.sessionTeam && (g_gametype.integer == GT_CTF || g_gametype.integer == GT_TEAM)) { return qfalse; } } } //Elder: can't hit if crouching but can still hit "dead" bodies :) if (traceEnt->client && traceEnt->health > 0 && traceEnt->r.maxs[2] < 20) { return qfalse; } else { G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_LOCATIONAL, MOD_KICK); } //end Makro //Makro - moved some code up by a few lines to allow breakables to be kicked if (traceEnt->client != NULL && traceEnt->takedamage) { tent = G_TempEntity(tr.endpos, EV_JUMPKICK); tent->s.otherEntityNum = traceEnt->s.number; tent->s.otherEntityNum2 = ent->s.number; tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.weapon = 0; if (traceEnt->client->uniqueWeapons > 0) { //Elder: toss a unique weapon if kicked //Need to make sure to cancel any reload attempts - test this Cmd_Unzoom(traceEnt); traceEnt->client->fastReloads = 0; traceEnt->client->reloadAttempts = 0; traceEnt->client->ps.weaponTime = 0; //Set the entity's weapon to the target's weapon before he/she throws it tent->s.weapon = ThrowWeapon(traceEnt, qtrue); ent->client->pers.records[REC_KICKHITS]++; } // Don't need other sound event kickSuccess = qfalse; } //Elder: Our set of locally called sounds if (kickSuccess) { G_AddEvent(ent, EV_RQ3_SOUND, RQ3_SOUND_KICK); } return qtrue; } qboolean DoorKick(trace_t * trIn, gentity_t * ent, vec3_t origin, vec3_t forward) { gentity_t *traceEnt; trace_t tr; qboolean ok = qfalse; traceEnt = &g_entities[trIn->entityNum]; if (Q_stricmp(traceEnt->classname, "func_door_rotating") == 0) { vec3_t d_right, d_forward; float crossProduct; vec3_t end; // Find the hit point and the muzzle point with respect // to the door's origin, then project down to the XY plane // and take the cross product VectorSubtract(trIn->endpos, traceEnt->s.origin, d_right); VectorSubtract(origin, traceEnt->s.origin, d_forward); crossProduct = d_forward[0] * d_right[1] - d_right[0] * d_forward[1]; //Makro - doors that open away from the player can be kicked from either side if (traceEnt->spawnflags & SP_OPENAWAY) { ok = qtrue; } else { // See if we are on the proper side to do it //Makro - it didn't take into account moverstate ok = ((traceEnt->pos2[1] > traceEnt->pos1[1]) && crossProduct > 0) || ((traceEnt->pos2[1] < traceEnt->pos1[1]) && crossProduct < 0); if (traceEnt->moverState == ROTATOR_1TO2 || traceEnt->moverState == ROTATOR_POS2) { ok = !ok; } } if (ok && !traceEnt->targetname && !traceEnt->takedamage) { //Cmd_OpenDoor( ent ); //Makro - Cmd_OpenDoor opens ALL the doors near the kicked one Use_BinaryMover(traceEnt, traceEnt, ent); VectorMA(trIn->endpos, 25, forward, end); trap_Trace(&tr, trIn->endpos, NULL, NULL, end, trIn->entityNum, MASK_SHOT); if (!(tr.surfaceFlags & SURF_NOIMPACT)) { traceEnt = &g_entities[tr.entityNum]; if (traceEnt->client) { *trIn = tr; } } return qtrue; } } return qfalse; } /* ====================================================================== GAUNTLET ====================================================================== */ /* void Weapon_Gauntlet( gentity_t *ent ) { } */ /* =============== CheckGauntletAttack =============== */ qboolean CheckGauntletAttack(gentity_t * ent) { trace_t tr; vec3_t end; gentity_t *tent; gentity_t *traceEnt; int damage; // set aiming directions AngleVectors(ent->client->ps.viewangles, forward, right, up); CalcMuzzlePoint(ent, forward, right, up, muzzle); VectorMA(muzzle, 32, forward, end); trap_Trace(&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT); if (tr.surfaceFlags & SURF_NOIMPACT) { return qfalse; } if ( ent->client->noclip ) { return qfalse; } traceEnt = &g_entities[tr.entityNum]; // send blood impact if (traceEnt->takedamage && traceEnt->client) { tent = G_TempEntity(tr.endpos, EV_MISSILE_HIT); tent->s.otherEntityNum = traceEnt->s.number; tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.weapon = ent->s.weapon; } if (!traceEnt->takedamage) { return qfalse; } damage = 50; G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_GAUNTLET); return qtrue; } /* ====================================================================== MACHINEGUN ====================================================================== */ /* ====================== SnapVectorTowards Round a vector to integers for more efficient network transmission, but make sure that it rounds towards a given point rather than blindly truncating. This prevents it from truncating into a wall. ====================== */ // JBravo: unlagged. moved to q_shared.c void SnapVectorTowards(vec3_t v, vec3_t to) { int i; for (i = 0; i < 3; i++) { if (to[i] <= v[i]) { v[i] = floor(v[i]); } else { v[i] = ceil(v[i]); } } } #define MACHINEGUN_SPREAD 200 #define MACHINEGUN_DAMAGE 7 #define MACHINEGUN_TEAM_DAMAGE 5 // wimpier MG in teamplay void Bullet_Fire(gentity_t * ent, float spread, int damage, int MOD) { trace_t tr; vec3_t end; float r; float u; gentity_t *tent; gentity_t *tent2; gentity_t *traceEnt; int i, passent; // int seed = ent->client->attackTime % 256; //Makro int Material; // Elder: Statistics tracking // JBravo: Take NON TP modes into account if (ent->client && ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY)) { switch (MOD) { case MOD_PISTOL: ent->client->pers.records[REC_MK23SHOTS]++; break; case MOD_M4: ent->client->pers.records[REC_M4SHOTS]++; break; case MOD_MP5: ent->client->pers.records[REC_MP5SHOTS]++; break; case MOD_AKIMBO: ent->client->pers.records[REC_AKIMBOSHOTS]++; break; } } u = crandom() * spread; r = crandom() * spread; VectorMA(muzzle, 8192, forward, end); VectorMA(end, r, right, end); VectorMA(end, u, up, end); passent = ent->s.number; for (i = 0; i < 10; i++) { // JBravo: unlagged // G_DoTimeShiftFor(ent); trap_Trace(&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT); // G_UndoTimeShiftFor(ent); // we traced from the muzzle, ignoring our entity, but triggered start solid. // lets try again from our origin since we might just be really close to another player. if ( tr.startsolid ) { trap_Trace(&tr, ent->client->ps.origin, NULL, NULL, end, passent, MASK_SHOT); } //Makro - saving the material flag to avoid useless calls to the GetMaterialFromFlag function Material = GetMaterialFromFlag(tr.surfaceFlags); if (tr.surfaceFlags & SURF_NOIMPACT) { return; } traceEnt = &g_entities[tr.entityNum]; // snap the endpos to integers, but nudged towards the line SnapVectorTowards(tr.endpos, muzzle); if (traceEnt->takedamage && traceEnt->client) { if (LogAccuracyHit(traceEnt, ent)) { ent->client->accuracy_hits++; // Elder: Statistics tracking if ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY) { switch (MOD) { case MOD_PISTOL: ent->client->pers.records[REC_MK23HITS]++; break; case MOD_M4: ent->client->pers.records[REC_M4HITS]++; break; case MOD_MP5: ent->client->pers.records[REC_MP5HITS]++; break; case MOD_AKIMBO: ent->client->pers.records[REC_AKIMBOHITS]++; break; } } } } else if (IsMetalMat(Material)) { tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_METAL); tent->s.eventParm = ReflectVectorByte(forward, tr.plane.normal); tent->s.otherEntityNum = ent->s.number; tent->s.clientNum = ent->s.clientNum; } else if (Material == MAT_GLASS) { tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_GLASS); tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.otherEntityNum = ent->s.number; tent->s.clientNum = ent->s.clientNum; //Makro - new sounds } else if (IsWoodMat(Material)) { tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_WOOD); tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.otherEntityNum = ent->s.number; tent->s.clientNum = ent->s.clientNum; //Makro - new sounds } else if (Material == MAT_BRICK) { tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_BRICK); tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.otherEntityNum = ent->s.number; tent->s.clientNum = ent->s.clientNum; //Makro - new sounds } else if (Material == MAT_CERAMIC) { tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_CERAMIC); tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.otherEntityNum = ent->s.number; tent->s.clientNum = ent->s.clientNum; //Makro - new fx } else if (IsSnowMat(Material)) { tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_SNOW); tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.otherEntityNum = ent->s.number; tent->s.clientNum = ent->s.clientNum; //Makro - new fx } else if (Material == MAT_GRASS) { tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_GRASS); tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.otherEntityNum = ent->s.number; tent->s.clientNum = ent->s.clientNum; } else { tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_WALL); tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.otherEntityNum = ent->s.number; tent->s.clientNum = ent->s.clientNum; } //Makro - moved the pressure code out of these if's if (traceEnt->s.eType == ET_PRESSURE) { // Pressure entities G_CreatePressure(tr.endpos, tr.plane.normal, traceEnt); } if (traceEnt->takedamage) { if (traceEnt->client) traceEnt->client->kevlarHit = qfalse; G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD); // FIXME: poor implementation if (traceEnt->client && traceEnt->client->kevlarHit == qfalse ) { //&& bg_itemlist[traceEnt->client->ps.stats[STAT_HOLDABLE_ITEM]].giTag == HI_KEVLAR) { if ( !OnSameTeam(traceEnt, ent) || (OnSameTeam(traceEnt, ent) && g_friendlyFire.integer > 0) ) { tent2 = G_TempEntity(tr.endpos, EV_BULLET_HIT_FLESH); //tent->s.eventParm = traceEnt->s.number; tent2->s.eventParm = DirToByte(forward); tent2->s.otherEntityNum2 = traceEnt->s.number; // Need this? tent2->s.otherEntityNum = ent->s.number; } } // NiceAss: Added so the M4 will shoot through bodies // Makro - changed from || to &&. Q3 crashed before if (traceEnt->client && ent->client) { if ((MOD == MOD_M4 && traceEnt->client->kevlarHit == qfalse) || // NiceAss: And you can shoot through teammates OnSameTeam(traceEnt, ent)) { VectorCopy(tr.endpos, muzzle); passent = tr.entityNum; continue; } } } break; } } /* ====================================================================== SHOTGUN ====================================================================== */ // DEFAULT_SHOTGUN_SPREAD and DEFAULT_SHOTGUN_COUNT are in bg_public.h, because // client predicts same spreads #define DEFAULT_SHOTGUN_DAMAGE 10 qboolean ShotgunPellet(vec3_t start, vec3_t end, gentity_t * ent) { trace_t tr; int damage, i, passent; gentity_t *traceEnt; vec3_t tr_start, tr_end; passent = ent->s.number; VectorCopy(start, tr_start); VectorCopy(end, tr_end); for (i = 0; i < 10; i++) { trap_Trace(&tr, tr_start, NULL, NULL, tr_end, passent, MASK_SHOT); traceEnt = &g_entities[tr.entityNum]; // send bullet impact if (tr.surfaceFlags & SURF_NOIMPACT) { return qfalse; } if (traceEnt->takedamage) { //Elder: added to discern handcannon and m3 damage if (ent->client && ent->client->ps.weapon == WP_HANDCANNON) { //G_Printf("Firing handcannon\n"); damage = HANDCANNON_DAMAGE; G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_HANDCANNON); } else { //G_Printf("Firing M3\n"); damage = M3_DAMAGE; G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_M3); } if (LogAccuracyHit(traceEnt, ent)) { return qtrue; } if ( OnSameTeam(traceEnt, ent) ) { VectorCopy( tr.endpos, tr_start ); passent = tr.entityNum; continue; } } return qfalse; } return qfalse; } // this should match CG_ShotgunPattern void ShotgunPattern(vec3_t origin, vec3_t origin2, int seed, gentity_t * ent, int shotType) { int i; float r, u; vec3_t end; vec3_t forward, right, up; // int oldScore; qboolean hitClient = qfalse; //Elder: added int count; //Makro - wasn't initialized, caused a warning in MSVC int hc_multipler = 4; // derive the right and up vectors from the forward vector, because // the client won't have any other information VectorNormalize2(origin2, forward); PerpendicularVector(right, forward); CrossProduct(forward, right, up); // oldScore = ent->client->ps.persistant[PERS_SCORE]; //Elder: added if (shotType == WP_M3) { // Elder: Statistics tracking if ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY) ent->client->pers.records[REC_M3SHOTS]++; count = DEFAULT_M3_COUNT; } else if (shotType == WP_HANDCANNON) { // Elder: Statistics tracking // JBravo: moving counting elsewhere to not count each HC shot twice as this gets called once // for each barrel of the HC // if ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY) // ent->client->pers.records[REC_HANDCANNONSHOTS]++; count = DEFAULT_HANDCANNON_COUNT; hc_multipler = 4; } else { // Elder: Statistics tracking // JBravo: moving counting elsewhere to not count each HC shot twice as this gets called once // if ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY) // ent->client->pers.records[REC_HANDCANNONSHOTS]++; count = DEFAULT_HANDCANNON_COUNT; hc_multipler = 5; } // G_DoTimeShiftFor(ent); // generate the "random" spread pattern for (i = 0; i < count; i++) { if (shotType == WP_M3) { r = Q_crandom(&seed) * DEFAULT_M3_HSPREAD; u = Q_crandom(&seed) * DEFAULT_M3_VSPREAD; } else { r = Q_crandom(&seed) * DEFAULT_SHOTGUN_HSPREAD * 4; u = Q_crandom(&seed) * DEFAULT_SHOTGUN_VSPREAD * hc_multipler; } VectorMA(origin, 8192, forward, end); VectorMA(end, r, right, end); VectorMA(end, u, up, end); if (ShotgunPellet(origin, end, ent)) { if (!hitClient) { hitClient = qtrue; ent->client->accuracy_hits++; // Elder: Statistics tracking if ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY) { switch (shotType) { case WP_M3: ent->client->pers.records[REC_M3HITS]++; //ent->client->m3Hits++; break; case WP_HANDCANNON: ent->client->pers.records[REC_HANDCANNONHITS]++; //ent->client->hcHits++; break; } } } } } // G_UndoTimeShiftFor(ent); } void weapon_supershotgun_fire(gentity_t * ent) { gentity_t *tent; // send shotgun blast tent = G_TempEntity(muzzle, EV_SHOTGUN); VectorScale(forward, 4096, tent->s.origin2); SnapVector(tent->s.origin2); tent->s.eventParm = rand() & 255; // seed for spread pattern tent->s.otherEntityNum = ent->s.number; //Elder: removed for now //ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent, qtrue ); } /* ====================================================================== RAILGUN ====================================================================== */ /* ================= weapon_railgun_fire ================= */ #define MAX_RAIL_HITS 4 void weapon_railgun_fire(gentity_t * ent) { vec3_t end; trace_t trace; gentity_t *tent; gentity_t *traceEnt; int damage; int i; int hits; int unlinked; int passent; gentity_t *unlinkedEntities[MAX_RAIL_HITS]; damage = 100; VectorMA(muzzle, 8192, forward, end); // trace only against the solids, so the railgun will go through people // G_DoTimeShiftFor(ent); unlinked = 0; hits = 0; passent = ent->s.number; do { trap_Trace(&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT); if (trace.entityNum >= ENTITYNUM_MAX_NORMAL) { break; } traceEnt = &g_entities[trace.entityNum]; if (traceEnt->takedamage) { if (LogAccuracyHit(traceEnt, ent)) { hits++; } G_Damage(traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); } if (trace.contents & CONTENTS_SOLID) { break; // we hit something solid enough to stop the beam } // unlink this entity, so the next trace will go past it trap_UnlinkEntity(traceEnt); unlinkedEntities[unlinked] = traceEnt; unlinked++; } while (unlinked < MAX_RAIL_HITS); // G_UndoTimeShiftFor(ent); // link back in any entities we unlinked for (i = 0; i < unlinked; i++) { trap_LinkEntity(unlinkedEntities[i]); } // the final trace endpos will be the terminal point of the rail trail // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards(trace.endpos, muzzle); // send railgun beam effect tent = G_TempEntity(trace.endpos, EV_RAILTRAIL); // set player number for custom colors on the railtrail tent->s.clientNum = ent->s.clientNum; VectorCopy(muzzle, tent->s.origin2); // move origin a bit to come closer to the drawn gun muzzle VectorMA(tent->s.origin2, 4, right, tent->s.origin2); VectorMA(tent->s.origin2, -1, up, tent->s.origin2); // no explosion at end if SURF_NOIMPACT, but still make the trail if (trace.surfaceFlags & SURF_NOIMPACT) { tent->s.eventParm = 255; // don't make the explosion at the end } else { tent->s.eventParm = DirToByte(trace.plane.normal); } tent->s.clientNum = ent->s.clientNum; // give the shooter a reward sound if they have made two railgun hits in a row if (hits == 0) { // complete miss ent->client->accurateCount = 0; } else { // check for "impressive" reward sound ent->client->accurateCount += hits; if (ent->client->accurateCount >= 2) { ent->client->accurateCount -= 2; //Blaze: Removed because it uses the persistant stats stuff //ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; // add the sprite over the player's head ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP); ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE; ent->client->rewardTime = level.time + REWARD_SPRITE_TIME; } ent->client->accuracy_hits++; } } /* ====================================================================== LIGHTNING GUN ====================================================================== */ void Weapon_LightningFire(gentity_t * ent) { trace_t tr; vec3_t end; gentity_t *traceEnt, *tent; int damage, i, passent; damage = 8; passent = ent->s.number; for (i = 0; i < 10; i++) { VectorMA(muzzle, LIGHTNING_RANGE, forward, end); trap_Trace(&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT); if (tr.entityNum == ENTITYNUM_NONE) { return; } traceEnt = &g_entities[tr.entityNum]; if (traceEnt->takedamage) { G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_LIGHTNING); } if (traceEnt->takedamage && traceEnt->client) { tent = G_TempEntity(tr.endpos, EV_MISSILE_HIT); tent->s.otherEntityNum = traceEnt->s.number; tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.weapon = ent->s.weapon; if (LogAccuracyHit(traceEnt, ent)) { ent->client->accuracy_hits++; } } else if (!(tr.surfaceFlags & SURF_NOIMPACT)) { tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS); tent->s.eventParm = DirToByte(tr.plane.normal); } break; } } /* ======================================================================== KNIFE ATTACKS ======================================================================== */ //Elder: changed to void function void Knife_Attack(gentity_t * self, int damage) { trace_t tr; vec3_t end; gentity_t *hitent; gentity_t *tent; if (self->client && ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY)) // self->client->pers.records[REC_KNIFESLASHSHOTS]++; VectorMA(muzzle, KNIFE_RANGE, forward, end); trap_Trace(&tr, muzzle, NULL, NULL, end, self->s.number, MASK_SHOT); hitent = &g_entities[tr.entityNum]; // don't need to check for water //if (!((tr.surfaceFlags) && (tr.surfaceFlags & SURF_SKY))) if (!(tr.surfaceFlags & SURF_SKY)) { if (tr.fraction < 1.0) { if (hitent->takedamage) { //Elder: no knock-back on knife slashes G_Damage(hitent, self, self, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_KNIFE); if (hitent->client) self->client->knife_sound = -2; } else self->client->knife_sound = -1; } } // NiceAss: Play appropriate sound on 5th slash if (self->client->ps.stats[STAT_BURST] > 4) { self->client->pers.records[REC_KNIFESLASHSHOTS]++; if (self->client->knife_sound == 0) { // Missed // TODO: Miss sound should be here, like in AQ2 } else if (self->client->knife_sound == -1) { // Hit wall //Elder TODO: take into account surface flags for clank tent = G_TempEntity(tr.endpos, EV_KNIFE_MISS); tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.weapon = WP_KNIFE; //Makro - added tent->s.powerups = GetMaterialFromFlag(tr.surfaceFlags);; } else if (self->client->knife_sound == -2) { // Hit player tent = G_TempEntity(tr.endpos, EV_RQ3_SOUND); tent->s.eventParm = RQ3_SOUND_KNIFEHIT; if (self->client && ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY)) self->client->pers.records[REC_KNIFESLASHHITS]++; } self->client->knife_sound = 0; } if (hitent->s.eType == ET_PRESSURE) { // Pressure entity G_CreatePressure(tr.endpos, tr.plane.normal, hitent); } } //static int knives = 0; //Elder: this function does not appear to be in use /* void Knife_Touch(gentity_t * ent, gentity_t * other, trace_t * trace) { vec3_t origin; gitem_t *xr_item; gentity_t *xr_drop; G_Printf("Knife Touched Something\n"); if (other == ent->parent) return; if (trace && (trace->surfaceFlags & SURF_SKY)) { //Blaze: Get rid of the knife if it hits the sky // G_FreeEdict (ent); //Elder: I think you want this G_FreeEntity(ent); return; } if (ent->parent->client) { //Blaze: Play the clank hit noise // gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/clank.wav"), 1, ATTN_NORM, 0); // PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); } // calculate position for the explosion entity VectorMA(ent->s.origin, -0.02, ent->s.origin2, origin); //glass fx if (0 == Q_stricmp(other->classname, "func_explosive")) { // ignore it, so it can bounce return; } else // --- if (other->takedamage) { G_Damage(other, ent, ent, ent->s.origin2, ent->s.origin, THROW_DAMAGE, 0, MOD_KNIFE_THROWN); } else { // code to manage excess knives in the game, guarantees that // no more than knifelimit knives will be stuck in walls. // if knifelimit == 0 then it won't be in effect and it can // start removing knives even when less than the limit are // out there. // if ( g_rxn_knifelimit.value != 0 ) // { // knives++; // // if (knives > g_rxn_knifelimit.value) // knives = 1; // // knife = FindEdictByClassnum ("weapon_Knife", knives); // // if (knife) // { // knife->nextthink = level.time + .1; // } // // } //spawn a knife in the object //Elder: todo - rotate the knife model so it's collinear with trajectory //and eliminate the jittering xr_item = BG_FindItemForWeapon(WP_KNIFE); //xr_drop = dropWeapon( ent, xr_item, 0, FL_DROPPED_ITEM); xr_drop = LaunchItem(xr_item, ent->s.pos.trBase, 0, FL_THROWN_KNIFE); xr_drop->count = 1; } G_FreeEntity(ent); } */ //gentity_t *Knife_Throw (gentity_t *self,vec3_t start, vec3_t dir, int damage, int speed ) //Elder: moved knife_throw to g_missile.c where it belongs //====================================================================== //Elder: can probably be static int RQ3_Spread(gentity_t * ent, int spread) { int runspeed = 225; int walkspeed = 10; int stage = 0; float factor[] = { 0.7f, 1.0f, 2.0f, 6.0f }; float xyspeed = (ent->client->ps.velocity[0] * ent->client->ps.velocity[0] + ent->client->ps.velocity[1] * ent->client->ps.velocity[1]); //crouching //if (ent->client->ps.pm_flags & PMF_DUCKED) // make sure player is actually on the ground if (ent->client->ps.groundEntityNum != ENTITYNUM_NONE && ent->client->ps.pm_flags & PMF_DUCKED) return (spread * 0.65); //running if (xyspeed > runspeed * runspeed) stage = 3; //walking else if (xyspeed >= walkspeed * walkspeed) stage = 2; //standing else stage = 1; //added laser advantage if ((ent->client->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_LASER)) && (ent->client->ps.weapon == WP_PISTOL || ent->client->ps.weapon == WP_MP5 || ent->client->ps.weapon == WP_M4)) { //G_Printf("Using laser advantage\n"); if (stage == 1) stage = 0; else stage = 1; } return (int) (spread * factor[stage]); } /* ============ Knife Attack ============ */ void Weapon_Knife_Fire(gentity_t * ent) { // Knife_Attack(ent,THROW_DAMAGE); // gentity_t *m; // Homer: if client is supposed to be slashing, go to that function instead if ((ent->client->ps.persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE) == RQ3_KNIFEMODE) { //Elder: added Knife_Attack(ent, SLASH_DAMAGE); return; } // extra vertical velocity //Slicer reduced from 0.2 to 0.1 forward[2] += 0.1f; //Elder: already done in Knife_Throw //VectorNormalize( forward ); //m = Knife_Throw(ent, muzzle, forward, THROW_DAMAGE, 1200); // JBravo: ff if (g_gametype.integer >= GT_TEAM) setFFState(ent); fire_knife(ent, muzzle, forward); } /* ============ M4 Attack ============ */ void Weapon_M4_Fire(gentity_t * ent) { float spread; // Homer: increment burst if needed if ((ent->client->ps.persistant[PERS_WEAPONMODES] & RQ3_M4MODE) == RQ3_M4MODE) { spread = M4_SPREAD * 0.7; } else { spread = M4_SPREAD; //M4-kick stuff // NiceAss; Make it so M4 kickback doesn't happen // if looking straight up if (ent->client->ps.viewangles[0] > -85.0f) { ent->client->consecutiveShots++; if (ent->client->consecutiveShots > 23) ent->client->consecutiveShots = 23; } } // JBravo: ff if (g_gametype.integer >= GT_TEAM) setFFState(ent); Bullet_Fire(ent, RQ3_Spread(ent, spread), M4_DAMAGE, MOD_M4); } /* ============ MK23 Attack ============ */ void Weapon_MK23_Fire(gentity_t * ent) { // int i; float spread; // Homer: increment burst if needed if ((ent->client->ps.persistant[PERS_WEAPONMODES] & RQ3_MK23MODE) == RQ3_MK23MODE) { spread = PISTOL_SPREAD * 0.7; //ent->client->ps.stats[STAT_BURST]++; } else { spread = PISTOL_SPREAD; } // JBravo: ff if (g_gametype.integer >= GT_TEAM) setFFState(ent); Bullet_Fire(ent, RQ3_Spread(ent, spread), PISTOL_DAMAGE, MOD_PISTOL); } /* ============ SSG3000 Attack ============ */ void Weapon_SSG3000_FireOld(gentity_t * ent) { float spread; //Elder: changed to use RQ3_Spread as well if (RQ3_isZoomed(ent)) { spread = 0; } else { spread = RQ3_Spread(ent, SNIPER_SPREAD); } // JBravo: ff if (g_gametype.integer >= GT_TEAM) setFFState(ent); Bullet_Fire(ent, spread, SNIPER_DAMAGE, MOD_SNIPER); //Elder: bolt action plus save last zoom ent->client->weaponfireNextTime = level.time + RQ3_SSG3000_BOLT_DELAY; RQ3_SaveZoomLevel(ent); } /* ================= Elder: This is a triple-hybrid bastard child of railgun_fire, old SSG, and bullet_fire code weapon_ssg3000_fire ================= */ #define MAX_SSG3000_HITS 8 void Weapon_SSG3000_Fire(gentity_t * ent) { vec3_t end; trace_t trace; gentity_t *tent, *tentWall, *traceEnt = NULL; gentity_t *unlinkedEntities[MAX_SSG3000_HITS]; int damage, i, hits, unlinked, passent, Material; qboolean hitBreakable; float r, u, spread; // Elder: Statistics tracking if (ent->client && ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY)) ent->client->pers.records[REC_SSG3000SHOTS]++; VectorMA(muzzle, 8192, forward, end); //Elder: added to assist in zoom crap if (!RQ3_isZoomed(ent)) { spread = RQ3_Spread(ent, SNIPER_SPREAD); u = crandom() * spread; r = crandom() * spread; VectorMA(end, r, right, end); VectorMA(end, u, up, end); } damage = SNIPER_DAMAGE; // JBravo: unlagged NEW // G_DoTimeShiftFor(ent); // trace only against the solids, so the SSG3000 will go through people unlinked = 0; hits = 0; passent = ent->s.number; do { //Elder: need to store this flag because //the entity may get wiped out in G_Damage hitBreakable = qfalse; trap_Trace(&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT); // Basically, it hit a wall (worldspawn) if (trace.entityNum >= ENTITYNUM_MAX_NORMAL) break; traceEnt = &g_entities[trace.entityNum]; if (traceEnt->unbreakable == qtrue) { Material = GetMaterialFromFlag(trace.surfaceFlags); if (IsMetalMat(Material)) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_METAL); else if (Material == MAT_GLASS) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_GLASS); else if (IsWoodMat(Material)) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_WOOD); else if (Material == MAT_BRICK) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_BRICK); else if (Material == MAT_CERAMIC) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_CERAMIC); else if (IsSnowMat(Material)) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_SNOW); else if (Material == MAT_GRASS) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_GRASS); else tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_WALL); tent->s.eventParm = DirToByte(trace.plane.normal); tent->s.otherEntityNum = ent->s.number; G_Damage(traceEnt, ent, ent, forward, trace.endpos, 0, 0, MOD_SNIPER); return; } if (traceEnt->takedamage) { //flag hitBreakable - bullets go through even //if it doesn't "shatter" - but that's usually //not the case if (traceEnt->s.eType == ET_BREAKABLE) { hitBreakable = qtrue; } // send impacts if (traceEnt->client ) { if ( !OnSameTeam(traceEnt, ent) || (OnSameTeam(traceEnt, ent) && g_friendlyFire.integer > 0) ) { tent = G_TempEntity(trace.endpos, EV_SSG3000_HIT_FLESH); tent->s.eventParm = DirToByte(forward); tent->s.otherEntityNum2 = traceEnt->s.number; tent->s.otherEntityNum = ent->s.number; } } else { // impact type Material = GetMaterialFromFlag(trace.surfaceFlags); if (IsMetalMat(Material)) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_METAL); else if (Material == MAT_GLASS) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_GLASS); else if (IsWoodMat(Material)) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_WOOD); else if (Material == MAT_BRICK) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_BRICK); else if (Material == MAT_CERAMIC) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_CERAMIC); else if (IsSnowMat(Material)) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_SNOW); else if (Material == MAT_GRASS) tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_GRASS); else tent = G_TempEntity(trace.endpos, EV_BULLET_HIT_WALL); tent->s.eventParm = DirToByte(trace.plane.normal); tent->s.otherEntityNum = ent->s.number; } if (LogAccuracyHit(traceEnt, ent)) { hits++; } //G_Printf("(%d) SSG: Doing damage to target\n", level.time); G_Damage(traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_SNIPER); } //Elder: go through non-solids and breakables //If we ever wanted to "shoot through walls" we'd do stuff here if ( hitBreakable == qfalse && (trace.contents & CONTENTS_SOLID) ) { break; // we hit something solid enough to stop the beam } if (hitBreakable == qfalse || OnSameTeam(traceEnt, ent) ) { trap_UnlinkEntity(traceEnt); unlinkedEntities[unlinked] = traceEnt; unlinked++; } } while (unlinked < MAX_SSG3000_HITS); // G_UndoTimeShiftFor(ent); // link back in any entities we unlinked for (i = 0; i < unlinked; i++) { trap_LinkEntity(unlinkedEntities[i]); } // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards(trace.endpos, muzzle); // send wall bullet impact Material = GetMaterialFromFlag(trace.surfaceFlags); if (!(trace.surfaceFlags & SURF_NOIMPACT)) { //Makro - new surfaceparm system if (IsMetalMat(Material)) tentWall = G_TempEntity(trace.endpos, EV_BULLET_HIT_METAL); else if (Material == MAT_GLASS) tentWall = G_TempEntity(trace.endpos, EV_BULLET_HIT_GLASS); else if (IsWoodMat(Material)) tentWall = G_TempEntity(trace.endpos, EV_BULLET_HIT_WOOD); else if (Material == MAT_BRICK) tentWall = G_TempEntity(trace.endpos, EV_BULLET_HIT_BRICK); else if (Material == MAT_CERAMIC) tentWall = G_TempEntity(trace.endpos, EV_BULLET_HIT_CERAMIC); else if (IsSnowMat(Material)) tentWall = G_TempEntity(trace.endpos, EV_BULLET_HIT_SNOW); else if (Material == MAT_GRASS) tentWall = G_TempEntity(trace.endpos, EV_BULLET_HIT_GRASS); else tentWall = G_TempEntity(trace.endpos, EV_BULLET_HIT_WALL); tentWall->s.eventParm = DirToByte(trace.plane.normal); tentWall->s.otherEntityNum = ent->s.number; if (traceEnt && traceEnt->s.eType == ET_PRESSURE) G_CreatePressure(trace.endpos, trace.plane.normal, traceEnt); } // give the shooter a reward sound if they have made two railgun hits in a row if (hits == 0) { // complete miss ent->client->accurateCount = 0; } else { // check for "impressive" reward sound ent->client->accurateCount += hits; if (ent->client->accurateCount >= 3) { ent->client->accurateCount -= 3; //Blaze: Removed because it uses the persistant stats stuff //ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; // add the sprite over the player's head ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP); ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE; ent->client->rewardTime = level.time + REWARD_SPRITE_TIME; } ent->client->accuracy_hits++; if ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY) ent->client->pers.records[REC_SSG3000HITS]++; //ent->client->ssgHits++; } //Elder: bolt action plus save last zoom //ent->client->weaponfireNextTime = level.time + RQ3_SSG3000_BOLT_DELAY; RQ3_SaveZoomLevel(ent); } /* ============ MP5 Attack ============ */ void Weapon_MP5_Fire(gentity_t * ent) { float spread; // Homer: increment burst if needed if ((ent->client->ps.persistant[PERS_WEAPONMODES] & RQ3_MP5MODE) == RQ3_MP5MODE) { spread = MP5_SPREAD * 0.7; //ent->client->ps.stats[STAT_BURST]++; } else { spread = MP5_SPREAD; } // JBravo: ff if (g_gametype.integer >= GT_TEAM) setFFState(ent); Bullet_Fire(ent, RQ3_Spread(ent, spread), MP5_DAMAGE, MOD_MP5); } /* ============ Handcannon Attack ============ */ void Weapon_HandCannon_Fire(gentity_t * ent) { gentity_t *tent, *tent2; // JBravo: ff if (g_gametype.integer >= GT_TEAM) setFFState(ent); //Elder: added for damage report RQ3_InitShotgunDamageReport(); // send shotgun blast tent = G_TempEntity(muzzle, EV_HANDCANNON); VectorScale(forward, 4096, tent->s.origin2); SnapVector(tent->s.origin2); tent->s.eventParm = rand() & 255; // seed for spread pattern tent->s.otherEntityNum = ent->s.number; tent->s.angles2[1] -= 5; ShotgunPattern(tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent, WP_HANDCANNON); // send shotgun blast tent2 = G_TempEntity(muzzle, EV_HANDCANNON); VectorScale(forward, 4096, tent2->s.origin2); SnapVector(tent2->s.origin2); tent2->s.eventParm = rand() & 255; // seed for spread pattern tent2->s.otherEntityNum = ent->s.number; tent2->s.angles2[1] += 5; //Elder: note negative one //JBravo: Yes, why is that ? removing... //JBravo: thats because the second barrel has a higher vspread. reverting back to -1 ShotgunPattern(tent2->s.pos.trBase, tent2->s.origin2, tent2->s.eventParm, ent, -1); // ShotgunPattern(tent2->s.pos.trBase, tent2->s.origin2, tent2->s.eventParm, ent, WP_HANDCANNON); // JBravo: count the HC blasts here if ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY) ent->client->pers.records[REC_HANDCANNONSHOTS]++; //Elder: added for damage report RQ3_ProduceShotgunDamageReport(ent); } /* ============ Shotgun Attack ============ */ void Weapon_M3_Fire(gentity_t * ent) { //Blaze: call to shotgun fire function here gentity_t *tent; // JBravo: ff if (g_gametype.integer >= GT_TEAM) setFFState(ent); //Elder: added for damage report RQ3_InitShotgunDamageReport(); // send shotgun blast tent = G_TempEntity(muzzle, EV_SHOTGUN); VectorScale(forward, 4096, tent->s.origin2); SnapVector(tent->s.origin2); tent->s.eventParm = rand() & 255; // seed for spread pattern tent->s.otherEntityNum = ent->s.number; ShotgunPattern(tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent, WP_M3); //Elder: added for damage report RQ3_ProduceShotgunDamageReport(ent); } /* ============== RQ3_ShotgunDamageReport Added by Elder Used to determine hits from a shotgun source Modelled after the one done for AQ2 ============== */ void RQ3_InitShotgunDamageReport(void) { //Elder: Reset all tookShellHit 'slots' to zero memset(tookShellHit, 0, MAX_CLIENTS * sizeof(int)); } //Elder: similar to AQ2 source #define MAX_NAME_REPORTS 8 void RQ3_ProduceShotgunDamageReport(gentity_t * self) { int i; int totalNames = 0; int printed = 0; char textbuf[1024]; gclient_t *hitClient; //for (l = 1; l <= g_maxclients.integer; l++) // Run through array to see if anyone was hit for (i = 0; i < MAX_CLIENTS; i++) { if (tookShellHit[i]) totalNames++; } if (totalNames) { // Clamp number of names to report if (totalNames > MAX_NAME_REPORTS) totalNames = MAX_NAME_REPORTS; //Q_strncpyz(textbuf, "You hit", sizeof(textbuf)); strcpy(textbuf, "You hit "); for (i = 0; i < MAX_CLIENTS; i++) { if (tookShellHit[i]) { //grammar set if (printed == (totalNames - 1)) { if (totalNames == 2) Q_strcat(textbuf, sizeof(textbuf), "^7 and "); else if (totalNames != 1) Q_strcat(textbuf, sizeof(textbuf), "^7, and "); } else if (printed) Q_strcat(textbuf, sizeof(textbuf), "^7, "); //add to text buffer hitClient = g_entities[i].client; Q_strcat(textbuf, sizeof(textbuf), hitClient->pers.netname); printed++; } if (printed == totalNames) break; } trap_SendServerCommand(self - g_entities, va("print \"%s^7.\n\"", textbuf)); //gi.cprintf(self, PRINT_HIGH, "%s\n", textbuf); } } /* ============ Akimbo Attack ============ */ void Weapon_Akimbo_Fire(gentity_t * ent) { float spread; //Blaze: Will need 2 of these spread = AKIMBO_SPREAD; // JBravo: ff if (g_gametype.integer >= GT_TEAM) setFFState(ent); Bullet_Fire(ent, RQ3_Spread(ent, spread), AKIMBO_DAMAGE, MOD_AKIMBO); //Elder: reset plus added 1 bullet check //if (ent->client->weaponfireNextTime > 0 || ent->client->ps.ammo[WP_AKIMBO] < 2) //ent->client->weaponfireNextTime = 0; //else //ent->client->weaponfireNextTime = level.time + RQ3_AKIMBO_DELAY2; //Bullet_Fire( ent, RQ3_Spread(ent, spread), AKIMBO_DAMAGE, MOD_AKIMBO); } /* ============ Grenade Attack ============ */ void Weapon_Grenade_Fire(gentity_t * ent) { // gentity_t *m; // extra vertical velocity // Elder: not present in AQ2 //forward[2] += 0.5f; VectorNormalize(forward); // JBravo: ff if (g_gametype.integer >= GT_TEAM) setFFState(ent); fire_grenade(ent, muzzle, forward); } /* =============== LogAccuracyHit =============== */ qboolean LogAccuracyHit(gentity_t * target, gentity_t * attacker) { //Makro - some checks; Q3 crashed with .dlls //when shooting one of the barrels in archives if (target == NULL || attacker == NULL) { return qfalse; } if (!target->takedamage) { return qfalse; } if (target == attacker) { return qfalse; } if (!target->client) { return qfalse; } if (!attacker->client) { return qfalse; } if (target->client->ps.stats[STAT_HEALTH] <= 0) { // Elder: Statistics tracking if ((g_gametype.integer == GT_TEAMPLAY && level.team_round_going) || g_gametype.integer != GT_TEAMPLAY) attacker->client->pers.records[REC_CORPSESHOTS]++; return qfalse; } if (OnSameTeam(target, attacker)) { return qfalse; } return qtrue; } /* =============== CalcMuzzlePoint set muzzle location relative to pivoting eye =============== */ void CalcMuzzlePoint(gentity_t * ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint) { // VectorCopy(ent->s.pos.trBase, muzzlePoint); VectorCopy(ent->client->ps.origin, muzzlePoint); muzzlePoint[2] += ent->client->ps.viewheight; VectorMA(muzzlePoint, 14, forward, muzzlePoint); // snap to integer coordinates for more efficient network bandwidth usage } /* =============== CalcMuzzlePointOrigin set muzzle location relative to pivoting eye =============== */ void CalcMuzzlePointOrigin(gentity_t * ent, vec3_t origin, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint) { // VectorCopy(ent->s.pos.trBase, muzzlePoint); VectorCopy(ent->client->ps.origin, muzzlePoint); muzzlePoint[2] += ent->client->ps.viewheight; VectorMA(muzzlePoint, 14, forward, muzzlePoint); // snap to integer coordinates for more efficient network bandwidth usage } /* =============== FireWeapon =============== */ void FireWeapon(gentity_t * ent) { // track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked //Blaze: dont record accuracy for knife // if( ent->s.weapon != WP_KNIFE) { ent->client->accuracy_shots++; // } // set aiming directions AngleVectors(ent->client->ps.viewangles, forward, right, up); // CalcMuzzlePointOrigin(ent, ent->client->oldOrigin, forward, right, up, muzzle); CalcMuzzlePointOrigin(ent, ent->client->ps.origin, forward, right, up, muzzle); // fire the specific weapon switch (ent->s.weapon) { //Blaze: The functions get called when you shoot your gun case WP_KNIFE: Weapon_Knife_Fire(ent); //ent->client->knifeShots++; break; case WP_GRENADE: Weapon_Grenade_Fire(ent); //ent->client->grenShots++; break; case WP_PISTOL: Weapon_MK23_Fire(ent); //ent->client->mk23Shots++; break; case WP_M4: Weapon_M4_Fire(ent); //ent->client->m4Shots++; break; case WP_SSG3000: Weapon_SSG3000_Fire(ent); //ent->client->ssgShots++; break; case WP_MP5: Weapon_MP5_Fire(ent); //ent->client->mp5Shots++; break; case WP_HANDCANNON: Weapon_HandCannon_Fire(ent); //ent->client->hcShots++; break; case WP_M3: Weapon_M3_Fire(ent); //ent->client->m3Shots++; break; case WP_AKIMBO: Weapon_Akimbo_Fire(ent); //ent->client->akimboShots++; break; default: // FIXME G_Error( "Bad ent->s.weapon" ); break; } } /* ============ Laser Sight Stuff Laser Sight / Flash Light Functions ============ */ void Laser_Gen(gentity_t * ent, qboolean enabled) { gentity_t *las; //Elder: force it to laser int type = 1; // First, if it's not the right weapon, leave if (ent->client->ps.weapon != WP_PISTOL && ent->client->ps.weapon != WP_MP5 && ent->client->ps.weapon != WP_M4) { //Kill laser if it exists if (ent->client->lasersight) { G_FreeEntity(ent->client->lasersight); ent->client->lasersight = NULL; } ent->client->ps.powerups[PW_LASERSIGHT] = 0; return; } //Get rid of you? if (ent->client->lasersight || enabled == qfalse) { // JBravo: fixing the bad gEnt crashbug if (ent->client->lasersight) G_FreeEntity(ent->client->lasersight); ent->client->lasersight = NULL; ent->client->ps.powerups[PW_LASERSIGHT] = 0; return; } ent->client->ps.powerups[PW_LASERSIGHT] = INT_MAX; las = G_Spawn(); las->nextthink = level.time + 10; las->think = Laser_Think; las->s.clientNum = ent->s.number; las->r.ownerNum = ent->s.number; las->parent = ent; las->s.eType = ET_LASER; //Elder: added to enable lerping in cgame las->s.pos.trType = TR_INTERPOLATE; //Lets tell it if flashlight or laser if (type == 2) { las->s.eventParm = 2; //tells CG that it is a flashlight las->classname = "flashlight"; } else { las->s.eventParm = 1; //tells CG that it is a laser sight las->classname = "lasersight"; } ent->client->lasersight = las; } void Laser_Think(gentity_t * self) { vec3_t end, start, forward, up; trace_t tr; //, tr2; int l = 0, passent; // gentity_t *traceEnt; //Makro - added //int contents; //If the player dies, is spectator, or wrong weapon, kill the dot if (self->parent->client->ps.pm_type == PM_DEAD || self->parent->client->ps.pm_type == PM_SPECTATOR || (self->parent->client->ps.weapon != WP_PISTOL && self->parent->client->ps.weapon != WP_MP5 && self->parent->client->ps.weapon != WP_M4)) { //Make sure you kill the reference before freeing the entity self->parent->client->lasersight = NULL; self->parent->client->ps.powerups[PW_LASERSIGHT] = 0; G_FreeEntity(self); return; } //Set Aiming Directions AngleVectors(self->parent->client->ps.viewangles, forward, right, up); CalcMuzzlePoint(self->parent, forward, right, up, start); passent = self->parent->s.number; self->s.eFlags &= ~EF_FIRING; // Keep tracing if it hits glass. for (l = 0; l < 10; l++) { VectorMA(start, 8192, forward, end); //Trace Position trap_Trace(&tr, start, NULL, NULL, end, passent, MASK_SHOT); // traceEnt = &g_entities[tr.entityNum]; //Did you not hit anything? if (tr.surfaceFlags & SURF_NOIMPACT || tr.surfaceFlags & SURF_SKY) { self->nextthink = level.time + 10; trap_UnlinkEntity(self); return; } //Makro - new surfaceparm system if (!(GetMaterialFromFlag(tr.surfaceFlags) == MAT_GLASS)) break; VectorMA(tr.endpos, 10, forward, start); // Nudge it forward a little bit } //Move you forward to keep you visible if (tr.fraction != 1) VectorMA(tr.endpos, -4, forward, tr.endpos); //Set Your position VectorCopy(tr.endpos, self->r.currentOrigin); VectorCopy(tr.endpos, self->s.pos.trBase); vectoangles(tr.plane.normal, self->s.angles); trap_LinkEntity(self); //Prep next move self->nextthink = level.time + 10; } /* ================= ReloadWeapon Added by Elder Handles server-side management of numclips ================= */ void ReloadWeapon(gentity_t * ent, int stage) { if (stage == 1 && (ent->client->ps.weapon == WP_SSG3000 || ent->client->ps.weapon == WP_M3)) { if (ent->client->ps.weapon == WP_M3) ent->client->numClips[WP_HANDCANNON] = ent->client->numClips[WP_M3]; ent->client->numClips[ent->client->ps.weapon]--; } else if (stage == 2) { ent->client->numClips[ent->client->ps.weapon]--; // remove an extra clip if using HC or Akimbos if (ent->client->ps.weapon == WP_HANDCANNON || ent->client->ps.weapon == WP_AKIMBO) ent->client->numClips[ent->client->ps.weapon]--; //Elder: sync hc and m3 ammo + mk23 and akimbo ammo - a switch might look nicer if (ent->client->ps.weapon == WP_M3) { ent->client->numClips[WP_HANDCANNON] = ent->client->numClips[WP_M3]; } else if (ent->client->ps.weapon == WP_HANDCANNON) { ent->client->numClips[WP_M3] = ent->client->numClips[WP_HANDCANNON]; } else if (ent->client->ps.weapon == WP_PISTOL) { ent->client->numClips[WP_AKIMBO] = ent->client->numClips[WP_PISTOL]; } else if (ent->client->ps.weapon == WP_AKIMBO) { ent->client->numClips[WP_PISTOL] = ent->client->numClips[WP_AKIMBO]; } } }