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