Merge pull request #10232 from penev92/modSupport

Implement mod loading from the support directory
This commit is contained in:
Matthias Mailänder
2016-01-11 12:37:35 +01:00
12 changed files with 139 additions and 92 deletions

View File

@@ -191,7 +191,7 @@ namespace OpenRA
public static void InitializeSettings(Arguments args)
{
Settings = new Settings(Platform.ResolvePath("^", "settings.yaml"), args);
Settings = new Settings(Platform.ResolvePath(Path.Combine("^", "settings.yaml")), args);
}
internal static void Initialize(Arguments args)

View File

@@ -29,7 +29,7 @@ namespace OpenRA
}
}
// Describes what is to be loaded in order to run a mod
/// <summary> Describes what is to be loaded in order to run a mod. </summary>
public class Manifest
{
public static readonly Dictionary<string, Manifest> AllMods = LoadMods();
@@ -61,13 +61,16 @@ namespace OpenRA
readonly TypeDictionary modules = new TypeDictionary();
readonly Dictionary<string, MiniYaml> yaml;
public Manifest(string mod)
public Manifest(string modId, string modPath = null)
{
var path = Platform.ResolvePath(".", "mods", mod, "mod.yaml");
if (modPath == null)
modPath = ModMetadata.CandidateModPaths[modId];
var path = Path.Combine(modPath, "mod.yaml");
yaml = new MiniYaml(null, MiniYaml.FromFile(path)).ToDictionary();
Mod = FieldLoader.Load<ModMetadata>(yaml["Metadata"]);
Mod.Id = mod;
Mod.Id = modId;
// TODO: Use fieldloader
Folders = YamlList(yaml, "Folders", true);
@@ -106,12 +109,10 @@ namespace OpenRA
RequiresMods = yaml["RequiresMods"].ToDictionary(my => my.Value);
// Allow inherited mods to import parent maps.
var compat = new List<string>();
compat.Add(mod);
var compat = new List<string> { Mod.Id };
if (yaml.ContainsKey("SupportsMapsFrom"))
foreach (var c in yaml["SupportsMapsFrom"].Value.Split(','))
compat.Add(c.Trim());
compat.AddRange(yaml["SupportsMapsFrom"].Value.Split(',').Select(c => c.Trim()));
MapCompatibility = compat.ToArray();
@@ -156,8 +157,10 @@ namespace OpenRA
if (!yaml.ContainsKey(key))
return new string[] { };
var list = yaml[key].ToDictionary().Keys.ToArray();
return parsePaths ? list.Select(Platform.ResolvePath).ToArray() : list;
if (parsePaths)
return yaml[key].Nodes.Select(node => Platform.ResolvePath(node.Key, node.Value.Value ?? string.Empty)).ToArray();
return yaml[key].ToDictionary().Keys.ToArray();
}
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key, bool parsePaths = false)
@@ -168,15 +171,19 @@ namespace OpenRA
var inner = new Dictionary<string, string>();
foreach (var node in yaml[key].Nodes)
{
var line = node.Key;
if (node.Value.Value != null)
line += ":" + node.Value.Value;
// '@' may be used in mod.yaml to indicate extra information (similar to trait @ tags).
// Applies to MapFolders (to indicate System and User directories) and Packages (to indicate package annotation).
if (node.Key.Contains('@'))
if (line.Contains('@'))
{
var split = node.Key.Split('@');
inner.Add(split[0], split[1]);
var split = line.Split('@');
inner.Add(parsePaths ? Platform.ResolvePath(split[0]) : split[0], split[1]);
}
else
inner.Add(node.Key, null);
inner.Add(line, null);
}
return new ReadOnlyDictionary<string, string>(inner);
@@ -198,20 +205,16 @@ namespace OpenRA
static Dictionary<string, Manifest> LoadMods()
{
var basePath = Platform.ResolvePath(".", "mods");
var mods = Directory.GetDirectories(basePath)
.Select(x => x.Substring(basePath.Length + 1));
var ret = new Dictionary<string, Manifest>();
foreach (var mod in mods)
foreach (var mod in ModMetadata.CandidateModPaths)
{
if (!File.Exists(Platform.ResolvePath(".", "mods", mod, "mod.yaml")))
if (!File.Exists(Path.Combine(mod.Value, "mod.yaml")))
continue;
try
{
var manifest = new Manifest(mod);
ret.Add(mod, manifest);
var manifest = new Manifest(mod.Key, mod.Value);
ret.Add(mod.Key, manifest);
}
catch (Exception ex)
{

View File

@@ -17,6 +17,7 @@ namespace OpenRA
{
public class ModMetadata
{
public static readonly Dictionary<string, string> CandidateModPaths = GetCandidateMods();
public static readonly Dictionary<string, ModMetadata> AllMods = ValidateMods();
public string Id;
@@ -24,21 +25,19 @@ namespace OpenRA
public string Description;
public string Version;
public string Author;
public string LogoImagePath;
public string PreviewImagePath;
public bool Hidden;
public ContentInstaller Content;
static Dictionary<string, ModMetadata> ValidateMods()
{
var basePath = Platform.ResolvePath(".", "mods");
var mods = Directory.GetDirectories(basePath)
.Select(x => x.Substring(basePath.Length + 1));
var ret = new Dictionary<string, ModMetadata>();
foreach (var m in mods)
foreach (var pair in CandidateModPaths)
{
try
{
var yamlPath = Platform.ResolvePath(".", "mods", m, "mod.yaml");
var yamlPath = Path.Combine(pair.Value, "mod.yaml");
if (!File.Exists(yamlPath))
continue;
@@ -47,22 +46,40 @@ namespace OpenRA
if (!nd.ContainsKey("Metadata"))
continue;
var mod = FieldLoader.Load<ModMetadata>(nd["Metadata"]);
mod.Id = m;
var metadata = FieldLoader.Load<ModMetadata>(nd["Metadata"]);
metadata.Id = pair.Key;
if (nd.ContainsKey("ContentInstaller"))
mod.Content = FieldLoader.Load<ContentInstaller>(nd["ContentInstaller"]);
metadata.Content = FieldLoader.Load<ContentInstaller>(nd["ContentInstaller"]);
ret.Add(m, mod);
ret.Add(pair.Key, metadata);
}
catch (Exception ex)
{
Console.WriteLine("An exception occurred when trying to load ModMetadata for `{0}`:".F(m));
Console.WriteLine("An exception occurred when trying to load ModMetadata for `{0}`:".F(pair.Key));
Console.WriteLine(ex.Message);
}
}
return ret;
}
static Dictionary<string, string> GetCandidateMods()
{
// Get mods that are in the game folder.
var basePath = Platform.ResolvePath(Path.Combine(".", "mods"));
var mods = Directory.GetDirectories(basePath)
.ToDictionary(x => x.Substring(basePath.Length + 1));
// Get mods that are in the support folder.
var supportPath = Platform.ResolvePath(Path.Combine("^", "mods"));
if (!Directory.Exists(supportPath))
return mods;
foreach (var pair in Directory.GetDirectories(supportPath).ToDictionary(x => x.Substring(supportPath.Length + 1)))
mods.Add(pair.Key, pair.Value);
return mods;
}
}
}

View File

@@ -95,11 +95,19 @@ namespace OpenRA
public static string GameDir { get { return AppDomain.CurrentDomain.BaseDirectory; } }
/// <summary>Replace special character prefixes with full paths</summary>
/// <summary>Replaces special character prefixes with full paths.</summary>
public static string ResolvePath(string path)
{
path = path.TrimEnd(new char[] { ' ', '\t' });
// If the path contains ':', chances are it is a package path.
// If it isn't, someone passed an already resolved path, which is wrong.
if (path.IndexOf(":", StringComparison.Ordinal) > 1)
{
var split = path.Split(':');
return ResolvePath(split[0], split[1]);
}
// paths starting with ^ are relative to the support dir
if (path.StartsWith("^"))
path = SupportDir + path.Substring(1);
@@ -111,7 +119,17 @@ namespace OpenRA
return path;
}
/// <summary>Replace special character prefixes with full paths</summary>
/// <summary>Replaces package names with full paths. Avoid using this for non-package paths.</summary>
public static string ResolvePath(string package, string target)
{
// Resolve mod package paths.
if (ModMetadata.AllMods.ContainsKey(package))
package = ModMetadata.CandidateModPaths[package];
return ResolvePath(Path.Combine(package, target));
}
/// <summary>Replace special character prefixes with full paths.</summary>
public static string ResolvePath(params string[] path)
{
return ResolvePath(path.Aggregate(Path.Combine));

View File

@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using OpenRA.Graphics;
@@ -49,7 +50,7 @@ namespace OpenRA
var resolution = GetResolution(graphicSettings);
var rendererName = serverSettings.Dedicated ? "Null" : graphicSettings.Renderer;
var rendererPath = Platform.ResolvePath(".", "OpenRA.Platforms." + rendererName + ".dll");
var rendererPath = Platform.ResolvePath(Path.Combine(".", "OpenRA.Platforms." + rendererName + ".dll"));
Device = CreateDevice(Assembly.LoadFile(rendererPath), resolution.Width, resolution.Height, graphicSettings.Mode);

View File

@@ -33,7 +33,7 @@ namespace OpenRA
public Sound(string engineName)
{
var enginePath = Platform.ResolvePath(".", "OpenRA.Platforms." + engineName + ".dll");
var enginePath = Platform.ResolvePath(Path.Combine(".", "OpenRA.Platforms." + engineName + ".dll"));
soundEngine = CreateDevice(Assembly.LoadFile(enginePath));
}

View File

@@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
try
{
using (var preview = new Bitmap(Platform.ResolvePath(".", "mods", mod.Id, "preview.png")))
using (var preview = new Bitmap(Platform.ResolvePath(mod.PreviewImagePath)))
if (preview.Width == 296 && preview.Height == 196)
previews.Add(mod.Id, sheetBuilder.Add(preview));
}
@@ -90,7 +90,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
try
{
using (var logo = new Bitmap(Platform.ResolvePath(".", "mods", mod.Id, "logo.png")))
using (var logo = new Bitmap(Platform.ResolvePath(mod.LogoImagePath)))
if (logo.Width == 96 && logo.Height == 96)
logos.Add(mod.Id, sheetBuilder.Add(logo));
}

View File

@@ -17,7 +17,7 @@ Chrome:
Assemblies:
./mods/common/OpenRA.Mods.Common.dll
./mods/ra/OpenRA.Mods.RA.dll
./mods/d2k/OpenRA.Mods.D2k.dll
d2k:OpenRA.Mods.D2k.dll
./mods/cnc/OpenRA.Mods.Cnc.dll
./mods/ts/OpenRA.Mods.TS.dll

View File

@@ -3,6 +3,8 @@ Metadata:
Description: Join the Global Defense Initiative or the Brotherhood of Nod in our\nrecreation of the classic game that started it all.\n\nTiberian Dawn modernizes the original Command & Conquer gameplay\nby introducing features from later games, including per-factory\nproduction queues, unit veterancy, and capturable tech structures.
Version: {DEV_VERSION}
Author: the OpenRA Developers
LogoImagePath: ./mods/cnc/logo.png
PreviewImagePath: ./mods/cnc/preview.png
RequiresMods:
modchooser: {DEV_VERSION}

View File

@@ -3,96 +3,98 @@ Metadata:
Description: Three great houses fight for the precious spice, melange.\nHe who controls the spice controls the universe!\n\nTry to establish a foothold on the desert planet Arrakis\nwith its harsh environmental conditions and protect your\nharvesting operations from giant sandworms as well as\nruthless enemy factions.
Version: {DEV_VERSION}
Author: the OpenRA Developers
LogoImagePath: d2k:logo.png
PreviewImagePath: d2k:preview.png
RequiresMods:
modchooser: {DEV_VERSION}
Folders:
.
./mods/d2k
./mods/d2k/bits
./mods/d2k/bits/tex
./mods/d2k/bits/xmas
./mods/d2k/uibits
d2k:
d2k:bits
d2k:bits/tex
d2k:bits/xmas
d2k:uibits
~^Content/d2k
~^Content/d2k/GAMESFX
~^Content/d2k/Movies
~^Content/d2k/Music
MapFolders:
./mods/d2k/maps@System
d2k:maps@System
~^maps/d2k@User
Packages:
SOUND.RS
Rules:
./mods/d2k/rules/misc.yaml
./mods/d2k/rules/ai.yaml
./mods/d2k/rules/player.yaml
./mods/d2k/rules/world.yaml
./mods/d2k/rules/palettes.yaml
./mods/d2k/rules/defaults.yaml
./mods/d2k/rules/vehicles.yaml
./mods/d2k/rules/starport.yaml
./mods/d2k/rules/husks.yaml
./mods/d2k/rules/structures.yaml
./mods/d2k/rules/aircraft.yaml
./mods/d2k/rules/infantry.yaml
./mods/d2k/rules/arrakis.yaml
d2k:rules/misc.yaml
d2k:rules/ai.yaml
d2k:rules/player.yaml
d2k:rules/world.yaml
d2k:rules/palettes.yaml
d2k:rules/defaults.yaml
d2k:rules/vehicles.yaml
d2k:rules/starport.yaml
d2k:rules/husks.yaml
d2k:rules/structures.yaml
d2k:rules/aircraft.yaml
d2k:rules/infantry.yaml
d2k:rules/arrakis.yaml
Sequences:
./mods/d2k/sequences/aircraft.yaml
./mods/d2k/sequences/vehicles.yaml
./mods/d2k/sequences/infantry.yaml
./mods/d2k/sequences/structures.yaml
./mods/d2k/sequences/misc.yaml
d2k:sequences/aircraft.yaml
d2k:sequences/vehicles.yaml
d2k:sequences/infantry.yaml
d2k:sequences/structures.yaml
d2k:sequences/misc.yaml
TileSets:
./mods/d2k/tilesets/arrakis.yaml
d2k:tilesets/arrakis.yaml
MapGrid:
TileSize: 32,32
Type: Rectangular
Cursors:
./mods/d2k/cursors.yaml
d2k:cursors.yaml
Chrome:
./mods/d2k/chrome.yaml
d2k:chrome.yaml
Assemblies:
./mods/common/OpenRA.Mods.Common.dll
./mods/cnc/OpenRA.Mods.Cnc.dll
./mods/d2k/OpenRA.Mods.D2k.dll
d2k:OpenRA.Mods.D2k.dll
ChromeLayout:
./mods/d2k/chrome/ingame.yaml
d2k:chrome/ingame.yaml
./mods/ra/chrome/ingame-chat.yaml
./mods/ra/chrome/ingame-diplomacy.yaml
./mods/ra/chrome/ingame-fmvplayer.yaml
./mods/d2k/chrome/ingame-menu.yaml
d2k:chrome/ingame-menu.yaml
./mods/ra/chrome/ingame-info.yaml
./mods/ra/chrome/ingame-infoscripterror.yaml
./mods/ra/chrome/ingame-infobriefing.yaml
./mods/ra/chrome/ingame-infoobjectives.yaml
./mods/d2k/chrome/ingame-infostats.yaml
./mods/d2k/chrome/ingame-observer.yaml
d2k:chrome/ingame-infostats.yaml
d2k:chrome/ingame-observer.yaml
./mods/ra/chrome/ingame-observerstats.yaml
./mods/d2k/chrome/ingame-player.yaml
d2k:chrome/ingame-player.yaml
./mods/ra/chrome/ingame-perf.yaml
./mods/ra/chrome/ingame-debug.yaml
./mods/d2k/chrome/mainmenu.yaml
d2k:chrome/mainmenu.yaml
./mods/ra/chrome/settings.yaml
./mods/ra/chrome/credits.yaml
./mods/ra/chrome/lobby.yaml
./mods/ra/chrome/lobby-mappreview.yaml
./mods/d2k/chrome/lobby-players.yaml
./mods/d2k/chrome/lobby-options.yaml
d2k:chrome/lobby-players.yaml
d2k:chrome/lobby-options.yaml
./mods/ra/chrome/lobby-music.yaml
./mods/ra/chrome/lobby-kickdialogs.yaml
./mods/ra/chrome/lobby-globalchat.yaml
./mods/d2k/chrome/color-picker.yaml
d2k:chrome/color-picker.yaml
./mods/ra/chrome/map-chooser.yaml
./mods/ra/chrome/multiplayer.yaml
./mods/ra/chrome/multiplayer-browser.yaml
@@ -100,33 +102,33 @@ ChromeLayout:
./mods/ra/chrome/multiplayer-directconnect.yaml
./mods/ra/chrome/multiplayer-globalchat.yaml
./mods/ra/chrome/connection.yaml
./mods/d2k/chrome/dropdowns.yaml
d2k:chrome/dropdowns.yaml
./mods/ra/chrome/musicplayer.yaml
./mods/d2k/chrome/tooltips.yaml
d2k:chrome/tooltips.yaml
./mods/ra/chrome/assetbrowser.yaml
./mods/d2k/chrome/missionbrowser.yaml
d2k:chrome/missionbrowser.yaml
./mods/ra/chrome/confirmation-dialogs.yaml
./mods/ra/chrome/editor.yaml
./mods/ra/chrome/replaybrowser.yaml
Weapons:
./mods/d2k/weapons.yaml
./mods/d2k/weapons/debris.yaml
d2k:weapons.yaml
d2k:weapons/debris.yaml
Voices:
./mods/d2k/audio/voices.yaml
d2k:audio/voices.yaml
Notifications:
./mods/d2k/audio/notifications.yaml
d2k:audio/notifications.yaml
Music:
./mods/d2k/audio/music.yaml
d2k:audio/music.yaml
Translations:
./mods/d2k/languages/english.yaml
d2k:languages/english.yaml
LoadScreen: LogoStripeLoadScreen
Image: ./mods/d2k/uibits/loadscreen.png
Image: d2k:uibits/loadscreen.png
Text: Filling Crates..., Breeding Sandworms..., Fuelling carryalls..., Deploying harvesters..., Preparing 'thopters..., Summoning mentats...
ContentInstaller:
@@ -161,7 +163,7 @@ LobbyDefaults:
TechLevel: Unrestricted
ChromeMetrics:
./mods/d2k/metrics.yaml
d2k:metrics.yaml
Fonts:
Regular:
@@ -171,7 +173,7 @@ Fonts:
Font:./mods/common/FreeSansBold.ttf
Size:14
Title:
Font:./mods/d2k/Dune2k.ttf
Font:d2k:Dune2k.ttf
Size:32
MediumBold:
Font:./mods/common/FreeSansBold.ttf
@@ -190,7 +192,7 @@ Fonts:
Size:10
Missions:
./mods/d2k/missions.yaml
d2k:missions.yaml
SupportsMapsFrom: d2k

View File

@@ -3,6 +3,8 @@ Metadata:
Description: In a world where Hitler was assassinated and the Third Reich never\nexisted, the Soviet Union seeks power over all of Europe. Allied\nagainst this Evil Empire, the free world faces a Cold War turned hot.\n\nRed Alert fuses the quick and fun gameplay of the original\nC&C: Red Alert, with balance improvements and new gameplay\nfeatures inspired by modern RTS games.
Version: {DEV_VERSION}
Author: the OpenRA Developers
LogoImagePath: ./mods/ra/logo.png
PreviewImagePath: ./mods/ra/preview.png
RequiresMods:
modchooser: {DEV_VERSION}

View File

@@ -3,6 +3,8 @@ Metadata:
Description: Developer stub, not yet ready for release!
Version: {DEV_VERSION}
Author: the OpenRA Developers
LogoImagePath: ./mods/ts/logo.png
PreviewImagePath: ./mods/ts/preview.png
RequiresMods:
modchooser: {DEV_VERSION}
@@ -142,7 +144,7 @@ ChromeLayout:
./mods/ra/chrome/ingame-infobriefing.yaml
./mods/ra/chrome/ingame-infoobjectives.yaml
./mods/ra/chrome/ingame-infostats.yaml
./mods/d2k/chrome/ingame-observer.yaml
d2k:chrome/ingame-observer.yaml
./mods/ts/chrome/ingame-observerstats.yaml
./mods/ts/chrome/ingame-player.yaml
./mods/ra/chrome/ingame-perf.yaml