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