Expose mod.yaml content to localisation.

Mod metadata, load screens and mod content is all now sourced from ftl files, allowing these items to be translated.

Translations are now initialized as part of ModData creation, as currently they are made available too late for the usage we need here.

The "modcontent" mod learns a new parameter for "Content.TranslationFile" - this allows a mod to provide the path of a translation file to the mod which it can load. This allows mods such as ra, cnc, d2k, ts to own the translations for their ModContent, yet still make them accessible to the modcontent mod.

CheckFluentReference learns to validate all these new fields to ensure translations have been set.
This commit is contained in:
RoosterDragon
2024-09-23 19:58:33 +01:00
committed by Gustas
parent d1583e8587
commit bb17cfa179
36 changed files with 292 additions and 144 deletions

View File

@@ -27,7 +27,6 @@ namespace OpenRA
{
public readonly string Id;
public readonly string Version;
public readonly string Title;
public readonly string LaunchPath;
public readonly string[] LaunchArgs;
public Sprite Icon { get; internal set; }
@@ -127,7 +126,6 @@ namespace OpenRA
{
new MiniYamlNode("Id", mod.Id),
new MiniYamlNode("Version", mod.Metadata.Version),
new MiniYamlNode("Title", mod.Metadata.Title),
new MiniYamlNode("LaunchPath", launchPath),
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
}));

View File

@@ -395,7 +395,7 @@ namespace OpenRA
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
Console.WriteLine("Internal mods:");
foreach (var mod in Mods)
Console.WriteLine($"\t{mod.Key}: {mod.Value.Metadata.Title} ({mod.Value.Metadata.Version})");
Console.WriteLine($"\t{mod.Key} ({mod.Value.Metadata.Version})");
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
@@ -420,7 +420,7 @@ namespace OpenRA
Console.WriteLine("External mods:");
foreach (var mod in ExternalMods)
Console.WriteLine($"\t{mod.Key}: {mod.Value.Title} ({mod.Value.Version})");
Console.WriteLine($"\t{mod.Key} ({mod.Value.Version})");
InitializeMod(modID, args);
}
@@ -499,8 +499,8 @@ namespace OpenRA
Cursor = new CursorManager(ModData.CursorProvider, ModData.Manifest.CursorSheetSize);
var metadata = ModData.Manifest.Metadata;
if (!string.IsNullOrEmpty(metadata.WindowTitle))
Renderer.Window.SetWindowTitle(metadata.WindowTitle);
if (!string.IsNullOrEmpty(metadata.WindowTitleTranslated))
Renderer.Window.SetWindowTitle(metadata.WindowTitleTranslated);
PerfHistory.Items["render"].HasNormalTick = false;
PerfHistory.Items["batches"].HasNormalTick = false;

View File

@@ -45,12 +45,20 @@ namespace OpenRA
public class ModMetadata
{
public string Title;
public string Version;
public string Website;
public string WebIcon32;
public string WindowTitle;
public bool Hidden;
// FieldLoader used here, must matching naming in YAML.
#pragma warning disable IDE1006 // Naming Styles
[FluentReference]
readonly string Title;
public readonly string Version;
public readonly string Website;
public readonly string WebIcon32;
[FluentReference]
readonly string WindowTitle;
public readonly bool Hidden;
#pragma warning restore IDE1006 // Naming Styles
public string TitleTranslated => FluentProvider.GetString(Title);
public string WindowTitleTranslated => WindowTitle != null ? FluentProvider.GetString(WindowTitle) : null;
}
/// <summary>Describes what is to be loaded in order to run a mod.</summary>

View File

