X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fcommon%2Futil.qc;h=afd7f1c5ce25ea2bad16b2d3dda0c58c30d4ddad;hb=e1326a2cccc2d471cd11161ab7eda42dfe4d5d2f;hp=19339a36e99635908c379b21383ceff01d7dc42a;hpb=5bf53ba7af3a48304314d60bc3a4389c9f1d6586;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/common/util.qc b/qcsrc/common/util.qc index 19339a36e..afd7f1c5c 100644 --- a/qcsrc/common/util.qc +++ b/qcsrc/common/util.qc @@ -39,6 +39,16 @@ void wordwrap_sprint(string s, float l) #endif #endif +#ifndef SVQC +string draw_UseSkinFor(string pic) +{ + if(substring(pic, 0, 1) == "/") + return substring(pic, 1, strlen(pic)-1); + else + return strcat(draw_currentSkin, "/", pic); +} +#endif + string unescape(string in) { float i, len; @@ -186,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); } @@ -450,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 @@ -846,30 +864,38 @@ float cvar_settemp(string tmp_cvar, string tmp_value) { float created_saved_value; entity e; - + + created_saved_value = 0; + if not(tmp_cvar || tmp_value) { dprint("Error: Invalid usage of cvar_settemp(string, string); !\n"); - return FALSE; + return 0; } - + + if(!cvar_type(tmp_cvar)) + { + print(sprintf("Error: cvar %s doesn't exist!\n", tmp_cvar)); + return 0; + } + for(e = world; (e = find(e, classname, "saved_cvar_value")); ) 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(tmp_cvar); - e.message = strzone(cvar_string(tmp_cvar)); - created_saved_value = TRUE; - - // an entity for this cvar already exists - :saved - + created_saved_value = -1; // skip creation + + if(created_saved_value != -1) + { + // creating a new entity to keep track of this cvar + e = spawn(); + e.classname = "saved_cvar_value"; + e.netname = strzone(tmp_cvar); + e.message = strzone(cvar_string(tmp_cvar)); + created_saved_value = 1; + } + // update the cvar to the value given cvar_set(tmp_cvar, tmp_value); - + return created_saved_value; } @@ -877,12 +903,18 @@ float cvar_settemp_restore() { float i; entity e; - while((e = find(world, classname, "saved_cvar_value"))) + while((e = find(e, classname, "saved_cvar_value"))) { - cvar_set(e.netname, e.message); - remove(e); + if(cvar_type(e.netname)) + { + cvar_set(e.netname, e.message); + remove(e); + ++i; + } + else + print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname)); } - + return i; } @@ -899,6 +931,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); } @@ -1563,6 +1597,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")) @@ -1570,6 +1707,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) @@ -1806,6 +1947,7 @@ float matchacl(string acl, string str) while(acl) { t = car(acl); acl = cdr(acl); + d = 1; if(substring(t, 0, 1) == "-") { @@ -1814,10 +1956,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; @@ -2164,3 +2307,171 @@ 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) +} + +.float FindConnectedComponent_processing; +void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass) +{ + entity queue_start, queue_end; + + // we build a queue of to-be-processed entities. + // queue_start is the next entity to be checked for neighbors + // queue_end is the last entity added + + if(e.FindConnectedComponent_processing) + error("recursion or broken cleanup"); + + // start with a 1-element queue + queue_start = queue_end = e; + queue_end.fld = world; + queue_end.FindConnectedComponent_processing = 1; + + // for each queued item: + for(; queue_start; queue_start = queue_start.fld) + { + // find all neighbors of queue_start + entity t; + for(t = world; (t = nxt(t, queue_start, pass)); ) + { + if(t.FindConnectedComponent_processing) + continue; + if(iscon(t, queue_start, pass)) + { + // it is connected? ADD IT. It will look for neighbors soon too. + queue_end.fld = t; + queue_end = t; + queue_end.fld = world; + queue_end.FindConnectedComponent_processing = 1; + } + } + } + + // unmark + for(queue_start = e; queue_start; queue_start = queue_start.fld) + queue_start.FindConnectedComponent_processing = 0; +}