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;
26 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype)
28 local vector hitloc, force, endpoint, dir;
29 local entity ent, endent;
30 local float endq3surfaceflags;
35 entity pseudoprojectile;
40 railgun_start = start;
43 dir = normalize(end - start);
44 length = vlen(end - start);
47 // go a little bit into the wall because we need to hit this wall later
50 // trace multiple times until we hit a wall, each obstacle will be made
51 // non-solid so we can hit the next, while doing this we spawn effects and
52 // note down which entities were hit so we can damage them later
55 if(self.antilag_debug)
56 WarpZone_traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
58 WarpZone_traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
60 // if it is world we can't hurt it so stop now
61 if (trace_ent == world || trace_fraction == 1)
64 // make the entity non-solid so we can hit the next one
65 trace_ent.railgunhit = TRUE;
66 trace_ent.railgunhitloc = end;
67 trace_ent.railgunhitsolidbackup = trace_ent.solid;
68 trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
70 // stop if this is a wall
71 if (trace_ent.solid == SOLID_BSP)
74 // make the entity non-solid
75 trace_ent.solid = SOLID_NOT;
78 endpoint = trace_endpos;
80 endq3surfaceflags = trace_dphitq3surfaceflags;
82 // find all the entities the railgun hit and restore their solid state
83 ent = findfloat(world, railgunhit, TRUE);
86 // restore their solid type
87 ent.solid = ent.railgunhitsolidbackup;
88 ent = findfloat(ent, railgunhit, TRUE);
91 // spawn a temporary explosion entity for RadiusDamage calls
92 //explosion = spawn();
94 // Find all non-hit players the beam passed close by
95 if(deathtype == WEP_MINSTANEX || deathtype == WEP_NEX)
97 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
99 // nearest point on the beam
100 beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
102 f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
106 snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav");
108 if(!pseudoprojectile)
109 pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
110 soundtoat(MSG_ONE, pseudoprojectile, beampos, CHAN_PROJECTILE, snd, VOL_BASE * f, ATTN_NONE);
114 remove(pseudoprojectile);
117 // find all the entities the railgun hit and hurt them
118 ent = findfloat(world, railgunhit, TRUE);
121 // get the details we need to call the damage function
122 hitloc = ent.railgunhitloc;
124 //for stats so that team hit will count as a miss
125 if(ent.flags & FL_CLIENT)
126 if(ent.deadflag == DEAD_NO)
130 if(ent.team == self.team)
133 f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
134 ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
138 Damage (ent, self, self, bdamage * f, deathtype, hitloc, force * ffs);
140 // create a small explosion to throw gibs around (if applicable)
141 //setorigin (explosion, hitloc);
142 //RadiusDamage (explosion, self, 10, 0, 50, world, 300, deathtype);
144 ent.railgunhitloc = '0 0 0';
145 ent.railgunhitsolidbackup = SOLID_NOT;
146 ent.railgunhit = FALSE;
147 ent.railgundistance = 0;
149 // advance to the next entity
150 ent = findfloat(ent, railgunhit, TRUE);
153 // calculate hits and fired shots for hitscan
154 if not(inWarmupStage)
156 self.stats_fired[self.weapon - 1] += 1;
157 self.stat_fired = self.weapon + 64 * floor(self.stats_fired[self.weapon - 1]);
160 self.stats_hit[self.weapon - 1] += 1;
161 self.stat_hit = self.weapon + 64 * floor(self.stats_hit[self.weapon - 1]);
165 trace_endpos = endpoint;
167 trace_dphitq3surfaceflags = endq3surfaceflags;
173 void W_BallisticBullet_Hit (void)
177 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
179 if(other.solid == SOLID_BSP)
180 Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * f, self.projectiledeathtype, self);
182 if(other && other != self.enemy)
184 endzcurveparticles();
188 damage_headshotbonus = self.dmg_edge;
189 railgun_start = self.origin - 2 * frametime * self.velocity;
190 railgun_end = self.origin + 2 * frametime * self.velocity;
192 Damage(other, self, self.owner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
193 damage_headshotbonus = 0;
195 if(self.dmg_edge != 0)
198 AnnounceTo(self.owner, "headshot");
200 AnnounceTo(self.owner, "awesome");
203 // calculate hits for ballistic weapons
204 if (other.flags & FL_CLIENT) // is the player a client
205 if (other.deadflag == DEAD_NO) // is the victim a corpse
206 if ((!(teamplay)) | (other.team != self.owner.team)) // not teamplay (ctf, kh, tdm etc) or the victim is in the same team
207 if not(inWarmupStage) // not in warm up stage
209 self.owner.stats_hit[self.owner.weapon - 1] += 1;
210 self.owner.stat_hit = self.owner.weapon + 64 * floor(self.owner.stats_hit[self.owner.weapon - 1]);
214 self.enemy = other; // don't hit the same player twice with the same bullet
217 .void(void) W_BallisticBullet_LeaveSolid_think_save;
218 .float W_BallisticBullet_LeaveSolid_nextthink_save;
219 .vector W_BallisticBullet_LeaveSolid_origin;
220 .vector W_BallisticBullet_LeaveSolid_velocity;
222 void W_BallisticBullet_LeaveSolid_think()
224 setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
225 self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
227 self.think = self.W_BallisticBullet_LeaveSolid_think_save;
228 self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
229 self.W_BallisticBullet_LeaveSolid_think_save = SUB_Null;
231 self.flags &~= FL_ONGROUND;
233 if(self.enemy.solid == SOLID_BSP)
236 f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
237 Damage_DamageInfo(self.origin, 0, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * -f, self.projectiledeathtype, self);
240 UpdateCSQCProjectile(self);
243 float W_BallisticBullet_LeaveSolid(entity e, vector vel, float constant)
245 // move the entity along its velocity until it's out of solid, then let it resume
247 float dt, dst, velfactor, v0, vs;
251 // outside the world? forget it
252 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)
255 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
258 E0_m = 0.5 * v0 * v0;
259 maxdist = E0_m / constant;
260 // maxdist = 0.5 * v0 * v0 / constant
261 // dprint("max dist = ", ftos(maxdist), "\n");
263 if(maxdist <= cvar("g_ballistics_mindistance"))
266 traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self);
268 if(trace_fraction == 1) // 1: we never got out of solid
271 self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
273 dst = max(cvar("g_ballistics_mindistance"), vlen(trace_endpos - self.origin));
274 // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
275 Es_m = E0_m - constant * dst;
278 // roundoff errors got us
284 dt = dst / (0.5 * (v0 + vs));
285 // this is not correct, but the differential equations have no analytic
286 // solution - and these times are very small anyway
287 //print("dt = ", ftos(dt), "\n");
289 self.W_BallisticBullet_LeaveSolid_think_save = self.think;
290 self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
291 self.think = W_BallisticBullet_LeaveSolid_think;
292 self.nextthink = time + dt;
294 vel = vel * velfactor;
296 self.velocity = '0 0 0';
297 self.flags |= FL_ONGROUND; // prevent moving
298 self.W_BallisticBullet_LeaveSolid_velocity = vel;
303 void W_BallisticBullet_Touch (void)
307 if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
311 W_BallisticBullet_Hit ();
313 // if we hit "weapclip", bail out
315 // rationale of this check:
317 // any shader that is solid, nodraw AND trans is meant to clip weapon
318 // shots and players, but has no other effect!
320 // if it is not trans, it is caulk and should not have this side effect
323 // common/weapclip (intended)
324 // common/noimpact (is supposed to eat projectiles, but is erased farther above)
325 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
326 if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
327 if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
333 density = other.ballistics_density;
338 if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius * density))
344 self.projectiledeathtype |= HITTYPE_BOUNCE;
347 void endFireBallisticBullet()
349 endzcurveparticles();
352 entity fireBallisticBullet_trace_callback_ent;
353 float fireBallisticBullet_trace_callback_eff;
354 void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
356 if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
357 zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
360 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)
362 float lag, dt, savetime, density;
366 antilagging = (cvar("g_antilag_bullets") && (pSpeed >= cvar("g_antilag_bullets")));
370 proj.classname = "bullet";
372 PROJECTILE_MAKETRIGGER(proj);
373 if(gravityfactor > 0)
375 proj.movetype = MOVETYPE_TOSS;
376 proj.gravity = gravityfactor;
379 proj.movetype = MOVETYPE_FLY;
380 proj.think = SUB_Remove;
381 proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
382 W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
383 proj.angles = vectoangles(proj.velocity);
384 proj.dmg_radius = cvar("g_ballistics_materialconstant") / bulletconstant;
385 // so: bulletconstant = bullet mass / area of bullet circle
386 setorigin(proj, start);
387 proj.flags = FL_PROJECTILE;
389 proj.touch = W_BallisticBullet_Touch;
391 proj.dmg_edge = headshotbonus;
392 proj.dmg_force = force;
393 proj.projectiledeathtype = dtype;
395 proj.oldvelocity = proj.velocity;
397 other = proj; MUTATOR_CALLHOOK(EditProjectile);
403 if(tracereffects & EF_RED)
404 eff = particleeffectnum("tr_rifle");
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(cvar("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;
427 // update the accuracy stats - increase shots fired by 1
428 if not(inWarmupStage)
430 oldself.stats_fired[oldself.weapon - 1] += 1;
431 oldself.stat_fired = oldself.weapon + 64 * floor(oldself.stats_fired[oldself.weapon - 1]);
436 // DP tracetoss is stupid and always traces in 0.05s
437 // ticks. This makes it trace in 0.05*0.125s ticks
443 self.velocity = self.velocity * 0.125;
444 self.gravity *= 0.125 * 0.125;
446 fireBallisticBullet_trace_callback_ent = self;
447 fireBallisticBullet_trace_callback_eff = eff;
448 WarpZone_TraceToss_ThroughZone(self, oldself, world, fireBallisticBullet_trace_callback);
452 if(trace_fraction == 1)
454 // won't hit anything anytime soon (DP's
455 // tracetoss does 200 tics of, here,
456 // 0.05*0.125s, that is, 1.25 seconds
459 dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
460 setorigin(self, trace_endpos);
461 self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
463 if(!SUB_OwnerCheck())
465 if(SUB_NoImpactCheck())
469 W_BallisticBullet_Hit();
472 density = other.ballistics_density;
477 if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius * density))
480 W_BallisticBullet_LeaveSolid_think();
482 frametime = savetime;
494 // update the accuracy stats
495 if not(inWarmupStage)
497 self.stats_fired[self.weapon - 1] += 1;
498 self.stat_fired = self.weapon + 64 * floor(self.stats_fired[self.weapon - 1]);
501 if(tracereffects & EF_RED)
502 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE);
503 else if(tracereffects & EF_BLUE)
504 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
506 CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
509 void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
513 dir = normalize(dir + randomvec() * spread);
514 end = start + dir * MAX_SHOT_DISTANCE;
515 if(self.antilag_debug)
516 traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
518 traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
522 if ((trace_fraction != 1.0) && (pointcontents (trace_endpos) != CONTENT_SKY))
524 pointparticles(particleeffectnum("TE_KNIGHTSPIKE"),end,trace_plane_normal * 2500,1);
525 if (trace_ent.solid == SOLID_BSP && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
526 Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, self);
527 Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
528 //void(float effectnum, vector org, vector vel, float howmany) pointparticles = #337; // same as in CSQC
533 void W_PrepareExplosionByDamage(entity attacker, void() explode)
535 self.takedamage = DAMAGE_NO;
536 self.event_damage = SUB_Null;
537 self.owner = attacker;
538 self.realowner = attacker;
540 // do not explode NOW but in the NEXT FRAME!
541 // because recursive calls to RadiusDamage are not allowed
542 self.nextthink = time;
543 self.think = explode;