]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/util.qc
Merge remote-tracking branch 'origin/terencehill/color_picker_carets'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / util.qc
index f9577c6fedcb0ef410ef3946d0770d246f761778..15863f369f4d0da86e849d93d5ee0b77b609fde8 100644 (file)
@@ -148,7 +148,7 @@ void wordwrap_cb(string s, float l, void(string) callback)
 float dist_point_line(vector p, vector l0, vector ldir)
 {
        ldir = normalize(ldir);
-       
+
        // remove the component in line direction
        p = p - (p * ldir) * ldir;
 
@@ -196,11 +196,13 @@ 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);
 }
 
-float time;
 vector colormapPaletteColor(float c, float isPants)
 {
        switch(c)
@@ -239,7 +241,7 @@ vector colormapPaletteColor(float c, float isPants)
 string fstrunzone(string s)
 {
        string sc;
-       if not(s)
+       if (!s)
                return s;
        sc = strcat(s, "");
        strunzone(s);
@@ -262,7 +264,7 @@ void db_save(float db, string pFilename)
 {
        float fh, i, n;
        fh = fopen(pFilename, FILE_WRITE);
-       if(fh < 0) 
+       if(fh < 0)
        {
                print(strcat("^1Can't write DB to ", pFilename));
                return;
@@ -456,10 +458,15 @@ string ScoreString(float pFlags, float pValue)
                valstr = TIME_ENCODED_TOSTRING(pValue);
        else
                valstr = ftos(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
@@ -657,7 +664,7 @@ string fixPriorityList(string order, float from, float to, float subtract, float
                                neworder = strcat(neworder, ftos(w), " ");
                }
        }
-       
+
        return substring(neworder, 0, strlen(neworder) - 1);
 }
 
@@ -670,7 +677,7 @@ string mapPriorityList(string order, string(string) mapfunc)
        neworder = "";
        for(i = 0; i < n; ++i)
                neworder = strcat(neworder, mapfunc(argv(i)), " ");
-       
+
        return substring(neworder, 0, strlen(neworder) - 1);
 }
 
@@ -695,7 +702,7 @@ string swapInPriorityList(string order, float i, float j)
                }
                return substring(s, 0, strlen(s) - 1);
        }
-       
+
        return order;
 }
 
@@ -856,43 +863,57 @@ float cvar_settemp(string tmp_cvar, string tmp_value)
 {
        float created_saved_value;
        entity e;
-       
-       if not(tmp_cvar || tmp_value)
+
+       created_saved_value = 0;
+
+       if (!(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;
 }
 
 float cvar_settemp_restore()
 {
-       float i;
-       entity e;
-       while((e = find(world, classname, "saved_cvar_value")))
+       float i = 0;
+       entity e = world;
+       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;
 }
 
@@ -909,6 +930,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);
 }
 
@@ -1077,7 +1100,7 @@ vector rgb_to_hsv(vector rgb)
                hsv_y = 0;
        else
                hsv_y = 1 - mi/ma;
-       
+
        return hsv;
 }
 
@@ -1095,7 +1118,7 @@ vector rgb_to_hsl(vector rgb)
        ma = max(rgb_x, rgb_y, rgb_z);
 
        hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
-       
+
        hsl_z = 0.5 * (mi + ma);
        if(mi == ma)
                hsl_y = 0;
@@ -1103,7 +1126,7 @@ vector rgb_to_hsl(vector rgb)
                hsl_y = (ma - mi) / (2*hsl_z);
        else // if(hsl_z > 0.5)
                hsl_y = (ma - mi) / (2 - 2*hsl_z);
-       
+
        return hsl;
 }
 
@@ -1115,7 +1138,7 @@ vector hsl_to_rgb(vector hsl)
                maminusmi = hsl_y * 2 * hsl_z;
        else
                maminusmi = hsl_y * (2 - 2 * hsl_z);
