2 void W_GiveWeapon (entity e, float wep, string name)
15 if (other.classname == "player")
17 sprint (other, "You got the ^2");
25 .float railgundistance;
27 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype)
29 vector hitloc, force, endpoint, dir;
31 float endq3surfaceflags;
38 entity pseudoprojectile;
41 pseudoprojectile = world;
43 railgun_start = start;
46 dir = normalize(end - start);
47 length = vlen(end - start);
50 // go a little bit into the wall because we need to hit this wall later
55 // trace multiple times until we hit a wall, each obstacle will be made
56 // non-solid so we can hit the next, while doing this we spawn effects and
57 // note down which entities were hit so we can damage them later
61 if(self.antilag_debug)
62 WarpZone_traceline_antilag (self, start, end, FALSE, o, self.antilag_debug);
64 WarpZone_traceline_antilag (self, start, end, FALSE, o, ANTILAG_LATENCY(self));
65 if(o && WarpZone_trace_firstzone)
71 if(trace_ent.solid == SOLID_BSP || trace_ent.solid == SOLID_SLIDEBOX)
72 Damage_DamageInfo(trace_endpos, bdamage, 0, 0, force, deathtype, trace_ent.species, self);
74 // if it is world we can't hurt it so stop now
75 if (trace_ent == world || trace_fraction == 1)
78 // make the entity non-solid so we can hit the next one
79 trace_ent.railgunhit = TRUE;
80 trace_ent.railgunhitloc = end;
81 trace_ent.railgunhitsolidbackup = trace_ent.solid;
82 trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
83 trace_ent.railgunforce = WarpZone_TransformVelocity(WarpZone_trace_transform, force);
85 // stop if this is a wall
86 if (trace_ent.solid == SOLID_BSP)
89 // make the entity non-solid
90 trace_ent.solid = SOLID_NOT;
93 endpoint = trace_endpos;
95 endq3surfaceflags = trace_dphitq3surfaceflags;
97 // find all the entities the railgun hit and restore their solid state
98 ent = findfloat(world, railgunhit, TRUE);
101 // restore their solid type
102 ent.solid = ent.railgunhitsolidbackup;
103 ent = findfloat(ent, railgunhit, TRUE);
106 // spawn a temporary explosion entity for RadiusDamage calls
107 //explosion = spawn();
109 // Find all non-hit players the beam passed close by
110 if(deathtype == WEP_MINSTANEX || deathtype == WEP_NEX)
112 FOR_EACH_REALCLIENT(msg_entity) if(msg_entity != self) if(!msg_entity.railgunhit) if not(msg_entity.classname == "spectator" && msg_entity.enemy == self) // we use realclient, so spectators can hear the whoosh too
114 // nearest point on the beam
115 beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
117 f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
121 snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav");
123 if(!pseudoprojectile)
124 pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
125 soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTN_NONE);
129 remove(pseudoprojectile);
132 // find all the entities the railgun hit and hurt them
133 ent = findfloat(world, railgunhit, TRUE);
136 // get the details we need to call the damage function
137 hitloc = ent.railgunhitloc;
139 f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
140 ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
142 if(accuracy_isgooddamage(self.realowner, ent))
143 totaldmg += bdamage * f;
147 Damage (ent, self, self, bdamage * f, deathtype, hitloc, ent.railgunforce * ffs);
149 // create a small explosion to throw gibs around (if applicable)
150 //setorigin (explosion, hitloc);
151 //RadiusDamage (explosion, self, 10, 0, 50, world, 300, deathtype);
153 ent.railgunhitloc = '0 0 0';
154 ent.railgunhitsolidbackup = SOLID_NOT;
155 ent.railgunhit = FALSE;
156 ent.railgundistance = 0;
158 // advance to the next entity
159 ent = findfloat(ent, railgunhit, TRUE);
162 // calculate hits and fired shots for hitscan
163 accuracy_add(self, self.weapon, 0, min(bdamage, totaldmg));
165 trace_endpos = endpoint;
167 trace_dphitq3surfaceflags = endq3surfaceflags;
174 void W_BallisticBullet_Hit (void)
178 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
179 q = 1 + self.dmg_edge / self.dmg;
181 if(other.solid == SOLID_BSP || other.solid == SOLID_SLIDEBOX)
182 Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * f, self.projectiledeathtype, other.species, self);
184 if(other && other != self.enemy)
186 endzcurveparticles();
190 damage_headshotbonus = self.dmg_edge * f;
191 railgun_start = self.origin - 2 * frametime * self.velocity;
192 railgun_end = self.origin + 2 * frametime * self.velocity;
193 g = accuracy_isgooddamage(self.realowner, other);
194 Damage(other, self, self.realowner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
195 damage_headshotbonus = 0;
199 if(self.dmg_edge > 0)
202 AnnounceTo(self.realowner, "headshot");
204 AnnounceTo(self.realowner, "awesome");
207 // calculate hits for ballistic weapons
210 // do not exceed 100%
211 q = min(self.dmg * q, self.dmg_total + f * self.dmg) - self.dmg_total;
212 self.dmg_total += f * self.dmg;
213 accuracy_add(self.realowner, self.realowner.weapon, 0, q);
217 self.enemy = other; // don't hit the same player twice with the same bullet
220 .void(void) W_BallisticBullet_LeaveSolid_think_save;
221 .float W_BallisticBullet_LeaveSolid_nextthink_save;
222 .vector W_BallisticBullet_LeaveSolid_origin;
223 .vector W_BallisticBullet_LeaveSolid_velocity;
225 void W_BallisticBullet_LeaveSolid_think()
227 setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
228 self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
230 self.think = self.W_BallisticBullet_LeaveSolid_think_save;
231 self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
232 self.W_BallisticBullet_LeaveSolid_think_save = SUB_Null;
234 self.flags &~= FL_ONGROUND;
236 if(self.enemy.solid == SOLID_BSP)
239 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
240 Damage_DamageInfo(self.origin, 0, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * -f, self.projectiledeathtype, 0, self);
243 UpdateCSQCProjectile(self);
246 float W_BallisticBullet_LeaveSolid(float eff)
248 // move the entity along its velocity until it's out of solid, then let it resume
249 vector vel = self.velocity;
250 float dt, dst, velfactor, v0, vs;
253 float constant = self.dmg_radius * (other.ballistics_density ? other.ballistics_density : 1);
255 // outside the world? forget it
256 if(self.origin_x > world.maxs_x || self.origin_y > world.maxs_y || self.origin_z > world.maxs_z || self.origin_x < world.mins_x || self.origin_y < world.mins_y || self.origin_z < world.mins_z)
259 // special case for zero density and zero bullet constant:
261 if(self.dmg_radius == 0)
263 if(other.ballistics_density < 0)
264 constant = 0; // infinite travel distance
266 return 0; // no penetration
270 if(other.ballistics_density < 0)
271 constant = 0; // infinite travel distance
272 else if(other.ballistics_density == 0)
273 constant = self.dmg_radius;
275 constant = self.dmg_radius * other.ballistics_density;
278 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
281 E0_m = 0.5 * v0 * v0;
285 maxdist = E0_m / constant;
286 // maxdist = 0.5 * v0 * v0 / constant
287 // dprint("max dist = ", ftos(maxdist), "\n");
289 if(maxdist <= autocvar_g_ballistics_mindistance)
294 maxdist = vlen(other.maxs - other.mins) + 1; // any distance, as long as we leave the entity
297 traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self, TRUE);
298 if(trace_fraction == 1) // 1: we never got out of solid
301 self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
303 dst = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - self.origin));
304 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
305 Es_m = E0_m - constant * dst;
308 // roundoff errors got us
314 dt = dst / (0.5 * (v0 + vs));
315 // this is not correct, but the differential equations have no analytic
316 // solution - and these times are very small anyway
317 //print("dt = ", ftos(dt), "\n");
319 self.W_BallisticBullet_LeaveSolid_think_save = self.think;
320 self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
321 self.think = W_BallisticBullet_LeaveSolid_think;
322 self.nextthink = time + dt;
324 vel = vel * velfactor;
326 self.velocity = '0 0 0';
327 self.flags |= FL_ONGROUND; // prevent moving
328 self.W_BallisticBullet_LeaveSolid_velocity = vel;
331 if(vlen(trace_endpos - self.origin) > 4)
333 endzcurveparticles();
334 trailparticles(self, eff, self.origin, trace_endpos);
340 void W_BallisticBullet_Touch (void)
344 if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
348 W_BallisticBullet_Hit ();
350 // if we hit "weapclip", bail out
352 // rationale of this check:
354 // any shader that is solid, nodraw AND trans is meant to clip weapon
355 // shots and players, but has no other effect!
357 // if it is not trans, it is caulk and should not have this side effect
360 // common/weapclip (intended)
361 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
362 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
363 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
364 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
371 if(!W_BallisticBullet_LeaveSolid(-1))
377 self.projectiledeathtype |= HITTYPE_BOUNCE;
380 void endFireBallisticBullet()
382 endzcurveparticles();
385 entity fireBallisticBullet_trace_callback_ent;
386 float fireBallisticBullet_trace_callback_eff;
387 void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
389 if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
390 zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
391 WarpZone_trace_forent = world;
395 void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, float lifetime, float damage, float headshotbonus, float force, float dtype, float tracereffects, float gravityfactor, float bulletconstant)
397 float lag, dt, savetime, density;
401 antilagging = (autocvar_g_antilag_bullets && (pSpeed >= autocvar_g_antilag_bullets));
405 proj.classname = "bullet";
406 proj.owner = proj.realowner = self;
407 PROJECTILE_MAKETRIGGER(proj);
408 if(gravityfactor > 0)
410 proj.movetype = MOVETYPE_TOSS;
411 proj.gravity = gravityfactor;
414 proj.movetype = MOVETYPE_FLY;
415 proj.think = SUB_Remove;
416 proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
417 W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
418 proj.angles = vectoangles(proj.velocity);
420 proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
423 // so: bulletconstant = bullet mass / area of bullet circle
424 setorigin(proj, start);
425 proj.flags = FL_PROJECTILE;
427 proj.touch = W_BallisticBullet_Touch;
429 proj.dmg_edge = headshotbonus;
430 proj.dmg_force = force;
431 proj.projectiledeathtype = dtype;
433 proj.oldvelocity = proj.velocity;
435 other = proj; MUTATOR_CALLHOOK(EditProjectile);
441 if(tracereffects & EF_RED)
442 eff = particleeffectnum("tr_rifle");
443 else if(tracereffects & EF_BLUE)
444 eff = particleeffectnum("tr_rifle_weak");
446 eff = particleeffectnum("tr_bullet");
448 // NOTE: this may severely throw off weapon balance
449 lag = ANTILAG_LATENCY(self);
452 if(clienttype(self) != CLIENTTYPE_REAL)
454 if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
455 lag = 0; // only do hitscan, but no antilag
460 antilag_takeback(pl, time - lag);
465 savetime = frametime;
470 // DP tracetoss is stupid and always traces in 0.05s
471 // ticks. This makes it trace in 0.05*0.125s ticks
477 self.velocity = self.velocity * 0.125;
478 self.gravity *= 0.125 * 0.125;
480 fireBallisticBullet_trace_callback_ent = self;
481 fireBallisticBullet_trace_callback_eff = eff;
482 WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
486 if(trace_fraction == 1)
488 // won't hit anything anytime soon (DP's
489 // tracetoss does 200 tics of, here,
490 // 0.05*0.125s, that is, 1.25 seconds
493 dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
494 setorigin(self, trace_endpos);
495 self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
497 if(!SUB_OwnerCheck())
499 if(SUB_NoImpactCheck())
503 W_BallisticBullet_Hit();
506 // if we hit "weapclip", bail out
508 // rationale of this check:
510 // any shader that is solid, nodraw AND trans is meant to clip weapon
511 // shots and players, but has no other effect!
513 // if it is not trans, it is caulk and should not have this side effect
516 // common/weapclip (intended)
517 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
518 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
519 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
520 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
524 if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1))
527 W_BallisticBullet_LeaveSolid_think();
529 self.projectiledeathtype |= HITTYPE_BOUNCE;
531 frametime = savetime;
544 if(tracereffects & EF_RED)
545 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE);
546 else if(tracereffects & EF_BLUE)
547 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
549 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
552 void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
556 dir = normalize(dir + randomvec() * spread);
557 end = start + dir * MAX_SHOT_DISTANCE;
558 if(self.antilag_debug)
559 traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
561 traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
565 if (pointcontents (trace_endpos) != CONTENT_SKY)
567 if not (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
568 Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, trace_ent.species, self);
570 Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
575 float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
577 float is_from_contents = (deathtype == DEATH_SLIME || deathtype == DEATH_LAVA);
578 float is_from_owner = (inflictor == projowner);
579 float is_from_exception = (exception != -1);
581 //dprint(strcat("W_CheckProjectileDamage: from_contents ", ftos(is_from_contents), " : from_owner ", ftos(is_from_owner), " : exception ", strcat(ftos(is_from_exception), " (", ftos(exception), "). \n")));
583 if(autocvar_g_projectiles_damage <= -2)
585 return FALSE; // no damage to projectiles at all, not even with the exceptions
587 else if(autocvar_g_projectiles_damage == -1)
589 if(is_from_exception)
590 return (exception); // if exception is detected, allow it to override
592 return FALSE; // otherwise, no other damage is allowed
594 else if(autocvar_g_projectiles_damage == 0)
596 if(is_from_exception)
597 return (exception); // if exception is detected, allow it to override
598 else if not(is_from_contents)
599 return FALSE; // otherwise, only allow damage from contents
601 else if(autocvar_g_projectiles_damage == 1)
603 if(is_from_exception)
604 return (exception); // if exception is detected, allow it to override
605 else if not(is_from_contents || is_from_owner)
606 return FALSE; // otherwise, only allow self damage and damage from contents
608 else if(autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions
610 if(is_from_exception)
611 return (exception); // if exception is detected, allow it to override
614 return TRUE; // if none of these return, then allow damage anyway.
617 void W_PrepareExplosionByDamage(entity attacker, void() explode)
619 self.takedamage = DAMAGE_NO;
620 self.event_damage = SUB_Null;
622 if((attacker.flags & FL_CLIENT) && !autocvar_g_projectiles_keep_owner)
624 self.owner = attacker;
625 self.realowner = attacker;
628 // do not explode NOW but in the NEXT FRAME!
629 // because recursive calls to RadiusDamage are not allowed
630 self.nextthink = time;
631 self.think = explode;