6 bool autocvar_g_nades_nade_small;
9 REGISTER_STAT(NADES_SMALL, int, autocvar_g_nades_nade_small)
12 entity Nade_TrailEffect(int proj, int nade_team)
16 case PROJECTILE_NADE: return EFFECT_NADE_TRAIL(nade_team);
17 case PROJECTILE_NADE_BURN: return EFFECT_NADE_TRAIL_BURN(nade_team);
20 FOREACH(Nades, true, LAMBDA(
21 for (int j = 0; j < 2; j++)
23 if (it.m_projectile[j] == proj)
25 string trail = it.m_trail[j].eent_eff_name;
26 if (trail) return it.m_trail[j];
37 REGISTER_MUTATOR(cl_nades, true);
38 MUTATOR_HOOKFUNCTION(cl_nades, HUD_Draw_overlay)
40 if (STAT(HEALING_ORB) <= time) return false;
41 MUTATOR_ARGV(0, vector) = NADE_TYPE_HEAL.m_color;
42 MUTATOR_ARGV(0, float) = STAT(HEALING_ORB_ALPHA);
45 MUTATOR_HOOKFUNCTION(cl_nades, Ent_Projectile)
48 if (self.cnt == PROJECTILE_NAPALM_FOUNTAIN)
51 self.traileffect = EFFECT_FIREBALL.m_id;
54 if (Nade_FromProjectile(self.cnt) != NADE_TYPE_Null)
56 setmodel(self, MDL_PROJECTILE_NADE);
57 entity trail = Nade_TrailEffect(self.cnt, self.team);
58 if (trail.eent_eff_name) self.traileffect = trail.m_id;
62 MUTATOR_HOOKFUNCTION(cl_nades, EditProjectile)
65 if (self.cnt == PROJECTILE_NAPALM_FOUNTAIN)
67 loopsound(self, CH_SHOTS_SINGLE, SND(FIREBALL_FLY2), VOL_BASE, ATTEN_NORM);
68 self.mins = '-16 -16 -16';
69 self.maxs = '16 16 16';
72 entity nade_type = Nade_FromProjectile(self.cnt);
73 if (nade_type == NADE_TYPE_Null) return;
74 if(STAT(NADES_SMALL, NULL))
76 self.mins = '-8 -8 -8';
81 self.mins = '-16 -16 -16';
82 self.maxs = '16 16 16';
84 self.colormod = nade_type.m_color;
85 self.move_movetype = MOVETYPE_BOUNCE;
86 self.move_touch = func_null;
88 self.avelocity = randomvec() * 720;
90 if (nade_type == NADE_TYPE_TRANSLOCATE || nade_type == NADE_TYPE_SPAWN)
91 self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
93 self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
95 bool Projectile_isnade(int p)
97 return Nade_FromProjectile(p) != NADE_TYPE_Null;
99 void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time)
101 float bonusNades = STAT(NADE_BONUS);
102 float bonusProgress = STAT(NADE_BONUS_SCORE);
103 float bonusType = STAT(NADE_BONUS_TYPE);
104 Nade def = Nades_from(bonusType);
105 vector nadeColor = def.m_color;
106 string nadeIcon = def.m_icon;
108 vector iconPos, textPos;
110 if(autocvar_hud_panel_ammo_iconalign)
112 iconPos = myPos + eX * 2 * mySize.y;
118 textPos = myPos + eX * mySize.y;
121 if(bonusNades > 0 || bonusProgress > 0)
123 DrawNadeProgressBar(myPos, mySize, bonusProgress, nadeColor);
125 if(autocvar_hud_panel_ammo_text)
126 drawstring_aspect(textPos, ftos(bonusNades), eX * (2/3) * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
129 drawpic_aspect_skin_expanding(iconPos, nadeIcon, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, expand_time);
131 drawpic_aspect_skin(iconPos, nadeIcon, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
138 #include <common/gamemodes/all.qh>
139 #include <common/monsters/spawn.qh>
140 #include <common/monsters/sv_monsters.qh>
141 #include <server/g_subs.qh>
143 REGISTER_MUTATOR(nades, cvar("g_nades"));
145 .float nade_time_primed;
147 .entity nade_spawnloc;
149 void nade_timer_think()
151 self.skin = 8 - (self.owner.wait - time) / (autocvar_g_nades_nade_lifetime / 10);
152 self.nextthink = time;
153 if(!self.owner || wasfreed(self.owner))
157 void nade_burn_spawn(entity _nade)
159 CSQCProjectile(_nade, true, Nades_from(_nade.nade_type).m_projectile[true], true);
162 void nade_spawn(entity _nade)
164 entity timer = new(nade_timer);
165 setmodel(timer, MDL_NADE_TIMER);
166 setattachment(timer, _nade, "");
167 timer.colormap = _nade.colormap;
168 timer.glowmod = _nade.glowmod;
169 timer.think = nade_timer_think;
170 timer.nextthink = time;
171 timer.wait = _nade.wait;
175 _nade.effects |= EF_LOWPRECISION;
177 CSQCProjectile(_nade, true, Nades_from(_nade.nade_type).m_projectile[false], true);
180 void napalm_damage(float dist, float damage, float edgedamage, float burntime)
189 RandomSelection_Init();
190 for(e = WarpZone_FindRadius(self.origin, dist, true); e; e = e.chain)
191 if(e.takedamage == DAMAGE_AIM)
192 if(self.realowner != e || autocvar_g_nades_napalm_selfdamage)
193 if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
197 p.x += e.mins.x + random() * (e.maxs.x - e.mins.x);
198 p.y += e.mins.y + random() * (e.maxs.y - e.mins.y);
199 p.z += e.mins.z + random() * (e.maxs.z - e.mins.z);
200 d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p);
203 e.fireball_impactvec = p;
204 RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e));
207 if(RandomSelection_chosen_ent)
209 d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec);
210 d = damage + (edgedamage - damage) * (d / dist);
211 Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE);
212 //trailparticles(self, particleeffectnum(EFFECT_FIREBALL_LASER), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
213 Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
218 void napalm_ball_think()
220 if(round_handler_IsActive())
221 if(!round_handler_IsRoundStarted())
227 if(time > self.pushltime)
233 vector midpoint = ((self.absmin + self.absmax) * 0.5);
234 if(pointcontents(midpoint) == CONTENT_WATER)
236 self.velocity = self.velocity * 0.5;
238 if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
239 { self.velocity_z = 200; }
242 self.angles = vectoangles(self.velocity);
244 napalm_damage(autocvar_g_nades_napalm_ball_radius,autocvar_g_nades_napalm_ball_damage,
245 autocvar_g_nades_napalm_ball_damage,autocvar_g_nades_napalm_burntime);
247 self.nextthink = time + 0.1;
251 void nade_napalm_ball()
256 spamsound(self, CH_SHOTS, SND(FIREBALL_FIRE), VOL_BASE, ATTEN_NORM);
259 proj.owner = self.owner;
260 proj.realowner = self.realowner;
261 proj.team = self.owner.team;
262 proj.bot_dodge = true;
263 proj.bot_dodgerating = autocvar_g_nades_napalm_ball_damage;
264 proj.movetype = MOVETYPE_BOUNCE;
265 proj.projectiledeathtype = DEATH_NADE_NAPALM.m_id;
266 PROJECTILE_MAKETRIGGER(proj);
267 setmodel(proj, MDL_Null);
268 proj.scale = 1;//0.5;
269 setsize(proj, '-4 -4 -4', '4 4 4');
270 setorigin(proj, self.origin);
271 proj.think = napalm_ball_think;
272 proj.nextthink = time;
273 proj.damageforcescale = autocvar_g_nades_napalm_ball_damageforcescale;
274 proj.effects = EF_LOWPRECISION | EF_FLAME;
276 kick.x =(random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread;
277 kick.y = (random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread;
278 kick.z = (random()/2+0.5) * autocvar_g_nades_napalm_ball_spread;
279 proj.velocity = kick;
281 proj.pushltime = time + autocvar_g_nades_napalm_ball_lifetime;
283 proj.angles = vectoangles(proj.velocity);
284 proj.flags = FL_PROJECTILE;
285 proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
287 //CSQCProjectile(proj, true, PROJECTILE_NAPALM_FIRE, true);
291 void napalm_fountain_think()
294 if(round_handler_IsActive())
295 if(!round_handler_IsRoundStarted())
301 if(time >= self.ltime)
307 vector midpoint = ((self.absmin + self.absmax) * 0.5);
308 if(pointcontents(midpoint) == CONTENT_WATER)
310 self.velocity = self.velocity * 0.5;
312 if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
313 { self.velocity_z = 200; }
315 UpdateCSQCProjectile(self);
318 napalm_damage(autocvar_g_nades_napalm_fountain_radius, autocvar_g_nades_napalm_fountain_damage,
319 autocvar_g_nades_napalm_fountain_edgedamage, autocvar_g_nades_napalm_burntime);
321 self.nextthink = time + 0.1;
322 if(time >= self.nade_special_time)
324 self.nade_special_time = time + autocvar_g_nades_napalm_fountain_delay;
329 void nade_napalm_boom()
333 for (c = 0; c < autocvar_g_nades_napalm_ball_count; c++)
338 fountain.owner = self.owner;
339 fountain.realowner = self.realowner;
340 fountain.origin = self.origin;
341 setorigin(fountain, fountain.origin);
342 fountain.think = napalm_fountain_think;
343 fountain.nextthink = time;
344 fountain.ltime = time + autocvar_g_nades_napalm_fountain_lifetime;
345 fountain.pushltime = fountain.ltime;
346 fountain.team = self.team;
347 fountain.movetype = MOVETYPE_TOSS;
348 fountain.projectiledeathtype = DEATH_NADE_NAPALM.m_id;
349 fountain.bot_dodge = true;
350 fountain.bot_dodgerating = autocvar_g_nades_napalm_fountain_damage;
351 fountain.nade_special_time = time;
352 setsize(fountain, '-16 -16 -16', '16 16 16');
353 CSQCProjectile(fountain, true, PROJECTILE_NAPALM_FOUNTAIN, true);
356 void nade_ice_freeze(entity freezefield, entity frost_target, float freeze_time)
358 frost_target.frozen_by = freezefield.realowner;
359 Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1);
360 Freeze(frost_target, 1/freeze_time, 3, false);
362 Drop_Special_Items(frost_target);
365 void nade_ice_think()
368 if(round_handler_IsActive())
369 if(!round_handler_IsRoundStarted())
375 if(time >= self.ltime)
377 if ( autocvar_g_nades_ice_explode )
379 entity expef = EFFECT_NADE_EXPLODE(self.realowner.team);
380 Send_Effect(expef, self.origin + '0 0 1', '0 0 0', 1);
381 sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
383 RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
384 autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy);
385 Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
386 autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self);
393 self.nextthink = time+0.1;
398 randomr = exp(-5*randomr*randomr)*autocvar_g_nades_nade_radius;
400 randomw = random()*M_PI*2;
402 randomp.x = randomr*cos(randomw);
403 randomp.y = randomr*sin(randomw);
405 Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, self.origin + randomp, '0 0 0', 1);
407 if(time >= self.nade_special_time)
409 self.nade_special_time = time+0.7;
411 Send_Effect(EFFECT_ELECTRO_IMPACT, self.origin, '0 0 0', 1);
412 Send_Effect(EFFECT_ICEFIELD, self.origin, '0 0 0', 1);
416 float current_freeze_time = self.ltime - time - 0.1;
419 for(e = findradius(self.origin, autocvar_g_nades_nade_radius); e; e = e.chain)
421 if(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(e, self.realowner) || e == self.realowner))
422 if(e.takedamage && !IS_DEAD(e))
424 if(!e.revival_time || ((time - e.revival_time) >= 1.5))
426 if(current_freeze_time > 0)
427 nade_ice_freeze(self, e, current_freeze_time);
434 fountain.owner = self.owner;
435 fountain.realowner = self.realowner;
436 fountain.origin = self.origin;
437 setorigin(fountain, fountain.origin);
438 fountain.think = nade_ice_think;
439 fountain.nextthink = time;
440 fountain.ltime = time + autocvar_g_nades_ice_freeze_time;
441 fountain.pushltime = fountain.wait = fountain.ltime;
442 fountain.team = self.team;
443 fountain.movetype = MOVETYPE_TOSS;
444 fountain.projectiledeathtype = DEATH_NADE_ICE.m_id;
445 fountain.bot_dodge = false;
446 setsize(fountain, '-16 -16 -16', '16 16 16');
447 fountain.nade_special_time = time+0.3;
448 fountain.angles = self.angles;
450 if ( autocvar_g_nades_ice_explode )
452 setmodel(fountain, MDL_PROJECTILE_GRENADE);
453 entity timer = new(nade_timer);
454 setmodel(timer, MDL_NADE_TIMER);
455 setattachment(timer, fountain, "");
456 timer.colormap = self.colormap;
457 timer.glowmod = self.glowmod;
458 timer.think = nade_timer_think;
459 timer.nextthink = time;
460 timer.wait = fountain.ltime;
461 timer.owner = fountain;
465 setmodel(fountain, MDL_Null);
468 void nade_translocate_boom()
470 if(self.realowner.vehicle)
473 vector locout = self.origin + '0 0 1' * (1 - self.realowner.mins.z - 24);
474 tracebox(locout, self.realowner.mins, self.realowner.maxs, locout, MOVE_NOMONSTERS, self.realowner);
475 locout = trace_endpos;
477 makevectors(self.realowner.angles);
479 MUTATOR_CALLHOOK(PortalTeleport, self.realowner);
481 TeleportPlayer(self, self.realowner, locout, self.realowner.angles, v_forward * vlen(self.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
484 void nade_spawn_boom()
486 entity spawnloc = spawn();
487 setorigin(spawnloc, self.origin);
488 setsize(spawnloc, self.realowner.mins, self.realowner.maxs);
489 spawnloc.movetype = MOVETYPE_NONE;
490 spawnloc.solid = SOLID_NOT;
491 spawnloc.drawonlytoclient = self.realowner;
492 spawnloc.effects = EF_STARDUST;
493 spawnloc.cnt = autocvar_g_nades_spawn_count;
495 if(self.realowner.nade_spawnloc)
497 remove(self.realowner.nade_spawnloc);
498 self.realowner.nade_spawnloc = world;
501 self.realowner.nade_spawnloc = spawnloc;
504 void nade_heal_think()
506 if(time >= self.ltime)
512 self.nextthink = time;
514 if(time >= self.nade_special_time)
516 self.nade_special_time = time+0.25;
517 self.nade_show_particles = 1;
520 self.nade_show_particles = 0;
523 void nade_heal_touch()
527 if(IS_PLAYER(other) || IS_MONSTER(other))
529 if(!STAT(FROZEN, other))
531 health_factor = autocvar_g_nades_heal_rate*frametime/2;
532 if ( other != self.realowner )
534 if ( SAME_TEAM(other,self) )
535 health_factor *= autocvar_g_nades_heal_friend;
537 health_factor *= autocvar_g_nades_heal_foe;
539 if ( health_factor > 0 )
541 maxhealth = (IS_MONSTER(other)) ? other.max_health : g_pickup_healthmega_max;
542 if ( other.health < maxhealth )
544 if ( self.nade_show_particles )
545 Send_Effect(EFFECT_HEALING, other.origin, '0 0 0', 1);
546 other.health = min(other.health+health_factor, maxhealth);
548 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
550 else if ( health_factor < 0 )
552 Damage(other,self,self.realowner,-health_factor,DEATH_NADE_HEAL.m_id,other.origin,'0 0 0');
557 if ( IS_REAL_CLIENT(other) || IS_VEHICLE(other) )
559 entity show_red = (IS_VEHICLE(other)) ? other.owner : other;
560 show_red.stat_healing_orb = time+0.1;
561 show_red.stat_healing_orb_alpha = 0.75 * (self.ltime - time) / self.healer_lifetime;
565 void nade_heal_boom()
569 healer.owner = self.owner;
570 healer.realowner = self.realowner;
571 setorigin(healer, self.origin);
572 healer.healer_lifetime = autocvar_g_nades_heal_time; // save the cvar
573 healer.ltime = time + healer.healer_lifetime;
574 healer.team = self.realowner.team;
575 healer.bot_dodge = false;
576 healer.solid = SOLID_TRIGGER;
577 healer.touch = nade_heal_touch;
579 setmodel(healer, MDL_NADE_HEAL);
580 healer.healer_radius = autocvar_g_nades_nade_radius;
581 vector size = '1 1 1' * healer.healer_radius / 2;
582 setsize(healer,-size,size);
584 Net_LinkEntity(healer, true, 0, healer_send);
586 healer.think = nade_heal_think;
587 healer.nextthink = time;
588 healer.SendFlags |= 1;
591 void nade_monster_boom()
593 entity e = spawnmonster(self.pokenade_type, 0, self.realowner, self.realowner, self.origin, false, false, 1);
595 if(autocvar_g_nades_pokenade_monster_lifetime > 0)
596 e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime;
597 e.monster_skill = MONSTER_SKILL_INSANE;
603 bool nade_blast = true;
605 switch ( Nades_from(self.nade_type) )
607 case NADE_TYPE_NAPALM:
608 nade_blast = autocvar_g_nades_napalm_blast;
609 expef = EFFECT_EXPLOSION_MEDIUM;
613 expef = EFFECT_ELECTRO_COMBO; // hookbomb_explode electro_combo bigplasma_impact
615 case NADE_TYPE_TRANSLOCATE:
618 case NADE_TYPE_MONSTER:
619 case NADE_TYPE_SPAWN:
621 switch(self.realowner.team)
623 case NUM_TEAM_1: expef = EFFECT_SPAWN_RED; break;
624 case NUM_TEAM_2: expef = EFFECT_SPAWN_BLUE; break;
625 case NUM_TEAM_3: expef = EFFECT_SPAWN_YELLOW; break;
626 case NUM_TEAM_4: expef = EFFECT_SPAWN_PINK; break;
627 default: expef = EFFECT_SPAWN_NEUTRAL; break;
632 expef = EFFECT_SPAWN_RED;
636 case NADE_TYPE_NORMAL:
637 expef = EFFECT_NADE_EXPLODE(self.realowner.team);
642 Send_Effect(expef, findbetterlocation(self.origin, 8), '0 0 0', 1);
644 sound(self, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM);
645 sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
647 self.event_damage = func_null; // prevent somehow calling damage in the next call
651 RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
652 autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy);
653 Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self);
657 switch ( Nades_from(self.nade_type) )
659 case NADE_TYPE_NAPALM: nade_napalm_boom(); break;
660 case NADE_TYPE_ICE: nade_ice_boom(); break;
661 case NADE_TYPE_TRANSLOCATE: nade_translocate_boom(); break;
662 case NADE_TYPE_SPAWN: nade_spawn_boom(); break;
663 case NADE_TYPE_HEAL: nade_heal_boom(); break;
664 case NADE_TYPE_MONSTER: nade_monster_boom(); break;
667 FOREACH_ENTITY_ENT(aiment, self,
669 if(it.classname == "grapplinghook")
670 RemoveGrapplingHook(it.realowner);
676 void spawn_held_nade(entity player, entity nowner, float ntime, int ntype, string pntype);
677 void nade_pickup(entity this, entity thenade)
679 spawn_held_nade(this, thenade.realowner, autocvar_g_nades_pickup_time, thenade.nade_type, thenade.pokenade_type);
681 // set refire so player can't even
682 this.nade_refire = time + autocvar_g_nades_nade_refire;
686 this.nade.nade_time_primed = thenade.nade_time_primed;
689 bool CanThrowNade(entity this);
693 UpdateCSQCProjectile(self);
695 if(other == self.realowner)
696 return; // no self impacts
698 if(autocvar_g_nades_pickup)
699 if(time >= self.spawnshieldtime)
700 if(!other.nade && self.health == self.max_health) // no boosted shot pickups, thank you very much
702 if(CanThrowNade(other)) // prevent some obvious things, like dead players
703 if(IS_REAL_CLIENT(other)) // above checks for IS_PLAYER, don't need to do it here
705 nade_pickup(other, self);
706 sound(self, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
710 /*float is_weapclip = 0;
711 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
712 if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
713 if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
715 if(ITEM_TOUCH_NEEDKILL()) // || is_weapclip)
717 FOREACH_ENTITY_ENT(aiment, self,
719 if(it.classname == "grapplinghook")
720 RemoveGrapplingHook(it.realowner);
728 //setsize(self, '-2 -2 -2', '2 2 2');
729 //UpdateCSQCProjectile(self);
730 if(self.health == self.max_health)
732 spamsound(self, CH_SHOTS, SND(GRENADE_BOUNCE_RANDOM()), VOL_BASE, ATTEN_NORM);
742 sound(self, CH_SHOTS_SINGLE, SND_NADE_BEEP, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
743 self.think = nade_boom;
744 self.nextthink = max(self.wait, time);
747 void nade_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
749 if(ITEM_DAMAGE_NEEDKILL(deathtype))
751 this.takedamage = DAMAGE_NO;
752 WITHSELF(this, nade_boom());
756 if(this.nade_type == NADE_TYPE_TRANSLOCATE.m_id || this.nade_type == NADE_TYPE_SPAWN.m_id)
759 if (MUTATOR_CALLHOOK(Nade_Damage, DEATH_WEAPONOF(deathtype), force, damage)) {}
760 else if(DEATH_ISWEAPON(deathtype, WEP_BLASTER))
765 else if(DEATH_ISWEAPON(deathtype, WEP_VAPORIZER) && (deathtype & HITTYPE_SECONDARY))
767 force *= 0.5; // too much
770 else if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER))
773 damage = this.max_health * 0.55;
775 else if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN))
776 damage = this.max_health * 0.1;
777 else if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) // WEAPONTODO
779 if(deathtype & HITTYPE_SECONDARY)
781 damage = this.max_health * 0.1;
785 damage = this.max_health * 1.15;
788 this.velocity += force;
789 UpdateCSQCProjectile(this);
791 if(damage <= 0 || ((IS_ONGROUND(this)) && IS_PLAYER(attacker)))
794 if(this.health == this.max_health)
796 sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
797 this.nextthink = max(time + autocvar_g_nades_nade_lifetime, time);
798 this.think = nade_beep;
801 this.health -= damage;
803 if ( this.nade_type != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker) )
804 this.realowner = attacker;
807 WITHSELF(this, W_PrepareExplosionByDamage(attacker, nade_boom));
809 nade_burn_spawn(this);
812 void toss_nade(entity e, bool set_owner, vector _velocity, float _time)
817 entity _nade = e.nade;
823 makevectors(e.v_angle);
825 W_SetupShot(e, false, false, SND_Null, CH_WEAPON_A, 0);
827 Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_NADES);
829 vector offset = (v_forward * autocvar_g_nades_throw_offset.x)
830 + (v_right * autocvar_g_nades_throw_offset.y)
831 + (v_up * autocvar_g_nades_throw_offset.z);
832 if(autocvar_g_nades_throw_offset == '0 0 0')
835 setorigin(_nade, w_shotorg + offset + (v_right * 25) * -1);
836 //setmodel(_nade, MDL_PROJECTILE_NADE);
837 //setattachment(_nade, world, "");
838 PROJECTILE_MAKETRIGGER(_nade);
839 if(STAT(NADES_SMALL, e))
840 setsize(_nade, '-8 -8 -8', '8 8 8');
842 setsize(_nade, '-16 -16 -16', '16 16 16');
843 _nade.movetype = MOVETYPE_BOUNCE;
845 tracebox(_nade.origin, _nade.mins, _nade.maxs, _nade.origin, false, _nade);
846 if (trace_startsolid)
847 setorigin(_nade, e.origin);
849 if(self.v_angle.x >= 70 && self.v_angle.x <= 110 && PHYS_INPUT_BUTTON_CROUCH(self))
850 _nade.velocity = '0 0 100';
851 else if(autocvar_g_nades_nade_newton_style == 1)
852 _nade.velocity = e.velocity + _velocity;
853 else if(autocvar_g_nades_nade_newton_style == 2)
854 _nade.velocity = _velocity;
856 _nade.velocity = W_CalculateProjectileVelocity(e.velocity, _velocity, true);
861 _nade.touch = nade_touch;
862 _nade.spawnshieldtime = time + 0.1; // prevent instantly picking up again
863 _nade.health = autocvar_g_nades_nade_health;
864 _nade.max_health = _nade.health;
865 _nade.takedamage = DAMAGE_AIM;
866 _nade.event_damage = nade_damage;
867 _nade.customizeentityforclient = func_null;
868 _nade.exteriormodeltoclient = world;
869 _nade.traileffectnum = 0;
870 _nade.teleportable = true;
871 _nade.pushable = true;
873 _nade.missile_flags = MIF_SPLASH | MIF_ARC;
874 _nade.damagedbycontents = true;
875 _nade.angles = vectoangles(_nade.velocity);
876 _nade.flags = FL_PROJECTILE;
877 _nade.projectiledeathtype = DEATH_NADE.m_id;
878 _nade.toss_time = time;
879 _nade.solid = SOLID_CORPSE; //((_nade.nade_type == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX);
881 if(_nade.nade_type == NADE_TYPE_TRANSLOCATE.m_id || _nade.nade_type == NADE_TYPE_SPAWN.m_id)
882 _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
884 _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
890 _nade.think = nade_boom;
891 _nade.nextthink = _time;
894 e.nade_refire = time + autocvar_g_nades_nade_refire;
898 void nades_GiveBonus(entity player, float score)
900 if (autocvar_g_nades)
901 if (autocvar_g_nades_bonus)
902 if (IS_REAL_CLIENT(player))
903 if (IS_PLAYER(player) && player.bonus_nades < autocvar_g_nades_bonus_max)
904 if (STAT(FROZEN, player) == 0)
905 if (!IS_DEAD(player))
907 if ( player.bonus_nade_score < 1 )
908 player.bonus_nade_score += score/autocvar_g_nades_bonus_score_max;
910 if ( player.bonus_nade_score >= 1 )
912 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_BONUS);
913 play2(player, SND(KH_ALARM));
914 player.bonus_nades++;
915 player.bonus_nade_score -= 1;
920 /** Remove all bonus nades from a player */
921 void nades_RemoveBonus(entity player)
923 player.bonus_nades = player.bonus_nade_score = 0;
926 MUTATOR_HOOKFUNCTION(nades, PutClientInServer)
929 nades_RemoveBonus(self);
932 float nade_customize()
934 //if(IS_SPEC(other)) { return false; }
935 if(other == self.exteriormodeltoclient || (IS_SPEC(other) && other.enemy == self.exteriormodeltoclient))
937 // somewhat hide the model, but keep the glow
939 if(self.traileffectnum)
940 self.traileffectnum = 0;
945 //self.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
946 if(!self.traileffectnum)
947 self.traileffectnum = _particleeffectnum(Nade_TrailEffect(Nades_from(self.nade_type).m_projectile[false], self.team).eent_eff_name);
954 void spawn_held_nade(entity player, entity nowner, float ntime, int ntype, string pntype)
956 entity n = new(nade), fn = new(fake_nade);
958 n.nade_type = bound(1, ntype, Nades_COUNT);
959 n.pokenade_type = pntype;
961 setmodel(n, MDL_PROJECTILE_NADE);
962 //setattachment(n, player, "bip01 l hand");
963 n.exteriormodeltoclient = player;
964 n.customizeentityforclient = nade_customize;
965 n.traileffectnum = _particleeffectnum(Nade_TrailEffect(Nades_from(n.nade_type).m_projectile[false], player.team).eent_eff_name);
966 n.colormod = Nades_from(n.nade_type).m_color;
967 n.realowner = nowner;
968 n.colormap = player.colormap;
969 n.glowmod = player.glowmod;
970 n.wait = time + max(0, ntime);
971 n.nade_time_primed = time;
973 n.nextthink = max(n.wait - 3, time);
974 n.projectiledeathtype = DEATH_NADE.m_id;
976 setmodel(fn, MDL_NADE_VIEW);
977 .entity weaponentity = weaponentities[0]; // TODO: unhardcode
978 setattachment(fn, player.(weaponentity), "");
979 fn.realowner = fn.owner = player;
980 fn.colormod = Nades_from(n.nade_type).m_color;
981 fn.colormap = player.colormap;
982 fn.glowmod = player.glowmod;
983 fn.think = SUB_Remove_self;
984 fn.nextthink = n.wait;
987 player.fake_nade = fn;
992 if(autocvar_g_nades_bonus_only)
993 if(!self.bonus_nades)
994 return; // only allow bonus nades
1000 remove(self.fake_nade);
1003 string pntype = self.pokenade_type;
1005 if(self.items & ITEM_Strength.m_itemid && autocvar_g_nades_bonus_onstrength)
1006 ntype = self.nade_type;
1007 else if (self.bonus_nades >= 1)
1009 ntype = self.nade_type;
1010 pntype = self.pokenade_type;
1011 self.bonus_nades -= 1;
1015 ntype = ((autocvar_g_nades_client_select) ? self.cvar_cl_nade_type : autocvar_g_nades_nade_type);
1016 pntype = ((autocvar_g_nades_client_select) ? self.cvar_cl_pokenade_type : autocvar_g_nades_pokenade_monster_type);
1019 spawn_held_nade(self, self, autocvar_g_nades_nade_lifetime, ntype, pntype);
1022 bool CanThrowNade(entity this)
1033 if (!autocvar_g_nades)
1034 return false; // allow turning them off mid match
1036 if(forbidWeaponUse(this))
1039 if (!IS_PLAYER(this))
1045 .bool nade_altbutton;
1047 void nades_CheckThrow()
1049 if(!CanThrowNade(self))
1052 entity held_nade = self.nade;
1055 self.nade_altbutton = true;
1056 if(time > self.nade_refire)
1058 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_NADE_THROW);
1060 self.nade_refire = time + autocvar_g_nades_nade_refire;
1065 self.nade_altbutton = false;
1066 if (time >= held_nade.nade_time_primed + 1) {
1067 makevectors(self.v_angle);
1068 float _force = time - held_nade.nade_time_primed;
1069 _force /= autocvar_g_nades_nade_lifetime;
1070 _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));
1071 toss_nade(self, true, (v_forward * 0.75 + v_up * 0.2 + v_right * 0.05) * _force, 0);
1076 void nades_Clear(entity player)
1079 remove(player.nade);
1080 if(player.fake_nade)
1081 remove(player.fake_nade);
1083 player.nade = player.fake_nade = world;
1084 player.nade_timer = 0;
1087 MUTATOR_HOOKFUNCTION(nades, VehicleEnter)
1090 toss_nade(vh_player, true, '0 0 100', max(vh_player.nade.wait, time + 0.05));
1095 CLASS(NadeOffhand, OffhandWeapon)
1096 METHOD(NadeOffhand, offhand_think, void(NadeOffhand this, entity player, bool key_pressed))
1098 entity held_nade = player.nade;
1101 player.nade_timer = bound(0, (time - held_nade.nade_time_primed) / autocvar_g_nades_nade_lifetime, 1);
1102 // LOG_TRACEF("%d %d\n", player.nade_timer, time - held_nade.nade_time_primed);
1103 makevectors(player.angles);
1104 held_nade.velocity = player.velocity;
1105 setorigin(held_nade, player.origin + player.view_ofs + v_forward * 8 + v_right * -8 + v_up * 0);
1106 held_nade.angles_y = player.angles.y;
1108 if (time + 0.1 >= held_nade.wait)
1109 toss_nade(player, false, '0 0 0', time + 0.05);
1112 if (!CanThrowNade(player)) return;
1113 if (!(time > player.nade_refire)) return;
1117 held_nade = player.nade;
1119 } else if (time >= held_nade.nade_time_primed + 1) {
1121 makevectors(player.v_angle);
1122 float _force = time - held_nade.nade_time_primed;
1123 _force /= autocvar_g_nades_nade_lifetime;
1124 _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));
1125 toss_nade(player, false, (v_forward * 0.7 + v_up * 0.2 + v_right * 0.1) * _force, 0);
1129 ENDCLASS(NadeOffhand)
1130 NadeOffhand OFFHAND_NADE; STATIC_INIT(OFFHAND_NADE) { OFFHAND_NADE = NEW(NadeOffhand); }
1132 MUTATOR_HOOKFUNCTION(nades, ForbidThrowCurrentWeapon, CBC_ORDER_LAST)
1135 if (self.offhand != OFFHAND_NADE || (self.weapons & WEPSET(HOOK)) || autocvar_g_nades_override_dropweapon) {
1142 MUTATOR_HOOKFUNCTION(nades, PlayerPreThink)
1144 if (!IS_PLAYER(self)) { return false; }
1146 if (self.nade && (self.offhand != OFFHAND_NADE || (self.weapons & WEPSET(HOOK)))) OFFHAND_NADE.offhand_think(OFFHAND_NADE, self, self.nade_altbutton);
1150 if ( autocvar_g_nades_bonus && autocvar_g_nades )
1153 float key_count = 0;
1154 FOR_EACH_KH_KEY(key) if(key.owner == self) { ++key_count; }
1157 if(self.flagcarried || self.ballcarried) // this player is important
1158 time_score = autocvar_g_nades_bonus_score_time_flagcarrier;
1160 time_score = autocvar_g_nades_bonus_score_time;
1163 time_score = autocvar_g_nades_bonus_score_time_flagcarrier * key_count; // multiply by the number of keys the player is holding
1165 if(autocvar_g_nades_bonus_client_select)
1167 self.nade_type = self.cvar_cl_nade_type;
1168 self.pokenade_type = self.cvar_cl_pokenade_type;
1172 self.nade_type = autocvar_g_nades_bonus_type;
1173 self.pokenade_type = autocvar_g_nades_pokenade_monster_type;
1176 self.nade_type = bound(1, self.nade_type, Nades_COUNT);
1178 if(self.bonus_nade_score >= 0 && autocvar_g_nades_bonus_score_max)
1179 nades_GiveBonus(self, time_score / autocvar_g_nades_bonus_score_max);
1183 self.bonus_nades = self.bonus_nade_score = 0;
1189 if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout)
1193 vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
1195 FOREACH_CLIENT(IS_PLAYER(it) && it != self, LAMBDA(
1197 if(STAT(FROZEN, it) == 0)
1198 if(SAME_TEAM(it, self))
1199 if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, it.absmin, it.absmax))
1203 if(STAT(FROZEN, self) == 1)
1210 if(n && STAT(FROZEN, self) == 3) // OK, there is at least one teammate reviving us
1212 self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
1213 self.health = max(1, self.revive_progress * start_health);
1215 if(self.revive_progress >= 1)
1219 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
1220 Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname);
1223 FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, LAMBDA(
1224 other.revive_progress = self.revive_progress;
1225 other.reviving = false;
1232 MUTATOR_HOOKFUNCTION(nades, PlayerSpawn)
1234 if(autocvar_g_nades_spawn)
1235 self.nade_refire = time + autocvar_g_spawnshieldtime;
1237 self.nade_refire = time + autocvar_g_nades_nade_refire;
1239 if(autocvar_g_nades_bonus_client_select)
1240 self.nade_type = self.cvar_cl_nade_type;
1242 self.nade_timer = 0;
1244 if (!self.offhand) self.offhand = OFFHAND_NADE;
1246 if(self.nade_spawnloc)
1248 setorigin(self, self.nade_spawnloc.origin);
1249 self.nade_spawnloc.cnt -= 1;
1251 if(self.nade_spawnloc.cnt <= 0)
1253 remove(self.nade_spawnloc);
1254 self.nade_spawnloc = world;
1261 MUTATOR_HOOKFUNCTION(nades, PlayerDies, CBC_ORDER_LAST)
1263 if(frag_target.nade)
1264 if(!STAT(FROZEN, frag_target) || !autocvar_g_freezetag_revive_nade)
1265 toss_nade(frag_target, true, '0 0 100', max(frag_target.nade.wait, time + 0.05));
1267 float killcount_bonus = ((frag_attacker.killcount >= 1) ? bound(0, autocvar_g_nades_bonus_score_minor * frag_attacker.killcount, autocvar_g_nades_bonus_score_medium) : autocvar_g_nades_bonus_score_minor);
1269 if(IS_PLAYER(frag_attacker))
1271 if (SAME_TEAM(frag_attacker, frag_target) || frag_attacker == frag_target)
1272 nades_RemoveBonus(frag_attacker);
1273 else if(frag_target.flagcarried)
1274 nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_medium);
1275 else if(autocvar_g_nades_bonus_score_spree && frag_attacker.killcount > 1)
1277 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
1278 case counta: { nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_spree); break; }
1279 switch(frag_attacker.killcount)
1282 default: nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); break;
1287 nades_GiveBonus(frag_attacker, killcount_bonus);
1290 nades_RemoveBonus(frag_target);
1295 MUTATOR_HOOKFUNCTION(nades, PlayerDamage_Calculate)
1297 if(STAT(FROZEN, frag_target))
1298 if(autocvar_g_freezetag_revive_nade)
1299 if(frag_attacker == frag_target)
1300 if(frag_deathtype == DEATH_NADE.m_id)
1301 if(time - frag_inflictor.toss_time <= 0.1)
1303 Unfreeze(frag_target);
1304 frag_target.health = autocvar_g_freezetag_revive_nade_health;
1305 Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3);
1307 frag_force = '0 0 0';
1308 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_NADE, frag_target.netname);
1309 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
1315 MUTATOR_HOOKFUNCTION(nades, MonsterDies)
1317 if(IS_PLAYER(frag_attacker))
1318 if(DIFF_TEAM(frag_attacker, frag_target))
1319 if(!(frag_target.spawnflags & MONSTERFLAG_SPAWNED))
1320 nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor);
1325 MUTATOR_HOOKFUNCTION(nades, DropSpecialItems)
1327 if(frag_target.nade)
1328 toss_nade(frag_target, true, '0 0 0', time + 0.05);
1333 bool nades_RemovePlayer()
1336 nades_RemoveBonus(self);
1340 MUTATOR_HOOKFUNCTION(nades, MakePlayerObserver) { nades_RemovePlayer(); }
1341 MUTATOR_HOOKFUNCTION(nades, ClientDisconnect) { nades_RemovePlayer(); }
1342 MUTATOR_HOOKFUNCTION(nades, reset_map_global) { nades_RemovePlayer(); }
1344 MUTATOR_HOOKFUNCTION(nades, SpectateCopy)
1346 self.nade_timer = other.nade_timer;
1347 self.nade_type = other.nade_type;
1348 self.pokenade_type = other.pokenade_type;
1349 self.bonus_nades = other.bonus_nades;
1350 self.bonus_nade_score = other.bonus_nade_score;
1351 self.stat_healing_orb = other.stat_healing_orb;
1352 self.stat_healing_orb_alpha = other.stat_healing_orb_alpha;
1356 REPLICATE(cvar_cl_nade_type, int, "cl_nade_type");
1357 REPLICATE(cvar_cl_pokenade_type, string, "cl_pokenade_type");
1359 MUTATOR_HOOKFUNCTION(nades, BuildMutatorsString)
1361 ret_string = strcat(ret_string, ":Nades");
1365 MUTATOR_HOOKFUNCTION(nades, BuildMutatorsPrettyString)
1367 ret_string = strcat(ret_string, ", Nades");
1371 MUTATOR_HOOKFUNCTION(nades, BuildGameplayTipsString)
1373 ret_string = strcat(ret_string, "\n\n^3nades^8 are enabled, press 'g' to use them\n");