]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/w_mortar.qc
Bouncing effects for mortar grenades
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / w_mortar.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(
3 /* WEP_##id */ MORTAR,
4 /* function */ w_glauncher,
5 /* ammotype */ IT_ROCKETS,
6 /* impulse  */ 4,
7 /* flags    */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
8 /* rating   */ BOT_PICKUP_RATING_MID,
9 /* model    */ "gl",
10 /* netname  */ "grenadelauncher",
11 /* fullname */ _("Mortar")
12 );
13
14 #define MORTAR_SETTINGS(w_cvar,w_prop) \
15         w_cvar(WEP_MORTAR, mortar, MO_BOTH, ammo) \
16         w_cvar(WEP_MORTAR, mortar, MO_BOTH, animtime) \
17         w_cvar(WEP_MORTAR, mortar, MO_NONE, bouncefactor) \
18         w_cvar(WEP_MORTAR, mortar, MO_NONE, bouncestop) \
19         w_cvar(WEP_MORTAR, mortar, MO_BOTH, damage) \
20         w_cvar(WEP_MORTAR, mortar, MO_BOTH, damageforcescale) \
21         w_cvar(WEP_MORTAR, mortar, MO_BOTH, edgedamage) \
22         w_cvar(WEP_MORTAR, mortar, MO_BOTH, force) \
23         w_cvar(WEP_MORTAR, mortar, MO_BOTH, health) \
24         w_cvar(WEP_MORTAR, mortar, MO_BOTH, lifetime) \
25         w_cvar(WEP_MORTAR, mortar, MO_SEC,  lifetime_bounce) \
26         w_cvar(WEP_MORTAR, mortar, MO_BOTH, lifetime_stick) \
27         w_cvar(WEP_MORTAR, mortar, MO_BOTH, radius) \
28         w_cvar(WEP_MORTAR, mortar, MO_BOTH, refire) \
29         w_cvar(WEP_MORTAR, mortar, MO_PRI,  remote_minbouncecnt) \
30         w_cvar(WEP_MORTAR, mortar, MO_BOTH, speed) \
31         w_cvar(WEP_MORTAR, mortar, MO_BOTH, speed_up) \
32         w_cvar(WEP_MORTAR, mortar, MO_BOTH, type) \
33         w_prop(WEP_MORTAR, mortar, reloading_ammo, reload_ammo) \
34         w_prop(WEP_MORTAR, mortar, reloading_time, reload_time) \
35         w_prop(WEP_MORTAR, mortar, switchdelay_raise, switchdelay_raise) \
36         w_prop(WEP_MORTAR, mortar, switchdelay_drop, switchdelay_drop)
37
38 #ifdef SVQC
39 MORTAR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
40 .float gl_detonate_later;
41 .float gl_bouncecnt;
42 #endif
43 #else
44 #ifdef SVQC
45
46 void W_Grenade_Explode (void)
47 {
48         if(other.takedamage == DAMAGE_AIM)
49                 if(IS_PLAYER(other))
50                         if(DIFF_TEAM(self.realowner, other))
51                                 if(other.deadflag == DEAD_NO)
52                                         if(IsFlying(other))
53                                                 Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
54
55         self.event_damage = func_null;
56         self.takedamage = DAMAGE_NO;
57
58         if(self.movetype == MOVETYPE_NONE)
59                 self.velocity = self.oldvelocity;
60
61         RadiusDamage (self, self.realowner, WEP_CVAR_PRI(mortar, damage), WEP_CVAR_PRI(mortar, edgedamage), WEP_CVAR_PRI(mortar, radius), world, world, WEP_CVAR_PRI(mortar, force), self.projectiledeathtype, other);
62
63         remove (self);
64 }
65
66 void W_Grenade_Explode2 (void)
67 {
68         if(other.takedamage == DAMAGE_AIM)
69                 if(IS_PLAYER(other))
70                         if(DIFF_TEAM(self.realowner, other))
71                                 if(other.deadflag == DEAD_NO)
72                                         if(IsFlying(other))
73                                                 Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
74
75         self.event_damage = func_null;
76         self.takedamage = DAMAGE_NO;
77
78         if(self.movetype == MOVETYPE_NONE)
79                 self.velocity = self.oldvelocity;
80
81         RadiusDamage (self, self.realowner, WEP_CVAR_SEC(mortar, damage), WEP_CVAR_SEC(mortar, edgedamage), WEP_CVAR_SEC(mortar, radius), world, world, WEP_CVAR_SEC(mortar, force), self.projectiledeathtype, other);
82
83         remove (self);
84 }
85
86 void W_Grenade_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
87 {
88         if (self.health <= 0)
89                 return;
90                 
91         if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
92                 return; // g_projectiles_damage says to halt
93                 
94         self.health = self.health - damage;
95         
96         if (self.health <= 0)
97                 W_PrepareExplosionByDamage(attacker, self.use);
98 }
99
100 void W_Grenade_Think1 (void)
101 {
102         self.nextthink = time;
103         if (time > self.cnt)
104         {
105                 other = world;
106                 self.projectiledeathtype |= HITTYPE_BOUNCE;
107                 W_Grenade_Explode ();
108                 return;
109         }
110         if(self.gl_detonate_later && self.gl_bouncecnt >= WEP_CVAR_PRI(mortar, remote_minbouncecnt))
111                 W_Grenade_Explode();
112 }
113
114 void W_Grenade_Touch1 (void)
115 {
116         PROJECTILE_TOUCH;
117         if (other.takedamage == DAMAGE_AIM || WEP_CVAR_PRI(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile
118         {
119                 self.use ();
120         }
121         else if (WEP_CVAR_PRI(mortar, type) == 1) // bounce
122         {
123                 float r;
124                 r = random() * 6;
125                 if(r < 1)
126                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM);
127                 else if(r < 2)
128                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM);
129                 else if(r < 3)
130                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM);
131                 else if(r < 4)
132                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM);
133                 else if(r < 5)
134                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM);
135                 else
136                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM);
137                 pointparticles(particleeffectnum("hagar_bounce"), self.origin, self.velocity, 1);
138                 self.projectiledeathtype |= HITTYPE_BOUNCE;
139                 self.gl_bouncecnt += 1;
140         }
141         else if(WEP_CVAR_PRI(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
142         {
143                 spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM);
144
145                 // let it stick whereever it is
146                 self.oldvelocity = self.velocity;
147                 self.velocity = '0 0 0';
148                 self.movetype = MOVETYPE_NONE; // also disables gravity
149                 self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
150                 UpdateCSQCProjectile(self);
151
152                 // do not respond to any more touches
153                 self.solid = SOLID_NOT;
154
155                 self.nextthink = min(self.nextthink, time + WEP_CVAR_PRI(mortar, lifetime_stick));
156         }
157 }
158
159 void W_Grenade_Touch2 (void)
160 {
161         PROJECTILE_TOUCH;
162         if (other.takedamage == DAMAGE_AIM || WEP_CVAR_SEC(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile
163         {
164                 self.use ();
165         }
166         else if (WEP_CVAR_SEC(mortar, type) == 1) // bounce
167         {
168                 float r;
169                 r = random() * 6;
170                 if(r < 1)
171                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM);
172                 else if(r < 2)
173                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM);
174                 else if(r < 3)
175                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM);
176                 else if(r < 4)
177                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM);
178                 else if(r < 5)
179                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM);
180                 else
181                         spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM);
182                 pointparticles(particleeffectnum("hagar_bounce"), self.origin, self.velocity, 1);
183                 self.projectiledeathtype |= HITTYPE_BOUNCE;
184                 self.gl_bouncecnt += 1;
185                 
186                 if (WEP_CVAR_SEC(mortar, lifetime_bounce) && self.gl_bouncecnt == 1)
187                         self.nextthink = time + WEP_CVAR_SEC(mortar, lifetime_bounce);
188                         
189         }
190         else if(WEP_CVAR_SEC(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
191         {
192                 spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM);
193
194                 // let it stick whereever it is
195                 self.oldvelocity = self.velocity;
196                 self.velocity = '0 0 0';
197                 self.movetype = MOVETYPE_NONE; // also disables gravity
198                 self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
199                 UpdateCSQCProjectile(self);
200
201                 // do not respond to any more touches
202                 self.solid = SOLID_NOT;
203
204                 self.nextthink = min(self.nextthink, time + WEP_CVAR_SEC(mortar, lifetime_stick));
205         }
206 }
207
208 void W_Grenade_Attack (void)
209 {
210         entity gren;
211
212         W_DecreaseAmmo(ammo_rockets, WEP_CVAR_PRI(mortar, ammo), autocvar_g_balance_mortar_reload_ammo); // WEAPONTODO
213
214         W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage));
215         w_shotdir = v_forward; // no TrueAim for grenades please
216
217         pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
218
219         gren = spawn ();
220         gren.owner = gren.realowner = self;
221         gren.classname = "grenade";
222         gren.bot_dodge = TRUE;
223         gren.bot_dodgerating = WEP_CVAR_PRI(mortar, damage);
224         gren.movetype = MOVETYPE_BOUNCE;
225         gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
226         gren.bouncestop = WEP_CVAR(mortar, bouncestop);
227         PROJECTILE_MAKETRIGGER(gren);
228         gren.projectiledeathtype = WEP_MORTAR;
229         setorigin(gren, w_shotorg);
230         setsize(gren, '-3 -3 -3', '3 3 3');
231
232         gren.cnt = time + WEP_CVAR_PRI(mortar, lifetime);
233         gren.nextthink = time;
234         gren.think = W_Grenade_Think1;
235         gren.use = W_Grenade_Explode;
236         gren.touch = W_Grenade_Touch1;
237
238         gren.takedamage = DAMAGE_YES;
239         gren.health = WEP_CVAR_PRI(mortar, health);
240         gren.damageforcescale = WEP_CVAR_PRI(mortar, damageforcescale);
241         gren.event_damage = W_Grenade_Damage;
242         gren.damagedbycontents = TRUE;
243         gren.missile_flags = MIF_SPLASH | MIF_ARC;
244         W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_mortar_primary); // WEAPONTODO
245
246         gren.angles = vectoangles (gren.velocity);
247         gren.flags = FL_PROJECTILE;
248
249         if(WEP_CVAR_PRI(mortar, type) == 0 || WEP_CVAR_PRI(mortar, type) == 2)
250                 CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
251         else
252                 CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE);
253
254         other = gren; MUTATOR_CALLHOOK(EditProjectile);
255 }
256
257 void W_Grenade_Attack2 (void)
258 {
259         entity gren;
260
261         W_DecreaseAmmo(ammo_rockets, WEP_CVAR_SEC(mortar, ammo), autocvar_g_balance_mortar_reload_ammo);
262
263         W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage));
264         w_shotdir = v_forward; // no TrueAim for grenades please
265
266         pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
267
268         gren = spawn ();
269         gren.owner = gren.realowner = self;
270         gren.classname = "grenade";
271         gren.bot_dodge = TRUE;
272         gren.bot_dodgerating = WEP_CVAR_SEC(mortar, damage);
273         gren.movetype = MOVETYPE_BOUNCE;
274         gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
275         gren.bouncestop = WEP_CVAR(mortar, bouncestop);
276         PROJECTILE_MAKETRIGGER(gren);
277         gren.projectiledeathtype = WEP_MORTAR | HITTYPE_SECONDARY;
278         setorigin(gren, w_shotorg);
279         setsize(gren, '-3 -3 -3', '3 3 3');
280
281         gren.nextthink = time + WEP_CVAR_SEC(mortar, lifetime);
282         gren.think = adaptor_think2use_hittype_splash;
283         gren.use = W_Grenade_Explode2;
284         gren.touch = W_Grenade_Touch2;
285
286         gren.takedamage = DAMAGE_YES;
287         gren.health = WEP_CVAR_SEC(mortar, health);
288         gren.damageforcescale = WEP_CVAR_SEC(mortar, damageforcescale);
289         gren.event_damage = W_Grenade_Damage;
290         gren.damagedbycontents = TRUE;
291         gren.missile_flags = MIF_SPLASH | MIF_ARC;
292         W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_mortar_secondary); // WEAPONTODO
293
294         gren.angles = vectoangles (gren.velocity);
295         gren.flags = FL_PROJECTILE;
296
297         if(WEP_CVAR_SEC(mortar, type) == 0 || WEP_CVAR_SEC(mortar, type) == 2)
298                 CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
299         else
300                 CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE);
301
302         other = gren; MUTATOR_CALLHOOK(EditProjectile);
303 }
304
305 void spawnfunc_weapon_grenadelauncher (void)
306 {
307         weapon_defaultspawnfunc(WEP_MORTAR);
308 }
309
310 .float bot_secondary_grenademooth;
311 float w_glauncher(float req)
312 {
313         entity nade;
314         float nadefound;
315         float ammo_amount;
316         switch(req)
317         {
318                 case WR_AIM:
319                 {
320                         self.BUTTON_ATCK = FALSE;
321                         self.BUTTON_ATCK2 = FALSE;
322                         if (self.bot_secondary_grenademooth == 0) // WEAPONTODO: merge this into using WEP_CVAR_BOTH
323                         {
324                                 if(bot_aim(WEP_CVAR_PRI(mortar, speed), WEP_CVAR_PRI(mortar, speed_up), WEP_CVAR_PRI(mortar, lifetime), TRUE))
325                                 {
326                                         self.BUTTON_ATCK = TRUE;
327                                         if(random() < 0.01) self.bot_secondary_grenademooth = 1;
328                                 }
329                         }
330                         else
331                         {
332                                 if(bot_aim(WEP_CVAR_SEC(mortar, speed), WEP_CVAR_SEC(mortar, speed_up), WEP_CVAR_SEC(mortar, lifetime), TRUE))
333                                 {
334                                         self.BUTTON_ATCK2 = TRUE;
335                                         if(random() < 0.02) self.bot_secondary_grenademooth = 0;
336                                 }
337                         }
338                         
339                         return TRUE;
340                 }
341                 /*case WR_CALCINFO:
342                 {
343                         wepinfo_pri_refire = max3(sys_frametime, WEP_CVAR_PRI(mortar, refire), WEP_CVAR_PRI(mortar, animtime));
344                         wepinfo_pri_dps = (WEP_CVAR_PRI(mortar, damage) * (1 / wepinfo_pri_refire));
345                         wepinfo_pri_speed = (1 / max(1, (10000 / max(1, WEP_CVAR_PRI(mortar, speed)))));
346
347                         // for the range calculation, closer to 1 is better
348                         wepinfo_pri_range_max = 2000 * wepinfo_pri_speed;
349                         wepinfo_pri_range = wepinfo_pri_speed * WEP_CVAR_PRI(mortar, 
350                         
351                         wepinfo_sec_refire = max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime));
352                         wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / wepinfo_sec_refire));
353                         
354                         wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime))));
355                         wepinfo_ter_dps = 0;
356                         */
357                 case WR_THINK:
358                 {
359                         if(autocvar_g_balance_mortar_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo))) // forced reload
360                                 WEP_ACTION(self.weapon, WR_RELOAD);
361                         else if (self.BUTTON_ATCK)
362                         {
363                                 if (weapon_prepareattack(0, WEP_CVAR_PRI(mortar, refire)))
364                                 {
365                                         W_Grenade_Attack();
366                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(mortar, animtime), w_ready);
367                                 }
368                         }
369                         else if (self.BUTTON_ATCK2)
370                         {
371                                 if (cvar("g_balance_mortar_secondary_remote_detonateprimary"))
372                                 {
373                                         nadefound = 0;
374                                         for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == 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, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM);
384                                 }
385                                 else if (weapon_prepareattack(1, WEP_CVAR_SEC(mortar, refire)))
386                                 {
387                                         W_Grenade_Attack2();
388                                         weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(mortar, animtime), w_ready);
389                                 }
390                         }
391                         
392                         return TRUE;
393                 }
394                 case WR_INIT:
395                 {
396                         precache_model ("models/weapons/g_gl.md3");
397                         precache_model ("models/weapons/v_gl.md3");
398                         precache_model ("models/weapons/h_gl.iqm");
399                         precache_sound ("weapons/grenade_bounce1.wav");
400                         precache_sound ("weapons/grenade_bounce2.wav");
401                         precache_sound ("weapons/grenade_bounce3.wav");
402                         precache_sound ("weapons/grenade_bounce4.wav");
403                         precache_sound ("weapons/grenade_bounce5.wav");
404                         precache_sound ("weapons/grenade_bounce6.wav");
405                         precache_sound ("weapons/grenade_stick.wav");
406                         precache_sound ("weapons/grenade_fire.wav");
407                         MORTAR_SETTINGS(WEP_SKIPCVAR, WEP_SET_PROP)
408                         return TRUE;
409                 }
410                 case WR_SETUP:
411                 {
412                         self.current_ammo = ammo_rockets;
413                         return TRUE;
414                 }
415                 case WR_CHECKAMMO1:
416                 {
417                         ammo_amount = self.ammo_rockets >= WEP_CVAR_PRI(mortar, ammo);
418                         ammo_amount += self.(weapon_load[WEP_MORTAR]) >= WEP_CVAR_PRI(mortar, ammo);
419                         return ammo_amount;
420                 }
421                 case WR_CHECKAMMO2:
422                 {
423                         ammo_amount = self.ammo_rockets >= WEP_CVAR_SEC(mortar, ammo);
424                         ammo_amount += self.(weapon_load[WEP_MORTAR]) >= WEP_CVAR_SEC(mortar, ammo);
425                         return ammo_amount;
426                 }
427                 case WR_CONFIG:
428                 {
429                         MORTAR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
430                         return TRUE;
431                 }
432                 case WR_RELOAD:
433                 {
434                         W_Reload(min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo)), "weapons/reload.wav"); // WEAPONTODO
435                         return TRUE;
436                 }
437                 case WR_SUICIDEMESSAGE:
438                 {
439                         if(w_deathtype & HITTYPE_SECONDARY)
440                                 return WEAPON_MORTAR_SUICIDE_BOUNCE;
441                         else
442                                 return WEAPON_MORTAR_SUICIDE_EXPLODE;
443                 }
444                 case WR_KILLMESSAGE:
445                 {
446                         if(w_deathtype & HITTYPE_SECONDARY)
447                                 return WEAPON_MORTAR_MURDER_BOUNCE;
448                         else
449                                 return WEAPON_MORTAR_MURDER_EXPLODE;
450                 }
451         }
452         return TRUE;
453 }
454 #endif
455 #ifdef CSQC
456 float w_glauncher(float req)
457 {
458         switch(req)
459         {
460                 case WR_IMPACTEFFECT:
461                 {
462                         vector org2;
463                         org2 = w_org + w_backoff * 12;
464                         pointparticles(particleeffectnum("grenade_explode"), org2, '0 0 0', 1);
465                         if(!w_issilent)
466                                 sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
467                                 
468                         return TRUE;
469                 }
470                 case WR_INIT:
471                 {
472                         precache_sound("weapons/grenade_impact.wav");
473                         return TRUE;
474                 }
475         }
476         return TRUE;
477 }
478 #endif
479 #endif