pseudoprojectile = world;
- railgun_start = start;
- railgun_end = end;
-
dir = normalize(end - start);
length = vlen(end - start);
force = dir * bforce;
trace_dphitq3surfaceflags = endq3surfaceflags;
}
-.float dmg_force;
-.float dmg_radius;
-.float dmg_total;
-//.float last_yoda;
-void W_BallisticBullet_Hit (void)
-{
- float f, q, g;
-
- f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
- q = 1 + self.dmg_edge / self.dmg;
-
- if(other.solid == SOLID_BSP || other.solid == SOLID_SLIDEBOX)
- Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * f, self.projectiledeathtype, other.species, self);
-
- if(other && other != self.enemy)
- {
- endzcurveparticles();
-
- yoda = 0;
- railgun_start = self.origin - 2 * frametime * self.velocity;
- railgun_end = self.origin + 2 * frametime * self.velocity;
- g = accuracy_isgooddamage(self.realowner, other);
- Damage(other, self, self.realowner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
-
- /*if(yoda && (time > (self.last_yoda + 5)))
- {
- Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
- self.last_yoda = time;
- }*/
-
- // calculate hits for ballistic weapons
- if(g)
- {
- // do not exceed 100%
- q = min(self.dmg * q, self.dmg_total + f * self.dmg) - self.dmg_total;
- self.dmg_total += f * self.dmg;
- accuracy_add(self.realowner, self.realowner.weapon, 0, q);
- }
- }
-
- self.enemy = other; // don't hit the same player twice with the same bullet
-}
-
-.void(void) W_BallisticBullet_LeaveSolid_think_save;
-.float W_BallisticBullet_LeaveSolid_nextthink_save;
-.vector W_BallisticBullet_LeaveSolid_origin;
-.vector W_BallisticBullet_LeaveSolid_velocity;
-
-void W_BallisticBullet_LeaveSolid_think()
-{
- setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
- self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
-
- self.think = self.W_BallisticBullet_LeaveSolid_think_save;
- self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
- self.W_BallisticBullet_LeaveSolid_think_save = func_null;
-
- self.flags &= ~FL_ONGROUND;
-
- if(self.enemy.solid == SOLID_BSP)
- {
- float f;
- f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
- Damage_DamageInfo(self.origin, 0, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * -f, self.projectiledeathtype, 0, self);
- }
-
- UpdateCSQCProjectile(self);
-}
-
-float W_BallisticBullet_LeaveSolid(float eff)
-{
- // move the entity along its velocity until it's out of solid, then let it resume
- vector vel = self.velocity;
- float dt, dst, velfactor, v0, vs;
- float maxdist;
- float E0_m, Es_m;
- float constant = self.dmg_radius * (other.ballistics_density ? other.ballistics_density : 1);
-
- // outside the world? forget it
- 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)
- return 0;
-
- // special case for zero density and zero bullet constant:
-
- if(self.dmg_radius == 0)
- {
- if(other.ballistics_density < 0)
- constant = 0; // infinite travel distance
- else
- return 0; // no penetration
- }
- else
- {
- if(other.ballistics_density < 0)
- constant = 0; // infinite travel distance
- else if(other.ballistics_density == 0)
- constant = self.dmg_radius;
- else
- constant = self.dmg_radius * other.ballistics_density;
- }
-
- // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
- v0 = vlen(vel);
-
- E0_m = 0.5 * v0 * v0;
-
- if(constant)
- {
- maxdist = E0_m / constant;
- // maxdist = 0.5 * v0 * v0 / constant
- // dprint("max dist = ", ftos(maxdist), "\n");
-
- if(maxdist <= autocvar_g_ballistics_mindistance)
- return 0;
- }
- else
- {
- maxdist = vlen(other.maxs - other.mins) + 1; // any distance, as long as we leave the entity
- }
-
- traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self, TRUE);
- if(trace_fraction == 1) // 1: we never got out of solid
- return 0;
-
- self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
-
- dst = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - self.origin));
- // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
- Es_m = E0_m - constant * dst;
- if(Es_m <= 0)
- {
- // roundoff errors got us
- return 0;
- }
- vs = sqrt(2 * Es_m);
- velfactor = vs / v0;
-
- dt = dst / (0.5 * (v0 + vs));
- // this is not correct, but the differential equations have no analytic
- // solution - and these times are very small anyway
- //print("dt = ", ftos(dt), "\n");
-
- self.W_BallisticBullet_LeaveSolid_think_save = self.think;
- self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
- self.think = W_BallisticBullet_LeaveSolid_think;
- self.nextthink = time + dt;
-
- vel = vel * velfactor;
-
- self.velocity = '0 0 0';
- self.flags |= FL_ONGROUND; // prevent moving
- self.W_BallisticBullet_LeaveSolid_velocity = vel;
-
- if(eff >= 0)
- if(vlen(trace_endpos - self.origin) > 4)
- {
- endzcurveparticles();
- trailparticles(self, eff, self.origin, trace_endpos);
- }
-
- return 1;
-}
-
-void W_BallisticBullet_Touch (void)
-{
- //float density;
-
- if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
- return;
-
- PROJECTILE_TOUCH;
- W_BallisticBullet_Hit ();
-
- if(self.dmg_radius < 0) // these NEVER penetrate solid
- {
- remove(self);
- return;
- }
-
- // if we hit "weapclip", bail out
- //
- // rationale of this check:
- //
- // any shader that is solid, nodraw AND trans is meant to clip weapon
- // shots and players, but has no other effect!
- //
- // if it is not trans, it is caulk and should not have this side effect
- //
- // matching shaders:
- // common/weapclip (intended)
- // common/noimpact (is supposed to eat projectiles, but is erased farther above)
- if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
- if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
- if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
- {
- remove(self);
- return;
- }
-
- // go through solid!
- if(!W_BallisticBullet_LeaveSolid(-1))
- {
- remove(self);
- return;
- }
-
- self.projectiledeathtype |= HITTYPE_BOUNCE;
-}
-
-void endFireBallisticBullet()
-{
- endzcurveparticles();
-}
-
-entity fireBallisticBullet_trace_callback_ent;
float fireBallisticBullet_trace_callback_eff;
void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
{
- if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
- zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
+ if(vlen(hit - start) > 16)
+ trailparticles(world, fireBallisticBullet_trace_callback_eff, start, hit);
WarpZone_trace_forent = world;
- self.owner = world;
}
-void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, float lifetime, float damage, float force, float dtype, float tracereffects, float bulletconstant)
+void fireBullet(vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, float tracereffects)
{
- float lag, dt, savetime; //, density;
- entity pl, oldself;
-
- entity proj;
- proj = spawn();
- proj.classname = "bullet";
- proj.owner = proj.realowner = self;
- PROJECTILE_MAKETRIGGER(proj);
- proj.movetype = MOVETYPE_FLY;
- proj.think = SUB_Remove;
- proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
- W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, TRUE);
- proj.angles = vectoangles(proj.velocity);
- if(bulletconstant > 0)
- proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
- else if(bulletconstant == 0)
- proj.dmg_radius = 0;
- else
- proj.dmg_radius = -1;
- // so: bulletconstant = bullet mass / area of bullet circle
- setorigin(proj, start);
- proj.flags = FL_PROJECTILE;
-
- proj.touch = W_BallisticBullet_Touch;
- proj.dmg = damage;
- proj.dmg_force = force;
- proj.projectiledeathtype = dtype;
-
- proj.oldvelocity = proj.velocity;
+ // TODO antilag takeback
+ vector end;
- other = proj; MUTATOR_CALLHOOK(EditProjectile);
+ dir = normalize(dir + randomvec() * spread);
+ end = start + dir * MAX_SHOT_DISTANCE;
- float eff;
+ entity pl;
+ entity last_hit = world;
+ float solid_penetration_left = 1;
+ float total_damage = 0;
if(tracereffects & EF_RED)
- eff = particleeffectnum("tr_rifle");
+ fireBallisticBullet_trace_callback_eff = particleeffectnum("tr_rifle");
else if(tracereffects & EF_BLUE)
- eff = particleeffectnum("tr_rifle_weak");
+ fireBallisticBullet_trace_callback_eff = particleeffectnum("tr_rifle_weak");
else
- eff = particleeffectnum("tr_bullet");
+ fireBallisticBullet_trace_callback_eff = particleeffectnum("tr_bullet");
- // NOTE: this may severely throw off weapon balance
- lag = ANTILAG_LATENCY(self);
+ float lag = ANTILAG_LATENCY(self);
if(lag < 0.001)
lag = 0;
if (!IS_REAL_CLIENT(self))
lag = 0;
if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
lag = 0; // only do hitscan, but no antilag
-
if(lag)
FOR_EACH_PLAYER(pl)
if(pl != self)
antilag_takeback(pl, time - lag);
- oldself = self;
- self = proj;
-
- savetime = frametime;
- frametime = 0.05;
-
- for(;;)
+ for (;;)
{
- // DP tracetoss is stupid and always traces in 0.05s
- // ticks. This makes it trace in 0.05*0.125s ticks
- // instead.
- vector v0;
- v0 = self.velocity;
- self.velocity = self.velocity * 0.125;
- trace_fraction = 0;
- fireBallisticBullet_trace_callback_ent = self;
- fireBallisticBullet_trace_callback_eff = eff;
- WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
- self.velocity = v0;
-
- if(trace_fraction == 1)
+ // TODO also show effect while tracing
+ WarpZone_TraceBox_ThroughZone(start, '0 0 0', '0 0 0', end, FALSE, self, world, fireBallisticBullet_trace_callback);
+ dir = WarpZone_TransformVelocity(WarpZone_trace_transform, dir);
+ end = WarpZone_TransformOrigin(WarpZone_trace_transform, end);
+ start = trace_endpos;
+ entity hit = trace_ent;
+
+ // When hitting sky, stop.
+ if (pointcontents(start) == CONTENT_SKY)
break;
- // won't hit anything anytime soon (DP's
- // tracetoss does 200 tics of, here,
- // 0.05*0.125s, that is, 1.25 seconds
-
- other = trace_ent;
- dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
- setorigin(self, trace_endpos);
- self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
-
- if(!SUB_OwnerCheck())
- {
- if(SUB_NoImpactCheck())
- break;
- // hit the player
- W_BallisticBullet_Hit();
- }
-
- if(proj.dmg_radius < 0) // these NEVER penetrate solid
+ if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
break;
// if we hit "weapclip", bail out
//
// matching shaders:
// common/weapclip (intended)
- // common/noimpact (is supposed to eat projectiles, but is erased farther above)
+ // common/noimpact (is supposed to eat projectiles, but is erased anyway)
+ float is_weapclip = 0;
if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
+ is_weapclip = 1;
+
+ // Avoid self-damage // FIXME can this happen?
+ if (hit != self)
+ {
+ if(hit.solid == SOLID_BSP || hit.solid == SOLID_SLIDEBOX)
+ Damage_DamageInfo(self.origin, damage * solid_penetration_left, 0, 0, max(1, force) * dir * solid_penetration_left, dtype, hit.species, self);
+
+ if(hit && hit != last_hit)
+ {
+ yoda = 0;
+ float g = accuracy_isgooddamage(self, hit);
+ // FIXME preserve trace stuff
+ Damage(hit, self, self, damage * solid_penetration_left, dtype, self.origin, force * dir * solid_penetration_left);
+ // calculate hits for ballistic weapons
+ if(g)
+ {
+ // do not exceed 100%
+ float added_damage = min(damage - total_damage, damage * solid_penetration_left);
+ total_damage += damage * solid_penetration_left;
+ accuracy_add(self, self.weapon, 0, added_damage);
+ }
+ }
+
+ last_hit = hit; // don't hit the same player twice with the same bullet
+ }
+
+ if (is_weapclip)
break;
// go through solid!
- if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1))
+ // outside the world? forget it
+ if(end_x > world.maxs_x || end_y > world.maxs_y || end_z > world.maxs_z || end_x < world.mins_x || end_y < world.mins_y || end_z < world.mins_z)
+ break;
+
+ float maxdist;
+ if(max_solid_penetration < 0)
break;
+ else if(hit.ballistics_density < -1)
+ break; // -2: no solid penetration, ever
+ else if(hit.ballistics_density < 0)
+ maxdist = vlen(hit.maxs - hit.mins) + 1; // -1: infinite travel distance
+ else if(hit.ballistics_density == 0)
+ maxdist = max_solid_penetration * solid_penetration_left;
+ else
+ maxdist = max_solid_penetration * solid_penetration_left * hit.ballistics_density;
+
+ if(maxdist <= autocvar_g_ballistics_mindistance)
+ break;
+
+ // move the entity along its velocity until it's out of solid, then let it resume
+ traceline_inverted (start, self.origin + dir * maxdist, MOVE_NORMAL, self, TRUE);
+ if(trace_fraction == 1) // 1: we never got out of solid
+ break;
+
+ float dist_taken = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - start));
+ solid_penetration_left *= (dist_taken / maxdist);
+
+ // Only show effect when going through a player (invisible otherwise)
+ if (hit && (hit.solid != SOLID_BSP))
+ if(vlen(trace_endpos - start) > 4)
+ trailparticles(self, fireBallisticBullet_trace_callback_eff, start, trace_endpos);
- W_BallisticBullet_LeaveSolid_think();
+ start = trace_endpos;
- self.projectiledeathtype |= HITTYPE_BOUNCE;
+ if(hit.solid == SOLID_BSP)
+ Damage_DamageInfo(start, 0, 0, 0, max(1, force) * normalize(dir) * -solid_penetration_left, dtype, 0, self);
}
- frametime = savetime;
- self = oldself;
if(lag)
FOR_EACH_PLAYER(pl)
if(pl != self)
antilag_restore(pl);
-
- remove(proj);
-
- return;
}
-void fireBallisticBullet_simple(vector start, vector dir, float spread, float pSpeed, float lifetime, float damage, float force, float dtype, float tracereffects, float bulletconstant)
+// DEPRECATED kill this adaptor once we can
+void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, float lifetime, float damage, float force, float dtype, float tracereffects, float bulletconstant)
{
- vector end;
-
- dir = normalize(dir + randomvec() * spread);
- end = start + dir * MAX_SHOT_DISTANCE;
-
- for (;;)
- {
- // TODO also show effect
- if(self.antilag_debug)
- WarpZone_traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
- else
- WarpZone_traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
-
- if (pointcontents (trace_endpos) != CONTENT_SKY)
- {
- trace_endpos = end;
- return;
- }
-
- // Avoid self-damage
- if (!trace_ent || (trace_ent == self))
- {
- if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
- return;
-
- W_BallisticBullet_Hit(); // FIXME
- }
-
- // if we hit "weapclip", bail out
- //
- // rationale of this check:
- //
- // any shader that is solid, nodraw AND trans is meant to clip weapon
- // shots and players, but has no other effect!
- //
- // if it is not trans, it is caulk and should not have this side effect
- //
- // matching shaders:
- // common/weapclip (intended)
- // common/noimpact (is supposed to eat projectiles, but is erased farther above)
- if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
- if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
- if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
- return;
-
- // go through solid!
- if(!W_BallisticBullet_LeaveSolid((trace_ent && (trace_ent.solid != SOLID_BSP)) ? eff : -1)) // FIXME
- return;
-
- W_BallisticBullet_LeaveSolid_think(); // FIXME
- }
+ fireBullet(start, dir, spread, (0.5 * pSpeed * pSpeed * bulletconstant) / autocvar_g_ballistics_materialconstant, damage, force, dtype, tracereffects);
}
-
-void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
+void endFireBallisticBullet()
{
- vector end;
-
- dir = normalize(dir + randomvec() * spread);
- end = start + dir * MAX_SHOT_DISTANCE;
- if(self.antilag_debug)
- traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
- else
- traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
-
- end = trace_endpos;
-
- if (pointcontents (trace_endpos) != CONTENT_SKY)
- {
- if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
- Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, trace_ent.species, self);
-
- Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
- }
- trace_endpos = end;
}
float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)