diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs index 0407abfea7..eb80cee69f 100644 --- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs +++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs @@ -4,11 +4,13 @@ using Content.Client.UserInterface.ControlExtensions; using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls.FancyTree; using Content.Client.UserInterface.Systems.Info; +using Content.Shared.CCVar; using Content.Shared.Guidebook; using Content.Shared.Localizations; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; +using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.Prototypes; @@ -19,6 +21,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler { [Dependency] private readonly DocumentParsingManager _parsingMan = default!; [Dependency] private readonly IResourceManager _resourceManager = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; private Dictionary, GuideEntry> _entries = new(); @@ -159,7 +162,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler foreach (var entry in GetSortedEntries(roots)) { if (!entry.CrystallPunkAllowed) continue; //CrystallEdge guidebook filter - if (entry.LocFilter is not null && entry.LocFilter != ContentLocalizationManager.Culture) continue; //CrystallEdge guidebook filter + if (entry.LocFilter is not null && entry.LocFilter != _cfg.GetCVar(CCVars.Language)) continue; //CrystallEdge guidebook filter AddEntry(entry.Id, parent, addedEntries); } diff --git a/Content.Client/_CP14/Localization/CP14LocalizationVisualsSystem.cs b/Content.Client/_CP14/Localization/CP14LocalizationVisualsSystem.cs index 734df4a210..5e22e8f992 100644 --- a/Content.Client/_CP14/Localization/CP14LocalizationVisualsSystem.cs +++ b/Content.Client/_CP14/Localization/CP14LocalizationVisualsSystem.cs @@ -1,10 +1,13 @@ +using Content.Shared.CCVar; using Content.Shared.Localizations; using Robust.Client.GameObjects; +using Robust.Shared.Configuration; namespace Content.Client._CP14.Localization; public sealed class CP14LocalizationVisualsSystem : EntitySystem { + [Dependency] private readonly IConfigurationManager _cfg = default!; public override void Initialize() { base.Initialize(); @@ -19,7 +22,7 @@ public sealed class CP14LocalizationVisualsSystem : EntitySystem foreach (var (map, pDictionary) in visuals.Comp.MapStates) { - if (!pDictionary.TryGetValue(ContentLocalizationManager.Culture, out var state)) + if (!pDictionary.TryGetValue(_cfg.GetCVar(CCVars.Language), out var state)) return; if (sprite.LayerMapTryGet(map, out _)) diff --git a/Content.Client/_CP14/Shitcode/CP14EdSystem.cs b/Content.Client/_CP14/Shitcode/CP14EdSystem.cs index 628ab9406b..418d7ea96e 100644 --- a/Content.Client/_CP14/Shitcode/CP14EdSystem.cs +++ b/Content.Client/_CP14/Shitcode/CP14EdSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared.CCVar; using Robust.Shared; using Robust.Shared.Configuration; @@ -13,5 +14,12 @@ public sealed class CP14EdSystem : EntitySystem public override void Initialize() { _cfg.SetCVar(CVars.EntitiesCategoryFilter, "ForkFiltered"); + + _cfg.OnValueChanged(CCVars.Language, OnLanguageChange, true); + } + + private void OnLanguageChange(string obj) + { + _cfg.SetCVar(CVars.LocCultureName, obj); } } diff --git a/Content.Server/_CP14/RoundEnd/CP14RoundEndSystem.CBT.cs b/Content.Server/_CP14/RoundEnd/CP14RoundEndSystem.CBT.cs index 730bffc2f0..e1313d9008 100644 --- a/Content.Server/_CP14/RoundEnd/CP14RoundEndSystem.CBT.cs +++ b/Content.Server/_CP14/RoundEnd/CP14RoundEndSystem.CBT.cs @@ -11,32 +11,86 @@ public sealed partial class CP14RoundEndSystem [Dependency] private readonly GameTicker _ticker = default!; private TimeSpan _nextUpdateTime = TimeSpan.Zero; - private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(45f); + private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(60f); private bool _enabled; private void InitCbt() { - _enabled = _configManager.GetCVar(CCVars.CP14ClosedBetaTest); - _configManager.OnValueChanged(CCVars.CP14ClosedBetaTest, _ => { _enabled = _configManager.GetCVar(CCVars.CP14ClosedBetaTest); }, true); + _enabled = _cfg.GetCVar(CCVars.CP14ClosedBetaTest); + _cfg.OnValueChanged(CCVars.CP14ClosedBetaTest, + _ => { _enabled = _cfg.GetCVar(CCVars.CP14ClosedBetaTest); }, + true); } // Вы можете сказать: Эд, ты ебанулся? Это же лютый щиткод! // И я вам отвечу: Да. Но сама система ограничения времени работы сервера - временная штука на этап разработки, которая будет удалена. Мне просто лень каждый раз запускать и выключать сервер ручками. private void UpdateCbt(float _) { - if (!_enabled) - return; - - if (_nextUpdateTime > _timing.CurTime) + if (!_enabled || _timing.CurTime < _nextUpdateTime) return; _nextUpdateTime = _timing.CurTime + _updateFrequency; + var now = DateTime.UtcNow.AddHours(3); // Moscow time - DateTime nowMoscow = DateTime.UtcNow.AddHours(3); + OpenWeekendRule(now); + EnglishDayRule(now); + LimitPlaytimeRule(now); + ApplyAnnouncements(now); + } - //Disable any round timers - if (nowMoscow.Hour is < 18 or > 20) + private void OpenWeekendRule(DateTime now) + { + var curWhitelist = _cfg.GetCVar(CCVars.WhitelistEnabled); + var isOpenWeened = now.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday; + + if (isOpenWeened && curWhitelist) + { + _cfg.SetCVar(CCVars.WhitelistEnabled, false); + } + else if (!isOpenWeened && !curWhitelist) + { + _cfg.SetCVar(CCVars.WhitelistEnabled, true); + } + } + + private void EnglishDayRule(DateTime now) + { + var curLang = _cfg.GetCVar(CCVars.Language); + var englishDay = now.DayOfWeek == DayOfWeek.Saturday; + + if (englishDay && curLang != "en-US") + { + _cfg.SetCVar(CCVars.Language, "en-US"); + + _chatSystem.DispatchGlobalAnnouncement( + "WARNING: The server changes its language to English. For the changes to apply to your device, reconnect to the server.", + announcementSound: new SoundPathSpecifier("/Audio/Effects/beep1.ogg"), + sender: "Server" + ); + } + else if (!englishDay && curLang != "ru-RU") + { + _cfg.SetCVar(CCVars.Language, "ru-RU"); + + _chatSystem.DispatchGlobalAnnouncement( + "WARNING: The server changes its language to Russian. For the changes to apply to your device, reconnect to the server.", + announcementSound: new SoundPathSpecifier("/Audio/Effects/beep1.ogg"), + sender: "Server" + ); + } + } + + private void LimitPlaytimeRule(DateTime now) + { + var playtime = now.Hour is >= 18 and < 21; + + if (playtime) + { + if (_ticker.Paused) + _ticker.TogglePause(); + } + else { if (_ticker.RunLevel == GameRunLevel.InRound) _roundEnd.EndRound(); @@ -44,20 +98,30 @@ public sealed partial class CP14RoundEndSystem if (!_ticker.Paused) _ticker.TogglePause(); } - else - { - if (_ticker.Paused) - _ticker.TogglePause(); - } + } - if (nowMoscow.Hour == 20 && nowMoscow.Minute == 45) + private void ApplyAnnouncements(DateTime now) + { + var timeMap = new (int Hour, int Minute, Action Action)[] { - _chatSystem.DispatchGlobalAnnouncement("ВНИМАНИЕ: Сервер автоматически завершит раунд через 15 минут", announcementSound: new SoundPathSpecifier("/Audio/Effects/beep1.ogg"), sender: "Сервер"); - } - if (nowMoscow.Hour == 21 && nowMoscow.Minute == 02) + (20, 45, () => + { + _chatSystem.DispatchGlobalAnnouncement( + Loc.GetString("cp14-cbt-close-15m"), + announcementSound: new SoundPathSpecifier("/Audio/Effects/beep1.ogg"), + sender: "Server" + ); + }), + (21, 2, () => + { + _consoleHost.ExecuteCommand("golobby"); + }), + }; + + foreach (var (hour, minute, action) in timeMap) { - _consoleHost.ExecuteCommand("golobby"); - _consoleHost.ExecuteCommand("set-motd Плейтест на сегодня уже закончен. Следующий запуск в 18:00 МСК."); + if (now.Hour == hour && now.Minute == minute) + action.Invoke(); } } } diff --git a/Content.Server/_CP14/RoundEnd/CP14RoundEndSystem.cs b/Content.Server/_CP14/RoundEnd/CP14RoundEndSystem.cs index 0542b13d19..39c52fc200 100644 --- a/Content.Server/_CP14/RoundEnd/CP14RoundEndSystem.cs +++ b/Content.Server/_CP14/RoundEnd/CP14RoundEndSystem.cs @@ -17,7 +17,7 @@ public sealed partial class CP14RoundEndSystem : EntitySystem [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly CP14DemiplaneSystem _demiplane = default!; [Dependency] private readonly RoundEndSystem _roundEnd = default!; - [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; private TimeSpan _roundEndMoment = TimeSpan.Zero; @@ -77,7 +77,7 @@ public sealed partial class CP14RoundEndSystem : EntitySystem private void StartRoundEndTimer() { - var roundEndDelay = TimeSpan.FromMinutes(_configManager.GetCVar(CCVars.CP14RoundEndMinutes)); + var roundEndDelay = TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.CP14RoundEndMinutes)); _roundEndMoment = _timing.CurTime + roundEndDelay; diff --git a/Content.Shared/CCVar/CCVars.Localization.cs b/Content.Shared/CCVar/CCVars.Localization.cs new file mode 100644 index 0000000000..7cbee2ff3d --- /dev/null +++ b/Content.Shared/CCVar/CCVars.Localization.cs @@ -0,0 +1,12 @@ +using Robust.Shared.Configuration; + +namespace Content.Shared.CCVar; + +public sealed partial class CCVars +{ + /// + /// Language used for the in-game localization. + /// + public static readonly CVarDef Language = + CVarDef.Create("localization.language", "en-US", CVar.SERVER | CVar.REPLICATED); +} diff --git a/Content.Shared/Localizations/ContentLocalizationManager.cs b/Content.Shared/Localizations/ContentLocalizationManager.cs index d1ab35aa4e..8a54338cb4 100644 --- a/Content.Shared/Localizations/ContentLocalizationManager.cs +++ b/Content.Shared/Localizations/ContentLocalizationManager.cs @@ -1,6 +1,8 @@ using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using Content.Shared.CCVar; +using Robust.Shared.Configuration; using Robust.Shared.Utility; namespace Content.Shared.Localizations @@ -8,10 +10,7 @@ namespace Content.Shared.Localizations public sealed class ContentLocalizationManager { [Dependency] private readonly ILocalizationManager _loc = default!; - - // If you want to change your codebase's language, do it here. - //public const string Culture = "en-US"; - public const string Culture = "ru-RU"; + [Dependency] private readonly IConfigurationManager _cfg = default!; /// /// Custom format strings used for parsing and displaying minutes:seconds timespans. @@ -26,14 +25,9 @@ namespace Content.Shared.Localizations public void Initialize() { - var culture = new CultureInfo(Culture); - _loc.LoadCulture(culture); - // Uncomment for Ru localization - var fallbackCulture = new CultureInfo("en-US"); - _loc.LoadCulture(fallbackCulture); - _loc.SetFallbackCluture(fallbackCulture); - // + var culture = new CultureInfo(_cfg.GetCVar(CCVars.Language)); + _loc.LoadCulture(culture); _loc.AddFunction(culture, "PRESSURE", FormatPressure); _loc.AddFunction(culture, "POWERWATTS", FormatPowerWatts); _loc.AddFunction(culture, "POWERJOULES", FormatPowerJoules); @@ -44,7 +38,6 @@ namespace Content.Shared.Localizations _loc.AddFunction(culture, "NATURALPERCENT", FormatNaturalPercent); _loc.AddFunction(culture, "PLAYTIME", FormatPlaytime); - _loc.AddFunction(culture, "MANY", FormatMany); // TODO: Temporary fix for MANY() fluent errors. Remove after resolve errors. /* * The following language functions are specific to the english localization. When working on your own @@ -53,13 +46,22 @@ namespace Content.Shared.Localizations */ var cultureEn = new CultureInfo("en-US"); + if (!_loc.HasCulture(cultureEn)) + _loc.LoadCulture(cultureEn); + _loc.SetFallbackCluture(cultureEn); // I don't think there's any reason to change the fallback culture. _loc.AddFunction(cultureEn, "MAKEPLURAL", FormatMakePlural); _loc.AddFunction(cultureEn, "MANY", FormatMany); - _loc.AddFunction(cultureEn, "NATURALFIXED", FormatNaturalFixed); - _loc.AddFunction(cultureEn, "LOC", FormatLoc); - _loc.AddFunction(cultureEn, "NATURALPERCENT", FormatNaturalPercent); - _loc.AddFunction(cultureEn, "POWERJOULES", FormatPowerJoules); - _loc.AddFunction(cultureEn, "TOSTRING", args => FormatToString(cultureEn, args)); + + _cfg.OnValueChanged(CCVars.Language, OnCultureUpdate, true); + } + + private void OnCultureUpdate(string value) + { + var culture = new CultureInfo(value); + if (!_loc.HasCulture(culture)) + _loc.LoadCulture(culture); + _loc.DefaultCulture = culture; + _loc.ReloadLocalizations(); } private ILocValue FormatMany(LocArgs args) @@ -80,7 +82,7 @@ namespace Content.Shared.Localizations { var number = ((LocValueNumber) args.Args[0]).Value * 100; var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value); - var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone(); + var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(_cfg.GetCVar(CCVars.Language))).Clone(); formatter.NumberDecimalDigits = maxDecimals; return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd(char.Parse(formatter.NumberDecimalSeparator)) + "%"); } @@ -89,7 +91,7 @@ namespace Content.Shared.Localizations { var number = ((LocValueNumber) args.Args[0]).Value; var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value); - var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone(); + var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(_cfg.GetCVar(CCVars.Language))).Clone(); formatter.NumberDecimalDigits = maxDecimals; return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd(char.Parse(formatter.NumberDecimalSeparator))); } @@ -159,7 +161,6 @@ namespace Content.Shared.Localizations /// public static string FormatPlaytime(TimeSpan time) { - time = TimeSpan.FromMinutes(Math.Ceiling(time.TotalMinutes)); var hours = (int)time.TotalHours; var minutes = time.Minutes; return Loc.GetString($"zzzz-fmt-playtime", ("hours", hours), ("minutes", minutes)); diff --git a/Resources/ConfigPresets/_CP14/Dev.toml b/Resources/ConfigPresets/_CP14/Dev.toml index fe6e9757ec..29581af64d 100644 --- a/Resources/ConfigPresets/_CP14/Dev.toml +++ b/Resources/ConfigPresets/_CP14/Dev.toml @@ -1,6 +1,9 @@ [whitelist] enabled = true +[localization] +language = "ru-RU" + [log] path = "logs" format = "log_%(date)s-%(time)s.txt" diff --git a/Resources/Locale/en-US/_CP14/gameTicking/cbt.ftl b/Resources/Locale/en-US/_CP14/gameTicking/cbt.ftl new file mode 100644 index 0000000000..268ab04410 --- /dev/null +++ b/Resources/Locale/en-US/_CP14/gameTicking/cbt.ftl @@ -0,0 +1 @@ +cp14-cbt-close-15m = WARNING: The server will automatically end the round after 15 minutes \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_CP14/gameTicking/cbt.ftl b/Resources/Locale/ru-RU/_CP14/gameTicking/cbt.ftl new file mode 100644 index 0000000000..a7a5ff7745 --- /dev/null +++ b/Resources/Locale/ru-RU/_CP14/gameTicking/cbt.ftl @@ -0,0 +1 @@ +cp14-cbt-close-15m = ВНИМАНИЕ: Сервер автоматически завершит раунд через 15 минут \ No newline at end of file