]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/nades/nades.qc
8fa8a277997331e7652d141e1ea9efc43376aeed
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mutators / mutator / nades / nades.qc
1 #include "nades.qh"
2
3 #include "../overkill/okmachinegun.qh"
4 #include "../overkill/okshotgun.qh"
5
6 #ifdef SVQC
7 bool autocvar_g_nades_nade_small;
8 float autocvar_g_nades_spread = 0.04;
9 #endif
10
11 REGISTER_STAT(NADES_SMALL, int, autocvar_g_nades_nade_small)
12
13 #ifdef GAMEQC
14
15 REPLICATE(cvar_cl_nade_type, int, "cl_nade_type");
16 REPLICATE(cvar_cl_pokenade_type, string, "cl_pokenade_type");
17
18 entity Nade_TrailEffect(int proj, int nade_team)
19 {
20     switch (proj)
21     {
22         case PROJECTILE_NADE:       return EFFECT_NADE_TRAIL(nade_team);
23         case PROJECTILE_NADE_BURN:  return EFFECT_NADE_TRAIL_BURN(nade_team);
24     }
25
26     FOREACH(Nades, true, {
27         for (int j = 0; j < 2; j++)
28         {
29             if (it.m_projectile[j] == proj)
30             {
31                 string trail = it.m_trail[j].eent_eff_name;
32                 if (trail) return it.m_trail[j];
33                 break;
34             }
35         }
36     });
37
38     return EFFECT_Null;
39 }
40 #endif
41
42 REGISTER_NET_TEMP(TE_CSQC_DARKBLINKING);
43 #ifdef CSQC
44 #include <client/draw.qh>
45 #include <client/hud/hud.qh>
46
47 float dark_appeartime;
48 float dark_fadetime;
49 bool darkblink;
50
51 void HUD_DarkBlinking()
52 {
53         vector bottomright = vec2(vid_conwidth, vid_conheight);
54         drawfill('0 0 0', bottomright, NADE_TYPE_DARK.m_color, 0.986, DRAWFLAG_NORMAL);
55 }
56
57 REGISTER_MUTATOR(cl_nades, true);
58 MUTATOR_HOOKFUNCTION(cl_nades, HUD_Draw_overlay)
59 {
60         if (STAT(NADE_DARK_TIME) > time)
61         {
62                 M_ARGV(0, vector) = NADE_TYPE_DARK.m_color;
63                 HUD_DarkBlinking();
64                 return true;
65         }
66         return false;
67 }
68
69 NET_HANDLE(TE_CSQC_DARKBLINKING, bool isNew)
70 {
71         return = true;
72
73         if(darkblink) return;
74
75         localcmd("play2 sound/misc/blind\n");
76         darkblink = true;
77         dark_appeartime = time;
78         dark_fadetime = STAT(NADE_DARK_TIME);
79 }
80
81 MUTATOR_HOOKFUNCTION(cl_nades, Ent_Projectile)
82 {
83         entity proj = M_ARGV(0, entity);
84
85         if (proj.cnt == PROJECTILE_NAPALM_FOUNTAIN)
86         {
87                 proj.modelindex = 0;
88                 proj.traileffect = EFFECT_FIREBALL.m_id;
89                 return true;
90         }
91         if (Nade_FromProjectile(proj.cnt) != NADE_TYPE_Null)
92         {
93                 setmodel(proj, MDL_PROJECTILE_NADE);
94                 entity trail = Nade_TrailEffect(proj.cnt, proj.team);
95                 if (trail.eent_eff_name) proj.traileffect = trail.m_id;
96                 return true;
97         }
98 }
99 MUTATOR_HOOKFUNCTION(cl_nades, EditProjectile)
100 {
101         entity proj = M_ARGV(0, entity);
102
103         if (proj.cnt == PROJECTILE_NAPALM_FOUNTAIN)
104         {
105                 loopsound(proj, CH_SHOTS_SINGLE, SND_FIREBALL_FLY2, VOL_BASE, ATTEN_NORM);
106                 proj.mins = '-16 -16 -16';
107                 proj.maxs = '16 16 16';
108         }
109
110         entity nade_type = Nade_FromProjectile(proj.cnt);
111         if (nade_type == NADE_TYPE_Null) return;
112         if(STAT(NADES_SMALL))
113         {
114                 proj.mins = '-8 -8 -8';
115                 proj.maxs = '8 8 8';
116         }
117         else
118         {
119                 proj.mins = '-16 -16 -16';
120                 proj.maxs = '16 16 16';
121         }
122         proj.colormod = nade_type.m_color;
123         set_movetype(proj, MOVETYPE_BOUNCE);
124         settouch(proj, func_null);
125         proj.scale = 1.5;
126         proj.avelocity = randomvec() * 720;
127         proj.alphamod = nade_type.m_alpha;
128
129         if (nade_type == NADE_TYPE_TRANSLOCATE || nade_type == NADE_TYPE_SPAWN)
130                 proj.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
131         else
132                 proj.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
133 }
134
135 MUTATOR_HOOKFUNCTION(cl_nades, BuildGameplayTipsString)
136 {
137         if (mut_is_active(MUT_NADES))
138         {
139                 string key = getcommandkey(_("drop weapon / throw nade"), "dropweapon");
140                 M_ARGV(0, string) = strcat(M_ARGV(0, string),
141                         "\n", sprintf(_("^3nades^8 are enabled, press ^3%s^8 to use them"), key), "\n");
142         }
143 }
144
145 bool Projectile_isnade(int p)
146 {
147         return Nade_FromProjectile(p) != NADE_TYPE_Null;
148 }
149 void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time)
150 {
151         float bonusNades    = STAT(NADE_BONUS);
152         float bonusProgress = STAT(NADE_BONUS_SCORE);
153         float bonusType     = STAT(NADE_BONUS_TYPE);
154         Nade def = REGISTRY_GET(Nades, bonusType);
155         vector nadeColor    = def.m_color;
156         string nadeIcon     = def.m_icon;
157
158         vector iconPos, textPos;
159
160         if(autocvar_hud_panel_ammo_iconalign)
161         {
162                 iconPos = myPos + eX * 2 * mySize.y;
163                 textPos = myPos;
164         }
165         else
166         {
167                 iconPos = myPos;
168                 textPos = myPos + eX * mySize.y;
169         }
170
171         if(bonusNades > 0 || bonusProgress > 0)
172         {
173                 DrawNadeProgressBar(myPos, mySize, bonusProgress, nadeColor);
174
175                 if(autocvar_hud_panel_ammo_text)
176                         drawstring_aspect(textPos, ftos(bonusNades), vec2((2/3) * mySize.x, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
177
178                 if(draw_expanding)
179                         drawpic_aspect_skin_expanding(iconPos, nadeIcon, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, expand_time);
180
181                 drawpic_aspect_skin(iconPos, nadeIcon, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
182         }
183 }
184 #endif
185
186 #ifdef SVQC
187
188 #include <common/gamemodes/_mod.qh>
189 #include <common/monsters/sv_spawn.qh>
190 #include <common/monsters/sv_monsters.qh>
191 #include <server/command/common.qh>
192
193 .float nade_time_primed;
194 .float nade_lifetime;
195
196 .entity nade_spawnloc;
197
198
199 void nade_timer_think(entity this)
200 {
201         this.skin = 8 - (this.owner.wait - time) / (this.owner.nade_lifetime / 10);
202         this.nextthink = time;
203         if(!this.owner || wasfreed(this.owner))
204                 delete(this);
205 }
206
207 void nade_burn_spawn(entity _nade)
208 {
209         CSQCProjectile(_nade, true, REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, _nade)).m_projectile[true], true);
210 }
211
212 void nade_spawn(entity _nade)
213 {
214         entity timer = new(nade_timer);
215         setmodel(timer, MDL_NADE_TIMER);
216         setattachment(timer, _nade, "");
217         timer.colormap = _nade.colormap;
218         timer.glowmod = _nade.glowmod;
219         setthink(timer, nade_timer_think);
220         timer.nextthink = time;
221         timer.wait = _nade.wait;
222         timer.owner = _nade;
223         timer.skin = 10;
224
225         _nade.effects |= EF_LOWPRECISION;
226
227         CSQCProjectile(_nade, true, REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, _nade)).m_projectile[false], true);
228 }
229
230 void normal_nade_boom(entity this)
231 {
232         RadiusDamage(this, this.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
233                 autocvar_g_nades_nade_radius, this, NULL, autocvar_g_nades_nade_force, this.projectiledeathtype, DMG_NOWEP, this.enemy);
234         Damage_DamageInfo(this.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
235                 autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, this.projectiledeathtype, 0, this);
236 }
237
238 void napalm_damage(entity this, float dist, float damage, float edgedamage, float burntime)
239 {
240         entity e;
241         float d;
242         vector p;
243
244         if ( damage < 0 )
245                 return;
246
247         RandomSelection_Init();
248         for(e = WarpZone_FindRadius(this.origin, dist, true); e; e = e.chain)
249                 if(e.takedamage == DAMAGE_AIM)
250                 if(this.realowner != e || autocvar_g_nades_napalm_selfdamage)
251                 if(!IS_PLAYER(e) || !this.realowner || DIFF_TEAM(e, this))
252                 if(!STAT(FROZEN, e))
253                 {
254                         p = e.origin;
255                         p.x += e.mins.x + random() * (e.maxs.x - e.mins.x);
256                         p.y += e.mins.y + random() * (e.maxs.y - e.mins.y);
257                         p.z += e.mins.z + random() * (e.maxs.z - e.mins.z);
258                         d = vlen(WarpZone_UnTransformOrigin(e, this.origin) - p);
259                         if(d < dist)
260                         {
261                                 e.fireball_impactvec = p;
262                                 RandomSelection_AddEnt(e, 1 / (1 + d), !StatusEffects_active(STATUSEFFECT_Burning, e));
263                         }
264                 }
265         if(RandomSelection_chosen_ent)
266         {
267                 d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, this.origin) - RandomSelection_chosen_ent.fireball_impactvec);
268                 d = damage + (edgedamage - damage) * (d / dist);
269                 Fire_AddDamage(RandomSelection_chosen_ent, this.realowner, d * burntime, burntime, this.projectiledeathtype);
270                 //trailparticles(this, particleeffectnum(EFFECT_FIREBALL_LASER), this.origin, RandomSelection_chosen_ent.fireball_impactvec);
271                 Send_Effect(EFFECT_FIREBALL_LASER, this.origin, RandomSelection_chosen_ent.fireball_impactvec - this.origin, 1);
272         }
273 }
274
275
276 void napalm_ball_think(entity this)
277 {
278         if(round_handler_IsActive())
279         if(!round_handler_IsRoundStarted())
280         {
281                 delete(this);
282                 return;
283         }
284
285         if(time > this.pushltime)
286         {
287                 delete(this);
288                 return;
289         }
290
291         vector midpoint = ((this.absmin + this.absmax) * 0.5);
292         if(pointcontents(midpoint) == CONTENT_WATER)
293         {
294                 this.velocity = this.velocity * 0.5;
295
296                 if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
297                         { this.velocity_z = 200; }
298         }
299
300         this.angles = vectoangles(this.velocity);
301
302         napalm_damage(this, autocvar_g_nades_napalm_ball_radius,autocvar_g_nades_napalm_ball_damage,
303                                   autocvar_g_nades_napalm_ball_damage,autocvar_g_nades_napalm_burntime);
304
305         this.nextthink = time + 0.1;
306 }
307
308
309 void nade_napalm_ball(entity this)
310 {
311         entity proj;
312         vector kick;
313
314         spamsound(this, CH_SHOTS, SND_FIREBALL_FIRE, VOL_BASE, ATTEN_NORM);
315
316         proj = new(grenade);
317         proj.owner = this.owner;
318         proj.realowner = this.realowner;
319         proj.team = this.owner.team;
320         proj.bot_dodge = true;
321         proj.bot_dodgerating = autocvar_g_nades_napalm_ball_damage;
322         set_movetype(proj, MOVETYPE_BOUNCE);
323         proj.projectiledeathtype = DEATH_NADE_NAPALM.m_id;
324         PROJECTILE_MAKETRIGGER(proj);
325         setmodel(proj, MDL_Null);
326         proj.scale = 1;//0.5;
327         setsize(proj, '-4 -4 -4', '4 4 4');
328         setorigin(proj, this.origin);
329         setthink(proj, napalm_ball_think);
330         proj.nextthink = time;
331         proj.damageforcescale = autocvar_g_nades_napalm_ball_damageforcescale;
332         proj.effects = EF_LOWPRECISION | EF_FLAME;
333
334         kick.x =(random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread;
335         kick.y = (random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread;
336         kick.z = (random()/2+0.5) * autocvar_g_nades_napalm_ball_spread;
337         proj.velocity = kick;
338
339         proj.pushltime = time + autocvar_g_nades_napalm_ball_lifetime;
340
341         proj.angles = vectoangles(proj.velocity);
342         proj.flags = FL_PROJECTILE;
343         IL_PUSH(g_projectiles, proj);
344         IL_PUSH(g_bot_dodge, proj);
345         proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
346
347         //CSQCProjectile(proj, true, PROJECTILE_NAPALM_FIRE, true);
348 }
349
350
351 void napalm_fountain_think(entity this)
352 {
353
354         if(round_handler_IsActive())
355         if(!round_handler_IsRoundStarted())
356         {
357                 delete(this);
358                 return;
359         }
360
361         if(time >= this.ltime)
362         {
363                 delete(this);
364                 return;
365         }
366
367         vector midpoint = ((this.absmin + this.absmax) * 0.5);
368         if(pointcontents(midpoint) == CONTENT_WATER)
369         {
370                 this.velocity = this.velocity * 0.5;
371
372                 if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
373                         { this.velocity_z = 200; }
374
375                 UpdateCSQCProjectile(this);
376         }
377
378         napalm_damage(this, autocvar_g_nades_napalm_fountain_radius, autocvar_g_nades_napalm_fountain_damage,
379                 autocvar_g_nades_napalm_fountain_edgedamage, autocvar_g_nades_napalm_burntime);
380
381         this.nextthink = time + 0.1;
382         if(time >= this.nade_special_time)
383         {
384                 this.nade_special_time = time + autocvar_g_nades_napalm_fountain_delay;
385                 nade_napalm_ball(this);
386         }
387 }
388
389 void nade_napalm_boom(entity this)
390 {
391         for (int c = 0; c < autocvar_g_nades_napalm_ball_count; c++)
392                 nade_napalm_ball(this);
393
394         entity fountain = new(nade_napalm_fountain);
395         fountain.owner = this.owner;
396         fountain.realowner = this.realowner;
397         fountain.origin = this.origin;
398         fountain.flags = FL_PROJECTILE;
399         IL_PUSH(g_projectiles, fountain);
400         IL_PUSH(g_bot_dodge, fountain);
401         setorigin(fountain, fountain.origin);
402         setthink(fountain, napalm_fountain_think);
403         fountain.nextthink = time;
404         fountain.ltime = time + autocvar_g_nades_napalm_fountain_lifetime;
405         fountain.pushltime = fountain.ltime;
406         fountain.team = this.team;
407         set_movetype(fountain, MOVETYPE_TOSS);
408         fountain.projectiledeathtype = DEATH_NADE_NAPALM.m_id;
409         fountain.bot_dodge = true;
410         fountain.bot_dodgerating = autocvar_g_nades_napalm_fountain_damage;
411         fountain.nade_special_time = time;
412         setsize(fountain, '-16 -16 -16', '16 16 16');
413         CSQCProjectile(fountain, true, PROJECTILE_NAPALM_FOUNTAIN, true);
414 }
415
416 void nade_ice_freeze(entity freezefield, entity frost_target, float freezetime)
417 {
418         frost_target.frozen_by = freezefield.realowner;
419         Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1);
420         Freeze(frost_target, 1 / freezetime, FROZEN_TEMP_DYING, false);
421
422         Drop_Special_Items(frost_target);
423 }
424
425 void nade_ice_think(entity this)
426 {
427         if(round_handler_IsActive())
428         if(!round_handler_IsRoundStarted())
429         {
430                 delete(this);
431                 return;
432         }
433
434         if(time >= this.ltime)
435         {
436                 if ( autocvar_g_nades_ice_explode )
437                 {
438                         entity expef = EFFECT_NADE_EXPLODE(this.realowner.team);
439                         Send_Effect(expef, this.origin + '0 0 1', '0 0 0', 1);
440                         sound(this, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
441
442                         normal_nade_boom(this);
443                 }
444                 delete(this);
445                 return;
446         }
447
448
449         this.nextthink = time + 0.1;
450
451         // gaussian
452         float randomr;
453         randomr = random();
454         randomr = exp(-5 * randomr * randomr) * autocvar_g_nades_nade_radius;
455         float randomw;
456         randomw = random() * M_PI * 2;
457         vector randomp;
458         randomp.x = randomr * cos(randomw);
459         randomp.y = randomr * sin(randomw);
460         randomp.z = 1;
461         Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, this.origin + randomp, '0 0 0', 1);
462
463         if(time >= this.nade_special_time)
464         {
465                 this.nade_special_time = time + 0.7;
466
467                 Send_Effect(EFFECT_ELECTRO_IMPACT, this.origin, '0 0 0', 1);
468                 Send_Effect(EFFECT_ICEFIELD, this.origin, '0 0 0', 1);
469         }
470
471
472         float current_freeze_time = this.ltime - time - 0.1;
473
474 #define ICE_NADE_RADIUS_TEAMCHECK(checked) \
475         if (checked) \
476         if (!it.revival_time || ((time - it.revival_time) >= 1.5)) \
477         if (!STAT(FROZEN, it)) \
478                 nade_ice_freeze(this, it, current_freeze_time); \
479         break;
480
481         FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_nades_nade_radius, it != this && it.takedamage && !IS_DEAD(it) && GetResource(it, RES_HEALTH) > 0 && current_freeze_time > 0,
482         {
483                 switch (autocvar_g_nades_ice_teamcheck)
484                 {
485                         // 1: nade owner isn't affected; 2: no teammate is affected; any other number than 1 and 2: friendly fire
486                         case 1:  ICE_NADE_RADIUS_TEAMCHECK(it != this.realowner);
487                         case 2:  ICE_NADE_RADIUS_TEAMCHECK(DIFF_TEAM(it, this.realowner) && it != this.realowner);
488                         default: ICE_NADE_RADIUS_TEAMCHECK(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(it, this.realowner) || it == this.realowner));
489                 }
490         });
491 #undef ICE_NADE_RADIUS_TEAMCHECK
492 }
493
494 void nade_ice_boom(entity this)
495 {
496         entity fountain = new(nade_ice_fountain);
497         fountain.owner = this.owner;
498         fountain.realowner = this.realowner;
499         fountain.origin = this.origin;
500         setorigin(fountain, fountain.origin);
501         setthink(fountain, nade_ice_think);
502         fountain.nextthink = time;
503         fountain.ltime = time + autocvar_g_nades_ice_freeze_time;
504         fountain.pushltime = fountain.wait = fountain.ltime;
505         fountain.team = this.team;
506         set_movetype(fountain, MOVETYPE_TOSS);
507         fountain.projectiledeathtype = DEATH_NADE_ICE.m_id;
508         fountain.bot_dodge = false;
509         setsize(fountain, '-16 -16 -16', '16 16 16');
510         fountain.nade_special_time = time + 0.3;
511         fountain.angles = this.angles;
512
513         if ( autocvar_g_nades_ice_explode )
514         {
515                 setmodel(fountain, MDL_PROJECTILE_GRENADE);
516                 entity timer = new(nade_timer);
517                 setmodel(timer, MDL_NADE_TIMER);
518                 setattachment(timer, fountain, "");
519                 timer.colormap = this.colormap;
520                 timer.glowmod = this.glowmod;
521                 setthink(timer, nade_timer_think);
522                 timer.nextthink = time;
523                 timer.wait = fountain.ltime;
524                 timer.owner = fountain;
525                 timer.skin = 10;
526         }
527         else
528                 setmodel(fountain, MDL_Null);
529 }
530
531 void nade_translocate_boom(entity this)
532 {
533         if(this.realowner.vehicle)
534                 return;
535
536         setsize(this, PL_MIN_CONST-'16 16 16', PL_MAX_CONST+'16 16 16');
537
538         if(!move_out_of_solid(this))
539         {
540                 sprint(this.realowner, "^1Couldn't move the translocator out of solid! origin: ", vtos(this.origin), "\n");
541                 return;
542         }
543
544         vector locout = this.origin + '0 0 1' * (1 - this.realowner.mins.z - 24);
545         tracebox(locout, this.realowner.mins, this.realowner.maxs, locout, MOVE_NOMONSTERS, this.realowner);
546         locout = trace_endpos;
547
548         makevectors(this.realowner.angles);
549
550         MUTATOR_CALLHOOK(PortalTeleport, this.realowner);
551
552         TeleportPlayer(this, this.realowner, locout, this.realowner.angles, v_forward * vlen(this.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
553 }
554
555 void nade_spawn_boom(entity this)
556 {
557         entity player = this.realowner;
558         entity spawnloc = new(nade_spawn_loc);
559         setorigin(spawnloc, this.origin);
560         setsize(spawnloc, player.mins, player.maxs);
561         set_movetype(spawnloc, MOVETYPE_NONE);
562         spawnloc.solid = SOLID_NOT;
563         spawnloc.drawonlytoclient = player;
564         spawnloc.effects = EF_STARDUST;
565         spawnloc.cnt = autocvar_g_nades_spawn_count;
566
567         if(player.nade_spawnloc)
568                 delete(player.nade_spawnloc);
569
570         player.nade_spawnloc = spawnloc;
571 }
572
573 void nades_orb_think(entity this)
574 {
575         if(time >= this.ltime)
576         {
577                 delete(this);
578                 return;
579         }
580
581         this.nextthink = time;
582
583         if(time >= this.nade_special_time)
584         {
585                 this.nade_special_time = time+0.25;
586                 this.nade_show_particles = 1;
587         }
588         else
589                 this.nade_show_particles = 0;
590 }
591
592 entity nades_spawn_orb(entity own, entity realown, vector org, float orb_ltime, float orb_rad)
593 {
594         // NOTE: this function merely places an orb
595         // you must add a custom touch function to the returned entity if desired
596         // also set .colormod if you wish to have it colorized
597         entity orb = new(nades_spawn_orb);
598         orb.owner = own;
599         orb.realowner = realown;
600         setorigin(orb, org);
601
602         orb.orb_lifetime = orb_ltime; // required for timers
603         orb.ltime = time + orb.orb_lifetime;
604         orb.bot_dodge = false;
605         orb.team = realown.team;
606         orb.solid = SOLID_TRIGGER;
607
608         setmodel(orb, MDL_NADE_ORB);
609         orb.skin = 1;
610         orb.orb_radius = orb_rad; // required for fading
611         vector size = '1 1 1' * orb.orb_radius / 2;
612         setsize(orb, -size, size);
613
614         Net_LinkEntity(orb, true, 0, orb_send);
615         orb.SendFlags |= 1;
616
617         setthink(orb, nades_orb_think);
618         orb.nextthink = time;
619
620         return orb;
621 }
622
623 void nade_entrap_touch(entity this, entity toucher)
624 {
625         if(DIFF_TEAM(toucher, this.realowner)) // TODO: what if realowner changes team or disconnects?
626         {
627                 if (!isPushable(toucher))
628                         return;
629
630                 float pushdeltatime = time - toucher.lastpushtime;
631                 if (pushdeltatime > 0.15) pushdeltatime = 0;
632                 toucher.lastpushtime = time;
633                 if(!pushdeltatime) return;
634
635                 // div0: ticrate independent, 1 = identity (not 20)
636                 toucher.velocity = toucher.velocity * (autocvar_g_nades_entrap_strength ** pushdeltatime);
637
638         #ifdef SVQC
639                 UpdateCSQCProjectile(toucher);
640         #endif
641         }
642
643         if ( IS_REAL_CLIENT(toucher) || (IS_VEHICLE(toucher) && toucher.owner) )
644         {
645                 entity show_tint = (IS_VEHICLE(toucher) && toucher.owner) ? toucher.owner : toucher;
646                 show_tint.nade_entrap_time = time + 0.1;
647         }
648 }
649
650 void nade_entrap_boom(entity this)
651 {
652         entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_entrap_time, autocvar_g_nades_entrap_radius);
653
654         settouch(orb, nade_entrap_touch);
655         orb.colormod = NADE_TYPE_ENTRAP.m_color;
656 }
657
658 void nade_heal_touch(entity this, entity toucher)
659 {
660         float maxhealth;
661         float health_factor;
662
663         if(IS_PLAYER(toucher) || IS_MONSTER(toucher) || IS_VEHICLE(toucher))
664         if(!IS_DEAD(toucher))
665         if(!STAT(FROZEN, toucher))
666         {
667                 health_factor = autocvar_g_nades_heal_rate*frametime/2;
668                 if ( toucher != this.realowner )
669                         health_factor *= (SAME_TEAM(toucher,this)) ? autocvar_g_nades_heal_friend : autocvar_g_nades_heal_foe;
670
671                 if ( health_factor > 0 )
672                 {
673                         maxhealth = (IS_MONSTER(toucher)) ? toucher.max_health : g_pickup_healthmega_max;
674                         float hp = GetResource(toucher, RES_HEALTH);
675                         if (hp < maxhealth)
676                         {
677                                 if (this.nade_show_particles)
678                                         Send_Effect(EFFECT_HEALING, toucher.origin, '0 0 0', 1);
679                                 
680                                 GiveResourceWithLimit(toucher, RES_HEALTH, health_factor, maxhealth);
681                         }
682                 }
683                 else if ( health_factor < 0 )
684                         Damage(toucher,this,this.realowner,-health_factor,DEATH_NADE_HEAL.m_id,DMG_NOWEP,toucher.origin,'0 0 0');
685         }
686 }
687
688 void nade_heal_boom(entity this)
689 {
690         entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_heal_time, autocvar_g_nades_nade_radius);
691
692         settouch(orb, nade_heal_touch);
693         orb.colormod = '1 0 0';
694 }
695
696 void nade_monster_boom(entity this)
697 {
698         if(!autocvar_g_monsters)
699                 return;
700         entity e = spawn();
701         e.noalign = true; // don't drop to floor
702         e = spawnmonster(e, this.pokenade_type, MON_Null, this.realowner, this.realowner, this.origin, false, false, 1);
703         if(!e)
704                 return; // monster failed to be spawned
705
706         if(autocvar_g_nades_pokenade_monster_lifetime > 0)
707                 e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime;
708         e.monster_skill = MONSTER_SKILL_INSANE;
709 }
710
711 void nade_veil_touch(entity this, entity toucher)
712 {
713         if ( IS_REAL_CLIENT(toucher) || (IS_VEHICLE(toucher) && toucher.owner) )
714         {
715                 entity show_tint = (IS_VEHICLE(toucher) && toucher.owner) ? toucher.owner : toucher;
716
717                 float tint_alpha = 0.75;
718                 if(SAME_TEAM(toucher, this.realowner))
719                 {
720                         tint_alpha = 0.45;
721                         if(!show_tint.nade_veil_time)
722                         {
723                                 toucher.nade_veil_prevalpha = toucher.alpha;
724                                 toucher.alpha = -1;
725                         }
726                 }
727                 show_tint.nade_veil_time = time + 0.1;
728         }
729 }
730
731 void nade_veil_boom(entity this)
732 {
733         entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_veil_time, autocvar_g_nades_veil_radius);
734
735         settouch(orb, nade_veil_touch);
736         orb.colormod = NADE_TYPE_VEIL.m_color;
737 }
738
739 void nade_ammo_touch(entity this, entity toucher)
740 {
741         float maxammo = 999;
742         float ammo_factor;
743         float amshells = GetResource(toucher, RES_SHELLS);
744         float ambullets = GetResource(toucher, RES_BULLETS);
745         float amrockets = GetResource(toucher, RES_ROCKETS);
746         float amcells = GetResource(toucher, RES_CELLS);
747         float amplasma = GetResource(toucher, RES_PLASMA);
748         if(IS_PLAYER(toucher) || IS_MONSTER(toucher))
749         if(!IS_DEAD(toucher))
750         if(!STAT(FROZEN, toucher))
751         {
752                 ammo_factor = autocvar_g_nades_ammo_rate*frametime/2;
753                 if ( toucher != this.realowner )
754                         ammo_factor *= (SAME_TEAM(toucher, this)) ? autocvar_g_nades_ammo_friend : autocvar_g_nades_ammo_foe;
755
756 #define CHECK_AMMO_RESOURCE_LIMIT(amresource, res_resource) \
757         if (amresource < maxammo) \
758                 GiveResourceWithLimit(toucher, res_resource, ammo_factor, maxammo);
759
760 #define DROP_AMMO_RESOURCE(amresource, res_resource) \
761         if (amresource > 0) \
762                 SetResource(toucher, res_resource, amresource + ammo_factor);
763                 
764                 if ( ammo_factor > 0 )
765                 {
766                         CHECK_AMMO_RESOURCE_LIMIT(amshells,  RES_SHELLS);
767                         CHECK_AMMO_RESOURCE_LIMIT(ambullets, RES_BULLETS);
768                         CHECK_AMMO_RESOURCE_LIMIT(amrockets, RES_ROCKETS);
769                         CHECK_AMMO_RESOURCE_LIMIT(amcells,   RES_CELLS);
770                         CHECK_AMMO_RESOURCE_LIMIT(amplasma,  RES_PLASMA);
771
772                         if (this.nade_show_particles)
773                                 Send_Effect(EFFECT_HEALING, toucher.origin, '0 0 0', 1);
774                 }
775                 else if ( ammo_factor < 0 )
776                 {
777                         //Foe drops ammo points
778                         DROP_AMMO_RESOURCE(amshells,  RES_SHELLS);
779                         DROP_AMMO_RESOURCE(ambullets, RES_BULLETS);
780                         DROP_AMMO_RESOURCE(amrockets, RES_ROCKETS);
781                         DROP_AMMO_RESOURCE(amcells,   RES_CELLS);
782                         DROP_AMMO_RESOURCE(amplasma,  RES_PLASMA);
783
784                         return;
785                 }
786         }
787 #undef CHECK_AMMO_RESOURCE_LIMIT
788 #undef DROP_AMMO_RESOURCE
789
790         if ( IS_REAL_CLIENT(toucher) || (IS_VEHICLE(toucher) && toucher.owner) )
791         {
792                 entity show_tint = (IS_VEHICLE(toucher) && toucher.owner) ? toucher.owner : toucher;
793                 show_tint.nade_ammo_time = time + 0.1;
794         }
795 }
796
797 void nade_ammo_boom(entity this)
798 {
799         entity orb = nades_spawn_orb(this.owner, this.realowner, this.origin, autocvar_g_nades_ammo_time, autocvar_g_nades_nade_radius);
800
801         settouch(orb, nade_ammo_touch);
802         orb.colormod = '0.66 0.33 0';
803 }
804
805 void DarkBlinking(entity e)
806 {
807         if(e == NULL) return;
808
809         int accepted = VerifyClientEntity(e, true, false);
810
811         if(accepted > 0)
812         {
813                 msg_entity = e;
814                 WriteHeader(MSG_ONE, TE_CSQC_DARKBLINKING);
815         }
816 }
817
818 void nade_dark_think(entity this)
819 {
820         if(round_handler_IsActive())
821         if(!round_handler_IsRoundStarted())
822         {
823                 delete(this);
824                 return;
825         }
826
827         if(time >= this.ltime)
828         {
829                 if ( autocvar_g_nades_dark_explode )
830                 {
831                         entity expef = EFFECT_NADE_EXPLODE(this.realowner.team);
832                         Send_Effect(expef, this.origin + '0 0 1', '0 0 0', 1);
833                         sound(this, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
834
835                         normal_nade_boom(this);
836                 }
837                 else
838                         Send_Effect(EFFECT_SPAWN_PURPLE, this.origin + '0 0 1', '0 0 0', 1);
839
840                 delete(this);
841                 return;
842         }
843
844         this.nextthink = time + 0.1;
845
846         // gaussian
847         float randomr;
848         randomr = random();
849         randomr = exp(-5 * randomr * randomr) * autocvar_g_nades_nade_radius;
850         float randomw;
851         randomw = random() * M_PI * 2;
852         vector randomp;
853         randomp.x = randomr * cos(randomw);
854         randomp.y = randomr * sin(randomw);
855         randomp.z = 1;
856         Send_Effect(EFFECT_DARKFIELD, this.origin + randomp, '0 0 0', 1);
857
858         if(time >= this.nade_special_time)
859         {
860                 this.nade_special_time = time + 0.7;
861                 Send_Effect(EFFECT_DARKFIELD, this.origin, '0 0 0', 1);
862         }
863
864         float current_dark_time = this.ltime - time - 0.1;
865
866 #define DARK_NADE_RADIUS_TEAMCHECK(checked) \
867         if (checked) \
868         if ( IS_REAL_CLIENT(it) ) \
869         { \
870                 STAT(NADE_DARK_TIME, it) = time + 0.1; \
871                 DarkBlinking(it); \
872         } \
873         break;
874
875         FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_nades_nade_radius, it != this && it.takedamage && !IS_DEAD(it) && GetResource(it, RES_HEALTH) > 0 && current_dark_time > 0,
876         {
877                 switch (autocvar_g_nades_dark_teamcheck)
878                 {
879                         // 1: nade owner isn't affected; 2: no teammate is affected; any other number than 1 and 2: friendly fire
880                         case 1:  DARK_NADE_RADIUS_TEAMCHECK(it != this.realowner);
881                         case 2:  DARK_NADE_RADIUS_TEAMCHECK(DIFF_TEAM(it, this.realowner) && it != this.realowner);
882                         default: DARK_NADE_RADIUS_TEAMCHECK(!autocvar_g_nades_dark_teamcheck || (DIFF_TEAM(it, this.realowner) && it != this.realowner));
883                 }
884         });
885 #undef DARK_NADE_RADIUS_TEAMCHECK
886 }
887
888 void nade_dark_boom(entity this)
889 {
890         entity fountain = new(nade_dark_fountain);
891         fountain.owner = this.owner;
892         fountain.realowner = this.realowner;
893         fountain.origin = this.origin;
894         setorigin(fountain, fountain.origin);
895         setthink(fountain, nade_dark_think);
896         fountain.nextthink = time;
897         fountain.ltime = time + autocvar_g_nades_dark_time;
898         fountain.pushltime = fountain.wait = fountain.ltime;
899         fountain.team = this.team;
900         set_movetype(fountain, MOVETYPE_TOSS);
901         fountain.projectiledeathtype = DEATH_NADE.m_id;
902         fountain.bot_dodge = false;
903         setsize(fountain, '-16 -16 -16', '16 16 16');
904         fountain.nade_special_time = time + 0.3;
905         fountain.angles = this.angles;
906
907         if ( autocvar_g_nades_dark_explode )
908         {
909                 setmodel(fountain, MDL_PROJECTILE_GRENADE);
910                 entity timer = new(nade_timer);
911                 setmodel(timer, MDL_NADE_TIMER);
912                 setattachment(timer, fountain, "");
913                 timer.colormap = this.colormap;
914                 timer.glowmod = this.glowmod;
915                 setthink(timer, nade_timer_think);
916                 timer.nextthink = time;
917                 timer.wait = fountain.ltime;
918                 timer.owner = fountain;
919                 timer.skin = 10;
920         }
921         else
922                 setmodel(fountain, MDL_Null);
923 }
924
925 void nade_boom(entity this)
926 {
927         entity expef = NULL;
928         bool nade_blast = true;
929
930 #define GET_NADE_TYPE_SPAWN_EFFECT(team_owner) \
931         ((team_owner) == NUM_TEAM_1 ? EFFECT_SPAWN_RED : \
932         ((team_owner) == NUM_TEAM_2 ? EFFECT_SPAWN_BLUE : \
933         ((team_owner) == NUM_TEAM_3 ? EFFECT_SPAWN_YELLOW : \
934         ((team_owner) == NUM_TEAM_4 ? EFFECT_SPAWN_PINK : \
935         EFFECT_SPAWN_NEUTRAL))))
936
937 #define SET_NADE_EFFECT(nade_type, blast, exp_effect) \
938         case nade_type: \
939                 nade_blast = blast; \
940                 expef = exp_effect; \
941                 break;
942
943         switch ( REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, this)) )
944         {
945                 SET_NADE_EFFECT(NADE_TYPE_NAPALM,      autocvar_g_nades_napalm_blast, EFFECT_EXPLOSION_MEDIUM);
946                 SET_NADE_EFFECT(NADE_TYPE_ICE,         false,                         EFFECT_ELECTRO_COMBO /* hookbomb_explode electro_combo bigplasma_impact */);
947                 SET_NADE_EFFECT(NADE_TYPE_TRANSLOCATE, false,                         NULL);
948                 SET_NADE_EFFECT(NADE_TYPE_MONSTER,     true,                          (!autocvar_g_monsters) ? EFFECT_NADE_EXPLODE(this.realowner.team) : NULL);
949                 SET_NADE_EFFECT(NADE_TYPE_SPAWN,       false,                         GET_NADE_TYPE_SPAWN_EFFECT(this.realowner.team));
950                 SET_NADE_EFFECT(NADE_TYPE_HEAL,        false,                         EFFECT_SPAWN_RED);
951                 SET_NADE_EFFECT(NADE_TYPE_ENTRAP,      false,                         EFFECT_SPAWN_YELLOW);
952                 SET_NADE_EFFECT(NADE_TYPE_VEIL,        false,                         EFFECT_SPAWN_NEUTRAL);
953                 SET_NADE_EFFECT(NADE_TYPE_AMMO,        false,                         EFFECT_SPAWN_BROWN);
954                 SET_NADE_EFFECT(NADE_TYPE_DARK,        false,                         EFFECT_EXPLOSION_MEDIUM);
955                 SET_NADE_EFFECT(NADE_TYPE_NORMAL,      true,                          EFFECT_NADE_EXPLODE(this.realowner.team));
956                 default: expef = EFFECT_NADE_EXPLODE(this.realowner.team); break;
957         }
958 #undef GET_NADE_TYPE_SPAWN_EFFECT
959 #undef SET_NADE_EFFECT
960
961         if(expef)
962                 Send_Effect(expef, findbetterlocation(this.origin, 8), '0 0 0', 1);
963
964         sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM);
965         sound(this, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
966
967         this.event_damage = func_null; // prevent somehow calling damage in the next call
968
969         if(nade_blast)
970                 normal_nade_boom(this);
971
972         if(this.takedamage)
973         switch ( REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, this)) )
974         {
975                 case NADE_TYPE_NAPALM:      nade_napalm_boom(this);       break;
976                 case NADE_TYPE_ICE:         nade_ice_boom(this);          break;
977                 case NADE_TYPE_TRANSLOCATE: nade_translocate_boom(this);  break;
978                 case NADE_TYPE_SPAWN:       nade_spawn_boom(this);        break;
979                 case NADE_TYPE_HEAL:        nade_heal_boom(this);         break;
980                 case NADE_TYPE_MONSTER:     nade_monster_boom(this);      break;
981                 case NADE_TYPE_ENTRAP:      nade_entrap_boom(this);       break;
982                 case NADE_TYPE_VEIL:        nade_veil_boom(this);         break;
983                 case NADE_TYPE_AMMO:        nade_ammo_boom(this);         break;
984                 case NADE_TYPE_DARK:        nade_dark_boom(this);         break;
985         }
986
987         IL_EACH(g_projectiles, it.classname == "grapplinghook" && it.aiment == this,
988         {
989                 RemoveHook(it);
990         });
991
992         delete(this);
993 }
994
995 void spawn_held_nade(entity player, entity nowner, float ntime, int ntype, string pntype);
996 void nade_pickup(entity this, entity thenade)
997 {
998         spawn_held_nade(this, thenade.realowner, autocvar_g_nades_pickup_time, STAT(NADE_BONUS_TYPE, thenade), thenade.pokenade_type);
999
1000         // set refire so player can't even
1001         this.nade_refire = time + autocvar_g_nades_nade_refire;
1002         STAT(NADE_TIMER, this) = 0;
1003
1004         if(this.nade)
1005                 this.nade.nade_time_primed = thenade.nade_time_primed;
1006 }
1007
1008 bool CanThrowNade(entity this);
1009 void nade_touch(entity this, entity toucher)
1010 {
1011         if(toucher)
1012                 UpdateCSQCProjectile(this);
1013
1014         if(toucher == this.realowner)
1015                 return; // no this impacts
1016
1017         if(autocvar_g_nades_pickup)
1018         if(time >= this.spawnshieldtime)
1019         if(!toucher.nade && GetResource(this, RES_HEALTH) == this.max_health) // no boosted shot pickups, thank you very much
1020         if(CanThrowNade(toucher)) // prevent some obvious things, like dead players
1021         if(IS_REAL_CLIENT(toucher)) // above checks for IS_PLAYER, don't need to do it here
1022         {
1023                 nade_pickup(toucher, this);
1024                 sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
1025                 delete(this);
1026                 return;
1027         }
1028         /*float is_weapclip = 0;
1029         if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
1030         if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
1031         if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
1032                 is_weapclip = 1;*/
1033         if(ITEM_TOUCH_NEEDKILL()) // || is_weapclip)
1034         {
1035                 IL_EACH(g_projectiles, it.classname == "grapplinghook" && it.aiment == this,
1036                 {
1037                         RemoveHook(it);
1038                 });
1039                 delete(this);
1040                 return;
1041         }
1042
1043         PROJECTILE_TOUCH(this, toucher);
1044
1045         //setsize(this, '-2 -2 -2', '2 2 2');
1046         //UpdateCSQCProjectile(this);
1047         if(GetResource(this, RES_HEALTH) == this.max_health)
1048         {
1049                 spamsound(this, CH_SHOTS, SND_GRENADE_BOUNCE_RANDOM(), VOL_BASE, ATTEN_NORM);
1050                 return;
1051         }
1052
1053         this.enemy = toucher;
1054         nade_boom(this);
1055 }
1056
1057 void nade_beep(entity this)
1058 {
1059         sound(this, CH_SHOTS_SINGLE, SND_NADE_BEEP, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
1060         setthink(this, nade_boom);
1061         this.nextthink = max(this.wait, time);
1062 }
1063
1064 void nade_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
1065 {
1066         if(ITEM_DAMAGE_NEEDKILL(deathtype))
1067         {
1068                 this.takedamage = DAMAGE_NO;
1069                 nade_boom(this);
1070                 return;
1071         }
1072
1073         if(STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_TRANSLOCATE.m_id || STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_SPAWN.m_id)
1074                 return;
1075
1076         if (MUTATOR_CALLHOOK(Nade_Damage, this, DEATH_WEAPONOF(deathtype), force, damage)) {}
1077         else if(DEATH_ISWEAPON(deathtype, WEP_BLASTER))
1078         {
1079                 force *= 1.5;
1080                 damage = 0;
1081         }
1082         else if(DEATH_ISWEAPON(deathtype, WEP_VAPORIZER) && (deathtype & HITTYPE_SECONDARY))
1083         {
1084                 force *= 0.5; // too much
1085                 damage = 0;
1086         }
1087         else if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER) || DEATH_ISWEAPON(deathtype, WEP_OVERKILL_NEX))
1088         {
1089                 force *= 6;
1090                 damage = this.max_health * 0.55;
1091         }
1092         else if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN) || DEATH_ISWEAPON(deathtype, WEP_OVERKILL_MACHINEGUN))
1093                 damage = this.max_health * 0.1;
1094         else if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) || DEATH_ISWEAPON(deathtype, WEP_OVERKILL_SHOTGUN)) // WEAPONTODO
1095         {
1096                 if(!(deathtype & HITTYPE_SECONDARY))
1097                         damage = this.max_health * 1.15;
1098         }
1099
1100         // melee slaps
1101         entity death_weapon = DEATH_WEAPONOF(deathtype);
1102         if(((deathtype & HITTYPE_SECONDARY) ? (death_weapon.spawnflags & WEP_TYPE_MELEE_SEC) : (death_weapon.spawnflags & WEP_TYPE_MELEE_PRI)))
1103         {
1104                 damage = this.max_health * 0.1;
1105                 force *= 10;
1106         }
1107
1108         this.velocity += force;
1109         UpdateCSQCProjectile(this);
1110
1111         if(damage <= 0 || ((IS_ONGROUND(this)) && IS_PLAYER(attacker)))
1112                 return;
1113
1114         float hp = GetResource(this, RES_HEALTH);
1115         if(hp == this.max_health)
1116         {
1117                 sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
1118                 this.nextthink = max(time + this.nade_lifetime, time);
1119                 setthink(this, nade_beep);
1120         }
1121
1122         hp -= damage;
1123         SetResource(this, RES_HEALTH, hp);
1124
1125         if(STAT(NADE_BONUS_TYPE, this) != NADE_TYPE_TRANSLOCATE.m_id && STAT(NADE_BONUS_TYPE, this) != NADE_TYPE_SPAWN.m_id)
1126         if(STAT(NADE_BONUS_TYPE, this) != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker))
1127                 this.realowner = attacker;
1128
1129         if(hp <= 0)
1130         {
1131                 if(autocvar_g_nades_spawn_destroy_damage > 0 && STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_SPAWN.m_id)
1132                         Damage(this.realowner, attacker, attacker, autocvar_g_nades_spawn_destroy_damage, DEATH_TOUCHEXPLODE.m_id, DMG_NOWEP, this.realowner.origin, '0 0 0');
1133
1134                 if(autocvar_g_nades_translocate_destroy_damage > 0 && STAT(NADE_BONUS_TYPE, this) == NADE_TYPE_TRANSLOCATE.m_id)
1135                 {
1136                         Damage(this.realowner, attacker, attacker, autocvar_g_nades_translocate_destroy_damage, DEATH_TOUCHEXPLODE.m_id, DMG_NOWEP, this.realowner.origin, '0 0 0');
1137                         W_PrepareExplosionByDamage(this, this.realowner, nade_boom); // Don't change the owner
1138
1139                         return;
1140                 }
1141
1142                 W_PrepareExplosionByDamage(this, attacker, nade_boom);
1143         }
1144         else
1145                 nade_burn_spawn(this);
1146 }
1147
1148 void toss_nade(entity e, bool set_owner, vector _velocity, float _time)
1149 {
1150         if(e.nade == NULL)
1151                 return;
1152
1153         entity _nade = e.nade;
1154         e.nade = NULL;
1155
1156         if(e.fake_nade)
1157                 delete(e.fake_nade);
1158         e.fake_nade = NULL;
1159
1160         Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_NADES);
1161
1162         makevectors(e.v_angle);
1163
1164         // NOTE: always throw from first weapon entity?
1165         W_SetupShot(e, _nade.weaponentity_fld, false, false, SND_Null, CH_WEAPON_A, 0, DEATH_NADE.m_id);
1166
1167         vector offset = (v_forward * autocvar_g_nades_throw_offset.x)
1168                       + (v_right * autocvar_g_nades_throw_offset.y)
1169                       + (v_up * autocvar_g_nades_throw_offset.z);
1170
1171         setorigin(_nade, w_shotorg + offset);
1172         //setmodel(_nade, MDL_PROJECTILE_NADE);
1173         //setattachment(_nade, NULL, "");
1174         PROJECTILE_MAKETRIGGER(_nade);
1175         if(STAT(NADES_SMALL, e))
1176                 setsize(_nade, '-8 -8 -8', '8 8 8');
1177         else
1178                 setsize(_nade, '-16 -16 -16', '16 16 16');
1179         set_movetype(_nade, MOVETYPE_BOUNCE);
1180
1181         tracebox(_nade.origin, _nade.mins, _nade.maxs, _nade.origin, MOVE_NOMONSTERS, _nade);
1182         if (trace_startsolid)
1183                 setorigin(_nade, e.origin);
1184
1185         if(e.v_angle.x >= 70 && e.v_angle.x <= 110 && PHYS_INPUT_BUTTON_CROUCH(e))
1186                 _nade.velocity = '0 0 100';
1187         else if(autocvar_g_nades_nade_newton_style == 1)
1188                 _nade.velocity = e.velocity + _velocity;
1189         else if(autocvar_g_nades_nade_newton_style == 2)
1190                 _nade.velocity = _velocity;
1191         else
1192                 _nade.velocity = W_CalculateProjectileVelocity(e, e.velocity, _velocity, true);
1193
1194         if(set_owner)
1195                 _nade.realowner = e;
1196
1197         settouch(_nade, nade_touch);
1198         _nade.spawnshieldtime = time + 0.1; // prevent instantly picking up again
1199         SetResource(_nade, RES_HEALTH, autocvar_g_nades_nade_health);
1200         _nade.max_health = GetResource(_nade, RES_HEALTH);
1201         _nade.takedamage = DAMAGE_AIM;
1202         _nade.event_damage = nade_damage;
1203         setcefc(_nade, func_null);
1204         _nade.exteriormodeltoclient = NULL;
1205         _nade.traileffectnum = 0;
1206         _nade.teleportable = true;
1207         _nade.pushable = true;
1208         _nade.gravity = 1;
1209         _nade.missile_flags = MIF_SPLASH | MIF_ARC;
1210         _nade.damagedbycontents = true;
1211         IL_PUSH(g_damagedbycontents, _nade);
1212         _nade.angles = vectoangles(_nade.velocity);
1213         _nade.flags = FL_PROJECTILE;
1214         IL_PUSH(g_projectiles, _nade);
1215         IL_PUSH(g_bot_dodge, _nade);
1216         _nade.projectiledeathtype = DEATH_NADE.m_id;
1217         _nade.toss_time = time;
1218         _nade.solid = SOLID_CORPSE; //((STAT(NADE_BONUS_TYPE, _nade) == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX);
1219
1220         if(STAT(NADE_BONUS_TYPE, _nade) == NADE_TYPE_TRANSLOCATE.m_id || STAT(NADE_BONUS_TYPE, _nade) == NADE_TYPE_SPAWN.m_id)
1221                 _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1222         else
1223                 _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
1224
1225         nade_spawn(_nade);
1226
1227         if(_time)
1228         {
1229                 setthink(_nade, nade_boom);
1230                 _nade.nextthink = _time;
1231         }
1232
1233         e.nade_refire = time + autocvar_g_nades_nade_refire;
1234         STAT(NADE_TIMER, e) = 0;
1235 }
1236
1237 void nades_GiveBonus(entity player, float score)
1238 {
1239         if (autocvar_g_nades)
1240         if (autocvar_g_nades_bonus)
1241         if (IS_REAL_CLIENT(player))
1242         if (IS_PLAYER(player) && STAT(NADE_BONUS, player) < autocvar_g_nades_bonus_max)
1243         if (!STAT(FROZEN, player))
1244         if (!IS_DEAD(player))
1245         {
1246                 if ( STAT(NADE_BONUS_SCORE, player) < 1 )
1247                         STAT(NADE_BONUS_SCORE, player) += score/autocvar_g_nades_bonus_score_max;
1248
1249                 if ( STAT(NADE_BONUS_SCORE, player) >= 1 )
1250                 {
1251                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_BONUS);
1252                         play2(player, SND(NADE_BONUS));
1253                         STAT(NADE_BONUS, player)++;
1254                         STAT(NADE_BONUS_SCORE, player) -= 1;
1255                 }
1256         }
1257 }
1258
1259 /** Remove all bonus nades from a player */
1260 void nades_RemoveBonus(entity player)
1261 {
1262         STAT(NADE_BONUS, player) = STAT(NADE_BONUS_SCORE, player) = 0;
1263 }
1264
1265 MUTATOR_HOOKFUNCTION(nades, PutClientInServer)
1266 {
1267     entity player = M_ARGV(0, entity);
1268
1269         nades_RemoveBonus(player);
1270 }
1271
1272 bool nade_customize(entity this, entity client)
1273 {
1274         //if(IS_SPEC(client)) { return false; }
1275         if(client == this.exteriormodeltoclient || (IS_SPEC(client) && client.enemy == this.exteriormodeltoclient))
1276         {
1277                 // somewhat hide the model, but keep the glow
1278                 //this.effects = 0;
1279                 if(this.traileffectnum)
1280                         this.traileffectnum = 0;
1281                 this.alpha = -1;
1282         }
1283         else
1284         {
1285                 //this.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
1286                 if(!this.traileffectnum)
1287                 {
1288                         entity nade = REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, this));
1289                         this.traileffectnum = _particleeffectnum(Nade_TrailEffect(nade.m_projectile[false], this.team).eent_eff_name);
1290                 }
1291                 this.alpha = 1;
1292         }
1293
1294         return true;
1295 }
1296
1297 void spawn_held_nade(entity player, entity nowner, float ntime, int ntype, string pntype)
1298 {
1299         entity n = new(nade), fn = new(fake_nade);
1300
1301         STAT(NADE_BONUS_TYPE, n) = max(1, ntype);
1302         n.pokenade_type = pntype;
1303
1304         if(REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, n)) == NADE_TYPE_Null)
1305                 STAT(NADE_BONUS_TYPE, n) = NADE_TYPE_NORMAL.m_id;
1306
1307         .entity weaponentity = weaponentities[0]; // TODO: unhardcode
1308
1309         setmodel(n, MDL_PROJECTILE_NADE);
1310         //setattachment(n, player, "bip01 l hand");
1311         n.exteriormodeltoclient = player;
1312         setcefc(n, nade_customize);
1313         n.traileffectnum = _particleeffectnum(Nade_TrailEffect(REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, n)).m_projectile[false], player.team).eent_eff_name);
1314         n.colormod = REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, n)).m_color;
1315         n.realowner = nowner;
1316         n.colormap = player.colormap;
1317         n.glowmod = player.glowmod;
1318         n.wait = time + max(0, ntime);
1319         n.nade_time_primed = time;
1320         setthink(n, nade_beep);
1321         n.nextthink = max(n.wait - 3, time);
1322         n.projectiledeathtype = DEATH_NADE.m_id;
1323         n.weaponentity_fld = weaponentity;
1324         n.nade_lifetime = ntime;
1325         n.alpha = REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, n)).m_alpha;
1326
1327         setmodel(fn, MDL_NADE_VIEW);
1328         //setattachment(fn, player.(weaponentity), "");
1329         fn.viewmodelforclient = player;
1330         fn.realowner = fn.owner = player;
1331         fn.colormod = REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, n)).m_color;
1332         fn.colormap = player.colormap;
1333         fn.glowmod = player.glowmod;
1334         setthink(fn, SUB_Remove);
1335         fn.nextthink = n.wait;
1336         fn.weaponentity_fld = weaponentity;
1337         fn.alpha = REGISTRY_GET(Nades, STAT(NADE_BONUS_TYPE, n)).m_alpha;
1338
1339         player.nade = n;
1340         player.fake_nade = fn;
1341 }
1342
1343 void nade_prime(entity this)
1344 {
1345         if(autocvar_g_nades_bonus_only && !STAT(NADE_BONUS, this))
1346                 return; // only allow bonus nades
1347
1348         // TODO: handle old nade if it exists?
1349         if(this.nade)
1350                 delete(this.nade);
1351         this.nade = NULL;
1352
1353         if(this.fake_nade)
1354                 delete(this.fake_nade);
1355         this.fake_nade = NULL;
1356
1357         int ntype;
1358         string pntype = this.pokenade_type;
1359
1360         if(StatusEffects_active(STATUSEFFECT_Strength, this) && autocvar_g_nades_bonus_onstrength)
1361                 ntype = STAT(NADE_BONUS_TYPE, this);
1362         else if (STAT(NADE_BONUS, this) >= 1)
1363         {
1364                 ntype = STAT(NADE_BONUS_TYPE, this);
1365                 pntype = this.pokenade_type;
1366                 STAT(NADE_BONUS, this) -= 1;
1367         }
1368         else
1369         {
1370                 ntype   = ((autocvar_g_nades_client_select) ? CS_CVAR(this).cvar_cl_nade_type : autocvar_g_nades_nade_type);
1371                 pntype  = ((autocvar_g_nades_client_select) ? CS_CVAR(this).cvar_cl_pokenade_type : autocvar_g_nades_pokenade_monster_type);
1372         }
1373
1374         spawn_held_nade(this, this, autocvar_g_nades_nade_lifetime, ntype, pntype);
1375 }
1376
1377 bool CanThrowNade(entity this)
1378 {
1379         return !(this.vehicle || !autocvar_g_nades || IS_DEAD(this) || !IS_PLAYER(this) || weaponLocked(this));
1380 }
1381
1382 .bool nade_altbutton;
1383
1384 void nades_CheckThrow(entity this)
1385 {
1386         if(!CanThrowNade(this))
1387                 return;
1388
1389         entity held_nade = this.nade;
1390         if (!held_nade)
1391         {
1392                 this.nade_altbutton = true;
1393                 if(time > this.nade_refire)
1394                 {
1395                         nade_prime(this);
1396                         this.nade_refire = time + autocvar_g_nades_nade_refire;
1397                 }
1398         }
1399         else
1400         {
1401                 this.nade_altbutton = false;
1402                 if (time >= held_nade.nade_time_primed + 1) {
1403                         makevectors(this.v_angle);
1404                         float _force = time - held_nade.nade_time_primed;
1405                         _force /= autocvar_g_nades_nade_lifetime;
1406                         _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));
1407                         vector dir = (v_forward * 0.75 + v_up * 0.2 + v_right * 0.05);
1408                         dir = W_CalculateSpread(dir, autocvar_g_nades_spread, autocvar_g_weaponspreadfactor, autocvar_g_projectiles_spread_style);
1409                         toss_nade(this, true, dir * _force, 0);
1410                 }
1411         }
1412 }
1413
1414 void nades_Clear(entity player)
1415 {
1416         if(player.nade)
1417                 delete(player.nade);
1418         if(player.fake_nade)
1419                 delete(player.fake_nade);
1420
1421         player.nade = player.fake_nade = NULL;
1422         STAT(NADE_TIMER, player) = 0;
1423 }
1424
1425 int nades_CheckTypes(entity player, int cl_ntype)
1426 {
1427 #define CL_NADE_TYPE_CHECK(cl_ntype, cvar) \
1428         case cl_ntype.m_id: \
1429                 if (!cvar) return NADE_TYPE_NORMAL.m_id; \
1430                 break;
1431
1432         switch (cl_ntype)
1433         {
1434                 CL_NADE_TYPE_CHECK(NADE_TYPE_NAPALM,      autocvar_g_nades_napalm);
1435                 CL_NADE_TYPE_CHECK(NADE_TYPE_ICE,         autocvar_g_nades_ice);
1436                 CL_NADE_TYPE_CHECK(NADE_TYPE_TRANSLOCATE, autocvar_g_nades_translocate);
1437                 CL_NADE_TYPE_CHECK(NADE_TYPE_SPAWN,       autocvar_g_nades_spawn);
1438                 CL_NADE_TYPE_CHECK(NADE_TYPE_HEAL,        autocvar_g_nades_heal);
1439                 CL_NADE_TYPE_CHECK(NADE_TYPE_MONSTER,     autocvar_g_nades_pokenade);
1440                 CL_NADE_TYPE_CHECK(NADE_TYPE_ENTRAP,      autocvar_g_nades_entrap);
1441                 CL_NADE_TYPE_CHECK(NADE_TYPE_VEIL,        autocvar_g_nades_veil);
1442                 CL_NADE_TYPE_CHECK(NADE_TYPE_AMMO,        autocvar_g_nades_ammo);
1443                 CL_NADE_TYPE_CHECK(NADE_TYPE_DARK,        autocvar_g_nades_dark);
1444         }
1445         return cl_ntype;
1446 #undef CL_NADE_TYPE_CHECK
1447 }
1448
1449 MUTATOR_HOOKFUNCTION(nades, VehicleEnter)
1450 {
1451         entity player = M_ARGV(0, entity);
1452
1453         if(player.nade)
1454                 toss_nade(player, true, '0 0 100', max(player.nade.wait, time + 0.05));
1455 }
1456
1457 CLASS(NadeOffhand, OffhandWeapon)
1458     METHOD(NadeOffhand, offhand_think, void(NadeOffhand this, entity player, bool key_pressed))
1459     {
1460         entity held_nade = player.nade;
1461
1462         if (!CanThrowNade(player)) return;
1463         if (!(time > player.nade_refire)) return;
1464                 if (key_pressed) {
1465                         if (!held_nade) {
1466                                 nade_prime(player);
1467                                 held_nade = player.nade;
1468                         }
1469                 } else if (time >= held_nade.nade_time_primed + 1) {
1470                         if (held_nade) {
1471                                 makevectors(player.v_angle);
1472                                 float _force = time - held_nade.nade_time_primed;
1473                                 _force /= autocvar_g_nades_nade_lifetime;
1474                                 _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));
1475                                 vector dir = (v_forward * 0.7 + v_up * 0.2 + v_right * 0.1);
1476                                 dir = W_CalculateSpread(dir, autocvar_g_nades_spread, autocvar_g_weaponspreadfactor, autocvar_g_projectiles_spread_style);
1477                                 toss_nade(player, false, dir * _force, 0);
1478                         }
1479                 }
1480     }
1481 ENDCLASS(NadeOffhand)
1482 NadeOffhand OFFHAND_NADE;
1483 REGISTER_MUTATOR(nades, autocvar_g_nades)
1484 {
1485         MUTATOR_ONADD
1486         {
1487                 OFFHAND_NADE = NEW(NadeOffhand);
1488         }
1489         return 0;
1490 }
1491
1492 MUTATOR_HOOKFUNCTION(nades, ForbidThrowCurrentWeapon, CBC_ORDER_LAST)
1493 {
1494     entity player = M_ARGV(0, entity);
1495
1496         if (player.offhand != OFFHAND_NADE || (STAT(WEAPONS, player) & WEPSET(HOOK)) || autocvar_g_nades_override_dropweapon) {
1497                 nades_CheckThrow(player);
1498                 return true;
1499         }
1500 }
1501
1502 #ifdef IN_REVIVING_RANGE
1503         #undef IN_REVIVING_RANGE
1504 #endif
1505
1506 // returns true if player is reviving it
1507 #define IN_REVIVING_RANGE(player, it, revive_extra_size) \
1508         (it != player && !IS_DEAD(it) && SAME_TEAM(it, player) \
1509         && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
1510
1511 MUTATOR_HOOKFUNCTION(nades, PlayerPreThink)
1512 {
1513         entity player = M_ARGV(0, entity);
1514
1515         if (!IS_PLAYER(player)) { return; }
1516
1517         if (player.nade && (player.offhand != OFFHAND_NADE || (STAT(WEAPONS, player) & WEPSET(HOOK))))
1518                 OFFHAND_NADE.offhand_think(OFFHAND_NADE, player, player.nade_altbutton);
1519
1520         entity held_nade = player.nade;
1521         if (held_nade)
1522         {
1523                 STAT(NADE_TIMER, player) = bound(0, (time - held_nade.nade_time_primed) / held_nade.nade_lifetime, 1);
1524                 // LOG_TRACEF("%d %d", STAT(NADE_TIMER, player), time - held_nade.nade_time_primed);
1525                 makevectors(player.angles);
1526                 held_nade.velocity = player.velocity;
1527                 setorigin(held_nade, player.origin + player.view_ofs + v_forward * 8 + v_right * -8 + v_up * 0);
1528                 held_nade.angles_y = player.angles.y;
1529
1530                 if (time + 0.1 >= held_nade.wait)
1531                 {
1532                         toss_nade(player, false, '0 0 0', time + 0.05);
1533                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_THROW);
1534                 }
1535         }
1536
1537         if(IS_PLAYER(player))
1538         {
1539                 if ( autocvar_g_nades_bonus && autocvar_g_nades )
1540                 {
1541                         entity key;
1542                         float key_count = 0;
1543                         FOR_EACH_KH_KEY(key) if(key.owner == player) { ++key_count; }
1544
1545                         float time_score;
1546                         if(GameRules_scoring_is_vip(player))
1547                                 time_score = autocvar_g_nades_bonus_score_time_flagcarrier;
1548                         else
1549                                 time_score = autocvar_g_nades_bonus_score_time;
1550
1551                         if(key_count)
1552                                 time_score = autocvar_g_nades_bonus_score_time_flagcarrier * key_count; // multiply by the number of keys the player is holding
1553
1554                         if(autocvar_g_nades_bonus_client_select)
1555                         {
1556                                 STAT(NADE_BONUS_TYPE, player) = nades_CheckTypes(player, CS_CVAR(player).cvar_cl_nade_type);
1557                                 player.pokenade_type = CS_CVAR(player).cvar_cl_pokenade_type;
1558                         }
1559                         else
1560                         {
1561                                 STAT(NADE_BONUS_TYPE, player) = autocvar_g_nades_bonus_type;
1562                                 player.pokenade_type = autocvar_g_nades_pokenade_monster_type;
1563                         }
1564
1565                         STAT(NADE_BONUS_TYPE, player) = bound(1, STAT(NADE_BONUS_TYPE, player), Nades_COUNT);
1566
1567                         if(STAT(NADE_BONUS_SCORE, player) >= 0 && autocvar_g_nades_bonus_score_max)
1568                                 nades_GiveBonus(player, time_score / autocvar_g_nades_bonus_score_max);
1569                 }
1570                 else
1571                 {
1572                         STAT(NADE_BONUS, player) = STAT(NADE_BONUS_SCORE, player) = 0;
1573                 }
1574
1575                 if(player.nade_veil_time && player.nade_veil_time <= time)
1576                 {
1577                         player.nade_veil_time = 0;
1578                         if(player.vehicle)
1579                                 player.vehicle.alpha = player.vehicle.nade_veil_prevalpha;
1580                         else
1581                                 player.alpha = player.nade_veil_prevalpha;
1582                 }
1583         }
1584
1585         if (!(frametime && IS_PLAYER(player)))
1586                 return true;
1587
1588         entity revivers_last = NULL;
1589         entity revivers_first = NULL;
1590
1591         bool player_is_reviving = false;
1592         int n = 0;
1593         vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
1594         FOREACH_CLIENT(IS_PLAYER(it) && IN_REVIVING_RANGE(player, it, revive_extra_size), {
1595                 // check if player is reviving anyone
1596                 if (STAT(FROZEN, it) == FROZEN_TEMP_DYING)
1597                 {
1598                         if ((STAT(FROZEN, player) == FROZEN_TEMP_DYING))
1599                                 continue;
1600                         if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
1601                                 continue;
1602                         player_is_reviving = true;
1603                         break;
1604                 }
1605
1606                 if (!(STAT(FROZEN, player) == FROZEN_TEMP_DYING))
1607                         continue; // both player and it are NOT frozen
1608                 if (revivers_last)
1609                         revivers_last.chain = it;
1610                 revivers_last = it;
1611                 if (!revivers_first)
1612                         revivers_first = it;
1613                 ++n;
1614         });
1615         if (revivers_last)
1616                 revivers_last.chain = NULL;
1617
1618         if (!n) // no teammate nearby
1619         {
1620                 // freezetag already resets revive progress
1621                 if (!g_freezetag && !STAT(FROZEN, player) && !player_is_reviving)
1622                         STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
1623         }
1624         else if (n > 0 && STAT(FROZEN, player) == FROZEN_TEMP_DYING) // OK, there is at least one teammate reviving us
1625         {
1626                 STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
1627                 // undo what PlayerPreThink did
1628                 STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * player.revive_speed, 1);
1629                 SetResource(player, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * start_health));
1630
1631                 if(STAT(REVIVE_PROGRESS, player) >= 1)
1632                 {
1633                         Unfreeze(player, false);
1634
1635                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, revivers_first.netname);
1636                         Send_Notification(NOTIF_ONE, revivers_first, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
1637                 }
1638
1639                 for(entity it = revivers_first; it; it = it.chain)
1640                         STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
1641         }
1642 }
1643
1644 MUTATOR_HOOKFUNCTION(nades, PlayerPhysics_UpdateStats)
1645 {
1646         entity player = M_ARGV(0, entity);
1647         // these automatically reset, no need to worry
1648
1649         if(player.nade_entrap_time > time)
1650                 STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_nades_entrap_speed;
1651 }
1652
1653 MUTATOR_HOOKFUNCTION(nades, MonsterMove)
1654 {
1655     entity mon = M_ARGV(0, entity);
1656
1657         if (mon.nade_entrap_time > time)
1658         {
1659                 M_ARGV(1, float) *= autocvar_g_nades_entrap_speed; // run speed
1660                 M_ARGV(2, float) *= autocvar_g_nades_entrap_speed; // walk speed
1661         }
1662
1663         if (mon.nade_veil_time && mon.nade_veil_time <= time)
1664         {
1665                 mon.alpha = mon.nade_veil_prevalpha;
1666                 mon.nade_veil_time = 0;
1667         }
1668 }
1669
1670 MUTATOR_HOOKFUNCTION(nades, PlayerSpawn)
1671 {
1672         entity player = M_ARGV(0, entity);
1673
1674         player.nade_refire = (autocvar_g_nades_onspawn) 
1675                 ? time + autocvar_g_nades_nade_refire 
1676                 : time + autocvar_g_spawnshieldtime;
1677
1678         if(autocvar_g_nades_bonus_client_select)
1679                 STAT(NADE_BONUS_TYPE, player) = CS_CVAR(player).cvar_cl_nade_type;
1680
1681         STAT(NADE_TIMER, player) = 0;
1682
1683         if (!player.offhand) player.offhand = OFFHAND_NADE;
1684
1685         if(player.nade_spawnloc)
1686         {
1687                 setorigin(player, player.nade_spawnloc.origin);
1688                 player.nade_spawnloc.cnt -= 1;
1689
1690                 if(player.nade_spawnloc.cnt <= 0)
1691                 {
1692                         delete(player.nade_spawnloc);
1693                         player.nade_spawnloc = NULL;
1694                 }
1695
1696                 if(autocvar_g_nades_spawn_health_respawn > 0)
1697                         SetResource(player, RES_HEALTH, autocvar_g_nades_spawn_health_respawn);
1698         }
1699 }
1700
1701 MUTATOR_HOOKFUNCTION(nades, PlayerDies, CBC_ORDER_LAST)
1702 {
1703         entity frag_attacker = M_ARGV(1, entity);
1704         entity frag_target = M_ARGV(2, entity);
1705
1706         if(frag_target.nade)
1707         if(!STAT(FROZEN, frag_target) || !autocvar_g_freezetag_revive_nade)
1708                 toss_nade(frag_target, true, '0 0 100', max(frag_target.nade.wait, time + 0.05));
1709
1710         if(IS_PLAYER(frag_attacker))
1711         {
1712                 float killcount_bonus = ((CS(frag_attacker).killcount >= 1) ? bound(0, autocvar_g_nades_bonus_score_minor * CS(frag_attacker).killcount, autocvar_g_nades_bonus_score_medium) 
1713                                                                                                                                         : autocvar_g_nades_bonus_score_minor);
1714                 if (SAME_TEAM(frag_attacker, frag_target) || frag_attacker == frag_target)
1715                         nades_RemoveBonus(frag_attacker);
1716                 else if(GameRules_scoring_is_vip(frag_target))
1717                         nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_medium);
1718                 else if(autocvar_g_nades_bonus_score_spree && CS(frag_attacker).killcount > 1)
1719                 {
1720                         #define SPREE_ITEM(counta,countb,center,normal,gentle) \
1721                                 case counta: { nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_spree); break; }
1722                         switch(CS(frag_attacker).killcount)
1723                         {
1724                                 KILL_SPREE_LIST
1725                                 default: nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); break;
1726                         }
1727                         #undef SPREE_ITEM
1728                 }
1729                 else
1730                         nades_GiveBonus(frag_attacker, killcount_bonus);
1731         }
1732
1733         nades_RemoveBonus(frag_target);
1734 }
1735
1736 MUTATOR_HOOKFUNCTION(nades, Damage_Calculate)
1737 {
1738         entity frag_inflictor = M_ARGV(0, entity);
1739         entity frag_attacker = M_ARGV(1, entity);
1740         entity frag_target = M_ARGV(2, entity);
1741         float frag_deathtype = M_ARGV(3, float);
1742
1743         if(autocvar_g_freezetag_revive_nade && STAT(FROZEN, frag_target) && frag_attacker == frag_target && frag_deathtype == DEATH_NADE.m_id)
1744         if(time - frag_inflictor.toss_time <= 0.1)
1745         {
1746                 Unfreeze(frag_target, false);
1747                 SetResource(frag_target, RES_HEALTH, autocvar_g_freezetag_revive_nade_health);
1748                 Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3);
1749                 M_ARGV(4, float) = 0;
1750                 M_ARGV(6, vector) = '0 0 0';
1751                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_NADE, frag_target.netname);
1752                 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
1753         }
1754 }
1755
1756 MUTATOR_HOOKFUNCTION(nades, MonsterDies)
1757 {
1758         entity frag_target = M_ARGV(0, entity);
1759         entity frag_attacker = M_ARGV(1, entity);
1760
1761         if(IS_PLAYER(frag_attacker))
1762         if(DIFF_TEAM(frag_attacker, frag_target))
1763         if(!(frag_target.spawnflags & MONSTERFLAG_SPAWNED))
1764                 nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor);
1765 }
1766
1767 MUTATOR_HOOKFUNCTION(nades, DropSpecialItems)
1768 {
1769         entity frag_target = M_ARGV(0, entity);
1770
1771         if(frag_target.nade)
1772                 toss_nade(frag_target, true, '0 0 0', time + 0.05);
1773 }
1774
1775 void nades_RemovePlayer(entity this)
1776 {
1777         nades_Clear(this);
1778         nades_RemoveBonus(this);
1779 }
1780
1781 MUTATOR_HOOKFUNCTION(nades, MakePlayerObserver) { entity player = M_ARGV(0, entity); nades_RemovePlayer(player); }
1782 MUTATOR_HOOKFUNCTION(nades, ClientDisconnect) { entity player = M_ARGV(0, entity); nades_RemovePlayer(player); }
1783 MUTATOR_HOOKFUNCTION(nades, reset_map_global)
1784 {
1785         FOREACH_CLIENT(IS_PLAYER(it),
1786         {
1787                 nades_RemovePlayer(it);
1788         });
1789 }
1790
1791 MUTATOR_HOOKFUNCTION(nades, SpectateCopy)
1792 {
1793         entity spectatee = M_ARGV(0, entity);
1794         entity client = M_ARGV(1, entity);
1795
1796         STAT(NADE_TIMER, client) = STAT(NADE_TIMER, spectatee);
1797         STAT(NADE_BONUS_TYPE, client) = STAT(NADE_BONUS_TYPE, spectatee);
1798         client.pokenade_type = spectatee.pokenade_type;
1799         STAT(NADE_BONUS, client) = STAT(NADE_BONUS, spectatee);
1800         STAT(NADE_BONUS_SCORE, client) = STAT(NADE_BONUS_SCORE, spectatee);
1801 }
1802
1803 MUTATOR_HOOKFUNCTION(nades, BuildMutatorsPrettyString)
1804 {
1805         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Nades");
1806 }
1807
1808 MUTATOR_HOOKFUNCTION(nades, BuildMutatorsString)
1809 {
1810         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Nades");
1811 }
1812
1813 #endif