2c92ae21c8b06ed02176f631de8cb4df796606ae
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / mortar.qc
1 #ifndef IMPLEMENTATION
2 CLASS(Mortar, Weapon)
3 /* ammotype  */ ATTRIB(Mortar, ammo_field, .int, ammo_rockets)
4 /* impulse   */ ATTRIB(Mortar, impulse, int, 4)
5 /* flags     */ ATTRIB(Mortar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
6 /* rating    */ ATTRIB(Mortar, bot_pickupbasevalue, float, BOT_PICKUP_RATING_MID);
7 /* color     */ ATTRIB(Mortar, wpcolor, vector, '1 0 0');
8 /* modelname */ ATTRIB(Mortar, mdl, string, "gl");
9 #ifndef MENUQC
10 /* model     */ ATTRIB(Mortar, m_model, Model, MDL_MORTAR_ITEM);
11 #endif
12 /* crosshair */ ATTRIB(Mortar, w_crosshair, string, "gfx/crosshairgrenadelauncher");
13 /* crosshair */ ATTRIB(Mortar, w_crosshair_size, float, 0.7);
14 /* wepimg    */ ATTRIB(Mortar, model2, string, "weapongrenadelauncher");
15 /* refname   */ ATTRIB(Mortar, netname, string, "mortar");
16 /* wepname   */ ATTRIB(Mortar, message, string, _("Mortar"));
17 ENDCLASS(Mortar)
18 REGISTER_WEAPON(MORTAR, NEW(Mortar));
19
20 #define MORTAR_SETTINGS(w_cvar,w_prop) MORTAR_SETTINGS_LIST(w_cvar, w_prop, MORTAR, mortar)
21 #define MORTAR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
22         w_cvar(id, sn, BOTH, ammo) \
23         w_cvar(id, sn, BOTH, animtime) \
24         w_cvar(id, sn, NONE, bouncefactor) \
25         w_cvar(id, sn, NONE, bouncestop) \
26         w_cvar(id, sn, BOTH, damage) \
27         w_cvar(id, sn, BOTH, damageforcescale) \
28         w_cvar(id, sn, BOTH, edgedamage) \
29         w_cvar(id, sn, BOTH, force) \
30         w_cvar(id, sn, BOTH, health) \
31         w_cvar(id, sn, BOTH, lifetime) \
32         w_cvar(id, sn, SEC,  lifetime_bounce) \
33         w_cvar(id, sn, BOTH, lifetime_stick) \
34         w_cvar(id, sn, BOTH, radius) \
35         w_cvar(id, sn, BOTH, refire) \
36         w_cvar(id, sn, SEC,  remote_detonateprimary) \
37         w_cvar(id, sn, PRI,  remote_minbouncecnt) \
38         w_cvar(id, sn, BOTH, speed) \
39         w_cvar(id, sn, BOTH, speed_up) \
40         w_cvar(id, sn, BOTH, speed_z) \
41         w_cvar(id, sn, BOTH, spread) \
42         w_cvar(id, sn, BOTH, type) \
43         w_prop(id, sn, float,  reloading_ammo, reload_ammo) \
44         w_prop(id, sn, float,  reloading_time, reload_time) \
45         w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
46         w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
47         w_prop(id, sn, string, weaponreplace, weaponreplace) \
48         w_prop(id, sn, float,  weaponstart, weaponstart) \
49         w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
50         w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
51
52 #ifdef SVQC
53 MORTAR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
54 .float gl_detonate_later;
55 .float gl_bouncecnt;
56 #endif
57 #endif
58 #ifdef IMPLEMENTATION
59 #ifdef SVQC
60
61 void spawnfunc_weapon_mortar(void) { weapon_defaultspawnfunc(WEP_MORTAR.m_id); }
62 void spawnfunc_weapon_grenadelauncher(void) { spawnfunc_weapon_mortar(); }
63
64 void W_Mortar_Grenade_Explode(void)
65 {SELFPARAM();
66         if(other.takedamage == DAMAGE_AIM)
67                 if(IS_PLAYER(other))
68                         if(DIFF_TEAM(self.realowner, other))
69                                 if(other.deadflag == DEAD_NO)
70                                         if(IsFlying(other))
71                                                 Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
72
73         self.event_damage = func_null;
74         self.takedamage = DAMAGE_NO;
75
76         if(self.movetype == MOVETYPE_NONE)
77                 self.velocity = self.oldvelocity;
78
79         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);
80
81         remove(self);
82 }
83
84 void W_Mortar_Grenade_Explode2(void)
85 {SELFPARAM();
86         if(other.takedamage == DAMAGE_AIM)
87                 if(IS_PLAYER(other))
88                         if(DIFF_TEAM(self.realowner, other))
89                                 if(other.deadflag == DEAD_NO)
90                                         if(IsFlying(other))
91                                                 Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
92
93         self.event_damage = func_null;
94         self.takedamage = DAMAGE_NO;
95
96         if(self.movetype == MOVETYPE_NONE)
97                 self.velocity = self.oldvelocity;
98
99         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);
100
101         remove(self);
102 }
103
104
105 void W_Mortar_Grenade_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
106 {SELFPARAM();
107         if(self.health <= 0)
108                 return;
109
110         if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
111                 return; // g_projectiles_damage says to halt
112
113         self.health = self.health - damage;
114
115         if(self.health <= 0)
116                 W_PrepareExplosionByDamage(attacker, self.use);
117 }
118
119 void W_Mortar_Grenade_Think1(void)
120 {SELFPARAM();
121         self.nextthink = time;
122         if(time > self.cnt)
123         {
124                 other = world;
125                 self.projectiledeathtype |= HITTYPE_BOUNCE;
126                 W_Mortar_Grenade_Explode();
127                 return;
128         }
129         if(self.gl_detonate_later && self.gl_bouncecnt >= WEP_CVAR_PRI(mortar, remote_minbouncecnt))
130                 W_Mortar_Grenade_Explode();
131 }
132
133 void W_Mortar_Grenade_Touch1(void)
134 {SELFPARAM();
135         PROJECTILE_TOUCH;
136         if(other.takedamage == DAMAGE_AIM || WEP_CVAR_PRI(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile
137         {
138                 self.use();
139         }
140         else if(WEP_CVAR_PRI(mortar, type) == 1) // bounce
141         {
142                 spamsound(self, CH_SHOTS, SND(GRENADE_BOUNCE_RANDOM()), VOL_BASE, ATTN_NORM);
143                 Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1);
144                 self.projectiledeathtype |= HITTYPE_BOUNCE;
145                 self.gl_bouncecnt += 1;
146         }
147         else if(WEP_CVAR_PRI(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
148         {
149                 spamsound(self, CH_SHOTS, SND(GRENADE_STICK), VOL_BASE, ATTN_NORM);
150
151                 // let it stick whereever it is
152                 self.oldvelocity = self.velocity;
153                 self.velocity = '0 0 0';
154                 self.movetype = MOVETYPE_NONE; // also disables gravity
155                 self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
156                 UpdateCSQCProjectile(self);
157
158                 // do not respond to any more touches
159                 self.solid = SOLID_NOT;
160
161                 self.nextthink = min(self.nextthink, time + WEP_CVAR_PRI(mortar, lifetime_stick));
162         }
163 }
164
165 void W_Mortar_Grenade_Touch2(void)
166 {SELFPARAM();
167         PROJECTILE_TOUCH;
168         if(other.takedamage == DAMAGE_AIM || WEP_CVAR_SEC(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile
169         {
170                 self.use();
171         }
172         else if(WEP_CVAR_SEC(mortar, type) == 1) // bounce
173         {
174                 spamsound(self, CH_SHOTS, SND(GRENADE_BOUNCE_RANDOM()), VOL_BASE, ATTN_NORM);
175                 Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1);
176                 self.projectiledeathtype |= HITTYPE_BOUNCE;
177                 self.gl_bouncecnt += 1;
178
179                 if(WEP_CVAR_SEC(mortar, lifetime_bounce) && self.gl_bouncecnt == 1)
180                         self.nextthink = time + WEP_CVAR_SEC(mortar, lifetime_bounce);
181
182         }
183         else if(WEP_CVAR_SEC(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
184         {
185                 spamsound(self, CH_SHOTS, SND(GRENADE_STICK), VOL_BASE, ATTN_NORM);
186
187                 // let it stick whereever it is
188                 self.oldvelocity = self.velocity;
189                 self.velocity = '0 0 0';
190                 self.movetype = MOVETYPE_NONE; // also disables gravity
191                 self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
192                 UpdateCSQCProjectile(self);
193
194                 // do not respond to any more touches
195                 self.solid = SOLID_NOT;
196
197                 self.nextthink = min(self.nextthink, time + WEP_CVAR_SEC(mortar, lifetime_stick));
198         }
199 }
200
201 void W_Mortar_Attack(Weapon thiswep)
202 {SELFPARAM();
203         entity gren;
204
205         W_DecreaseAmmo(thiswep, WEP_CVAR_PRI(mortar, ammo));
206
207         W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', false, 4, SND(GRENADE_FIRE), CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage));
208         w_shotdir = v_forward; // no TrueAim for grenades please
209
210         Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
211
212         gren = spawn();
213         gren.owner = gren.realowner = self;
214         gren.classname = "grenade";
215         gren.bot_dodge = true;
216         gren.bot_dodgerating = WEP_CVAR_PRI(mortar, damage);
217         gren.movetype = MOVETYPE_BOUNCE;
218         gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
219         gren.bouncestop = WEP_CVAR(mortar, bouncestop);
220         PROJECTILE_MAKETRIGGER(gren);
221         gren.projectiledeathtype = WEP_MORTAR.m_id;
222         setorigin(gren, w_shotorg);
223         setsize(gren, '-3 -3 -3', '3 3 3');
224
225         gren.cnt = time + WEP_CVAR_PRI(mortar, lifetime);
226         gren.nextthink = time;
227         gren.think = W_Mortar_Grenade_Think1;
228         gren.use = W_Mortar_Grenade_Explode;
229         gren.touch = W_Mortar_Grenade_Touch1;
230
231         gren.takedamage = DAMAGE_YES;
232         gren.health = WEP_CVAR_PRI(mortar, health);
233         gren.damageforcescale = WEP_CVAR_PRI(mortar, damageforcescale);
234         gren.event_damage = W_Mortar_Grenade_Damage;
235         gren.damagedbycontents = true;
236         gren.missile_flags = MIF_SPLASH | MIF_ARC;
237         W_SetupProjVelocity_UP_PRI(gren, mortar);
238
239         gren.angles = vectoangles(gren.velocity);
240         gren.flags = FL_PROJECTILE;
241
242         if(WEP_CVAR_PRI(mortar, type) == 0 || WEP_CVAR_PRI(mortar, type) == 2)
243                 CSQCProjectile(gren, true, PROJECTILE_GRENADE, true);
244         else
245                 CSQCProjectile(gren, true, PROJECTILE_GRENADE_BOUNCING, true);
246
247         MUTATOR_CALLHOOK(EditProjectile, self, gren);
248 }
249
250 void W_Mortar_Attack2(Weapon thiswep)
251 {SELFPARAM();
252         entity gren;
253
254         W_DecreaseAmmo(thiswep, WEP_CVAR_SEC(mortar, ammo));
255
256         W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', false, 4, SND(GRENADE_FIRE), CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage));
257         w_shotdir = v_forward; // no TrueAim for grenades please
258
259         Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
260
261         gren = spawn();
262         gren.owner = gren.realowner = self;
263         gren.classname = "grenade";
264         gren.bot_dodge = true;
265         gren.bot_dodgerating = WEP_CVAR_SEC(mortar, damage);
266         gren.movetype = MOVETYPE_BOUNCE;
267         gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
268         gren.bouncestop = WEP_CVAR(mortar, bouncestop);
269         PROJECTILE_MAKETRIGGER(gren);
270         gren.projectiledeathtype = WEP_MORTAR.m_id | HITTYPE_SECONDARY;
271         setorigin(gren, w_shotorg);
272         setsize(gren, '-3 -3 -3', '3 3 3');
273
274         gren.nextthink = time + WEP_CVAR_SEC(mortar, lifetime);
275         gren.think = adaptor_think2use_hittype_splash;
276         gren.use = W_Mortar_Grenade_Explode2;
277         gren.touch = W_Mortar_Grenade_Touch2;
278
279         gren.takedamage = DAMAGE_YES;
280         gren.health = WEP_CVAR_SEC(mortar, health);
281         gren.damageforcescale = WEP_CVAR_SEC(mortar, damageforcescale);
282         gren.event_damage = W_Mortar_Grenade_Damage;
283         gren.damagedbycontents = true;
284         gren.missile_flags = MIF_SPLASH | MIF_ARC;
285         W_SetupProjVelocity_UP_SEC(gren, mortar);
286
287         gren.angles = vectoangles(gren.velocity);
288         gren.flags = FL_PROJECTILE;
289
290         if(WEP_CVAR_SEC(mortar, type) == 0 || WEP_CVAR_SEC(mortar, type) == 2)
291                 CSQCProjectile(gren, true, PROJECTILE_GRENADE, true);
292         else
293                 CSQCProjectile(gren, true, PROJECTILE_GRENADE_BOUNCING, true);
294
295         MUTATOR_CALLHOOK(EditProjectile, self, gren);
296 }
297
298 .float bot_secondary_grenademooth;
299
300                 METHOD(Mortar, wr_aim, bool(entity thiswep))
301                 {
302                         self.BUTTON_ATCK = false;
303                         self.BUTTON_ATCK2 = false;
304                         if(self.bot_secondary_grenademooth == 0) // WEAPONTODO: merge this into using WEP_CVAR_BOTH
305                         {
306                                 if(bot_aim(WEP_CVAR_PRI(mortar, speed), WEP_CVAR_PRI(mortar, speed_up), WEP_CVAR_PRI(mortar, lifetime), true))
307                                 {
308                                         self.BUTTON_ATCK = true;
309                                         if(random() < 0.01) self.bot_secondary_grenademooth = 1;
310                                 }
311                         }
312                         else
313                         {
314                                 if(bot_aim(WEP_CVAR_SEC(mortar, speed), WEP_CVAR_SEC(mortar, speed_up), WEP_CVAR_SEC(mortar, lifetime), true))
315                                 {
316                                         self.BUTTON_ATCK2 = true;
317                                         if(random() < 0.02) self.bot_secondary_grenademooth = 0;
318                                 }
319                         }
320
321                         return true;
322                 }
323                 /*case WR_CALCINFO:
324                 {
325                         wepinfo_pri_refire = max3(sys_frametime, WEP_CVAR_PRI(mortar, refire), WEP_CVAR_PRI(mortar, animtime));
326                         wepinfo_pri_dps = (WEP_CVAR_PRI(mortar, damage) * (1 / wepinfo_pri_refire));
327                         wepinfo_pri_speed = (1 / max(1, (10000 / max(1, WEP_CVAR_PRI(mortar, speed)))));
328
329                         // for the range calculation, closer to 1 is better
330                         wepinfo_pri_range_max = 2000 * wepinfo_pri_speed;
331                         wepinfo_pri_range = wepinfo_pri_speed * WEP_CVAR_PRI(mortar,
332
333                         wepinfo_sec_refire = max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime));
334                         wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / wepinfo_sec_refire));
335
336                         wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime))));
337                         wepinfo_ter_dps = 0;
338                         */
339                 METHOD(Mortar, wr_think, bool(entity thiswep, bool fire1, bool fire2))
340                 {
341                         if(autocvar_g_balance_mortar_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo))) // forced reload
342                                 _WEP_ACTION(self.weapon, WR_RELOAD);
343                         else if(fire1)
344                         {
345                                 if(weapon_prepareattack(false, WEP_CVAR_PRI(mortar, refire)))
346                                 {
347                                         W_Mortar_Attack(thiswep);
348                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(mortar, animtime), w_ready);
349                                 }
350                         }
351                         else if(fire2)
352                         {
353                                 if(WEP_CVAR_SEC(mortar, remote_detonateprimary))
354                                 {
355                                         bool nadefound = false;
356                                         entity nade;
357                                         for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == self)
358                                         {
359                                                 if(!nade.gl_detonate_later)
360                                                 {
361                                                         nade.gl_detonate_later = true;
362                                                         nadefound = true;
363                                                 }
364                                         }
365                                         if(nadefound)
366                                                 sound(self, CH_WEAPON_B, SND_ROCKET_DET, VOL_BASE, ATTN_NORM);
367                                 }
368                                 else if(weapon_prepareattack(true, WEP_CVAR_SEC(mortar, refire)))
369                                 {
370                                         W_Mortar_Attack2(thiswep);
371                                         weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(mortar, animtime), w_ready);
372                                 }
373                         }
374
375                         return true;
376                 }
377                 METHOD(Mortar, wr_init, bool(entity thiswep))
378                 {
379                         MORTAR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
380                         return true;
381                 }
382                 METHOD(Mortar, wr_checkammo1, bool(entity thiswep))
383                 {
384                         float ammo_amount = self.WEP_AMMO(MORTAR) >= WEP_CVAR_PRI(mortar, ammo);
385                         ammo_amount += self.(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_PRI(mortar, ammo);
386                         return ammo_amount;
387                 }
388                 METHOD(Mortar, wr_checkammo2, bool(entity thiswep))
389                 {
390                         float ammo_amount = self.WEP_AMMO(MORTAR) >= WEP_CVAR_SEC(mortar, ammo);
391                         ammo_amount += self.(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_SEC(mortar, ammo);
392                         return ammo_amount;
393                 }
394                 METHOD(Mortar, wr_config, bool(entity thiswep))
395                 {
396                         MORTAR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
397                         return true;
398                 }
399                 METHOD(Mortar, wr_reload, bool(entity thiswep))
400                 {
401                         W_Reload(min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo)), SND(RELOAD)); // WEAPONTODO
402                         return true;
403                 }
404                 METHOD(Mortar, wr_suicidemessage, bool(entity thiswep))
405                 {
406                         if(w_deathtype & HITTYPE_SECONDARY)
407                                 return WEAPON_MORTAR_SUICIDE_BOUNCE;
408                         else
409                                 return WEAPON_MORTAR_SUICIDE_EXPLODE;
410                 }
411                 METHOD(Mortar, wr_killmessage, bool(entity thiswep))
412                 {
413                         if(w_deathtype & HITTYPE_SECONDARY)
414                                 return WEAPON_MORTAR_MURDER_BOUNCE;
415                         else
416                                 return WEAPON_MORTAR_MURDER_EXPLODE;
417                 }
418
419 #endif
420 #ifdef CSQC
421
422                 METHOD(Mortar, wr_impacteffect, bool(entity thiswep))
423                 {
424                         vector org2;
425                         org2 = w_org + w_backoff * 12;
426                         pointparticles(particleeffectnum(EFFECT_GRENADE_EXPLODE), org2, '0 0 0', 1);
427                         if(!w_issilent)
428                                 sound(self, CH_SHOTS, SND_GRENADE_IMPACT, VOL_BASE, ATTN_NORM);
429
430                         return true;
431                 }
432                 METHOD(Mortar, wr_init, bool(entity thiswep))
433                 {
434                         return true;
435                 }
436                 METHOD(Mortar, wr_zoomreticle, bool(entity thiswep))
437                 {
438                         // no weapon specific image for this weapon
439                         return false;
440                 }
441
442 #endif
443 #endif