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