mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-01 23:59:12 +01:00
Merge remote-tracking branch 'origin/feat/switch-blades' into feat/gl46-migration
This commit is contained in:
@@ -598,6 +598,40 @@ bool global_settings::ConfigParseSimulation(cParser& Parser, const std::string&
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token == "sleepers3d")
|
||||
{
|
||||
// model of the sleepers and switch timbers, put together from its submodels begin, main and end; none: no such sleepers
|
||||
ParseOne(Parser, Sleepers3D);
|
||||
if (Sleepers3D == "none")
|
||||
{
|
||||
Sleepers3D.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token == "sleepers3dbed")
|
||||
{
|
||||
// the ballast under the tracks with 3D sleepers; no: the sleepers are laid on the ground alone
|
||||
ParseOne(Parser, Sleepers3DBed);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token == "sleepers3dplain")
|
||||
{
|
||||
// model, skin and the height of the rail seat of the sleepers of a track running alone, square to it; none: the timbers of sleepers3d there too
|
||||
Parser.getTokens(3, false);
|
||||
Parser >> Sleepers3DPlain >> Sleepers3DPlainSkin >> Sleepers3DPlainSeat;
|
||||
if (Sleepers3DPlain == "none")
|
||||
{
|
||||
Sleepers3DPlain.clear();
|
||||
}
|
||||
if (Sleepers3DPlainSkin == "none")
|
||||
{
|
||||
Sleepers3DPlainSkin.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token == "createswitchtrackbeds")
|
||||
{
|
||||
ParseOne(Parser, CreateSwitchTrackbeds);
|
||||
@@ -1851,6 +1885,9 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
export_as_text( Output, "smoothtraction", bSmoothTraction );
|
||||
export_as_text( Output, "splinefidelity", SplineFidelity );
|
||||
export_as_text( Output, "sleeperdistance", SleeperDistance );
|
||||
export_as_text( Output, "sleepers3d", ( Sleepers3D.empty() ? std::string{ "none" } : Sleepers3D ) );
|
||||
export_as_text( Output, "sleepers3dbed", Sleepers3DBed );
|
||||
Output << "sleepers3dplain " << ( Sleepers3DPlain.empty() ? std::string{ "none" } : Sleepers3DPlain ) << ' ' << ( Sleepers3DPlainSkin.empty() ? std::string{ "none" } : Sleepers3DPlainSkin ) << ' ' << Sleepers3DPlainSeat << '\n';
|
||||
export_as_text( Output, "rendercab", render_cab );
|
||||
export_as_text( Output, "createswitchtrackbeds", CreateSwitchTrackbeds );
|
||||
export_as_text( Output, "gfx.resource.sweep", ResourceSweep );
|
||||
|
||||
@@ -171,11 +171,17 @@ struct global_settings {
|
||||
float FieldOfView{ 45.f }; // vertical field of view for the camera. TODO: move it to the renderer
|
||||
bool EditorOrtho{ false };
|
||||
float EditorOrthoExtent{ 150.f };
|
||||
bool EditorOrthoBallast{ true }; // the ballast of the railway drawn in the top view of the editor
|
||||
GLint iMaxTextureSize{ 4096 }; // maksymalny rozmiar tekstury
|
||||
GLint iMaxCabTextureSize{ 4096 }; // largest allowed texture in vehicle cab
|
||||
int iMultisampling{ 2 }; // tryb antyaliasingu: 0=brak,1=2px,2=4px,3=8px,4=16px
|
||||
float SplineFidelity{ 1.f }; // determines segment size during conversion of splines to geometry
|
||||
float SleeperDistance{ 250.f }; // max distance (in meters) at which per-track sleeper models are still drawn; 0 disables sleeper rendering entirely
|
||||
std::string Sleepers3D; // model of the sleepers and switch timbers laid where the simulator makes the ballast; empty: none
|
||||
std::string Sleepers3DPlain { "quark/podklad1" }; // model of the sleepers of a track running alone, those of Sleepers3D being the timbers of switches and crossings; empty: the timbers there too
|
||||
std::string Sleepers3DPlainSkin { "quark/podklad_betonowy1" };
|
||||
bool Sleepers3DBed { true }; // whether the ballast is laid under the tracks with 3D sleepers
|
||||
float Sleepers3DPlainSeat { 0.24f }; // how high the rails sit on the plain sleeper above the origin of its model; below 0: on its top
|
||||
bool Smoke{ true }; // toggles smoke simulation and visualization
|
||||
float SmokeFidelity{ 1.f }; // determines amount of generated smoke particles
|
||||
bool ResourceSweep{ true }; // gfx resource garbage collection
|
||||
|
||||
@@ -63,6 +63,15 @@ cParser::cParser(std::string const &Stream, buffertype const Type, std::string P
|
||||
// the file is read in one go, tokens are then extracted straight from the memory
|
||||
std::ifstream file(Path, std::ios_base::binary);
|
||||
if (file.fail())
|
||||
{
|
||||
// named in another case than it's on the disk
|
||||
if (auto const cased{FileCased(Path)}; false == cased.empty())
|
||||
{
|
||||
file.clear();
|
||||
file.open(cased, std::ios_base::binary);
|
||||
}
|
||||
}
|
||||
if (file.fail())
|
||||
{
|
||||
mFailBit = true;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ void locale::init()
|
||||
if (editor.is_open())
|
||||
while (parse_translation(editor));
|
||||
|
||||
WriteLog("translation: " + std::to_string(lang_mapping.size()) + " strings loaded");
|
||||
WriteLog("translation: " + std::to_string(lang_mapping.size()) + " strings and " + std::to_string(plural_mapping.size()) + " plural strings loaded");
|
||||
}
|
||||
|
||||
const std::string& locale::lookup_s(const std::string &msg, bool constant)
|
||||
@@ -78,6 +78,35 @@ const char* locale::lookup_c(const char *msg, bool constant)
|
||||
return msg;
|
||||
}
|
||||
|
||||
const char* locale::lookup_n(const char *singular, const char *plural, long long n)
|
||||
{
|
||||
auto cached = plural_cache.find(singular);
|
||||
if (cached == plural_cache.end()) {
|
||||
auto it = plural_mapping.find(std::string(singular));
|
||||
cached = plural_cache.emplace(singular, it != plural_mapping.end() ? &it->second : nullptr).first;
|
||||
}
|
||||
|
||||
if (cached->second == nullptr)
|
||||
return n == 1 ? singular : plural;
|
||||
|
||||
auto const &forms = *cached->second;
|
||||
auto const form = std::min(plural_form(n), forms.size() - 1);
|
||||
return forms[form].empty() ? (n == 1 ? singular : plural) : forms[form].c_str();
|
||||
}
|
||||
|
||||
std::size_t locale::plural_form(long long n) const
|
||||
{
|
||||
n = std::llabs(n);
|
||||
if (Global.asLang.compare(0, 2, "pl") == 0) {
|
||||
if (n == 1)
|
||||
return 0;
|
||||
auto const units = n % 10;
|
||||
auto const tens = n % 100;
|
||||
return units >= 2 && units <= 4 && (tens < 10 || tens >= 20) ? 1 : 2;
|
||||
}
|
||||
return n == 1 ? 0 : 1;
|
||||
}
|
||||
|
||||
bool locale::parse_translation(std::istream &stream)
|
||||
{
|
||||
std::string line;
|
||||
@@ -85,14 +114,33 @@ bool locale::parse_translation(std::istream &stream)
|
||||
std::string msgid;
|
||||
std::string msgstr;
|
||||
std::string msgctxt;
|
||||
std::vector<std::string> forms;
|
||||
char last = 'x';
|
||||
|
||||
auto const commit = [&]() {
|
||||
if (!msgid.empty() && !forms.empty())
|
||||
plural_mapping.emplace(msgid, std::move(forms));
|
||||
else if (!msgid.empty() && !msgstr.empty()) {
|
||||
// TODO: context support is incomplete
|
||||
if (!msgctxt.empty())
|
||||
lang_mapping.emplace(msgctxt + "\x1d" + msgid, msgstr);
|
||||
else
|
||||
lang_mapping.emplace(msgid, msgstr);
|
||||
}
|
||||
};
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
if (line.size() > 0 && line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (line.starts_with("msgid"))
|
||||
if (line.starts_with("msgid_plural"))
|
||||
last = 'p';
|
||||
else if (line.starts_with("msgid"))
|
||||
last = 'i';
|
||||
else if (line.starts_with("msgstr[")) {
|
||||
last = 'n';
|
||||
forms.resize(std::max<std::size_t>(forms.size(), std::atoi(line.c_str() + 7) + 1));
|
||||
}
|
||||
else if (line.starts_with("msgstr"))
|
||||
last = 's';
|
||||
else if (line.starts_with("msgctxt"))
|
||||
@@ -103,21 +151,21 @@ bool locale::parse_translation(std::istream &stream)
|
||||
msgid += parse_c_literal(line);
|
||||
else if (last == 's')
|
||||
msgstr += parse_c_literal(line);
|
||||
else if (last == 'n')
|
||||
forms.back() += parse_c_literal(line);
|
||||
else if (last == 'c')
|
||||
msgctxt += parse_c_literal(line);
|
||||
}
|
||||
else {
|
||||
if (!msgid.empty() && !msgstr.empty()) {
|
||||
// TODO: context support is incomplete
|
||||
if (!msgctxt.empty())
|
||||
lang_mapping.emplace(msgctxt + "\x1d" + msgid, msgstr);
|
||||
else
|
||||
lang_mapping.emplace(msgid, msgstr);
|
||||
}
|
||||
commit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!msgid.empty()) {
|
||||
commit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class locale {
|
||||
public:
|
||||
@@ -20,13 +21,17 @@ public:
|
||||
|
||||
const char* lookup_c(const char *msg, bool constant = false);
|
||||
const std::string& lookup_s(const std::string &msg, bool constant = false);
|
||||
const char* lookup_n(const char *singular, const char *plural, long long n);
|
||||
|
||||
private:
|
||||
bool parse_translation(std::istream &stream);
|
||||
std::string parse_c_literal(const std::string &str);
|
||||
std::size_t plural_form(long long n) const;
|
||||
|
||||
std::unordered_map<std::string, std::string> lang_mapping;
|
||||
std::unordered_map<const void*, const void*> pointer_cache;
|
||||
std::unordered_map<std::string, std::vector<std::string>> plural_mapping;
|
||||
std::unordered_map<const void*, const std::vector<std::string>*> plural_cache;
|
||||
};
|
||||
|
||||
extern locale Translations;
|
||||
@@ -34,5 +39,6 @@ extern locale Translations;
|
||||
#define STR(x) Translations.lookup_s(x, false)
|
||||
#define STR_C(x) Translations.lookup_c(x, true)
|
||||
#define STRN(x) x
|
||||
#define STR_P(singular, plural, n) Translations.lookup_n(singular, plural, n)
|
||||
|
||||
//find . -name "*.cpp" | xargs xgettext --from-code="UTF-8" -k --keyword=STR --keyword=STR_C --keyword=STRN
|
||||
//find . -name "*.cpp" | xargs xgettext --from-code="UTF-8" -k --keyword=STR --keyword=STR_C --keyword=STRN --keyword=STR_P:1,2
|
||||
|
||||
@@ -471,6 +471,20 @@ bool FileExists(std::string const &Filename)
|
||||
return std::filesystem::exists(Filename);
|
||||
}
|
||||
|
||||
std::string FileCased(std::string const &Filename)
|
||||
{
|
||||
std::error_code error;
|
||||
if (std::filesystem::exists(Filename, error))
|
||||
return Filename;
|
||||
std::filesystem::path const path{Filename};
|
||||
auto const directory{path.has_parent_path() ? path.parent_path() : std::filesystem::path{"."}};
|
||||
auto const wanted{ToLower(path.filename().string())};
|
||||
for (std::filesystem::directory_iterator entry{directory, error}, end; entry != end; entry.increment(error))
|
||||
if (ToLower(entry->path().filename().string()) == wanted)
|
||||
return path.has_parent_path() ? (directory / entry->path().filename()).string() : entry->path().filename().string();
|
||||
return {};
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> FileExists(std::vector<std::string> const &Names, std::vector<std::string> const &Extensions)
|
||||
{
|
||||
|
||||
|
||||
@@ -217,6 +217,9 @@ template <typename Type_> bool extract_value(Type_ &Variable, std::string const
|
||||
template <> bool extract_value(bool &Variable, std::string const &Key, std::string const &Input, std::string const &Default);
|
||||
|
||||
bool FileExists(std::string const &Filename);
|
||||
// the file as it's named on the disk when only the case of its name differs, as in the sceneries made on Windows; empty when
|
||||
// there's none
|
||||
std::string FileCased(std::string const &Filename);
|
||||
|
||||
std::pair<std::string, std::string> FileExists(std::vector<std::string> const &Names, std::vector<std::string> const &Extensions);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user