2 void W_GiveWeapon (entity e, float wep, string name)
14 if (other.classname == "player")
16 sprint (other, "You got the ^2");
24 .float railgundistance;
26 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype)
28 vector hitloc, force, endpoint, dir;
30 float endq3surfaceflags;
37 entity pseudoprojectile;
40 pseudoprojectile = world;
42 railgun_start = start;
45 dir = normalize(end - start);
46 length = vlen(end - start);
49 // go a little bit into the wall because we need to hit this wall later
54 // trace multiple times until we hit a wall, each obstacle will be made
55 // non-solid so we can hit the next, while doing this we spawn effects and
56 // note down which entities were hit so we can damage them later
60 if(self.antilag_debug)
61 WarpZone_traceline_antilag (self, start, end, FALSE, o, self.antilag_debug);
63 WarpZone_traceline_antilag (self, start, end, FALSE, o, ANTILAG_LATENCY(self));
64 if(o && WarpZone_trace_firstzone)
70 if(trace_ent.solid == SOLID_BSP || trace_ent.solid == SOLID_SLIDEBOX)
71 Damage_DamageInfo(trace_endpos, bdamage, 0, 0, force, deathtype, trace_ent.species, self);
73 // if it is world we can't hurt it so stop now
74 if (trace_ent == world || trace_fraction == 1)
77 // make the entity non-solid so we can hit the next one
78 trace_ent.railgunhit = TRUE;
79 trace_ent.railgunhitloc = end;
80 trace_ent.railgunhitsolidbackup = trace_ent.solid;
81 trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
82 trace_ent.railgunforce = WarpZone_TransformVelocity(WarpZone_trace_transform, force);
84 // stop if this is a wall
85 if (trace_ent.solid == SOLID_BSP)
88 // make the entity non-solid
89 trace_ent.solid = SOLID_NOT;
92 endpoint = trace_endpos;
94 endq3surfaceflags = trace_dphitq3surfaceflags;
96 // find all the entities the railgun hit and restore their solid state
97 ent = findfloat(world, railgunhit, TRUE);
100 // restore their solid type
101 ent.solid = ent.railgunhitsolidbackup;
102 ent = findfloat(ent, railgunhit, TRUE);
105 // spawn a temporary explosion entity for RadiusDamage calls
106 //explosion = spawn();
108 // Find all non-hit players the beam passed close by
109 if(deathtype == WEP_MINSTANEX || deathtype == WEP_NEX)
111 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
113 // nearest point on the beam
114 beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
116 f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
120 snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav");
122 if(!pseudoprojectile)
123 pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
124 soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTN_NONE);
128 remove(pseudoprojectile);
131 // find all the entities the railgun hit and hurt them
132 ent = findfloat(world, railgunhit, TRUE);
135 // get the details we need to call the damage function
136 hitloc = ent.railgunhitloc;
138 f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
139 ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
141 if(accuracy_isgooddamage(self.realowner, ent))
142 totaldmg += bdamage * f;
146 Damage (ent, self, self, bdamage * f, deathtype, hitloc, ent.railgunforce * ffs);
148 // create a small explosion to throw gibs around (if applicable)
149 //setorigin (explosion, hitloc);
150 //RadiusDamage (explosion, self, 10, 0, 50, world, 300, deathtype);
152 ent.railgunhitloc = '0 0 0';
153 ent.railgunhitsolidbackup = SOLID_NOT;
154 ent.railgunhit = FALSE;
155 ent.railgundistance = 0;
157 // advance to the next entity
158 ent = findfloat(ent, railgunhit, TRUE);
161 // calculate hits and fired shots for hitscan
162 accuracy_add(self, self.weapon, 0, min(bdamage, totaldmg));
164 trace_endpos = endpoint;
166 trace_dphitq3surfaceflags = endq3surfaceflags;
173 void W_BallisticBullet_Hit (void)
177 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
178 q = 1 + self.dmg_edge / self.dmg;
180 if(other.solid == SOLID_BSP || other.solid == SOLID_SLIDEBOX)
181 Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * f, self.projectiledeathtype, other.species, self);
183 if(other && other != self.enemy)
185 endzcurveparticles();
189 damage_headshotbonus = self.dmg_edge * f;
190 railgun_start = self.origin - 2 * frametime * self.velocity;
191 railgun_end = self.origin + 2 * frametime * self.velocity;
192 g = accuracy_isgooddamage(self.realowner, other);
193 Damage(other, self, self.realowner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
194 damage_headshotbonus = 0;
198 if(self.dmg_edge > 0)
201 AnnounceTo(self.realowner, "headshot");
203 AnnounceTo(self.realowner, "awesome");
206 // calculate hits for ballistic weapons
209 // do not exceed 100%
210 q = min(self.dmg * q, self.dmg_total + f * self.dmg) - self.dmg_total;
211 self.dmg_total += f * self.dmg;
212 accuracy_add(self.realowner, self.realowner.weapon, 0, q);
216 self.enemy = other; // don't hit the same player twice with the same bullet
219 .void(void) W_BallisticBullet_LeaveSolid_think_save;
220 .float W_BallisticBullet_LeaveSolid_nextthink_save;
221 .vector W_BallisticBullet_LeaveSolid_origin;
222 .vector W_BallisticBullet_LeaveSolid_velocity;
224 void W_BallisticBullet_LeaveSolid_think()
226 setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
227 self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
229 self.think = self.W_BallisticBullet_LeaveSolid_think_save;
230 self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
231 self.W_BallisticBullet_LeaveSolid_think_save = func_null;
233 self.flags &~= FL_ONGROUND;
235 if(self.enemy.solid == SOLID_BSP)
238 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
239 Damage_DamageInfo(self.origin, 0, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * -f, self.projectiledeathtype, 0, self);
242 UpdateCSQCProjectile(self);
245 float W_BallisticBullet_LeaveSolid(float eff)
247 // move the entity along its velocity until it's out of solid, then let it resume
248 vector vel = self.velocity;
249 float dt, dst, velfactor, v0, vs;
252 float constant = self.dmg_radius * (other.ballistics_density ? other.ballistics_density : 1);
254 // outside the world? forget it
255 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)
258 // special case for zero density and zero bullet constant:
260 if(self.dmg_radius == 0)
262 if(other.ballistics_density < 0)
263 constant = 0; // infinite travel distance
265 return 0; // no penetration
269 if(other.ballistics_density < 0)
270 constant = 0; // infinite travel distance
271 else if(other.ballistics_density == 0)
272 constant = self.dmg_radius;
274 constant = self.dmg_radius * other.ballistics_density;
277 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
280 E0_m = 0.5 * v0 * v0;
284 maxdist = E0_m / constant;
285 // maxdist = 0.5 * v0 * v0 / constant
286 // dprint("max dist = ", ftos(maxdist), "\n");
288 if(maxdist <= autocvar_g_ballistics_mindistance)
293 maxdist = vlen(other.maxs - other.mins) + 1; // any distance, as long as we leave the entity
296 traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self, TRUE);
297 if(trace_fraction == 1) // 1: we never got out of solid
300 self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
302 dst = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - self.origin));
303 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
304 Es_m = E0_m - constant * dst;
307 // roundoff errors got us
313 dt = dst / (0.5 * (v0 + vs));
314 // this is not correct, but the differential equations have no analytic
315 // solution - and these times are very small anyway
316 //print("dt = ", ftos(dt), "\n");
318 self.W_BallisticBullet_LeaveSolid_think_save = self.think;
319 self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
320 self.think = W_BallisticBullet_LeaveSolid_think;
321 self.nextthink = time + dt;
323 vel = vel * velfactor;
325 self.velocity = '0 0 0';
326 self.flags |= FL_ONGROUND; // prevent moving
327 self.W_BallisticBullet_LeaveSolid_velocity = vel;
330 if(vlen(trace_endpos - self.origin) > 4)
332 endzcurveparticles();
333 trailparticles(self, eff, self.origin, trace_endpos);
339 void W_BallisticBullet_Touch (void)
343 if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
347 W_BallisticBullet_Hit ();
349 if(self.dmg_radius < 0) // these NEVER penetrate solid
355 // if we hit "weapclip", bail out
357 // rationale of this check:
359 // any shader that is solid, nodraw AND trans is meant to clip weapon
360 // shots and players, but has no other effect!
362 // if it is not trans, it is caulk and should not have this side effect
365 // common/weapclip (intended)
366 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
367 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
368 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
369 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
376 if(!W_BallisticBullet_LeaveSolid(-1))
382 self.projectiledeathtype |= HITTYPE_BOUNCE;
385 void endFireBallisticBullet()
387 endzcurveparticles();
390 entity fireBallisticBullet_trace_callback_ent;
391 float fireBallisticBullet_trace_callback_eff;
392 void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
394 if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
395 zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
396 WarpZone_trace_forent = world;
400 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)
402 float lag, dt, savetime; //, density;
406 antilagging = (autocvar_g_antilag_bullets && (pSpeed >= autocvar_g_antilag_bullets));
410 proj.classname = "bullet";
411 proj.owner = proj.realowner = self;
412 PROJECTILE_MAKETRIGGER(proj);
413 if(gravityfactor > 0)
415 proj.movetype = MOVETYPE_TOSS;
416 proj.gravity = gravityfactor;
419 proj.movetype = MOVETYPE_FLY;
420 proj.think = SUB_Remove;
421 proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
422 W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
423 proj.angles = vectoangles(proj.velocity);
424 if(bulletconstant > 0)
425 proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
426 else if(bulletconstant == 0)
429 proj.dmg_radius = -1;
430 // so: bulletconstant = bullet mass / area of bullet circle
431 setorigin(proj, start);
432 proj.flags = FL_PROJECTILE;
434 proj.touch = W_BallisticBullet_Touch;
436 proj.dmg_edge = headshotbonus;
437 proj.dmg_force = force;
438 proj.projectiledeathtype = dtype;
440 proj.oldvelocity = proj.velocity;
442 other = proj; MUTATOR_CALLHOOK(EditProjectile);
448 if(tracereffects & EF_RED)
449 eff = particleeffectnum("tr_rifle");
450 else if(tracereffects & EF_BLUE)
451 eff = particleeffectnum("tr_rifle_weak");
453 eff = particleeffectnum("tr_bullet");
455 // NOTE: this may severely throw off weapon balance
456 lag = ANTILAG_LATENCY(self);
459 if(clienttype(self) != CLIENTTYPE_REAL)
461 if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
462 lag = 0; // only do hitscan, but no antilag
467 antilag_takeback(pl, time - lag);
472 savetime = frametime;
477 // DP tracetoss is stupid and always traces in 0.05s
478 // ticks. This makes it trace in 0.05*0.125s ticks
484 self.velocity = self.velocity * 0.125;
485 self.gravity *= 0.125 * 0.125;
487 fireBallisticBullet_trace_callback_ent = self;
488 fireBallisticBullet_trace_callback_eff = eff;
489 WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
493 if(trace_fraction == 1)
495 // won't hit anything anytime soon (DP's
496 // tracetoss does 200 tics of, here,
497 // 0.05*0.125s, that is, 1.25 seconds
500 dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
501 setorigin(self, trace_endpos);
502 self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
504 if(!SUB_OwnerCheck())
506 if(SUB_NoImpactCheck())
510 W_BallisticBullet_Hit();
513 if(proj.dmg_radius < 0) // these NEVER penetrate solid
516 // if we hit "weapclip", bail out
518 // rationale of this check:
520 // any shader that is solid, nodraw AND trans is meant to clip weapon
521 // shots and players, but has no other effect!
523 // if it is not trans, it is caulk and should not have this side effect
526 // common/weapclip (intended)
527 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
528 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
529 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
530 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
534 if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1))
537 W_BallisticBullet_LeaveSolid_think();
539 self.projectiledeathtype |= HITTYPE_BOUNCE;
541 frametime = savetime;
554 if(tracereffects & EF_RED)
555 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE);
556 else if(tracereffects & EF_BLUE)
557 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
559 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
562 void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
566 dir = normalize(dir + randomvec() * spread);
567 end = start + dir * MAX_SHOT_DISTANCE;
568 if(self.antilag_debug)
569 traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
571 traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
575 if (pointcontents (trace_endpos) != CONTENT_SKY)
577 if not (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
578 Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, trace_ent.species, self);
580 Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
585 float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
587 float is_from_contents = (deathtype == DEATH_SLIME || deathtype == DEATH_LAVA);
588 float is_from_owner = (inflictor == projowner);
589 float is_from_exception = (exception != -1);
591 //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")));
593 if(autocvar_g_projectiles_damage <= -2)
595 return FALSE; // no damage to projectiles at all, not even with the exceptions
597 else if(autocvar_g_projectiles_damage == -1)
599 if(is_from_exception)
600 return (exception); // if exception is detected, allow it to override
602 return FALSE; // otherwise, no other damage is allowed
604 else if(autocvar_g_projectiles_damage == 0)
606 if(is_from_exception)
607 return (exception); // if exception is detected, allow it to override
608 else if not(is_from_contents)
609 return FALSE; // otherwise, only allow damage from contents
611 else if(autocvar_g_projectiles_damage == 1)
613 if(is_from_exception)
614 return (exception); // if exception is detected, allow it to override
615 else if not(is_from_contents || is_from_owner)
616 return FALSE; // otherwise, only allow self damage and damage from contents
618 else if(autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions
620 if(is_from_exception)
621 return (exception); // if exception is detected, allow it to override
624 return TRUE; // if none of these return, then allow damage anyway.
627 void W_PrepareExplosionByDamage(entity attacker, void() explode)
629 self.takedamage = DAMAGE_NO;
630 self.event_damage = func_null;
632 if((attacker.flags & FL_CLIENT) && !autocvar_g_projectiles_keep_owner)
634 self.owner = attacker;
635 self.realowner = attacker;
638 // do not explode NOW but in the NEXT FRAME!
639 // because recursive calls to RadiusDamage are not allowed
640 self.nextthink = time;
641 self.think = explode;