-       
+
        // hsl_z     = 0.5 * mi + 0.5 * ma
        // maminusmi =     - mi +       ma
        mi = hsl_z - 0.5 * maminusmi;
@@ -1176,7 +1199,7 @@ float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLe
                // terminate, as the range still halves each time - but nevertheless, it is
                // guaranteed that it finds ONE valid cutoff place (where "left" is in
                // range, and "right" is outside).
-               
+
                // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
                // and decrease left on the basis of the chars detected of the truncated tag
                // Even if the ^xrgb tag is not complete/correct, left is decreased
@@ -1204,7 +1227,7 @@ float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLe
                                }
                        }
        }
-       
+
        return left;
 }
 
@@ -1240,7 +1263,7 @@ float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_
                // terminate, as the range still halves each time - but nevertheless, it is
                // guaranteed that it finds ONE valid cutoff place (where "left" is in
                // range, and "right" is outside).
-               
+
                // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
                // and decrease left on the basis of the chars detected of the truncated tag
                // Even if the ^xrgb tag is not complete/correct, left is decreased
@@ -1268,7 +1291,7 @@ float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_
                                }
                        }
        }
-       
+
        return left;
 }
 
@@ -1315,7 +1338,7 @@ string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunc
        string s;
 
        s = getWrappedLine_remaining;
-       
+
        if(w <= 0)
        {
                getWrappedLine_remaining = string_null;
@@ -1361,7 +1384,7 @@ string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
        string s;
 
        s = getWrappedLine_remaining;
-       
+
        if(w <= 0)
        {
                getWrappedLine_remaining = string_null;
@@ -1452,8 +1475,12 @@ float isGametypeInFilter(float gt, float tp, float ts, string pattern)
                if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
                if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
                if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
-               if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
-                       return 0;
+               {
+                       if (!subpattern4)
+                               return 0;
+                       if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
+                               return 0;
+               }
        }
        return 1;
 }
@@ -1573,6 +1600,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"))
@@ -1582,7 +1712,7 @@ void check_unacceptable_compiler_bugs()
                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)
+       if (!s)
                error("The empty string counts as false. We do not want that!");
 }
 
@@ -1701,10 +1831,13 @@ void RandomSelection_Add(entity e, float f, string s, float weight, float priori
        }
 }
 
-vector healtharmor_maxdamage(float h, float a, float armorblock)
+#ifndef MENUQC
+vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
 {
        // NOTE: we'll always choose the SMALLER value...
        float healthdamage, armordamage, armorideal;
+       if (deathtype == DEATH_DROWN)  // Why should armor help here...
+               armorblock = 0;
        vector v;
        healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
        armordamage = a + (h - 1); // damage we can take if we could use more armor
@@ -1723,14 +1856,17 @@ vector healtharmor_maxdamage(float h, float a, float armorblock)
        return v;
 }
 
-vector healtharmor_applydamage(float a, float armorblock, float damage)
+vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
 {
        vector v;
+       if (deathtype == DEATH_DROWN)  // Why should armor help here...
+               armorblock = 0;
        v_y = bound(0, damage * armorblock, a); // save
        v_x = bound(0, damage - v_y, damage); // take
        v_z = 0;
        return v;
 }
+#endif
 
 string getcurrentmod()
 {
@@ -1753,6 +1889,22 @@ float ReadInt24_t()
        v += ReadByte(); // note: this is unsigned
        return v;
 }
+vector ReadInt48_t()
+{
+       vector v;
+       v_x = ReadInt24_t();
+       v_y = ReadInt24_t();
+       v_z = 0;
+       return v;
+}
+vector ReadInt72_t()
+{
+       vector v;
+       v_x = ReadInt24_t();
+       v_y = ReadInt24_t();
+       v_z = ReadInt24_t();
+       return v;
+}
 #else
 void WriteInt24_t(float dst, float val)
 {
@@ -1760,6 +1912,17 @@ void WriteInt24_t(float dst, float val)
        WriteShort(dst, (v = floor(val / 256)));
        WriteByte(dst, val - v * 256); // 0..255
 }
