X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=blobdiff_plain;f=qcsrc%2Fcommon%2Futil.qc;h=f99312b5b2eae92a96b025d1e9d8ecc15a8dbcd5;hp=e698a28bd8a4caff798f7767820924ffd8c9bd85;hb=cfdec7de4f6fff90a2142be820eaeb43a5f7f572;hpb=b6116d15c9345367b982b1d9dd13d726aff03925 diff --git a/qcsrc/common/util.qc b/qcsrc/common/util.qc index e698a28bd8..f99312b5b2 100644 --- a/qcsrc/common/util.qc +++ b/qcsrc/common/util.qc @@ -196,6 +196,9 @@ float median(float a, float b, float c) // works for up to 10 decimals! string ftos_decimals(float number, float decimals) { + // inhibit stupid negative zero + if(number == 0) + number = 0; // we have sprintf... return sprintf("%.*f", decimals, number); } @@ -460,6 +463,11 @@ string ScoreString(float pFlags, float pValue) 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 @@ -852,28 +860,50 @@ void get_mi_min_max_texcoords(float mode) } #endif -void cvar_settemp(string cv, string val) +float cvar_settemp(string tmp_cvar, string tmp_value) { + float created_saved_value; entity e; + + created_saved_value = FALSE; + + if not(tmp_cvar || tmp_value) + { + dprint("Error: Invalid usage of cvar_settemp(string, string); !\n"); + return FALSE; + } + for(e = world; (e = find(e, classname, "saved_cvar_value")); ) - if(e.netname == cv) - goto saved; + if(e.netname == tmp_cvar) + goto saved; // skip creation + + // creating a new entity to keep track of this cvar e = spawn(); e.classname = "saved_cvar_value"; - e.netname = strzone(cv); - e.message = strzone(cvar_string(cv)); -:saved - cvar_set(cv, val); + e.netname = strzone(tmp_cvar); + e.message = strzone(cvar_string(tmp_cvar)); + created_saved_value = TRUE; + + // an entity for this cvar already exists + :saved + + // update the cvar to the value given + cvar_set(tmp_cvar, tmp_value); + + return created_saved_value; } -void cvar_settemp_restore() +float cvar_settemp_restore() { + float i; entity e; while((e = find(world, classname, "saved_cvar_value"))) { cvar_set(e.netname, e.message); remove(e); } + + return i; } float almost_equals(float a, float b) @@ -889,6 +919,8 @@ float almost_in_bounds(float a, float b, float c) { float eps; eps = (max(a, -a) + max(c, -c)) * 0.001; + if(a > c) + eps = -eps; return b == median(a - eps, b, c + eps); } @@ -1553,6 +1585,109 @@ vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0 return v; } +vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style) +{ + vector ret; + + // make origin and speed relative + eorg -= myorg; + if(newton_style) + evel -= myvel; + + // now solve for ret, ret normalized: + // eorg + t * evel == t * ret * spd + // or, rather, solve for t: + // |eorg + t * evel| == t * spd + // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2 + // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0 + vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg); + // p = 2 * (eorg * evel) / (evel * evel - spd * spd) + // q = (eorg * eorg) / (evel * evel - spd * spd) + if(!solution_z) // no real solution + { + // happens if D < 0 + // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2 + // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2 + // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2 + // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg)) + // spd^2 < evel^2 * sin^2 angle(evel, eorg) + // spd < |evel| * sin angle(evel, eorg) + return '0 0 0'; + } + else if(solution_x > 0) + { + // both solutions > 0: take the smaller one + // happens if p < 0 and q > 0 + ret = normalize(eorg + solution_x * evel); + } + else if(solution_y > 0) + { + // one solution > 0: take the larger one + // happens if q < 0 or q == 0 and p < 0 + ret = normalize(eorg + solution_y * evel); + } + else + { + // no solution > 0: reject + // happens if p > 0 and q >= 0 + // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0 + // (eorg * eorg) / (evel * evel - spd * spd) >= 0 + // + // |evel| >= spd + // eorg * evel > 0 + // + // "Enemy is moving away from me at more than spd" + return '0 0 0'; + } + + // NOTE: we always got a solution if spd > |evel| + + if(newton_style == 2) + ret = normalize(ret * spd + myvel); + + return ret; +} + +vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma) +{ + if(!newton_style) + return spd * mydir; + + if(newton_style == 2) + { + // true Newtonian projectiles with automatic aim adjustment + // + // solve: |outspeed * mydir - myvel| = spd + // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0 + // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2) + // PLUS SIGN! + // not defined? + // then... + // myvel^2 - (mydir * myvel)^2 > spd^2 + // velocity without mydir component > spd + // fire at smallest possible spd that works? + // |(mydir * myvel) * myvel - myvel| = spd + + vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd); + + float outspeed; + if(solution_z) + outspeed = solution_y; // the larger one + else + { + //outspeed = 0; // slowest possible shot + outspeed = solution_x; // the real part (that is, the average!) + //dprint("impossible shot, adjusting\n"); + } + + outspeed = bound(spd * mi, outspeed, spd * ma); + return mydir * outspeed; + } + + // real Newtonian + return myvel + spd * mydir; +} + void check_unacceptable_compiler_bugs() { if(cvar("_allow_unacceptable_compiler_bugs")) @@ -1560,6 +1695,10 @@ void check_unacceptable_compiler_bugs() tokenize_console("foo bar"); if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar") error("fteqcc bug introduced with revision 3178 detected. Please upgrade fteqcc to a later revision, downgrade fteqcc to revision 3177, or pester Spike until he fixes it. You can set _allow_unacceptable_compiler_bugs 1 to skip this check, but expect stuff to be horribly broken then."); + + string s = ""; + if not(s) + error("The empty string counts as false. We do not want that!"); } float compressShotOrigin(vector v) @@ -1796,6 +1935,7 @@ float matchacl(string acl, string str) while(acl) { t = car(acl); acl = cdr(acl); + d = 1; if(substring(t, 0, 1) == "-") { @@ -1804,10 +1944,11 @@ float matchacl(string acl, string str) } else if(substring(t, 0, 1) == "+") t = substring(t, 1, strlen(t) - 1); + if(substring(t, -1, 1) == "*") { t = substring(t, 0, strlen(t) - 1); - s = substring(s, 0, strlen(t)); + s = substring(str, 0, strlen(t)); } else s = str; @@ -2081,6 +2222,46 @@ float lowestbit(float f) return f; } +/* +string strlimitedlen(string input, string truncation, float strip_colors, float limit) +{ + if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit) + return input; + else + return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation); +}*/ + +// escape the string to make it safe for consoles +string MakeConsoleSafe(string input) +{ + input = strreplace("\n", "", input); + input = strreplace("\\", "\\\\", input); + input = strreplace("$", "$$", input); + input = strreplace("\"", "\\\"", input); + return input; +} + +#ifndef MENUQC +// get true/false value of a string with multiple different inputs +float InterpretBoolean(string input) +{ + switch(strtolower(input)) + { + case "yes": + case "true": + case "on": + return TRUE; + + case "no": + case "false": + case "off": + return FALSE; + + default: return stof(input); + } +} +#endif + #ifdef CSQC entity ReadCSQCEntity() { @@ -2114,3 +2295,321 @@ void m_shutdown() } cvar_settemp_restore(); // this must be done LAST, but in any case } + +#define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05 +#define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT) +#define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT) +// this will use the value: +// 128 +// accuracy near zero is APPROXPASTTIME_MAX/(256*255) +// accuracy at x is 1/derivative, i.e. +// APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536 +#ifdef SVQC +void WriteApproxPastTime(float dst, float t) +{ + float dt = time - t; + + // warning: this is approximate; do not resend when you don't have to! + // be careful with sendflags here! + // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75 + + // map to range... + dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt)); + + // round... + dt = rint(bound(0, dt, 255)); + + WriteByte(dst, dt); +} +#endif +#ifdef CSQC +float ReadApproxPastTime() +{ + float dt = ReadByte(); + + // map from range...PPROXPASTTIME_MAX / 256 + dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt)); + + return servertime - dt; +} +#endif + +#ifndef MENUQC +.float skeleton_bones_index; +void Skeleton_SetBones(entity e) +{ + // set skeleton_bones to the total number of bones on the model + if(e.skeleton_bones_index == e.modelindex) + return; // same model, nothing to update + + float skelindex; + skelindex = skel_create(e.modelindex); + e.skeleton_bones = skel_get_numbones(skelindex); + skel_delete(skelindex); + e.skeleton_bones_index = e.modelindex; +} +#endif + +string to_execute_next_frame; +void execute_next_frame() +{ + if(to_execute_next_frame) + { + localcmd("\n", to_execute_next_frame, "\n"); + strunzone(to_execute_next_frame); + to_execute_next_frame = string_null; + } +} +void queue_to_execute_next_frame(string s) +{ + if(to_execute_next_frame) + { + s = strcat(s, "\n", to_execute_next_frame); + strunzone(to_execute_next_frame); + } + 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