]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator_nades.qc
Limit max angle so player can look backwards & throw nade
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator_nades.qc
1 void nade_timer_think()
2 {
3         self.skin = 8 - (self.owner.wait - time) / (autocvar_g_nades_nade_lifetime / 10);
4         self.nextthink = time;
5         if(!self.owner || wasfreed(self.owner))
6                 remove(self);
7         
8 }
9
10 void nade_burn_spawn(entity _nade)
11 {
12         float p;
13         
14         switch(_nade.realowner.team)
15         {
16                 case NUM_TEAM_1: p = PROJECTILE_NADE_RED_BURN; break;
17                 case NUM_TEAM_2: p = PROJECTILE_NADE_BLUE_BURN; break;
18                 case NUM_TEAM_3: p = PROJECTILE_NADE_YELLOW_BURN; break;
19                 case NUM_TEAM_4: p = PROJECTILE_NADE_PINK_BURN; break;
20                 default:                 p = PROJECTILE_NADE_BURN; break;
21         }
22         
23         CSQCProjectile(_nade, TRUE, p, TRUE);
24 }
25
26 void nade_spawn(entity _nade)
27 {
28         float p;
29         entity timer = spawn();
30         setmodel(timer, "models/ok_nade_counter/ok_nade_counter.md3");
31         setattachment(timer, _nade, "");
32         timer.classname = "nade_timer";
33         timer.colormap = _nade.colormap;
34         timer.glowmod = _nade.glowmod;
35         timer.think = nade_timer_think;
36         timer.nextthink = time;
37         timer.wait = _nade.wait;
38         timer.owner = _nade;    
39         timer.skin = 10;
40         
41         switch(_nade.realowner.team)
42         {
43                 case NUM_TEAM_1: p = PROJECTILE_NADE_RED; break;
44                 case NUM_TEAM_2: p = PROJECTILE_NADE_BLUE; break;
45                 case NUM_TEAM_3: p = PROJECTILE_NADE_YELLOW; break;
46                 case NUM_TEAM_4: p = PROJECTILE_NADE_PINK; break;
47                 default:                 p = PROJECTILE_NADE; break;
48         }
49         
50         CSQCProjectile(_nade, TRUE, p, TRUE);
51         
52 }
53
54 void nade_boom()
55 {
56         string expef;
57         
58         switch(self.realowner.team)
59         {
60                 case NUM_TEAM_1: expef = "nade_red_explode"; break;
61                 case NUM_TEAM_2: expef = "nade_blue_explode"; break;
62                 case NUM_TEAM_3: expef = "nade_yellow_explode"; break;
63                 case NUM_TEAM_4: expef = "nade_pink_explode"; break;
64                 default:                 expef = "nade_explode"; break;
65         }
66         
67         sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
68         sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
69         pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1);
70         
71         Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self);
72
73         self.takedamage = DAMAGE_NO;
74         RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
75                                  autocvar_g_nades_nade_radius, self, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy);
76
77         remove(self);
78 }
79
80 void nade_touch()
81 {
82         PROJECTILE_TOUCH;
83         //setsize(self, '-2 -2 -2', '2 2 2');
84         //UpdateCSQCProjectile(self);
85         if(self.health == self.max_health)
86         {
87                 spamsound(self, CH_SHOTS, strcat("weapons/grenade_bounce", ftos(1 + rint(random() * 5)), ".wav"), VOL_BASE, ATTEN_NORM);
88                 return;
89         }
90
91         self.enemy = other;
92         nade_boom();
93 }
94
95 void nade_beep()
96 {
97         sound(self, CH_SHOTS_SINGLE, "overkill/grenadebip.ogg", VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
98         self.think = nade_boom;
99         self.nextthink = max(self.wait, time);
100 }
101
102 void nade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
103 {
104         if(DEATH_ISWEAPON(deathtype, WEP_LASER))
105                 return;
106
107         if(DEATH_ISWEAPON(deathtype, WEP_NEX) || DEATH_ISWEAPON(deathtype, WEP_MINSTANEX))
108         {
109                 force *= 6;
110                 damage = self.max_health * 0.55;
111         }
112
113         if(DEATH_ISWEAPON(deathtype, WEP_UZI))
114                 damage = self.max_health * 0.1;
115
116         if(DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) && !(deathtype & HITTYPE_SECONDARY))
117                 damage = self.max_health * 1.1;
118                 
119         if(DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) && (deathtype & HITTYPE_SECONDARY))
120         {
121                 damage = self.max_health * 0.1;
122                 force *= 15;
123         }
124         
125         self.velocity += force;
126
127         if(!damage || (self.flags & FL_ONGROUND && IS_PLAYER(attacker)))
128                 return;
129
130         if(self.health == self.max_health)
131         {
132                 sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
133                 self.nextthink = max(time + autocvar_g_nades_nade_lifetime, time);
134                 self.think = nade_beep;
135         }
136
137         self.health   -= damage;
138         self.realowner = attacker;
139
140         if(self.health <= 0)
141                 W_PrepareExplosionByDamage(attacker, nade_boom);
142         else
143                 nade_burn_spawn(self);
144 }
145
146 void toss_nade(entity e, vector _velocity, float _time)
147 {
148         entity _nade = e.nade;
149         e.nade = world;
150         
151         remove(e.fake_nade);
152         e.fake_nade = world;
153         
154         makevectors(e.v_angle);
155         
156         W_SetupShot(e, FALSE, FALSE, "", CH_WEAPON_A, 0);
157         
158         Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER_CPID, CPID_NADES);
159         
160         //setorigin(_nade, CENTER_OR_VIEWOFS(e) + (v_right * 10) * -1);
161         setorigin(_nade, w_shotorg + (v_right * 25) * -1);
162         setmodel(_nade, "models/weapons/v_ok_grenade.md3");
163         setattachment(_nade, world, "");
164         PROJECTILE_MAKETRIGGER(_nade);
165         setsize(_nade, '-16 -16 -16', '16 16 16');
166         _nade.movetype = MOVETYPE_BOUNCE;
167         
168         tracebox(_nade.origin, _nade.mins, _nade.maxs, _nade.origin, FALSE, _nade);
169         if (trace_startsolid)
170                 setorigin(_nade, e.origin);
171         
172         if(self.v_angle_x >= 70 && self.v_angle_x <= 110)
173                 _nade.velocity = '0 0 100';
174         else if(autocvar_g_nades_nade_newton_style == 1)
175                 _nade.velocity = e.velocity + _velocity;
176         else if(autocvar_g_nades_nade_newton_style == 2)
177                 _nade.velocity = _velocity;
178         else
179                 _nade.velocity = W_CalculateProjectileVelocity(e.velocity, _velocity, TRUE);
180
181         //_nade.solid = SOLID_BBOX; // TODO: remember why this was needed
182         _nade.touch = nade_touch;
183         _nade.health = autocvar_g_nades_nade_health;
184         _nade.max_health = _nade.health;
185         _nade.takedamage = DAMAGE_AIM;
186         _nade.event_damage = nade_damage;
187         _nade.teleportable = TRUE;
188         _nade.pushable = TRUE;
189         _nade.gravity = 1;
190         _nade.missile_flags = MIF_SPLASH | MIF_ARC;
191         _nade.damagedbycontents = TRUE;
192         _nade.angles = vectoangles(_nade.velocity);
193         _nade.flags = FL_PROJECTILE;
194
195         nade_spawn(_nade);
196
197         if(_time)
198         {
199                 _nade.think = nade_boom;
200                 _nade.nextthink = _time;
201         }
202
203         e.nade_refire = time + autocvar_g_nades_nade_refire;
204 }
205
206 void nade_prime()
207 {
208         if(self.nade)
209                 remove(self.nade);
210                 
211         if(self.fake_nade)
212                 remove(self.fake_nade);
213         
214         self.nade = spawn();
215         setmodel(self.nade, "null");
216         setattachment(self.nade, self, "bip01 l hand");
217         self.nade.classname = "nade";
218         self.nade.realowner = self;
219         self.nade.colormap = self.colormap;
220         self.nade.glowmod = self.glowmod;
221         self.nade.wait = time + autocvar_g_nades_nade_lifetime;
222         self.nade.lifetime = time;
223         self.nade.think = nade_beep;
224         self.nade.nextthink = max(self.nade.wait - 3, time);
225         self.nade.projectiledeathtype = DEATH_NADE;
226
227         self.fake_nade = spawn();
228         setmodel(self.fake_nade, "models/weapons/h_ok_grenade.iqm");
229         setattachment(self.fake_nade, self.weaponentity, "");
230         self.fake_nade.classname = "fake_nade";
231         //self.fake_nade.viewmodelforclient = self;
232         self.fake_nade.realowner = self.fake_nade.owner = self;
233         self.fake_nade.colormap = self.colormap;
234         self.fake_nade.glowmod = self.glowmod;
235         self.fake_nade.think = SUB_Remove;
236         self.fake_nade.nextthink = self.nade.wait;
237 }
238
239 float CanThrowNade()
240 {
241         if(self.vehicle)
242                 return FALSE;
243                 
244         if(gameover)
245                 return FALSE;
246                 
247         if(self.deadflag != DEAD_NO)
248                 return FALSE;
249         
250         if not(autocvar_g_nades)
251                 return FALSE; // allow turning them off mid match
252                 
253         if(forbidWeaponUse())
254                 return FALSE;
255                 
256         if not(IS_PLAYER(self))
257                 return FALSE;
258                 
259         return TRUE;
260 }
261
262 void nades_CheckThrow()
263 {
264         if(!CanThrowNade())
265                 return;
266                 
267         if(!self.nade)
268         {
269                 if(self.nade_refire < time)
270                 {
271                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_NADE_THROW);
272                         nade_prime();
273                         self.nade_refire = time + autocvar_g_nades_nade_refire;
274                 }
275         }
276         else
277         {
278                 if(time - self.nade.lifetime >= 1)
279                 {
280                         makevectors(self.v_angle);
281                         float _force = time - self.nade.lifetime;
282                         _force /= autocvar_g_nades_nade_lifetime;
283                         _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));
284                         toss_nade(self, (v_forward * 0.75 + v_up * 0.2 + v_right * 0.05) * _force, 0);
285                 }
286         }
287 }
288
289 MUTATOR_HOOKFUNCTION(nades_VehicleEnter)
290 {
291         if(other.nade)
292                 toss_nade(other, '0 0 100', max(other.nade.wait, time + 0.05));
293         
294         return FALSE;
295 }
296
297 MUTATOR_HOOKFUNCTION(nades_PlayerPreThink)
298 {
299         float key_pressed = ((g_grappling_hook || client_hasweapon(self, WEP_HOOK, FALSE, FALSE) || (weaponsInMap & WEPSET_HOOK)) ? self.button16 : self.BUTTON_HOOK);
300         
301         if(self.nade)
302                 if(self.nade.wait - 0.1 <= time)
303                         toss_nade(self, '0 0 0', time + 0.05);
304                         
305         if(CanThrowNade())
306         if(self.nade_refire < time)
307         {
308                 if(key_pressed)
309                 {
310                         if(!self.nade)
311                                 nade_prime();
312                 }
313                 else if(time - self.nade.lifetime >= 1)
314                 {
315                         if(self.nade)
316                         {
317                                 makevectors(self.v_angle);
318                                 float _force = time - self.nade.lifetime;
319                                 _force /= autocvar_g_nades_nade_lifetime;
320                                 _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));                         
321                                 toss_nade(self, (v_forward * 0.7 + v_up * 0.2 + v_right * 0.1) * _force, 0);
322                         }
323                 }
324         }
325
326         return FALSE;
327 }
328
329 MUTATOR_HOOKFUNCTION(nades_PlayerSpawn)
330 {
331         if(autocvar_g_nades_spawn)
332                 self.nade_refire = time + autocvar_g_spawnshieldtime;
333         else
334                 self.nade_refire  = time + autocvar_g_nades_nade_refire;
335
336         return FALSE;
337 }
338
339 MUTATOR_HOOKFUNCTION(nades_PlayerDies)
340 {
341         if(self.nade)
342                 toss_nade(self, '0 0 100', max(self.nade.wait, time + 0.05));
343                 
344         return FALSE;
345 }
346
347 MUTATOR_HOOKFUNCTION(nades_RemovePlayer)
348 {
349         if(self.nade)
350                 remove(self.nade);
351
352         if(self.fake_nade)
353                 remove(self.fake_nade);
354                 
355         return FALSE;
356 }
357
358 MUTATOR_HOOKFUNCTION(nades_BuildMutatorsString)
359 {
360         ret_string = strcat(ret_string, ":Nades");
361         return FALSE;
362 }
363
364 MUTATOR_HOOKFUNCTION(nades_BuildMutatorsPrettyString)
365 {
366         ret_string = strcat(ret_string, ", Nades");
367         return FALSE;
368 }
369
370 MUTATOR_DEFINITION(mutator_nades)
371 {
372         MUTATOR_HOOK(VehicleEnter, nades_VehicleEnter, CBC_ORDER_ANY);
373         MUTATOR_HOOK(PlayerPreThink, nades_PlayerPreThink, CBC_ORDER_ANY);
374         MUTATOR_HOOK(PlayerSpawn, nades_PlayerSpawn, CBC_ORDER_ANY);
375         MUTATOR_HOOK(PlayerDies, nades_PlayerDies, CBC_ORDER_ANY);
376         MUTATOR_HOOK(MakePlayerObserver, nades_RemovePlayer, CBC_ORDER_ANY);
377         MUTATOR_HOOK(ClientDisconnect, nades_RemovePlayer, CBC_ORDER_ANY);
378         MUTATOR_HOOK(BuildMutatorsString, nades_BuildMutatorsString, CBC_ORDER_ANY);
379         MUTATOR_HOOK(BuildMutatorsPrettyString, nades_BuildMutatorsPrettyString, CBC_ORDER_ANY);
380         
381         MUTATOR_ONADD
382         {
383                 precache_model("models/ok_nade_counter/ok_nade_counter.md3");
384                 
385                 precache_model("models/weapons/h_ok_grenade.iqm");
386                 precache_model("models/weapons/v_ok_grenade.md3");
387                 precache_sound("weapons/rocket_impact.wav");
388                 precache_sound("weapons/grenade_bounce1.wav");
389                 precache_sound("weapons/grenade_bounce2.wav");
390                 precache_sound("weapons/grenade_bounce3.wav");
391                 precache_sound("weapons/grenade_bounce4.wav");
392                 precache_sound("weapons/grenade_bounce5.wav");
393                 precache_sound("weapons/grenade_bounce6.wav");
394                 precache_sound("overkill/grenadebip.ogg");
395         }
396
397         return FALSE;
398 }