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)
192 endzcurveparticles();
196 damage_headshotbonus = self.dmg_edge;
197 railgun_start = self.origin - 2 * frametime * self.velocity;
198 railgun_end = self.origin + 2 * frametime * self.velocity;
200 if(other.flags & FL_CLIENT)
201 if(other.deadflag == DEAD_NO)
205 if(other.team == self.owner.team)
208 Damage(other, self, self.owner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
209 damage_headshotbonus = 0;
211 if(self.dmg_edge != 0)
214 announce(self.owner, "announcer/male/headshot.wav");
216 announce(self.owner, "announcer/male/awesome.wav");
219 //calculate hits for ballistic weapons
220 if not(inWarmupStage)
221 if not(self.owner.isbot)
224 self.owner.bullets_hit[self.owner.weapon] += 1;
226 self.owner.damage_hits = self.owner.weapon + 64 * rint(self.owner.bullets_hit[self.owner.weapon]);
229 //sound (self, CHAN_PROJECTILE, "weapons/electro_impact.wav", VOL_BASE, ATTN_NORM);
232 self.enemy = other; // don't hit the same player twice with the same bullet
235 .void(void) W_BallisticBullet_LeaveSolid_think_save;
236 .float W_BallisticBullet_LeaveSolid_nextthink_save;
237 .vector W_BallisticBullet_LeaveSolid_origin;
238 .vector W_BallisticBullet_LeaveSolid_velocity;
240 void W_BallisticBullet_LeaveSolid_think()
242 setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
243 self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
245 self.think = self.W_BallisticBullet_LeaveSolid_think_save;
246 self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
247 self.W_BallisticBullet_LeaveSolid_think_save = SUB_Null;
249 self.flags &~= FL_ONGROUND;
251 if(self.enemy.solid == SOLID_BSP)
254 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
255 Damage_DamageInfo(self.origin, 0, 0, 0, self.dmg_force * normalize(self.velocity) * -f, self.projectiledeathtype, self);
258 UpdateCSQCProjectile(self);
261 // a fake logarithm function
266 if(x > 0.9 && x < 1.1)
268 return 2 * log(sqrt(x));
271 float W_BallisticBullet_LeaveSolid(entity e, vector vel, float constant)
273 // move the entity along its velocity until it's out of solid, then let it resume
275 float dt, dst, velfactor, v0, vs;
279 // outside the world? forget it
280 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)
283 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
286 E0_m = 0.5 * v0 * v0;
287 maxdist = E0_m / constant;
288 // maxdist = 0.5 * v0 * v0 / constant
289 // dprint("max dist = ", ftos(maxdist), "\n");
294 traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self);
296 if(trace_fraction == 1) // 1: we never got out of solid
299 self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
301 dst = vlen(trace_endpos - self.origin);
302 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
303 Es_m = E0_m - constant * dst;
306 // roundoff errors got us
312 dt = dst / (0.5 * (v0 + vs));
313 // this is not correct, but the differential equations have no analytic
314 // solution - and these times are very small anyway
315 //print("dt = ", ftos(dt), "\n");
317 self.W_BallisticBullet_LeaveSolid_think_save = self.think;
318 self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
319 self.think = W_BallisticBullet_LeaveSolid_think;
320 self.nextthink = time + dt;
322 vel = vel * velfactor;
324 self.velocity = '0 0 0';
325 self.flags |= FL_ONGROUND; // prevent moving
326 self.W_BallisticBullet_LeaveSolid_velocity = vel;
331 void W_BallisticBullet_Touch (void)
333 if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
337 W_BallisticBullet_Hit ();
340 if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius))
346 self.projectiledeathtype |= HITTYPE_BOUNCE;
349 void endFireBallisticBullet()
351 endzcurveparticles();
353 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)
355 float lag, dt, savetime;
360 proj.classname = "bullet";
362 PROJECTILE_MAKETRIGGER(proj);
363 if(gravityfactor > 0)
365 proj.movetype = MOVETYPE_TOSS;
366 proj.gravity = gravityfactor;
369 proj.movetype = MOVETYPE_FLY;
370 proj.think = SUB_Remove;
371 proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
372 proj.velocity = (dir + randomvec() * spread) * pSpeed;
373 W_SetupProjectileVelocity(proj);
374 proj.angles = vectoangles(proj.velocity);
375 proj.dmg_radius = cvar("g_ballistics_materialconstant") / bulletconstant;
376 // so: bulletconstant = bullet mass / area of bullet circle
377 setorigin(proj, start);
378 proj.flags = FL_PROJECTILE;
380 proj.touch = W_BallisticBullet_Touch;
382 proj.dmg_edge = headshotbonus;
383 proj.dmg_force = force;
384 proj.projectiledeathtype = dtype;
386 proj.oldvelocity = proj.velocity;
388 //calculate fired bullets for ballistics
389 if not(inWarmupStage)
392 self.bullets_fired[self.weapon] += 1;
393 self.maxdamage_fired = self.weapon + 64 * rint(self.bullets_fired[self.weapon]);
396 if(cvar("g_antilag_bullets"))
397 if(pSpeed >= cvar("g_antilag_bullets"))
399 // NOTE: this may severely throw off weapon balance
400 lag = ANTILAG_LATENCY(self);
403 if(clienttype(self) != CLIENTTYPE_REAL)
405 if(cvar("g_antilag") == 0)
406 lag = 0; // only do hitscan, but no antilag
410 antilag_takeback(pl, time - lag);
415 savetime = frametime;
420 // DP tracetoss is stupid and always traces in 0.05s
421 // ticks. This makes it trace in 0.05*0.125s ticks
427 self.velocity = self.velocity * 0.125;
428 self.gravity *= 0.125 * 0.125;
430 tracetoss(self, oldself);
434 if not(inWarmupStage)
437 self.bullets_fired[self.weapon] += 1;
438 self.maxdamage_fired = self.weapon + 64 * rint(self.bullets_fired[self.weapon]);
441 if(vlen(trace_endpos - self.origin) > 32)
442 zcurveparticles_from_tracetoss(particleeffectnum("tr_bullet"), self.origin, trace_endpos, self.velocity);
443 if(trace_fraction == 1)
445 // won't hit anything anytime soon (DP's
446 // tracetoss does 200 tics of, here,
447 // 0.05*0.125s, that is, 1.25 seconds
450 dt = vlen(trace_endpos - self.origin) / vlen(self.velocity); // this is only approximate!
451 setorigin(self, trace_endpos);
452 self.velocity_z -= sv_gravity * dt;
454 if(!SUB_OwnerCheck())
456 if(SUB_NoImpactCheck())
460 W_BallisticBullet_Hit ();
464 if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius))
467 W_BallisticBullet_LeaveSolid_think();
469 frametime = savetime;
481 if(tracereffects & EF_RED)
482 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
484 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
488 void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
493 dir = normalize(dir + randomvec() * spread);
494 end = start + dir * MAX_SHOT_DISTANCE;
495 if(self.antilag_debug)
496 traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
498 traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
502 if ((trace_fraction != 1.0) && (pointcontents (trace_endpos) != CONTENT_SKY))
504 pointparticles(particleeffectnum("TE_KNIGHTSPIKE"),end,trace_plane_normal * 2500,1);
505 if (trace_ent.solid == SOLID_BSP && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
506 Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * force, dtype, self);
507 Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
508 //void(float effectnum, vector org, vector vel, float howmany) pointparticles = #337; // same as in CSQC
514 void W_PrepareExplosionByDamage(entity attacker, void() explode)
516 self.takedamage = DAMAGE_NO;
517 self.event_damage = SUB_Null;
518 self.owner = attacker;
520 // do not explode NOW but in the NEXT FRAME!
521 // because recursive calls to RadiusDamage are not allowed
522 self.nextthink = time;
523 self.think = explode;