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