+void WriteInt48_t(float dst, vector val)
+{
+       WriteInt24_t(dst, val_x);
+       WriteInt24_t(dst, val_y);
+}
+void WriteInt72_t(float dst, vector val)
+{
+       WriteInt24_t(dst, val_x);
+       WriteInt24_t(dst, val_y);
+       WriteInt24_t(dst, val_z);
+}
 #endif
 #endif
 
@@ -1820,6 +1983,7 @@ float matchacl(string acl, string str)
        while(acl)
        {
                t = car(acl); acl = cdr(acl);
+
                d = 1;
                if(substring(t, 0, 1) == "-")
                {
@@ -1828,10 +1992,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;
@@ -1868,7 +2033,7 @@ string get_model_datafilename(string m, float sk, string fil)
 float get_model_parameters(string m, float sk)
 {
        string fn, s, c;
-       float fh;
+       float fh, i;
 
        get_model_parameters_modelname = string_null;
        get_model_parameters_modelskin = -1;
@@ -1878,9 +2043,21 @@ float get_model_parameters(string m, float sk)
        get_model_parameters_weight = -1;
        get_model_parameters_age = -1;
        get_model_parameters_desc = string_null;
+       get_model_parameters_bone_upperbody = string_null;
+       get_model_parameters_bone_weapon = string_null;
+       for(i = 0; i < MAX_AIM_BONES; ++i)
+       {
+               get_model_parameters_bone_aim[i] = string_null;
+               get_model_parameters_bone_aimweight[i] = 0;
+       }
+       get_model_parameters_fixbone = 0;
 
-       if not(m)
+       if (!m)
                return 1;
+
+       if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
+               m = strcat(substring(m, 0, -10), substring(m, -4, -1));
+
        if(sk < 0)
        {
                if(substring(m, -4, -1) != ".txt")
@@ -1929,6 +2106,18 @@ float get_model_parameters(string m, float sk)
                        get_model_parameters_weight = stof(s);
                if(c == "age")
                        get_model_parameters_age = stof(s);
+               if(c == "bone_upperbody")
+                       get_model_parameters_bone_upperbody = s;
+               if(c == "bone_weapon")
+                       get_model_parameters_bone_weapon = s;
+               for(i = 0; i < MAX_AIM_BONES; ++i)
+                       if(c == strcat("bone_aim", ftos(i)))
+                       {
+                               get_model_parameters_bone_aimweight[i] = stof(car(s));
+                               get_model_parameters_bone_aim[i] = cdr(s);
+                       }
+               if(c == "fixbone")
+                       get_model_parameters_fixbone = stof(s);
        }
 
        while((s = fgets(fh)))
@@ -2097,11 +2286,11 @@ float xdecode(string s)
 
 float lowestbit(float f)
 {
-       f &~= f * 2;
-       f &~= f * 4;
-       f &~= f * 16;
-       f &~= f * 256;
-       f &~= f * 65536;
+       f &= ~(f * 2);
+       f &= ~(f * 4);
+       f &= ~(f * 16);
+       f &= ~(f * 256);
+       f &= ~(f * 65536);
        return f;
 }
 
@@ -2134,12 +2323,12 @@ float InterpretBoolean(string input)
                case "true":
                case "on":
                        return TRUE;
-               
+
                case "no":
                case "false":
                case "off":
                        return FALSE;
-               
+
                default: return stof(input);
        }
 }
@@ -2252,3 +2441,314 @@ void queue_to_execute_next_frame(string s)
        }
        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)
