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 it is world we can't hurt it so stop now
70 if (trace_ent == world || trace_fraction == 1)
73 // make the entity non-solid so we can hit the next one
74 trace_ent.railgunhit = TRUE;
75 trace_ent.railgunhitloc = end;
76 trace_ent.railgunhitsolidbackup = trace_ent.solid;
77 trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
78 trace_ent.railgunforce = WarpZone_TransformVelocity(WarpZone_trace_transform, force);
80 // stop if this is a wall
81 if (trace_ent.solid == SOLID_BSP)
84 // make the entity non-solid
85 trace_ent.solid = SOLID_NOT;
88 endpoint = trace_endpos;
90 endq3surfaceflags = trace_dphitq3surfaceflags;
92 // find all the entities the railgun hit and restore their solid state
93 ent = findfloat(world, railgunhit, TRUE);
96 // restore their solid type
97 ent.solid = ent.railgunhitsolidbackup;
98 ent = findfloat(ent, railgunhit, TRUE);
101 // spawn a temporary explosion entity for RadiusDamage calls
102 //explosion = spawn();
104 // Find all non-hit players the beam passed close by
105 if(deathtype == WEP_MINSTANEX || deathtype == WEP_NEX)
107 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
109 // nearest point on the beam
110 beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
112 f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
116 snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav");
118 if(!pseudoprojectile)
119 pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
120 soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTN_NONE);
124 remove(pseudoprojectile);
127 // find all the entities the railgun hit and hurt them
128 ent = findfloat(world, railgunhit, TRUE);
131 // get the details we need to call the damage function
132 hitloc = ent.railgunhitloc;
134 f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
135 ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
137 if(accuracy_isgooddamage(self.realowner, ent))
138 totaldmg += bdamage * f;
142 Damage (ent, self, self, bdamage * f, deathtype, hitloc, ent.railgunforce * ffs);
144 // create a small explosion to throw gibs around (if applicable)
145 //setorigin (explosion, hitloc);
146 //RadiusDamage (explosion, self, 10, 0, 50, world, 300, deathtype);
148 ent.railgunhitloc = '0 0 0';
149 ent.railgunhitsolidbackup = SOLID_NOT;
150 ent.railgunhit = FALSE;
151 ent.railgundistance = 0;
153 // advance to the next entity
154 ent = findfloat(ent, railgunhit, TRUE);
157 // calculate hits and fired shots for hitscan
158 accuracy_add(self, self.weapon, 0, min(bdamage, totaldmg));
160 trace_endpos = endpoint;
162 trace_dphitq3surfaceflags = endq3surfaceflags;
169 void W_BallisticBullet_Hit (void)
173 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
174 q = 1 + self.dmg_edge / self.dmg;
176 if(other.solid == SOLID_BSP)
177 Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * f, self.projectiledeathtype, self);
179 if(other && other != self.enemy)
181 endzcurveparticles();
185 damage_headshotbonus = self.dmg_edge * f;
186 railgun_start = self.origin - 2 * frametime * self.velocity;
187 railgun_end = self.origin + 2 * frametime * self.velocity;
188 g = accuracy_isgooddamage(self.realowner, other);
189 Damage(other, self, self.realowner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
190 damage_headshotbonus = 0;
194 if(self.dmg_edge > 0)
197 AnnounceTo(self.realowner, "headshot");
199 AnnounceTo(self.realowner, "awesome");
202 // calculate hits for ballistic weapons
205 // do not exceed 100%
206 q = min(self.dmg * q, self.dmg_total + f * self.dmg) - self.dmg_total;
207 self.dmg_total += f * self.dmg;
208 accuracy_add(self.realowner, self.realowner.weapon, 0, q);
212 self.enemy = other; // don't hit the same player twice with the same bullet
215 .void(void) W_BallisticBullet_LeaveSolid_think_save;
216 .float W_BallisticBullet_LeaveSolid_nextthink_save;
217 .vector W_BallisticBullet_LeaveSolid_origin;
218 .vector W_BallisticBullet_LeaveSolid_velocity;
220 void W_BallisticBullet_LeaveSolid_think()
222 setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
223 self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
225 self.think = self.W_BallisticBullet_LeaveSolid_think_save;
226 self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
227 self.W_BallisticBullet_LeaveSolid_think_save = SUB_Null;
229 self.flags &~= FL_ONGROUND;
231 if(self.enemy.solid == SOLID_BSP)
234 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
235 Damage_DamageInfo(self.origin, 0, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * -f, self.projectiledeathtype, self);
238 UpdateCSQCProjectile(self);
241 float W_BallisticBullet_LeaveSolid(entity e, vector vel, float constant)
243 // move the entity along its velocity until it's out of solid, then let it resume
245 float dt, dst, velfactor, v0, vs;
249 // outside the world? forget it
250 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)
253 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
256 E0_m = 0.5 * v0 * v0;
257 maxdist = E0_m / constant;
258 // maxdist = 0.5 * v0 * v0 / constant
259 // dprint("max dist = ", ftos(maxdist), "\n");
261 if(maxdist <= autocvar_g_ballistics_mindistance)
264 traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self);
266 if(trace_fraction == 1) // 1: we never got out of solid
269 self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
271 dst = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - self.origin));
272 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
273 Es_m = E0_m - constant * dst;
276 // roundoff errors got us
282 dt = dst / (0.5 * (v0 + vs));
283 // this is not correct, but the differential equations have no analytic
284 // solution - and these times are very small anyway
285 //print("dt = ", ftos(dt), "\n");
287 self.W_BallisticBullet_LeaveSolid_think_save = self.think;
288 self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
289 self.think = W_BallisticBullet_LeaveSolid_think;
290 self.nextthink = time + dt;
292 vel = vel * velfactor;
294 self.velocity = '0 0 0';
295 self.flags |= FL_ONGROUND; // prevent moving
296 self.W_BallisticBullet_LeaveSolid_velocity = vel;
301 void W_BallisticBullet_Touch (void)
305 if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
309 W_BallisticBullet_Hit ();
311 // if we hit "weapclip", bail out
313 // rationale of this check:
315 // any shader that is solid, nodraw AND trans is meant to clip weapon
316 // shots and players, but has no other effect!
318 // if it is not trans, it is caulk and should not have this side effect
321 // common/weapclip (intended)
322 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
323 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
324 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
325 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
331 density = other.ballistics_density;
336 if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius * density))
342 self.projectiledeathtype |= HITTYPE_BOUNCE;
345 void endFireBallisticBullet()
347 endzcurveparticles();
350 entity fireBallisticBullet_trace_callback_ent;
351 float fireBallisticBullet_trace_callback_eff;
352 void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
354 if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
355 zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
358 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)
360 float lag, dt, savetime, density;
364 antilagging = (autocvar_g_antilag_bullets && (pSpeed >= autocvar_g_antilag_bullets));
368 proj.classname = "bullet";
369 proj.owner = proj.realowner = self;
370 PROJECTILE_MAKETRIGGER(proj);
371 if(gravityfactor > 0)
373 proj.movetype = MOVETYPE_TOSS;
374 proj.gravity = gravityfactor;
377 proj.movetype = MOVETYPE_FLY;
378 proj.think = SUB_Remove;
379 proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
380 W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
381 proj.angles = vectoangles(proj.velocity);
382 proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
383 // so: bulletconstant = bullet mass / area of bullet circle
384 setorigin(proj, start);
385 proj.flags = FL_PROJECTILE;
387 proj.touch = W_BallisticBullet_Touch;
389 proj.dmg_edge = headshotbonus;
390 proj.dmg_force = force;
391 proj.projectiledeathtype = dtype;
393 proj.oldvelocity = proj.velocity;
395 other = proj; MUTATOR_CALLHOOK(EditProjectile);
401 if(tracereffects & EF_RED)
402 eff = particleeffectnum("tr_rifle");
403 else if(tracereffects & EF_BLUE)
404 eff = particleeffectnum("tr_rifle_weak");
406 eff = particleeffectnum("tr_bullet");
408 // NOTE: this may severely throw off weapon balance
409 lag = ANTILAG_LATENCY(self);
412 if(clienttype(self) != CLIENTTYPE_REAL)
414 if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
415 lag = 0; // only do hitscan, but no antilag
419 antilag_takeback(pl, time - lag);
424 savetime = frametime;
429 // DP tracetoss is stupid and always traces in 0.05s
430 // ticks. This makes it trace in 0.05*0.125s ticks
436 self.velocity = self.velocity * 0.125;
437 self.gravity *= 0.125 * 0.125;
439 fireBallisticBullet_trace_callback_ent = self;
440 fireBallisticBullet_trace_callback_eff = eff;
441 // FIXME can we somehow do this with just ONE trace?
442 WarpZone_TraceToss(self, self.owner);
443 if(self.owner && WarpZone_trace_firstzone)
450 WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
454 if(trace_fraction == 1)
456 // won't hit anything anytime soon (DP's
457 // tracetoss does 200 tics of, here,
458 // 0.05*0.125s, that is, 1.25 seconds
461 dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
462 setorigin(self, trace_endpos);
463 self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
465 if(!SUB_OwnerCheck())
467 if(SUB_NoImpactCheck())
471 W_BallisticBullet_Hit();
474 // if we hit "weapclip", bail out
476 // rationale of this check:
478 // any shader that is solid, nodraw AND trans is meant to clip weapon
479 // shots and players, but has no other effect!
481 // if it is not trans, it is caulk and should not have this side effect
484 // common/weapclip (intended)
485 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
486 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
487 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
488 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
491 density = other.ballistics_density;
496 if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius * density))
499 W_BallisticBullet_LeaveSolid_think();
501 frametime = savetime;
513 if(tracereffects & EF_RED)
514 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE);
515 else if(tracereffects & EF_BLUE)
516 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
518 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
521 void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
525 dir = normalize(dir + randomvec() * spread);
526 end = start + dir * MAX_SHOT_DISTANCE;
527 if(self.antilag_debug)
528 traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
530 traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
534 if ((trace_fraction != 1.0) && (pointcontents (trace_endpos) != CONTENT_SKY))
536 if not (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
537 Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, self);
539 Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
544 float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
546 float is_from_contents = (deathtype == DEATH_SLIME || deathtype == DEATH_LAVA);
547 float is_from_owner = (inflictor == projowner);
548 float is_from_exception = (exception != -1);
550 //print(strcat("from_contents ", ftos(is_from_contents), " : from_owner ", ftos(is_from_owner), " : exception ", strcat(ftos(is_from_exception), " (", ftos(exception), "). \n")));
552 if(autocvar_g_projectiles_damage <= -2)
554 return FALSE; // no damage to projectiles at all, not even with the exceptions
556 else if(autocvar_g_projectiles_damage == -1)
558 if(is_from_exception)
559 return (exception); // if exception is detected, allow it to override
561 return FALSE; // otherwise, no other damage is allowed
563 else if(autocvar_g_projectiles_damage == 0)
565 if(is_from_exception)
566 return (exception); // if exception is detected, allow it to override
567 else if not(is_from_contents)
568 return FALSE; // otherwise, only allow damage from contents
570 else if(autocvar_g_projectiles_damage == 1)
572 if(is_from_exception)
573 return (exception); // if exception is detected, allow it to override
574 else if not(is_from_contents || is_from_owner)
575 return FALSE; // otherwise, only allow self damage and damage from contents
577 else if(autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions
579 if(is_from_exception)
580 return (exception); // if exception is detected, allow it to override
583 return TRUE; // if none of these return, then allow damage anyway.
586 void W_PrepareExplosionByDamage(entity attacker, void() explode)
588 self.takedamage = DAMAGE_NO;
589 self.event_damage = SUB_Null;
590 self.owner = attacker;
591 self.realowner = attacker;
593 // do not explode NOW but in the NEXT FRAME!
594 // because recursive calls to RadiusDamage are not allowed
595 self.nextthink = time;
596 self.think = explode;