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