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