Xonotic
shotgun.qc
Go to the documentation of this file.
1 #include "shotgun.qh"
2 
3 #ifdef SVQC
4 
5 // enable to debug melee range
6 //#define SHOTGUN_MELEEDEBUG
7 
8 void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary, float ammocount, float damage, float bullets, float spread, float solidpenetration, float force, entity bullet_trail_effect)
9 {
10  W_DecreaseAmmo(thiswep, actor, ammocount, weaponentity);
11 
12  W_SetupShot(actor, weaponentity, true, 5, SND_SHOTGUN_FIRE, ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), damage * bullets, thiswep.m_id);
13 
14  // TRICK: do the antilag outside the regular fireBullet function, so it isn't performed unnecessarily on every single bullet!
15  float lag = antilag_getlag(actor);
16  if(lag && bullets > 0)
17  antilag_takeback_all(actor, lag);
18 
19  for(int sc = 0;sc < bullets;sc = sc + 1)
20  fireBullet_antilag(actor, weaponentity, w_shotorg, w_shotdir, spread, solidpenetration, damage, 0, force, thiswep.m_id, bullet_trail_effect, false);
21 
22  if(lag && bullets > 0)
23  antilag_restore_all(actor);
24 
25  W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
26 
27  // casing code
28  if(autocvar_g_casings >= 1)
29  {
30  makevectors(actor.v_angle); // for some reason, this is lost
31  //for(int sc = 0;sc < WEP_CVAR_PRI(shotgun, ammo);sc = sc + 1)
32  SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, actor, weaponentity);
33  }
34 }
35 
36 .float swing_prev;
37 .entity swing_alreadyhit;
38 void W_Shotgun_Melee_Think(entity this)
39 {
40  // declarations
41  float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
42  entity target_victim;
43  vector targpos;
44 
45  if(!this.cnt) // set start time of melee
46  {
47  this.cnt = time;
49  }
50 
51  makevectors(this.realowner.v_angle); // update values for v_* vectors
52 
53  // calculate swing percentage based on time
54  meleetime = WEP_CVAR_SEC(shotgun, melee_time) * W_WeaponRateFactor(this.realowner);
55  swing = bound(0, (this.cnt + meleetime - time) / meleetime, 10);
56  f = ((1 - swing) * WEP_CVAR_SEC(shotgun, melee_traces));
57 
58  // check to see if we can still continue, otherwise give up now
59  if(IS_DEAD(this.realowner) && WEP_CVAR_SEC(shotgun, melee_no_doubleslap))
60  {
61  delete(this);
62  return;
63  }
64 
65  // if okay, perform the traces needed for this frame
66  for(i=this.swing_prev; i < f; ++i)
67  {
68  swing_factor = ((1 - (i / WEP_CVAR_SEC(shotgun, melee_traces))) * 2 - 1);
69 
70  targpos = (this.realowner.origin + this.realowner.view_ofs
71  + (v_forward * WEP_CVAR_SEC(shotgun, melee_range))
72  + (v_up * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_up))
73  + (v_right * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_side)));
74 
75  WarpZone_traceline_antilag(this.realowner, this.realowner.origin + this.realowner.view_ofs, targpos, false, this.realowner, ((IS_CLIENT(this.realowner)) ? ANTILAG_LATENCY(this.realowner) : 0));
76 
77  // draw lightning beams for debugging
78  #ifdef SHOTGUN_MELEEDEBUG
79  te_lightning2(NULL, targpos, this.realowner.origin + this.realowner.view_ofs + v_forward * 5 - v_up * 5);
80  te_customflash(targpos, 40, 2, '1 1 1');
81  #endif
82 
83  is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body" || IS_MONSTER(trace_ent));
84 
85  if((trace_fraction < 1) // if trace is good, apply the damage and remove this
86  && (trace_ent.takedamage != DAMAGE_NO)
87  && (trace_ent != this.swing_alreadyhit)
88  && (is_player || WEP_CVAR_SEC(shotgun, melee_nonplayerdamage)))
89  {
90  target_victim = trace_ent; // so it persists through other calls
91 
92  if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
93  swing_damage = (WEP_CVAR_SEC(shotgun, damage) * min(1, swing_factor + 1));
94  else
95  swing_damage = (WEP_CVAR_SEC(shotgun, melee_nonplayerdamage) * min(1, swing_factor + 1));
96 
97  //print(strcat(this.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
98 
99  Damage(target_victim, this.realowner, this.realowner,
100  swing_damage, WEP_SHOTGUN.m_id | HITTYPE_SECONDARY, this.weaponentity_fld,
101  this.realowner.origin + this.realowner.view_ofs,
102  v_forward * WEP_CVAR_SEC(shotgun, force));
103 
104  if(accuracy_isgooddamage(this.realowner, target_victim)) { accuracy_add(this.realowner, WEP_SHOTGUN, 0, swing_damage); }
105 
106  // draw large red flash for debugging
107  #ifdef SHOTGUN_MELEEDEBUG
108  te_customflash(targpos, 200, 2, '15 0 0');
109  #endif
110 
111  if(WEP_CVAR_SEC(shotgun, melee_multihit)) // allow multiple hits with one swing, but not against the same player twice.
112  {
113  this.swing_alreadyhit = target_victim;
114  continue; // move along to next trace
115  }
116  else
117  {
118  delete(this);
119  return;
120  }
121  }
122  }
123 
124  if(time >= this.cnt + meleetime)
125  {
126  // melee is finished
127  delete(this);
128  return;
129  }
130  else
131  {
132  // set up next frame
133  this.swing_prev = i;
134  this.nextthink = time;
135  }
136 }
137 
138 void W_Shotgun_Attack2(Weapon thiswep, entity actor, .entity weaponentity, int fire)
139 {
140  sound(actor, CH_WEAPON_A, SND_SHOTGUN_MELEE, VOL_BASE, ATTEN_NORM);
141  weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(shotgun, animtime), w_ready);
142 
143  entity meleetemp = new_pure(meleetemp);
144  meleetemp.realowner = actor;
145  setthink(meleetemp, W_Shotgun_Melee_Think);
146  meleetemp.nextthink = time + WEP_CVAR_SEC(shotgun, melee_delay) * W_WeaponRateFactor(actor);
147  meleetemp.weaponentity_fld = weaponentity;
148  W_SetupShot_Range(actor, weaponentity, true, 0, SND_Null, 0, WEP_CVAR_SEC(shotgun, damage), WEP_CVAR_SEC(shotgun, melee_range), WEP_SHOTGUN.m_id | HITTYPE_SECONDARY);
149 }
150 
151 // alternate secondary weapon frames
152 void W_Shotgun_Attack3_Frame2(Weapon thiswep, entity actor, .entity weaponentity, int fire)
153 {
154  if (!thiswep.wr_checkammo2(thiswep, actor, weaponentity))
155  if (!(actor.items & IT_UNLIMITED_AMMO))
156  {
157  W_SwitchWeapon_Force(actor, w_getbestweapon(actor, weaponentity), weaponentity);
158  w_ready(thiswep, actor, weaponentity, fire);
159  return;
160  }
161 
162  sound(actor, CH_WEAPON_SINGLE, SND_Null, VOL_BASE, ATTN_NORM); // kill previous sound
163  W_Shotgun_Attack(thiswep, actor, weaponentity, true,
164  WEP_CVAR_PRI(shotgun, ammo),
165  WEP_CVAR_PRI(shotgun, damage),
166  WEP_CVAR_PRI(shotgun, bullets),
167  WEP_CVAR_PRI(shotgun, spread),
168  WEP_CVAR_PRI(shotgun, solidpenetration),
169  WEP_CVAR_PRI(shotgun, force),
170  EFFECT_BULLET_WEAK); // actually is secondary, but we trick the last shot into playing full reload sound
171  weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), w_ready);
172 }
173 void W_Shotgun_Attack3_Frame1(Weapon thiswep, entity actor, .entity weaponentity, int fire)
174 {
175  if (!thiswep.wr_checkammo2(thiswep, actor, weaponentity))
176  if (!(actor.items & IT_UNLIMITED_AMMO))
177  {
178  W_SwitchWeapon_Force(actor, w_getbestweapon(actor, weaponentity), weaponentity);
179  w_ready(thiswep, actor, weaponentity, fire);
180  return;
181  }
182 
183  W_Shotgun_Attack(thiswep, actor, weaponentity, false,
184  WEP_CVAR_PRI(shotgun, ammo),
185  WEP_CVAR_PRI(shotgun, damage),
186  WEP_CVAR_PRI(shotgun, bullets),
187  WEP_CVAR_PRI(shotgun, spread),
188  WEP_CVAR_PRI(shotgun, solidpenetration),
189  WEP_CVAR_PRI(shotgun, force),
190  EFFECT_BULLET_WEAK);
191  weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame2);
192 }
193 
194 .float shotgun_primarytime;
195 
196 METHOD(Shotgun, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
197 {
198  if(vdist(actor.origin - actor.enemy.origin, <=, WEP_CVAR_SEC(shotgun, melee_range)))
199  PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
200  else
201  PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
202 }
203 
204 METHOD(Shotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
205 {
206  if(WEP_CVAR(shotgun, reload_ammo) && actor.(weaponentity).clip_load < WEP_CVAR_PRI(shotgun, ammo)) // forced reload
207  {
208  // don't force reload an empty shotgun if its melee attack is active
209  if(WEP_CVAR(shotgun, secondary) < 2) {
210  thiswep.wr_reload(thiswep, actor, weaponentity);
211  }
212  }
213  else
214  {
215  if(fire & 1)
216  {
217  if(time >= actor.(weaponentity).shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
218  {
219  if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(shotgun, animtime)))
220  {
221  W_Shotgun_Attack(thiswep, actor, weaponentity, true,
222  WEP_CVAR_PRI(shotgun, ammo),
223  WEP_CVAR_PRI(shotgun, damage),
224  WEP_CVAR_PRI(shotgun, bullets),
225  WEP_CVAR_PRI(shotgun, spread),
226  WEP_CVAR_PRI(shotgun, solidpenetration),
227  WEP_CVAR_PRI(shotgun, force),
228  EFFECT_BULLET_WEAK);
229  actor.(weaponentity).shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor(actor);
230  weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready);
231  }
232  }
233  }
234  else if((fire & 2) && WEP_CVAR(shotgun, secondary) == 2)
235  {
236  if(time >= actor.(weaponentity).shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
237  {
238  if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_SEC(shotgun, alt_animtime)))
239  {
240  W_Shotgun_Attack(thiswep, actor, weaponentity, false,
241  WEP_CVAR_PRI(shotgun, ammo),
242  WEP_CVAR_PRI(shotgun, damage),
243  WEP_CVAR_PRI(shotgun, bullets),
244  WEP_CVAR_PRI(shotgun, spread),
245  WEP_CVAR_PRI(shotgun, solidpenetration),
246  WEP_CVAR_PRI(shotgun, force),
247  EFFECT_BULLET_WEAK);
248  actor.(weaponentity).shotgun_primarytime = time + WEP_CVAR_SEC(shotgun, alt_refire) * W_WeaponRateFactor(actor);
249  weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame1);
250  }
251  }
252  }
253  }
254  if(actor.(weaponentity).clip_load >= 0) // we are not currently reloading
255  if(WEP_CVAR(shotgun, secondary) == 1)
256  if(((fire & 1) && GetResource(actor, thiswep.ammo_type) <= 0 && !(actor.items & IT_UNLIMITED_AMMO)) || (fire & 2))
257  if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(shotgun, refire)))
258  {
259  // attempt forcing playback of the anim by switching to another anim (that we never play) here...
260  weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, 0, W_Shotgun_Attack2);
261  }
262 }
263 METHOD(Shotgun, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
264 {
265  float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(shotgun, ammo);
266  ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
267  return ammo_amount;
268 }
269 METHOD(Shotgun, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
270 {
271  if(IS_BOT_CLIENT(actor))
272  if(vdist(actor.origin - actor.enemy.origin, >, WEP_CVAR_SEC(shotgun, melee_range)))
273  return false; // bots cannot use secondary out of range (fixes constant melee when out of ammo)
274  switch(WEP_CVAR(shotgun, secondary))
275  {
276  case 1: return true; // melee does not use ammo
277  case 2: // secondary triple shot
278  {
279  float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(shotgun, ammo);
280  ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
281  return ammo_amount;
282  }
283  default: return false; // secondary unavailable
284  }
285 }
286 METHOD(Shotgun, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
287 {
288  W_Reload(actor, weaponentity, WEP_CVAR_PRI(shotgun, ammo), SND_RELOAD); // WEAPONTODO
289 }
290 METHOD(Shotgun, wr_suicidemessage, Notification(entity thiswep))
291 {
292  return WEAPON_THINKING_WITH_PORTALS;
293 }
294 METHOD(Shotgun, wr_killmessage, Notification(entity thiswep))
295 {
297  return WEAPON_SHOTGUN_MURDER_SLAP;
298  else
299  return WEAPON_SHOTGUN_MURDER;
300 }
301 
302 #endif
303 #ifdef CSQC
304 .float prevric;
305 
306 METHOD(Shotgun, wr_impacteffect, void(entity thiswep, entity actor))
307 {
308  vector org2 = w_org + w_backoff * 2;
309  pointparticles(EFFECT_SHOTGUN_IMPACT, org2, w_backoff * 1000, 1);
310  if(!w_issilent && time - actor.prevric > 0.25)
311  {
312  if(w_random < 0.05)
314  actor.prevric = time;
315  }
316 }
317 
318 #endif
#define WEP_CVAR_PRI(wepname, name)
Definition: all.qh:300
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition: player.qh:148
bool bot_aim(entity this,.entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity)
#define WEP_CVAR_SEC(wepname, name)
Definition: all.qh:301
float weapon_load[REGISTRY_MAX(Weapons)]
Definition: weaponsystem.qh:29
vector w_shotorg
Definition: tracing.qh:18
void W_SwitchWeapon_Force(Player this, Weapon w,.entity weaponentity)
Definition: selection.qc:243
#define w_getbestweapon(ent, wepent)
Definition: selection.qh:23
#define IS_CLIENT(v)
Definition: utils.qh:13
#define W_SetupShot_Range(ent, wepent, antilag, recoil, snd, chan, maxdamage, range, deathtype)
Definition: tracing.qh:35
float antilag_getlag(entity e)
Definition: antilag.qc:151
entity() spawn
void w_ready(Weapon thiswep, entity actor,.entity weaponentity, int fire)
#define WEP_CVAR(wepname, name)
Definition: all.qh:299
#define IS_MONSTER(v)
Definition: utils.qh:21
Sound SND_RIC_RANDOM()
Definition: all.inc:84
vector w_shotdir
Definition: tracing.qh:19
#define METHOD(cname, name, prototype)
Definition: oo.qh:257
entity trace_ent
Definition: csprogsdefs.qc:40
void WarpZone_traceline_antilag(entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
Definition: antilag.qc:221
void antilag_restore_all(entity ignore)
Definition: antilag.qc:138
const float ATTN_NORM
Definition: csprogsdefs.qc:226
float ammo
Definition: sv_turrets.qh:44
float cnt
Definition: powerups.qc:24
void W_PlayStrengthSound(entity player)
Definition: common.qc:44
const int CH_WEAPON_A
Definition: sound.qh:7
vector v_up
Definition: csprogsdefs.qc:31
bool weapon_prepareattack(Weapon thiswep, entity actor,.entity weaponentity, bool secondary, float attacktime)
const int CH_WEAPON_SINGLE
Definition: sound.qh:9
#define pointparticles
Definition: csprogsdefs.qh:13
#define NULL
Definition: post.qh:17
const float VOL_BASE
Definition: sound.qh:36
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition: player.qh:146
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition: damage.qc:583
const int HITTYPE_SECONDARY
Definition: all.qh:25
#define W_SetupShot(ent, wepent, antilag, recoil, snd, chan, maxdamage, deathtype)
Definition: tracing.qh:33
#define IS_DEAD(s)
Definition: utils.qh:26
const float ATTEN_NORM
Definition: sound.qh:30
float nextthink
Definition: csprogsdefs.qc:121
const int CH_SHOTS
Definition: sound.qh:14
float w_deathtype
Definition: damage.qh:97
vector(float skel, float bonenum) _skel_get_boneabs_hidden
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
Definition: cl_resources.qc:10
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
void accuracy_add(entity this, Weapon w, float fired, float hit)
Definition: accuracy.qc:83
entity Notification
always last
Definition: all.qh:82
void weapon_thinkf(entity actor,.entity weaponentity, WFRAME fr, float t, void(Weapon thiswep, entity actor,.entity weaponentity, int fire) func)
bool accuracy_isgooddamage(entity attacker, entity targ)
Definition: accuracy.qc:112
vector v_right
Definition: csprogsdefs.qc:31
entity realowner
Definition: common.qh:25
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition: utils.qh:15
#define new_pure(class)
purely logical entities (.origin doesn&#39;t work)
Definition: oo.qh:62
#define ANTILAG_LATENCY(e)
Definition: antilag.qh:19
#define setthink(e, f)
void W_DecreaseAmmo(Weapon wep, entity actor, float ammo_use,.entity weaponentity)
#define sound(e, c, s, v, a)
Definition: sound.qh:52
fields which are explicitly/manually set are marked with "M", fields set automatically are marked wit...
Definition: weapon.qh:41
float time
Definition: csprogsdefs.qc:16
int m_id
Definition: weapon.qh:42
void antilag_takeback_all(entity ignore, float lag)
Definition: antilag.qc:125
#define makevectors
Definition: post.qh:21
float trace_fraction
Definition: csprogsdefs.qc:36
float W_WeaponRateFactor(entity this)
Definition: weaponsystem.qc:33
float DAMAGE_NO
Definition: progsdefs.qc:282
#define IS_PLAYER(v)
Definition: utils.qh:9
void fireBullet_antilag(entity this,.entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float headshot_multiplier, float force, float dtype, entity tracer_effect, bool do_antilag)
Definition: tracing.qc:357
vector v_forward
Definition: csprogsdefs.qc:31
void W_Reload(entity actor,.entity weaponentity, float sent_ammo_min, Sound sent_sound)