]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_grenadelauncher.qc
Doh, it CAN be done differently than my ugly list. Use WR checks
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_grenadelauncher.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(GRENADE_LAUNCHER, w_glauncher, IT_ROCKETS, 4, WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_MID, "gl", "grenadelauncher", _("Mortar"))
3 #else
4 #ifdef SVQC
5 .float gl_detonate_later;
6 .float gl_bouncecnt;
7
8 void W_GrenadeLauncher_SetAmmoCounter()
9 {
10         // set clip_load to the weapon we have switched to, if the gun uses reloading
11         if(!autocvar_g_balance_grenadelauncher_reload_ammo)
12                 self.clip_load = 0; // also keeps crosshair ammo from displaying
13         else
14         {
15                 self.clip_load = self.grenadelauncher_load;
16                 self.clip_size = autocvar_g_balance_grenadelauncher_reload_ammo; // for the crosshair ammo display
17         }
18 }
19
20 void W_GrenadeLauncher_ReloadedAndReady()
21 {
22         float t;
23
24         // now do the ammo transfer
25         self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
26         while(self.clip_load < autocvar_g_balance_grenadelauncher_reload_ammo && self.ammo_rockets) // make sure we don't add more ammo than we have
27         {
28                 self.clip_load += 1;
29                 self.ammo_rockets -= 1;
30         }
31         self.grenadelauncher_load = self.clip_load;
32
33         t = ATTACK_FINISHED(self) - autocvar_g_balance_grenadelauncher_reload_time - 1;
34         ATTACK_FINISHED(self) = t;
35         w_ready();
36 }
37
38 void W_GrenadeLauncher_Reload()
39 {
40         // return if reloading is disabled for this weapon
41         if(!autocvar_g_balance_grenadelauncher_reload_ammo)
42                 return;
43
44         if(!W_ReloadCheck(self.ammo_rockets, min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo)))
45                 return;
46
47         float t;
48
49         sound (self, CHAN_WEAPON2, "weapons/reload.wav", VOL_BASE, ATTN_NORM);
50
51         t = max(time, ATTACK_FINISHED(self)) + autocvar_g_balance_grenadelauncher_reload_time + 1;
52         ATTACK_FINISHED(self) = t;
53
54         weapon_thinkf(WFRAME_RELOAD, autocvar_g_balance_grenadelauncher_reload_time, W_GrenadeLauncher_ReloadedAndReady);
55
56         self.old_clip_load = self.clip_load;
57         self.clip_load = -1;
58 }
59
60 void W_Grenade_Explode (void)
61 {
62         if(other.takedamage == DAMAGE_AIM)
63                 if(other.classname == "player")
64                         if(IsDifferentTeam(self.owner, other))
65                                 if(other.deadflag == DEAD_NO)
66                                         if(IsFlying(other))
67                                                 AnnounceTo(self.owner, "airshot");
68
69         self.event_damage = SUB_Null;
70         self.takedamage = DAMAGE_NO;
71
72         if(self.movetype == MOVETYPE_NONE)
73                 self.velocity = self.oldvelocity;
74
75         RadiusDamage (self, self.owner, autocvar_g_balance_grenadelauncher_primary_damage, autocvar_g_balance_grenadelauncher_primary_edgedamage, autocvar_g_balance_grenadelauncher_primary_radius, world, autocvar_g_balance_grenadelauncher_primary_force, self.projectiledeathtype, other);
76
77         remove (self);
78 }
79
80 void W_Grenade_Explode2 (void)
81 {
82         if(other.takedamage == DAMAGE_AIM)
83                 if(other.classname == "player")
84                         if(IsDifferentTeam(self.owner, other))
85                                 if(IsFlying(other))
86                                         AnnounceTo(self.owner, "airshot");
87
88         self.event_damage = SUB_Null;
89         self.takedamage = DAMAGE_NO;
90
91         if(self.movetype == MOVETYPE_NONE)
92                 self.velocity = self.oldvelocity;
93
94         RadiusDamage (self, self.owner, autocvar_g_balance_grenadelauncher_secondary_damage, autocvar_g_balance_grenadelauncher_secondary_edgedamage, autocvar_g_balance_grenadelauncher_secondary_radius, world, autocvar_g_balance_grenadelauncher_secondary_force, self.projectiledeathtype, other);
95
96         remove (self);
97 }
98
99 void W_Grenade_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
100 {
101         if (self.health <= 0)
102                 return;
103         self.health = self.health - damage;
104         if (self.health <= 0)
105         {
106                 W_PrepareExplosionByDamage(attacker, self.think);
107         }
108 }
109
110 void W_Grenade_Think1 (void)
111 {
112         self.nextthink = time;
113         if (time > self.cnt)
114         {
115                 other = world;
116                 self.projectiledeathtype |= HITTYPE_BOUNCE;
117                 W_Grenade_Explode ();
118                 return;
119         }
120         if(self.gl_detonate_later && self.gl_bouncecnt >= autocvar_g_balance_grenadelauncher_primary_remote_minbouncecnt)
121                 W_Grenade_Explode();
122 }
123
124 void W_Grenade_Touch1 (void)
125 {
126         PROJECTILE_TOUCH;
127         if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_primary_type == 0) // always explode when hitting a player, or if normal mortar projectile
128         {
129                 self.use ();
130         }
131         else if (autocvar_g_balance_grenadelauncher_primary_type == 1) // bounce
132         {
133                 float r;
134                 r = random() * 6;
135                 if(r < 1)
136                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM);
137                 else if(r < 2)
138                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM);
139                 else if(r < 3)
140                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM);
141                 else if(r < 4)
142                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM);
143                 else if(r < 5)
144                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM);
145                 else
146                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM);
147                 self.projectiledeathtype |= HITTYPE_BOUNCE;
148                 self.gl_bouncecnt += 1;
149         }
150         else if(autocvar_g_balance_grenadelauncher_primary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
151         {
152                 spamsound (self, CHAN_PROJECTILE, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM);
153
154                 // let it stick whereever it is
155                 self.oldvelocity = self.velocity;
156                 self.velocity = '0 0 0';
157                 self.movetype = MOVETYPE_NONE; // also disables gravity
158                 self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
159                 UpdateCSQCProjectile(self);
160
161                 // do not respond to any more touches
162                 self.solid = SOLID_NOT;
163
164                 self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_primary_lifetime2);
165         }
166 }
167
168 void W_Grenade_Touch2 (void)
169 {
170         PROJECTILE_TOUCH;
171         if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_secondary_type == 0) // always explode when hitting a player, or if normal mortar projectile
172         {
173                 self.use ();
174         }
175         else if (autocvar_g_balance_grenadelauncher_secondary_type == 1) // bounce
176         {
177                 float r;
178                 r = random() * 6;
179                 if(r < 1)
180                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM);
181                 else if(r < 2)
182                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM);
183                 else if(r < 3)
184                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM);
185                 else if(r < 4)
186                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM);
187                 else if(r < 5)
188                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM);
189                 else
190                         spamsound (self, CHAN_PROJECTILE, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM);
191                 self.projectiledeathtype |= HITTYPE_BOUNCE;
192                 self.gl_bouncecnt += 1;
193         }
194         else if(autocvar_g_balance_grenadelauncher_secondary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
195         {
196                 spamsound (self, CHAN_PROJECTILE, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM);
197
198                 // let it stick whereever it is
199                 self.oldvelocity = self.velocity;
200                 self.velocity = '0 0 0';
201                 self.movetype = MOVETYPE_NONE; // also disables gravity
202                 self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
203                 UpdateCSQCProjectile(self);
204
205                 // do not respond to any more touches
206                 self.solid = SOLID_NOT;
207
208                 self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_secondary_lifetime2);
209         }
210 }
211
212 void W_Grenade_Attack (void)
213 {
214         local entity gren;
215
216         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
217         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
218         {
219                 if(autocvar_g_balance_grenadelauncher_reload_ammo)
220                 {
221                         self.clip_load -= autocvar_g_balance_grenadelauncher_primary_ammo;
222                         self.grenadelauncher_load = self.clip_load;
223                 }
224                 else
225                         self.ammo_rockets -= autocvar_g_balance_grenadelauncher_primary_ammo;
226         }
227
228         W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CHAN_WEAPON, autocvar_g_balance_grenadelauncher_primary_damage);
229         w_shotdir = v_forward; // no TrueAim for grenades please
230
231         pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
232
233         gren = spawn ();
234         gren.owner = self;
235         gren.classname = "grenade";
236         gren.bot_dodge = TRUE;
237         gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_primary_damage;
238         gren.movetype = MOVETYPE_BOUNCE;
239         gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor;
240         gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop;
241         PROJECTILE_MAKETRIGGER(gren);
242         gren.projectiledeathtype = WEP_GRENADE_LAUNCHER;
243         setorigin(gren, w_shotorg);
244         setsize(gren, '-3 -3 -3', '3 3 3');
245
246         gren.cnt = time + autocvar_g_balance_grenadelauncher_primary_lifetime;
247         gren.nextthink = time;
248         gren.think = W_Grenade_Think1;
249         gren.use = W_Grenade_Explode;
250         gren.touch = W_Grenade_Touch1;
251
252         gren.takedamage = DAMAGE_YES;
253         gren.health = autocvar_g_balance_grenadelauncher_primary_health;
254         gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale;
255         gren.event_damage = W_Grenade_Damage;
256         W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary);
257
258         gren.angles = vectoangles (gren.velocity);
259         gren.flags = FL_PROJECTILE;
260
261         if(autocvar_g_balance_grenadelauncher_primary_type == 0 || autocvar_g_balance_grenadelauncher_primary_type == 2)
262                 CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
263         else
264                 CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE);
265
266         other = gren; MUTATOR_CALLHOOK(EditProjectile);
267 }
268
269 void W_Grenade_Attack2 (void)
270 {
271         local entity gren;
272
273         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
274         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
275         {
276                 if(autocvar_g_balance_grenadelauncher_reload_ammo)
277                 {
278                         self.clip_load -= autocvar_g_balance_grenadelauncher_secondary_ammo;
279                         self.grenadelauncher_load = self.clip_load;
280                 }
281                 else
282                         self.ammo_rockets -= autocvar_g_balance_grenadelauncher_secondary_ammo;
283         }
284
285         W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CHAN_WEAPON, autocvar_g_balance_grenadelauncher_secondary_damage);
286         w_shotdir = v_forward; // no TrueAim for grenades please
287
288         pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
289
290         gren = spawn ();
291         gren.owner = self;
292         gren.classname = "grenade";
293         gren.bot_dodge = TRUE;
294         gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_secondary_damage;
295         gren.movetype = MOVETYPE_BOUNCE;
296         gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor;
297         gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop;
298         PROJECTILE_MAKETRIGGER(gren);
299         gren.projectiledeathtype = WEP_GRENADE_LAUNCHER | HITTYPE_SECONDARY;
300         setorigin(gren, w_shotorg);
301         setsize(gren, '-3 -3 -3', '3 3 3');
302
303         gren.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime;
304         gren.think = adaptor_think2use_hittype_splash;
305         gren.use = W_Grenade_Explode2;
306         gren.touch = W_Grenade_Touch2;
307
308         gren.takedamage = DAMAGE_YES;
309         gren.health = autocvar_g_balance_grenadelauncher_secondary_health;
310         gren.damageforcescale = autocvar_g_balance_grenadelauncher_secondary_damageforcescale;
311         gren.event_damage = W_Grenade_Damage;
312         W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_secondary);
313
314         gren.angles = vectoangles (gren.velocity);
315         gren.flags = FL_PROJECTILE;
316
317         if(autocvar_g_balance_grenadelauncher_secondary_type == 0 || autocvar_g_balance_grenadelauncher_secondary_type == 2)
318                 CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
319         else
320                 CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE);
321
322         other = gren; MUTATOR_CALLHOOK(EditProjectile);
323 }
324
325 void spawnfunc_weapon_grenadelauncher (void)
326 {
327         weapon_defaultspawnfunc(WEP_GRENADE_LAUNCHER);
328 }
329
330 .float bot_secondary_grenademooth;
331 float w_glauncher(float req)
332 {
333         entity nade;
334         float nadefound;
335
336         if (req == WR_AIM)
337         {
338                 self.BUTTON_ATCK = FALSE;
339                 self.BUTTON_ATCK2 = FALSE;
340                 if (self.bot_secondary_grenademooth == 0)
341                 {
342                         if(bot_aim(autocvar_g_balance_grenadelauncher_primary_speed, autocvar_g_balance_grenadelauncher_primary_speed_up, autocvar_g_balance_grenadelauncher_primary_lifetime, TRUE))
343                         {
344                                 self.BUTTON_ATCK = TRUE;
345                                 if(random() < 0.01) self.bot_secondary_grenademooth = 1;
346                         }
347                 }
348                 else
349                 {
350                         if(bot_aim(autocvar_g_balance_grenadelauncher_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_grenadelauncher_secondary_lifetime, TRUE))
351                         {
352                                 self.BUTTON_ATCK2 = TRUE;
353                                 if(random() < 0.02) self.bot_secondary_grenademooth = 0;
354                         }
355                 }
356         }
357         else if (req == WR_THINK)
358         {
359                 if(autocvar_g_balance_grenadelauncher_reload_ammo && self.clip_load < min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo)) // forced reload
360                         W_GrenadeLauncher_Reload();
361                 else if (self.BUTTON_ATCK)
362                 {
363                         if (weapon_prepareattack(0, autocvar_g_balance_grenadelauncher_primary_refire))
364                         {
365                                 W_Grenade_Attack();
366                                 weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_grenadelauncher_primary_animtime, w_ready);
367                         }
368                 }
369                 else if (self.BUTTON_ATCK2)
370                 {
371                         if (cvar("g_balance_grenadelauncher_secondary_remote_detonateprimary"))
372                         {
373                                 nadefound = 0;
374                                 for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.owner == self)
375                                 {
376                                         if(!nade.gl_detonate_later)
377                                         {
378                                                 nade.gl_detonate_later = TRUE;
379                                                 nadefound = 1;
380                                         }
381                                 }
382                                 if(nadefound)
383                                         sound (self, CHAN_WEAPON2, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM);
384                         }
385                         else if (weapon_prepareattack(1, autocvar_g_balance_grenadelauncher_secondary_refire))
386                         {
387                                 W_Grenade_Attack2();
388                                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_grenadelauncher_secondary_animtime, w_ready);
389                         }
390                 }
391         if(self.wish_reload)
392         {
393             if(self.switchweapon == self.weapon)
394             {
395                 if(self.weaponentity.state == WS_READY)
396                 {
397                     self.wish_reload = 0;
398                     W_GrenadeLauncher_Reload();
399                 }
400             }
401         }
402         }
403         else if (req == WR_PRECACHE)
404         {
405                 precache_model ("models/weapons/g_gl.md3");
406                 precache_model ("models/weapons/v_gl.md3");
407                 precache_model ("models/weapons/h_gl.iqm");
408                 precache_sound ("weapons/grenade_bounce1.wav");
409                 precache_sound ("weapons/grenade_bounce2.wav");
410                 precache_sound ("weapons/grenade_bounce3.wav");
411                 precache_sound ("weapons/grenade_bounce4.wav");
412                 precache_sound ("weapons/grenade_bounce5.wav");
413                 precache_sound ("weapons/grenade_bounce6.wav");
414                 precache_sound ("weapons/grenade_stick.wav");
415                 precache_sound ("weapons/grenade_fire.wav");
416                 precache_sound ("weapons/reload.wav");
417         }
418         else if (req == WR_SETUP)
419         {
420                 weapon_setup(WEP_GRENADE_LAUNCHER);
421                 W_GrenadeLauncher_SetAmmoCounter();
422         }
423         else if (req == WR_CHECKAMMO1)
424         {
425                 if(autocvar_g_balance_grenadelauncher_reload_ammo)
426                         return self.grenadelauncher_load >= autocvar_g_balance_grenadelauncher_primary_ammo;
427                 else
428                         return self.ammo_rockets >= autocvar_g_balance_grenadelauncher_primary_ammo;
429         }
430         else if (req == WR_CHECKAMMO2)
431         {
432                 if(autocvar_g_balance_grenadelauncher_reload_ammo)
433                         return self.grenadelauncher_load >= autocvar_g_balance_grenadelauncher_secondary_ammo;
434                 else
435                         return self.ammo_rockets >= autocvar_g_balance_grenadelauncher_secondary_ammo;
436         }
437         else if (req == WR_RELOAD)
438         {
439                 W_GrenadeLauncher_Reload();
440         }
441         else if (req == WR_SWITCHABLE)
442         {
443                 // checks if this weapon can be switched to, when reloading is enabled
444                 // returns true if there's either enough load in the weapon to use it,
445                 // or we have enough ammo to reload the weapon to a usable point
446                 float ammo_amount;
447                 ammo_amount = min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo);
448                 return self.grenadelauncher_load >= ammo_amount || self.ammo_rockets >= ammo_amount;
449         }
450         return TRUE;
451 };
452 #endif
453 #ifdef CSQC
454 float w_glauncher(float req)
455 {
456         if(req == WR_IMPACTEFFECT)
457         {
458                 vector org2;
459                 org2 = w_org + w_backoff * 12;
460                 pointparticles(particleeffectnum("grenade_explode"), org2, '0 0 0', 1);
461                 if(!w_issilent)
462                         sound(self, CHAN_PROJECTILE, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
463         }
464         else if(req == WR_PRECACHE)
465         {
466                 precache_sound("weapons/grenade_impact.wav");
467         }
468         else if (req == WR_SUICIDEMESSAGE)
469         {
470                 if(w_deathtype & HITTYPE_SECONDARY)
471                         w_deathtypestring = "%s tried out his own grenade";
472                 else
473                         w_deathtypestring = "%s detonated";
474         }
475         else if (req == WR_KILLMESSAGE)
476         {
477                 if(w_deathtype & HITTYPE_SPLASH)
478                         if(w_deathtype & HITTYPE_BOUNCE) // (must be secondary then)
479                                 w_deathtypestring = "%s didn't see %s's grenade";
480                         else // unchecked: SECONDARY
481                                 w_deathtypestring = "%s almost dodged %s's grenade";
482                 else // unchecked: SECONDARY, BOUNCE
483                         w_deathtypestring = "%s ate %s's grenade";
484         }
485         return TRUE;
486 }
487 #endif
488 #endif