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 constant = self.dmg_radius * (other.ballistics_density ? other.ballistics_density : 1);
251 float dt, dst, velfactor, v0, vs;
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 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
262 E0_m = 0.5 * v0 * v0;
263 maxdist = E0_m / constant;
264 // maxdist = 0.5 * v0 * v0 / constant
265 // dprint("max dist = ", ftos(maxdist), "\n");
267 if(maxdist <= autocvar_g_ballistics_mindistance)
270 traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self, TRUE);
271 if(trace_fraction == 1) // 1: we never got out of solid
274 self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
276 dst = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - self.origin));
277 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
278 Es_m = E0_m - constant * dst;
281 // roundoff errors got us
287 dt = dst / (0.5 * (v0 + vs));
288 // this is not correct, but the differential equations have no analytic
289 // solution - and these times are very small anyway
290 //print("dt = ", ftos(dt), "\n");
292 self.W_BallisticBullet_LeaveSolid_think_save = self.think;
293 self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
294 self.think = W_BallisticBullet_LeaveSolid_think;
295 self.nextthink = time + dt;
297 vel = vel * velfactor;
299 self.velocity = '0 0 0';
300 self.flags |= FL_ONGROUND; // prevent moving
301 self.W_BallisticBullet_LeaveSolid_velocity = vel;
304 if(vlen(trace_endpos - self.origin) > 4)
306 endzcurveparticles();
307 trailparticles(self, eff, self.origin, trace_endpos);
313 void W_BallisticBullet_Touch (void)
317 if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
321 W_BallisticBullet_Hit ();
323 // if we hit "weapclip", bail out
325 // rationale of this check:
327 // any shader that is solid, nodraw AND trans is meant to clip weapon
328 // shots and players, but has no other effect!
330 // if it is not trans, it is caulk and should not have this side effect
333 // common/weapclip (intended)
334 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
335 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
336 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
337 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
344 if(!W_BallisticBullet_LeaveSolid(-1))
350 self.projectiledeathtype |= HITTYPE_BOUNCE;
353 void endFireBallisticBullet()
355 endzcurveparticles();
358 entity fireBallisticBullet_trace_callback_ent;
359 float fireBallisticBullet_trace_callback_eff;
360 void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
362 if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
363 zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
364 WarpZone_trace_forent = world;
368 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)
370 float lag, dt, savetime, density;
374 antilagging = (autocvar_g_antilag_bullets && (pSpeed >= autocvar_g_antilag_bullets));
378 proj.classname = "bullet";
379 proj.owner = proj.realowner = self;
380 PROJECTILE_MAKETRIGGER(proj);
381 if(gravityfactor > 0)
383 proj.movetype = MOVETYPE_TOSS;
384 proj.gravity = gravityfactor;
387 proj.movetype = MOVETYPE_FLY;
388 proj.think = SUB_Remove;
389 proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
390 W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
391 proj.angles = vectoangles(proj.velocity);
392 proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
393 // so: bulletconstant = bullet mass / area of bullet circle
394 setorigin(proj, start);
395 proj.flags = FL_PROJECTILE;
397 proj.touch = W_BallisticBullet_Touch;
399 proj.dmg_edge = headshotbonus;
400 proj.dmg_force = force;
401 proj.projectiledeathtype = dtype;
403 proj.oldvelocity = proj.velocity;
405 other = proj; MUTATOR_CALLHOOK(EditProjectile);
411 if(tracereffects & EF_RED)
412 eff = particleeffectnum("tr_rifle");
413 else if(tracereffects & EF_BLUE)
414 eff = particleeffectnum("tr_rifle_weak");
416 eff = particleeffectnum("tr_bullet");
418 // NOTE: this may severely throw off weapon balance
419 lag = ANTILAG_LATENCY(self);
422 if(clienttype(self) != CLIENTTYPE_REAL)
424 if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
425 lag = 0; // only do hitscan, but no antilag
430 antilag_takeback(pl, time - lag);
435 savetime = frametime;
440 // DP tracetoss is stupid and always traces in 0.05s
441 // ticks. This makes it trace in 0.05*0.125s ticks
447 self.velocity = self.velocity * 0.125;
448 self.gravity *= 0.125 * 0.125;
450 fireBallisticBullet_trace_callback_ent = self;
451 fireBallisticBullet_trace_callback_eff = eff;
452 WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
456 if(trace_fraction == 1)
458 // won't hit anything anytime soon (DP's
459 // tracetoss does 200 tics of, here,
460 // 0.05*0.125s, that is, 1.25 seconds
463 dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
464 setorigin(self, trace_endpos);
465 self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
467 if(!SUB_OwnerCheck())
469 if(SUB_NoImpactCheck())
473 W_BallisticBullet_Hit();
476 // if we hit "weapclip", bail out
478 // rationale of this check:
480 // any shader that is solid, nodraw AND trans is meant to clip weapon
481 // shots and players, but has no other effect!
483 // if it is not trans, it is caulk and should not have this side effect
486 // common/weapclip (intended)
487 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
488 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
489 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
490 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
494 if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1))
497 W_BallisticBullet_LeaveSolid_think();
499 self.projectiledeathtype |= HITTYPE_BOUNCE;
501 frametime = savetime;
514 if(tracereffects & EF_RED)
515 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE);
516 else if(tracereffects & EF_BLUE)
517 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
519 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
522 void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
526 dir = normalize(dir + randomvec() * spread);
527 end = start + dir * MAX_SHOT_DISTANCE;
528 if(self.antilag_debug)
529 traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
531 traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
535 if (pointcontents (trace_endpos) != CONTENT_SKY)
537 if not (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
538 Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, trace_ent.species, self);
540 Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
545 float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
547 float is_from_contents = (deathtype == DEATH_SLIME || deathtype == DEATH_LAVA);
548 float is_from_owner = (inflictor == projowner);
549 float is_from_exception = (exception != -1);
551 //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")));
553 if(autocvar_g_projectiles_damage <= -2)
555 return FALSE; // no damage to projectiles at all, not even with the exceptions
557 else if(autocvar_g_projectiles_damage == -1)
559 if(is_from_exception)
560 return (exception); // if exception is detected, allow it to override
562 return FALSE; // otherwise, no other damage is allowed
564 else if(autocvar_g_projectiles_damage == 0)
566 if(is_from_exception)
567 return (exception); // if exception is detected, allow it to override
568 else if not(is_from_contents)
569 return FALSE; // otherwise, only allow damage from contents
571 else if(autocvar_g_projectiles_damage == 1)
573 if(is_from_exception)
574 return (exception); // if exception is detected, allow it to override
575 else if not(is_from_contents || is_from_owner)
576 return FALSE; // otherwise, only allow self damage and damage from contents
578 else if(autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions
580 if(is_from_exception)
581 return (exception); // if exception is detected, allow it to override
584 return TRUE; // if none of these return, then allow damage anyway.
587 void W_PrepareExplosionByDamage(entity attacker, void() explode)
589 self.takedamage = DAMAGE_NO;
590 self.event_damage = SUB_Null;
592 if((attacker.flags & FL_CLIENT) && !autocvar_g_projectiles_keep_owner)
594 self.owner = attacker;
595 self.realowner = attacker;
598 // do not explode NOW but in the NEXT FRAME!
599 // because recursive calls to RadiusDamage are not allowed
600 self.nextthink = time;
601 self.think = explode;