Fix #8316: Make sort industries by production and transported with a cargo filter...
[openttd-github.git] / src / game / game_text.cpp
blobca102f91203865db789903137d0b8dbcf26d223d
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file game_text.cpp Implementation of handling translated strings. */
10 #include "../stdafx.h"
11 #include "../strgen/strgen.h"
12 #include "../debug.h"
13 #include "../fileio_func.h"
14 #include "../tar_type.h"
15 #include "../script/squirrel_class.hpp"
16 #include "../strings_func.h"
17 #include "game_text.hpp"
18 #include "game.hpp"
19 #include "game_info.hpp"
21 #include "table/strings.h"
23 #include <stdarg.h>
24 #include <memory>
26 #include "../safeguards.h"
28 void CDECL strgen_warning(const char *s, ...)
30 char buf[1024];
31 va_list va;
32 va_start(va, s);
33 vseprintf(buf, lastof(buf), s, va);
34 va_end(va);
35 Debug(script, 0, "{}:{}: warning: {}", _file, _cur_line, buf);
36 _warnings++;
39 void CDECL strgen_error(const char *s, ...)
41 char buf[1024];
42 va_list va;
43 va_start(va, s);
44 vseprintf(buf, lastof(buf), s, va);
45 va_end(va);
46 Debug(script, 0, "{}:{}: error: {}", _file, _cur_line, buf);
47 _errors++;
50 void NORETURN CDECL strgen_fatal(const char *s, ...)
52 char buf[1024];
53 va_list va;
54 va_start(va, s);
55 vseprintf(buf, lastof(buf), s, va);
56 va_end(va);
57 Debug(script, 0, "{}:{}: FATAL: {}", _file, _cur_line, buf);
58 throw std::exception();
61 /**
62 * Read all the raw language strings from the given file.
63 * @param file The file to read from.
64 * @return The raw strings, or nullptr upon error.
66 LanguageStrings ReadRawLanguageStrings(const std::string &file)
68 size_t to_read;
69 FILE *fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
70 if (fh == nullptr) return LanguageStrings();
72 FileCloser fhClose(fh);
74 auto pos = file.rfind(PATHSEPCHAR);
75 if (pos == std::string::npos) return LanguageStrings();
76 std::string langname = file.substr(pos + 1);
78 /* Check for invalid empty filename */
79 if (langname.empty() || langname.front() == '.') return LanguageStrings();
81 LanguageStrings ret(langname.substr(0, langname.find('.')));
83 char buffer[2048];
84 while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != nullptr) {
85 size_t len = strlen(buffer);
87 /* Remove trailing spaces/newlines from the string. */
88 size_t i = len;
89 while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
90 buffer[i] = '\0';
92 ret.lines.emplace_back(buffer, i);
94 if (len > to_read) {
95 to_read = 0;
96 } else {
97 to_read -= len;
101 return ret;
105 /** A reader that simply reads using fopen. */
106 struct StringListReader : StringReader {
107 StringList::const_iterator p; ///< The current location of the iteration.
108 StringList::const_iterator end; ///< The end of the iteration.
111 * Create the reader.
112 * @param data The data to fill during reading.
113 * @param strings The language strings we are reading.
114 * @param master Are we reading the master file?
115 * @param translation Are we reading a translation?
117 StringListReader(StringData &data, const LanguageStrings &strings, bool master, bool translation) :
118 StringReader(data, strings.language.c_str(), master, translation), p(strings.lines.begin()), end(strings.lines.end())
122 char *ReadLine(char *buffer, const char *last) override
124 if (this->p == this->end) return nullptr;
126 strecpy(buffer, this->p->c_str(), last);
127 this->p++;
129 return buffer;
133 /** Class for writing an encoded language. */
134 struct TranslationWriter : LanguageWriter {
135 StringList &strings; ///< The encoded strings.
138 * Writer for the encoded data.
139 * @param strings The string table to add the strings to.
141 TranslationWriter(StringList &strings) : strings(strings)
145 void WriteHeader(const LanguagePackHeader *header)
147 /* We don't use the header. */
150 void Finalise()
152 /* Nothing to do. */
155 void WriteLength(uint length)
157 /* We don't write the length. */
160 void Write(const byte *buffer, size_t length)
162 this->strings.emplace_back((const char *)buffer, length);
166 /** Class for writing the string IDs. */
167 struct StringNameWriter : HeaderWriter {
168 StringList &strings; ///< The string names.
171 * Writer for the string names.
172 * @param strings The string table to add the strings to.
174 StringNameWriter(StringList &strings) : strings(strings)
178 void WriteStringID(const char *name, int stringid)
180 if (stringid == (int)this->strings.size()) this->strings.emplace_back(name);
183 void Finalise(const StringData &data)
185 /* Nothing to do. */
190 * Scanner to find language files in a GameScript directory.
192 class LanguageScanner : protected FileScanner {
193 private:
194 GameStrings *gs;
195 std::string exclude;
197 public:
198 /** Initialise */
199 LanguageScanner(GameStrings *gs, const std::string &exclude) : gs(gs), exclude(exclude) {}
202 * Scan.
204 void Scan(const char *directory)
206 this->FileScanner::Scan(".txt", directory, false);
209 bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override
211 if (exclude == filename) return true;
213 auto ls = ReadRawLanguageStrings(filename);
214 if (!ls.IsValid()) return false;
216 gs->raw_strings.push_back(std::move(ls));
217 return true;
222 * Load all translations that we know of.
223 * @return Container with all (compiled) translations.
225 GameStrings *LoadTranslations()
227 const GameInfo *info = Game::GetInfo();
228 std::string basename(info->GetMainScript());
229 auto e = basename.rfind(PATHSEPCHAR);
230 if (e == std::string::npos) return nullptr;
231 basename.erase(e + 1);
233 std::string filename = basename + "lang" PATHSEP "english.txt";
234 if (!FioCheckFileExists(filename, GAME_DIR)) return nullptr;
236 auto ls = ReadRawLanguageStrings(filename);
237 if (!ls.IsValid()) return nullptr;
239 GameStrings *gs = new GameStrings();
240 try {
241 gs->raw_strings.push_back(std::move(ls));
243 /* Scan for other language files */
244 LanguageScanner scanner(gs, filename);
245 std::string ldir = basename + "lang" PATHSEP;
247 const std::string tar_filename = info->GetTarFile();
248 TarList::iterator iter;
249 if (!tar_filename.empty() && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
250 /* The main script is in a tar file, so find all files that
251 * are in the same tar and add them to the langfile scanner. */
252 for (const auto &tar : _tar_filelist[GAME_DIR]) {
253 /* Not in the same tar. */
254 if (tar.second.tar_filename != iter->first) continue;
256 /* Check the path and extension. */
257 if (tar.first.size() <= ldir.size() || tar.first.compare(0, ldir.size(), ldir) != 0) continue;
258 if (tar.first.compare(tar.first.size() - 4, 4, ".txt") != 0) continue;
260 scanner.AddFile(tar.first, 0, tar_filename);
262 } else {
263 /* Scan filesystem */
264 scanner.Scan(ldir.c_str());
267 gs->Compile();
268 return gs;
269 } catch (...) {
270 delete gs;
271 return nullptr;
275 /** Compile the language. */
276 void GameStrings::Compile()
278 StringData data(32);
279 StringListReader master_reader(data, this->raw_strings[0], true, false);
280 master_reader.ParseFile();
281 if (_errors != 0) throw std::exception();
283 this->version = data.Version();
285 StringNameWriter id_writer(this->string_names);
286 id_writer.WriteHeader(data);
288 for (const auto &p : this->raw_strings) {
289 data.FreeTranslation();
290 StringListReader translation_reader(data, p, false, p.language != "english");
291 translation_reader.ParseFile();
292 if (_errors != 0) throw std::exception();
294 this->compiled_strings.emplace_back(p.language);
295 TranslationWriter writer(this->compiled_strings.back().lines);
296 writer.WriteLang(data);
300 /** The currently loaded game strings. */
301 GameStrings *_current_data = nullptr;
304 * Get the string pointer of a particular game string.
305 * @param id The ID of the game string.
306 * @return The encoded string.
308 const char *GetGameStringPtr(uint id)
310 if (id >= _current_data->cur_language->lines.size()) return GetStringPtr(STR_UNDEFINED);
311 return _current_data->cur_language->lines[id].c_str();
315 * Register the current translation to the Squirrel engine.
316 * @param engine The engine to update/
318 void RegisterGameTranslation(Squirrel *engine)
320 delete _current_data;
321 _current_data = LoadTranslations();
322 if (_current_data == nullptr) return;
324 HSQUIRRELVM vm = engine->GetVM();
325 sq_pushroottable(vm);
326 sq_pushstring(vm, "GSText", -1);
327 if (SQ_FAILED(sq_get(vm, -2))) return;
329 int idx = 0;
330 for (const auto &p : _current_data->string_names) {
331 sq_pushstring(vm, p.c_str(), -1);
332 sq_pushinteger(vm, idx);
333 sq_rawset(vm, -3);
334 idx++;
337 sq_pop(vm, 2);
339 ReconsiderGameScriptLanguage();
343 * Reconsider the game script language, so we use the right one.
345 void ReconsiderGameScriptLanguage()
347 if (_current_data == nullptr) return;
349 char temp[MAX_PATH];
350 strecpy(temp, _current_language->file, lastof(temp));
352 /* Remove the extension */
353 char *l = strrchr(temp, '.');
354 assert(l != nullptr);
355 *l = '\0';
357 /* Skip the path */
358 char *language = strrchr(temp, PATHSEPCHAR);
359 assert(language != nullptr);
360 language++;
362 for (auto &p : _current_data->compiled_strings) {
363 if (p.language == language) {
364 _current_data->cur_language = &p;
365 return;
369 _current_data->cur_language = &_current_data->compiled_strings[0];