2 void W_GiveWeapon (entity e, float wep, string name)
9 e.weapons = e.weapons | W_WeaponBit(wep);
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 railgun_start = start;
44 dir = normalize(end - start);
45 length = vlen(end - start);
48 // go a little bit into the wall because we need to hit this wall later
53 // trace multiple times until we hit a wall, each obstacle will be made
54 // non-solid so we can hit the next, while doing this we spawn effects and
55 // note down which entities were hit so we can damage them later
59 if(self.antilag_debug)
60 WarpZone_traceline_antilag (self, start, end, FALSE, o, self.antilag_debug);
62 WarpZone_traceline_antilag (self, start, end, FALSE, o, ANTILAG_LATENCY(self));
63 if(o && WarpZone_trace_firstzone)
69 if(trace_ent.solid == SOLID_BSP || trace_ent.solid == SOLID_SLIDEBOX)
70 Damage_DamageInfo(trace_endpos, bdamage, 0, 0, force, deathtype, trace_ent.species, self);
72 // if it is world we can't hurt it so stop now
73 if (trace_ent == world || trace_fraction == 1)
76 // make the entity non-solid so we can hit the next one
77 trace_ent.railgunhit = TRUE;
78 trace_ent.railgunhitloc = end;
79 trace_ent.railgunhitsolidbackup = trace_ent.solid;
80 trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
81 trace_ent.railgunforce = WarpZone_TransformVelocity(WarpZone_trace_transform, force);
83 // stop if this is a wall
84 if (trace_ent.solid == SOLID_BSP)
87 // make the entity non-solid
88 trace_ent.solid = SOLID_NOT;
91 endpoint = trace_endpos;
93 endq3surfaceflags = trace_dphitq3surfaceflags;
95 // find all the entities the railgun hit and restore their solid state
96 ent = findfloat(world, railgunhit, TRUE);
99 // restore their solid type
100 ent.solid = ent.railgunhitsolidbackup;
101 ent = findfloat(ent, railgunhit, TRUE);
104 // spawn a temporary explosion entity for RadiusDamage calls
105 //explosion = spawn();
107 // Find all non-hit players the beam passed close by
108 if(deathtype == WEP_MINSTANEX || deathtype == WEP_NEX)
110 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
112 // nearest point on the beam
113 beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
115 f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
119 snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav");
121 if(!pseudoprojectile)
122 pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
123 soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTN_NONE);
127 remove(pseudoprojectile);
130 // find all the entities the railgun hit and hurt them
131 ent = findfloat(world, railgunhit, TRUE);
134 // get the details we need to call the damage function
135 hitloc = ent.railgunhitloc;
137 f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
138 ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
140 if(accuracy_isgooddamage(self.realowner, ent))
141 totaldmg += bdamage * f;
145 Damage (ent, self, self, bdamage * f, deathtype, hitloc, ent.railgunforce * ffs);
147 // create a small explosion to throw gibs around (if applicable)
148 //setorigin (explosion, hitloc);
149 //RadiusDamage (explosion, self, 10, 0, 50, world, 300, deathtype);
151 ent.railgunhitloc = '0 0 0';
152 ent.railgunhitsolidbackup = SOLID_NOT;
153 ent.railgunhit = FALSE;
154 ent.railgundistance = 0;
156 // advance to the next entity
157 ent = findfloat(ent, railgunhit, TRUE);
160 // calculate hits and fired shots for hitscan
161 accuracy_add(self, self.weapon, 0, min(bdamage, totaldmg));
163 trace_endpos = endpoint;
165 trace_dphitq3surfaceflags = endq3surfaceflags;
172 void W_BallisticBullet_Hit (void)
176 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
177 q = 1 + self.dmg_edge / self.dmg;
179 if(other.solid == SOLID_BSP || other.solid == SOLID_SLIDEBOX)
180 Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * f, self.projectiledeathtype, other.species, self);
182 if(other && other != self.enemy)
184 endzcurveparticles();
188 damage_headshotbonus = self.dmg_edge * f;
189 railgun_start = self.origin - 2 * frametime * self.velocity;
190 railgun_end = self.origin + 2 * frametime * self.velocity;
191 g = accuracy_isgooddamage(self.realowner, other);
192 Damage(other, self, self.realowner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
193 damage_headshotbonus = 0;
197 if(self.dmg_edge > 0)
200 AnnounceTo(self.realowner, "headshot");
202 AnnounceTo(self.realowner, "awesome");
205 // calculate hits for ballistic weapons
208 // do not exceed 100%
209 q = min(self.dmg * q, self.dmg_total + f * self.dmg) - self.dmg_total;
210 self.dmg_total += f * self.dmg;
211 accuracy_add(self.realowner, self.realowner.weapon, 0, q);
215 self.enemy = other; // don't hit the same player twice with the same bullet
218 .void(void) W_BallisticBullet_LeaveSolid_think_save;
219 .float W_BallisticBullet_LeaveSolid_nextthink_save;
220 .vector W_BallisticBullet_LeaveSolid_origin;
221 .vector W_BallisticBullet_LeaveSolid_velocity;
223 void W_BallisticBullet_LeaveSolid_think()
225 setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
226 self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
228 self.think = self.W_BallisticBullet_LeaveSolid_think_save;
229 self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
230 self.W_BallisticBullet_LeaveSolid_think_save = SUB_Null;
232 self.flags &~= FL_ONGROUND;
234 if(self.enemy.solid == SOLID_BSP)
237 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
238 Damage_DamageInfo(self.origin, 0, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * -f, self.projectiledeathtype, 0, self);
241 UpdateCSQCProjectile(self);
244 float W_BallisticBullet_LeaveSolid(entity e, vector vel, float constant)
246 // move the entity along its velocity until it's out of solid, then let it resume
248 float dt, dst, velfactor, v0, vs;
252 // outside the world? forget it
253 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)
256 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
259 E0_m = 0.5 * v0 * v0;
260 maxdist = E0_m / constant;
261 // maxdist = 0.5 * v0 * v0 / constant
262 // dprint("max dist = ", ftos(maxdist), "\n");
264 if(maxdist <= autocvar_g_ballistics_mindistance)
267 traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self);
269 if(trace_fraction == 1) // 1: we never got out of solid
272 self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
274 dst = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - self.origin));
275 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
276 Es_m = E0_m - constant * dst;
279 // roundoff errors got us
285 dt = dst / (0.5 * (v0 + vs));
286 // this is not correct, but the differential equations have no analytic
287 // solution - and these times are very small anyway
288 //print("dt = ", ftos(dt), "\n");
290 self.W_BallisticBullet_LeaveSolid_think_save = self.think;
291 self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
292 self.think = W_BallisticBullet_LeaveSolid_think;
293 self.nextthink = time + dt;
295 vel = vel * velfactor;
297 self.velocity = '0 0 0';
298 self.flags |= FL_ONGROUND; // prevent moving
299 self.W_BallisticBullet_LeaveSolid_velocity = vel;
304 void W_BallisticBullet_Touch (void)
308 if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
312 W_BallisticBullet_Hit ();
314 // if we hit "weapclip", bail out
316 // rationale of this check:
318 // any shader that is solid, nodraw AND trans is meant to clip weapon
319 // shots and players, but has no other effect!
321 // if it is not trans, it is caulk and should not have this side effect
324 // common/weapclip (intended)
325 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
326 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
327 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
328 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
334 density = other.ballistics_density;
339 if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius * density))
345 self.projectiledeathtype |= HITTYPE_BOUNCE;
348 void endFireBallisticBullet()
350 endzcurveparticles();
353 entity fireBallisticBullet_trace_callback_ent;
354 float fireBallisticBullet_trace_callback_eff;
355 void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
357 if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
358 zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
359 WarpZone_trace_forent = world;
363 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)
365 float lag, dt, savetime, density;
369 antilagging = (autocvar_g_antilag_bullets && (pSpeed >= autocvar_g_antilag_bullets));
373 proj.classname = "bullet";
374 proj.owner = proj.realowner = self;
375 PROJECTILE_MAKETRIGGER(proj);
376 if(gravityfactor > 0)
378 proj.movetype = MOVETYPE_TOSS;
379 proj.gravity = gravityfactor;
382 proj.movetype = MOVETYPE_FLY;
383 proj.think = SUB_Remove;
384 proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
385 W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
386 proj.angles = vectoangles(proj.velocity);
387 proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
388 // so: bulletconstant = bullet mass / area of bullet circle
389 setorigin(proj, start);
390 proj.flags = FL_PROJECTILE;
392 proj.touch = W_BallisticBullet_Touch;
394 proj.dmg_edge = headshotbonus;
395 proj.dmg_force = force;
396 proj.projectiledeathtype = dtype;
398 proj.oldvelocity = proj.velocity;
400 other = proj; MUTATOR_CALLHOOK(EditProjectile);
406 if(tracereffects & EF_RED)
407 eff = particleeffectnum("tr_rifle");
408 else if(tracereffects & EF_BLUE)
409 eff = particleeffectnum("tr_rifle_weak");
411 eff = particleeffectnum("tr_bullet");
413 // NOTE: this may severely throw off weapon balance
414 lag = ANTILAG_LATENCY(self);
417 if(clienttype(self) != CLIENTTYPE_REAL)
419 if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
420 lag = 0; // only do hitscan, but no antilag
425 antilag_takeback(pl, time - lag);
430 savetime = frametime;
435 // DP tracetoss is stupid and always traces in 0.05s
436 // ticks. This makes it trace in 0.05*0.125s ticks
442 self.velocity = self.velocity * 0.125;
443 self.gravity *= 0.125 * 0.125;
445 fireBallisticBullet_trace_callback_ent = self;
446 fireBallisticBullet_trace_callback_eff = eff;
447 WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
451 if(trace_fraction == 1)
453 // won't hit anything anytime soon (DP's
454 // tracetoss does 200 tics of, here,
455 // 0.05*0.125s, that is, 1.25 seconds
458 dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
459 setorigin(self, trace_endpos);
460 self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
462 if(!SUB_OwnerCheck())
464 if(SUB_NoImpactCheck())
468 W_BallisticBullet_Hit();
471 // if we hit "weapclip", bail out
473 // rationale of this check:
475 // any shader that is solid, nodraw AND trans is meant to clip weapon
476 // shots and players, but has no other effect!
478 // if it is not trans, it is caulk and should not have this side effect
481 // common/weapclip (intended)
482 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
483 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
484 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
485 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
488 density = other.ballistics_density;
493 if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius * density))
496 W_BallisticBullet_LeaveSolid_think();
498 frametime = savetime;
511 if(tracereffects & EF_RED)
512 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE);
513 else if(tracereffects & EF_BLUE)
514 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
516 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
519 void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
523 dir = normalize(dir + randomvec() * spread);
524 end = start + dir * MAX_SHOT_DISTANCE;
525 if(self.antilag_debug)
526 traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
528 traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
532 if (pointcontents (trace_endpos) != CONTENT_SKY)
534 if not (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
535 Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, trace_ent.species, self);
537 Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
542 float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
544 float is_from_contents = (deathtype == DEATH_SLIME || deathtype == DEATH_LAVA);
545 float is_from_owner = (inflictor == projowner);
546 float is_from_exception = (exception != -1);
548 //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")));
550 if(autocvar_g_projectiles_damage <= -2)
552 return FALSE; // no damage to projectiles at all, not even with the exceptions
554 else if(autocvar_g_projectiles_damage == -1)
556 if(is_from_exception)
557 return (exception); // if exception is detected, allow it to override
559 return FALSE; // otherwise, no other damage is allowed
561 else if(autocvar_g_projectiles_damage == 0)
563 if(is_from_exception)
564 return (exception); // if exception is detected, allow it to override
565 else if not(is_from_contents)
566 return FALSE; // otherwise, only allow damage from contents
568 else if(autocvar_g_projectiles_damage == 1)
570 if(is_from_exception)
571 return (exception); // if exception is detected, allow it to override
572 else if not(is_from_contents || is_from_owner)
573 return FALSE; // otherwise, only allow self damage and damage from contents
575 else if(autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions
577 if(is_from_exception)
578 return (exception); // if exception is detected, allow it to override
581 return TRUE; // if none of these return, then allow damage anyway.
584 void W_PrepareExplosionByDamage(entity attacker, void() explode)
586 self.takedamage = DAMAGE_NO;
587 self.event_damage = SUB_Null;
588 self.owner = attacker;
589 self.realowner = attacker;
591 // do not explode NOW but in the NEXT FRAME!
592 // because recursive calls to RadiusDamage are not allowed
593 self.nextthink = time;
594 self.think = explode;