1 /*
2 Copyright © 2011-2012 Clint Bellanger
3 Copyright © 2012 Stefan Beller
4 Copyright © 2013 Henrik Andersson
5 Copyright © 2013 Kurt Rinnert
6 Copyright © 2012-2016 Justin Jacobs
7 
8 This file is part of FLARE.
9 
10 FLARE is free software: you can redistribute it and/or modify it under the terms
11 of the GNU General Public License as published by the Free Software Foundation,
12 either version 3 of the License, or (at your option) any later version.
13 
14 FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License along with
19 FLARE.  If not, see http://www.gnu.org/licenses/
20 */
21 
22 #include "Avatar.h"
23 #include "EngineSettings.h"
24 #include "InputState.h"
25 #include "MessageEngine.h"
26 #include "Platform.h"
27 #include "Settings.h"
28 #include "SharedResources.h"
29 #include "Utils.h"
30 #include "UtilsFileSystem.h"
31 #include "UtilsMath.h"
32 #include "UtilsParsing.h"
33 
34 #include <cmath>
35 #include <stdarg.h>
36 #include <ctype.h>
37 #include <iomanip>
38 #include <iostream>
39 #include <locale>
40 #include <string.h>
41 
42 int Utils::LOCK_INDEX = 0;
43 
44 bool Utils::LOG_FILE_INIT = false;
45 bool Utils::LOG_FILE_CREATED = false;
46 std::string Utils::LOG_PATH;
47 std::queue<std::pair<SDL_LogPriority, std::string> > Utils::LOG_MSG;
48 
49 /**
50  * Point: A simple x/y coordinate structure
51  */
Point()52 Point::Point()
53 	: x(0)
54 	, y(0)
55 {}
56 
Point(int _x,int _y)57 Point::Point(int _x, int _y)
58 	: x(_x)
59 	, y(_y)
60 {}
61 
Point(const FPoint & _fp)62 Point::Point(const FPoint& _fp)
63 	: x(static_cast<int>(_fp.x))
64 	, y(static_cast<int>(_fp.y))
65 {}
66 
67 /**
68  * FPoint: A floating point version of Point
69  */
FPoint()70 FPoint::FPoint()
71 	: x(0)
72 	, y(0)
73 {}
74 
FPoint(float _x,float _y)75 FPoint::FPoint(float _x, float _y)
76 	: x(_x)
77 	, y(_y)
78 {}
79 
FPoint(Point _p)80 FPoint::FPoint(Point _p)
81 	: x(static_cast<float>(_p.x))
82 	, y(static_cast<float>(_p.y))
83 {}
84 
align()85 void FPoint::align() {
86 	// this rounds the float values to the nearest multiple of 1/(2^4)
87 	// 1/(2^4) was chosen because it's a "nice" floating point number, removing 99% of rounding errors
88 	x = floorf(x / 0.0625f) * 0.0625f;
89 	y = floorf(y / 0.0625f) * 0.0625f;
90 }
91 
92 /**
93  * Rect: A rectangle defined by the top-left x/y and the width/height
94  */
Rect()95 Rect::Rect()
96 	: x(0)
97 	, y(0)
98 	, w(0)
99 	, h(0)
100 {}
101 
Rect(int _x,int _y,int _w,int _h)102 Rect::Rect(int _x, int _y, int _w, int _h)
103 	: x(_x)
104 	, y(_y)
105 	, w(_w)
106 	, h(_h)
107 {}
108 
Rect(const SDL_Rect & _r)109 Rect::Rect(const SDL_Rect& _r)
110 	: x(_r.x)
111 	, y(_r.y)
112 	, w(_r.w)
113 	, h(_r.h)
114 {}
115 
operator SDL_Rect() const116 Rect::operator SDL_Rect() const {
117 	SDL_Rect r;
118 	r.x = x;
119 	r.y = y;
120 	r.w = w;
121 	r.h = h;
122 	return r;
123 }
124 
125 /**
126  * Color: RGBA color; defaults to 100% opaque black
127  */
Color()128 Color::Color()
129 	: r(0)
130 	, g(0)
131 	, b(0)
132 	, a(255)
133 {}
134 
Color(Uint8 _r,Uint8 _g,Uint8 _b,Uint8 _a)135 Color::Color(Uint8 _r, Uint8 _g, Uint8 _b, Uint8 _a)
136 	: r(_r)
137 	, g(_g)
138 	, b(_b)
139 	, a(_a)
140 {}
141 
operator SDL_Color() const142 Color::operator SDL_Color() const {
143 	SDL_Color c;
144 	c.r = r;
145 	c.g = g;
146 	c.b = b;
147 	c.a = a;
148 	return c;
149 }
150 
operator ==(const Color & other)151 bool Color::operator ==(const Color &other) {
152 	return r == other.r && g == other.g && b == other.b && a == other.a;
153 }
154 
operator !=(const Color & other)155 bool Color::operator !=(const Color &other) {
156 	return !((*this) == other);
157 }
158 
encodeRGBA()159 uint32_t Color::encodeRGBA() {
160 	uint32_t result = static_cast<uint32_t>(a);
161 	result |= static_cast<uint32_t>(r) << 24;
162 	result |= static_cast<uint32_t>(g) << 16;
163 	result |= static_cast<uint32_t>(b) << 8;
164 	return result;
165 }
166 
decodeRGBA(const uint32_t encoded)167 void Color::decodeRGBA(const uint32_t encoded) {
168 	a = static_cast<uint8_t>(encoded);
169 	r = static_cast<uint8_t>(encoded >> 24);
170 	g = static_cast<uint8_t>(encoded >> 16);
171 	b = static_cast<uint8_t>(encoded >> 8);
172 }
173 
Timer(unsigned _duration)174 Timer::Timer(unsigned _duration)
175 	: current(0)
176 	, duration(_duration)
177 {
178 }
179 
getCurrent()180 unsigned Timer::getCurrent() {
181 	return current;
182 }
183 
getDuration()184 unsigned Timer::getDuration() {
185 	return duration;
186 }
187 
setCurrent(unsigned val)188 void Timer::setCurrent(unsigned val) {
189 	current = val;
190 	if (current > duration)
191 		current = duration;
192 }
193 
setDuration(unsigned val)194 void Timer::setDuration(unsigned val) {
195 	current = duration = val;
196 }
197 
tick()198 bool Timer::tick() {
199 	if (current > 0)
200 		current--;
201 
202 	if (current == 0)
203 		return true;
204 
205 	return false;
206 }
207 
isEnd()208 bool Timer::isEnd() {
209 	return current == 0;
210 }
211 
isBegin()212 bool Timer::isBegin() {
213 	return current == duration;
214 }
215 
reset(int type)216 void Timer::reset(int type) {
217 	if (type == Timer::END)
218 		current = 0;
219 	else if (type == Timer::BEGIN)
220 		current = duration;
221 }
222 
isWholeSecond()223 bool Timer::isWholeSecond() {
224 	return current % settings->max_frames_per_sec == 0;
225 }
226 
screenToMap(int x,int y,float camx,float camy)227 FPoint Utils::screenToMap(int x, int y, float camx, float camy) {
228 	FPoint r;
229 	if (eset->tileset.orientation == eset->tileset.TILESET_ISOMETRIC) {
230 		float scrx = float(x - settings->view_w_half) * 0.5f;
231 		float scry = float(y - settings->view_h_half) * 0.5f;
232 
233 		r.x = (eset->tileset.units_per_pixel_x * scrx) + (eset->tileset.units_per_pixel_y * scry) + camx;
234 		r.y = (eset->tileset.units_per_pixel_y * scry) - (eset->tileset.units_per_pixel_x * scrx) + camy;
235 	}
236 	else {
237 		r.x = static_cast<float>(x - settings->view_w_half) * (eset->tileset.units_per_pixel_x) + camx;
238 		r.y = static_cast<float>(y - settings->view_h_half) * (eset->tileset.units_per_pixel_y) + camy;
239 	}
240 	return r;
241 }
242 
243 /**
244  * Returns a point (in map units) of a given (x,y) tupel on the screen
245  * when the camera is at a given position.
246  */
mapToScreen(float x,float y,float camx,float camy)247 Point Utils::mapToScreen(float x, float y, float camx, float camy) {
248 	Point r;
249 
250 	// adjust to the center of the viewport
251 	// we do this calculation first to avoid negative integer division
252 	float adjust_x = (settings->view_w_half + 0.5f) * eset->tileset.units_per_pixel_x;
253 	float adjust_y = (settings->view_h_half + 0.5f) * eset->tileset.units_per_pixel_y;
254 
255 	if (eset->tileset.orientation == eset->tileset.TILESET_ISOMETRIC) {
256 		r.x = int(floorf(((x - camx - y + camy + adjust_x)/eset->tileset.units_per_pixel_x)+0.5f));
257 		r.y = int(floorf(((x - camx + y - camy + adjust_y)/eset->tileset.units_per_pixel_y)+0.5f));
258 	}
259 	else if (eset->tileset.orientation == eset->tileset.TILESET_ORTHOGONAL) {
260 		r.x = int((x - camx + adjust_x)/eset->tileset.units_per_pixel_x);
261 		r.y = int((y - camy + adjust_y)/eset->tileset.units_per_pixel_y);
262 	}
263 	return r;
264 }
265 
266 /**
267  * Apply parameter distance to position and direction
268  */
calcVector(const FPoint & pos,int direction,float dist)269 FPoint Utils::calcVector(const FPoint& pos, int direction, float dist) {
270 	FPoint p;
271 	p.x = pos.x;
272 	p.y = pos.y;
273 
274 	float dist_straight = dist;
275 	float dist_diag = dist * 0.7071f; //  1/sqrt(2)
276 
277 	switch (direction) {
278 		case 0:
279 			p.x -= dist_diag;
280 			p.y += dist_diag;
281 			break;
282 		case 1:
283 			p.x -= dist_straight;
284 			break;
285 		case 2:
286 			p.x -= dist_diag;
287 			p.y -= dist_diag;
288 			break;
289 		case 3:
290 			p.y -= dist_straight;
291 			break;
292 		case 4:
293 			p.x += dist_diag;
294 			p.y -= dist_diag;
295 			break;
296 		case 5:
297 			p.x += dist_straight;
298 			break;
299 		case 6:
300 			p.x += dist_diag;
301 			p.y += dist_diag;
302 			break;
303 		case 7:
304 			p.y += dist_straight;
305 			break;
306 	}
307 	return p;
308 }
309 
calcDist(const FPoint & p1,const FPoint & p2)310 float Utils::calcDist(const FPoint& p1, const FPoint& p2) {
311 	return sqrtf((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
312 }
313 
314 /**
315  * is target within the area defined by center and radius?
316  */
isWithinRadius(const FPoint & center,float radius,const FPoint & target)317 bool Utils::isWithinRadius(const FPoint& center, float radius, const FPoint& target) {
318 	return (calcDist(center, target) < radius);
319 }
320 
321 /**
322  * is target within the area defined by rectangle r?
323  */
isWithinRect(const Rect & r,const Point & target)324 bool Utils::isWithinRect(const Rect& r, const Point& target) {
325 	return target.x >= r.x && target.y >= r.y && target.x < r.x+r.w && target.y < r.y+r.h;
326 }
327 
calcDirection(float x0,float y0,float x1,float y1)328 unsigned char Utils::calcDirection(float x0, float y0, float x1, float y1) {
329 	float theta = calcTheta(x0, y0, x1, y1);
330 	float val = theta / (static_cast<float>(M_PI)/4);
331 	int dir = static_cast<int>(((val < 0) ? ceilf(val-0.5f) : floorf(val+0.5f)) + 4);
332 	dir = (dir + 1) % 8;
333 	if (dir >= 0 && dir < 8)
334 		return static_cast<unsigned char>(dir);
335 	else
336 		return 0;
337 }
338 
339 // convert cartesian to polar theta where (x1,x2) is the origin
calcTheta(float x1,float y1,float x2,float y2)340 float Utils::calcTheta(float x1, float y1, float x2, float y2) {
341 	// calculate base angle
342 	float dx = x2 - x1;
343 	float dy = y2 - y1;
344 	float exact_dx = x2 - x1;
345 	float theta;
346 
347 	// convert cartesian to polar coordinates
348 	if (exact_dx == 0) {
349 		if (dy > 0.0) theta = static_cast<float>(M_PI)/2.0f;
350 		else theta = static_cast<float>(-M_PI)/2.0f;
351 	}
352 	else {
353 		theta = atanf(dy/dx);
354 		if (dx < 0.0 && dy >= 0.0) theta += static_cast<float>(M_PI);
355 		if (dx < 0.0 && dy < 0.0) theta -= static_cast<float>(M_PI);
356 	}
357 	return theta;
358 }
359 
abbreviateKilo(int amount)360 std::string Utils::abbreviateKilo(int amount) {
361 	std::stringstream ss;
362 	if (amount < 1000)
363 		ss << amount;
364 	else
365 		ss << (amount/1000) << msg->get("k");
366 
367 	return ss.str();
368 }
369 
alignToScreenEdge(int alignment,Rect * r)370 void Utils::alignToScreenEdge(int alignment, Rect *r) {
371 	if (!r) return;
372 
373 	if (alignment == ALIGN_TOPLEFT) {
374 		// do nothing
375 	}
376 	else if (alignment == ALIGN_TOP) {
377 		r->x = (settings->view_w_half - r->w/2) + r->x;
378 	}
379 	else if (alignment == ALIGN_TOPRIGHT) {
380 		r->x = (settings->view_w - r->w) + r->x;
381 	}
382 	else if (alignment == ALIGN_LEFT) {
383 		r->y = (settings->view_h_half - r->h/2) + r->y;
384 	}
385 	else if (alignment == ALIGN_CENTER) {
386 		r->x = (settings->view_w_half - r->w/2) + r->x;
387 		r->y = (settings->view_h_half - r->h/2) + r->y;
388 	}
389 	else if (alignment == ALIGN_RIGHT) {
390 		r->x = (settings->view_w - r->w) + r->x;
391 		r->y = (settings->view_h_half - r->h/2) + r->y;
392 	}
393 	else if (alignment == ALIGN_BOTTOMLEFT) {
394 		r->y = (settings->view_h - r->h) + r->y;
395 	}
396 	else if (alignment == ALIGN_BOTTOM) {
397 		r->x = (settings->view_w_half - r->w/2) + r->x;
398 		r->y = (settings->view_h - r->h) + r->y;
399 	}
400 	else if (alignment == ALIGN_BOTTOMRIGHT) {
401 		r->x = (settings->view_w - r->w) + r->x;
402 		r->y = (settings->view_h - r->h) + r->y;
403 	}
404 	else if (alignment == ALIGN_FRAME_TOPLEFT) {
405 		r->x = ((settings->view_w - eset->resolutions.frame_w)/2) + r->x;
406 		r->y = ((settings->view_h - eset->resolutions.frame_h)/2) + r->y;
407 	}
408 	else if (alignment == ALIGN_FRAME_TOP) {
409 		r->x = ((settings->view_w - eset->resolutions.frame_w)/2) + (eset->resolutions.frame_w/2 - r->w/2) + r->x;
410 		r->y = ((settings->view_h - eset->resolutions.frame_h)/2) + r->y;
411 	}
412 	else if (alignment == ALIGN_FRAME_TOPRIGHT) {
413 		r->x = ((settings->view_w - eset->resolutions.frame_w)/2) + (eset->resolutions.frame_w - r->w) + r->x;
414 		r->y = ((settings->view_h - eset->resolutions.frame_h)/2) + r->y;
415 	}
416 	else if (alignment == ALIGN_FRAME_LEFT) {
417 		r->x = ((settings->view_w - eset->resolutions.frame_w)/2) + r->x;
418 		r->y = ((settings->view_h - eset->resolutions.frame_h)/2) + (eset->resolutions.frame_h/2 - r->h/2) + r->y;
419 	}
420 	else if (alignment == ALIGN_FRAME_CENTER) {
421 		r->x = ((settings->view_w - eset->resolutions.frame_w)/2) + (eset->resolutions.frame_w/2 - r->w/2) + r->x;
422 		r->y = ((settings->view_h - eset->resolutions.frame_h)/2) + (eset->resolutions.frame_h/2 - r->h/2) + r->y;
423 	}
424 	else if (alignment == ALIGN_FRAME_RIGHT) {
425 		r->x = ((settings->view_w - eset->resolutions.frame_w)/2) + (eset->resolutions.frame_w - r->w) + r->x;
426 		r->y = ((settings->view_h - eset->resolutions.frame_h)/2) + (eset->resolutions.frame_h/2 - r->h/2) + r->y;
427 	}
428 	else if (alignment == ALIGN_FRAME_BOTTOMLEFT) {
429 		r->x = ((settings->view_w - eset->resolutions.frame_w)/2) + r->x;
430 		r->y = ((settings->view_h - eset->resolutions.frame_h)/2) + (eset->resolutions.frame_h - r->h) + r->y;
431 	}
432 	else if (alignment == ALIGN_FRAME_BOTTOM) {
433 		r->x = ((settings->view_w - eset->resolutions.frame_w)/2) + (eset->resolutions.frame_w/2 - r->w/2) + r->x;
434 		r->y = ((settings->view_h - eset->resolutions.frame_h)/2) + (eset->resolutions.frame_h - r->h) + r->y;
435 	}
436 	else if (alignment == ALIGN_FRAME_BOTTOMRIGHT) {
437 		r->x = ((settings->view_w - eset->resolutions.frame_w)/2) + (eset->resolutions.frame_w - r->w) + r->x;
438 		r->y = ((settings->view_h - eset->resolutions.frame_h)/2) + (eset->resolutions.frame_h - r->h) + r->y;
439 	}
440 	else {
441 		// do nothing
442 	}
443 }
444 
445 /**
446  * These functions provide a unified way to log messages, printf-style
447  */
logInfo(const char * format,...)448 void Utils::logInfo(const char* format, ...) {
449 	va_list args;
450 
451 	va_start(args, format);
452 	SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, format, args);
453 	va_end(args);
454 
455 	char file_buf[BUFSIZ];
456 	va_start(args, format);
457 	vsnprintf(file_buf, BUFSIZ, format, args);
458 	va_end(args);
459 
460 	if (!LOG_FILE_INIT) {
461 		LOG_MSG.push(std::pair<SDL_LogPriority, std::string>(SDL_LOG_PRIORITY_INFO, std::string(file_buf)));
462 	}
463 	else if (LOG_FILE_CREATED) {
464 		FILE *log_file = fopen(LOG_PATH.c_str(), "a");
465 		if (log_file) {
466 			fprintf(log_file, "INFO: ");
467 			fprintf(log_file, "%s", file_buf);
468 			fprintf(log_file, "\n");
469 			fclose(log_file);
470 		}
471 	}
472 
473 }
474 
logError(const char * format,...)475 void Utils::logError(const char* format, ...) {
476 	va_list args;
477 
478 	va_start(args, format);
479 	SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_ERROR, format, args);
480 	va_end(args);
481 
482 	char file_buf[BUFSIZ];
483 	va_start(args, format);
484 	vsnprintf(file_buf, BUFSIZ, format, args);
485 	va_end(args);
486 
487 	if (!LOG_FILE_INIT) {
488 		LOG_MSG.push(std::pair<SDL_LogPriority, std::string>(SDL_LOG_PRIORITY_ERROR, std::string(file_buf)));
489 	}
490 	else if (LOG_FILE_CREATED) {
491 		FILE *log_file = fopen(LOG_PATH.c_str(), "a");
492 		if (log_file) {
493 			fprintf(log_file, "ERROR: ");
494 			fprintf(log_file, "%s", file_buf);
495 			fprintf(log_file, "\n");
496 			fclose(log_file);
497 		}
498 	}
499 }
500 
logErrorDialog(const char * dialog_text,...)501 void Utils::logErrorDialog(const char* dialog_text, ...) {
502 	char pre_buf[BUFSIZ];
503 	char buf[BUFSIZ];
504 	snprintf(pre_buf, BUFSIZ, "%s%s", "FLARE Error\n", dialog_text);
505 
506 	va_list args;
507 	va_start(args, dialog_text);
508 	vsnprintf(buf, BUFSIZ, pre_buf, args);
509 	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "FLARE Error", buf, NULL);
510 	va_end(args);
511 }
512 
createLogFile()513 void Utils::createLogFile() {
514 	LOG_PATH = settings->path_conf + "/flare_log.txt";
515 
516 	// always create a new log file on each launch
517 	if (Filesystem::fileExists(LOG_PATH)) {
518 		Filesystem::removeFile(LOG_PATH);
519 	}
520 
521 	FILE *log_file = fopen(LOG_PATH.c_str(), "w+");
522 	if (log_file) {
523 		LOG_FILE_CREATED = true;
524 		fprintf(log_file, "### Flare log file\n\n");
525 
526 		while (!LOG_MSG.empty()) {
527 			if (LOG_MSG.front().first == SDL_LOG_PRIORITY_INFO)
528 				fprintf(log_file, "INFO: ");
529 			else if (LOG_MSG.front().first == SDL_LOG_PRIORITY_ERROR)
530 				fprintf(log_file, "ERROR: ");
531 
532 			fprintf(log_file, "%s", LOG_MSG.front().second.c_str());
533 			fprintf(log_file, "\n");
534 
535 			LOG_MSG.pop();
536 		}
537 		fclose(log_file);
538 	}
539 	else {
540 		while (!LOG_MSG.empty())
541 			LOG_MSG.pop();
542 
543 		logError("Utils: Could not create log file.");
544 	}
545 
546 	LOG_FILE_INIT = true;
547 }
548 
Exit(int code)549 void Utils::Exit(int code) {
550 	SDL_Quit();
551 	lockFileWrite(-1);
552 	exit(code);
553 }
554 
createSaveDir(int slot)555 void Utils::createSaveDir(int slot) {
556 	// game slots are currently 1-4
557 	if (slot == 0) return;
558 
559 	std::stringstream ss;
560 	ss << settings->path_user << "saves/" << eset->misc.save_prefix << "/";
561 
562 	Filesystem::createDir(ss.str());
563 
564 	ss << slot;
565 	Filesystem::createDir(ss.str());
566 }
567 
removeSaveDir(int slot)568 void Utils::removeSaveDir(int slot) {
569 	// game slots are currently 1-4
570 	if (slot == 0) return;
571 
572 	std::stringstream ss;
573 	ss << settings->path_user << "saves/" << eset->misc.save_prefix << "/" << slot;
574 
575 	if (Filesystem::isDirectory(ss.str())) {
576 		Filesystem::removeDirRecursive(ss.str());
577 	}
578 }
579 
resizeToScreen(int w,int h,bool crop,int align)580 Rect Utils::resizeToScreen(int w, int h, bool crop, int align) {
581 	Rect r;
582 
583 	// fit to height
584 	float ratio = settings->view_h / static_cast<float>(h);
585 	r.w = static_cast<int>(static_cast<float>(w) * ratio);
586 	r.h = settings->view_h;
587 
588 	if (!crop) {
589 		// fit to width
590 		if (r.w > settings->view_w) {
591 			ratio = settings->view_w / static_cast<float>(w);
592 			r.h = static_cast<int>(static_cast<float>(h) * ratio);
593 			r.w = settings->view_w;
594 		}
595 	}
596 
597 	alignToScreenEdge(align, &r);
598 
599 	return r;
600 }
601 
stringFindCaseInsensitive(const std::string & _a,const std::string & _b)602 size_t Utils::stringFindCaseInsensitive(const std::string &_a, const std::string &_b) {
603 	std::string a;
604 	std::string b;
605 
606 	for (size_t i=0; i<_a.size(); ++i) {
607 		a += static_cast<char>(tolower(static_cast<int>(_a[i])));
608 	}
609 
610 	for (size_t i=0; i<_b.size(); ++i) {
611 		b += static_cast<char>(tolower(static_cast<int>(_b[i])));
612 	}
613 
614 	return a.find(b);
615 }
616 
floatToString(const float value,size_t precision)617 std::string Utils::floatToString(const float value, size_t precision) {
618 	std::stringstream ss;
619 	ss << value;
620 	std::string temp = ss.str();
621 
622 	size_t decimal = temp.find(".");
623 	if (decimal != std::string::npos && temp.length() > decimal + precision + 1) {
624 		temp = temp.substr(0, decimal + precision + 1);
625 	}
626 
627 	return temp;
628 }
629 
getDurationString(const int duration,size_t precision)630 std::string Utils::getDurationString(const int duration, size_t precision) {
631 	float real_duration = static_cast<float>(duration) / settings->max_frames_per_sec;
632 	std::string temp = floatToString(real_duration, precision);
633 
634 	if (real_duration == 1.f) {
635 		return msg->get("%s second", temp);
636 	}
637 	else {
638 		return msg->get("%s seconds", temp);
639 	}
640 }
641 
substituteVarsInString(const std::string & _s,Avatar * avatar)642 std::string Utils::substituteVarsInString(const std::string &_s, Avatar* avatar) {
643 	std::string s = _s;
644 
645 	size_t begin = s.find("${");
646 	while (begin != std::string::npos) {
647 		size_t end = s.find("}");
648 
649 		if (end == std::string::npos)
650 			break;
651 
652 		size_t var_len = end-begin+1;
653 		std::string var = s.substr(begin,var_len);
654 
655 		if (avatar && var == "${AVATAR_NAME}") {
656 			s.replace(begin, var_len, avatar->stats.name);
657 		}
658 		else if (avatar && var == "${AVATAR_CLASS}") {
659 			s.replace(begin, var_len, avatar->stats.getShortClass());
660 		}
661 		else if (var == "${INPUT_MOVEMENT}") {
662 			s.replace(begin, var_len, inpt->getMovementString());
663 		}
664 		else if (var == "${INPUT_ATTACK}") {
665 			s.replace(begin, var_len, inpt->getAttackString());
666 		}
667 		else if (var == "${INPUT_CONTINUE}") {
668 			s.replace(begin, var_len, inpt->getContinueString());
669 		}
670 		else {
671 			logError("'%s' is not a valid string variable name.", var.c_str());
672 			// strip the brackets from the variable
673 			s.replace(begin, var_len, var.substr(2, var.length()-3));
674 		}
675 
676 		begin = s.find("${");
677 	}
678 
679 	return s;
680 }
681 
682 /**
683  * Keep two points within a certain range
684  */
clampDistance(float range,const FPoint & src,const FPoint & target)685 FPoint Utils::clampDistance(float range, const FPoint& src, const FPoint& target) {
686 	FPoint limit_target = target;
687 
688 	if (range > 0) {
689 		if (src.x+range < target.x)
690 			limit_target.x = src.x+range;
691 		if (src.x-range > target.x)
692 			limit_target.x = src.x-range;
693 		if (src.y+range < target.y)
694 			limit_target.y = src.y+range;
695 		if (src.y-range > target.y)
696 			limit_target.y = src.y-range;
697 	}
698 
699 	return limit_target;
700 }
701 
702 /**
703  * Compares two rectangles and returns true if they overlap
704  */
rectsOverlap(const Rect & a,const Rect & b)705 bool Utils::rectsOverlap(const Rect &a, const Rect &b) {
706 	Point a_1(a.x, a.y);
707 	Point a_2(a.x + a.w, a.y);
708 	Point a_3(a.x, a.y + a.h);
709 	Point a_4(a.x + a.w, a.y + a.h);
710 
711 	Point b_1(b.x, b.y);
712 	Point b_2(b.x + b.w, b.y);
713 	Point b_3(b.x, b.y + b.h);
714 	Point b_4(b.x + b.w, b.y + b.h);
715 
716 	bool a_in_b = isWithinRect(b, a_1) || isWithinRect(b, a_2) || isWithinRect(b, a_3) || isWithinRect(b, a_4);
717 	bool b_in_a = isWithinRect(a, b_1) || isWithinRect(a, b_2) || isWithinRect(a, b_3) || isWithinRect(a, b_4);
718 
719 	return a_in_b || b_in_a;
720 }
721 
rotateDirection(int direction,int val)722 int Utils::rotateDirection(int direction, int val) {
723 	direction += val;
724 	if (direction > 7)
725 		direction -= 7;
726 	else if (direction < 0)
727 		direction += 7;
728 
729 	return direction;
730 }
731 
getTimeString(const unsigned long time)732 std::string Utils::getTimeString(const unsigned long time) {
733 	std::stringstream ss;
734 	unsigned long hours = (time / 60) / 60;
735 	if (hours < 100)
736 		ss << std::setfill('0') << std::setw(2) << hours;
737 	else
738 		ss << hours;
739 
740 	ss << ":";
741 	unsigned long minutes = (time / 60) % 60;
742 	ss << std::setfill('0') << std::setw(2) << minutes;
743 
744 	ss << ":";
745 	unsigned long seconds = time % 60;
746 	ss << std::setfill('0') << std::setw(2) << seconds;
747 
748 	return ss.str();
749 }
750 
hashString(const std::string & str)751 unsigned long Utils::hashString(const std::string& str) {
752 	std::locale loc;
753 	const std::collate<char>& coll = std::use_facet<std::collate<char> >(loc);
754 	return coll.hash(str.data(), str.data() + str.length());
755 }
756 
strdup(const std::string & str)757 char* Utils::strdup(const std::string& str) {
758 	size_t length = str.length() + 1;
759 	char *x = static_cast<char*>(malloc(length));
760 	if (!x)
761 		return NULL;
762 	memcpy(x, str.c_str(), length);
763 	return x;
764 }
765 
lockFileRead()766 void Utils::lockFileRead() {
767 	if (!platform.has_lock_file)
768 		return;
769 
770 	std::string lock_file_path = Filesystem::convertSlashes(settings->path_conf + "flare_lock");
771 
772 	std::ifstream infile;
773 	infile.open(lock_file_path.c_str(), std::ios::in);
774 
775 	while (infile.good()) {
776 		std::string line = Parse::getLine(infile);
777 
778 		if (line.length() == 0 || line.at(0) == '#')
779 			continue;
780 
781 		LOCK_INDEX = Parse::toInt(line);
782 	}
783 
784 	infile.close();
785 	infile.clear();
786 
787 	if (LOCK_INDEX < 0)
788 		LOCK_INDEX = 0;
789 }
790 
lockFileWrite(int increment)791 void Utils::lockFileWrite(int increment) {
792 	if (!platform.has_lock_file)
793 		return;
794 
795 	std::string lock_file_path = settings->path_conf + "flare_lock";
796 
797 	if (increment < 0) {
798 		if (LOCK_INDEX == 0)
799 			return;
800 
801 		// refresh LOCK_INDEX in case any other instances were closed while this instance was running
802 		lockFileRead();
803 	}
804 
805 	std::ofstream outfile;
806 	outfile.open(lock_file_path.c_str(), std::ios::out);
807 	if (outfile.is_open()) {
808 		LOCK_INDEX += increment;
809 		outfile << "# Flare lock file. Counts instances of Flare" << std::endl;
810 		outfile << LOCK_INDEX << std::endl;
811 		outfile.close();
812 		outfile.clear();
813 	}
814 }
815 
lockFileCheck()816 void Utils::lockFileCheck() {
817 	if (!platform.has_lock_file)
818 		return;
819 
820 	LOCK_INDEX = 0;
821 
822 	lockFileRead();
823 
824 	if (LOCK_INDEX > 0){
825 		const SDL_MessageBoxButtonData buttons[] = {
826 			{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT|SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Quit" },
827 			{ 0, 1, "Continue" },
828 			{ 0, 2, "Reset" },
829 			{ 0, 3, "Safe Video" },
830 		};
831 		const SDL_MessageBoxData messageboxdata = {
832 			SDL_MESSAGEBOX_INFORMATION,
833 			NULL,
834 			"Flare",
835 			"Flare is unable to launch properly. This may be because it did not exit properly, or because there is another instance running.\n\nIf Flare crashed, it is recommended to try 'Safe Video' mode. This will try launching Flare with the minimum video settings.\n\nIf Flare is already running, you may:\n- 'Quit' Flare (safe, recommended)\n- 'Continue' to launch another copy of Flare.\n- 'Reset' the counter which tracks the number of copies of Flare that are currently running.\n  If this dialog is shown every time you launch Flare, this option should fix it.",
836 			static_cast<int>(SDL_arraysize(buttons)),
837 			buttons,
838 			NULL
839 		};
840 		int buttonid = 0;
841 		SDL_ShowMessageBox(&messageboxdata, &buttonid);
842 		if (buttonid == 0) {
843 			lockFileWrite(1);
844 			Exit(1);
845 		}
846 		else if (buttonid == 2) {
847 			LOCK_INDEX = 0;
848 		}
849 		else if (buttonid == 3) {
850 			LOCK_INDEX = 0;
851 			settings->safe_video = true;
852 		}
853 	}
854 
855 	lockFileWrite(1);
856 }
857 
858