// p_weapon.c #include "g_local.h" #include "m_player.h" //CW++ #ifdef LINUX #define min(a,b) (((a)<(b))?(a):(b)) #endif //CW-- qboolean is_quad; //CW static byte is_silenced; void P_ProjectSource(gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) { vec3_t _distance; VectorCopy(distance, _distance); if (client->pers.hand == LEFT_HANDED) _distance[1] *= -1.0F; else if (client->pers.hand == CENTER_HANDED) _distance[1] = 0.0F; G_ProjectSource(point, _distance, forward, right, result); } /* =============== PlayerNoise Each player can have two noise objects associated with it: a personal noise (jumping, pain, weapon firing), and a weapon target noise (bullet wall impacts) Bots that don't directly see opponents can move to a noise in hopes of seeing their enemy from there. =============== */ void PlayerNoise(edict_t *who, vec3_t where, int type) { edict_t *noise; //CW++ if (!who) return; //CW-- if ((type == PNOISE_WEAPON) && who->client) //CW { if (who->client->silencer_shots) { who->client->silencer_shots--; return; } } //CW++ if (NumBotsInGame == 0) return; //CW-- if (who->flags & FL_NOTARGET) return; if (!who->mynoise) { noise = G_Spawn(); noise->classname = "player_noise"; VectorSet(noise->mins, -8, -8, -8); VectorSet(noise->maxs, 8, 8, 8); noise->owner = who; noise->svflags = SVF_NOCLIENT; who->mynoise = noise; noise = G_Spawn(); noise->classname = "player_noise"; VectorSet(noise->mins, -8, -8, -8); VectorSet(noise->maxs, 8, 8, 8); noise->owner = who; noise->svflags = SVF_NOCLIENT; who->mynoise2 = noise; } if ((type == PNOISE_SELF) || (type == PNOISE_WEAPON)) noise = who->mynoise; else //PNOISE_IMPACT noise = who->mynoise2; VectorCopy(where, noise->s.origin); VectorSubtract(where, noise->maxs, noise->absmin); VectorAdd(where, noise->maxs, noise->absmax); noise->teleport_time = level.time; gi.linkentity(noise); } qboolean Pickup_Weapon(edict_t *ent, edict_t *other) { gitem_t *ammo; int index; index = ITEM_INDEX(ent->item); //Maj++ CheckCampSite(ent, other); //Maj-- if (((int)dmflags->value & DF_WEAPONS_STAY) && other->client->pers.inventory[index]) //CW { if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) return false; // leave the weapon for others to pickup } other->client->pers.inventory[index]++; if (!(ent->spawnflags & DROPPED_ITEM)) { // give them some ammo with it ammo = FindItem(ent->item->ammo); if ((int)dmflags->value & DF_INFINITE_AMMO) Add_Ammo(other, ammo, 1000); else Add_Ammo(other, ammo, ammo->quantity); if (!(ent->spawnflags & DROPPED_PLAYER_ITEM)) { if ((int)(dmflags->value) & DF_WEAPONS_STAY) //CW ent->flags |= FL_RESPAWN; else { //CW++ if (ent->delay) SetRespawn(ent, ent->delay); else //CW-- SetRespawn(ent, 30); } } } return true; } /* =============== ChangeWeapon The old weapon has been dropped all the way, so make the new one current =============== */ void ChangeWeapon(edict_t *ent) { int i; //CW++ int mode; if (!ent->client) { gi.dprintf("BUG: ChangeWeapon() called with non-client edict.\n"); if (ent->classname) gi.dprintf(" classname = %s\n", ent->classname); if (ent->owner && ent->owner->client) gi.dprintf(" owner->name = %s\n", ent->owner->client->pers.netname); return; } //CW-- ent->client->pers.lastweapon = ent->client->pers.weapon; ent->client->pers.weapon = ent->client->newweapon; ent->client->newweapon = NULL; ent->client->machinegun_shots = 0; // set visible model if (ent->s.modelindex == (MAX_MODELS-1)) { if (ent->client->pers.weapon) i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8); else i = 0; ent->s.skinnum = (ent - g_edicts - 1) | i; } if (ent->client->pers.weapon && ent->client->pers.weapon->ammo) ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo)); else ent->client->ammo_index = 0; if (!ent->client->pers.weapon) { // dead ent->client->ps.gunindex = 0; return; } ent->client->weaponstate = WEAPON_ACTIVATING; ent->client->ps.gunframe = 0; ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); ent->client->anim_priority = ANIM_PAIN; if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) { ent->s.frame = FRAME_crpain1; ent->client->anim_end = FRAME_crpain4; } else { ent->s.frame = FRAME_pain301; ent->client->anim_end = FRAME_pain304; } //CW++ ent->client->show_gausscharge = false; ent->client->show_gausstarget = 0; ent->client->agm_charge = 0; ent->client->agm_showcharge = false; ent->client->agm_tripped = false; ent->client->agm_on = false; ent->client->agm_push = false; ent->client->agm_pull = false; ent->client->agm_target = NULL; if (ent->isabot) return; mode = ent->client->pers.weap_note; if (ent->client->pers.weapon->weapmodel == WEAP_DESERTEAGLE) { if ((mode & WN_VALL) && (ent->client->pers.lastweapon->weapmodel != WEAP_DESERTEAGLE)) unicastSound(ent, gi.soundindex("voice/u_pistol.wav"), 1.0); //r1,CW } else if (ent->client->pers.weapon->weapmodel == WEAP_GAUSSPISTOL) { if (ent->client->gauss_particle) { if (ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] < GP_SLUGS_PER_SHOT) { gi_centerprintf(ent, "Not enough Slugs ...\nSwitching to Blaster mode\n"); ent->client->gauss_particle = false; ent->client->show_gausstarget = 0; ent->client->show_gausscharge = true; } else if (ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] < GP_CELLS_PER_SHOT) { gi_centerprintf(ent, "Not enough Cells ...\nSwitching to Blaster mode\n"); ent->client->gauss_particle = false; ent->client->show_gausstarget = 0; ent->client->show_gausscharge = true; } else { ent->client->show_gausstarget = 1; ent->client->show_gausscharge = false; ent->client->showinventory = 0; ent->client->showscores = 0; } } if (ent->client->gauss_particle) { if (mode & WN_TEXT) gi_centerprintf(ent, "Particle Beam\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_partcl.wav"), 1.0); //r1,CW } else { if (mode & WN_TEXT) gi_centerprintf(ent, "Blaster\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_blast.wav"), 1.0); //r1,CW } gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/gauss/use.wav"), 1, ATTN_NORM, 0); } else if (ent->client->pers.weapon->weapmodel == WEAP_JACKHAMMER) { if ((mode & WN_VALL) && (ent->client->pers.lastweapon->weapmodel != WEAP_JACKHAMMER)) unicastSound(ent, gi.soundindex("voice/u_jack.wav"), 1.0); //r1,CW gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/sshotr1b.wav"), 1, ATTN_NORM, 0); } else if (ent->client->pers.weapon->weapmodel == WEAP_MAC10) { if ((mode & WN_VALL) && (ent->client->pers.lastweapon->weapmodel != WEAP_MAC10)) unicastSound(ent, gi.soundindex("voice/u_mac10.wav"), 1.0); //r1,CW } else if (ent->client->pers.weapon->weapmodel == WEAP_C4) { if (mode & WN_VALL) unicastSound(ent, gi.soundindex("voice/u_c4.wav"), 1.0); //r1,CW } else if (ent->client->pers.weapon->weapmodel == WEAP_TRAP) { if (mode & WN_VALL) unicastSound(ent, gi.soundindex("voice/u_traps.wav"), 1.0); //r1,CW } else if (ent->client->pers.weapon->weapmodel == WEAP_ESG) { if (ent->client->multi_spike) { if (mode & WN_TEXT) gi_centerprintf(ent, "Multi-spikes\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_multi.wav"), 1.0); //r1,CW } else { if (mode & WN_TEXT) gi_centerprintf(ent, "Single spikes\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_spike.wav"), 1.0); //r1,CW } gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/esg/use.wav"), 1, ATTN_NORM, 0); } else if (ent->client->pers.weapon->weapmodel == WEAP_FLAMETHROWER) { if (ent->client->ft_firebomb) { if (ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] < FT_CELLS_PER_SHOT) { gi_centerprintf(ent, "Not enough Cells ...\nSwitching to Standard mode\n"); ent->client->ft_firebomb = false; } } if (ent->client->ft_firebomb) { if (mode & WN_TEXT) gi_centerprintf(ent, "Firebombs\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_firebm.wav"), 1.0); //r1,CW } else { if (mode & WN_TEXT) gi_centerprintf(ent, "Standard Flamethrower\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_flames.wav"), 1.0); //r1,CW } gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/flamer/use.wav"), 1, ATTN_NORM, 0); } else if (ent->client->pers.weapon->weapmodel == WEAP_ROCKETLAUNCHER) { if (ent->client->normal_rockets) { if (mode & WN_TEXT) gi_centerprintf(ent, "Normal Rockets\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_nrockt.wav"), 1.0); //r1,CW } else { if (mode & WN_TEXT) gi_centerprintf(ent, "Guided Rockets\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_grockt.wav"), 1.0); //r1,CW } } else if (ent->client->pers.weapon->weapmodel == WEAP_RAILGUN) { if (mode & WN_VALL) unicastSound(ent, gi.soundindex("voice/u_rail.wav"), 1.0); //r1,CW } else if (ent->client->pers.weapon->weapmodel == WEAP_SHOCKRIFLE) { if (ent->client->homing_plasma) { if (mode & WN_TEXT) gi_centerprintf(ent, "Homing Plasma\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_homin.wav"), 1.0); //r1,CW } else { if (mode & WN_TEXT) gi_centerprintf(ent, "Disintegrator\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_shock.wav"), 1.0); //r1,CW } } else if (ent->client->pers.weapon->weapmodel == WEAP_AGM) { ent->client->agm_charge = 0; ent->client->agm_showcharge = false; ent->client->agm_tripped = false; if (ent->client->agm_disrupt) { if (mode & WN_TEXT) gi_centerprintf(ent, "Cellular Disruption\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_cd.wav"), 1.0); //r1,CW } else { if (mode & WN_TEXT) gi_centerprintf(ent, "AG Manipulation\n"); if (mode & WN_VSEC) unicastSound(ent, gi.soundindex("voice/u_agm.wav"), 1.0); //r1,CW } } else if (ent->client->pers.weapon->weapmodel == WEAP_DISCLAUNCHER) { if (mode & WN_VALL) unicastSound(ent, gi.soundindex("voice/u_disc.wav"), 1.0); //r1,CW } else if (ent->client->pers.weapon->weapmodel == WEAP_CHAINSAW) { if (mode & WN_VALL) unicastSound(ent, gi.soundindex("voice/u_chnsaw.wav"), 1.0); //r1,CW gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/chainsaw/use.wav"), 1, ATTN_NORM, 0); } else if (ent->client->pers.weapon->weapmodel == WEAP_GRAPPLE) { if (mode & WN_VALL) unicastSound(ent, gi.soundindex("voice/u_grappl.wav"), 1.0); //r1,CW } //CW-- } /* ================= NoAmmoWeaponChange ================= */ void NoAmmoWeaponChange(edict_t *ent) { //CW++ if (!ent->client) { gi.dprintf("BUG: NoAmmoWeaponChange() called for non-client edict.\nPlease contact musashi@planetquake.com\n"); return; } if (ent->health < 1) { gi.dprintf("BUG: NoAmmoWeaponChange() called for a dead player.\nPlease contact musashi@planetquake.com\n"); return; } if ((ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] >= SR_CELLS_PER_SHOT) && ent->client->pers.inventory[ITEM_INDEX(FindItem("Shock Rifle"))]) { ent->client->newweapon = FindItem("Shock Rifle"); return; } //CW-- if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Slugs"))] && ent->client->pers.inventory[ITEM_INDEX(FindItem("Railgun"))]) { ent->client->newweapon = FindItem("Railgun"); return; } //CW++ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] && ent->client->pers.inventory[ITEM_INDEX(FindItem("Gauss Pistol"))]) { ent->client->newweapon = FindItem("Gauss Pistol"); return; } if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Rockets"))]) { if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Rocket Launcher"))]) { ent->client->newweapon = FindItem("Rocket Launcher"); return; } else if (ent->client->pers.inventory[ITEM_INDEX(FindItem("E.S.G."))]) { ent->client->newweapon = FindItem("E.S.G."); return; } else if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Disc Launcher"))]) { ent->client->newweapon = FindItem("Disc Launcher"); return; } } if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] && ent->client->pers.inventory[ITEM_INDEX(FindItem("Flamethrower"))]) { ent->client->newweapon = FindItem("Flamethrower"); return; } if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))] && ent->client->pers.inventory[ITEM_INDEX(FindItem("Mac-10"))]) { ent->client->newweapon = FindItem("Mac-10"); return; } if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Shells"))] && ent->client->pers.inventory[ITEM_INDEX(FindItem("Jackhammer"))]) { ent->client->newweapon = FindItem("Jackhammer"); return; } if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))] && ent->client->pers.inventory[ITEM_INDEX(FindItem("Desert Eagle"))]) { ent->client->newweapon = FindItem("Desert Eagle"); return; } if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] && ent->client->pers.inventory[ITEM_INDEX(FindItem("AG Manipulator"))]) { ent->client->newweapon = FindItem("AG Manipulator"); return; } if (ent->client->pers.inventory[ITEM_INDEX(FindItem("C4"))]) { ent->client->newweapon = FindItem("C4"); return; } if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Traps"))]) { ent->client->newweapon = FindItem("Traps"); return; } ent->client->newweapon = FindItem("Chainsaw"); //CW-- } /* ================= Think_Weapon Called by ClientBeginServerFrame and ClientThink ================= */ void Think_Weapon(edict_t *ent) { // if just died, put the weapon away if (ent->health < 1) { ent->client->newweapon = NULL; ChangeWeapon(ent); } //DH++ if (ent->flags & FL_TURRET_OWNER) { if (((ent->client->latched_buttons | ent->client->buttons) & BUTTON_ATTACK)) { ent->client->latched_buttons &= ~BUTTON_ATTACK; turret_breach_fire(ent->turret); } return; } //DH-- // call active weapon think routine if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink) { is_quad = (ent->client->quad_framenum > level.framenum); if (ent->client->silencer_shots) is_silenced = MZ_SILENCED; else is_silenced = 0; ent->client->pers.weapon->weaponthink(ent); } } /* ================ Use_Weapon Make the weapon ready if there is ammo ================ */ void Use_Weapon(edict_t *ent, gitem_t *item) { gitem_t *ammo_item; int ammo_index; // see if we're already using it if (item == ent->client->pers.weapon) return; //CW++ if (item->weapmodel == WEAP_GRAPPLE) { if (!(int)sv_allow_hook->value) return; if ((int)sv_hook_offhand->value) { gi_centerprintf(ent, "Cannot switch to off-hand grapple!\n"); return; } if (level.nohook) { gi_centerprintf(ent, "The Grapple is disabled for this level.\n"); return; } } //CW-- if (item->ammo && !((int)g_select_empty->value) && !(item->flags & IT_AMMO)) { ammo_item = FindItem(item->ammo); ammo_index = ITEM_INDEX(ammo_item); if (!ent->client->pers.inventory[ammo_index]) { gi_cprintf(ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name); return; } if (ent->client->pers.inventory[ammo_index] < item->quantity) { gi_cprintf(ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name); return; } } // change to this weapon when down ent->client->newweapon = item; } /* ================ Drop_Weapon ================ */ void Drop_Weapon(edict_t *ent, gitem_t *item) { int index; if ((int)(dmflags->value) & DF_WEAPONS_STAY) return; index = ITEM_INDEX(item); // see if we're already using it if (((item == ent->client->pers.weapon) || (item == ent->client->newweapon)) && (ent->client->pers.inventory[index] == 1)) { gi_cprintf(ent, PRINT_HIGH, "Can't drop current weapon\n"); return; } Drop_Item(ent, item); ent->client->pers.inventory[index]--; } /* ================ Weapon_Generic A generic function to handle the basics of weapon thinking ================ */ #define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1) #define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1) #define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1) static void Weapon_Generic2(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) { int n; if (ent->deadflag || (ent->s.modelindex != (MAX_MODELS-1))) // VWep animations screw up corpses return; if (ent->client->weaponstate == WEAPON_DROPPING) { if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) { ChangeWeapon(ent); return; } else if ((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4) { ent->client->anim_priority = ANIM_REVERSE; if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) { ent->s.frame = FRAME_crpain4+1; ent->client->anim_end = FRAME_crpain1; } else { ent->s.frame = FRAME_pain304+1; ent->client->anim_end = FRAME_pain301; } } ent->client->ps.gunframe++; return; } if (ent->client->weaponstate == WEAPON_ACTIVATING) { //CW++ if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST || ((int)dmflags->value & DF_FAST_SWITCH)) //CW-- { ent->client->weaponstate = WEAPON_READY; ent->client->ps.gunframe = FRAME_IDLE_FIRST; return; } ent->client->ps.gunframe++; return; } if (ent->client->newweapon && (ent->client->weaponstate != WEAPON_FIRING)) { ent->client->weaponstate = WEAPON_DROPPING; //CW++ if ((int)dmflags->value & DF_FAST_SWITCH) //CW-- { if (ent->client->pers.weapon->weapmodel == WEAP_CHAINSAW) gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/chainsaw/done.wav"), 1, ATTN_NORM, 0); ChangeWeapon(ent); return; } else ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; if ((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4) { ent->client->anim_priority = ANIM_REVERSE; if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) { ent->s.frame = FRAME_crpain4+1; ent->client->anim_end = FRAME_crpain1; } else { ent->s.frame = FRAME_pain304+1; ent->client->anim_end = FRAME_pain301; } } return; } if (ent->client->weaponstate == WEAPON_READY) { if (((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK)) { ent->client->latched_buttons &= ~BUTTON_ATTACK; if ((!ent->client->ammo_index) || (ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity)) { ent->client->ps.gunframe = FRAME_FIRE_FIRST; ent->client->weaponstate = WEAPON_FIRING; // start the animation ent->client->anim_priority = ANIM_ATTACK; if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) { ent->s.frame = FRAME_crattak1-1; ent->client->anim_end = FRAME_crattak9; } else { ent->s.frame = FRAME_attack1-1; ent->client->anim_end = FRAME_attack8; } } else { if (level.time >= ent->pain_debounce_time) { gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); ent->pain_debounce_time = level.time + 1.0; } NoAmmoWeaponChange(ent); } } else { if (ent->client->ps.gunframe == FRAME_IDLE_LAST) { ent->client->ps.gunframe = FRAME_IDLE_FIRST; return; } if (pause_frames) { for (n = 0; pause_frames[n]; n++) { if (ent->client->ps.gunframe == pause_frames[n]) { if (rand()&15) return; } } } ent->client->ps.gunframe++; return; } } if (ent->client->weaponstate == WEAPON_FIRING) { for (n = 0; fire_frames[n]; n++) { if (ent->client->ps.gunframe == fire_frames[n]) { //ZOID++ if (!CTFApplyStrengthSound(ent)) { //ZOID-- if (ent->client->quad_framenum > level.framenum) { //CW++ if ((ent->client->pers.weapon->weapmodel == WEAP_AGM) && !ent->client->agm_disrupt) ; else //CW-- gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); } } //ZOID++ CTFApplyHasteSound(ent); //ZOID-- fire(ent); break; } } //CW++ // Reload the Desert Eagle if required (a clip holds 9 bullets). if (ent->client->pers.weapon->weapmodel == WEAP_DESERTEAGLE) { if ((ent->client->machinegun_shots > 8) && (ent->client->ps.gunframe == 8)) { if (level.time >= ent->pain_debounce_time) ent->pain_debounce_time = level.time + 1.0; ent->client->machinegun_shots = 0; if (ent->client->pers.inventory[ent->client->ammo_index] < 1) NoAmmoWeaponChange(ent); else ent->client->newweapon = FindItem("Desert Eagle"); } } // Reload the Jackhammer if required (a clip holds 10 shells). else if (ent->client->pers.weapon->weapmodel == WEAP_JACKHAMMER) { if ((ent->client->machinegun_shots > 9) && (ent->client->ps.gunframe == 5)) { if (level.time >= ent->pain_debounce_time) ent->pain_debounce_time = level.time + 1.0; ent->client->machinegun_shots = 0; if (ent->client->pers.inventory[ent->client->ammo_index] < 1) NoAmmoWeaponChange(ent); else ent->client->newweapon = FindItem("Jackhammer"); } } // Reload the Mac-10 if required (a clip holds 32 bullets). else if (ent->client->pers.weapon->weapmodel == WEAP_MAC10) { if ((ent->client->machinegun_shots > 15) && (ent->client->ps.gunframe == 3)) { ent->client->ps.gunframe = 5; if (level.time >= ent->pain_debounce_time) ent->pain_debounce_time = level.time + 1.0; ent->client->machinegun_shots = 0; if (ent->client->pers.inventory[ent->client->ammo_index] < 1) NoAmmoWeaponChange(ent); else ent->client->newweapon = FindItem("Mac-10"); } } //CW-- if (!fire_frames[n]) ent->client->ps.gunframe++; if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1) ent->client->weaponstate = WEAPON_READY; } } //ZOID++ void Weapon_Generic(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) { int oldstate = ent->client->weaponstate; if (ent->client->frozen_framenum > level.framenum) return; Weapon_Generic2(ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, fire_frames, fire); // Run the weapon frame again if hasted, unless the weapon is the Grapple or AGM. if ((ent->client->pers.weapon->weapmodel == WEAP_GRAPPLE) && (ent->client->weaponstate == WEAPON_FIRING)) return; //CW++ if ((ent->client->pers.weapon->weapmodel == WEAP_AGM) && (ent->client->weaponstate == WEAPON_FIRING)) return; //CW-- // If the player has it/them, apply the Haste Tech effect... if ((CTFApplyHaste(ent) || ((ent->client->pers.weapon->weapmodel == WEAP_GRAPPLE) && (ent->client->weaponstate != WEAPON_FIRING))) && (oldstate == ent->client->weaponstate)) { Weapon_Generic2(ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, fire_frames, fire); } //CW++ // ... or the Haste powerup effect (not both together). else if ((ent->client->haste_framenum > level.framenum) && (oldstate == ent->client->weaponstate)) Weapon_Generic2(ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, fire_frames, fire); //CW-- } //ZOID-- /* ====================================================================== ROCKET LAUNCHER ====================================================================== */ void Weapon_RocketLauncher_Fire(edict_t *self) { vec3_t offset; vec3_t start; vec3_t forward; vec3_t right; int damage; int radius_damage; //CW++ qboolean guided; //CW-- // Set damage values. damage = (int)(sv_rocket_damage->value + (random() * 20.0)); //CW radius_damage = (int)sv_rocket_radius_damage->value; //CW if (is_quad) { damage *= (int)sv_quad_factor->value; //CW radius_damage *= (int)sv_quad_factor->value; //CW } // Set projectile start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); VectorScale(forward, -2.0, self->client->kick_origin); self->client->kick_angles[0] = -1.0; VectorSet(offset, 8.0, 8.0, self->viewheight-8.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); // Fire! //CW++ guided = (self->client->normal_rockets)?false:true; //CW-- Fire_Rocket(self, start, forward, damage, sv_rocket_speed->value, sv_rocket_radius->value, radius_damage, guided); //CW gi.WriteByte(svc_muzzleflash); gi.WriteShort(self-g_edicts); gi.WriteByte(MZ_ROCKET | is_silenced); gi.multicast(self->s.origin, MULTICAST_PVS); self->client->ps.gunframe++; PlayerNoise(self, start, PNOISE_WEAPON); //CW if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; } void Weapon_RocketLauncher(edict_t *self) { static int pause_frames[] = {25, 33, 42, 50, 0}; static int fire_frames[] = {5, 0}; Weapon_Generic(self, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire); } /* ====================================================================== RAILGUN ====================================================================== */ void Weapon_Railgun_Fire(edict_t *self) { vec3_t start; vec3_t forward; vec3_t right; vec3_t offset; int damage; int kick = 200; // Set damage and kick values. damage = (int)sv_railgun_damage->value; //CW if (is_quad) { damage *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } // Set projectile start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); VectorScale(forward, -3.0, self->client->kick_origin); self->client->kick_angles[0] = -3.0; VectorSet(offset, 0.0, 7.0, self->viewheight-8.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); // Fire! Fire_Rail(self, start, forward, damage, kick); gi.WriteByte(svc_muzzleflash); gi.WriteShort(self-g_edicts); gi.WriteByte(MZ_RAILGUN | is_silenced); gi.multicast(self->s.origin, MULTICAST_PVS); self->client->ps.gunframe++; PlayerNoise(self, start, PNOISE_WEAPON); if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; } void Weapon_Railgun(edict_t *self) { static int pause_frames[] = {56, 0}; static int fire_frames[] = {4, 0}; Weapon_Generic(self, 3, 18, 56, 61, pause_frames, fire_frames, Weapon_Railgun_Fire); } //CW++ // Awakening weapons. /* ====================================================================== DESERT EAGLE ====================================================================== */ void Weapon_DesertEagle_Fire(edict_t *self) { trace_t tr; vec3_t start; vec3_t forward; vec3_t right; vec3_t offset; vec3_t t_start; int damage; int kick = 2; // Set damage and kick values. There is a 20% chance of doing extra damage. damage = (int)sv_deserteagle_damage->value + ((random()<0.20)?15:0); if (is_quad) { damage *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } // Set projectile start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); VectorSet(offset, 0.0, 0.0, self->viewheight); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); VectorScale(forward, -2.0, self->client->kick_origin); self->client->kick_angles[0] = -1.0; // Fire! Fire_Bullet(self, start, forward, damage, kick, (int)sv_deserteagle_hspread->value, (int)sv_deserteagle_vspread->value, MOD_DESERTEAGLE); gi.WriteByte(svc_muzzleflash); gi.WriteShort(self-g_edicts); gi.WriteByte(MZ_BLASTER | is_silenced); gi.multicast(self->s.origin, MULTICAST_PVS); self->client->ps.gunframe++; self->client->machinegun_shots++; PlayerNoise(self, start, PNOISE_WEAPON); if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; // Spawn a tracer sometimes (50% chance, if the path is clear). VectorMA(start, 200.0, forward, t_start); tr = gi.trace(start, NULL, NULL, t_start, self, MASK_SHOT); if ((tr.fraction == 1.0) && (random() < 0.5)) Fire_Tracer(self, t_start, forward, 2000.0, 0.2); } void Weapon_DesertEagle(edict_t *self) { static int pause_frames[] = {19, 32, 0}; static int fire_frames[] = {6, 0}; Weapon_Generic(self, 5, 8, 52, 55, pause_frames, fire_frames, Weapon_DesertEagle_Fire); } /* ====================================================================== JACKHAMMER ====================================================================== */ void Weapon_Jackhammer_Fire(edict_t *self) { vec3_t start; vec3_t forward; vec3_t right; vec3_t offset; int damage; int kick = 8; // Set damage and kick values. damage = (int)sv_jackhammer_damage->value; if (is_quad) { damage *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } // Set projectile start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); VectorSet(offset, 0.0, 8.0, self->viewheight-8.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); VectorScale(forward, -2.0, self->client->kick_origin); self->client->kick_angles[0] = -2.0; // Fire! Fire_Shotgun(self, start, forward, damage, kick, (int)sv_jackhammer_hspread->value, (int)sv_jackhammer_vspread->value, DEFAULT_JACKHAMMER_COUNT, MOD_JACKHAMMER); gi.WriteByte(svc_muzzleflash); gi.WriteShort(self-g_edicts); gi.WriteByte(MZ_SSHOTGUN | is_silenced); gi.multicast(self->s.origin, MULTICAST_PVS); self->client->ps.gunframe++; self->client->machinegun_shots++; PlayerNoise(self, start, PNOISE_WEAPON); if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; } void Weapon_Jackhammer(edict_t *self) { static int pause_frames[] = {45, 0}; static int fire_frames[] = {4, 0}; Weapon_Generic(self, 3, 5, 45, 49, pause_frames, fire_frames, Weapon_Jackhammer_Fire); } /* ====================================================================== MAC-10 ====================================================================== */ void Weapon_Mac10_Fire(edict_t *self) { vec3_t start; vec3_t forward; vec3_t right; vec3_t offset; vec3_t t_start; trace_t tr; int damage; int kick = 2; int i; // Set damage and kick values. damage = (int)sv_mac10_damage->value; if (is_quad) { damage *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } // Set projectile start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); VectorSet(offset, 0.0, 0.0, self->viewheight); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); VectorScale(forward, -2.0, self->client->kick_origin); self->client->kick_angles[0] = -1.0; for (i = 1; i < 3; ++i) self->client->kick_angles[i] = (random() < 0.5)?-1.0:1.0; // Fire! (two bullets per trigger press). for (i = 0; i < 2; ++i) { Fire_Bullet(self, start, forward, damage, kick, (int)sv_mac10_hspread->value, (int)sv_mac10_vspread->value, MOD_MAC10); if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; // Check if there's enough ammo; swap weapons if we're dry. if (self->client->pers.inventory[self->client->ammo_index] < 1) { self->client->ps.gunframe = 5; if (level.time >= self->pain_debounce_time) { gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); self->pain_debounce_time = level.time + 1.0; } self->client->machinegun_shots = 0; NoAmmoWeaponChange(self); return; } } self->client->ps.gunframe++; self->client->machinegun_shots++; // Spawn a tracer every 4 shots. if ((self->client->machinegun_shots % 4) == 0) { VectorSet(offset, 24.0, 7.0, self->viewheight-6.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); VectorMA(start, 300.0, forward, t_start); tr = gi.trace(start, NULL, NULL, t_start, self, MASK_SHOT); if (tr.fraction == 1.0) Fire_Tracer(self, t_start, forward, 2000.0, 0.3); } // Loop the animation sequence if the firing button is still being pressed. if ((self->client->ps.gunframe == 5) && (self->client->buttons & BUTTON_ATTACK)) self->client->ps.gunframe = 3; // Do the muzzleflash and sound. gi.WriteByte(svc_muzzleflash); gi.WriteShort(self-g_edicts); gi.WriteByte(MZ_MACHINEGUN | is_silenced); gi.multicast(self->s.origin, MULTICAST_PVS); PlayerNoise(self, start, PNOISE_WEAPON); } void Weapon_Mac10(edict_t *self) { static int pause_frames[] = {29, 45, 0}; static int fire_frames[] = {3, 4, 0}; Weapon_Generic(self, 2, 4, 45, 49, pause_frames, fire_frames, Weapon_Mac10_Fire); } /* ====================================================================== C4 ====================================================================== */ void Weapon_C4_Fire(edict_t *self, qboolean held) { vec3_t offset; vec3_t forward; vec3_t right; vec3_t start; float timer; float radius; int damage; int speed; // Set damage values. damage = (int)sv_c4_damage->value; radius = sv_c4_radius->value; if (is_quad) damage *= (int)sv_quad_factor->value; // Set projectile start position. VectorSet(offset, 8.0, 8.0, self->viewheight); AngleVectors(self->client->v_angle, forward, right, NULL); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); // Determine throwing speed. timer = level.time - self->client->grenade_time; if (CTFApplyHaste(self) || (self->client->haste_framenum > level.framenum)) { timer *= 2.0; CTFApplyHasteSound(self); } speed = (int)(min(sv_c4_min_speed->value + (timer * sv_c4_hold_accel->value), sv_c4_max_speed->value)); // Fire! Fire_C4(self, start, forward, damage, speed, radius, held); gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; // Handle player model throwing animation. if (self->deadflag || (self->s.modelindex != (MAX_MODELS-1))) // VWep animations screw up corpses return; if (self->client->ps.pmove.pm_flags & PMF_DUCKED) { self->client->anim_priority = ANIM_ATTACK; self->s.frame = FRAME_crattak1-1; self->client->anim_end = FRAME_crattak3; } else { self->client->anim_priority = ANIM_REVERSE; self->s.frame = FRAME_wave08; self->client->anim_end = FRAME_wave01; } } void Weapon_C4(edict_t *self) { int remaining; if ((self->client->newweapon) && (self->client->weaponstate == WEAPON_READY)) { ChangeWeapon(self); return; } if (self->client->weaponstate == WEAPON_ACTIVATING) { self->client->weaponstate = WEAPON_READY; self->client->ps.gunframe = 16; return; } if (self->client->weaponstate == WEAPON_READY) { if (((self->client->latched_buttons | self->client->buttons) & BUTTON_ATTACK)) { self->client->latched_buttons &= ~BUTTON_ATTACK; if (self->client->pers.inventory[self->client->ammo_index]) { self->client->ps.gunframe = 1; self->client->weaponstate = WEAPON_FIRING; self->client->grenade_time = 0.0; self->client->c4_boom_time = 0.0; } else { if (level.time >= self->pain_debounce_time) { gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); self->pain_debounce_time = level.time + 1.0; } NoAmmoWeaponChange(self); } return; } if ((self->client->ps.gunframe == 29) || (self->client->ps.gunframe == 34) || (self->client->ps.gunframe == 39) || (self->client->ps.gunframe == 48)) { if (rand() & 15) return; } if (++self->client->ps.gunframe > 48) self->client->ps.gunframe = 16; return; } if (self->client->weaponstate == WEAPON_FIRING) { if (self->client->ps.gunframe == 5) gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/c4/arm.wav"), 1, ATTN_NORM, 0); if (self->client->ps.gunframe == 11) { if (!self->client->grenade_time) self->client->grenade_time = level.time; if (sv_c4_timelimit->value > 0.0) // auto-detonation 2-second warning sound { if (!self->client->c4_boom_time) self->client->c4_boom_time = level.time + sv_c4_timelimit->value; remaining = (int)(self->client->c4_boom_time - level.time); if ((remaining == 1) || (remaining == 2)) self->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav"); } // If player has waited too long, detonate it in their hand. if (!self->client->grenade_blew_up && (sv_c4_timelimit->value > 0.0) && (level.time >= self->client->c4_boom_time)) { self->client->weapon_sound = 0; Weapon_C4_Fire(self, true); self->client->grenade_blew_up = true; } if (self->client->buttons & BUTTON_ATTACK) return; if (self->client->grenade_blew_up) { if (level.time >= self->client->c4_boom_time) { self->client->ps.gunframe = 15; self->client->grenade_blew_up = false; } else return; } } if (self->client->ps.gunframe == 12) { self->client->weapon_sound = 0; Weapon_C4_Fire(self, false); } self->client->ps.gunframe++; if (self->client->ps.gunframe == 16) { self->client->grenade_time = 0.0; self->client->weaponstate = WEAPON_READY; } } } /* ====================================================================== TRAPS ====================================================================== */ void Weapon_Trap_Fire(edict_t *self, qboolean held) { vec3_t offset; vec3_t forward; vec3_t right; vec3_t start; float timer; int damage_hook; int damage_beam; int speed; // Set damage values. damage_hook = (int)sv_trap_hook_damage->value; damage_beam = (int)sv_trap_beam_damage->value; if (is_quad) { damage_hook *= (int)sv_quad_factor->value; damage_beam *= (int)sv_quad_factor->value; } // Set projectile start position. VectorSet(offset, 8.0, 8.0, self->viewheight); AngleVectors(self->client->v_angle, forward, right, NULL); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); // Determine throwing speed. timer = level.time - self->client->grenade_time; if (CTFApplyHaste(self) || (self->client->haste_framenum > level.framenum)) { timer *= 2.0; CTFApplyHasteSound(self); } speed = (int)(min(sv_trap_min_speed->value + (timer * sv_trap_hold_accel->value), sv_trap_max_speed->value)); // Fire! Fire_Trap(self, start, forward, damage_hook, speed, damage_beam, (int)sv_trap_beam_power->value, held); gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; // Handle player model throwing animation. if (self->deadflag || (self->s.modelindex != (MAX_MODELS-1))) // VWep animations screw up corpses return; if (self->client->ps.pmove.pm_flags & PMF_DUCKED) { self->client->anim_priority = ANIM_ATTACK; self->s.frame = FRAME_crattak1-1; self->client->anim_end = FRAME_crattak3; } else { self->client->anim_priority = ANIM_REVERSE; self->s.frame = FRAME_wave08; self->client->anim_end = FRAME_wave01; } } void Weapon_Trap(edict_t *self) { int remaining; if ((self->client->newweapon) && (self->client->weaponstate == WEAPON_READY)) { ChangeWeapon(self); return; } if (self->client->weaponstate == WEAPON_ACTIVATING) { self->client->weaponstate = WEAPON_READY; self->client->ps.gunframe = 16; return; } if (self->client->weaponstate == WEAPON_READY) { if (((self->client->latched_buttons | self->client->buttons) & BUTTON_ATTACK)) { self->client->latched_buttons &= ~BUTTON_ATTACK; if (self->client->pers.inventory[self->client->ammo_index]) { self->client->ps.gunframe = 1; self->client->weaponstate = WEAPON_FIRING; self->client->grenade_time = 0.0; self->client->c4_boom_time = 0.0; } else { if (level.time >= self->pain_debounce_time) { gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); self->pain_debounce_time = level.time + 1.0; } NoAmmoWeaponChange(self); } return; } if ((self->client->ps.gunframe == 29) || (self->client->ps.gunframe == 34) || (self->client->ps.gunframe == 39) || (self->client->ps.gunframe == 48)) { if (rand() & 15) return; } if (++self->client->ps.gunframe > 48) self->client->ps.gunframe = 16; return; } if (self->client->weaponstate == WEAPON_FIRING) { if (self->client->ps.gunframe == 5) gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/trap/arm.wav"), 1, ATTN_NORM, 0); if (self->client->ps.gunframe == 11) { if (!self->client->grenade_time) self->client->grenade_time = level.time; if (sv_traps_timelimit->value > 0.0) // auto-trapping 2-second warning sound { if (!self->client->c4_boom_time) self->client->c4_boom_time = level.time + sv_traps_timelimit->value; remaining = (int)(self->client->c4_boom_time - level.time); if ((remaining == 1) || (remaining == 2)) self->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav"); } // If player has waited too long, set the Trap off in their hand. if (!self->client->grenade_blew_up && (sv_traps_timelimit->value > 0.0) && (level.time >= self->client->c4_boom_time)) { self->client->weapon_sound = 0; Weapon_Trap_Fire(self, true); self->client->grenade_blew_up = true; } if (self->client->buttons & BUTTON_ATTACK) return; if (self->client->grenade_blew_up) { if (level.time >= self->client->c4_boom_time) { self->client->ps.gunframe = 15; self->client->grenade_blew_up = false; } else return; } } if (self->client->ps.gunframe == 12) { self->client->weapon_sound = 0; Weapon_Trap_Fire(self, false); } self->client->ps.gunframe++; if (self->client->ps.gunframe == 16) { self->client->grenade_time = 0.0; self->client->weaponstate = WEAPON_READY; } } } /* ====================================================================== EXPLOSIVE SPIKE GUN ====================================================================== */ void Weapon_ESG_Fire(edict_t *self) { vec3_t start; vec3_t start_p; vec3_t forward; vec3_t right; vec3_t offset; vec3_t dir; vec3_t end; trace_t tr; int damage; int radius_damage; int kick = 2; // Fire up to 3 spikes quickly if the firing button is pressed, then reload (via viewmodel animation). if (self->client->ps.gunframe == 14) { if ((self->client->buttons & BUTTON_ATTACK) && (self->client->machinegun_shots < 3)) self->client->ps.gunframe = 11; else { gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/esg/reload.wav"), 1, ATTN_NORM, 0); self->client->machinegun_shots = 0; self->client->ps.gunframe++; return; } } if (self->client->ps.gunframe == 24) { if (self->client->buttons & BUTTON_ATTACK) self->client->ps.gunframe = 11; else { self->client->ps.gunframe++; return; } } // Set damage and kick values. damage = (int)sv_spike_damage->value; radius_damage = (int)sv_spike_bang_damage->value; if (is_quad) { damage *= (int)sv_quad_factor->value; radius_damage *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } // Set projectile start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); VectorSet(offset, 8.0, 8.0, self->viewheight-8.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); VectorScale(forward, -2.0, self->client->kick_origin); self->client->kick_angles[0] = -2.0; // Set projectile end position and flight direction. AngleVectors(self->client->v_angle, forward, right, NULL); VectorMA(self->s.origin, WORLD_SIZE, forward, end); // was 8192.0 VectorSet(offset, 0.0, 0.0, self->viewheight-2.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start_p); tr = gi.trace(start_p, NULL, NULL, end, self, MASK_SHOT); VectorSubtract(tr.endpos, start, dir); VectorNormalize(dir); // Fire! Fire_Spike(self, start, dir, damage, sv_spike_speed->value, kick, sv_spike_bang_radius->value, radius_damage); gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/esg/fire.wav"), (is_silenced)?0.2:1, ATTN_NORM, 0); PlayerNoise(self, start, PNOISE_WEAPON); self->client->ps.gunframe++; self->client->machinegun_shots++; if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; // Check if there's enough ammo; swap weapons if we're dry. if (self->client->pers.inventory[self->client->ammo_index] < 1) { self->client->ps.gunframe = 29; if (level.time >= self->pain_debounce_time) { gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); self->pain_debounce_time = level.time + 1.0; } self->client->machinegun_shots = 0; NoAmmoWeaponChange(self); } } void Fire_MultiSpikes(edict_t *self, vec3_t start, vec3_t aimdir, int damage, float speed, int kick, float damage_radius, int radius_damage, int count) { vec3_t dir; vec3_t newdir; vec3_t end; vec3_t forward; vec3_t right; int i; switch (count) { case 0: case 1: Fire_Spike(self, start, aimdir, damage, speed, kick, damage_radius, radius_damage); break; case 2: vectoangles(aimdir, dir); AngleVectors(dir, forward, right, NULL); for (i = -1; i < 2; i += 2) { VectorMA(start, WORLD_SIZE, forward, end); // was 8192.0 VectorMA(end, i * SPIKE_SPREAD_HALF, right, end); VectorSubtract(end, start, newdir); VectorNormalize(newdir); Fire_Spike(self, start, newdir, damage, speed, kick, damage_radius, radius_damage); } break; case 3: vectoangles(aimdir, dir); AngleVectors(dir, forward, right, NULL); for (i = -1; i < 2; i++) { VectorMA(start, WORLD_SIZE, forward, end); // was 8192.0 VectorMA(end, i * SPIKE_SPREAD, right, end); VectorSubtract(end, start, newdir); VectorNormalize(newdir); Fire_Spike(self, start, newdir, damage, speed, kick, damage_radius, radius_damage); } break; case 4: vectoangles(aimdir, dir); AngleVectors(dir, forward, right, NULL); for (i = -3; i < 4; i += 2) { VectorMA(start, WORLD_SIZE, forward, end); // was 8192.0 VectorMA(end, i * SPIKE_SPREAD_HALF, right, end); VectorSubtract(end, start, newdir); VectorNormalize(newdir); Fire_Spike(self, start, newdir, damage, speed, kick, damage_radius, radius_damage); } break; case 5: vectoangles(aimdir, dir); AngleVectors(dir, forward, right, NULL); for (i = -2; i < 3; i++) { VectorMA(start, WORLD_SIZE, forward, end); // was 8192.0 VectorMA(end, i * SPIKE_SPREAD, right, end); VectorSubtract(end, start, newdir); VectorNormalize(newdir); Fire_Spike(self, start, newdir, damage, speed, kick, damage_radius, radius_damage); } break; case 6: vectoangles(aimdir, dir); AngleVectors(dir, forward, right, NULL); for (i = -5; i < 6; i += 2) { VectorMA(start, WORLD_SIZE, forward, end); // was 8192.0 VectorMA(end, i * SPIKE_SPREAD_HALF, right, end); VectorSubtract(end, start, newdir); VectorNormalize(newdir); Fire_Spike(self, start, newdir, damage, speed, kick, damage_radius, radius_damage); } break; default: gi.dprintf("BUG: Fire_MultiSpikes() called for > 6 spikes.\nPlease contact musashi@planetquake.com\n"); return; } } void Weapon_ESG_MultiFire(edict_t *self) { vec3_t start; vec3_t forward; vec3_t right; vec3_t offset; int damage; int radius_damage; int kick = 2; // If we ran out of ammo during the previous function call, fire all the loaded spikes. if (self->client->pers.inventory[self->client->ammo_index] == 0) goto fire_now; // As long as the firing button is held down, keep building up the store of spikes. When there are // six spikes in the chamber, or the firing button has been released, or we're about to run out // of ammo, fire the spikes together. if (self->client->ps.gunframe == 11) // start of weapon kick animation { if (self->client->buttons & BUTTON_ATTACK) { if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; if (self->client->machinegun_shots == -1) // reset the "recently fired" flag self->client->machinegun_shots = 0; self->client->machinegun_shots++; if (self->client->machinegun_shots < 6) { self->client->ps.gunframe = 15; gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/esg/reload.wav"), 1, ATTN_NORM, 0); if (self->client->pers.inventory[self->client->ammo_index] >= 0) return; } } else { if (self->client->machinegun_shots == -1) // have fired recently, so don't fire another spike yet return; } } if (self->client->ps.gunframe == 14) // end of weapon kick animation { if (self->client->buttons & BUTTON_ATTACK) self->client->ps.gunframe = 11; else self->client->ps.gunframe = 24; return; } if (self->client->ps.gunframe == 22) // near end of reload animation { if ((self->client->buttons & BUTTON_ATTACK) && (self->client->machinegun_shots < 6)) { self->client->ps.gunframe = 11; return; } } // Set damage and kick values. fire_now: damage = (int)sv_spike_damage->value; radius_damage = (int)sv_spike_bang_damage->value; if (is_quad) { damage *= (int)sv_quad_factor->value; radius_damage *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } // Set projectile start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); VectorSet(offset, 8.0, 8.0, self->viewheight-8.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); VectorScale(forward, -2.0, self->client->kick_origin); self->client->kick_angles[0] = -2.0; // Fire! Fire_MultiSpikes(self, start, forward, damage, sv_spike_speed->value, kick, sv_spike_bang_radius->value, radius_damage, self->client->machinegun_shots); gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/esg/fire.wav"), (is_silenced)?0.2:1, ATTN_NORM, 0); PlayerNoise(self, start, PNOISE_WEAPON); self->client->ps.gunframe = 12; if (self->client->machinegun_shots == 0) // was a quick fire-button press, so ammo not depleted yet (see above) self->client->pers.inventory[self->client->ammo_index]--; self->client->machinegun_shots = -1; // use as a "recently fired" flag // Check if there's enough ammo; swap weapons if we're dry. if (self->client->pers.inventory[self->client->ammo_index] < 1) { self->client->ps.gunframe = 29; if (level.time >= self->pain_debounce_time) { gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); self->pain_debounce_time = level.time + 1.0; } self->client->machinegun_shots = 0; NoAmmoWeaponChange(self); } } void Weapon_ESG(edict_t *self) { static int pause_frames[] = {30, 40, 0}; static int fire_frames[] = {11, 14, 24, 0}; static int fire_frames_multi[] = {11, 14, 22, 0}; if (self->client->multi_spike) { if ((self->client->ps.gunframe == 28) && (self->client->machinegun_shots == -1)) self->client->machinegun_shots = 0; // reset the "recently fired" flag Weapon_Generic(self, 7, 28, 40, 44, pause_frames, fire_frames_multi, Weapon_ESG_MultiFire); if (!(self->client->buttons & BUTTON_ATTACK) && (self->client->machinegun_shots > 0)) Weapon_ESG_MultiFire(self); } else Weapon_Generic(self, 7, 28, 40, 44, pause_frames, fire_frames, Weapon_ESG_Fire); } /* ====================================================================== FLAMETHROWER ====================================================================== */ void Weapon_Flamethrower_Fire(edict_t *self) { vec3_t forward; vec3_t right; vec3_t start; vec3_t offset; int damage; int damage_minor; // Check if there's enough ammo; swap weapons if we're dry. if (self->client->pers.inventory[self->client->ammo_index] < 1) { self->client->ps.gunframe = 9; if (level.time >= self->pain_debounce_time) { gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); self->pain_debounce_time = level.time + 1.0; } NoAmmoWeaponChange(self); return; } // Set projectile/puff start position. AngleVectors(self->client->v_angle, forward, right, NULL); VectorSet(offset, 20.0, 8.0, self->viewheight-8.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); // Flamethrower doesn't work underwater. if (self->waterlevel < 2) { // Set damage value. damage = (int)sv_flame_damage->value; damage_minor = (int)sv_flame_small_damage->value; if (is_quad) { damage *= (int)sv_quad_factor->value; damage_minor *= (int)sv_quad_factor->value; } // Fire! if (self->client->ps.gunframe == 5) Fire_Fireball(self, start, forward, damage, damage_minor, sv_flame_speed->value, true); else Fire_Fireball(self, start, forward, damage, damage_minor, sv_flame_speed->value, false); if (self->client->ps.gunframe % 2) { gi.WriteByte(svc_muzzleflash); gi.WriteShort(self-g_edicts); gi.WriteByte(MZ_HYPERBLASTER | is_silenced); gi.multicast(self->s.origin, MULTICAST_PVS); PlayerNoise(self, start, PNOISE_WEAPON); } } else { VectorSet(forward, 0.0, 0.0, 1.0); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_HEATBEAM_SPARKS); gi.WritePosition(start); gi.WriteDir(forward); gi.multicast(start, MULTICAST_PVS); if (self->client->ps.gunframe == 5) gi.sound(self, CHAN_WEAPON, gi.soundindex("misc/lasfly.wav"), 1, ATTN_NORM, 0); } // Advance animation frame, and remove 0.1 second delay between 4-flame bursts when holding down the fire button. self->client->ps.gunframe++; if ((self->client->ps.gunframe == 9) && (self->client->buttons & BUTTON_ATTACK)) self->client->ps.gunframe = 5; // Spend ammo. if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; } void Weapon_Flamethrower_FB_Fire(edict_t *self) { vec3_t forward; vec3_t right; vec3_t start; vec3_t offset; int damage; int damage_minor; int kick = 2; // We want the rate-of-fire to be 1 shot per second. if (self->last_move_time > level.time) { self->client->ps.gunframe = 5; return; } // Check if there's enough ammo; swap weapons if we're dry. if (self->client->pers.inventory[self->client->ammo_index] < FT_CELLS_PER_SHOT) { self->client->ps.gunframe = 9; if (level.time >= self->pain_debounce_time) { gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); self->pain_debounce_time = level.time + 1.0; } NoAmmoWeaponChange(self); return; } // Set projectile/puff start position. AngleVectors(self->client->v_angle, forward, right, NULL); VectorSet(offset, 20.0, 8.0, self->viewheight-8.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); // Flamethrower doesn't work underwater. if (self->waterlevel < 2) { // Set damage value. damage = (int)sv_firebomb_damage->value; damage_minor = (int)sv_flame_damage->value; if (is_quad) { damage *= (int)sv_quad_factor->value; damage_minor *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } // Fire! Fire_Firebomb(self, start, forward, damage, damage_minor, kick, sv_firebomb_radius->value, sv_firebomb_speed->value); self->last_move_time = level.time + 1.0; gi.WriteByte(svc_muzzleflash); gi.WriteShort(self-g_edicts); gi.WriteByte(MZ_HYPERBLASTER | is_silenced); gi.multicast(self->s.origin, MULTICAST_PVS); PlayerNoise(self, start, PNOISE_WEAPON); } else { VectorSet(forward, 0.0, 0.0, 1.0); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_HEATBEAM_SPARKS); gi.WritePosition(start); gi.WriteDir(forward); gi.multicast(start, MULTICAST_PVS); if (self->client->ps.gunframe == 5) gi.sound(self, CHAN_WEAPON, gi.soundindex("misc/lasfly.wav"), 1, ATTN_NORM, 0); } self->client->ps.gunframe++; // Spend ammo. if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index] -= FT_CELLS_PER_SHOT; } void Weapon_Flamethrower(edict_t *self) { static int pause_frames[] = {49, 0}; static int fire_frames[] = {5, 6, 7, 8, 0}; static int fire_frames_fb[] = {5, 0}; if (self->client->ft_firebomb) Weapon_Generic(self, 4, 8, 49, 53, pause_frames, fire_frames_fb, Weapon_Flamethrower_FB_Fire); else Weapon_Generic(self, 4, 8, 49, 53, pause_frames, fire_frames, Weapon_Flamethrower_Fire); } /* ====================================================================== SHOCK RIFLE ====================================================================== */ void Weapon_ShockRifle_Fire(edict_t *self) { vec3_t start; vec3_t forward; vec3_t right; vec3_t offset; float speed; int damage_plasma; int damage_shockbolt; int kick = 2; // Check if there's enough ammo; swap weapons if we're dry. if (self->client->pers.inventory[self->client->ammo_index] < SR_CELLS_PER_SHOT) { self->client->ps.gunframe = 16; if (level.time >= self->pain_debounce_time) { gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); self->pain_debounce_time = level.time + 1.0; } NoAmmoWeaponChange(self); return; } // Set damage, kick and speed values. damage_plasma = (int)sv_shock_homing_damage->value; damage_shockbolt = (int)sv_shock_radius_damage->value; if (is_quad) { damage_plasma *= (int)sv_quad_factor->value; damage_shockbolt *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } speed = (self->client->homing_plasma)? sv_shock_homing_speed->value : sv_shock_speed->value; // Set projectile start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); VectorSet(offset, 8.0, 8.0, self->viewheight-8.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); VectorScale(forward, -2.0, self->client->kick_origin); self->client->kick_angles[0] = -1.0; // Fire! Fire_Shock(self, start, forward, damage_plasma, speed, kick, damage_shockbolt, sv_shock_radius->value, self->client->homing_plasma); gi.WriteByte(svc_muzzleflash); gi.WriteShort(self-g_edicts); gi.WriteByte(MZ_BFG | is_silenced); gi.multicast(self->s.origin, MULTICAST_PVS); self->client->ps.gunframe++; PlayerNoise(self, start, PNOISE_WEAPON); if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index] -= SR_CELLS_PER_SHOT; } void Weapon_ShockRifle(edict_t *self) { static int pause_frames[] = {18, 29, 39, 58, 0}; static int fire_frames[] = {4, 0}; Weapon_Generic(self, 3, 15, 58, 62, pause_frames, fire_frames, Weapon_ShockRifle_Fire); } /* ====================================================================== GAUSS PISTOL ====================================================================== */ void Weapon_GaussPistol_Fire(edict_t *self) { vec3_t start; vec3_t forward; vec3_t right; vec3_t offset; int damage; int kick = 2; qboolean insufficient_ammo = false; // Check if there's enough ammo; swap weapons if we're dry. if (self->client->gauss_particle) { if ((self->client->pers.inventory[ITEM_INDEX(FindItem ("slugs"))] < GP_SLUGS_PER_SHOT) || (self->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))] < GP_CELLS_PER_SHOT)) insufficient_ammo = true; } else { if (self->client->pers.inventory[self->client->ammo_index] < 1) insufficient_ammo = true; } if (insufficient_ammo) { self->client->ps.gunframe = 10; if (level.time >= self->pain_debounce_time) { gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); self->pain_debounce_time = level.time + 1.0; } NoAmmoWeaponChange(self); return; } // Set damage and kick values. damage = (self->client->gauss_particle)? (int)sv_gauss_damage_particle->value : self->client->gauss_dmg; if (is_quad) { damage *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } // Set projectile start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); if (self->client->gauss_particle) VectorSet(offset, 24.0, 8.0, self->viewheight-5.0); // NB: should be same as offset in ShowGaussTarget() else VectorSet(offset, 0.0, 0.0, self->viewheight-2.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); VectorScale(forward, -2.0, self->client->kick_origin); self->client->kick_angles[0] = -1.0; // Fire! if (self->client->gauss_particle) Fire_Particle(self, start, forward, damage, kick); else { Fire_Instabolt(self, start, forward, damage, kick); self->client->gauss_framenum = level.framenum; self->client->gauss_dmg = (int)sv_gauss_damage_base->value; } gi.WriteByte(svc_muzzleflash); gi.WriteShort(self-g_edicts); gi.WriteByte(MZ_SHOTGUN | is_silenced); gi.multicast(self->s.origin, MULTICAST_PVS); self->client->ps.gunframe++; PlayerNoise(self, start, PNOISE_WEAPON); if (!((int)dmflags->value & DF_INFINITE_AMMO)) { if (self->client->gauss_particle) { self->client->pers.inventory[self->client->ammo_index] -= GP_CELLS_PER_SHOT; self->client->pers.inventory[ITEM_INDEX(FindItem ("slugs"))] -= GP_SLUGS_PER_SHOT; } else self->client->pers.inventory[self->client->ammo_index]--; } } void Weapon_GaussPistol(edict_t *self) { static int pause_frames[] = {18, 50, 0}; static int fire_frames[] = {4, 0}; if (!self->client->show_gausscharge) { if (!self->client->gauss_particle && ((self->client->weaponstate == WEAPON_FIRING) || (self->client->weaponstate == WEAPON_READY))) { self->client->show_gausscharge = true; self->client->gauss_framenum = level.framenum; self->client->gauss_dmg = (int)sv_gauss_damage_base->value; } else self->client->show_gausscharge = false; } Weapon_Generic(self, 3, 9, 50, 53, pause_frames, fire_frames, Weapon_GaussPistol_Fire); } /* ====================================================================== ANTI-GRAV MANIPULATOR ====================================================================== */ void AGM_Reset(edict_t *self) { self->client->agm_on = false; self->client->agm_push = false; self->client->agm_pull = false; if (self->client->agm_target != NULL) { self->client->agm_target->client->held_by_agm = false; self->client->agm_target->client->flung_by_agm = false; self->client->agm_target->client->thrown_by_agm = true; self->client->agm_target = NULL; } } void Weapon_AGM_Fire(edict_t *self) { vec3_t start; vec3_t forward; vec3_t right; vec3_t offset; if (!(self->client->buttons & BUTTON_ATTACK)) { self->client->weaponstate = WEAPON_READY; return; } // Once the AGM has tripped off, it can't be fired again until it has recharged to 100. if ((int)sv_agm_mode->value == 0) { if (self->client->agm_tripped && (self->client->agm_target == NULL)) { gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/agm/agm_trip.wav"), 1, ATTN_STATIC, 0); return; } } // Wrap round animation to the start of the firing set if the fire button is still being pressed. self->client->ps.gunframe++; if ((self->client->ps.gunframe == 21) && self->client->pers.inventory[self->client->ammo_index]) { if (self->client->buttons & BUTTON_ATTACK) self->client->ps.gunframe = 6; else return; } // Check if there's enough ammo; swap weapons if we're dry. if (self->client->pers.inventory[self->client->ammo_index] < (int)sv_agm_beam_cells->value) { self->client->ps.gunframe = 21; if (level.time >= self->pain_debounce_time) { gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); self->pain_debounce_time = level.time + 1.0; } AGM_Reset(self); NoAmmoWeaponChange(self); return; } // Set beam start position. AngleVectors(self->client->v_angle, forward, right, NULL); VectorSet(offset, 24.0, 7.0, self->viewheight-6.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); // Fire! if (self->client->agm_target != NULL) Move_AGM(self, start, forward); else { if (!self->client->agm_on) { self->client->agm_on = true; if (((int)sv_agm_mode->value == 0) && (!((int)dmflags->value & DF_INFINITE_AMMO))) self->client->pers.inventory[self->client->ammo_index] -= (int)sv_agm_shot_cells->value; } Fire_AGM(self, start, forward, self->client->agm_disrupt); gi.sound(self, CHAN_WEAPON, gi.soundindex("medic/medatck1.wav"), 1, ATTN_NORM, 0); PlayerNoise(self, start, PNOISE_WEAPON); } if (!((int)dmflags->value & DF_INFINITE_AMMO)) { self->client->pers.inventory[self->client->ammo_index] -= (int)sv_agm_beam_cells->value; // The Haste powerup/Tech effect needs to be applied separately due to the way that // the secondary mode is implemented. if (CTFApplyHaste(self) || (self->client->haste_framenum > level.framenum)) { self->client->pers.inventory[self->client->ammo_index] -= (int)sv_agm_beam_cells->value; CTFApplyHasteSound(self); } if (self->client->pers.inventory[self->client->ammo_index] < 0) self->client->pers.inventory[self->client->ammo_index] = 0; } if (self->client->pers.inventory[self->client->ammo_index] == 0) return; // Spend extra ammo if we're manipulating a player who has the Invulnerability. if ((self->client->agm_target != NULL) && (self->client->agm_target->client->invincible_framenum > level.framenum)) { if ((int)sv_agm_invuln_cells->value > 0) { self->client->pers.inventory[self->client->ammo_index] -= (int)sv_agm_invuln_cells->value; if (self->client->pers.inventory[self->client->ammo_index] < 0) self->client->pers.inventory[self->client->ammo_index] = 0; } } // Decrease AGM charge if there's no target. if ((self->client->agm_target == NULL) && ((int)sv_agm_mode->value == 0)) { self->client->agm_charge -= ((is_quad && self->client->agm_disrupt)?2:1) * (int)sv_agm_fire_rate->value; if (self->client->agm_charge <= 0) { self->client->agm_charge = 0; if (!self->client->agm_tripped) { self->client->agm_tripped = true; AGM_Reset(self); } } } } void Weapon_AGM(edict_t *self) { static int pause_frames[] = {21, 43, 49, 0}; static int fire_frames[] = {6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0}; static gitem_t *tech = NULL; if (!self->client->agm_showcharge && ((int)sv_agm_mode->value == 0)) self->client->agm_showcharge = true; // Implement the effects of the '+push' and '+pull' commands. if (self->client->agm_pull) { self->client->agm_range -= AGM_RANGE_DELTA; if (self->client->agm_range < AGM_RANGE_MIN) self->client->agm_range = AGM_RANGE_MIN; } else if (self->client->agm_push) { self->client->agm_range += AGM_RANGE_DELTA; if (self->client->agm_range > AGM_RANGE_MAX) self->client->agm_range = AGM_RANGE_MAX; } // If the player has stopped firing and they had an AGM target, flag that target as being thrown. if (self->client->agm_on && !(self->client->buttons & BUTTON_ATTACK)) { AGM_Reset(self); self->client->ps.gunframe = 21; // Play Quad or Tech sounds, if appropriate. if (!tech) tech = FindItemByClassname("item_tech2"); if (tech && self->client && self->client->pers.inventory[ITEM_INDEX(tech)]) { if (self->client->ctf_techsndtime < level.time) { self->client->ctf_techsndtime = level.time + 1.0; if (self->client->quad_framenum > level.framenum) gi.sound(self, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), 1, ATTN_NORM, 0); else gi.sound(self, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), 1, ATTN_NORM, 0); } } else if (self->client->quad_framenum > level.framenum) gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); } // Sanity check: if we have an AGM target and they've just died, but for some reason our agm_target // pointer hasn't been reset, then do it manually here. if ((self->client->agm_target != NULL) && (self->client->agm_target->health < 1)) { self->client->agm_on = false; self->client->agm_target->client->flung_by_agm = false; self->client->agm_target->client->thrown_by_agm = false; self->client->agm_target = NULL; } // Charge up the AGM if it's not firing. if ((int)sv_agm_mode->value == 0) { if (!self->client->agm_on && (self->client->agm_charge < 100)) { self->client->agm_charge += (int)sv_agm_charge_rate->value; if (self->client->agm_charge >= 100) { self->client->agm_charge = 100; if (self->client->agm_tripped) { self->client->agm_tripped = false; gi.sound(self, CHAN_VOICE, gi.soundindex("world/fusein.wav"), 1, ATTN_STATIC, 0); } } } } // Weapon actions. Weapon_Generic(self, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_AGM_Fire); } /* ====================================================================== DISC LAUNCHER ====================================================================== */ void Weapon_DiscLauncher_Fire(edict_t *self) { vec3_t offset; vec3_t start; vec3_t forward; vec3_t right; int damage; int kick = 20; // Set damage and kick values. damage = (int)sv_disc_damage->value; if (is_quad) { damage *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } // Set projectile start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); VectorScale(forward, -2.0, self->client->kick_origin); self->client->kick_angles[0] = -2.0; VectorSet(offset, 0.0, 8.0, self->viewheight-8.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); // Fire! Fire_Disc(self, start, forward, damage, sv_disc_speed->value, kick); gi.WriteByte(svc_muzzleflash); gi.WriteShort(self-g_edicts); gi.WriteByte(MZ_IONRIPPER | is_silenced); gi.multicast(self->s.origin, MULTICAST_PVS); self->client->ps.gunframe++; PlayerNoise(self, start, PNOISE_WEAPON); if (!((int)dmflags->value & DF_INFINITE_AMMO)) self->client->pers.inventory[self->client->ammo_index]--; } void Weapon_DiscLauncher(edict_t *self) { static int pause_frames[] = {25, 54, 0}; static int fire_frames[] = {5, 0}; Weapon_Generic(self, 4, 14, 54, 58, pause_frames, fire_frames, Weapon_DiscLauncher_Fire); } /* ====================================================================== CHAINSAW ====================================================================== */ void Weapon_Chainsaw_Fire(edict_t *self) { vec3_t offset; vec3_t start; vec3_t forward; vec3_t right; int damage; int kick = 25; // Set damage and kick values. damage = (int)sv_chainsaw_damage->value; if (is_quad) { damage *= (int)sv_quad_factor->value; kick *= (int)sv_quad_factor->value; } if ((self->client->haste_framenum > level.framenum) || CTFApplyHaste(self)) { damage *= 2; kick *= 2; } // Set damage point start position and weapon kick info. AngleVectors(self->client->v_angle, forward, right, NULL); VectorScale(forward, -2.0, self->client->kick_origin); self->client->kick_angles[0] = -2.0; VectorSet(offset, 8.0, 8.0, self->viewheight-8.0); P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); // Fire! Fire_Chainsaw(self, start, forward, damage, kick); gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/chainsaw/fire.wav"), 1, ATTN_NORM, 0); PlayerNoise(self, start, PNOISE_WEAPON); // We just want to repeat a couple of animation frames (19 and 20). Although the chainsaw model // has two nifty attack sequences, we want to make it more like the Doom II chainsaw. self->client->ps.gunframe++; self->client->machinegun_shots = 1; if ((self->client->buttons & BUTTON_ATTACK) && (self->client->ps.gunframe == 21)) { self->client->ps.gunframe = 19; return; } if (self->client->ps.gunframe == 8) self->client->ps.gunframe = 19; } void Weapon_Chainsaw(edict_t *self) { static int pause_frames[] = {69, 0}; static int fire_frames[] = {7, 19, 20, 21, 0}; // Handle customised view model animation. Note that the machinegun_shots hack is needed // to prevent screwing up the weaponstate value if instant switching is not enabled. if ((self->client->ps.gunframe < 22) && self->client->machinegun_shots && !(self->client->buttons & BUTTON_ATTACK)) { self->client->ps.gunframe = 28; self->client->machinegun_shots ^= 1; } Weapon_Generic(self, 6, 29, 69, 73, pause_frames, fire_frames, Weapon_Chainsaw_Fire); // Play the "revving down" sound at the start of the putaway sequence. (Note that this frame isn't // played if DF_FAST_SWITCH is set, so for that case we have to do it in Weapon_Generic2() manually). if (self->client->ps.gunframe == 70) gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/chainsaw/done.wav"), 1, ATTN_NORM, 0); } //CW--