Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
calculations.qc
Go to the documentation of this file.
1#include "calculations.qh"
2
3// =============================
4// Explosion Force Calculation
5// =============================
6
7float explosion_calcpush_getmultiplier(vector explosion_v, vector target_v)
8{
9 float a;
10 a = explosion_v * (explosion_v - target_v);
11
12 if(a <= 0)
13 // target is too fast to be hittable by this
14 return 0;
15
16 a /= (explosion_v * explosion_v);
17 // we know we can divide by this, or above a would be == 0
18
19 return a;
20}
21
22#if 0
23vector explosion_calcpush(vector explosion_v, float explosion_m, vector target_v, float target_m, float elasticity)
24{
25 // solution of the equations:
26 // v' = v + a vp // central hit
27 // m*v' + mp*vp' = m*v + mp*vp // conservation of momentum
28 // m*v'^2 + mp*vp'^2 = m*v^2 + mp*vp^2 // conservation of energy (ELASTIC hit)
29 // -> a = 0 // case 1: did not hit
30 // -> a = 2*mp*(vp^2 - vp.v) / ((m+mp) * vp^2) // case 2: did hit
31 // // non-elastic hits are somewhere between these two
32
33 // this would be physically correct, but we don't do that
34 return explosion_v * explosion_calcpush_getmultiplier(explosion_v, target_v) * (
35 (1 + elasticity) * (
36 explosion_m
37 ) / (
38 target_m + explosion_m
39 )
40 ); // note: this factor is at least 0, at most 2
41}
42#endif
43
44// simplified formula, tuned so that if the target has velocity 0, we get exactly the original force
45vector damage_explosion_calcpush(vector explosion_f, vector target_v, float speedfactor)
46{
47 // if below 1, the formulas make no sense (and would cause superjumps)
48 if(speedfactor < 1)
49 return explosion_f;
50
51#if 0
52 float m;
53 // find m so that
54 // speedfactor * (1 + e) * m / (1 + m) == 1
55 m = 1 / ((1 + 0) * speedfactor - 1);
56 vector v;
57 v = explosion_calcpush(explosion_f * speedfactor, m, target_v, 1, 0);
58 // the factor we then get is:
59 // 1
60 LOG_INFOF("MASS: %f\nv: %v -> %v\nENERGY BEFORE == %f + %f = %f\nENERGY AFTER >= %f",
61 m,
62 target_v, target_v + v,
63 target_v * target_v, m * explosion_f * speedfactor * explosion_f * speedfactor, target_v * target_v + m * explosion_f * speedfactor * explosion_f * speedfactor,
64 (target_v + v) * (target_v + v));
65 return v;
66#endif
67 return explosion_f * explosion_calcpush_getmultiplier(explosion_f * speedfactor, target_v);
68}
69
70
71// =========================
72// Shot Spread Calculation
73// =========================
74
76{
77 return v - (v * p) * p;
78}
79
80vector solve_cubic_pq(float p, float q)
81{
82 float D, u, v, a;
83 D = q*q/4.0 + p*p*p/27.0;
84 if(D < 0)
85 {
86 // irreducibilis
87 a = 1.0/3.0 * acos(-q/2.0 * sqrt(-27.0/(p*p*p)));
88 u = sqrt(-4.0/3.0 * p);
89 // a in range 0..pi/3
90 // cos(a)
91 // cos(a + 2pi/3)
92 // cos(a + 4pi/3)
93 return u * vec3(
94 cos(a + 2.0/3.0*M_PI),
95 cos(a + 4.0/3.0*M_PI),
96 cos(a)
97 );
98 }
99 else if(D == 0)
100 {
101 // simple
102 if(p == 0)
103 return '0 0 0';
104 u = 3*q/p;
105 v = -u/2;
106 return (u >= v) ? vec3(v, v, u) : vec3(u, v, v);
107 }
108 else
109 {
110 // cardano
111 //u = cbrt(-q/2.0 + sqrt(D));
112 //v = cbrt(-q/2.0 - sqrt(D));
113 a = cbrt(-q/2.0 + sqrt(D)) + cbrt(-q/2.0 - sqrt(D));
114 return vec3(a, a, a);
115 }
116}
117vector solve_cubic_abcd(float a, float b, float c, float d)
118{
119 // y = 3*a*x + b
120 // x = (y - b) / 3a
121 float p, q;
122 vector v;
123 p = (9*a*c - 3*b*b);
124 q = (27*a*a*d - 9*a*b*c + 2*b*b*b);
125 v = solve_cubic_pq(p, q);
126 v = (v - b * '1 1 1') * (1.0 / (3.0 * a));
127 if(a < 0)
128 v += '1 0 -1' * (v.z - v.x); // swap x, z
129 return v;
130}
131
133{
134 return normalize(cliptoplane(vec3(v.z, -v.x, v.y), v));
135}
136
137#ifdef SVQC
138 int W_GunAlign(entity this, int preferred_align)
139 {
140 if(this.m_gunalign)
141 return this.m_gunalign; // no adjustment needed
142
143 entity own = this.owner;
144
145 if(preferred_align < 1 || preferred_align > 4)
146 preferred_align = 3; // default
147
148 for(int j = 4; j > 1; --j) // > 1 as 1 is just center again
149 {
150 int taken = 0;
151 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
152 {
153 .entity weaponentity = weaponentities[slot];
154 if(own.(weaponentity).m_gunalign == j) // we know it can't be ours thanks to the above check
155 taken |= BIT(j);
156 if(own.(weaponentity).m_gunalign == preferred_align)
157 taken |= BIT(preferred_align);
158 }
159
160 if(!(taken & BIT(preferred_align)))
161 return preferred_align; // prefer the recommended
162 if(!(taken & BIT(j)))
163 return j; // or fall back if it's not available
164 }
165
166 return preferred_align; // return it anyway
167 }
168#else
169 int W_GunAlign(entity this, int preferred_align)
170 {
171 return this.m_gunalign > 0 ? this.m_gunalign : preferred_align;
172 }
173#endif
174
175#if 0
176int W_GetGunAlignment(entity player)
177{
178 int gunalign = STAT(GUNALIGN, player);
179 if(gunalign < 1 || gunalign > 4)
180 gunalign = 3; // default value
181 --gunalign;
182
183 return gunalign;
184}
185#endif
186
187vector W_CalculateSpreadPattern(int pattern, float bias, int counter, int total)
188{
189 vector s = '0 0 0';
190
191 switch (pattern)
192 {
193 default:
194 case 1:
195 {
196 // first is always centered
197 if (counter == 0)
198 return '0 0 0';
199
200 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (total - 1)));
201 s.y = v_forward.x;
202 s.z = v_forward.y;
203 // move outer projectiles randomly towards the center by max <bias> percent radius
204 if (bias)
205 s *= ((1 - bias) + random() * bias);
206
207 return s;
208 }
209 case 2:
210 {
211 // first is always centered
212 if (counter == 0)
213 return '0 0 0';
214
215 // points form a round circle with even distribution
216 // this could be simplified to '0 360 0' * (counter / total)
217 // but it would lose ALL the specific placements
218 // for example with this 2 pellets is center + directly up
219 // while 3 pellets is a horizontal sweep and and 4 is an upside
220 // down Y shape, 5 is a star instead of a pentagram,
221 // 9 is a 3x3 grid and 13 is a circle with center + 3 per quadrant
222 makevectors('0 360 0' * ((0.25 * ((total + 1) % 2)) + (counter / (total - 1))));
223
224 // return transform vector
225 s.y = v_forward.x;
226 s.z = v_forward.y;
227 if (bias)
228 s *= ((1 - bias) + random() * bias);
229
230 //else // return original result normal vector
231 //s = v_forward;
232
233 return s;
234 }
235 }
236}
237
238vector W_CalculateSpread(vector dir, float spread, int spread_style, bool must_normalize)
239{
240 float sigma;
241 vector v1 = '0 0 0', v2;
242 float dx, dy, r;
244 if (spread <= 0)
245 return (must_normalize ? normalize(dir) : dir);
246
247 switch (spread_style)
248 {
249 case 0:
250 // this is the baseline for the spread value!
251 // standard deviation: sqrt(2/5)
252 // density function: sqrt(1-r^2)
253 v1 = dir + randomvec() * spread;
254 return (must_normalize ? normalize(v1) : v1);
255 case 1:
256 // same thing, basically
257 return normalize(dir + cliptoplane(randomvec() * spread, dir));
258 case 2:
259 // circle spread... has at sigma=1 a standard deviation of sqrt(1/2)
260 sigma = spread * 0.89442719099991587855; // match baseline stddev
262 v2 = cross(dir, v1);
263 // random point on unit circle
264 dx = random() * 2 * M_PI;
265 dy = sin(dx);
266 dx = cos(dx);
267 // radius in our dist function
268 r = random();
269 r = sqrt(r);
270 return normalize(dir + (v1 * dx + v2 * dy) * r * sigma);
271 case 3: // gauss 3d
272 sigma = spread * 0.44721359549996; // match baseline stddev
273 // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right
274 v1.x = gsl_ran_ugaussian() * sigma;
275 v1.y = gsl_ran_ugaussian() * sigma;
276 v1.z = gsl_ran_ugaussian() * sigma;
277 if (vlen2(v1) > W_SPREAD_GAUSS_MAX_STDEV ** 2)
279 v2 = dir + v1;
280 return (must_normalize ? normalize(v2) : v2);
281 case 4: // gauss 2d
282 sigma = spread * 0.44721359549996; // match baseline stddev
283 // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right
284 v1.x = gsl_ran_ugaussian() * sigma;
285 v1.y = gsl_ran_ugaussian() * sigma;
286 v1.z = gsl_ran_ugaussian() * sigma;
287 if (vlen2(v1) > W_SPREAD_GAUSS_MAX_STDEV ** 2)
289 return normalize(dir + cliptoplane(v1, dir));
290 case 5: // 1-r
291 sigma = spread * 1.154700538379252; // match baseline stddev
293 v2 = cross(dir, v1);
294 // random point on unit circle
295 dx = random() * 2 * M_PI;
296 dy = sin(dx);
297 dx = cos(dx);
298 // radius in our dist function
299 r = random();
300 r = solve_cubic_abcd(-2, 3, 0, -r) * '0 1 0';
301 return normalize(dir + (v1 * dx + v2 * dy) * r * sigma);
302 case 6: // 1-r^2
303 sigma = spread * 1.095445115010332; // match baseline stddev
305 v2 = cross(dir, v1);
306 // random point on unit circle
307 dx = random() * 2 * M_PI;
308 dy = sin(dx);
309 dx = cos(dx);
310 // radius in our dist function
311 r = random();
312 r = sqrt(1 - r);
313 r = sqrt(1 - r);
314 return normalize(dir + (v1 * dx + v2 * dy) * r * sigma);
315 case 7: // (1-r) (2-r)
316 sigma = spread * 1.224744871391589; // match baseline stddev
318 v2 = cross(dir, v1);
319 // random point on unit circle
320 dx = random() * 2 * M_PI;
321 dy = sin(dx);
322 dx = cos(dx);
323 // radius in our dist function
324 r = random();
325 r = 1 - sqrt(r);
326 r = 1 - sqrt(r);
327 return normalize(dir + (v1 * dx + v2 * dy) * r * sigma);
328 default:
329 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)!");
330 }
331
332 return '0 0 0';
333 /*
334 * how to derive falloff functions:
335 * rho(r) := (2-r) * (1-r);
336 * a : 0;
337 * b : 1;
338 * rhor(r) := r * rho(r);
339 * cr(t) := integrate(rhor(r), r, a, t);
340 * scr(t) := integrate(rhor(r) * r^2, r, a, t);
341 * variance : scr(b) / cr(b);
342 * solve(cr(r) = rand * cr(b), r), programmmode:false;
343 * sqrt(0.4 / variance), numer;
344 */
345}
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition bits.qh:8
vector cliptoplane(vector v, vector p)
vector solve_cubic_pq(float p, float q)
vector W_CalculateSpreadPattern(int pattern, float bias, int counter, int total)
int W_GunAlign(entity this, int preferred_align)
float explosion_calcpush_getmultiplier(vector explosion_v, vector target_v)
vector findperpendicular(vector v)
vector damage_explosion_calcpush(vector explosion_f, vector target_v, float speedfactor)
vector solve_cubic_abcd(float a, float b, float c, float d)
vector W_CalculateSpread(vector dir, float spread, int spread_style, bool must_normalize)
float autocvar_g_weaponspreadfactor
const float W_SPREAD_GAUSS_MAX_STDEV
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
entity owner
Definition main.qh:87
vector v_forward
#define STAT(...)
Definition stats.qh:82
#define LOG_INFOF(...)
Definition log.qh:66
float cbrt(float e)
Definition mathlib.qc:132
#define M_PI
Definition mathlib.qh:108
float cos(float f)
float random(void)
float sqrt(float f)
vector randomvec(void)
float sin(float f)
vector normalize(vector v)
#define makevectors
Definition post.qh:21
#define error
Definition pre.qh:6
ERASEABLE float gsl_ran_ugaussian()
Returns a Gaussian random variate, with mean zero and standard deviation sigma 1 From the GNU Scienti...
Definition random.qc:87
vector
Definition self.qh:92
int dir
Definition impulse.qc:89
#define vlen2(v)
Definition vector.qh:4
#define cross(a, b)
Definition vector.qh:25
#define vec3(_x, _y, _z)
Definition vector.qh:95
int m_gunalign
Definition all.qh:385
const int MAX_WEAPONSLOTS
Definition weapon.qh:16
entity weaponentities[MAX_WEAPONSLOTS]
Definition weapon.qh:17