]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_sniperrifle.qc
Merge branch 'master' into samual/keepaway
[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                         if(self.switchweapon == self.weapon)
216                         if(self.weaponentity.state == WS_READY)
217                                 W_SniperRifle_Reload();
218                 }
219                 else
220                 {
221                         self.sniperrifle_accumulator = bound(time - autocvar_g_balance_sniperrifle_bursttime, self.sniperrifle_accumulator, time);
222                         if (self.BUTTON_ATCK)
223                         if (weapon_prepareattack_check(0, autocvar_g_balance_sniperrifle_primary_refire))
224                         if (time >= self.sniperrifle_accumulator + autocvar_g_balance_sniperrifle_primary_burstcost)
225                         {
226                                 weapon_prepareattack_do(0, autocvar_g_balance_sniperrifle_primary_refire);
227                                 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);
228                                 self.sniperrifle_accumulator += autocvar_g_balance_sniperrifle_primary_burstcost;
229                         }
230                         if (self.BUTTON_ATCK2)
231                         {       
232                                 if (autocvar_g_balance_sniperrifle_secondary)
233                                 {
234                                         if (weapon_prepareattack_check(1, autocvar_g_balance_sniperrifle_secondary_refire))
235                                         if (time >= self.sniperrifle_accumulator + autocvar_g_balance_sniperrifle_secondary_burstcost)
236                                         {
237                                                 weapon_prepareattack_do(1, autocvar_g_balance_sniperrifle_secondary_refire);
238                                                 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);
239                                                 self.sniperrifle_accumulator += autocvar_g_balance_sniperrifle_secondary_burstcost;
240                                         }
241                                 }
242                         }
243                 }
244         }
245         else if (req == WR_PRECACHE)
246         {
247                 precache_model ("models/weapons/g_campingrifle.md3");
248                 precache_model ("models/weapons/v_campingrifle.md3");
249                 precache_model ("models/weapons/h_campingrifle.iqm");
250                 precache_sound ("weapons/campingrifle_reload.wav");
251                 precache_sound ("weapons/campingrifle_fire.wav");
252                 precache_sound ("weapons/campingrifle_fire2.wav");
253         }
254         else if (req == WR_SETUP)
255         {
256                 weapon_setup(WEP_SNIPERRIFLE);
257
258                 full = W_SniperRifle_CheckMaxBullets(TRUE);
259                 if(autocvar_g_balance_sniperrifle_auto_reload_on_switch)
260                         if(!full)
261                                 self.sniperrifle_bulletcounter = -1;
262         }
263         else if (req == WR_CHECKAMMO1)
264                 return self.ammo_nails >= autocvar_g_balance_sniperrifle_primary_ammo;
265         else if (req == WR_CHECKAMMO2)
266                 return self.ammo_nails >= autocvar_g_balance_sniperrifle_secondary_ammo;
267         else if (req == WR_RELOAD)
268         {
269                 W_SniperRifle_Reload();
270         }
271         else if (req == WR_RESETPLAYER)
272         {
273                 self.sniperrifle_accumulator = time - autocvar_g_balance_sniperrifle_bursttime;
274                 self.sniperrifle_bulletcounter = autocvar_g_balance_sniperrifle_magazinecapacity;
275                 W_SniperRifle_CheckMaxBullets(FALSE);
276         }
277         return TRUE;
278 };
279 #endif
280 #ifdef CSQC
281 float w_sniperrifle(float req)
282 {
283         if(req == WR_IMPACTEFFECT)
284         {
285                 vector org2;
286                 org2 = w_org + w_backoff * 2;
287                 pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1);
288                 if(!w_issilent)
289                 {
290                         if(w_random < 0.2)
291                                 sound(self, CHAN_PROJECTILE, "weapons/ric1.wav", VOL_BASE, ATTN_NORM);
292                         else if(w_random < 0.4)
293                                 sound(self, CHAN_PROJECTILE, "weapons/ric2.wav", VOL_BASE, ATTN_NORM);
294                         else if(w_random < 0.5)
295                                 sound(self, CHAN_PROJECTILE, "weapons/ric3.wav", VOL_BASE, ATTN_NORM);
296                 }
297         }
298         else if(req == WR_PRECACHE)
299         {
300                 precache_sound("weapons/ric1.wav");
301                 precache_sound("weapons/ric2.wav");
302                 precache_sound("weapons/ric3.wav");
303         }
304         else if (req == WR_SUICIDEMESSAGE)
305         {
306                 if(w_deathtype & HITTYPE_SECONDARY)
307                         w_deathtypestring = "%s shot themself automatically";
308                 else
309                         w_deathtypestring = "%s sniped themself somehow";
310         }
311         else if (req == WR_KILLMESSAGE)
312         {
313                 if(w_deathtype & HITTYPE_SECONDARY)
314                 {
315                         if(w_deathtype & HITTYPE_BOUNCE)
316                                 w_deathtypestring = "%s failed to hide from %s's bullet hail";
317                         else
318                                 w_deathtypestring = "%s died in %s's bullet hail";
319                 }
320                 else
321                 {
322                         if(w_deathtype & HITTYPE_BOUNCE)
323                         {
324                                 // TODO special headshot message here too?
325                                 w_deathtypestring = "%s failed to hide from %s's rifle";
326                         }
327                         else
328                         {
329                                 if(w_deathtype & HITTYPE_HEADSHOT)
330                                         w_deathtypestring = "%s got hit in the head by %s";
331                                 else
332                                         w_deathtypestring = "%s was sniped by %s";
333                         }
334                 }
335         }
336         return TRUE;
337 }
338 #endif
339 #endif