]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/util.qc
support functions: cubic speed function for "whatever use" :P
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / util.qc
index 5e7845bbc4f3c42d80cb06a5e006df8af2330c17..6e534ffc19464a92777b956353b926f48ddd578e 100644 (file)
@@ -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);
 }
@@ -195,21 +208,21 @@ vector colormapPaletteColor(float c, float isPants)
 {
        switch(c)
        {
-               case  0: return '0.800000 0.800000 0.800000';
-               case  1: return '0.600000 0.400000 0.000000';
+               case  0: return '1.000000 1.000000 1.000000';
+               case  1: return '1.000000 0.333333 0.000000';
                case  2: return '0.000000 1.000000 0.501961';
                case  3: return '0.000000 1.000000 0.000000';
                case  4: return '1.000000 0.000000 0.000000';
-               case  5: return '0.000000 0.658824 1.000000';
+               case  5: return '0.000000 0.666667 1.000000';
                case  6: return '0.000000 1.000000 1.000000';
                case  7: return '0.501961 1.000000 0.000000';
                case  8: return '0.501961 0.000000 1.000000';
                case  9: return '1.000000 0.000000 1.000000';
                case 10: return '1.000000 0.000000 0.501961';
-               case 11: return '0.600000 0.600000 0.600000';
+               case 11: return '0.000000 0.000000 1.000000';
                case 12: return '1.000000 1.000000 0.000000';
-               case 13: return '0.000000 0.313725 1.000000';
-               case 14: return '1.000000 0.501961 0.000000';
+               case 13: return '0.000000 0.333333 1.000000';
+               case 14: return '1.000000 0.666667 0.000000';
                case 15:
                        if(isPants)
                                return
@@ -396,27 +409,6 @@ void buf_save(float buf, string pFilename)
        fclose(fh);
 }
 
-string GametypeNameFromType(float g)
-{
-       if      (g == GAME_DEATHMATCH) return "dm";
-       else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
-       else if (g == GAME_DOMINATION) return "dom";
-       else if (g == GAME_CTF) return "ctf";
-       else if (g == GAME_RUNEMATCH) return "rune";
-       else if (g == GAME_LMS) return "lms";
-       else if (g == GAME_ARENA) return "arena";
-       else if (g == GAME_CA) return "ca";
-       else if (g == GAME_KEYHUNT) return "kh";
-       else if (g == GAME_ONSLAUGHT) return "ons";
-       else if (g == GAME_ASSAULT) return "as";
-       else if (g == GAME_RACE) return "rc";
-       else if (g == GAME_NEXBALL) return "nexball";
-       else if (g == GAME_CTS) return "cts";
-       else if (g == GAME_FREEZETAG) return "freezetag";
-       else if (g == GAME_KEEPAWAY) return "ka";
-       return "dm";
-}
-
 string mmsss(float tenths)
 {
        float minutes;
@@ -867,6 +859,8 @@ 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)
        {
@@ -920,6 +914,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);
 }
 
@@ -1430,7 +1426,7 @@ string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_
 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
 {
        string subpattern, subpattern2, subpattern3, subpattern4;
-       subpattern = strcat(",", GametypeNameFromType(gt), ",");
+       subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
        if(tp)
                subpattern2 = ",teams,";
        else
@@ -1439,7 +1435,7 @@ float isGametypeInFilter(float gt, float tp, float ts, string pattern)
                subpattern3 = ",teamspawns,";
        else
                subpattern3 = ",noteamspawns,";
-       if(gt == GAME_RACE || gt == GAME_CTS)
+       if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
                subpattern4 = ",race,";
        else
                subpattern4 = string_null;
@@ -1584,6 +1580,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"))
@@ -1591,6 +1690,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)
@@ -1827,6 +1930,7 @@ float matchacl(string acl, string str)
        while(acl)
        {
                t = car(acl); acl = cdr(acl);
+
                d = 1;
                if(substring(t, 0, 1) == "-")
                {
@@ -1835,10 +1939,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;
@@ -2185,3 +2290,129 @@ 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)
+}