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