]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/weapon/shotgun.qc
814d5730368d3ec40c1bb3c405efee9a492d001e
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / shotgun.qc
1 #ifndef IMPLEMENTATION
2 REGISTER_WEAPON(
3 /* WEP_##id  */ SHOTGUN,
4 /* function  */ W_Shotgun,
5 /* ammotype  */ ammo_shells,
6 /* impulse   */ 2,
7 /* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
8 /* rating    */ BOT_PICKUP_RATING_LOW,
9 /* color     */ '0.5 0.25 0',
10 /* modelname */ "shotgun",
11 /* simplemdl */ "foobar",
12 /* crosshair */ "gfx/crosshairshotgun 0.65",
13 /* wepimg    */ "weaponshotgun",
14 /* refname   */ "shotgun",
15 /* wepname   */ _("Shotgun")
16 );
17
18 #define SHOTGUN_SETTINGS(w_cvar,w_prop) SHOTGUN_SETTINGS_LIST(w_cvar, w_prop, SHOTGUN, shotgun)
19 #define SHOTGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
20         w_cvar(id, sn, PRI,  ammo) \
21         w_cvar(id, sn, BOTH, animtime) \
22         w_cvar(id, sn, BOTH, refire) \
23         w_cvar(id, sn, PRI,  bullets) \
24         w_cvar(id, sn, BOTH, damage) \
25         w_cvar(id, sn, BOTH, force) \
26         w_cvar(id, sn, PRI,  solidpenetration) \
27         w_cvar(id, sn, PRI,  spread) \
28         w_cvar(id, sn, NONE, secondary) \
29         w_cvar(id, sn, SEC,  melee_time) \
30         w_cvar(id, sn, SEC,  melee_no_doubleslap) \
31         w_cvar(id, sn, SEC,  melee_traces) \
32         w_cvar(id, sn, SEC,  melee_swing_up) \
33         w_cvar(id, sn, SEC,  melee_swing_side) \
34         w_cvar(id, sn, SEC,  melee_nonplayerdamage) \
35         w_cvar(id, sn, SEC,  melee_multihit) \
36         w_cvar(id, sn, SEC,  melee_delay) \
37         w_cvar(id, sn, SEC,  melee_range) \
38         w_cvar(id, sn, SEC,  alt_animtime) \
39         w_cvar(id, sn, SEC,  alt_refire) \
40         w_prop(id, sn, float,  reloading_ammo, reload_ammo) \
41         w_prop(id, sn, float,  reloading_time, reload_time) \
42         w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
43         w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
44         w_prop(id, sn, string, weaponreplace, weaponreplace) \
45         w_prop(id, sn, float,  weaponstart, weaponstart) \
46         w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
47         w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
48
49 #ifdef SVQC
50 SHOTGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
51 #endif
52 #endif
53 #ifdef IMPLEMENTATION
54 #ifdef SVQC
55 void spawnfunc_weapon_shotgun(void) { weapon_defaultspawnfunc(WEP_SHOTGUN.m_id); }
56
57 void W_Shotgun_Attack(float isprimary)
58 {SELFPARAM();
59         float   sc;
60         entity flash;
61
62         W_DecreaseAmmo(WEP_CVAR_PRI(shotgun, ammo));
63
64         W_SetupShot(self, true, 5, W_Sound("shotgun_fire"), ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), WEP_CVAR_PRI(shotgun, damage) * WEP_CVAR_PRI(shotgun, bullets));
65         for(sc = 0;sc < WEP_CVAR_PRI(shotgun, bullets);sc = sc + 1)
66                 fireBullet(w_shotorg, w_shotdir, WEP_CVAR_PRI(shotgun, spread), WEP_CVAR_PRI(shotgun, solidpenetration), WEP_CVAR_PRI(shotgun, damage), WEP_CVAR_PRI(shotgun, force), WEP_SHOTGUN.m_id, 0);
67
68         Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, WEP_CVAR_PRI(shotgun, ammo));
69
70         // casing code
71         if(autocvar_g_casings >= 1)
72                 for(sc = 0;sc < WEP_CVAR_PRI(shotgun, ammo);sc = sc + 1)
73                         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, self);
74
75         // muzzle flash for 1st person view
76         flash = spawn();
77         setmodel(flash, MDL_SHOTGUN_MUZZLEFLASH); // precision set below
78         flash.think = SUB_Remove;
79         flash.nextthink = time + 0.06;
80         flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
81         W_AttachToShotorg(flash, '5 0 0');
82 }
83
84 .float swing_prev;
85 .entity swing_alreadyhit;
86 void W_Shotgun_Melee_Think(void)
87 {SELFPARAM();
88         // declarations
89         float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
90         entity target_victim;
91         vector targpos;
92
93         if(!self.cnt) // set start time of melee
94         {
95                 self.cnt = time;
96                 W_PlayStrengthSound(self.realowner);
97         }
98
99         makevectors(self.realowner.v_angle); // update values for v_* vectors
100
101         // calculate swing percentage based on time
102         meleetime = WEP_CVAR_SEC(shotgun, melee_time) * W_WeaponRateFactor();
103         swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
104         f = ((1 - swing) * WEP_CVAR_SEC(shotgun, melee_traces));
105
106         // check to see if we can still continue, otherwise give up now
107         if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR_SEC(shotgun, melee_no_doubleslap))
108         {
109                 remove(self);
110                 return;
111         }
112
113         // if okay, perform the traces needed for this frame
114         for(i=self.swing_prev; i < f; ++i)
115         {
116                 swing_factor = ((1 - (i / WEP_CVAR_SEC(shotgun, melee_traces))) * 2 - 1);
117
118                 targpos = (self.realowner.origin + self.realowner.view_ofs
119                         + (v_forward * WEP_CVAR_SEC(shotgun, melee_range))
120                         + (v_up * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_up))
121                         + (v_right * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_side)));
122
123                 WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, false, self, ANTILAG_LATENCY(self.realowner));
124
125                 // draw lightning beams for debugging
126                 //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5);
127                 //te_customflash(targpos, 40,  2, '1 1 1');
128
129                 is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body" || IS_MONSTER(trace_ent));
130
131                 if((trace_fraction < 1) // if trace is good, apply the damage and remove self
132                         && (trace_ent.takedamage == DAMAGE_AIM)
133                         && (trace_ent != self.swing_alreadyhit)
134                         && (is_player || WEP_CVAR_SEC(shotgun, melee_nonplayerdamage)))
135                 {
136                         target_victim = trace_ent; // so it persists through other calls
137
138                         if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
139                                 swing_damage = (WEP_CVAR_SEC(shotgun, damage) * min(1, swing_factor + 1));
140                         else
141                                 swing_damage = (WEP_CVAR_SEC(shotgun, melee_nonplayerdamage) * min(1, swing_factor + 1));
142
143                         //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
144
145                         Damage(target_victim, self.realowner, self.realowner,
146                                 swing_damage, WEP_SHOTGUN.m_id | HITTYPE_SECONDARY,
147                                 self.realowner.origin + self.realowner.view_ofs,
148                                 v_forward * WEP_CVAR_SEC(shotgun, force));
149
150                         if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN.m_id, 0, swing_damage); }
151
152                         // draw large red flash for debugging
153                         //te_customflash(targpos, 200, 2, '15 0 0');
154
155                         if(WEP_CVAR_SEC(shotgun, melee_multihit)) // allow multiple hits with one swing, but not against the same player twice.
156                         {
157                                 self.swing_alreadyhit = target_victim;
158                                 continue; // move along to next trace
159                         }
160                         else
161                         {
162                                 remove(self);
163                                 return;
164                         }
165                 }
166         }
167
168         if(time >= self.cnt + meleetime)
169         {
170                 // melee is finished
171                 remove(self);
172                 return;
173         }
174         else
175         {
176                 // set up next frame
177                 self.swing_prev = i;
178                 self.nextthink = time;
179         }
180 }
181
182 void W_Shotgun_Attack2(void)
183 {SELFPARAM();
184         sound(self, CH_WEAPON_A, W_Sound("shotgun_melee"), VOL_BASE, ATTEN_NORM);
185         weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(shotgun, animtime), w_ready);
186
187         entity meleetemp;
188         meleetemp = spawn();
189         meleetemp.realowner = self;
190         meleetemp.think = W_Shotgun_Melee_Think;
191         meleetemp.nextthink = time + WEP_CVAR_SEC(shotgun, melee_delay) * W_WeaponRateFactor();
192         W_SetupShot_Range(self, true, 0, "", 0, WEP_CVAR_SEC(shotgun, damage), WEP_CVAR_SEC(shotgun, melee_range));
193 }
194
195 // alternate secondary weapon frames
196 void W_Shotgun_Attack3_Frame2()
197 {SELFPARAM();
198         if (!WEP_ACTION(self.weapon, WR_CHECKAMMO2))
199         if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
200         {
201                 W_SwitchWeapon_Force(self, w_getbestweapon(self));
202                 w_ready();
203                 return;
204         }
205
206         sound(self, CH_WEAPON_SINGLE, "misc/null.wav", VOL_BASE, ATTN_NORM); // kill previous sound
207         W_Shotgun_Attack(true); // actually is secondary, but we trick the last shot into playing full reload sound
208         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), w_ready);
209 }
210 void W_Shotgun_Attack3_Frame1()
211 {SELFPARAM();
212         if (!WEP_ACTION(self.weapon, WR_CHECKAMMO2))
213         if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
214         {
215                 W_SwitchWeapon_Force(self, w_getbestweapon(self));
216                 w_ready();
217                 return;
218         }
219
220         W_Shotgun_Attack(false);
221         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame2);
222 }
223
224 .float shotgun_primarytime;
225
226 float W_Shotgun(float req)
227 {SELFPARAM();
228         float ammo_amount;
229         switch(req)
230         {
231                 case WR_AIM:
232                 {
233                         if(vlen(self.origin-self.enemy.origin) <= WEP_CVAR_SEC(shotgun, melee_range))
234                                 self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, false);
235                         else
236                                 self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, false);
237
238                         return true;
239                 }
240                 case WR_THINK:
241                 {
242                         if(WEP_CVAR(shotgun, reload_ammo) && self.clip_load < WEP_CVAR_PRI(shotgun, ammo)) // forced reload
243                         {
244                                 // don't force reload an empty shotgun if its melee attack is active
245                                 if(WEP_CVAR(shotgun, secondary) < 2)
246                                         WEP_ACTION(self.weapon, WR_RELOAD);
247                         }
248                         else
249                         {
250                                 if(self.BUTTON_ATCK)
251                                 {
252                                         if(time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
253                                         {
254                                                 if(weapon_prepareattack(0, WEP_CVAR_PRI(shotgun, animtime)))
255                                                 {
256                                                         W_Shotgun_Attack(true);
257                                                         self.shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor();
258                                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready);
259                                                 }
260                                         }
261                                 }
262                                 else if(self.BUTTON_ATCK2 && WEP_CVAR(shotgun, secondary) == 2)
263                                 {
264                                         if(time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
265                                         {
266                                                 if(weapon_prepareattack(0, WEP_CVAR_SEC(shotgun, alt_animtime)))
267                                                 {
268                                                         W_Shotgun_Attack(false);
269                                                         self.shotgun_primarytime = time + WEP_CVAR_SEC(shotgun, alt_refire) * W_WeaponRateFactor();
270                                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame1);
271                                                 }
272                                         }
273                                 }
274                         }
275                         if(self.clip_load >= 0) // we are not currently reloading
276                         if(!self.crouch) // no crouchmelee please
277                         if(WEP_CVAR(shotgun, secondary) == 1)
278                         if((self.BUTTON_ATCK && self.WEP_AMMO(SHOTGUN) <= 0 && !(self.items & IT_UNLIMITED_WEAPON_AMMO)) || self.BUTTON_ATCK2)
279                         if(weapon_prepareattack(1, WEP_CVAR_SEC(shotgun, refire)))
280                         {
281                                 // attempt forcing playback of the anim by switching to another anim (that we never play) here...
282                                 weapon_thinkf(WFRAME_FIRE1, 0, W_Shotgun_Attack2);
283                         }
284
285                         return true;
286                 }
287                 case WR_INIT:
288                 {
289                         precache_sound("misc/itempickup.wav");
290                         precache_sound(W_Sound("shotgun_fire"));
291                         precache_sound(W_Sound("shotgun_melee"));
292                         SHOTGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
293                         return true;
294                 }
295                 case WR_SETUP:
296                 {
297                         self.ammo_field = ammo_none;
298                         return true;
299                 }
300                 case WR_CHECKAMMO1:
301                 {
302                         ammo_amount = self.WEP_AMMO(SHOTGUN) >= WEP_CVAR_PRI(shotgun, ammo);
303                         ammo_amount += self.(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
304                         return ammo_amount;
305                 }
306                 case WR_CHECKAMMO2:
307                 {
308                         if(IS_BOT_CLIENT(self))
309                         if(vlen(self.origin-self.enemy.origin) > WEP_CVAR_SEC(shotgun, melee_range))
310                                 return false; // bots cannot use secondary out of range (fixes constant melee when out of ammo)
311                         switch(WEP_CVAR(shotgun, secondary))
312                         {
313                                 case 1: return true; // melee does not use ammo
314                                 case 2: // secondary triple shot
315                                 {
316                                         ammo_amount = self.WEP_AMMO(SHOTGUN) >= WEP_CVAR_PRI(shotgun, ammo);
317                                         ammo_amount += self.(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
318                                         return ammo_amount;
319                                 }
320                                 default: return false; // secondary unavailable
321                         }
322                 }
323                 case WR_CONFIG:
324                 {
325                         SHOTGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
326                         return true;
327                 }
328                 case WR_RELOAD:
329                 {
330                         W_Reload(WEP_CVAR_PRI(shotgun, ammo), W_Sound("reload")); // WEAPONTODO
331                         return true;
332                 }
333                 case WR_SUICIDEMESSAGE:
334                 {
335                         return WEAPON_THINKING_WITH_PORTALS;
336                 }
337                 case WR_KILLMESSAGE:
338                 {
339                         if(w_deathtype & HITTYPE_SECONDARY)
340                                 return WEAPON_SHOTGUN_MURDER_SLAP;
341                         else
342                                 return WEAPON_SHOTGUN_MURDER;
343                 }
344         }
345         return false;
346 }
347 #endif
348 #ifdef CSQC
349 .float prevric;
350 float W_Shotgun(float req)
351 {SELFPARAM();
352         switch(req)
353         {
354                 case WR_IMPACTEFFECT:
355                 {
356                         vector org2;
357                         org2 = w_org + w_backoff * 2;
358                         pointparticles(particleeffectnum(EFFECT_SHOTGUN_IMPACT), org2, w_backoff * 1000, 1);
359                         if(!w_issilent && time - self.prevric > 0.25)
360                         {
361                                 if(w_random < 0.0165)
362                                         sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTEN_NORM);
363                                 else if(w_random < 0.033)
364                                         sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTEN_NORM);
365                                 else if(w_random < 0.05)
366                                         sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTEN_NORM);
367                                 self.prevric = time;
368                         }
369
370                         return true;
371                 }
372                 case WR_INIT:
373                 {
374                         precache_sound(W_Sound("ric1"));
375                         precache_sound(W_Sound("ric2"));
376                         precache_sound(W_Sound("ric3"));
377                         return true;
378                 }
379                 case WR_ZOOMRETICLE:
380                 {
381                         // no weapon specific image for this weapon
382                         return false;
383                 }
384         }
385         return false;
386 }
387 #endif
388 #endif