1 ////////////////////////////////////////////////////////////////////////////////
2 // Copyright (C) 2004-2011 by The Allacrost Project
3 // Copyright (C) 2012-2016 by Bertram (Valyria Tear)
4 // All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software
7 // and you may modify it and/or redistribute it under the terms of this license.
8 // See http://www.gnu.org/copyleft/gpl.html for details.
9 ////////////////////////////////////////////////////////////////////////////////
10
11 /** ****************************************************************************
12 *** \file system.cpp
13 *** \author Tyler Olsen, roots@allacrost.org
14 *** \author Andy Gardner, chopperdave@allacrost.org
15 *** \author Yohann Ferreira, yohann ferreira orange fr
16 *** \brief Source file for system code management
17 *** ***************************************************************************/
18
19 #include "engine/system.h"
20
21 #include "script/script.h"
22
23 #include "utils/utils_strings.h"
24 #include "utils/utils_files.h"
25
26 #include "common/app_name.h"
27 #include "common/app_settings.h"
28 #include "common/gui/textbox.h"
29
30 #include "mode_manager.h"
31
32 // Gettext
33 #ifndef DISABLE_TRANSLATIONS
34 #include <libintl.h>
35 #endif
36
37 #ifdef _WIN32
38 #include <direct.h>
39 #include <windows.h>
40 #endif
41
42 using namespace vt_common;
43 using namespace vt_utils;
44 using namespace vt_script;
45 using namespace vt_mode_manager;
46
47 namespace vt_system
48 {
49
50 SystemEngine *SystemManager = nullptr;
51 bool SYSTEM_DEBUG = false;
52
53 const std::string LANGUAGE_FILE = "data/config/languages.lua";
54
55 // If gettext translations are disabled, let's define a dummy gettext.
56 #ifdef DISABLE_TRANSLATIONS
gettext(const char * text)57 const char* gettext(const char *text)
58 {
59 return text;
60 }
61 #endif
62
Translate(const std::string & text)63 std::string Translate(const std::string& text)
64 {
65 // Don't translate an empty string as it will return the PO meta data.
66 if (text.empty())
67 return std::string();
68 return std::string(gettext(text.c_str()));
69 }
70
UTranslate(const std::string & text)71 ustring UTranslate(const std::string& text)
72 {
73 // Don't translate an empty string as it will return the PO meta data.
74 if (text.empty())
75 return ustring();
76 return MakeUnicodeString(Translate(text));
77 }
78
79 // Use: context|text
CTranslate(const std::string & text)80 std::string CTranslate(const std::string& text)
81 {
82 // Don't translate an empty string as it will return the PO meta data.
83 if (text.empty())
84 return std::string();
85
86 std::string translation = gettext(text.c_str());
87
88 size_t sep_id = translation.find_first_of('|', 0);
89
90 // No separator found or is the last character
91 if (sep_id == std::string::npos || sep_id == translation.size())
92 return translation;
93
94 return translation.substr(sep_id + 1);
95 }
96
CUTranslate(const std::string & text)97 ustring CUTranslate(const std::string& text)
98 {
99 // Don't translate an empty string as it will return the PO meta data.
100 if (text.empty())
101 return ustring();
102
103 return MakeUnicodeString(CTranslate(text));
104 }
105
106 // Inner templated VTranslate functions
_VTranslate(const std::string & text,const T & arg1)107 template<typename T> std::string _VTranslate(const std::string& text, const T& arg1)
108 {
109 // Don't translate an empty string as it will return the PO meta data.
110 if (text.empty())
111 return std::string();
112
113 std::string translation = gettext(text.c_str());
114
115 translation = strprintf(translation.c_str(), arg1);
116
117 return translation;
118 }
119
_VTranslate(const std::string & text,const T & arg1,const T & arg2)120 template<typename T> std::string _VTranslate(const std::string& text, const T& arg1, const T& arg2)
121 {
122 // Don't translate an empty string as it will return the PO meta data.
123 if (text.empty())
124 return std::string();
125
126 std::string translation = gettext(text.c_str());
127
128 translation = strprintf(translation.c_str(), arg1, arg2);
129
130 return translation;
131 }
132
_VTranslate(const std::string & text,const T & arg1,const T & arg2,const T & arg3)133 template<typename T> std::string _VTranslate(const std::string& text, const T& arg1, const T& arg2, const T& arg3)
134 {
135 // Don't translate an empty string as it will return the PO meta data.
136 if (text.empty()) {
137 return std::string();
138 }
139
140 std::string translation = gettext(text.c_str());
141 translation = strprintf(translation.c_str(), arg1, arg2, arg3);
142 return translation;
143 }
144
VTranslate(const std::string & text,int32_t arg1)145 std::string VTranslate(const std::string& text, int32_t arg1)
146 { return _VTranslate(text, arg1); }
VTranslate(const std::string & text,uint32_t arg1)147 std::string VTranslate(const std::string& text, uint32_t arg1)
148 { return _VTranslate(text, arg1); }
VTranslate(const std::string & text,const std::string & arg1)149 std::string VTranslate(const std::string& text, const std::string& arg1)
150 { return _VTranslate(text, arg1.c_str()); }
VTranslate(const std::string & text,float arg1)151 std::string VTranslate(const std::string& text, float arg1)
152 { return _VTranslate(text, arg1); }
VTranslate(const std::string & text,uint32_t arg1,uint32_t arg2)153 std::string VTranslate(const std::string& text, uint32_t arg1, uint32_t arg2)
154 { return _VTranslate(text, arg1, arg2); }
VTranslate(const std::string & text,const std::string & arg1,const std::string & arg2)155 std::string VTranslate(const std::string& text, const std::string& arg1, const std::string& arg2)
156 { return _VTranslate(text, arg1.c_str(), arg2.c_str()); }
VTranslate(const std::string & text,const std::string & arg1,const std::string & arg2,const std::string & arg3)157 std::string VTranslate(const std::string& text, const std::string& arg1, const std::string& arg2, const std::string& arg3)
158 { return _VTranslate(text, arg1.c_str(), arg2.c_str(), arg3.c_str()); }
159
NVTranslate(const std::string & singular,const std::string & plural,const uint32_t number)160 std::string NVTranslate(const std::string& singular,
161 const std::string& plural,
162 const uint32_t number) {
163 // Don't translate an empty string as it will return the PO meta data.
164 if (singular.empty() || plural.empty())
165 return std::string();
166 std::string translation = ngettext(singular.c_str(), plural.c_str(), number);
167 translation = strprintf(translation.c_str(), number);
168 return translation;
169 }
170
NVTranslate(const std::string & singular,const std::string & plural,const uint32_t number,const std::string & str)171 std::string NVTranslate(const std::string& singular,
172 const std::string& plural,
173 const uint32_t number,
174 const std::string& str) {
175 // Don't translate an empty string as it will return the PO meta data.
176 if (singular.empty() || plural.empty())
177 return std::string();
178 std::string translation = ngettext(singular.c_str(), plural.c_str(), number);
179 translation = strprintf(translation.c_str(), number, str.c_str());
180 return translation;
181 }
182
183 // -----------------------------------------------------------------------------
184 // SystemTimer Class
185 // -----------------------------------------------------------------------------
186
SystemTimer()187 SystemTimer::SystemTimer() :
188 _state(SYSTEM_TIMER_INVALID),
189 _auto_update(false),
190 _duration(0),
191 _number_loops(0),
192 _mode_owner(nullptr),
193 _time_expired(0),
194 _times_completed(0)
195 {}
196
SystemTimer(uint32_t duration,int32_t loops)197 SystemTimer::SystemTimer(uint32_t duration, int32_t loops) :
198 _state(SYSTEM_TIMER_INITIAL),
199 _auto_update(false),
200 _duration(duration),
201 _number_loops(loops),
202 _mode_owner(nullptr),
203 _time_expired(0),
204 _times_completed(0)
205 {}
206
~SystemTimer()207 SystemTimer::~SystemTimer()
208 {
209 if(_auto_update) {
210 SystemManager->RemoveAutoTimer(this);
211 }
212 }
213
Initialize(uint32_t duration,int32_t number_loops)214 void SystemTimer::Initialize(uint32_t duration, int32_t number_loops)
215 {
216 _state = SYSTEM_TIMER_INITIAL;
217 _duration = duration;
218 _number_loops = number_loops;
219 _time_expired = 0;
220 _times_completed = 0;
221 }
222
EnableAutoUpdate(GameMode * owner)223 void SystemTimer::EnableAutoUpdate(GameMode *owner)
224 {
225 if(_auto_update) {
226 IF_PRINT_WARNING(SYSTEM_DEBUG) << "timer already had auto update enabled" << std::endl;
227 return;
228 }
229
230 _auto_update = true;
231 _mode_owner = owner;
232 SystemManager->AddAutoTimer(this);
233 }
234
EnableManualUpdate()235 void SystemTimer::EnableManualUpdate()
236 {
237 if(_auto_update == false) {
238 IF_PRINT_WARNING(SYSTEM_DEBUG) << "timer was already in manual update mode" << std::endl;
239 return;
240 }
241
242 SystemManager->RemoveAutoTimer(this);
243 _auto_update = false;
244 _mode_owner = nullptr;
245 }
246
Update()247 void SystemTimer::Update()
248 {
249 Update(SystemManager->GetUpdateTime());
250 }
251
Update(uint32_t time)252 void SystemTimer::Update(uint32_t time)
253 {
254 if(_auto_update) {
255 IF_PRINT_WARNING(SYSTEM_DEBUG) << "update failed because timer is in automatic update mode" << std::endl;
256 return;
257 }
258 if(IsRunning() == false) {
259 return;
260 }
261
262 _UpdateTimer(time);
263 }
264
PercentComplete() const265 float SystemTimer::PercentComplete() const
266 {
267 switch(_state) {
268 case SYSTEM_TIMER_INITIAL:
269 return 0.0f;
270 case SYSTEM_TIMER_RUNNING:
271 case SYSTEM_TIMER_PAUSED:
272 return static_cast<float>(_time_expired) / static_cast<float>(_duration);
273 case SYSTEM_TIMER_FINISHED:
274 return 1.0f;
275 default:
276 return 0.0f;
277 }
278 }
279
SetDuration(uint32_t duration)280 void SystemTimer::SetDuration(uint32_t duration)
281 {
282 if(IsInitial() == false) {
283 IF_PRINT_WARNING(SYSTEM_DEBUG) << "function called when the timer was not in the initial state" << std::endl;
284 return;
285 }
286
287 _duration = duration;
288 }
289
SetTimeExpired(uint32_t time_expired)290 void SystemTimer::SetTimeExpired(uint32_t time_expired)
291 {
292 if (time_expired <= _duration)
293 _time_expired = time_expired;
294 else
295 _time_expired = _duration;
296 }
297
SetNumberLoops(int32_t loops)298 void SystemTimer::SetNumberLoops(int32_t loops)
299 {
300 if(IsInitial() == false) {
301 IF_PRINT_WARNING(SYSTEM_DEBUG) << "function called when the timer was not in the initial state" << std::endl;
302 return;
303 }
304
305 _number_loops = loops;
306 }
307
SetModeOwner(vt_mode_manager::GameMode * owner)308 void SystemTimer::SetModeOwner(vt_mode_manager::GameMode *owner)
309 {
310 if(IsInitial() == false) {
311 IF_PRINT_WARNING(SYSTEM_DEBUG) << "function called when the timer was not in the initial state" << std::endl;
312 return;
313 }
314
315 _mode_owner = owner;
316 }
317
_AutoUpdate()318 void SystemTimer::_AutoUpdate()
319 {
320 if(_auto_update == false) {
321 IF_PRINT_WARNING(SYSTEM_DEBUG) << "tried to automatically update a timer that does not have auto updates enabled" << std::endl;
322 return;
323 }
324 if(IsRunning() == false) {
325 return;
326 }
327
328 _UpdateTimer(SystemManager->GetUpdateTime());
329 }
330
_UpdateTimer(uint32_t time)331 void SystemTimer::_UpdateTimer(uint32_t time)
332 {
333 _time_expired += time;
334
335 if(_time_expired >= _duration) {
336 _times_completed++;
337
338 // Check if infinite looping is enabled
339 if(_number_loops < 0) {
340 _time_expired -= _duration;
341 }
342 // Check if the last loop has been completed
343 else if(_times_completed >= static_cast<uint32_t>(_number_loops)) {
344 _time_expired = 0;
345 _state = SYSTEM_TIMER_FINISHED;
346 }
347 // Otherwise there are still additional loops to complete
348 else {
349 _time_expired -= _duration;
350 }
351 }
352 }
353
354 // -----------------------------------------------------------------------------
355 // SystemEngine Class
356 // -----------------------------------------------------------------------------
357
SystemEngine()358 SystemEngine::SystemEngine():
359 _last_update(0),
360 _update_time(1), // Set to 1 to avoid hanging the system.
361 _hours_played(0),
362 _minutes_played(0),
363 _seconds_played(0),
364 _milliseconds_played(0),
365 _not_done(true),
366 _message_speed(vt_gui::DEFAULT_MESSAGE_SPEED),
367 _battle_target_cursor_memory(true),
368 _game_difficulty(2), // Normal
369 _game_save_slots(10) // Default slot number to handle
370 {
371 IF_PRINT_DEBUG(SYSTEM_DEBUG) << "constructor invoked" << std::endl;
372
373 SetLanguageLocale(DEFAULT_LOCALE);
374 _current_language_locale = DEFAULT_LOCALE; // In case no files were found.
375 _default_language_locale = DEFAULT_LOCALE; // In case no files were found.
376 }
377
~SystemEngine()378 SystemEngine::~SystemEngine()
379 {
380 IF_PRINT_DEBUG(SYSTEM_DEBUG) << "destructor invoked" << std::endl;
381 }
382
LoadLanguages()383 bool SystemEngine::LoadLanguages()
384 {
385 // Get the list of languages from the Lua file.
386 ReadScriptDescriptor read_data;
387 if(!read_data.OpenFile(LANGUAGE_FILE) || !read_data.DoesTableExist("languages")) {
388 PRINT_ERROR << "Failed to load language file: " << LANGUAGE_FILE << std::endl
389 << "The language list will be empty." << std::endl;
390 read_data.CloseFile();
391 return false;
392 }
393
394 std::vector<std::string> locale_list;
395 read_data.ReadTableKeys("languages", locale_list);
396
397 if (!read_data.OpenTable("languages") || locale_list.empty()) {
398 PRINT_ERROR << "Failed to load language file: " << LANGUAGE_FILE << std::endl
399 << "The language locale list was empty, or the languages table didn't exist." << std::endl;
400 read_data.CloseFile();
401 return false;
402 }
403
404 // Used to warn about missing po files, but only once at start.
405 static bool warnAboutMissingFiles = true;
406
407 _locales_properties.clear();
408
409 for (const std::string& locale : locale_list) {
410 if (locale == "default_locale") {
411 _default_language_locale = read_data.ReadString("default_locale");
412 continue;
413 }
414
415 if (!read_data.OpenTable(locale)) {
416 PRINT_WARNING << "Couldn't open locale table: '" << locale << "' in "
417 << LANGUAGE_FILE << ". Skipping ..." << std::endl;
418 continue;
419 }
420
421 LocaleProperties locale_property(MakeUnicodeString(read_data.ReadString("name")), locale);
422 locale_property.SetInterWordsSpacesUse(read_data.ReadBool("interwords_spaces"));
423 _locales_properties.insert(std::pair<std::string, LocaleProperties>(locale, locale_property));
424
425 // Test the current language availability
426 if (!vt_system::SystemManager->IsLanguageLocaleAvailable(locale)) {
427 if (warnAboutMissingFiles) {
428 std::string mo_filename = locale + "/LC_MESSAGES/" APPSHORTNAME ".mo";
429 PRINT_WARNING << "Couldn't locate gettext .mo file: '" << mo_filename << "'." << std::endl
430 << "The '" << locale << "' translation will be disabled." << std::endl;
431 }
432 }
433
434 read_data.CloseTable(); // locale
435 }
436
437 // Only warn once about missing language files.
438 warnAboutMissingFiles = false;
439
440 read_data.CloseTable(); // languages
441 if(read_data.IsErrorDetected())
442 PRINT_ERROR << "Error occurred while loading language list: " << read_data.GetErrorMessages() << std::endl;
443 read_data.CloseFile();
444 return true;
445 }
446
_Reinitl10n()447 std::string _Reinitl10n()
448 {
449 // Initialize the gettext library
450 setlocale(LC_ALL, "");
451 setlocale(LC_NUMERIC, "C");
452
453 std::string bind_text_domain_path;
454
455 #if defined(_WIN32) || defined(__APPLE__)
456 char buffer[PATH_MAX];
457 // Get the current working directory.
458 bind_text_domain_path = getcwd(buffer, PATH_MAX);
459 bind_text_domain_path.append("/data/locale/");
460
461 #elif (defined(__linux__) || defined(__FreeBSD__)) && !defined(RELEASE_BUILD)
462 // Look for translation files in LOCALEDIR only if they are not available in the
463 // current directory.
464 if(!vt_utils::DoesFileExist("data/locale/" + DEFAULT_LOCALE + "/LC_MESSAGES/" APPSHORTNAME ".mo")) {
465 bind_text_domain_path = LOCALEDIR;
466 } else {
467 char buffer[PATH_MAX];
468 // Get the current working directory.
469 bind_text_domain_path = getcwd(buffer, PATH_MAX);
470 bind_text_domain_path.append("/data/locale/");
471 }
472 #else
473 bind_text_domain_path = LOCALEDIR;
474 #endif
475 #ifndef DISABLE_TRANSLATIONS
476 bindtextdomain(APPSHORTNAME, bind_text_domain_path.c_str());
477 bind_textdomain_codeset(APPSHORTNAME, "UTF-8");
478 textdomain(APPSHORTNAME);
479 #endif
480 return bind_text_domain_path;
481 }
482
IsLanguageLocaleAvailable(const std::string & lang)483 bool SystemEngine::IsLanguageLocaleAvailable(const std::string& lang)
484 {
485 // Construct the corresponding mo filename path.
486 std::string mo_filename = _Reinitl10n();
487 mo_filename.append("/");
488 mo_filename.append(lang);
489 mo_filename.append("/LC_MESSAGES/" APPSHORTNAME ".mo");
490
491 // Note: English is always available as it's the default language
492 if (lang == DEFAULT_LOCALE)
493 return true;
494
495 // Test whether the file is existing.
496 if (!vt_utils::DoesFileExist(mo_filename))
497 return false;
498
499 return true;
500 }
501
SetLanguageLocale(const std::string & lang)502 bool SystemEngine::SetLanguageLocale(const std::string& lang)
503 {
504 // Test whether the file is existing.
505 // The function called also reinit the i10n paths
506 // so we don't have to do it here.
507 if (!IsLanguageLocaleAvailable(lang))
508 return false;
509
510 _current_language_locale = lang;
511 setlocale(LC_MESSAGES, _current_language_locale.c_str());
512 setlocale(LC_ALL, "");
513
514 #ifdef _WIN32
515 std::string lang_var = "LANGUAGE=" + _current_language_locale;
516 putenv(lang_var.c_str());
517 SetEnvironmentVariableA("LANGUAGE", _current_language_locale.c_str());
518 SetEnvironmentVariableA("LANG", _current_language_locale.c_str());
519 #else
520 setenv("LANGUAGE", _current_language_locale.c_str(), 1);
521 setenv("LANG", _current_language_locale.c_str(), 1);
522 #endif
523 return true;
524 }
525
SetMessageSpeed(float message_speed)526 void SystemEngine::SetMessageSpeed(float message_speed)
527 {
528 _message_speed = message_speed;
529
530 if (_message_speed < 40.0f)
531 _message_speed = 40.0f;
532
533 if (_message_speed > 600.0f)
534 _message_speed = 600.0f;
535 }
536
SetGameDifficulty(uint32_t game_difficulty)537 void SystemEngine::SetGameDifficulty(uint32_t game_difficulty)
538 {
539 _game_difficulty = game_difficulty;
540 if (_game_difficulty > 3)
541 _game_difficulty = 3;
542 else if (_game_difficulty < 1)
543 _game_difficulty = 1;
544 }
545
SingletonInitialize()546 bool SystemEngine::SingletonInitialize()
547 {
548 LoadLanguages();
549 return true;
550 }
551
InitializeTimers()552 void SystemEngine::InitializeTimers()
553 {
554 _last_update = SDL_GetTicks();
555 _update_time = 1; // Set to non-zero, otherwise bad things may happen...
556 _hours_played = 0;
557 _minutes_played = 0;
558 _seconds_played = 0;
559 _milliseconds_played = 0;
560 _auto_system_timers.clear();
561 }
562
InitializeUpdateTimer()563 void SystemEngine::InitializeUpdateTimer()
564 {
565 _last_update = SDL_GetTicks();
566 _update_time = 1;
567 }
568
AddAutoTimer(SystemTimer * timer)569 void SystemEngine::AddAutoTimer(SystemTimer *timer)
570 {
571 if(timer == nullptr) {
572 IF_PRINT_WARNING(SYSTEM_DEBUG) << "function received nullptr argument" << std::endl;
573 return;
574 }
575 if(timer->IsAutoUpdate() == false) {
576 IF_PRINT_WARNING(SYSTEM_DEBUG) << "timer did not have auto update feature enabled" << std::endl;
577 return;
578 }
579
580 if(_auto_system_timers.insert(timer).second == false) {
581 IF_PRINT_WARNING(SYSTEM_DEBUG) << "timer already existed in auto system timer container" << std::endl;
582 }
583 }
584
RemoveAutoTimer(SystemTimer * timer)585 void SystemEngine::RemoveAutoTimer(SystemTimer *timer)
586 {
587 if(timer == nullptr) {
588 IF_PRINT_WARNING(SYSTEM_DEBUG) << "function received nullptr argument" << std::endl;
589 return;
590 }
591 if(timer->IsAutoUpdate() == false) {
592 IF_PRINT_WARNING(SYSTEM_DEBUG) << "timer did not have auto update feature enabled" << std::endl;
593 }
594
595 if(_auto_system_timers.erase(timer) == 0) {
596 IF_PRINT_WARNING(SYSTEM_DEBUG) << "timer was not found in auto system timer container" << std::endl;
597 }
598 }
599
UpdateTimers()600 void SystemEngine::UpdateTimers()
601 {
602 // Update the update game timer
603 uint32_t tmp = _last_update;
604 _last_update = SDL_GetTicks();
605 _update_time = _last_update - tmp;
606
607 // Update the game play timer
608 _milliseconds_played += _update_time;
609 if(_milliseconds_played >= 1000) {
610 _seconds_played += _milliseconds_played / 1000;
611 _milliseconds_played = _milliseconds_played % 1000;
612 if(_seconds_played >= 60) {
613 _minutes_played += _seconds_played / 60;
614 _seconds_played = _seconds_played % 60;
615 if(_minutes_played >= 60) {
616 _hours_played += _minutes_played / 60;
617 _minutes_played = _minutes_played % 60;
618 }
619 }
620 }
621
622 // Update all SystemTimer objects
623 for(std::set<SystemTimer *>::iterator i = _auto_system_timers.begin(); i != _auto_system_timers.end(); ++i)
624 (*i)->_AutoUpdate();
625 }
626
ExamineSystemTimers()627 void SystemEngine::ExamineSystemTimers()
628 {
629 GameMode* active_mode = ModeManager->GetTop();
630
631 for(std::set<SystemTimer *>::iterator i = _auto_system_timers.begin(); i != _auto_system_timers.end(); ++i) {
632 GameMode* timer_mode = (*i)->GetModeOwner();
633 if(timer_mode == nullptr)
634 continue;
635
636 if(timer_mode == active_mode)
637 (*i)->Run();
638 else
639 (*i)->Pause();
640 }
641 }
642
643 } // namespace vt_system
644