174b3c400cc6ef8f3ed2674441c23c5dc6141295
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_campingrifle.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(CAMPINGRIFLE, w_campingrifle, IT_NAILS, 3, WEP_FLAG_NORMAL | WEP_TYPE_HITSCAN, BOT_PICKUP_RATING_MID, "campingrifle", "campingrifle", "Rifle");
3 #else
4 #ifdef SVQC
5 //Camping 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 campingrifle_accumulator;
10
11 float W_CampingRifle_CheckMaxBullets(float checkammo)
12 {
13         float maxbulls;
14         maxbulls = cvar("g_balance_campingrifle_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(cvar("g_balance_campingrifle_primary_ammo"), cvar("g_balance_campingrifle_secondary_ammo"))));
20         if(self.campingrifle_bulletcounter > maxbulls || !cvar("g_balance_campingrifle_magazinecapacity"))
21                 self.campingrifle_bulletcounter = maxbulls;
22         return (self.campingrifle_bulletcounter == maxbulls);
23 }
24
25 void W_CampingRifle_ReloadedAndReady()
26 {
27         float t;
28         self.campingrifle_bulletcounter = cvar("g_balance_campingrifle_magazinecapacity");
29         W_CampingRifle_CheckMaxBullets(TRUE);
30         t = ATTACK_FINISHED(self) - cvar("g_balance_campingrifle_reloadtime") - 1;
31         ATTACK_FINISHED(self) = t;
32         w_ready();
33 }
34
35 float W_CampingRifle_Reload()
36 {
37         float t;
38
39         W_CampingRifle_CheckMaxBullets(TRUE);
40
41         if(self.ammo_nails < min(cvar("g_balance_campingrifle_primary_ammo"), cvar("g_balance_campingrifle_secondary_ammo"))) // when we get here, bulletcounter must be 0 or -1
42         {
43                 print("cannot reload... not enough bullets\n");
44                 self.campingrifle_bulletcounter = -1; // reload later
45                 W_SwitchToOtherWeapon(self);
46                 return 0;
47         }
48         
49         if (self.campingrifle_bulletcounter >= cvar("g_balance_campingrifle_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)) + cvar("g_balance_campingrifle_reloadtime") + 1;
64         ATTACK_FINISHED(self) = t;
65
66         weapon_thinkf(WFRAME_RELOAD, cvar("g_balance_campingrifle_reloadtime"), W_CampingRifle_ReloadedAndReady);
67
68         self.campingrifle_bulletcounter = -1;
69
70         return 1;
71 }
72
73 void W_CampingRifle_CheckReloadAndReady()
74 {
75         w_ready();
76         if(self.campingrifle_bulletcounter <= 0)
77                 if(W_CampingRifle_Reload())
78                         return;
79 }
80
81 void W_CampingRifle_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, cvar("g_antilag_bullets") && pSpeed >= cvar("g_antilag_bullets"), 2, "weapons/campingrifle_fire2.wav", cvar("g_balance_campingrifle_secondary_damage"));
88         else
89                 W_SetupShot (self, cvar("g_antilag_bullets") && pSpeed >= cvar("g_antilag_bullets"), 2, "weapons/campingrifle_fire.wav", cvar("g_balance_campingrifle_primary_damage"));
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         fireBallisticBullet(w_shotorg, w_shotdir, pSpread, pSpeed, pLifetime, pDamage, pHeadshotAddedDamage / pDamage, pForce, deathtype, (cvar("g_balance_campingrifle_tracer") ? EF_RED : EF_BLUE), 1, pBulletConstant);
100         endFireBallisticBullet();
101
102         if (cvar("g_casings") >= 2)
103                 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);
104         
105         self.campingrifle_bulletcounter = self.campingrifle_bulletcounter - 1;
106         W_CampingRifle_CheckMaxBullets(TRUE);
107 }
108
109 void W_CampingRifle_Attack()
110 {
111         W_CampingRifle_FireBullet(cvar("g_balance_campingrifle_primary_spread"), cvar("g_balance_campingrifle_primary_damage"), cvar("g_balance_campingrifle_primary_headshotaddeddamage"), cvar("g_balance_campingrifle_primary_force"), cvar("g_balance_campingrifle_primary_speed"), cvar("g_balance_campingrifle_primary_lifetime"), cvar("g_balance_campingrifle_primary_ammo"), WEP_CAMPINGRIFLE, cvar("g_balance_campingrifle_primary_bulletconstant"));
112 }
113
114 void W_CampingRifle_Attack2()
115 {
116         W_CampingRifle_FireBullet(cvar("g_balance_campingrifle_secondary_spread"), cvar("g_balance_campingrifle_secondary_damage"), cvar("g_balance_campingrifle_secondary_headshotaddeddamage"), cvar("g_balance_campingrifle_secondary_force"), cvar("g_balance_campingrifle_secondary_speed"), cvar("g_balance_campingrifle_secondary_lifetime"), cvar("g_balance_campingrifle_secondary_ammo"), WEP_CAMPINGRIFLE | HITTYPE_SECONDARY, cvar("g_balance_campingrifle_secondary_bulletconstant"));
117 }
118
119 void spawnfunc_weapon_campingrifle (void)
120 {
121         weapon_defaultspawnfunc(WEP_CAMPINGRIFLE);
122 }
123
124 .void(void) campingrifle_bullethail_attackfunc;
125 .float campingrifle_bullethail_frame;
126 .float campingrifle_bullethail_animtime;
127 .float campingrifle_bullethail_refire;
128 void W_CampingRifle_BulletHail_Continue()
129 {
130         float r, sw, af;
131         W_CampingRifle_CheckReloadAndReady();
132         if(self.campingrifle_bulletcounter < 0)
133                 return; // reloading, so we are done
134         sw = self.switchweapon; // make it not detect weapon changes as reason to abort firing
135         af = ATTACK_FINISHED(self);
136         self.switchweapon = self.weapon;
137         ATTACK_FINISHED(self) = time;
138         print(ftos(self.ammo_nails), "\n");
139         r = weapon_prepareattack(self.campingrifle_bullethail_frame == WFRAME_FIRE2, self.campingrifle_bullethail_refire);
140         if(self.switchweapon == self.weapon)
141                 self.switchweapon = sw;
142         if(r)
143         {
144                 self.campingrifle_bullethail_attackfunc();
145                 weapon_thinkf(self.campingrifle_bullethail_frame, self.campingrifle_bullethail_animtime, W_CampingRifle_BulletHail_Continue);
146                 print("thinkf set\n");
147         }
148         else
149         {
150                 ATTACK_FINISHED(self) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time
151                 print("out of ammo... ", ftos(self.weaponentity.state), "\n");
152         }
153 }
154
155 void W_CampingRifle_BulletHail(float mode, void(void) AttackFunc, float fr, float animtime, float refire)
156 {
157         // if we get here, we have at least one bullet to fire
158         AttackFunc();
159         if(mode)
160         {
161                 // continue hail
162                 self.campingrifle_bullethail_attackfunc = AttackFunc;
163                 self.campingrifle_bullethail_frame = fr;
164                 self.campingrifle_bullethail_animtime = animtime;
165                 self.campingrifle_bullethail_refire = refire;
166                 weapon_thinkf(fr, animtime, W_CampingRifle_BulletHail_Continue);
167         }
168         else
169         {
170                 // just one shot
171                 weapon_thinkf(fr, animtime, W_CampingRifle_CheckReloadAndReady);
172         }
173 }
174
175 .float bot_secondary_campingriflemooth;
176 float w_campingrifle(float req)
177 {
178         float full;
179         if (req == WR_AIM)
180         {
181                 self.BUTTON_ATCK=FALSE;
182                 self.BUTTON_ATCK2=FALSE;
183                 if(vlen(self.origin-self.enemy.origin) > 1000)
184                         self.bot_secondary_campingriflemooth = 0;
185                 if(self.bot_secondary_campingriflemooth == 0)
186                 {
187                         if(bot_aim(cvar("g_balance_campingrifle_primary_speed"), 0, cvar("g_balance_campingrifle_primary_lifetime"), TRUE))
188                         {
189                                 self.BUTTON_ATCK = TRUE;
190                                 if(random() < 0.01) self.bot_secondary_campingriflemooth = 1;
191                         }
192                 }
193                 else
194                 {
195                         if(bot_aim(cvar("g_balance_campingrifle_secondary_speed"), 0, cvar("g_balance_campingrifle_secondary_lifetime"), TRUE))
196                         {
197                                 self.BUTTON_ATCK2 = TRUE;
198                                 if(random() < 0.03) self.bot_secondary_campingriflemooth = 0;
199                         }
200                 }
201         }
202         else if (req == WR_THINK)
203         {
204                 if(self.campingrifle_bulletcounter < 0) // forced reload (e.g. because interrupted)
205                 {
206                         if(self.switchweapon == self.weapon)
207                         if(self.weaponentity.state == WS_READY)
208                                 W_CampingRifle_Reload();
209                 }
210                 else
211                 {
212                         self.campingrifle_accumulator = bound(time - cvar("g_balance_campingrifle_bursttime"), self.campingrifle_accumulator, time);
213                         if (self.BUTTON_ATCK)
214                         if (weapon_prepareattack_check(0, cvar("g_balance_campingrifle_primary_refire")))
215                         if (time >= self.campingrifle_accumulator + cvar("g_balance_campingrifle_primary_burstcost"))
216                         {
217                                 weapon_prepareattack_do(0, cvar("g_balance_campingrifle_primary_refire"));
218                                 W_CampingRifle_BulletHail(cvar("g_balance_campingrifle_primary_bullethail"), W_CampingRifle_Attack, WFRAME_FIRE1, cvar("g_balance_campingrifle_primary_animtime"), cvar("g_balance_campingrifle_primary_refire"));
219                                 self.campingrifle_accumulator += cvar("g_balance_campingrifle_primary_burstcost");
220                         }
221                         if (self.BUTTON_ATCK2)
222                         if (weapon_prepareattack_check(1, cvar("g_balance_campingrifle_secondary_refire")))
223                         if (time >= self.campingrifle_accumulator + cvar("g_balance_campingrifle_secondary_burstcost"))
224                         {
225                                 weapon_prepareattack_do(1, cvar("g_balance_campingrifle_secondary_refire"));
226                                 W_CampingRifle_BulletHail(cvar("g_balance_campingrifle_secondary_bullethail"), W_CampingRifle_Attack2, WFRAME_FIRE2, cvar("g_balance_campingrifle_secondary_animtime"), cvar("g_balance_campingrifle_primary_refire"));
227                                 self.campingrifle_accumulator += cvar("g_balance_campingrifle_secondary_burstcost");
228                         }
229                 }
230         }
231         else if (req == WR_PRECACHE)
232         {               
233                 precache_model ("models/weapons/g_campingrifle.md3");
234                 precache_model ("models/weapons/v_campingrifle.md3");
235                 precache_model ("models/weapons/h_campingrifle.iqm");
236                 precache_sound ("weapons/campingrifle_reload.wav");
237                 precache_sound ("weapons/campingrifle_fire.wav");
238                 precache_sound ("weapons/campingrifle_fire2.wav");
239         }
240         else if (req == WR_SETUP)
241         {
242                 weapon_setup(WEP_CAMPINGRIFLE);
243
244                 full = W_CampingRifle_CheckMaxBullets(TRUE);
245                 if(cvar("g_balance_campingrifle_auto_reload_after_changing_weapons"))
246                         if(!full)
247                                 self.campingrifle_bulletcounter = -1;
248         }
249         else if (req == WR_CHECKAMMO1)
250                 return self.ammo_nails >= cvar("g_balance_campingrifle_primary_ammo");
251         else if (req == WR_CHECKAMMO2)
252                 return self.ammo_nails >= cvar("g_balance_campingrifle_secondary_ammo");
253         else if (req == WR_SUICIDEMESSAGE)
254         {
255                 if(w_deathtype & HITTYPE_SECONDARY)
256                         w_deathtypestring = "shot themself automatically";
257                 else
258                         w_deathtypestring = "sniped themself somehow";
259         }
260         else if (req == WR_KILLMESSAGE)
261         {
262                 if(w_deathtype & HITTYPE_SECONDARY)
263                 {
264                         if(w_deathtype & HITTYPE_BOUNCE)
265                                 w_deathtypestring = "failed to hide from #'s bullet hail";
266                         else
267                                 w_deathtypestring = "died in #'s bullet hail";
268                 }
269                 else
270                 {
271                         if(w_deathtype & HITTYPE_BOUNCE)
272                         {
273                                 // TODO special headshot message here too?
274                                 w_deathtypestring = "failed to hide from #'s rifle";
275                         }
276                         else
277                         {
278                                 if(w_deathtype & HITTYPE_HEADSHOT)
279                                         w_deathtypestring = "got hit in the head by #";
280                                 else
281                                         w_deathtypestring = "was sniped by #";
282                         }
283                 }
284         }
285         else if (req == WR_RELOAD)
286         {
287                 W_CampingRifle_Reload();
288         }
289         else if (req == WR_RESETPLAYER)
290         {
291                 self.campingrifle_accumulator = time - cvar("g_balance_campingrifle_bursttime");
292                 self.campingrifle_bulletcounter = cvar("g_balance_campingrifle_magazinecapacity");
293                 W_CampingRifle_CheckMaxBullets(FALSE);
294         }
295         return TRUE;
296 };
297 #endif
298 #ifdef CSQC
299 float w_campingrifle(float req)
300 {
301         return TRUE;
302 }
303 #endif
304 #endif