Merge branch 'master' into samual/water_and_damage_blur
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_sniperrifle.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(SNIPERRIFLE, w_sniperrifle, IT_NAILS, 7, WEP_FLAG_NORMAL | WEP_TYPE_HITSCAN, BOT_PICKUP_RATING_MID, "campingrifle", "sniperrifle", "Sniper Rifle");
3 #else
4 #ifdef SVQC
5 //Sniper rifle Primary mode: manually operated bolt*, Secondary: full automatic**
6 //* Manually operating the bolt means that all the power of the gas is used to propell the bullet. In this mode the bolt is prevented from moving backwards in response to the firing of the bullet.
7 //** In fully automatic mode some of the gas is used to extract and reload the next cartrige, thus there is less power and range.
8
9 .float sniperrifle_accumulator;
10
11 float W_SniperRifle_CheckMaxBullets(float checkammo)
12 {
13         float maxbulls;
14         maxbulls = autocvar_g_balance_sniperrifle_magazinecapacity;
15         if(!maxbulls)
16                 maxbulls = 8; // match HUD
17         if(checkammo)
18                 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
19                         maxbulls = min(maxbulls, floor(self.ammo_nails / min(autocvar_g_balance_sniperrifle_primary_ammo, autocvar_g_balance_sniperrifle_secondary_ammo)));
20         if(self.sniperrifle_bulletcounter > maxbulls || !autocvar_g_balance_sniperrifle_magazinecapacity)
21                 self.sniperrifle_bulletcounter = maxbulls;
22         return (self.sniperrifle_bulletcounter == maxbulls);
23 }
24
25 void W_SniperRifle_ReloadedAndReady()
26 {
27         float t;
28         self.sniperrifle_bulletcounter = autocvar_g_balance_sniperrifle_magazinecapacity;
29         W_SniperRifle_CheckMaxBullets(TRUE);
30         t = ATTACK_FINISHED(self) - autocvar_g_balance_sniperrifle_reloadtime - 1;
31         ATTACK_FINISHED(self) = t;
32         w_ready();
33 }
34
35 float W_SniperRifle_Reload()
36 {
37         float t;
38
39         W_SniperRifle_CheckMaxBullets(TRUE);
40
41         if(self.ammo_nails < min(autocvar_g_balance_sniperrifle_primary_ammo, autocvar_g_balance_sniperrifle_secondary_ammo)) // when we get here, bulletcounter must be 0 or -1
42         {
43                 print("cannot reload... not enough bullets\n");
44                 self.sniperrifle_bulletcounter = -1; // reload later
45                 W_SwitchToOtherWeapon(self);
46                 return 0;
47         }
48         
49         if (self.sniperrifle_bulletcounter >= autocvar_g_balance_sniperrifle_magazinecapacity)
50                 return 0;
51
52         if (self.weaponentity)
53         {
54                 if (self.weaponentity.wframe == WFRAME_RELOAD)
55                         return 0;
56
57                 // allow to switch away while reloading, but this will cause a new reload!
58                 self.weaponentity.state = WS_READY;
59         }
60
61         sound (self, CHAN_WEAPON2, "weapons/campingrifle_reload.wav", VOL_BASE, ATTN_NORM);
62
63         t = max(time, ATTACK_FINISHED(self)) + autocvar_g_balance_sniperrifle_reloadtime + 1;
64         ATTACK_FINISHED(self) = t;
65
66         weapon_thinkf(WFRAME_RELOAD, autocvar_g_balance_sniperrifle_reloadtime, W_SniperRifle_ReloadedAndReady);
67
68         self.sniperrifle_bulletcounter = -1;
69
70         return 1;
71 }
72
73 void W_SniperRifle_CheckReloadAndReady()
74 {
75         w_ready();
76         if(self.sniperrifle_bulletcounter <= 0)
77                 if(W_SniperRifle_Reload())
78                         return;
79 }
80
81 void W_SniperRifle_FireBullet(float pSpread, float pDamage, float pHeadshotAddedDamage, float pForce, float pSpeed, float pLifetime, float pAmmo, float deathtype, float pBulletConstant)
82 {
83         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
84                 self.ammo_nails -= pAmmo;
85
86         if(deathtype & HITTYPE_SECONDARY)
87                 W_SetupShot (self, autocvar_g_antilag_bullets && pSpeed >= autocvar_g_antilag_bullets, 2, "weapons/campingrifle_fire2.wav", CHAN_WEAPON, autocvar_g_balance_sniperrifle_secondary_damage + autocvar_g_balance_sniperrifle_secondary_headshotaddeddamage);
88         else
89                 W_SetupShot (self, autocvar_g_antilag_bullets && pSpeed >= autocvar_g_antilag_bullets, 2, "weapons/campingrifle_fire.wav", CHAN_WEAPON, autocvar_g_balance_sniperrifle_primary_damage + autocvar_g_balance_sniperrifle_primary_headshotaddeddamage);
90
91         pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 2000, 1);
92
93         if(self.BUTTON_ZOOM) // if zoomed, shoot from the eye
94         {
95                 w_shotdir = v_forward;
96                 w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward;
97         }
98
99         if(deathtype & HITTYPE_SECONDARY)
100                 fireBallisticBullet(w_shotorg, w_shotdir, pSpread, pSpeed, pLifetime, pDamage, pHeadshotAddedDamage / pDamage, pForce, deathtype, (autocvar_g_balance_sniperrifle_secondary_tracer ? EF_RED : EF_BLUE), 1, pBulletConstant);
101         else
102                 fireBallisticBullet(w_shotorg, w_shotdir, pSpread, pSpeed, pLifetime, pDamage, pHeadshotAddedDamage / pDamage, pForce, deathtype, (autocvar_g_balance_sniperrifle_primary_tracer ? EF_RED : EF_BLUE), 1, pBulletConstant);
103         endFireBallisticBullet();
104
105         if (autocvar_g_casings >= 2)
106                 SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self);
107         
108         self.sniperrifle_bulletcounter = self.sniperrifle_bulletcounter - 1;
109         W_SniperRifle_CheckMaxBullets(TRUE);
110 }
111
112 void W_SniperRifle_Attack()
113 {
114         W_SniperRifle_FireBullet(autocvar_g_balance_sniperrifle_primary_spread, autocvar_g_balance_sniperrifle_primary_damage, autocvar_g_balance_sniperrifle_primary_headshotaddeddamage, autocvar_g_balance_sniperrifle_primary_force, autocvar_g_balance_sniperrifle_primary_speed, autocvar_g_balance_sniperrifle_primary_lifetime, autocvar_g_balance_sniperrifle_primary_ammo, WEP_SNIPERRIFLE, autocvar_g_balance_sniperrifle_primary_bulletconstant);
115 }
116
117 void W_SniperRifle_Attack2()
118 {
119         W_SniperRifle_FireBullet(autocvar_g_balance_sniperrifle_secondary_spread, autocvar_g_balance_sniperrifle_secondary_damage, autocvar_g_balance_sniperrifle_secondary_headshotaddeddamage, autocvar_g_balance_sniperrifle_secondary_force, autocvar_g_balance_sniperrifle_secondary_speed, autocvar_g_balance_sniperrifle_secondary_lifetime, autocvar_g_balance_sniperrifle_secondary_ammo, WEP_SNIPERRIFLE | HITTYPE_SECONDARY, autocvar_g_balance_sniperrifle_secondary_bulletconstant);
120 }
121
122 void spawnfunc_weapon_sniperrifle (void)
123 {
124         weapon_defaultspawnfunc(WEP_SNIPERRIFLE);
125 }
126
127 // compatibility alias
128 void spawnfunc_weapon_campingrifle (void)
129 {
130         spawnfunc_weapon_sniperrifle();
131 }
132
133 .void(void) sniperrifle_bullethail_attackfunc;
134 .float sniperrifle_bullethail_frame;
135 .float sniperrifle_bullethail_animtime;
136 .float sniperrifle_bullethail_refire;
137 void W_SniperRifle_BulletHail_Continue()
138 {
139         float r, sw, af;
140         W_SniperRifle_CheckReloadAndReady();
141         if(self.sniperrifle_bulletcounter < 0)
142                 return; // reloading, so we are done
143         sw = self.switchweapon; // make it not detect weapon changes as reason to abort firing
144         af = ATTACK_FINISHED(self);
145         self.switchweapon = self.weapon;
146         ATTACK_FINISHED(self) = time;
147         print(ftos(self.ammo_nails), "\n");
148         r = weapon_prepareattack(self.sniperrifle_bullethail_frame == WFRAME_FIRE2, self.sniperrifle_bullethail_refire);
149         if(self.switchweapon == self.weapon)
150                 self.switchweapon = sw;
151         if(r)
152         {
153                 self.sniperrifle_bullethail_attackfunc();
154                 weapon_thinkf(self.sniperrifle_bullethail_frame, self.sniperrifle_bullethail_animtime, W_SniperRifle_BulletHail_Continue);
155                 print("thinkf set\n");
156         }
157         else
158         {
159                 ATTACK_FINISHED(self) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time
160                 print("out of ammo... ", ftos(self.weaponentity.state), "\n");
161         }
162 }
163
164 void W_SniperRifle_BulletHail(float mode, void(void) AttackFunc, float fr, float animtime, float refire)
165 {
166         // if we get here, we have at least one bullet to fire
167         AttackFunc();
168         if(mode)
169         {
170                 // continue hail
171                 self.sniperrifle_bullethail_attackfunc = AttackFunc;
172                 self.sniperrifle_bullethail_frame = fr;
173                 self.sniperrifle_bullethail_animtime = animtime;
174                 self.sniperrifle_bullethail_refire = refire;
175                 weapon_thinkf(fr, animtime, W_SniperRifle_BulletHail_Continue);
176         }
177         else
178         {
179                 // just one shot
180                 weapon_thinkf(fr, animtime, W_SniperRifle_CheckReloadAndReady);
181         }
182 }
183
184 .float bot_secondary_sniperriflemooth;
185 float w_sniperrifle(float req)
186 {
187         float full;
188         if (req == WR_AIM)
189         {
190                 self.BUTTON_ATCK=FALSE;
191                 self.BUTTON_ATCK2=FALSE;
192                 if(vlen(self.origin-self.enemy.origin) > 1000)
193                         self.bot_secondary_sniperriflemooth = 0;
194                 if(self.bot_secondary_sniperriflemooth == 0)
195                 {
196                         if(bot_aim(autocvar_g_balance_sniperrifle_primary_speed, 0, autocvar_g_balance_sniperrifle_primary_lifetime, TRUE))
197                         {
198                                 self.BUTTON_ATCK = TRUE;
199                                 if(random() < 0.01) self.bot_secondary_sniperriflemooth = 1;
200                         }
201                 }
202                 else
203                 {
204                         if(bot_aim(autocvar_g_balance_sniperrifle_secondary_speed, 0, autocvar_g_balance_sniperrifle_secondary_lifetime, TRUE))
205                         {
206                                 self.BUTTON_ATCK2 = TRUE;
207                                 if(random() < 0.03) self.bot_secondary_sniperriflemooth = 0;
208                         }
209                 }
210         }
211         else if (req == WR_THINK)
212         {
213                 if(self.sniperrifle_bulletcounter < 0) // forced reload (e.g. because interrupted)
214                 {
215             self.wish_reload = 1;
216                 }
217                 else
218                 {
219                         self.sniperrifle_accumulator = bound(time - autocvar_g_balance_sniperrifle_bursttime, self.sniperrifle_accumulator, time);
220                         if (self.BUTTON_ATCK)
221                         if (weapon_prepareattack_check(0, autocvar_g_balance_sniperrifle_primary_refire))
222                         if (time >= self.sniperrifle_accumulator + autocvar_g_balance_sniperrifle_primary_burstcost)
223                         {
224                                 weapon_prepareattack_do(0, autocvar_g_balance_sniperrifle_primary_refire);
225                                 W_SniperRifle_BulletHail(autocvar_g_balance_sniperrifle_primary_bullethail, W_SniperRifle_Attack, WFRAME_FIRE1, autocvar_g_balance_sniperrifle_primary_animtime, autocvar_g_balance_sniperrifle_primary_refire);
226                                 self.sniperrifle_accumulator += autocvar_g_balance_sniperrifle_primary_burstcost;
227                         }
228                         if (self.BUTTON_ATCK2)
229                         {       
230                                 if (autocvar_g_balance_sniperrifle_secondary)
231                                 {
232                     if(autocvar_g_balance_sniperrifle_secondary_reload)
233                         self.wish_reload = 1;
234                     else
235                     {
236                         if (weapon_prepareattack_check(1, autocvar_g_balance_sniperrifle_secondary_refire))
237                         if (time >= self.sniperrifle_accumulator + autocvar_g_balance_sniperrifle_secondary_burstcost)
238                         {
239                             weapon_prepareattack_do(1, autocvar_g_balance_sniperrifle_secondary_refire);
240                             W_SniperRifle_BulletHail(autocvar_g_balance_sniperrifle_secondary_bullethail, W_SniperRifle_Attack2, WFRAME_FIRE2, autocvar_g_balance_sniperrifle_secondary_animtime, autocvar_g_balance_sniperrifle_primary_refire);
241                             self.sniperrifle_accumulator += autocvar_g_balance_sniperrifle_secondary_burstcost;
242                         }
243                     }
244                                 }
245                         }
246                 }
247         if(self.wish_reload)
248         {
249             if(self.switchweapon == self.weapon)
250             {
251                 if(self.weaponentity.state == WS_READY)
252                 {
253                     self.wish_reload = 0;
254                     W_SniperRifle_Reload();
255                 }
256             }
257         }
258         }
259         else if (req == WR_PRECACHE)
260         {
261                 precache_model ("models/weapons/g_campingrifle.md3");
262                 precache_model ("models/weapons/v_campingrifle.md3");
263                 precache_model ("models/weapons/h_campingrifle.iqm");
264                 precache_sound ("weapons/campingrifle_reload.wav");
265                 precache_sound ("weapons/campingrifle_fire.wav");
266                 precache_sound ("weapons/campingrifle_fire2.wav");
267         }
268         else if (req == WR_SETUP)
269         {
270                 weapon_setup(WEP_SNIPERRIFLE);
271
272                 full = W_SniperRifle_CheckMaxBullets(TRUE);
273                 if(autocvar_g_balance_sniperrifle_auto_reload_on_switch)
274                         if(!full)
275                                 self.sniperrifle_bulletcounter = -1;
276         }
277         else if (req == WR_CHECKAMMO1)
278                 return self.ammo_nails >= autocvar_g_balance_sniperrifle_primary_ammo;
279         else if (req == WR_CHECKAMMO2)
280                 return self.ammo_nails >= autocvar_g_balance_sniperrifle_secondary_ammo;
281         else if (req == WR_RELOAD)
282         {
283         self.wish_reload = 1;
284         }
285         else if (req == WR_RESETPLAYER)
286         {
287                 self.sniperrifle_accumulator = time - autocvar_g_balance_sniperrifle_bursttime;
288                 self.sniperrifle_bulletcounter = autocvar_g_balance_sniperrifle_magazinecapacity;
289                 W_SniperRifle_CheckMaxBullets(FALSE);
290         }
291         return TRUE;
292 };
293 #endif
294 #ifdef CSQC
295 float w_sniperrifle(float req)
296 {
297         if(req == WR_IMPACTEFFECT)
298         {
299                 vector org2;
300                 org2 = w_org + w_backoff * 2;
301                 pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1);
302                 if(!w_issilent)
303                 {
304                         if(w_random < 0.2)
305                                 sound(self, CHAN_PROJECTILE, "weapons/ric1.wav", VOL_BASE, ATTN_NORM);
306                         else if(w_random < 0.4)
307                                 sound(self, CHAN_PROJECTILE, "weapons/ric2.wav", VOL_BASE, ATTN_NORM);
308                         else if(w_random < 0.5)
309                                 sound(self, CHAN_PROJECTILE, "weapons/ric3.wav", VOL_BASE, ATTN_NORM);
310                 }
311         }
312         else if(req == WR_PRECACHE)
313         {
314                 precache_sound("weapons/ric1.wav");
315                 precache_sound("weapons/ric2.wav");
316                 precache_sound("weapons/ric3.wav");
317         }
318         else if (req == WR_SUICIDEMESSAGE)
319         {
320                 if(w_deathtype & HITTYPE_SECONDARY)
321                         w_deathtypestring = "%s shot themself automatically";
322                 else
323                         w_deathtypestring = "%s sniped themself somehow";
324         }
325         else if (req == WR_KILLMESSAGE)
326         {
327                 if(w_deathtype & HITTYPE_SECONDARY)
328                 {
329                         if(w_deathtype & HITTYPE_BOUNCE)
330                                 w_deathtypestring = "%s failed to hide from %s's bullet hail";
331                         else
332                                 w_deathtypestring = "%s died in %s's bullet hail";
333                 }
334                 else
335                 {
336                         if(w_deathtype & HITTYPE_BOUNCE)
337                         {
338                                 // TODO special headshot message here too?
339                                 w_deathtypestring = "%s failed to hide from %s's rifle";
340                         }
341                         else
342                         {
343                                 if(w_deathtype & HITTYPE_HEADSHOT)
344                                         w_deathtypestring = "%s got hit in the head by %s";
345                                 else
346                                         w_deathtypestring = "%s was sniped by %s";
347                         }
348                 }
349         }
350         return TRUE;
351 }
352 #endif
353 #endif