1 .float bullets_hit[WEP_COUNT]; //for hitscan bullets hit
2 .float bullets_fired[WEP_COUNT]; //for hitscan bullets fired
4 FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(bullets_hit);
5 FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(bullets_fired);
7 void W_GiveWeapon (entity e, float wep, string name)
14 e.weapons = e.weapons | W_WeaponBit(wep);
20 if (other.classname == "player")
22 sprint (other, "You got the ^2");
31 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype)
33 local vector hitloc, force, endpoint, dir;
34 local entity ent, endent;
35 local float endq3surfaceflags;
36 //local entity explosion;
41 entity pseudoprojectile;
46 railgun_start = start;
49 dir = normalize(end - start);
50 length = vlen(end - start);
53 // go a little bit into the wall because we need to hit this wall later
56 // trace multiple times until we hit a wall, each obstacle will be made
57 // non-solid so we can hit the next, while doing this we spawn effects and
58 // note down which entities were hit so we can damage them later
61 if(self.antilag_debug)
62 traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
64 traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
66 // if it is world we can't hurt it so stop now
67 if (trace_ent == world || trace_fraction == 1)
70 // make the entity non-solid so we can hit the next one
71 trace_ent.railgunhit = TRUE;
72 trace_ent.railgunhitloc = end;
73 trace_ent.railgunhitsolidbackup = trace_ent.solid;
75 // stop if this is a wall
76 if (trace_ent.solid == SOLID_BSP)
79 // make the entity non-solid
80 trace_ent.solid = SOLID_NOT;
83 endpoint = trace_endpos;
85 endq3surfaceflags = trace_dphitq3surfaceflags;
87 // find all the entities the railgun hit and restore their solid state
88 ent = findfloat(world, railgunhit, TRUE);
91 // restore their solid type
92 ent.solid = ent.railgunhitsolidbackup;
93 ent = findfloat(ent, railgunhit, TRUE);
96 // spawn a temporary explosion entity for RadiusDamage calls
97 //explosion = spawn();
99 // Find all non-hit players the beam passed close by
100 if(deathtype == WEP_MINSTANEX || deathtype == WEP_NEX)
102 FOR_EACH_REALCLIENT(msg_entity) if(msg_entity != self) if(!msg_entity.railgunhit) // we use realclient, so spectators can hear the whoosh too
104 // nearest point on the beam
105 beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
107 f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
111 snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav");
113 if(!pseudoprojectile)
114 pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
115 soundtoat(MSG_ONE, pseudoprojectile, beampos, CHAN_PROJECTILE, snd, VOL_BASE * f, ATTN_NONE);
119 remove(pseudoprojectile);
122 // find all the entities the railgun hit and hurt them
123 ent = findfloat(world, railgunhit, TRUE);
126 // get the details we need to call the damage function
127 hitloc = ent.railgunhitloc;
128 ent.railgunhitloc = '0 0 0';
129 ent.railgunhitsolidbackup = SOLID_NOT;
130 ent.railgunhit = FALSE;
132 //for stats so that team hit will count as a miss
133 if(ent.flags & FL_CLIENT)
134 if(ent.deadflag == DEAD_NO)
138 if(ent.team == self.team)
142 if (ent.takedamage || ent.classname == "case")
143 Damage (ent, self, self, bdamage, deathtype, hitloc, force);
145 // create a small explosion to throw gibs around (if applicable)
146 //setorigin (explosion, hitloc);
147 //RadiusDamage (explosion, self, 10, 0, 50, world, 300, deathtype);
149 // advance to the next entity
150 ent = findfloat(ent, railgunhit, TRUE);
153 //calculate hits and fired shots for hitscan
154 if not(inWarmupStage)
157 self.bullets_fired[self.weapon] += 1;
160 self.bullets_hit[self.weapon] += 1;
162 // update the client and store in addstat() in g_world
163 self.damage_hits = self.weapon + 64 * rint(self.bullets_hit[self.weapon]);
164 self.maxdamage_fired = self.weapon + 64 * rint(self.bullets_fired[self.weapon]);
167 // we're done with the explosion entity, remove it
170 trace_endpos = endpoint;
172 trace_dphitq3surfaceflags = endq3surfaceflags;
178 void W_BallisticBullet_Hit (void)
185 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
187 if(other.solid == SOLID_BSP)
188 Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, self.dmg_force * normalize(self.velocity) * f, self.projectiledeathtype, self);
190 if(other && other != self.enemy)
194 damage_headshotbonus = self.dmg_edge;
195 railgun_start = self.origin - 2 * frametime * self.velocity;
196 railgun_end = self.origin + 2 * frametime * self.velocity;
198 if(other.flags & FL_CLIENT)
199 if(other.deadflag == DEAD_NO)
203 if(other.team == self.owner.team)
206 Damage(other, self, self.owner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
207 damage_headshotbonus = 0;
209 if(self.dmg_edge != 0)
212 announce(self.owner, "announcer/male/headshot.wav");
214 announce(self.owner, "announcer/male/awesome.wav");
217 //calculate hits for ballistic weapons
218 if not(self.owner.isbot)
221 self.owner.bullets_hit[self.owner.weapon] += 1;
223 self.owner.damage_hits = self.owner.weapon + 64 * rint(self.owner.bullets_hit[self.owner.weapon]);
226 //sound (self, CHAN_PROJECTILE, "weapons/electro_impact.wav", VOL_BASE, ATTN_NORM);
229 self.enemy = other; // don't hit the same player twice with the same bullet
232 .void(void) W_BallisticBullet_LeaveSolid_think_save;
233 .float W_BallisticBullet_LeaveSolid_nextthink_save;
234 .vector W_BallisticBullet_LeaveSolid_origin;
235 .vector W_BallisticBullet_LeaveSolid_velocity;
237 void W_BallisticBullet_LeaveSolid_think()
239 setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
240 self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
242 self.think = self.W_BallisticBullet_LeaveSolid_think_save;
243 self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
244 self.W_BallisticBullet_LeaveSolid_think_save = SUB_Null;
246 self.flags &~= FL_ONGROUND;
248 if(self.enemy.solid == SOLID_BSP)
251 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
252 Damage_DamageInfo(self.origin, 0, 0, 0, self.dmg_force * normalize(self.velocity) * -f, self.projectiledeathtype, self);
255 UpdateCSQCProjectile(self);
258 // a fake logarithm function
263 if(x > 0.9 && x < 1.1)
265 return 2 * log(sqrt(x));
268 float W_BallisticBullet_LeaveSolid(entity e, vector vel, float constant)
270 // move the entity along its velocity until it's out of solid, then let it resume
272 float dt, dst, velfactor, v0, vs;
276 // outside the world? forget it
277 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)
280 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
283 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");
291 traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self);
293 if(trace_fraction == 1) // 1: we never got out of solid
296 self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
298 dst = vlen(trace_endpos - self.origin);
299 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
300 Es_m = E0_m - constant * dst;
303 // roundoff errors got us
309 dt = dst / (0.5 * (v0 + vs));
310 // this is not correct, but the differential equations have no analytic
311 // solution - and these times are very small anyway
312 //print("dt = ", ftos(dt), "\n");
314 self.W_BallisticBullet_LeaveSolid_think_save = self.think;
315 self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
316 self.think = W_BallisticBullet_LeaveSolid_think;
317 self.nextthink = time + dt;
319 vel = vel * velfactor;
321 self.velocity = '0 0 0';
322 self.flags |= FL_ONGROUND; // prevent moving
323 self.W_BallisticBullet_LeaveSolid_velocity = vel;
328 void W_BallisticBullet_Touch (void)
330 if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
334 W_BallisticBullet_Hit ();
337 if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius))
343 self.projectiledeathtype |= HITTYPE_BOUNCE;
346 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)
348 float lag, dt, savetime;
353 proj.classname = "bullet";
355 PROJECTILE_MAKETRIGGER(proj);
356 if(gravityfactor > 0)
358 proj.movetype = MOVETYPE_TOSS;
359 proj.gravity = gravityfactor;
362 proj.movetype = MOVETYPE_FLY;
363 proj.think = SUB_Remove;
364 proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
365 proj.velocity = (dir + randomvec() * spread) * pSpeed;
366 W_SetupProjectileVelocity(proj);
367 proj.angles = vectoangles(proj.velocity);
368 proj.dmg_radius = cvar("g_ballistics_materialconstant") / bulletconstant;
369 // so: bulletconstant = bullet mass / area of bullet circle
370 setorigin(proj, start);
371 proj.flags = FL_PROJECTILE;
373 proj.touch = W_BallisticBullet_Touch;
375 proj.dmg_edge = headshotbonus;
376 proj.dmg_force = force;
377 proj.projectiledeathtype = dtype;
379 proj.oldvelocity = proj.velocity;
381 //calculate fired bullets for ballistics
384 self.bullets_fired[self.weapon] += 1;
385 self.maxdamage_fired = self.weapon + 64 * rint(self.bullets_fired[self.weapon]);
388 if(cvar("g_antilag_bullets"))
389 if(pSpeed >= cvar("g_antilag_bullets"))
391 // NOTE: this may severely throw off weapon balance
392 lag = ANTILAG_LATENCY(self);
395 if(clienttype(self) != CLIENTTYPE_REAL)
397 if(cvar("g_antilag") == 0)
398 lag = 0; // only do hitscan, but no antilag
402 antilag_takeback(pl, time - lag);
407 savetime = frametime;
412 // DP tracetoss is stupid and always traces in 0.05s
413 // ticks. This makes it trace in 0.05*0.125s ticks
419 self.velocity = self.velocity * 0.125;
420 self.gravity *= 0.125 * 0.125;
422 tracetoss(self, oldself);
428 self.bullets_fired[self.weapon] += 1;
429 self.maxdamage_fired = self.weapon + 64 * rint(self.bullets_fired[self.weapon]);
432 if(vlen(trace_endpos - self.origin) > 32)
433 zcurveparticles_from_tracetoss(particleeffectnum("tr_bullet"), self.origin, trace_endpos, self.velocity);
434 if(trace_fraction == 1)
436 // won't hit anything anytime soon (DP's
437 // tracetoss does 200 tics of, here,
438 // 0.05*0.125s, that is, 1.25 seconds
441 dt = vlen(trace_endpos - self.origin) / vlen(self.velocity); // this is only approximate!
442 setorigin(self, trace_endpos);
443 self.velocity_z -= sv_gravity * dt;
445 if(!SUB_OwnerCheck())
447 if(SUB_NoImpactCheck())
451 W_BallisticBullet_Hit ();
455 if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius))
458 W_BallisticBullet_LeaveSolid_think();
460 frametime = savetime;
472 if(tracereffects & EF_RED)
473 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
475 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
479 void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
484 dir = normalize(dir + randomvec() * spread);
485 end = start + dir * MAX_SHOT_DISTANCE;
486 if(self.antilag_debug)
487 traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
489 traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
493 if ((trace_fraction != 1.0) && (pointcontents (trace_endpos) != CONTENT_SKY))
495 pointparticles(particleeffectnum("TE_KNIGHTSPIKE"),end,trace_plane_normal * 2500,1);
496 if (trace_ent.solid == SOLID_BSP && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
497 Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * force, dtype, self);
498 Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
499 //void(float effectnum, vector org, vector vel, float howmany) pointparticles = #337; // same as in CSQC
505 void W_PrepareExplosionByDamage(entity attacker, void() explode)
507 self.takedamage = DAMAGE_NO;
508 self.event_damage = SUB_Null;
509 self.owner = attacker;
511 // do not explode NOW but in the NEXT FRAME!
512 // because recursive calls to RadiusDamage are not allowed
513 self.nextthink = time;
514 self.think = explode;