@@ -65,6 +65,8 @@ namespace OpenRA
Manifest.LoadCustomData(ObjectCreator);
FluentProvider.Initialize(this, DefaultFileSystem);
if (useLoadScreen)
{
LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(Manifest.LoadScreen.Value);

View File

@@ -182,13 +182,13 @@ namespace OpenRA.Network
if (external != null && external.Version == Version)
{
// Use external mod registration to populate the section header
ModTitle = external.Title;
ModTitle = external.Id;
}
else if (Game.Mods.TryGetValue(Mod, out var mod))
{
// Use internal mod data to populate the section header, but
// on-connect switching must use the external mod plumbing.
ModTitle = mod.Metadata.Title;
ModTitle = mod.Metadata.TitleTranslated;
}
else
{
@@ -199,7 +199,7 @@ namespace OpenRA.Network
.FirstOrDefault(m => m.Id == Mod);
if (guessMod != null)
ModTitle = guessMod.Title;
ModTitle = guessMod.Id;
else
ModTitle = $"Unknown mod: {Mod}";
}
@@ -222,7 +222,7 @@ namespace OpenRA.Network
Map = server.Map.Uid;
Mod = manifest.Id;
Version = manifest.Metadata.Version;
ModTitle = manifest.Metadata.Title;
ModTitle = manifest.Metadata.TitleTranslated;
ModWebsite = manifest.Metadata.Website;
ModIcon32 = manifest.Metadata.WebIcon32;
Protected = !string.IsNullOrEmpty(server.Settings.Password);

View File

@@ -122,7 +122,7 @@ namespace OpenRA.Network
Log.Write("sync", $"Player: {Game.Settings.Player.Name} ({Platform.CurrentPlatform} {Environment.OSVersion} {Platform.RuntimeVersion})");
if (Game.IsHost)
Log.Write("sync", "Player is host.");
Log.Write("sync", $"Game ID: {orderManager.LobbyInfo.GlobalSettings.GameUid} (Mod: {mod.Title} at Version {mod.Version})");
Log.Write("sync", $"Game ID: {orderManager.LobbyInfo.GlobalSettings.GameUid} (Mod: {mod.TitleTranslated} at Version {mod.Version})");
Log.Write("sync", $"Sync for net frame {r.Frame} -------------");
Log.Write("sync", $"SharedRandom: {r.SyncedRandom} (#{r.TotalCount})");
Log.Write("sync", "Synced Traits:");

View File

@@ -31,8 +31,8 @@ namespace OpenRA
if (Game.ModData != null)
{
var mod = Game.ModData.Manifest.Metadata;
Log.Write("exception", $"{mod.Title} mod version {mod.Version}");
var manifest = Game.ModData.Manifest;
Log.Write("exception", $"{manifest.Id} mod version {manifest.Metadata.Version}");
}
if (Game.OrderManager != null && Game.OrderManager.World != null && Game.OrderManager.World.Map != null)

View File

@@ -19,6 +19,9 @@ namespace OpenRA.Mods.Cnc
{
public sealed class CncLoadScreen : SheetLoadScreen
{
[FluentReference]
const string Loading = "loadscreen-loading";
int loadTick;
Sprite nodLogo, gdiLogo, evaLogo, brightBlock, dimBlock;
@@ -31,11 +34,15 @@ namespace OpenRA.Mods.Cnc
int lastDensity;
Size lastResolution;
string message = "";
public override void Init(ModData modData, Dictionary<string, string> info)
{
base.Init(modData, info);
versionText = modData.Manifest.Metadata.Version;
message = FluentProvider.GetString(Loading);
}
public override void DisplayInner(Renderer r, Sheet s, int density)
@@ -89,7 +96,7 @@ namespace OpenRA.Mods.Cnc
if (r.Fonts != null)
{
var loadingFont = r.Fonts["BigBold"];
var loadingText = Info["Text"];
var loadingText = message;
var loadingPos = new float2((bounds.Width - loadingFont.Measure(loadingText).X) / 2, barY);
loadingFont.DrawText(loadingText, loadingPos, Color.Gray);

View File

@@ -48,7 +48,12 @@ namespace OpenRA.Mods.Common.FileSystem
if (contentInstalled)
return false;
Game.InitializeMod(content.ContentInstallerMod, new Arguments("Content.Mod=" + modData.Manifest.Id));
string translationPath;
using (var fs = (FileStream)modData.DefaultFileSystem.Open(content.Translation))
translationPath = fs.Name;
Game.InitializeMod(
content.ContentInstallerMod,
new Arguments(new[] { "Content.Mod=" + modData.Manifest.Id, "Content.TranslationFile=" + translationPath }));
return true;
}
}

View File

@@ -43,16 +43,17 @@ namespace OpenRA.Mods.Common.Lint
var mapTranslations = FieldLoader.GetValue<string[]>("value", map.TranslationDefinitions.Value);
foreach (var language in GetModLanguages(modData))
var allModTranslations = modData.Manifest.Translations.Append(modData.Manifest.Get<ModContent>().Translation).ToArray();
foreach (var language in GetModLanguages(allModTranslations))
{
// Check keys and variables are not missing across all language files.
// But for maps we don't warn on unused keys. They might be unused on *this* map,
// but the mod or another map may use them and we don't have sight of that.
CheckKeys(
modData.Manifest.Translations.Concat(mapTranslations), map.Open, usedKeys,
allModTranslations.Concat(mapTranslations), map.Open, usedKeys,
language, _ => false, emitError, emitWarning);
var modFluentBundle = new FluentBundle(language, modData.Manifest.Translations, modData.DefaultFileSystem, _ => { });
var modFluentBundle = new FluentBundle(language, allModTranslations, modData.DefaultFileSystem, _ => { });
var mapFluentBundle = new FluentBundle(language, mapTranslations, map, error => emitError(error.Message));
foreach (var group in usedKeys.KeysWithContext)
@@ -78,14 +79,15 @@ namespace OpenRA.Mods.Common.Lint
foreach (var context in usedKeys.EmptyKeyContexts)
emitWarning($"Empty key in mod translation files required by {context}");
foreach (var language in GetModLanguages(modData))
var allModTranslations = modData.Manifest.Translations.Append(modData.Manifest.Get<ModContent>().Translation).ToArray();
foreach (var language in GetModLanguages(allModTranslations))
{
Console.WriteLine($"Testing language: {language}");
CheckModWidgets(modData, usedKeys, testedFields);
// With the fully populated keys, check keys and variables are not missing and not unused across all language files.
var keyWithAttrs = CheckKeys(
modData.Manifest.Translations, modData.DefaultFileSystem.Open, usedKeys,
allModTranslations, modData.DefaultFileSystem.Open, usedKeys,
language,
file =>
!modData.Manifest.AllowUnusedTranslationsInExternalPackages ||
@@ -113,9 +115,9 @@ namespace OpenRA.Mods.Common.Lint
$"`{field.ReflectedType.Name}.{field.Name}` - previous warnings may be incorrect");
}
static IEnumerable<string> GetModLanguages(ModData modData)
static IEnumerable<string> GetModLanguages(IEnumerable<string> translations)
{
return modData.Manifest.Translations
return translations
.Select(filename => FilenameRegex.Match(filename).Groups["language"].Value)
.Distinct()
.OrderBy(l => l);
@@ -249,51 +251,55 @@ namespace OpenRA.Mods.Common.Lint
.Where(t => t.IsSubclassOf(typeof(TraitInfo)) || t.IsSubclassOf(typeof(Warhead)))
.SelectMany(t => t.GetFields().Where(f => f.HasAttribute<FluentReferenceAttribute>())));
// HACK: Need to hardcode the custom loader for GameSpeeds.
var gameSpeeds = modData.Manifest.Get<GameSpeeds>();
var gameSpeedNameField = typeof(GameSpeed).GetField(nameof(GameSpeed.Name));
var gameSpeedFluentReference = Utility.GetCustomAttributes<FluentReferenceAttribute>(gameSpeedNameField, true)[0];
testedFields.Add(gameSpeedNameField);
foreach (var speed in gameSpeeds.Speeds.Values)
usedKeys.Add(speed.Name, gameSpeedFluentReference, $"`{nameof(GameSpeed)}.{nameof(GameSpeed.Name)}`");
// TODO: linter does not work with LoadUsing
GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
usedKeys, testedFields, Utility.GetFields(typeof(GameSpeed)), modData.Manifest.Get<GameSpeeds>().Speeds.Values);
// TODO: linter does not work with LoadUsing
foreach (var actorInfo in modData.DefaultRules.Actors)
GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
usedKeys, testedFields,
Utility.GetFields(typeof(ResourceRendererInfo.ResourceTypeInfo)),
modData.DefaultRules.Actors
.SelectMany(actorInfo => actorInfo.Value.TraitInfos<ResourceRendererInfo>())
.SelectMany(info => info.ResourceTypes.Values));
const BindingFlags Binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
var constFields = modData.ObjectCreator.GetTypes().SelectMany(modType => modType.GetFields(Binding)).Where(f => f.IsLiteral);
GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
usedKeys, testedFields, constFields, new[] { (object)null });
var modMetadataFields = typeof(ModMetadata).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
usedKeys, testedFields, modMetadataFields, new[] { modData.Manifest.Metadata });
var modContent = modData.Manifest.Get<ModContent>();
GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
usedKeys, testedFields, Utility.GetFields(typeof(ModContent)), new[] { modContent });
GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
usedKeys, testedFields, Utility.GetFields(typeof(ModContent.ModPackage)), modContent.Packages.Values);
return (usedKeys, testedFields);
}
static void GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
Keys usedKeys, List<FieldInfo> testedFields,
IEnumerable<FieldInfo> newFields, IEnumerable<object> objects)
{
var fieldsWithAttribute =
newFields
.Select(f => (Field: f, FluentReference: Utility.GetCustomAttributes<FluentReferenceAttribute>(f, true).SingleOrDefault()))
.Where(x => x.FluentReference != null)
.ToArray();
testedFields.AddRange(fieldsWithAttribute.Select(x => x.Field));
foreach (var obj in objects)
{
foreach (var info in actorInfo.Value.TraitInfos<ResourceRendererInfo>())
foreach (var (field, fluentReference) in fieldsWithAttribute)
{
var resourceTypeNameField = typeof(ResourceRendererInfo.ResourceTypeInfo).GetField(nameof(ResourceRendererInfo.ResourceTypeInfo.Name));
var resourceTypeFluentReference = Utility.GetCustomAttributes<FluentReferenceAttribute>(resourceTypeNameField, true)[0];
testedFields.Add(resourceTypeNameField);
foreach (var resourceTypes in info.ResourceTypes)
usedKeys.Add(
resourceTypes.Value.Name,
resourceTypeFluentReference,
$"`{nameof(ResourceRendererInfo.ResourceTypeInfo)}.{nameof(ResourceRendererInfo.ResourceTypeInfo.Name)}`");
}
}
foreach (var modType in modData.ObjectCreator.GetTypes())
{
const BindingFlags Binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
foreach (var field in modType.GetFields(Binding))
{
// Checking for constant string fields.
if (!field.IsLiteral)
continue;
var fluentReference = Utility.GetCustomAttributes<FluentReferenceAttribute>(field, true).SingleOrDefault();
if (fluentReference == null)
continue;
testedFields.Add(field);
var keys = LintExts.GetFieldValues(null, field, fluentReference.DictionaryReference);
var keys = LintExts.GetFieldValues(obj, field, fluentReference.DictionaryReference);
foreach (var key in keys)
usedKeys.Add(key, fluentReference, $"`{field.ReflectedType.Name}.{field.Name}`");
}
}
return (usedKeys, testedFields);
}
static void CheckModWidgets(ModData modData, Keys usedKeys, List<FieldInfo> testedFields)

View File

