#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;
// 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);
}
{
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
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;
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
vector decompressShortVector(float data)
{
vector out;
- float pitch, yaw, len;
+ float p, y, len;
if(data == 0)
return '0 0 0';
- pitch = (data & 0xF000) / 0x1000;
- yaw = (data & 0x0F80) / 0x80;
- len = (data & 0x007F);
+ p = (data & 0xF000) / 0x1000;
+ y = (data & 0x0F80) / 0x80;
+ len = (data & 0x007F);
- //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
+ //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
- if(pitch == 0)
+ if(p == 0)
{
out_x = 0;
out_y = 0;
- if(yaw == 31)
+ if(y == 31)
out_z = -1;
else
out_z = +1;
}
else
{
- yaw = .19634954084936207740 * yaw;
- pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
- out_x = cos(yaw) * cos(pitch);
- out_y = sin(yaw) * cos(pitch);
- out_z = -sin(pitch);
+ y = .19634954084936207740 * y;
+ p = .19634954084936207740 * p - 1.57079632679489661922;
+ out_x = cos(y) * cos(p);
+ out_y = sin(y) * cos(p);
+ out_z = -sin(p);
}
//print("decompressed: ", vtos(out), "\n");
float compressShortVector(vector vec)
{
vector ang;
- float pitch, yaw, len;
+ float p, y, len;
if(vlen(vec) == 0)
return 0;
//print("compress: ", vtos(vec), "\n");
error("BOGUS vectoangles");
//print("angles: ", vtos(ang), "\n");
- pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
- if(pitch == 0)
+ p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
+ if(p == 0)
{
if(vec_z < 0)
- yaw = 31;
+ y = 31;
else
- yaw = 30;
+ y = 30;
}
else
- yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
+ y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
len = invertLengthLog(vlen(vec));
- //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
+ //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
- return (pitch * 0x1000) + (yaw * 0x80) + len;
+ return (p * 0x1000) + (y * 0x80) + len;
}
void compressShortVector_init()
}
#endif
-#ifdef CSQC
-void cvar_settemp(string pKey, string pValue)
+float cvar_settemp(string tmp_cvar, string tmp_value)
{
- error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
-}
-void cvar_settemp_restore()
-{
- error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
-}
-#else
-void cvar_settemp(string pKey, string pValue)
-{
- float i;
- string settemp_var;
- if(cvar_string(pKey) == pValue)
- return;
- i = cvar("settemp_idx");
- cvar_set("settemp_idx", ftos(i+1));
- settemp_var = strcat("_settemp_x", ftos(i));
-#ifdef MENUQC
- registercvar(settemp_var, "", 0);
-#else
- registercvar(settemp_var, "");
-#endif
- cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
- cvar_set(settemp_var, cvar_string(pKey));
- cvar_set(pKey, pValue);
+ 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 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)
+ 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;
}
-void cvar_settemp_restore()
+float cvar_settemp_restore()
{
- // undo what cvar_settemp did
- float n, i;
- n = tokenize_console(cvar_string("settemp_list"));
- for(i = 0; i < n - 3; i += 3)
- cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
- cvar_set("settemp_list", "0");
+ float i = 0;
+ entity e = world;
+ while((e = find(e, classname, "saved_cvar_value")))
+ {
+ 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;
}
-#endif
float almost_equals(float a, float b)
{
{
float eps;
eps = (max(a, -a) + max(c, -c)) * 0.001;
+ if(a > c)
+ eps = -eps;
return b == median(a - eps, b, c + eps);
}
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
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;
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 not(subpattern4)
+ return 0;
+ if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
+ return 0;
+ }
}
return 1;
}
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"))
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)
return v;
}
#else
-void WriteInt24_t(float dest, float val)
+void WriteInt24_t(float dst, float val)
{
float v;
- WriteShort(dest, (v = floor(val / 256)));
- WriteByte(dest, val - v * 256); // 0..255
+ WriteShort(dst, (v = floor(val / 256)));
+ WriteByte(dst, val - v * 256); // 0..255
}
#endif
#endif
while(acl)
{
t = car(acl); acl = cdr(acl);
+
d = 1;
if(substring(t, 0, 1) == "-")
{
}
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;
f &~= f * 65536;
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()
+{
+ float f;
+ f = ReadShort();
+ if(f == 0)
+ return world;
+ return findfloat(world, entnum, f);
+}
+#endif
+
+float shutdown_running;
+#ifdef SVQC
+void SV_Shutdown()
+#endif
+#ifdef CSQC
+void CSQC_Shutdown()
+#endif
+#ifdef MENUQC
+void m_shutdown()
+#endif
+{
+ if(shutdown_running)
+ {
+ print("Recursive shutdown detected! Only restoring cvars...\n");
+ }
+ else
+ {
+ shutdown_running = 1;
+ 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;
+}
+
+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