1 /*
2 C-Dogs SDL
3 A port of the legendary (and fun) action/arcade cdogs.
4 Copyright (C) 1995 Ronny Wester
5 Copyright (C) 2003 Jeremy Chin
6 Copyright (C) 2003-2007 Lucas Martin-King
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
22 This file incorporates work covered by the following copyright and
23 permission notice:
24
25 Copyright (c) 2013-2017, 2019-2021 Cong Xu
26 All rights reserved.
27
28 Redistribution and use in source and binary forms, with or without
29 modification, are permitted provided that the following conditions are met:
30
31 Redistributions of source code must retain the above copyright notice, this
32 list of conditions and the following disclaimer.
33 Redistributions in binary form must reproduce the above copyright notice,
34 this list of conditions and the following disclaimer in the documentation
35 and/or other materials provided with the distribution.
36
37 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
41 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47 POSSIBILITY OF SUCH DAMAGE.
48 */
49 #define _BSD_SOURCE
50 #define _DEFAULT_SOURCE
51
52 #include "utils.h"
53
54 #include <assert.h>
55 #include <errno.h>
56 #include <limits.h>
57 #include <math.h>
58 #include <stdlib.h>
59 #include <string.h>
60
61 #include <tinydir/tinydir.h>
62
63 #include "events.h"
64 #include "joystick.h"
65 #include "sys_config.h"
66
67 bool gTrue = true;
68 bool gFalse = false;
69
70 // From answer by ThiefMaster
71 // http://stackoverflow.com/a/5309508/2038264
72 // License: http://creativecommons.org/licenses/by-sa/3.0/
73 // Author profile: http://stackoverflow.com/users/298479/thiefmaster
StrGetFileExt(const char * filename)74 const char *StrGetFileExt(const char *filename)
75 {
76 const char *dot = strrchr(filename, '.');
77 if (!dot || dot == filename)
78 {
79 return "";
80 }
81 return dot + 1;
82 }
83
84 // Note: includes trailing slash
PathGetDirname(char * buf,const char * path)85 void PathGetDirname(char *buf, const char *path)
86 {
87 const char *basename = PathGetBasename(path);
88 if (basename == path)
89 {
90 strcpy(buf, "");
91 }
92 else
93 {
94 strncpy(buf, path, basename - path);
95 buf[basename - path] = '\0';
96 }
97 }
PathGetBasename(const char * path)98 const char *PathGetBasename(const char *path)
99 {
100 const char *fslash = strrchr(path, '/');
101 const char *bslash = strrchr(path, '\\');
102 const char *slash =
103 fslash ? (bslash ? MAX(fslash, bslash) : fslash) : bslash;
104 if (slash == NULL)
105 {
106 // no slashes found, simply return path
107 return path;
108 }
109 else
110 {
111 return slash + 1;
112 }
113 }
PathGetWithoutExtension(char * buf,const char * path)114 void PathGetWithoutExtension(char *buf, const char *path)
115 {
116 const char *dot = strrchr(path, '.');
117 if (dot)
118 {
119 strncpy(buf, path, dot - path);
120 buf[dot - path] = '\0';
121 }
122 else
123 {
124 strcpy(buf, path);
125 }
126 }
PathGetBasenameWithoutExtension(char * buf,const char * path)127 void PathGetBasenameWithoutExtension(char *buf, const char *path)
128 {
129 const char *basename = PathGetBasename(path);
130 PathGetWithoutExtension(buf, basename);
131 }
132
133 #ifdef _WIN32
134 #include "sys_config.h"
135 #define realpath(src, dst) _fullpath(dst, src, CDOGS_PATH_MAX)
136 #endif
137 static bool IsAbsolutePath(const char *path);
RealPath(const char * src,char * dest)138 void RealPath(const char *src, char *dest)
139 {
140 char *res;
141 #ifndef _WIN32
142 // realpath will fail if the file does not exist; if this is the
143 // case then resolve the path ourselves
144 tinydir_file file;
145 const bool exists = tinydir_file_open(&file, src) == 0;
146 if (!exists)
147 {
148 char srcBuf[CDOGS_PATH_MAX];
149 // First, convert slashes
150 srcBuf[0] = '\0';
151 strncat(srcBuf, src, CDOGS_PATH_MAX - 1);
152 for (char *c = srcBuf; *c != '\0'; c++)
153 {
154 if (*c == '\\')
155 {
156 *c = '/';
157 }
158 }
159
160 // Then, add on the CWD if the path is not absolute
161 char resolveBuf[CDOGS_PATH_MAX];
162 resolveBuf[0] = '\0';
163 if (!IsAbsolutePath(srcBuf))
164 {
165 CDogsGetCWD(resolveBuf);
166 strcat(resolveBuf, "/");
167 }
168 // Add the path
169 strcat(resolveBuf, srcBuf);
170
171 // Finally, resolve the path one level at a time, ignoring '//'s, '.'s
172 // and resolving '..'s to the parent level
173 char *cOut = dest;
174 char cLast = '\0';
175 for (const char *c = resolveBuf; *c != '\0'; c++)
176 {
177 if (*c == '.' && (cLast == '.' || cLast == '/'))
178 {
179 if (cLast == '.')
180 {
181 // '..' parent dir
182 // Rewind the out ptr to the last path separator
183 if (cOut > dest + 1)
184 {
185 // Skip past the last slash
186 cOut -= 2;
187 while (*cOut != '/' && cOut > dest)
188 {
189 cOut--;
190 }
191 // Go back in front of the slash
192 if (*cOut == '/')
193 {
194 cOut++;
195 }
196 }
197 }
198 else if (cLast == '/')
199 {
200 // ignore for now
201 }
202 }
203 else if (*c == '/' && (cLast == '/' || cLast == '.'))
204 {
205 // Double slash; ignore
206 }
207 else
208 {
209 *cOut = *c;
210 cOut++;
211 }
212 cLast = *c;
213 }
214 // Write terminating char
215 *cOut = '\0';
216 res = dest;
217 }
218 else
219 #endif
220 res = realpath(src, dest);
221 if (!res)
222 {
223 fprintf(
224 stderr, "Cannot resolve relative path %s: %s\n", src,
225 strerror(errno));
226 // Default to relative path
227 strcpy(dest, src);
228 }
229 // Convert \'s to /'s (for consistency)
230 for (char *c = dest; *c != '\0'; c++)
231 {
232 if (*c == '\\')
233 {
234 *c = '/';
235 }
236 #ifdef _WIN32
237 else
238 {
239 *c = (char)toupper(*c);
240 }
241 #endif
242 }
243 }
244
245 // Convert an absolute path to a relative path
246 // e.g. /a/path/from/here, /a/path/to -> ../to
247 static void TrimSlashes(char *s);
RelPath(char * buf,const char * to,const char * from)248 void RelPath(char *buf, const char *to, const char *from)
249 {
250 #ifdef _WIN32
251 // If path is on a different Windows drive, just return the path
252 if (strlen(from) >= 2 && strlen(to) >= 2 && IsAbsolutePath(from) &&
253 IsAbsolutePath(to) && toupper(from[0]) != toupper(to[0]))
254 {
255 strcpy(buf, to);
256 return;
257 }
258 #endif
259 // Make sure both to/from paths were generated using RealPath,
260 // so that common substring means they share part of their paths,
261 // and not due to alternate renderings of the same path like
262 // different path separator chars, or relative paths.
263 char toBuf[CDOGS_PATH_MAX];
264 char fromBuf[CDOGS_PATH_MAX];
265 RealPath(to, toBuf);
266 RealPath(from, fromBuf);
267
268 // Remove trailing slashes
269 TrimSlashes(toBuf);
270 TrimSlashes(fromBuf);
271
272 // First, find common string prefix
273 const char *t = toBuf;
274 const char *f = fromBuf;
275 const char *tSlash = t;
276 const char *fSlash = f;
277 while (*t && *f)
278 {
279 if (*t != *f)
280 {
281 break;
282 }
283 if (*t == '/')
284 {
285 tSlash = t;
286 fSlash = f;
287 }
288 t++;
289 f++;
290 }
291 // e.g.: tSlash = "/to", fSlash = "/from/here"
292
293 // Check if one is a complete substring of the other
294 if (!*f)
295 {
296 tSlash = t;
297 fSlash = f;
298 }
299
300 // For every folder in "from", add "..", and finally add "to"
301 strcpy(buf, "");
302 while (*fSlash)
303 {
304 if (*fSlash == '/')
305 {
306 strcat(buf, "../");
307 }
308 fSlash++;
309 }
310 if (*tSlash == '/')
311 {
312 tSlash++;
313 }
314 strcat(buf, tSlash);
315 }
TrimSlashes(char * s)316 static void TrimSlashes(char *s)
317 {
318 char *end = s + strlen(s) - 1;
319 while (end > s && *end == '/')
320 {
321 end--;
322 }
323 *(end + 1) = '\0';
324 }
325
FixPathSeparator(char * dst,const char * src)326 void FixPathSeparator(char *dst, const char *src)
327 {
328 const char *s = src;
329 char *d = dst;
330 #ifdef _WIN32
331 #define SEP '\\'
332 #define SEP_OTHER '/'
333 #else
334 #define SEP '/'
335 #define SEP_OTHER '\\'
336 #endif
337 while (*s != '\0')
338 {
339 if (*s == SEP_OTHER)
340 *d = SEP;
341 else
342 *d = *s;
343 d++;
344 s++;
345 }
346 #undef SEP
347 *d = '\0';
348 }
349
350 #ifdef __APPLE__
351 #include <mach-o/dyld.h>
352 #endif
CDogsGetCWD(char * buf)353 char *CDogsGetCWD(char *buf)
354 {
355 #ifdef __APPLE__
356 uint32_t size = CDOGS_PATH_MAX;
357 if (_NSGetExecutablePath(buf, &size))
358 {
359 return NULL;
360 }
361 // This gives us the executable path; find the dirname
362 *strrchr(buf, '/') = '\0';
363 return buf;
364 #else
365 return getcwd(buf, CDOGS_PATH_MAX);
366 #endif
367 }
RelPathFromCWD(char * buf,const char * to)368 void RelPathFromCWD(char *buf, const char *to)
369 {
370 if (to == NULL || strlen(to) == 0)
371 {
372 return;
373 }
374 char cwd[CDOGS_PATH_MAX];
375 CASSERT(CDogsGetCWD(cwd) != NULL, "error");
376 if (CDogsGetCWD(cwd) == NULL)
377 {
378 fprintf(stderr, "Error getting CWD; %s\n", strerror(errno));
379 strcpy(buf, to);
380 }
381 else
382 {
383 RelPath(buf, to, cwd);
384 }
385 }
386
GetDataFilePath(char * buf,const char * path)387 void GetDataFilePath(char *buf, const char *path)
388 {
389 if (IsAbsolutePath(path))
390 {
391 strcpy(buf, path);
392 return;
393 }
394 char relbuf[CDOGS_PATH_MAX];
395 // Don't bother prepending CWD if data dir already an absolute path
396 if (IsAbsolutePath(CDOGS_DATA_DIR))
397 {
398 sprintf(relbuf, "%s%s", CDOGS_DATA_DIR, path);
399 }
400 else
401 {
402 char cwd[CDOGS_PATH_MAX];
403 if (CDogsGetCWD(cwd) == NULL)
404 {
405 fprintf(stderr, "Error getting CWD; %s\n", strerror(errno));
406 strcpy(cwd, "");
407 }
408 sprintf(relbuf, "%s/%s%s", cwd, CDOGS_DATA_DIR, path);
409 }
410 RealPath(relbuf, buf);
411 }
412
IsAbsolutePath(const char * path)413 static bool IsAbsolutePath(const char *path)
414 {
415 #ifdef _WIN32
416 return strlen(path) > 1 && path[1] == ':';
417 #else
418 return path[0] == '/';
419 #endif
420 }
421
Round(double x)422 double Round(double x)
423 {
424 return floor(x + 0.5);
425 }
426
ToDegrees(double radians)427 double ToDegrees(double radians)
428 {
429 return radians * 180.0 / MPI;
430 }
431
CalcClosestPointOnLineSegmentToPoint(const struct vec2 l1,const struct vec2 l2,const struct vec2 p)432 struct vec2 CalcClosestPointOnLineSegmentToPoint(
433 const struct vec2 l1, const struct vec2 l2, const struct vec2 p)
434 {
435 // Using parametric representation, line l1->l2 is
436 // P(t) = l1 + t(l2 - l1)
437 // Projection of point p on line is
438 // t = ((p.x - l1.x)(l2.x - l1.x) + (p.y - l1.y)(l2.y - l1.y)) / ||l2 -
439 // l1||^2
440 const float lineDistanceSquared = svec2_distance_squared(l1, l2);
441 // Early exit since same point means 0 distance, and div by 0
442 if (lineDistanceSquared == 0)
443 {
444 return l1;
445 }
446 const float numerator =
447 (p.x - l1.x) * (l2.x - l1.x) + (p.y - l1.y) * (l2.y - l1.y);
448 const float t = CLAMP(numerator / lineDistanceSquared, 0, 1);
449 const struct vec2 closestPoint =
450 svec2(l1.x + t * (l2.x - l1.x), l1.y + t * (l2.y - l1.y));
451 return closestPoint;
452 }
453
InputDeviceName(const int d,const int deviceIndex)454 const char *InputDeviceName(const int d, const int deviceIndex)
455 {
456 switch (d)
457 {
458 case INPUT_DEVICE_KEYBOARD:
459 return "Keyboard";
460 case INPUT_DEVICE_MOUSE:
461 return "Mouse";
462 case INPUT_DEVICE_JOYSTICK:
463 return JoyName(deviceIndex);
464 case INPUT_DEVICE_AI:
465 return "AI";
466 default:
467 return "";
468 }
469 }
470
AllyCollisionStr(int a)471 const char *AllyCollisionStr(int a)
472 {
473 switch (a)
474 {
475 T2S(ALLYCOLLISION_NORMAL, "Normal");
476 T2S(ALLYCOLLISION_REPEL, "Repel");
477 T2S(ALLYCOLLISION_NONE, "None");
478 default:
479 return "";
480 }
481 }
StrAllyCollision(const char * s)482 int StrAllyCollision(const char *s)
483 {
484 S2T(ALLYCOLLISION_NORMAL, "Normal");
485 S2T(ALLYCOLLISION_REPEL, "Repel");
486 S2T(ALLYCOLLISION_NONE, "None");
487 return ALLYCOLLISION_NORMAL;
488 }
489
IntStr(int i)490 char *IntStr(int i)
491 {
492 static char buf[32];
493 sprintf(buf, "%d", i);
494 return buf;
495 }
PercentStr(int p)496 char *PercentStr(int p)
497 {
498 static char buf[32];
499 sprintf(buf, "%d%%", p);
500 return buf;
501 }
Div8Str(int i)502 char *Div8Str(int i)
503 {
504 static char buf[16];
505 sprintf(buf, "%d", i / 8);
506 return buf;
507 }
CamelToTitle(char * buf,const char * src)508 void CamelToTitle(char *buf, const char *src)
509 {
510 const char *first = src;
511 #define IS_UPPER(_x) ((_x) >= 'A' && (_x) <= 'Z')
512 while (*src)
513 {
514 // Word boundaries marked by capital letters, as long as:
515 // - It's not the first letter, and
516 // - The previous letter is lower case, or
517 // - The next letter is lower case
518 if (IS_UPPER(*src) && src != first &&
519 (!IS_UPPER(*(src - 1)) || (*(src + 1) && !IS_UPPER(*(src + 1)))))
520 {
521 *buf++ = ' ';
522 }
523 *buf++ = *src++;
524 }
525 *buf = '\0';
526 }
527 // From answer by plinth
528 // http://stackoverflow.com/a/744822/2038264
529 // License: http://creativecommons.org/licenses/by-sa/3.0/
530 // Author profile: http://stackoverflow.com/users/20481/plinth
StrEndsWith(const char * str,const char * suffix)531 bool StrEndsWith(const char *str, const char *suffix)
532 {
533 if (str == NULL || suffix == NULL)
534 {
535 return false;
536 }
537 const size_t lenStr = strlen(str);
538 const size_t lenSuffix = strlen(suffix);
539 if (lenSuffix > lenStr)
540 {
541 return false;
542 }
543 return strncmp(str + lenStr - lenSuffix, suffix, lenSuffix) == 0;
544 }
545
546 // From answer by chux
547 // https://stackoverflow.com/a/30734030/2038264
548 // License: http://creativecommons.org/licenses/by-sa/3.0/
Stricmp(const char * a,const char * b)549 int Stricmp(const char *a, const char *b)
550 {
551 int ca, cb;
552 do
553 {
554 ca = (unsigned char)*a++;
555 cb = (unsigned char)*b++;
556 ca = tolower(toupper(ca));
557 cb = tolower(toupper(cb));
558 } while (ca == cb && ca != '\0');
559 return ca - cb;
560 }
561
CompareIntsAsc(const void * v1,const void * v2)562 int CompareIntsAsc(const void *v1, const void *v2)
563 {
564 const int i1 = *(const int *)v1;
565 const int i2 = *(const int *)v2;
566 if (i1 < i2)
567 {
568 return -1;
569 }
570 else if (i1 > i2)
571 {
572 return 1;
573 }
574 return 0;
575 }
CompareIntsDesc(const void * v1,const void * v2)576 int CompareIntsDesc(const void *v1, const void *v2)
577 {
578 const int i1 = *(const int *)v1;
579 const int i2 = *(const int *)v2;
580 if (i1 > i2)
581 {
582 return -1;
583 }
584 else if (i1 < i2)
585 {
586 return 1;
587 }
588 return 0;
589 }
590
IntsEqual(const void * v1,const void * v2)591 bool IntsEqual(const void *v1, const void *v2)
592 {
593 return *(const int *)v1 == *(const int *)v2;
594 }
595
StrBodyPart(const char * s)596 BodyPart StrBodyPart(const char *s)
597 {
598 S2T(BODY_PART_HEAD, "head");
599 S2T(BODY_PART_HAIR, "hair");
600 S2T(BODY_PART_BODY, "body");
601 S2T(BODY_PART_LEGS, "legs");
602 S2T(BODY_PART_GUN_R, "gun_r");
603 S2T(BODY_PART_GUN_L, "gun_l");
604 return BODY_PART_HEAD;
605 }
606
Pulse256(const int t)607 int Pulse256(const int t)
608 {
609 const int pulsePeriod = ConfigGetInt(&gConfig, "Game.FPS") / 2;
610 int alphaUnscaled = (t % pulsePeriod) * 255 / (pulsePeriod / 2);
611 if (alphaUnscaled > 255)
612 {
613 alphaUnscaled = 255 * 2 - alphaUnscaled;
614 }
615 return alphaUnscaled;
616 }
617