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