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(self.dmg_radius < 0) // these NEVER penetrate solid
356 // if we hit "weapclip", bail out
358 // rationale of this check:
360 // any shader that is solid, nodraw AND trans is meant to clip weapon
361 // shots and players, but has no other effect!
363 // if it is not trans, it is caulk and should not have this side effect
366 // common/weapclip (intended)
367 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
368 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
369 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
370 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
377 if(!W_BallisticBullet_LeaveSolid(-1))
383 self.projectiledeathtype |= HITTYPE_BOUNCE;
386 void endFireBallisticBullet()
388 endzcurveparticles();
391 entity fireBallisticBullet_trace_callback_ent;
392 float fireBallisticBullet_trace_callback_eff;
393 void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
395 if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
396 zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
397 WarpZone_trace_forent = world;
401 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)
403 float lag, dt, savetime; //, density;
407 antilagging = (autocvar_g_antilag_bullets && (pSpeed >= autocvar_g_antilag_bullets));
411 proj.classname = "bullet";
412 proj.owner = proj.realowner = self;
413 PROJECTILE_MAKETRIGGER(proj);
414 if(gravityfactor > 0)
416 proj.movetype = MOVETYPE_TOSS;
417 proj.gravity = gravityfactor;
420 proj.movetype = MOVETYPE_FLY;
421 proj.think = SUB_Remove;
422 proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
423 W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
424 proj.angles = vectoangles(proj.velocity);
425 if(bulletconstant > 0)
426 proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
427 else if(bulletconstant == 0)
430 proj.dmg_radius = -1;
431 // so: bulletconstant = bullet mass / area of bullet circle
432 setorigin(proj, start);
433 proj.flags = FL_PROJECTILE;
435 proj.touch = W_BallisticBullet_Touch;
437 proj.dmg_edge = headshotbonus;
438 proj.dmg_force = force;
439 proj.projectiledeathtype = dtype;
441 proj.oldvelocity = proj.velocity;
443 other = proj; MUTATOR_CALLHOOK(EditProjectile);
449 if(tracereffects & EF_RED)
450 eff = particleeffectnum("tr_rifle");
451 else if(tracereffects & EF_BLUE)
452 eff = particleeffectnum("tr_rifle_weak");
454 eff = particleeffectnum("tr_bullet");
456 // NOTE: this may severely throw off weapon balance
457 lag = ANTILAG_LATENCY(self);
460 if(clienttype(self) != CLIENTTYPE_REAL)
462 if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
463 lag = 0; // only do hitscan, but no antilag
468 antilag_takeback(pl, time - lag);
473 savetime = frametime;
478 // DP tracetoss is stupid and always traces in 0.05s
479 // ticks. This makes it trace in 0.05*0.125s ticks
485 self.velocity = self.velocity * 0.125;
486 self.gravity *= 0.125 * 0.125;
488 fireBallisticBullet_trace_callback_ent = self;
489 fireBallisticBullet_trace_callback_eff = eff;
490 WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
494 if(trace_fraction == 1)
496 // won't hit anything anytime soon (DP's
497 // tracetoss does 200 tics of, here,
498 // 0.05*0.125s, that is, 1.25 seconds
501 dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
502 setorigin(self, trace_endpos);
503 self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
505 if(!SUB_OwnerCheck())
507 if(SUB_NoImpactCheck())
511 W_BallisticBullet_Hit();
514 if(proj.dmg_radius < 0) // these NEVER penetrate solid
517 // if we hit "weapclip", bail out
519 // rationale of this check:
521 // any shader that is solid, nodraw AND trans is meant to clip weapon
522 // shots and players, but has no other effect!
524 // if it is not trans, it is caulk and should not have this side effect
527 // common/weapclip (intended)
528 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
529 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
530 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
531 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
535 if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1))
538 W_BallisticBullet_LeaveSolid_think();
540 self.projectiledeathtype |= HITTYPE_BOUNCE;
542 frametime = savetime;
555 if(tracereffects & EF_RED)
556 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE);
557 else if(tracereffects & EF_BLUE)
558 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
560 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
563 void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
567 dir = normalize(dir + randomvec() * spread);
568 end = start + dir * MAX_SHOT_DISTANCE;
569 if(self.antilag_debug)
570 traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
572 traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
576 if (pointcontents (trace_endpos) != CONTENT_SKY)
578 if not (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
579 Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, trace_ent.species, self);
581 Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
586 float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
588 float is_from_contents = (deathtype == DEATH_SLIME || deathtype == DEATH_LAVA);
589 float is_from_owner = (inflictor == projowner);
590 float is_from_exception = (exception != -1);
592 //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")));
594 if(autocvar_g_projectiles_damage <= -2)
596 return FALSE; // no damage to projectiles at all, not even with the exceptions
598 else if(autocvar_g_projectiles_damage == -1)
600 if(is_from_exception)
601 return (exception); // if exception is detected, allow it to override
603 return FALSE; // otherwise, no other damage is allowed
605 else if(autocvar_g_projectiles_damage == 0)
607 if(is_from_exception)
608 return (exception); // if exception is detected, allow it to override
609 else if not(is_from_contents)
610 return FALSE; // otherwise, only allow damage from contents
612 else if(autocvar_g_projectiles_damage == 1)
614 if(is_from_exception)
615 return (exception); // if exception is detected, allow it to override
616 else if not(is_from_contents || is_from_owner)
617 return FALSE; // otherwise, only allow self damage and damage from contents
619 else if(autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions
621 if(is_from_exception)
622 return (exception); // if exception is detected, allow it to override
625 return TRUE; // if none of these return, then allow damage anyway.
628 void W_PrepareExplosionByDamage(entity attacker, void() explode)
630 self.takedamage = DAMAGE_NO;
631 self.event_damage = SUB_Null;
633 if((attacker.flags & FL_CLIENT) && !autocvar_g_projectiles_keep_owner)
635 self.owner = attacker;
636 self.realowner = attacker;
639 // do not explode NOW but in the NEXT FRAME!
640 // because recursive calls to RadiusDamage are not allowed
641 self.nextthink = time;
642 self.think = explode;