+
+       /*
+          On another note:
+          inflection point is always at (2s + e - 3) / (3s + 3e - 6).
+
+          s + e - 2 == 0: no inflection
+
+          s + e > 2:
+          0 < inflection < 1 if:
+          0 < 2s + e - 3 < 3s + 3e - 6
+          2s + e > 3 and 2e + s > 3
+
+          s + e < 2:
+          0 < inflection < 1 if:
+          0 > 2s + e - 3 > 3s + 3e - 6
+          2s + e < 3 and 2e + s < 3
+
+          Therefore: there is an inflection point iff:
+          e outside (3 - s)/2 .. 3 - s*2
+
+          in other words, if (s,e) in triangle (1,1)(0,3)(0,1.5) or in triangle (1,1)(3,0)(1.5,0)
+       */
+}
+
+.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;
+}
+
+// todo: this sucks, lets find a better way to do backtraces?
+#ifndef MENUQC
+void backtrace(string msg)
+{
+       float dev, war;
+       #ifdef SVQC
+       dev = autocvar_developer;
+       war = autocvar_prvm_backtraceforwarnings;
+       #else
+       dev = cvar("developer");
+       war = cvar("prvm_backtraceforwarnings");
+       #endif
+       cvar_set("developer", "1");
+       cvar_set("prvm_backtraceforwarnings", "1");
+       print("\n");
+       print("--- CUT HERE ---\nWARNING: ");
+       print(msg);
+       print("\n");
+       remove(world); // isn't there any better way to cause a backtrace?
+       print("\n--- CUT UNTIL HERE ---\n");
+       cvar_set("developer", ftos(dev));
+       cvar_set("prvm_backtraceforwarnings", ftos(war));
+}
+#endif
+
+// color code replace, place inside of sprintf and parse the string
+string CCR(string input)
+{
+       // See the autocvar declarations in util.qh for default values
+
+       // foreground/normal colors
+       input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
+       input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
+       input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
+       input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
+
+       // "kill" colors
+       input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
+       input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
+       input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
+
+       // background colors
+       input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
+       input = strreplace("^N", "^7", input); // "none"-- reset to white...
+       return input;
+}
+
+vector vec3(float x, float y, float z)
+{
+       vector v;
+       v_x = x;
+       v_y = y;
+       v_z = z;
+       return v;
+}
+
+#ifndef MENUQC
+vector animfixfps(entity e, vector a, vector b)
+{
+       // multi-frame anim: keep as-is
+       if(a_y == 1)
+       {
+               float dur;
+               dur = frameduration(e.modelindex, a_x);
+               if(dur <= 0 && b_y)
+               {
+                       a = b;
+                       dur = frameduration(e.modelindex, a_x);
+               }
+               if(dur > 0)
+                       a_z = 1.0 / dur;
+       }
+       return a;
+}
+#endif
+
+#ifdef SVQC
+void dedicated_print(string input) // print(), but only print if the server is not local
+{
+       if(server_is_dedicated) { print(input); }
+}
+#endif
+
+#ifndef MENUQC
+float Announcer_PickNumber(float type, float num)
+{
+       switch(type)
+       {
+               case CNT_GAMESTART:
+               {
+                       switch(num)
+                       {
+                               case 10: return ANNCE_NUM_GAMESTART_10;
+                               case 9:  return ANNCE_NUM_GAMESTART_9;
+                               case 8:  return ANNCE_NUM_GAMESTART_8;
+                               case 7:  return ANNCE_NUM_GAMESTART_7;
+                               case 6:  return ANNCE_NUM_GAMESTART_6;
+                               case 5:  return ANNCE_NUM_GAMESTART_5;
+                               case 4:  return ANNCE_NUM_GAMESTART_4;
+                               case 3:  return ANNCE_NUM_GAMESTART_3;
+                               case 2:  return ANNCE_NUM_GAMESTART_2;
+                               case 1:  return ANNCE_NUM_GAMESTART_1;
+                       }
+                       break;
+               }
+               case CNT_IDLE:
+               {
+                       switch(num)
+                       {
+                               case 10: return ANNCE_NUM_IDLE_10;
+                               case 9:  return ANNCE_NUM_IDLE_9;
+                               case 8:  return ANNCE_NUM_IDLE_8;
+                               case 7:  return ANNCE_NUM_IDLE_7;
+                               case 6:  return ANNCE_NUM_IDLE_6;
+                               case 5:  return ANNCE_NUM_IDLE_5;
+                               case 4:  return ANNCE_NUM_IDLE_4;
+                               case 3:  return ANNCE_NUM_IDLE_3;
+                               case 2:  return ANNCE_NUM_IDLE_2;
+                               case 1:  return ANNCE_NUM_IDLE_1;
+                       }
+                       break;
+               }
+               case CNT_KILL:
+               {
+                       switch(num)
+                       {
+                               case 10: return ANNCE_NUM_KILL_10;
+                               case 9:  return ANNCE_NUM_KILL_9;
+                               case 8:  return ANNCE_NUM_KILL_8;
+                               case 7:  return ANNCE_NUM_KILL_7;
+                               case 6:  return ANNCE_NUM_KILL_6;
+                               case 5:  return ANNCE_NUM_KILL_5;
+                               case 4:  return ANNCE_NUM_KILL_4;
+                               case 3:  return ANNCE_NUM_KILL_3;
+                               case 2:  return ANNCE_NUM_KILL_2;
+                               case 1:  return ANNCE_NUM_KILL_1;
+                       }
+                       break;
+               }
+               case CNT_RESPAWN:
+               {
+                       switch(num)
+                       {
+                               case 10: return ANNCE_NUM_RESPAWN_10;
+                               case 9:  return ANNCE_NUM_RESPAWN_9;
+                               case 8:  return ANNCE_NUM_RESPAWN_8;
+                               case 7:  return ANNCE_NUM_RESPAWN_7;
+                               case 6:  return ANNCE_NUM_RESPAWN_6;
+                               case 5:  return ANNCE_NUM_RESPAWN_5;
+                               case 4:  return ANNCE_NUM_RESPAWN_4;
+                               case 3:  return ANNCE_NUM_RESPAWN_3;
+                               case 2:  return ANNCE_NUM_RESPAWN_2;
+                               case 1:  return ANNCE_NUM_RESPAWN_1;
+                       }
+                       break;
+               }
+               case CNT_ROUNDSTART:
+               {
+                       switch(num)
+                       {
+                               case 10: return ANNCE_NUM_ROUNDSTART_10;
+                               case 9:  return ANNCE_NUM_ROUNDSTART_9;
+                               case 8:  return ANNCE_NUM_ROUNDSTART_8;
+                               case 7:  return ANNCE_NUM_ROUNDSTART_7;
+                               case 6:  return ANNCE_NUM_ROUNDSTART_6;
+                               case 5:  return ANNCE_NUM_ROUNDSTART_5;
+                               case 4:  return ANNCE_NUM_ROUNDSTART_4;
+                               case 3:  return ANNCE_NUM_ROUNDSTART_3;
+                               case 2:  return ANNCE_NUM_ROUNDSTART_2;
+                               case 1:  return ANNCE_NUM_ROUNDSTART_1;
+                       }
+                       break;
+               }
+               default:
+               {
+                       switch(num)
+                       {
+                               case 10: return ANNCE_NUM_10;
+                               case 9:  return ANNCE_NUM_9;
+                               case 8:  return ANNCE_NUM_8;
+                               case 7:  return ANNCE_NUM_7;
+                               case 6:  return ANNCE_NUM_6;
+                               case 5:  return ANNCE_NUM_5;
+                               case 4:  return ANNCE_NUM_4;
+                               case 3:  return ANNCE_NUM_3;
+                               case 2:  return ANNCE_NUM_2;
+                               case 1:  return ANNCE_NUM_1;
+                       }
+                       break;
+               }
+       }
+       return NOTIF_ABORT; // abort sending if none of these numbers were right
+}
+#endif