@@ -30,10 +30,11 @@ namespace OpenRA.Mods.Common.Lint
void ILintPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData)
{
Run(emitError, emitWarning, modData.DefaultFileSystem, modData.Manifest.Translations);
var allModTranslations = modData.Manifest.Translations.Append(modData.Manifest.Get<ModContent>().Translation);
Run(emitError, emitWarning, modData.DefaultFileSystem, allModTranslations);
}
static void Run(Action<string> emitError, Action<string> emitWarning, IReadOnlyFileSystem fileSystem, string[] paths)
static void Run(Action<string> emitError, Action<string> emitWarning, IReadOnlyFileSystem fileSystem, IEnumerable<string> paths)
{
foreach (var path in paths)
{

View File

@@ -9,7 +9,9 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Widgets;
using OpenRA.Primitives;
@@ -18,6 +20,9 @@ namespace OpenRA.Mods.Common.LoadScreens
{
public sealed class LogoStripeLoadScreen : SheetLoadScreen
{
[FluentReference]
const string Loading = "loadscreen-loading";
Rectangle stripeRect;
float2 logoPos;
Sprite stripe, logo;
@@ -26,14 +31,13 @@ namespace OpenRA.Mods.Common.LoadScreens
int lastDensity;
Size lastResolution;
string[] messages = { "Loading..." };
string[] messages = Array.Empty<string>();
public override void Init(ModData modData, Dictionary<string, string> info)
{
base.Init(modData, info);
if (info.TryGetValue("Text", out var text))
messages = text.Split(',');
messages = FluentProvider.GetString(Loading).Split(',').Select(x => x.Trim()).ToArray();
}
public override void DisplayInner(Renderer r, Sheet s, int density)
@@ -59,7 +63,7 @@ namespace OpenRA.Mods.Common.LoadScreens
if (logo != null)
r.RgbaSpriteRenderer.DrawSprite(logo, logoPos);
if (r.Fonts != null)
if (r.Fonts != null && messages.Length > 0)
{
var text = messages.Random(Game.CosmeticRandom);
var textSize = r.Fonts["Bold"].Measure(text);

View File

@@ -52,6 +52,10 @@ namespace OpenRA.Mods.Common.LoadScreens
if (modId == null || !Game.Mods.TryGetValue(modId, out var selectedMod))
throw new InvalidOperationException("Invalid or missing Content.Mod argument.");
var translationFilePath = args.GetValue("Content.TranslationFile", null);
if (translationFilePath == null || !File.Exists(translationFilePath))
throw new InvalidOperationException("Invalid or missing Content.TranslationFile argument.");
var content = selectedMod.Get<ModContent>(Game.ModData.ObjectCreator);
Ui.LoadWidget("MODCONTENT_BACKGROUND", Ui.Root, new WidgetArgs());
@@ -63,6 +67,7 @@ namespace OpenRA.Mods.Common.LoadScreens
{ "continueLoading", () => Game.RunAfterTick(() => Game.InitializeMod(modId, new Arguments())) },
{ "mod", selectedMod },
{ "content", content },
{ "translationFilePath", translationFilePath },
};
Ui.OpenWindow("CONTENT_PROMPT_PANEL", widgetArgs);
@@ -71,9 +76,10 @@ namespace OpenRA.Mods.Common.LoadScreens
{
var widgetArgs = new WidgetArgs
{
{ "onCancel", () => Game.RunAfterTick(() => Game.InitializeMod(modId, new Arguments())) },
{ "mod", selectedMod },
{ "content", content },
{ "onCancel", () => Game.RunAfterTick(() => Game.InitializeMod(modId, new Arguments())) }
{ "translationFilePath", translationFilePath },
};
Ui.OpenWindow("CONTENT_PANEL", widgetArgs);

View File

@@ -21,6 +21,7 @@ namespace OpenRA
{
public class ModPackage
{
[FluentReference]
public readonly string Title;
public readonly string Identifier;
public readonly string[] TestFiles = Array.Empty<string>();
@@ -100,10 +101,13 @@ namespace OpenRA
}
}
[FluentReference]
public readonly string InstallPromptMessage;
public readonly string QuickDownload;
[FluentReference]
public readonly string HeaderMessage;
public readonly string ContentInstallerMod = "modcontent";
public readonly string Translation;
[FieldLoader.LoadUsing(nameof(LoadPackages))]
public readonly Dictionary<string, ModPackage> Packages = new();

View File

@@ -62,7 +62,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
var maps = new List<(IReadWritePackage Package, string Map)>();
if (args.Length < 2)
{
Console.WriteLine($"Testing mod: {modData.Manifest.Metadata.Title}");
Console.WriteLine($"Testing mod: {modData.Manifest.Metadata.TitleTranslated}");
// Run all rule checks on the default mod rules.
CheckRules(modData, modData.DefaultRules);

View File

@@ -193,7 +193,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var switchButton = panel.Get<ButtonWidget>("SWITCH_BUTTON");
var mod = CurrentServerSettings.ServerExternalMod;
var modTitle = mod.Title;
var modTitle = mod.Id;
var modVersion = mod.Version;
switchButton.OnClick = () =>

View File

@@ -87,6 +87,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly ModData modData;
readonly ModContent content;
readonly Dictionary<string, ModContent.ModSource> sources;
readonly FluentBundle externalFluentBundle;
readonly Widget panel;
readonly LabelWidget titleLabel;
@@ -116,11 +117,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Mode visible = Mode.Progress;
[ObjectCreator.UseCtor]
public InstallFromSourceLogic(Widget widget, ModData modData, ModContent content, Dictionary<string, ModContent.ModSource> sources)
public InstallFromSourceLogic(
Widget widget, ModData modData, ModContent content, Dictionary<string, ModContent.ModSource> sources, FluentBundle externalFluentBundle)
{
this.modData = modData;
this.content = content;
this.sources = sources;
this.externalFluentBundle = externalFluentBundle;
Log.AddChannel("install", "install.log");
@@ -339,7 +342,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
var containerWidget = (ContainerWidget)checkboxListTemplate.Clone();
var checkboxWidget = containerWidget.Get<CheckboxWidget>("PACKAGE_CHECKBOX");
checkboxWidget.GetText = () => package.Title;
var title = externalFluentBundle.GetString(package.Title);
checkboxWidget.GetText = () => title;
checkboxWidget.IsDisabled = () => package.Required;
checkboxWidget.IsChecked = () => selectedPackages[package.Identifier];
checkboxWidget.OnClick = () => selectedPackages[package.Identifier] = !selectedPackages[package.Identifier];

View File

@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Widgets;
@@ -30,10 +31,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly Dictionary<string, ModContent.ModSource> sources = new();
readonly Dictionary<string, ModContent.ModDownload> downloads = new();
readonly FluentBundle externalFluentBundle;
bool sourceAvailable;
[ObjectCreator.UseCtor]
public ModContentLogic(Widget widget, Manifest mod, ModContent content, Action onCancel)
public ModContentLogic(Widget widget, Manifest mod, ModContent content, Action onCancel, string translationFilePath)
{
this.content = content;
@@ -58,20 +61,25 @@ namespace OpenRA.Mods.Common.Widgets.Logic
modFileSystem.UnmountAll();
externalFluentBundle = new FluentBundle(Game.Settings.Player.Language, File.ReadAllText(translationFilePath), _ => { });
scrollPanel = panel.Get<ScrollPanelWidget>("PACKAGES");
template = scrollPanel.Get<ContainerWidget>("PACKAGE_TEMPLATE");
var headerTemplate = panel.Get<LabelWidget>("HEADER_TEMPLATE");
var headerLines = !string.IsNullOrEmpty(content.HeaderMessage) ? content.HeaderMessage.Replace("\\n", "\n").Split('\n') : Array.Empty<string>();
var headerLines =
!string.IsNullOrEmpty(content.HeaderMessage)
? externalFluentBundle.GetString(content.HeaderMessage)
: null;
var headerHeight = 0;
foreach (var l in headerLines)
if (headerLines != null)
{
var line = (LabelWidget)headerTemplate.Clone();
line.GetText = () => l;
line.Bounds.Y += headerHeight;
panel.AddChild(line);
var label = (LabelWidget)headerTemplate.Clone();
label.GetText = () => headerLines;
label.IncreaseHeightToFitCurrentText();
panel.AddChild(label);
headerHeight += headerTemplate.Bounds.Height;
headerHeight += label.Bounds.Height;
}
panel.Bounds.Height += headerHeight;
@@ -85,7 +93,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
sourceButton.OnClick = () => Ui.OpenWindow("SOURCE_INSTALL_PANEL", new WidgetArgs
{
{ "sources", sources },
{ "content", content }
{ "content", content },
{ "externalFluentBundle", externalFluentBundle },
});
var backButton = panel.Get<ButtonWidget>("BACK_BUTTON");
@@ -109,7 +118,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
var container = template.Clone();
var titleWidget = container.Get<LabelWidget>("TITLE");
var title = p.Value.Title;
var title = externalFluentBundle.GetString(p.Value.Title);
titleWidget.GetText = () => title;
var requiredWidget = container.Get<LabelWidget>("REQUIRED");

View File

@@ -27,14 +27,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic
const string Quit = "button-quit";
readonly ModContent content;
readonly FluentBundle externalFluentBundle;
bool requiredContentInstalled;
[ObjectCreator.UseCtor]
public ModContentPromptLogic(ModData modData, Widget widget, Manifest mod, ModContent content, Action continueLoading)
public ModContentPromptLogic(ModData modData, Widget widget, Manifest mod, ModContent content, Action continueLoading, string translationFilePath)
{
this.content = content;
CheckRequiredContentInstalled();
externalFluentBundle = new FluentBundle(Game.Settings.Player.Language, File.ReadAllText(translationFilePath), _ => { });
var continueMessage = FluentProvider.GetString(Continue);
var quitMessage = FluentProvider.GetString(Quit);
@@ -42,17 +45,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var headerTemplate = panel.Get<LabelWidget>("HEADER_TEMPLATE");
var headerLines =
!string.IsNullOrEmpty(content.InstallPromptMessage)
? content.InstallPromptMessage.Replace("\\n", "\n").Split('\n')
: Array.Empty<string>();
? externalFluentBundle.GetString(content.InstallPromptMessage)
: null;
var headerHeight = 0;
foreach (var l in headerLines)
if (headerLines != null)
{
var line = (LabelWidget)headerTemplate.Clone();
line.GetText = () => l;
line.Bounds.Y += headerHeight;
panel.AddChild(line);
var label = (LabelWidget)headerTemplate.Clone();
label.GetText = () => headerLines;
label.IncreaseHeightToFitCurrentText();
panel.AddChild(label);
headerHeight += headerTemplate.Bounds.Height;
headerHeight += label.Bounds.Height;
}
panel.Bounds.Height += headerHeight;
@@ -64,9 +67,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
Ui.OpenWindow("CONTENT_PANEL", new WidgetArgs
{
{ "onCancel", CheckRequiredContentInstalled },
{ "mod", mod },
{ "content", content },
{ "onCancel", CheckRequiredContentInstalled }
{ "translationFilePath", translationFilePath },
});
};

View File

@@ -93,7 +93,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
return;
var content = modData.Manifest.Get<ModContent>();
Game.InitializeMod(content.ContentInstallerMod, new Arguments(new[] { "Content.Mod=" + modData.Manifest.Id }));
string translationPath;
using (var fs = (FileStream)modData.DefaultFileSystem.Open(content.Translation))
translationPath = fs.Name;
Game.InitializeMod(
content.ContentInstallerMod,
new Arguments(new[] { "Content.Mod=" + modData.Manifest.Id, "Content.TranslationFile=" + translationPath }));
});
};
}

View File

@@ -93,9 +93,6 @@ namespace OpenRA.Server
modData.MapCache.LoadPreviewImages = false; // PERF: Server doesn't need previews, save memory by not loading them.
modData.MapCache.LoadMaps();
// HACK: Related to the above one, initialize fluent so we can resolve lobby option ids to strings.
FluentProvider.Initialize(modData, modData.DefaultFileSystem);
var endpoints = new List<IPEndPoint> { new(IPAddress.IPv6Any, settings.ListenPort), new(IPAddress.Any, settings.ListenPort) };
var server = new Server(endpoints, settings, modData, ServerType.Dedicated);

View File

@@ -0,0 +1,6 @@
## Metadata
mod-title = Tiberian Dawn
mod-windowtitle = OpenRA - Tiberian Dawn
## CncLoadScreen
loadscreen-loading = Loading

View File

@@ -0,0 +1,13 @@
modcontent-installprompt =
Tiberian Dawn requires artwork and audio from the original game.
Quick Install will automatically download this content (without music or videos) from a mirror of the 2007 C&C Gold freeware release.
Advanced Install includes options for copying the music, videos, and other content from an original game disc or digital installation.
modcontent-header =
Game content may be extracted from the original game discs or an existing digital install. OpenRA can also download the base game files from an online mirror of the 2007 freeware release of C&C.
modcontent-package-basefiles = Base Game Files
modcontent-package-basemusic = Base Game Music
modcontent-package-gdi = GDI Campaign Briefings
modcontent-package-nod = Nod Campaign Briefings
modcontent-package-covertopsmusic = Covert Operations Music

View File

@@ -1,9 +1,9 @@
Metadata:
Title: Tiberian Dawn
Title: mod-title
Version: {DEV_VERSION}
Website: https://www.openra.net
WebIcon32: https://www.openra.net/images/icons/cnc_32x32.png
WindowTitle: OpenRA - Tiberian Dawn
WindowTitle: mod-windowtitle
PackageFormats: Mix
@@ -146,6 +146,7 @@ ChromeLayout:
Translations:
common|languages/en.ftl
common|languages/rules/en.ftl
cnc|languages/en.ftl
cnc|languages/chrome/en.ftl
cnc|languages/rules/en.ftl
@@ -176,7 +177,6 @@ LoadScreen: CncLoadScreen
Image: cnc|uibits/chrome.png
Image2x: cnc|uibits/chrome-2x.png
Image3x: cnc|uibits/chrome-3x.png
Text: Loading
ServerTraits:
LobbyCommands
@@ -278,34 +278,34 @@ GameSpeeds:
OrderLatency: 6
ModContent:
InstallPromptMessage: Tiberian Dawn requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without music\nor videos) from a mirror of the 2007 C&C Gold freeware release.\n\nAdvanced Install includes options for copying the music, videos, and\nother content from an original game disc or digital installation.
InstallPromptMessage: modcontent-installprompt
QuickDownload: basefiles
HeaderMessage: Game content may be extracted from the original game discs or an\nexisting digital install. OpenRA can also download the base game\nfiles from an online mirror of the 2007 freeware release of C&C.
HeaderMessage: modcontent-header
Packages:
ContentPackage@base:
Title: Base Game Files
Title: modcontent-package-basefiles
Identifier: base
TestFiles: ^SupportDir|Content/cnc/conquer.mix, ^SupportDir|Content/cnc/desert.mix, ^SupportDir|Content/cnc/sounds.mix, ^SupportDir|Content/cnc/speech.mix, ^SupportDir|Content/cnc/temperat.mix, ^SupportDir|Content/cnc/tempicnh.mix, ^SupportDir|Content/cnc/winter.mix
Sources: gdi95, nod95, tfd, tuc-steam, tuc-origin, cncr-steam, cncr-origin
Required: true
Download: basefiles
ContentPackage@music:
Title: Base Game Music
Title: modcontent-package-basemusic
Identifier: music
TestFiles: ^SupportDir|Content/cnc/scores.mix
Sources: gdi95, nod95, tfd, tuc-steam, tuc-origin, cncr-steam, cncr-origin
ContentPackage@movies-gdi:
Title: GDI Campaign Briefings
Title: modcontent-package-gdi
Identifier: movies-gdi
TestFiles: ^SupportDir|Content/cnc/movies/visor.vqa, ^SupportDir|Content/cnc/movies/turtkill.vqa, ^SupportDir|Content/cnc/movies/tbrinfo3.vqa, ^SupportDir|Content/cnc/movies/tbrinfo2.vqa, ^SupportDir|Content/cnc/movies/tbrinfo1.vqa, ^SupportDir|Content/cnc/movies/seige.vqa, ^SupportDir|Content/cnc/movies/samsite.vqa, ^SupportDir|Content/cnc/movies/samdie.vqa, ^SupportDir|Content/cnc/movies/sabotage.vqa, ^SupportDir|Content/cnc/movies/retro.vqa, ^SupportDir|Content/cnc/movies/podium.vqa, ^SupportDir|Content/cnc/movies/planecra.vqa, ^SupportDir|Content/cnc/movies/pintle.vqa, ^SupportDir|Content/cnc/movies/paratrop.vqa, ^SupportDir|Content/cnc/movies/nodsweep.vqa, ^SupportDir|Content/cnc/movies/nodlose.vqa, ^SupportDir|Content/cnc/movies/nodflees.vqa, ^SupportDir|Content/cnc/movies/nod1.vqa, ^SupportDir|Content/cnc/movies/nitejump.vqa, ^SupportDir|Content/cnc/movies/napalm.vqa, ^SupportDir|Content/cnc/movies/logo.vqa, ^SupportDir|Content/cnc/movies/landing.vqa, ^SupportDir|Content/cnc/movies/intro2.vqa, ^SupportDir|Content/cnc/movies/hellvaly.vqa, ^SupportDir|Content/cnc/movies/gunboat.vqa, ^SupportDir|Content/cnc/movies/generic.vqa, ^SupportDir|Content/cnc/movies/gdilose.vqa, ^SupportDir|Content/cnc/movies/gdifinb.vqa, ^SupportDir|Content/cnc/movies/gdifina.vqa, ^SupportDir|Content/cnc/movies/gdiend2.vqa, ^SupportDir|Content/cnc/movies/gdiend1.vqa, ^SupportDir|Content/cnc/movies/gdi9.vqa, ^SupportDir|Content/cnc/movies/gdi8b.vqa, ^SupportDir|Content/cnc/movies/gdi8a.vqa, ^SupportDir|Content/cnc/movies/gdi7.vqa, ^SupportDir|Content/cnc/movies/gdi6.vqa, ^SupportDir|Content/cnc/movies/gdi5.vqa, ^SupportDir|Content/cnc/movies/gdi4b.vqa, ^SupportDir|Content/cnc/movies/gdi4a.vqa, ^SupportDir|Content/cnc/movies/gdi3lose.vqa, ^SupportDir|Content/cnc/movies/gdi3.vqa, ^SupportDir|Content/cnc/movies/gdi2.vqa, ^SupportDir|Content/cnc/movies/gdi15.vqa, ^SupportDir|Content/cnc/movies/gdi14.vqa, ^SupportDir|Content/cnc/movies/gdi13.vqa, ^SupportDir|Content/cnc/movies/gdi12.vqa, ^SupportDir|Content/cnc/movies/gdi11.vqa, ^SupportDir|Content/cnc/movies/gdi10.vqa, ^SupportDir|Content/cnc/movies/gdi1.vqa, ^SupportDir|Content/cnc/movies/gameover.vqa, ^SupportDir|Content/cnc/movies/forestkl.vqa, ^SupportDir|Content/cnc/movies/flyy.vqa, ^SupportDir|Content/cnc/movies/flag.vqa, ^SupportDir|Content/cnc/movies/dino.vqa, ^SupportDir|Content/cnc/movies/desolat.vqa, ^SupportDir|Content/cnc/movies/consyard.vqa, ^SupportDir|Content/cnc/movies/cc2tease.vqa, ^SupportDir|Content/cnc/movies/burdet2.vqa, ^SupportDir|Content/cnc/movies/burdet1.vqa, ^SupportDir|Content/cnc/movies/bombflee.vqa, ^SupportDir|Content/cnc/movies/bombaway.vqa, ^SupportDir|Content/cnc/movies/bkground.vqa, ^SupportDir|Content/cnc/movies/bcanyon.vqa, ^SupportDir|Content/cnc/movies/banner.vqa
Sources: gdi95, tfd, tuc-steam, tuc-origin, cncr-steam, cncr-origin
ContentPackage@movies-nod:
Title: Nod Campaign Briefings
Title: modcontent-package-nod
Identifier: movies-nod
TestFiles: ^SupportDir|Content/cnc/movies/visor.vqa, ^SupportDir|Content/cnc/movies/trtkil_d.vqa, ^SupportDir|Content/cnc/movies/tiberfx.vqa, ^SupportDir|Content/cnc/movies/tankkill.vqa, ^SupportDir|Content/cnc/movies/tankgo.vqa, ^SupportDir|Content/cnc/movies/sundial.vqa, ^SupportDir|Content/cnc/movies/stealth.vqa, ^SupportDir|Content/cnc/movies/spycrash.vqa, ^SupportDir|Content/cnc/movies/sethpre.vqa, ^SupportDir|Content/cnc/movies/seige.vqa, ^SupportDir|Content/cnc/movies/samsite.vqa, ^SupportDir|Content/cnc/movies/retro.vqa, ^SupportDir|Content/cnc/movies/refint.vqa, ^SupportDir|Content/cnc/movies/obel.vqa, ^SupportDir|Content/cnc/movies/nuke.vqa, ^SupportDir|Content/cnc/movies/nodlose.vqa, ^SupportDir|Content/cnc/movies/nodfinal.vqa, ^SupportDir|Content/cnc/movies/nodend4.vqa, ^SupportDir|Content/cnc/movies/nodend3.vqa, ^SupportDir|Content/cnc/movies/nodend2.vqa, ^SupportDir|Content/cnc/movies/nodend1.vqa, ^SupportDir|Content/cnc/movies/nod9.vqa, ^SupportDir|Content/cnc/movies/nod8.vqa, ^SupportDir|Content/cnc/movies/nod7b.vqa, ^SupportDir|Content/cnc/movies/nod7a.vqa, ^SupportDir|Content/cnc/movies/nod6.vqa, ^SupportDir|Content/cnc/movies/nod5.vqa, ^SupportDir|Content/cnc/movies/nod4b.vqa, ^SupportDir|Content/cnc/movies/nod4a.vqa, ^SupportDir|Content/cnc/movies/nod3.vqa, ^SupportDir|Content/cnc/movies/nod2.vqa, ^SupportDir|Content/cnc/movies/nod1pre.vqa, ^SupportDir|Content/cnc/movies/nod13.vqa, ^SupportDir|Content/cnc/movies/nod12.vqa, ^SupportDir|Content/cnc/movies/nod11.vqa, ^SupportDir|Content/cnc/movies/nod10b.vqa, ^SupportDir|Content/cnc/movies/nod10a.vqa, ^SupportDir|Content/cnc/movies/nod1.vqa, ^SupportDir|Content/cnc/movies/logo.vqa, ^SupportDir|Content/cnc/movies/landing.vqa, ^SupportDir|Content/cnc/movies/kanepre.vqa, ^SupportDir|Content/cnc/movies/intro2.vqa, ^SupportDir|Content/cnc/movies/insites.vqa, ^SupportDir|Content/cnc/movies/generic.vqa, ^SupportDir|Content/cnc/movies/gdi1.vqa, ^SupportDir|Content/cnc/movies/gameover.vqa, ^SupportDir|Content/cnc/movies/forestkl.vqa, ^SupportDir|Content/cnc/movies/flag.vqa, ^SupportDir|Content/cnc/movies/dino.vqa, ^SupportDir|Content/cnc/movies/dessweep.vqa, ^SupportDir|Content/cnc/movies/deskill.vqa, ^SupportDir|Content/cnc/movies/desflees.vqa, ^SupportDir|Content/cnc/movies/consyard.vqa, ^SupportDir|Content/cnc/movies/cc2tease.vqa, ^SupportDir|Content/cnc/movies/bombflee.vqa, ^SupportDir|Content/cnc/movies/bombaway.vqa, ^SupportDir|Content/cnc/movies/bcanyon.vqa, ^SupportDir|Content/cnc/movies/banner.vqa, ^SupportDir|Content/cnc/movies/akira.vqa, ^SupportDir|Content/cnc/movies/airstrk.vqa
Sources: nod95, tfd, tuc-steam, tuc-origin, cncr-steam, cncr-origin
ContentPackage@music-covertops:
Title: Covert Operations Music
Title: modcontent-package-covertopsmusic
Identifier: music-covertops
TestFiles: ^SupportDir|Content/cnc/scores-covertops.mix
Sources: covertops, tfd, tuc-steam, tuc-origin, cncr-steam, cncr-origin
@@ -318,6 +318,7 @@ ModContent:
cnc|installer/nod95.yaml
cnc|installer/origin.yaml
cnc|installer/steam.yaml
Translation: cnc|languages/modcontent/en.ftl
DiscordService:
ApplicationId: 699223250181292033

View File

@@ -0,0 +1,6 @@
## Metadata
mod-title = Dune 2000
mod-windowtitle = OpenRA - Dune 2000
## LogoStripLoadScreen
loadscreen-loading = Filling Crates..., Breeding Sandworms..., Fuelling carryalls..., Deploying harvesters..., Preparing thopters..., Summoning mentats...

View File

@@ -0,0 +1,12 @@
modcontent-installprompt =
Dune 2000 requires artwork and audio from the original game.
Quick Install will automatically download this content (without music or videos) from an online mirror of the game files.
Advanced Install includes options for copying the music, videos, and other content from an original game disc.
modcontent-header =
The original game content may be copied from an original game disc, or downloaded from an online mirror of the game files.
modcontent-package-basefiles = Base Game Files
modcontent-package-106patch = 1.06 Patch Content
modcontent-package-music = Game Music
modcontent-package-briefings = Campaign Briefings

View File

@@ -1,9 +1,9 @@
Metadata:
Title: Dune 2000
Title: mod-title
Version: {DEV_VERSION}
Website: https://www.openra.net
WebIcon32: https://www.openra.net/images/icons/d2k_32x32.png
WindowTitle: OpenRA - Dune 2000
WindowTitle: mod-windowtitle
PackageFormats: D2kSoundResources
@@ -124,6 +124,7 @@ Translations:
common|languages/en.ftl
common|languages/chrome/en.ftl
common|languages/rules/en.ftl
d2k|languages/en.ftl
d2k|languages/chrome/en.ftl
d2k|languages/rules/en.ftl
@@ -160,7 +161,6 @@ LoadScreen: LogoStripeLoadScreen
Image: d2k|uibits/loadscreen.png
Image2x: d2k|uibits/loadscreen-2x.png
Image3x: d2k|uibits/loadscreen-3x.png
Text: Filling Crates..., Breeding Sandworms..., Fuelling carryalls..., Deploying harvesters..., Preparing thopters..., Summoning mentats...
ServerTraits:
LobbyCommands
@@ -259,31 +259,31 @@ GameSpeeds:
OrderLatency: 6
ModContent:
InstallPromptMessage: Dune 2000 requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without\nmusic or videos) from an online mirror of the game files.\n\nAdvanced Install includes options for copying the music, videos,\nand other content from an original game disc.
HeaderMessage: The original game content may be copied from an original game disc,\nor downloaded from an online mirror of the game files.
InstallPromptMessage: modcontent-installprompt
HeaderMessage: modcontent-header
QuickDownload: quickinstall
Packages:
ContentPackage@base:
Title: Base Game Files
Title: modcontent-package-basefiles
Identifier: base
TestFiles: ^SupportDir|Content/d2k/v3/BLOXBASE.R16, ^SupportDir|Content/d2k/v3/BLOXBAT.R16, ^SupportDir|Content/d2k/v3/BLOXBGBS.R16, ^SupportDir|Content/d2k/v3/BLOXICE.R16, ^SupportDir|Content/d2k/v3/BLOXTREE.R16, ^SupportDir|Content/d2k/v3/BLOXWAST.R16, ^SupportDir|Content/d2k/v3/SOUND.RS, ^SupportDir|Content/d2k/v3/PALETTE.BIN
Sources: d2k, gruntmods
Required: true
Download: basefiles
ContentPackage@patch:
Title: 1.06 Patch Content
Title: modcontent-package-106patch
Identifier: patch
TestFiles: ^SupportDir|Content/d2k/v3/DATA.R16
Sources: gruntmods
Required: true
Download: patch106
ContentPackage@music:
Title: Game Music
Title: modcontent-package-music
Identifier: music
TestFiles: ^SupportDir|Content/d2k/v3/Music/AMBUSH.AUD, ^SupportDir|Content/d2k/v3/Music/WAITGAME.AUD
Sources: d2k, gruntmods
ContentPackage@movies:
Title: Campaign Briefings
Title: modcontent-package-briefings
Identifier: movies
TestFiles: ^SupportDir|Content/d2k/v3/Movies/A_BR01_E.VQA
Sources: d2k
@@ -292,6 +292,7 @@ ModContent:
Sources:
d2k|installer/d2k.yaml
d2k|installer/gruntmods.yaml
Translation: d2k|languages/modcontent/en.ftl
DiscordService:
ApplicationId: 712711732770111550

View File

@@ -21,10 +21,12 @@ Background@CONTENT_PANEL:
Align: Center
Font: MediumBold
Label@HEADER_TEMPLATE:
X: 30
Y: 66
Width: PARENT_RIGHT
Width: PARENT_RIGHT - 60
Height: 16
Align: Center
WordWrap: true
ScrollPanel@PACKAGES:
X: 30
Y: 84
@@ -342,10 +344,12 @@ Background@CONTENT_PROMPT_PANEL:
Align: Center
Font: MediumBold
Label@HEADER_TEMPLATE:
X: 30
Y: 65
Width: PARENT_RIGHT
Width: PARENT_RIGHT - 60
Height: 16
Align: Center
WordWrap: true
Button@ADVANCED_BUTTON:
X: 30
Y: PARENT_BOTTOM - 52

View File

@@ -0,0 +1,2 @@
## Metadata
mod-title = Mod Content Manager

View File

@@ -1,5 +1,5 @@
Metadata:
Title: Mod Content Manager
Title: mod-title
Version: {DEV_VERSION}
Hidden: true
@@ -37,6 +37,7 @@ ChromeMetrics:
Translations:
common|languages/en.ftl
modcontent|languages/en.ftl
Fonts:
Tiny:

6
mods/ra/languages/en.ftl Normal file
View File

@@ -0,0 +1,6 @@
## Metadata
mod-title = Red Alert
mod-windowtitle = OpenRA - Red Alert
## LogoStripeLoadScreen
loadscreen-loading = Filling Crates..., Charging Capacitors..., Reticulating Splines..., Planting Trees..., Building Bridges..., Aging Empires..., Compiling EVA..., Constructing Pylons..., Activating Skynet..., Splitting Atoms...

View File

@@ -0,0 +1,16 @@
modcontent-installprompt =
Red Alert requires artwork and audio from the original game.
Quick Install will automatically download this content (without music or videos) from a mirror of the 2008 Red Alert freeware release.
Advanced Install includes options for copying the music, videos, and other content from an original game disc or digital installation.
modcontent-header =
Game content may be extracted from the original game discs or an existing digital install. OpenRA can also download the base game files from an online mirror of the 2008 freeware release of RA.
modcontent-package-basefiles = Base Game Files
modcontent-package-aftermathfiles = Aftermath Expansion Files
modcontent-package-deserttileset = C&C Desert Tileset
modcontent-package-basemusic = Base Game Music
modcontent-package-allied = Allied Campaign Briefings
modcontent-package-soviet = Soviet Campaign Briefings
modcontent-package-counterstrikemusic = Counterstrike Music
modcontent-package-aftermathmusic = Aftermath Music

View File

@@ -1,9 +1,9 @@
Metadata:
Title: Red Alert
Title: mod-title
Version: {DEV_VERSION}
Website: https://www.openra.net
WebIcon32: https://www.openra.net/images/icons/ra_32x32.png
WindowTitle: OpenRA - Red Alert
WindowTitle: mod-windowtitle
PackageFormats: Mix
@@ -142,6 +142,7 @@ Translations:
common|languages/en.ftl
common|languages/chrome/en.ftl
common|languages/rules/en.ftl
ra|languages/en.ftl
ra|languages/chrome/en.ftl
ra|languages/rules/en.ftl
@@ -179,7 +180,6 @@ LoadScreen: LogoStripeLoadScreen
Image: ra|uibits/loadscreen.png
Image2x: ra|uibits/loadscreen-2x.png
Image3x: ra|uibits/loadscreen-3x.png
Text: Filling Crates..., Charging Capacitors..., Reticulating Splines..., Planting Trees..., Building Bridges..., Aging Empires..., Compiling EVA..., Constructing Pylons..., Activating Skynet..., Splitting Atoms...
ServerTraits:
LobbyCommands
@@ -281,53 +281,53 @@ GameSpeeds:
OrderLatency: 6
ModContent:
InstallPromptMessage: Red Alert requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without music\nor videos) from a mirror of the 2008 Red Alert freeware release.\n\nAdvanced Install includes options for copying the music, videos, and\nother content from an original game disc or digital installation.
InstallPromptMessage: modcontent-installprompt
QuickDownload: quickinstall
HeaderMessage: Game content may be extracted from the original game discs or an\nexisting digital install. OpenRA can also download the base game\nfiles from an online mirror of the 2008 freeware release of RA.
HeaderMessage: modcontent-header
Packages:
ContentPackage@base:
Title: Base Game Files
Title: modcontent-package-basefiles
Identifier: base
TestFiles: ^SupportDir|Content/ra/v2/allies.mix, ^SupportDir|Content/ra/v2/conquer.mix, ^SupportDir|Content/ra/v2/interior.mix, ^SupportDir|Content/ra/v2/hires.mix, ^SupportDir|Content/ra/v2/lores.mix, ^SupportDir|Content/ra/v2/local.mix, ^SupportDir|Content/ra/v2/speech.mix, ^SupportDir|Content/ra/v2/russian.mix, ^SupportDir|Content/ra/v2/snow.mix, ^SupportDir|Content/ra/v2/sounds.mix, ^SupportDir|Content/ra/v2/temperat.mix
Sources: allied, soviet, tfd, ra-steam, ra-origin, cncr-steam, cncr-origin
Required: true
Download: basefiles
ContentPackage@aftermathbase:
Title: Aftermath Expansion Files
Title: modcontent-package-aftermathfiles
Identifier: aftermathbase
TestFiles: ^SupportDir|Content/ra/v2/expand/expand2.mix, ^SupportDir|Content/ra/v2/expand/hires1.mix, ^SupportDir|Content/ra/v2/expand/lores1.mix, ^SupportDir|Content/ra/v2/expand/chrotnk1.aud, ^SupportDir|Content/ra/v2/expand/fixit1.aud, ^SupportDir|Content/ra/v2/expand/jburn1.aud, ^SupportDir|Content/ra/v2/expand/jchrge1.aud, ^SupportDir|Content/ra/v2/expand/jcrisp1.aud, ^SupportDir|Content/ra/v2/expand/jdance1.aud, ^SupportDir|Content/ra/v2/expand/jjuice1.aud, ^SupportDir|Content/ra/v2/expand/jjump1.aud, ^SupportDir|Content/ra/v2/expand/jlight1.aud, ^SupportDir|Content/ra/v2/expand/jpower1.aud, ^SupportDir|Content/ra/v2/expand/jshock1.aud, ^SupportDir|Content/ra/v2/expand/jyes1.aud, ^SupportDir|Content/ra/v2/expand/madchrg2.aud, ^SupportDir|Content/ra/v2/expand/madexplo.aud, ^SupportDir|Content/ra/v2/expand/mboss1.aud, ^SupportDir|Content/ra/v2/expand/mhear1.aud, ^SupportDir|Content/ra/v2/expand/mhotdig1.aud, ^SupportDir|Content/ra/v2/expand/mhowdy1.aud, ^SupportDir|Content/ra/v2/expand/mhuh1.aud, ^SupportDir|Content/ra/v2/expand/mlaff1.aud, ^SupportDir|Content/ra/v2/expand/mrise1.aud, ^SupportDir|Content/ra/v2/expand/mwrench1.aud, ^SupportDir|Content/ra/v2/expand/myeehaw1.aud, ^SupportDir|Content/ra/v2/expand/myes1.aud
Sources: aftermath, tfd, ra-steam, ra-origin, cncr-steam, cncr-origin
Required: true
Download: aftermath
ContentPackage@cncdesert:
Title: C&C Desert Tileset
Title: modcontent-package-deserttileset
Identifier: cncdesert
TestFiles: ^SupportDir|Content/ra/v2/cnc/desert.mix
Sources: tfd, cnc-steam, cnc-origin, cnc95, cncr-steam, cncr-origin
Required: true
Download: cncdesert
ContentPackage@music:
Title: Base Game Music
Title: modcontent-package-basemusic
Identifier: music
TestFiles: ^SupportDir|Content/ra/v2/scores.mix
Sources: allied, soviet, tfd, ra-steam, ra-origin, cncr-steam, cncr-origin
ContentPackage@movies-allied:
Title: Allied Campaign Briefings
Title: modcontent-package-allied
Identifier: movies-allied
TestFiles: ^SupportDir|Content/ra/v2/movies/aagun.vqa, ^SupportDir|Content/ra/v2/movies/aftrmath.vqa, ^SupportDir|Content/ra/v2/movies/ally1.vqa, ^SupportDir|Content/ra/v2/movies/ally10.vqa, ^SupportDir|Content/ra/v2/movies/ally10b.vqa, ^SupportDir|Content/ra/v2/movies/ally11.vqa, ^SupportDir|Content/ra/v2/movies/ally12.vqa, ^SupportDir|Content/ra/v2/movies/ally14.vqa, ^SupportDir|Content/ra/v2/movies/ally2.vqa, ^SupportDir|Content/ra/v2/movies/ally4.vqa, ^SupportDir|Content/ra/v2/movies/ally5.vqa, ^SupportDir|Content/ra/v2/movies/ally6.vqa, ^SupportDir|Content/ra/v2/movies/ally8.vqa, ^SupportDir|Content/ra/v2/movies/ally9.vqa, ^SupportDir|Content/ra/v2/movies/allyend.vqa, ^SupportDir|Content/ra/v2/movies/allymorf.vqa, ^SupportDir|Content/ra/v2/movies/apcescpe.vqa, ^SupportDir|Content/ra/v2/movies/assess.vqa, ^SupportDir|Content/ra/v2/movies/battle.vqa, ^SupportDir|Content/ra/v2/movies/binoc.vqa, ^SupportDir|Content/ra/v2/movies/bmap.vqa, ^SupportDir|Content/ra/v2/movies/brdgtilt.vqa, ^SupportDir|Content/ra/v2/movies/crontest.vqa, ^SupportDir|Content/ra/v2/movies/cronfail.vqa, ^SupportDir|Content/ra/v2/movies/destroyr.vqa, ^SupportDir|Content/ra/v2/movies/dud.vqa, ^SupportDir|Content/ra/v2/movies/elevator.vqa, ^SupportDir|Content/ra/v2/movies/flare.vqa, ^SupportDir|Content/ra/v2/movies/frozen.vqa, ^SupportDir|Content/ra/v2/movies/grvestne.vqa, ^SupportDir|Content/ra/v2/movies/landing.vqa, ^SupportDir|Content/ra/v2/movies/masasslt.vqa, ^SupportDir|Content/ra/v2/movies/mcv.vqa, ^SupportDir|Content/ra/v2/movies/mcv_land.vqa, ^SupportDir|Content/ra/v2/movies/montpass.vqa, ^SupportDir|Content/ra/v2/movies/oildrum.vqa, ^SupportDir|Content/ra/v2/movies/overrun.vqa, ^SupportDir|Content/ra/v2/movies/prolog.vqa, ^SupportDir|Content/ra/v2/movies/redintro.vqa, ^SupportDir|Content/ra/v2/movies/shipsink.vqa, ^SupportDir|Content/ra/v2/movies/shorbom1.vqa, ^SupportDir|Content/ra/v2/movies/shorbom2.vqa, ^SupportDir|Content/ra/v2/movies/shorbomb.vqa, ^SupportDir|Content/ra/v2/movies/snowbomb.vqa, ^SupportDir|Content/ra/v2/movies/soviet1.vqa, ^SupportDir|Content/ra/v2/movies/sovtstar.vqa, ^SupportDir|Content/ra/v2/movies/spy.vqa, ^SupportDir|Content/ra/v2/movies/tanya1.vqa, ^SupportDir|Content/ra/v2/movies/tanya2.vqa, ^SupportDir|Content/ra/v2/movies/toofar.vqa, ^SupportDir|Content/ra/v2/movies/trinity.vqa
Sources: allied, tfd, ra-steam, ra-origin, cncr-steam, cncr-origin
ContentPackage@movies-soviet:
Title: Soviet Campaign Briefings
Title: modcontent-package-soviet
Identifier: movies-soviet
TestFiles: ^SupportDir|Content/ra/v2/movies/aagun.vqa, ^SupportDir|Content/ra/v2/movies/cronfail.vqa, ^SupportDir|Content/ra/v2/movies/airfield.vqa, ^SupportDir|Content/ra/v2/movies/ally1.vqa, ^SupportDir|Content/ra/v2/movies/allymorf.vqa, ^SupportDir|Content/ra/v2/movies/averted.vqa, ^SupportDir|Content/ra/v2/movies/beachead.vqa, ^SupportDir|Content/ra/v2/movies/bmap.vqa, ^SupportDir|Content/ra/v2/movies/bombrun.vqa, ^SupportDir|Content/ra/v2/movies/countdwn.vqa, ^SupportDir|Content/ra/v2/movies/double.vqa, ^SupportDir|Content/ra/v2/movies/dpthchrg.vqa, ^SupportDir|Content/ra/v2/movies/execute.vqa, ^SupportDir|Content/ra/v2/movies/flare.vqa, ^SupportDir|Content/ra/v2/movies/landing.vqa, ^SupportDir|Content/ra/v2/movies/mcvbrdge.vqa, ^SupportDir|Content/ra/v2/movies/mig.vqa, ^SupportDir|Content/ra/v2/movies/movingin.vqa, ^SupportDir|Content/ra/v2/movies/mtnkfact.vqa, ^SupportDir|Content/ra/v2/movies/nukestok.vqa, ^SupportDir|Content/ra/v2/movies/onthprwl.vqa, ^SupportDir|Content/ra/v2/movies/periscop.vqa, ^SupportDir|Content/ra/v2/movies/prolog.vqa, ^SupportDir|Content/ra/v2/movies/radrraid.vqa, ^SupportDir|Content/ra/v2/movies/redintro.vqa, ^SupportDir|Content/ra/v2/movies/search.vqa, ^SupportDir|Content/ra/v2/movies/sfrozen.vqa, ^SupportDir|Content/ra/v2/movies/sitduck.vqa, ^SupportDir|Content/ra/v2/movies/slntsrvc.vqa, ^SupportDir|Content/ra/v2/movies/snowbomb.vqa, ^SupportDir|Content/ra/v2/movies/snstrafe.vqa, ^SupportDir|Content/ra/v2/movies/sovbatl.vqa, ^SupportDir|Content/ra/v2/movies/sovcemet.vqa, ^SupportDir|Content/ra/v2/movies/sovfinal.vqa, ^SupportDir|Content/ra/v2/movies/soviet1.vqa, ^SupportDir|Content/ra/v2/movies/soviet10.vqa, ^SupportDir|Content/ra/v2/movies/soviet11.vqa, ^SupportDir|Content/ra/v2/movies/soviet12.vqa, ^SupportDir|Content/ra/v2/movies/soviet13.vqa, ^SupportDir|Content/ra/v2/movies/soviet14.vqa, ^SupportDir|Content/ra/v2/movies/soviet2.vqa, ^SupportDir|Content/ra/v2/movies/soviet3.vqa, ^SupportDir|Content/ra/v2/movies/soviet4.vqa, ^SupportDir|Content/ra/v2/movies/soviet5.vqa, ^SupportDir|Content/ra/v2/movies/soviet6.vqa, ^SupportDir|Content/ra/v2/movies/soviet7.vqa, ^SupportDir|Content/ra/v2/movies/soviet8.vqa, ^SupportDir|Content/ra/v2/movies/soviet9.vqa, ^SupportDir|Content/ra/v2/movies/sovmcv.vqa, ^SupportDir|Content/ra/v2/movies/sovtstar.vqa, ^SupportDir|Content/ra/v2/movies/spotter.vqa, ^SupportDir|Content/ra/v2/movies/strafe.vqa, ^SupportDir|Content/ra/v2/movies/take_off.vqa, ^SupportDir|Content/ra/v2/movies/tesla.vqa, ^SupportDir|Content/ra/v2/movies/v2rocket.vqa
Sources: soviet, tfd, ra-steam, ra-origin
ContentPackage@music-counterstrike:
Title: Counterstrike Music
Title: modcontent-package-counterstrikemusic
Identifier: music-counterstrike
TestFiles: ^SupportDir|Content/ra/v2/expand/araziod.aud, ^SupportDir|Content/ra/v2/expand/backstab.aud, ^SupportDir|Content/ra/v2/expand/chaos2.aud, ^SupportDir|Content/ra/v2/expand/shut_it.aud, ^SupportDir|Content/ra/v2/expand/2nd_hand.aud, ^SupportDir|Content/ra/v2/expand/twinmix1.aud, ^SupportDir|Content/ra/v2/expand/under3.aud, ^SupportDir|Content/ra/v2/expand/vr2.aud,
Sources: counterstrike, ra-steam, ra-origin, cncr-steam, cncr-origin
ContentPackage@music-aftermath:
Title: Aftermath Music
Title: modcontent-package-aftermathmusic
Identifier: music-aftermath
TestFiles: ^SupportDir|Content/ra/v2/expand/await.aud, ^SupportDir|Content/ra/v2/expand/bog.aud, ^SupportDir|Content/ra/v2/expand/float_v2.aud, ^SupportDir|Content/ra/v2/expand/gloom.aud, ^SupportDir|Content/ra/v2/expand/grndwire.aud, ^SupportDir|Content/ra/v2/expand/rpt.aud, ^SupportDir|Content/ra/v2/expand/search.aud, ^SupportDir|Content/ra/v2/expand/traction.aud, ^SupportDir|Content/ra/v2/expand/wastelnd.aud
Sources: aftermath, ra-steam, ra-origin, cncr-steam, cncr-origin
@@ -342,6 +342,7 @@ ModContent:
ra|installer/origin.yaml
ra|installer/soviet95.yaml
ra|installer/steam.yaml
Translation: ra|languages/modcontent/en.ftl
DiscordService:
ApplicationId: 699222659766026240

6
mods/ts/languages/en.ftl Normal file
View File

@@ -0,0 +1,6 @@
## Metadata
mod-title = Tiberian Sun
mod-windowtitle = OpenRA - Tiberian Sun
## LogoStripeLoadScreen
loadscreen-loading = Updating EVA installation..., Changing perspective...

View File

@@ -0,0 +1,12 @@
modcontent-installprompt =
Tiberian Sun requires artwork and audio from the original game.
Quick Install will automatically download this content (without music or videos) from a mirror of the 2012 Tiberian Sun freeware release.
Advanced Install includes options for copying the music, videos, and other content from an original game disc or digital installation.
modcontent-header =
Game content may be extracted from the original game discs or an existing digital install. OpenRA can also download the base game files from an online mirror of the 2012 freeware release of TS.
modcontent-package-basefiles = Base Game Files
modcontent-package-basemusic = Base Game Music
modcontent-package-firestormfiles = Firestorm Expansion Files
modcontent-package-firestormmusic = Firestorm Expansion Music

View File

@@ -1,9 +1,9 @@
Metadata:
Title: Tiberian Sun
Title: mod-title
Version: {DEV_VERSION}
Website: https://www.openra.net
WebIcon32: https://www.openra.net/images/icons/ts_32x32.png
WindowTitle: OpenRA - Tiberian Sun
WindowTitle: mod-windowtitle
PackageFormats: Mix
@@ -187,6 +187,7 @@ Translations:
common|languages/en.ftl
common|languages/chrome/en.ftl
common|languages/rules/en.ftl
ts|languages/en.ftl
ts|languages/chrome/en.ftl
ts|languages/rules/en.ftl
@@ -216,7 +217,6 @@ Hotkeys:
LoadScreen: LogoStripeLoadScreen
Image: ts|uibits/loadscreen.png
Text: Updating EVA installation..., Changing perspective...
ServerTraits:
LobbyCommands
@@ -312,31 +312,31 @@ GameSpeeds:
OrderLatency: 6
ModContent:
InstallPromptMessage: Tiberian Sun requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without music\nor videos) from a mirror of the 2012 Tiberian Sun freeware release.\n\nAdvanced Install includes options for copying the music, videos, and\nother content from an original game disc or digital installation.
InstallPromptMessage: modcontent-installprompt
QuickDownload: quickinstall
HeaderMessage: Game content may be extracted from the original game discs or an\nexisting digital install. OpenRA can also download the base game\nfiles from an online mirror of the 2012 freeware release of TS.
HeaderMessage: modcontent-header
Packages:
ContentPackage@tibsun:
Title: Base Game Files
Title: modcontent-package-basefiles
Identifier: tibsun
TestFiles: ^SupportDir|Content/ts/cache.mix, ^SupportDir|Content/ts/conquer.mix, ^SupportDir|Content/ts/isosnow.mix, ^SupportDir|Content/ts/isotemp.mix, ^SupportDir|Content/ts/local.mix, ^SupportDir|Content/ts/sidec01.mix, ^SupportDir|Content/ts/sidec02.mix, ^SupportDir|Content/ts/sno.mix, ^SupportDir|Content/ts/snow.mix, ^SupportDir|Content/ts/sounds.mix, ^SupportDir|Content/ts/speech01.mix, ^SupportDir|Content/ts/tem.mix, ^SupportDir|Content/ts/temperat.mix
Sources: tibsun, tfd, steam, origin
Required: true
Download: basefiles
ContentPackage@tibsun-music:
Title: Base Game Music
Title: modcontent-package-basemusic
Identifier: tibsun-music
TestFiles: ^SupportDir|Content/ts/scores.mix
Sources: tibsun, tfd, steam, origin
ContentPackage@fstorm:
Title: Firestorm Expansion Files
Title: modcontent-package-firestormfiles
Identifier: fstorm
TestFiles: ^SupportDir|Content/ts/firestorm/e01sc01.mix, ^SupportDir|Content/ts/firestorm/e01sc02.mix, ^SupportDir|Content/ts/firestorm/e01vox01.mix, ^SupportDir|Content/ts/firestorm/e01vox02.mix
Sources: tfd, steam, origin, fstorm
Required: true
Download: fstorm
ContentPackage@fstorm-music:
Title: Firestorm Expansion Music
Title: modcontent-package-firestormmusic
Identifier: fstorm-music
Sources: tfd, steam, origin, fstorm
TestFiles: ^SupportDir|Content/ts/firestorm/scores01.mix
@@ -348,6 +348,7 @@ ModContent:
ts|installer/steam.yaml
ts|installer/origin.yaml
ts|installer/tibsun.yaml
Translation: ts|languages/modcontent/en.ftl
DiscordService:
ApplicationId: 712713986558394399