// 0: only damage from contents (lava/slime) or exceptions
// 1: only self damage or damage from contents or exceptions
// 2: allow all damage to projectiles normally
+ set g_projectiles_keep_owner 0
set g_projectiles_newton_style 2
// possible values:
// 0: absolute velocity projectiles (like Quake)
set g_balance_laser_primary_damage 25
set g_balance_laser_primary_edgedamage 12.5
set g_balance_laser_primary_force 300
-set g_balance_laser_primary_radius 70
+set g_balance_laser_primary_radius 4000
set g_balance_laser_primary_speed 6000
-set g_balance_laser_primary_spread 0
+set g_balance_laser_primary_spread 0.12
set g_balance_laser_primary_refire 0.7
set g_balance_laser_primary_animtime 0.2
set g_balance_laser_primary_lifetime 5
//lightradiusfade 50
//lightcolor 1 0.9 0.7
//lightshadow 1
- velocityjitter 256 256 256
+
+// laser_shockwave_attack
+// used nowhere in code
+effect laser_shockwave_attack
+// glow and light
+//countabsolute 1
+//type smoke
+//color 0xcc0000 0xff0000
+//tex 65 65
+//size 10 15
+//alpha 256 512 6280
+//airfriction 10
+//sizeincrease 1.5
+//stretchfactor 2
+//lightradius 200
+//lightradiusfade 2000
+//lightcolor 3 0.1 0.1
+// electricity
+effect laser_shockwave_attack
+count 1
+type spark
+color 0xb44215 0xff0000
+tex 43 43
+size 5 7
+bounce 0
+alpha 4096 4096 20000
+airfriction 1
+originjitter 2 2 2
+velocityjitter 10 10 10
+velocitymultiplier 10
+sizeincrease 1.5
+stretchfactor 2.3
+rotate -180 180 4000 -4000
+// fire
+effect laser_shockwave_attack
+count 1
+type spark
+color 0xff4200 0xff0000
+tex 8 15
+size 7 9
+bounce 0
+alpha 4096 4096 20000
+airfriction 1
+originjitter 2 2 2
+velocityjitter 10 10 10
+velocitymultiplier 10
+sizeincrease 1.5
+stretchfactor 2
+
+// new_laser_impact
+// used nowhere in code
+// decal
+effect new_laser_impact
+countabsolute 1
+type decal
+tex 8 16
+size 72 72
+alpha 256 256 0
+originjitter 2 2 2
+// flare effect
+//effect new_laser_impact
+//countabsolute 1
+//type static
+//tex 39 39
+//color 0xFF2010 0xFF2010
+//alpha 256 256 1024
+//size 24 24
+// sparks that rapidly expand and rapidly slow down to form an interesting spherical effect
+effect new_laser_impact
+count 128
+type spark
+color 0x800000 0xFF8020
+alpha 256 256 1024
+size 4 4
+bounce 1.5
+gravity 0.5
+airfriction 1
+liquidfriction 1
+originjitter 20 20 20
++velocityjitter 256 256 256
DamageInfo_Precache();
Vehicles_Precache();
turrets_precache();
- Announcer_Precache();
+ Announcer_Precache();
Tuba_Precache();
if(autocvar_cl_reticle)
cl_notice_read();
bHandled = true;
break;
+ case TE_CSQC_SHOCKWAVEPARTICLE:
+ Net_ReadShockwaveParticle();
+ bHandled = true;
+ break;
default:
// No special logic for this temporary entity; return 0 so the engine can handle it
bHandled = false;
return valstr;
}
+float dotproduct(vector a, vector b)
+{
+ return a_x * b_x + a_y * b_y + a_z * b_z;
+}
+
vector cross(vector a, vector b)
{
return
to_execute_next_frame = strzone(s);
}
+ float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
+ {
+ return
+ ((( startspeedfactor + endspeedfactor - 2
+ ) * x - 2 * startspeedfactor - endspeedfactor + 3
+ ) * x + startspeedfactor
+ ) * x;
+ }
+
+ float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
+ {
+ if(startspeedfactor < 0 || endspeedfactor < 0)
+ return FALSE;
+
+ /*
+ // if this is the case, the possible zeros of the first derivative are outside
+ // 0..1
+ We can calculate this condition as condition
+ if(se <= 3)
+ return TRUE;
+ */
+
+ // better, see below:
+ if(startspeedfactor <= 3 && endspeedfactor <= 3)
+ return TRUE;
+
+ // if this is the case, the first derivative has no zeros at all
+ float se = startspeedfactor + endspeedfactor;
+ float s_e = startspeedfactor - endspeedfactor;
+ if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
+ return TRUE;
+
+ // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
+ // we also get s_e <= 6 - se
+ // 3 * (se - 4)^2 + (6 - se)^2
+ // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
+ // Therefore, above "better" check works!
+
+ return FALSE;
+
+ // known good cases:
+ // (0, [0..3])
+ // (0.5, [0..3.8])
+ // (1, [0..4])
+ // (1.5, [0..3.9])
+ // (2, [0..3.7])
+ // (2.5, [0..3.4])
+ // (3, [0..3])
+ // (3.5, [0.2..2.3])
+ // (4, 1)
+ }
++
+#ifndef MENUQC
+vector cliptoplane(vector v, vector p)
+{
+ return v - (v * p) * p;
+}
+
+vector solve_cubic_pq(float p, float q)
+{
+ float D, u, v, a;
+ D = q*q/4.0 + p*p*p/27.0;
+ if(D < 0)
+ {
+ // irreducibilis
+ a = 1.0/3.0 * acos(-q/2.0 * sqrt(-27.0/(p*p*p)));
+ u = sqrt(-4.0/3.0 * p);
+ // a in range 0..pi/3
+ // cos(a)
+ // cos(a + 2pi/3)
+ // cos(a + 4pi/3)
+ return
+ u *
+ (
+ '1 0 0' * cos(a + 2.0/3.0*M_PI)
+ +
+ '0 1 0' * cos(a + 4.0/3.0*M_PI)
+ +
+ '0 0 1' * cos(a)
+ );
+ }
+ else if(D == 0)
+ {
+ // simple
+ if(p == 0)
+ return '0 0 0';
+ u = 3*q/p;
+ v = -u/2;
+ if(u >= v)
+ return '1 1 0' * v + '0 0 1' * u;
+ else
+ return '0 1 1' * v + '1 0 0' * u;
+ }
+ else
+ {
+ // cardano
+ u = cbrt(-q/2.0 + sqrt(D));
+ v = cbrt(-q/2.0 - sqrt(D));
+ return '1 1 1' * (u + v);
+ }
+}
+vector solve_cubic_abcd(float a, float b, float c, float d)
+{
+ // y = 3*a*x + b
+ // x = (y - b) / 3a
+ float p, q;
+ vector v;
+ p = (9*a*c - 3*b*b);
+ q = (27*a*a*d - 9*a*b*c + 2*b*b*b);
+ v = solve_cubic_pq(p, q);
+ v = (v - b * '1 1 1') * (1.0 / (3.0 * a));
+ if(a < 0)
+ v += '1 0 -1' * (v_z - v_x); // swap x, z
+ return v;
+}
+
+vector findperpendicular(vector v)
+{
+ vector p;
+ p_x = v_z;
+ p_y = -v_x;
+ p_z = v_y;
+ return normalize(cliptoplane(p, v));
+}
+
+vector W_CalculateSpread(vector forward, float spread, float spreadfactor, float spreadstyle)
+{
+ float sigma;
+ vector v1, v2;
+ float dx, dy, r;
+ float sstyle;
+ spread *= spreadfactor; //g_weaponspreadfactor;
+ if(spread <= 0)
+ return forward;
+ sstyle = spreadstyle; //autocvar_g_projectiles_spread_style;
+
+ if(sstyle == 0)
+ {
+ // this is the baseline for the spread value!
+ // standard deviation: sqrt(2/5)
+ // density function: sqrt(1-r^2)
+ return forward + randomvec() * spread;
+ }
+ else if(sstyle == 1)
+ {
+ // same thing, basically
+ return normalize(forward + cliptoplane(randomvec() * spread, forward));
+ }
+ else if(sstyle == 2)
+ {
+ // circle spread... has at sigma=1 a standard deviation of sqrt(1/2)
+ sigma = spread * 0.89442719099991587855; // match baseline stddev
+ v1 = findperpendicular(forward);
+ v2 = cross(forward, v1);
+ // random point on unit circle
+ dx = random() * 2 * M_PI;
+ dy = sin(dx);
+ dx = cos(dx);
+ // radius in our dist function
+ r = random();
+ r = sqrt(r);
+ return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
+ }
+ else if(sstyle == 3) // gauss 3d
+ {
+ sigma = spread * 0.44721359549996; // match baseline stddev
+ // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right
+ v1 = forward;
+ v1_x += gsl_ran_gaussian(sigma);
+ v1_y += gsl_ran_gaussian(sigma);
+ v1_z += gsl_ran_gaussian(sigma);
+ return v1;
+ }
+ else if(sstyle == 4) // gauss 2d
+ {
+ sigma = spread * 0.44721359549996; // match baseline stddev
+ // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right
+ v1_x = gsl_ran_gaussian(sigma);
+ v1_y = gsl_ran_gaussian(sigma);
+ v1_z = gsl_ran_gaussian(sigma);
+ return normalize(forward + cliptoplane(v1, forward));
+ }
+ else if(sstyle == 5) // 1-r
+ {
+ sigma = spread * 1.154700538379252; // match baseline stddev
+ v1 = findperpendicular(forward);
+ v2 = cross(forward, v1);
+ // random point on unit circle
+ dx = random() * 2 * M_PI;
+ dy = sin(dx);
+ dx = cos(dx);
+ // radius in our dist function
+ r = random();
+ r = solve_cubic_abcd(-2, 3, 0, -r) * '0 1 0';
+ return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
+ }
+ else if(sstyle == 6) // 1-r^2
+ {
+ sigma = spread * 1.095445115010332; // match baseline stddev
+ v1 = findperpendicular(forward);
+ v2 = cross(forward, v1);
+ // random point on unit circle
+ dx = random() * 2 * M_PI;
+ dy = sin(dx);
+ dx = cos(dx);
+ // radius in our dist function
+ r = random();
+ r = sqrt(1 - r);
+ r = sqrt(1 - r);
+ return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
+ }
+ else if(sstyle == 7) // (1-r) (2-r)
+ {
+ sigma = spread * 1.224744871391589; // match baseline stddev
+ v1 = findperpendicular(forward);
+ v2 = cross(forward, v1);
+ // random point on unit circle
+ dx = random() * 2 * M_PI;
+ dy = sin(dx);
+ dx = cos(dx);
+ // radius in our dist function
+ r = random();
+ r = 1 - sqrt(r);
+ r = 1 - sqrt(r);
+ return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
+ }
+ else
+ error("g_projectiles_spread_style must be 0 (sphere), 1 (flattened sphere), 2 (circle), 3 (gauss 3D), 4 (gauss plane), 5 (linear falloff), 6 (quadratic falloff), 7 (stronger falloff)!");
+ return '0 0 0';
+ /*
+ * how to derive falloff functions:
+ * rho(r) := (2-r) * (1-r);
+ * a : 0;
+ * b : 1;
+ * rhor(r) := r * rho(r);
+ * cr(t) := integrate(rhor(r), r, a, t);
+ * scr(t) := integrate(rhor(r) * r^2, r, a, t);
+ * variance : scr(b) / cr(b);
+ * solve(cr(r) = rand * cr(b), r), programmmode:false;
+ * sqrt(0.4 / variance), numer;
+ */
+}
+#endif
string ScoreString(float vflags, float value);
+float dotproduct(vector a, vector b);
vector cross(vector a, vector b);
void compressShortVector_init();
// for marking written-to values as unused where it's a good idea to do this
noref float unused_float;
-
-
+ // a function f with:
+ // f(0) = 0
+ // f(1) = 1
+ // f'(0) = startspeedfactor
+ // f'(1) = endspeedfactor
+ float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x);
+
+ // checks whether f'(x) = 0 anywhere from 0 to 1
+ // because if this is the case, the function is not usable for platforms
+ // as it may exceed 0..1 bounds, or go in reverse
+ float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor);
++
+#ifndef MENUQC
+vector W_CalculateSpread(vector forward, float spread, float spreadfactor, float spreadstyle)
+#endif
float autocvar_g_balance_laser_primary_force_other_scale;
float autocvar_g_balance_laser_primary_force_velocitybias;
float autocvar_g_balance_laser_primary_force_zscale;
+var float autocvar_g_balance_laser_primary_jumpradius = 150;
float autocvar_g_balance_laser_primary_lifetime;
float autocvar_g_balance_laser_primary_radius;
float autocvar_g_balance_laser_primary_refire;
float autocvar_g_balance_laser_primary_shotangle;
float autocvar_g_balance_laser_primary_speed;
+var float autocvar_g_balance_laser_primary_spread = 0.15;
float autocvar_g_balance_laser_secondary;
float autocvar_g_balance_laser_secondary_animtime;
float autocvar_g_balance_laser_secondary_damage;
string autocvar_g_playerstats_uri;
float autocvar_g_powerups;
float autocvar_g_projectiles_damage;
+ float autocvar_g_projectiles_keep_owner;
float autocvar_g_projectiles_newton_style;
float autocvar_g_projectiles_newton_style_2_maxfactor;
float autocvar_g_projectiles_newton_style_2_minfactor;
// create a small explosion to throw gibs around (if applicable)
//setorigin (explosion, hitloc);
- //RadiusDamage (explosion, self, 10, 0, 50, world, 300, deathtype);
+ //RadiusDamage (explosion, self, 10, 0, 50, world, world, 300, deathtype);
ent.railgunhitloc = '0 0 0';
ent.railgunhitsolidbackup = SOLID_NOT;
UpdateCSQCProjectile(self);
}
- float W_BallisticBullet_LeaveSolid(entity e, vector vel, float constant)
+ 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;
- maxdist = E0_m / constant;
- // maxdist = 0.5 * v0 * v0 / constant
- // dprint("max dist = ", ftos(maxdist), "\n");
- if(maxdist <= autocvar_g_ballistics_mindistance)
- return 0;
+ if(constant)
+ {
+ maxdist = E0_m / constant;
+ // maxdist = 0.5 * v0 * v0 / constant
+ // dprint("max dist = ", ftos(maxdist), "\n");
- traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self);
+ 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.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;
}
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:
return;
}
- density = other.ballistics_density;
- if(density == 0)
- density = 1;
-
// go through solid!
- if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius * density))
+ if(!W_BallisticBullet_LeaveSolid(-1))
{
remove(self);
return;
proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
proj.angles = vectoangles(proj.velocity);
- proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
+ 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;
W_BallisticBullet_Hit();
}
+ if(proj.dmg_radius < 0) // these NEVER penetrate solid
+ break;
+
// if we hit "weapclip", bail out
//
// rationale of this check:
if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
break;
- density = other.ballistics_density;
- if(density == 0)
- density = 1;
-
// go through solid!
- if(!W_BallisticBullet_LeaveSolid(self, self.velocity, self.dmg_radius * density))
+ if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1))
break;
W_BallisticBullet_LeaveSolid_think();
+
+ self.projectiledeathtype |= HITTYPE_BOUNCE;
}
frametime = savetime;
self = oldself;
self.takedamage = DAMAGE_NO;
self.event_damage = SUB_Null;
- if not(g_ca)
+ if((attacker.flags & FL_CLIENT) && !autocvar_g_projectiles_keep_owner)
{
self.owner = attacker;
self.realowner = attacker;