]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/lib/string.qh
Update default video settings
[xonotic/xonotic-data.pk3dir.git] / qcsrc / lib / string.qh
1 #pragma once
2
3 #include "nil.qh"
4 #include "sort.qh"
5 #include "oo.qh"
6
7 // this is not exactly 16KiB (16384 bytes) because one byte is reserved for the \0 terminator
8 #define VM_TEMPSTRING_MAXSIZE 16383
9
10 // string logic
11 //
12 // true: is truthy
13 // == "": is equal to ""
14 // is "": has the same string index as the string constant ""
15 // strunzone: can be strunzoned
16 //
17 // |              | true | == "" | is "" | strunzone |
18 // | :----------: | :--: | :---: | :---: | :-------: |
19 // | nil          |      | yes   |       |           |
20 // | strcat(nil)  | yes  | yes   |       |           |
21 // | strzone(nil) | yes  | yes   |       | yes       |
22 // | ""           | yes  | yes   | yes   |           |
23 // | strcat("")   | yes  | yes   |       |           |
24 // | strzone("")  | yes  | yes   |       | yes       |
25 // | "s"          | yes  |       |       |           |
26 // | strcat("s")  | yes  |       |       |           |
27 // | strzone("s") | yes  |       |       | yes       |
28
29 #ifdef CSQC
30         float stringwidth_colors(string s, vector theSize)
31         {
32                 return stringwidth_builtin(s, true, theSize);
33         }
34
35         float stringwidth_nocolors(string s, vector theSize)
36         {
37                 return stringwidth_builtin(s, false, theSize);
38         }
39 #endif
40 #ifdef MENUQC
41         float stringwidth_colors(string s, vector theSize)
42         {
43                 return stringwidth(s, true, theSize);
44         }
45
46         float stringwidth_nocolors(string s, vector theSize)
47         {
48                 return stringwidth(s, false, theSize);
49         }
50 #endif
51
52 #define strcpy(this, s) MACRO_BEGIN \
53         if (this) { \
54                 strunzone(this); \
55         } \
56         this = strzone(s); \
57 MACRO_END
58
59 #define strfree(this) MACRO_BEGIN \
60         if (this) { \
61                 strunzone(this); \
62         } \
63         this = string_null; \
64 MACRO_END
65
66 // Returns the number of days since 0000-03-01 (March 1, year 0)
67 // Starting counting from March, as the 1st month of the year, February becomes the 12th and last month,
68 // so its variable duration does not affect, given that the 29th is the last day of the period
69 ERASEABLE
70 int days_up_to_date(int Y, int M, int D)
71 {
72         int years = (M <= 2) ? Y - 1 : Y;
73
74         int leap_days = floor(years / 4) - floor(years / 100) + floor(years / 400);
75
76         // using these 2 formulas to save 2 arrays or switches (performance isn't important here)
77         int months = (M <= 2) ? (M + 9) : (M - 3); // 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
78         int leftover_days = (M <= 2) ? (M + 5) : floor(0.58 * M - 1.1); // 6, 7, 0, 1, 1, 2, 2, 3, 4, 4, 5, 5
79
80         int month_days = 30 * months + leftover_days;
81
82         return 365 * years + month_days + D + leap_days;
83 }
84
85 #define DAYS_UP_TO_EPOCH 719469 // days_up_to_date(1970, 1, 1);
86
87 // Returns the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).
88 // This function exists only as a replacement for strftime(false, "%s") which doesn't work
89 // on Windows (%s is not supported) and at least in some linux systems doesn't return the
90 // correct result
91 // NOTE: at the current date, the number (string) returned by both strftime(false, "%s") and
92 // strftime_s() is so high that can't be converted to int (with ftos) without precision loss
93 ERASEABLE
94 string strftime_s()
95 {
96         string date = strftime(false, "%Y-%m-%d %H:%M:%S");
97         int i, seconds = 0;
98         i =0; int Y = stof(substring(date, i, 4)); // years
99         i+=5; int M = stof(substring(date, i, 2)); // months
100         i+=3; int D = stof(substring(date, i, 2)); // days
101
102         i+=3; seconds += stof(substring(date, i, 2)) * 60 * 60; // hours
103         i+=3; seconds += stof(substring(date, i, 2)) * 60; // minutes
104         i+=3; seconds += stof(substring(date, i, 2)); // seconds
105
106         // doing so we loose precision
107         //seconds += (days_up_to_date(Y, M, D) - DAYS_UP_TO_EPOCH) * 24 * 60 * 60;
108         //return ftos(seconds);
109
110         int days_since_epoch = days_up_to_date(Y, M, D) - DAYS_UP_TO_EPOCH;
111         // use hundreds of seconds as unit to avoid precision loss
112         int hundreds_of_seconds = days_since_epoch * 24 * 6 * 6;
113         hundreds_of_seconds += floor(seconds / 100);
114
115         // tens of seconds and seconds
116         string seconds_str = ftos(seconds % 100);
117         if ((seconds % 100) < 10)
118                 seconds_str = strcat("0", seconds_str);
119
120         return strcat(ftos(hundreds_of_seconds), seconds_str);
121 }
122
123 /// \param[in] seconds number of seconds, can be negative too
124 /// \return time as "m:ss" string (floored)
125 ERASEABLE
126 string seconds_tostring(float seconds)
127 {
128         bool negative = false;
129         if (seconds < 0)
130         {
131                 negative = true;
132                 seconds = -seconds;
133                 if (floor(seconds) != seconds)
134                         seconds += 1; // make floor work in the other direction
135         }
136         int minutes = floor(seconds / 60);
137         seconds -= minutes * 60;
138         if (negative)
139                 return sprintf("-%d:%02d", minutes, seconds);
140         return sprintf("%d:%02d", minutes, seconds);
141 }
142
143 /// \param[in] tm integer clocked time in tenths or hundredths, CANNOT be negative
144 /// \param[in] hundredths if true append hundredths too, otherwise only tenths
145 /// \param[in] compact if true leading 0s are omitted (except the seconds unit digit)
146 /// \return clocked time as "m:ss.t" or "m:ss.th" string (rounded)
147 ERASEABLE
148 string clockedtime_tostring(int tm, bool hundredths, bool compact)
149 {
150         if (tm < 0)
151         {
152                 if (compact)
153                         return strcat("0.0", hundredths ? "0" : "");
154                 else
155                         return strcat("0:00.0", hundredths ? "0" : "");
156         }
157         int acc = hundredths ? 6000 : 600;
158         tm = floor(tm + 0.5);
159         int minutes = floor(tm / acc);
160         int tm_without_minutes = tm - minutes * acc;
161         // NOTE: the start digit of s is a placeholder and won't be displayed
162         string s = ftos(acc * 10 + tm_without_minutes);
163         if (!compact || minutes > 0)
164                 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, hundredths ? 2 : 1));
165
166         int ofs = 2, digits = 1;
167         if (tm_without_minutes >= 10 * (hundredths ? 100 : 10))
168         {
169                 ofs = 1;
170                 digits = 2;
171         }
172         return strcat(substring(s, ofs, digits), ".", substring(s, 3, hundredths ? 2 : 1));
173
174 }
175
176 #define mmsst(tm, compact) clockedtime_tostring(tm, false, compact)
177 #define mmssth(tm, compact) clockedtime_tostring(tm, true, compact)
178
179 ERASEABLE
180 string format_time(float seconds)
181 {
182         seconds = floor(seconds + 0.5);
183         float days = floor(seconds / 864000);
184         seconds -= days * 864000;
185         float hours = floor(seconds / 36000);
186         seconds -= hours * 36000;
187         float minutes = floor(seconds / 600);
188         seconds -= minutes * 600;
189         if (days > 0) return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
190         else return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
191 }
192
193 int ColorTranslateMode;
194
195 ERASEABLE
196 string ColorTranslateRGB(string s)
197 {
198         return (ColorTranslateMode & 1) ? strdecolorize(s) : s;
199 }
200
201 #ifdef GAMEQC
202 // color code replace, place inside of sprintf and parse the string... defaults described as constants
203 // foreground/normal colors
204 string autocvar_hud_colorset_foreground_1 = "2"; // F1 - Green  // primary priority (important names, etc)
205 string autocvar_hud_colorset_foreground_2 = "3"; // F2 - Yellow // secondary priority (items, locations, numbers, etc)
206 string autocvar_hud_colorset_foreground_3 = "4"; // F3 - Blue   // tertiary priority or relatively inconsequential text
207 string autocvar_hud_colorset_foreground_4 = "1"; // F4 - Red    // notice/attention grabbing texting
208 // "kill" colors
209 string autocvar_hud_colorset_kill_1 = "1";       // K1 - Red    // "bad" or "dangerous" text (death messages against you, kill notifications, etc)
210 string autocvar_hud_colorset_kill_2 = "3";       // K2 - Yellow // similar to above, but less important... OR, a highlight out of above message type
211 string autocvar_hud_colorset_kill_3 = "4";       // K3 - Blue   // "good" or "beneficial" text (you fragging someone, etc)
212 // background color
213 string autocvar_hud_colorset_background = "7";   // BG - White // neutral/unimportant text
214
215 /** color code replace, place inside of sprintf and parse the string */
216 string CCR(string input)
217 {
218         // foreground/normal colors
219         input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
220         input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
221         input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
222         input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
223
224         // "kill" colors
225         input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
226         input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
227         input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
228
229         // background colors
230         input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
231         input = strreplace("^N", "^7", input);  // "none"-- reset to white...
232         return input;
233 }
234 #endif
235
236 #define startsWith(haystack, needle) (strstrofs(haystack, needle, 0) == 0)
237
238 ERASEABLE
239 bool startsWithNocase(string haystack, string needle)
240 {
241         return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
242 }
243
244 noref string _endsWith_suffix;
245 #define endsWith(this, suffix) (_endsWith_suffix = suffix, substring(this, -strlen(_endsWith_suffix), -1) == _endsWith_suffix)
246
247 /** unzone the string, and return it as tempstring. Safe to be called on string_null */
248 ERASEABLE
249 string fstrunzone(string s)
250 {
251         if (!s) return s;
252         string sc = strcat(s, "");
253         strunzone(s);
254         return sc;
255 }
256
257 /** returns first word */
258 ERASEABLE
259 string car(string s)
260 {
261         int o = strstrofs(s, " ", 0);
262         if (o < 0) return s;
263         return substring(s, 0, o);
264 }
265
266 /** returns all but first word */
267 ERASEABLE
268 string cdr(string s)
269 {
270         int o = strstrofs(s, " ", 0);
271         if (o < 0) return string_null;
272         return substring(s, o + 1, strlen(s) - (o + 1));
273 }
274
275 ERASEABLE
276 string cons(string a, string b)
277 {
278         if (a == "") return b;
279         if (b == "") return a;
280         return strcat(a, " ", b);
281 }
282
283 ERASEABLE
284 string cons_mid(string a, string mid, string b)
285 {
286         if (a == "") return b;
287         if (b == "") return a;
288         return strcat(a, mid, b);
289 }
290
291 ERASEABLE
292 string substring_range(string s, float b, float e)
293 {
294         return substring(s, b, e - b);
295 }
296
297 ERASEABLE
298 string swapwords(string str, float i, float j)
299 {
300         float n;
301         string s1, s2, s3, s4, s5;
302         float si, ei, sj, ej, s0, en;
303         n = tokenizebyseparator(str, " ");  // must match g_maplist processing in ShuffleMaplist and "shuffle"
304         si = argv_start_index(i);
305         sj = argv_start_index(j);
306         ei = argv_end_index(i);
307         ej = argv_end_index(j);
308         s0 = argv_start_index(0);
309         en = argv_end_index(n - 1);
310         s1 = substring_range(str, s0, si);
311         s2 = substring_range(str, si, ei);
312         s3 = substring_range(str, ei, sj);
313         s4 = substring_range(str, sj, ej);
314         s5 = substring_range(str, ej, en);
315         return strcat(s1, s4, s3, s2, s5);
316 }
317
318 string _shufflewords_str;
319 ERASEABLE
320 void _shufflewords_swapfunc(float i, float j, entity pass)
321 {
322         _shufflewords_str = swapwords(_shufflewords_str, i, j);
323 }
324
325 ERASEABLE
326 string shufflewords(string str)
327 {
328         _shufflewords_str = str;
329         int n = tokenizebyseparator(str, " ");
330         shuffle(n, _shufflewords_swapfunc, NULL);
331         str = _shufflewords_str;
332         _shufflewords_str = string_null;
333         return str;
334 }
335
336 ERASEABLE
337 string unescape(string in)
338 {
339         in = strzone(in);  // but it doesn't seem to be necessary in my tests at least
340
341         int len = strlen(in);
342         string str = "";
343         for (int i = 0; i < len; ++i)
344         {
345                 string s = substring(in, i, 1);
346                 if (s == "\\")
347                 {
348                         s = substring(in, i + 1, 1);
349                         if (s == "n") str = strcat(str, "\n");
350                         else if (s == "\\") str = strcat(str, "\\");
351                         else str = strcat(str, substring(in, i, 2));
352                         ++i;
353                         continue;
354                 }
355                 str = strcat(str, s);
356         }
357         strunzone(in);
358         return str;
359 }
360
361 ERASEABLE
362 string strwords(string s, int w)
363 {
364         int endpos = 0;
365         for ( ; w && endpos >= 0; --w)
366                 endpos = strstrofs(s, " ", endpos + 1);
367         if (endpos < 0) return s;
368         return substring(s, 0, endpos);
369 }
370
371 #define strhasword(s, w) (strstrofs(strcat(" ", s, " "), strcat(" ", w, " "), 0) >= 0)
372
373 ERASEABLE
374 int u8_strsize(string s)
375 {
376         int l = 0;
377         for (int i = 0, c; (c = str2chr(s, i)) > 0; ++i, ++l)
378         {
379                 l += (c >= 0x80);
380                 l += (c >= 0x800);
381                 l += (c >= 0x10000);
382         }
383         return l;
384 }
385
386 // List of Unicode spaces: http://jkorpela.fi/chars/spaces.html
387 ERASEABLE
388 bool isInvisibleString(string s)
389 {
390         s = strdecolorize(s);
391         bool utf8 = cvar("utf8_enable");
392         for (int i = 0, n = strlen(s); i < n; ++i)
393         {
394                 int c = str2chr(s, i);
395                 switch (c)
396                 {
397                         case 0:
398                         case 32:           // space
399                                 break;
400                         case 192:          // charmap space
401                                 if (!utf8) break;
402                                 return false;
403                         case 0xE000: // invisible char of the utf8 quake charmap
404                         case 0xE00A: // invisible char of the utf8 quake charmap
405                         case 0xE0A0: // invisible char of the utf8 quake charmap
406                         case 0xE020: // invisible char of the utf8 quake charmap
407                         case 0x00A0: // NO-BREAK SPACE
408                         case 0x180E: // MONGOLIAN VOWEL SEPARATOR
409                         case 0x2000: // EN QUAD
410                         case 0x2001: // EM QUAD
411                         case 0x2002: // EN SPACE
412                         case 0x2003: // EM SPACE
413                         case 0x2004: // THREE-PER-EM SPACE
414                         case 0x2005: // FOUR-PER-EM SPACE
415                         case 0x2006: // SIX-PER-EM SPACE
416                         case 0x2007: // FIGURE SPACE
417                         case 0x2008: // PUNCTUATION SPACE
418                         case 0x2009: // THIN SPACE
419                         case 0x200A: // HAIR SPACE
420                         case 0x200B: // ZERO WIDTH SPACE
421                         case 0x202F: // NARROW NO-BREAK SPACE
422                         case 0x205F: // MEDIUM MATHEMATICAL SPACE
423                         case 0x3000: // IDEOGRAPHIC SPACE
424                         case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE
425                         case 0xFFA0: // Halfwidth Hangul Filler
426                         case 0x3164: // Hangul Filler
427                                 if (utf8) break;
428                         default:
429                                 return false;
430                 }
431         }
432         return true;
433 }
434
435 // Multiline text file buffers
436
437 ERASEABLE
438 int buf_load(string pFilename)
439 {
440         int buf = buf_create();
441         if (buf < 0) return -1;
442         int fh = fopen(pFilename, FILE_READ);
443         if (fh < 0)
444         {
445                 buf_del(buf);
446                 return -1;
447         }
448         string l;
449         for (int i = 0; (l = fgets(fh)); ++i)
450                 bufstr_set(buf, i, l);
451         fclose(fh);
452         return buf;
453 }
454
455 ERASEABLE
456 void buf_save(float buf, string pFilename)
457 {
458         int fh = fopen(pFilename, FILE_WRITE);
459         if (fh < 0) error(strcat("Can't write buf to ", pFilename));
460         int n = buf_getsize(buf);
461         for (int i = 0; i < n; ++i)
462                 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
463         fclose(fh);
464 }
465
466 /**
467  * converts a number to a string with the indicated number of decimals
468  */
469 ERASEABLE
470 string ftos_decimals(float number, int decimals)
471 {
472         // inhibit stupid negative zero
473         if (number == 0) number = 0;
474         return sprintf("%.*f", decimals, number);
475 }
476
477 /**
478  * converts a number to a string with the minimum number of decimals
479  */
480 ERASEABLE
481 string ftos_mindecimals(float number)
482 {
483         // inhibit stupid negative zero
484         if (number == 0) number = 0;
485         return sprintf("%.7g", number);
486 }
487
488 ERASEABLE
489 int vercmp_recursive(string v1, string v2)
490 {
491         int dot1 = strstrofs(v1, ".", 0);
492         int dot2 = strstrofs(v2, ".", 0);
493         string s1 = (dot1 == -1) ? v1 : substring(v1, 0, dot1);
494         string s2 = (dot2 == -1) ? v2 : substring(v2, 0, dot2);
495
496         float r;
497         r = stof(s1) - stof(s2);
498         if (r != 0) return r;
499
500         r = strcasecmp(s1, s2);
501         if (r != 0) return r;
502
503         if (dot1 == -1) return (dot2 == -1) ? 0 : -1;
504         else return (dot2 == -1) ? 1 : vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
505 }
506
507 ERASEABLE
508 int vercmp(string v1, string v2)
509 {
510         if (strcasecmp(v1, v2) == 0) return 0;  // early out check
511
512         // "git" beats all
513         if (v1 == "git") return 1;
514         if (v2 == "git") return -1;
515
516         return vercmp_recursive(v1, v2);
517 }
518
519 const string HEXDIGITS_MINSET = "0123456789ABCDEFabcdef";
520 const string HEXDIGITS = "0123456789ABCDEF0123456789abcdef";
521 #define HEXDIGIT_TO_DEC_RAW(d) (strstrofs(HEXDIGITS, (d), 0))
522 #define HEXDIGIT_TO_DEC(d) ((HEXDIGIT_TO_DEC_RAW(d) | 0x10) - 0x10)
523 #define DEC_TO_HEXDIGIT(d) (substring(HEXDIGITS_MINSET, (d), 1))
524 #define IS_HEXDIGIT(d) (strstrofs(HEXDIGITS_MINSET, (d), 0) >= 0)
525
526 const string DIGITS = "0123456789";
527 #define IS_DIGIT(d) (strstrofs(DIGITS, (d), 0) >= 0)
528
529 // returns true if the caret at position pos is escaped
530 ERASEABLE
531 bool isCaretEscaped(string theText, float pos)
532 {
533         // count all the previous carets
534         int carets = 0;
535         while(pos - carets >= 1 && substring(theText, pos - carets - 1, 1) == "^")
536                 ++carets;
537         // if number of previous carets is odd then this carets is escaped
538         return (carets & 1);
539 }
540
541 ERASEABLE
542 bool isValidColorCodeValue(string theText, int cc_len, int tag_start)
543 {
544         if (cc_len == 2)
545                 return IS_DIGIT(substring(theText, tag_start + 1, 1));
546         if (cc_len == 5)
547                 return (IS_HEXDIGIT(substring(theText, tag_start + 2, 1))
548                         && IS_HEXDIGIT(substring(theText, tag_start + 3, 1))
549                         && IS_HEXDIGIT(substring(theText, tag_start + 4, 1)));
550         return false;
551 }
552
553 // it returns 0 if pos is NOT in the middle or at the end of a color code
554 // otherwise it returns a vector with color code length as the first component
555 // and the offset from '^' position to pos as the second component
556 // e.g.:
557 // "j^2kl" | returns 0 if pos == 0 or 1 or 4
558 //    ^^   | returns '2 1' or '2 2' if pos == 2 or 3
559 ERASEABLE
560 vector checkColorCode(string theText, int text_len, int pos, bool check_at_the_end)
561 {
562         if (text_len == 0)
563                 text_len = strlen(theText);
564         string tag_type = "^";
565         int cc_len = 2;
566         int tag_len = 1;
567
568         LABEL(check_color_tag)
569
570         int ofs = cc_len;
571         if (!check_at_the_end)
572                 ofs--;
573         for (; ofs >= 1; --ofs)
574         {
575                 if (!(pos >= ofs && text_len >= pos + (cc_len - ofs)))
576                         continue;
577                 if(substring(theText, pos - ofs, tag_len) == tag_type)
578                 {
579                         if (!isCaretEscaped(theText, pos - ofs) && isValidColorCodeValue(theText, cc_len, pos - ofs))
580                                 return eX * cc_len + eY * ofs;
581                 }
582         }
583         if (cc_len == 2)
584         {
585                 tag_type = "^x";
586                 cc_len = 5;
587                 tag_len = 2;
588                 goto check_color_tag;
589         }
590         return '0 0 0';
591 }