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