Merge pull request #12638 from pchote/engineargs

Add engine plumbing to support externally developed mods.
This commit is contained in:
Taryn Hill
2017-02-16 20:58:41 -06:00
committed by GitHub
9 changed files with 75 additions and 50 deletions

View File

@@ -249,10 +249,10 @@ namespace OpenRA
// Special case handling of Game.Mod argument: if it matches a real filesystem path
// then we use this to override the mod search path, and replace it with the mod id
var modArgument = args.GetValue("Game.Mod", null);
string customModPath = null;
var explicitModPaths = new string[0];
if (modArgument != null && (File.Exists(modArgument) || Directory.Exists(modArgument)))
{
customModPath = modArgument;
explicitModPaths = new[] { modArgument };
args.ReplaceValue("Game.Mod", Path.GetFileNameWithoutExtension(modArgument));
}
@@ -314,7 +314,12 @@ namespace OpenRA
GlobalChat = new GlobalChat();
Mods = new InstalledMods(customModPath);
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
var modSearchPaths = modSearchArg != null ?
FieldLoader.GetValue<string[]>("Engine.ModsPath", modSearchArg) :
new[] { Path.Combine(".", "mods"), Path.Combine("^", "mods") };
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
Console.WriteLine("Internal mods:");
foreach (var mod in Mods)
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version);
@@ -368,8 +373,8 @@ namespace OpenRA
ModData = null;
// Fall back to default if the mod doesn't exist or has missing prerequisites.
if (!IsModInstalled(mod))
mod = new GameSettings().Mod;
if (mod == null || !IsModInstalled(mod))
mod = args.GetValue("Engine.DefaultMod", "modchooser");
Console.WriteLine("Loading mod: {0}", mod);
Settings.Game.Mod = mod;
@@ -379,16 +384,12 @@ namespace OpenRA
ModData = new ModData(Mods[mod], Mods, true);
ExternalMods.Register(ModData.Manifest);
if (!ModData.LoadScreen.BeforeLoad())
return;
using (new PerfTimer("LoadMaps"))
ModData.MapCache.LoadMaps();
// Mod assets are missing!
if (!ModData.LoadScreen.RequiredContentIsInstalled())
{
InitializeMod("modchooser", new Arguments());
return;
}
ModData.InitializeLoaders(ModData.DefaultFileSystem);
Renderer.InitializeFonts(ModData);

View File

@@ -29,34 +29,35 @@ namespace OpenRA
readonly Dictionary<string, Sprite> icons = new Dictionary<string, Sprite>();
public readonly IReadOnlyDictionary<string, Sprite> Icons;
public InstalledMods(string customModPath)
/// <summary>Initializes the collection of locally installed mods.</summary>
/// <param name="searchPaths">Filesystem paths to search for mod packages.</param>
/// <param name="explicitPaths">Filesystem paths to additional mod packages.</param>
public InstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
{
sheetBuilder = new SheetBuilder(SheetType.BGRA, 256);
Icons = new ReadOnlyDictionary<string, Sprite>(icons);
mods = GetInstalledMods(customModPath);
mods = GetInstalledMods(searchPaths, explicitPaths);
}
static IEnumerable<Pair<string, string>> GetCandidateMods()
static IEnumerable<Pair<string, string>> GetCandidateMods(IEnumerable<string> searchPaths)
{
// Get mods that are in the game folder.
var basePath = Platform.ResolvePath(Path.Combine(".", "mods"));
var mods = Directory.GetDirectories(basePath)
.Select(x => Pair.New(x.Substring(basePath.Length + 1), x))
.ToList();
var mods = new List<Pair<string, string>>();
foreach (var path in searchPaths)
{
try
{
var directory = new DirectoryInfo(Platform.ResolvePath(path));
foreach (var subdir in directory.EnumerateDirectories())
mods.Add(Pair.New(subdir.Name, subdir.FullName));
foreach (var m in Directory.GetFiles(basePath, "*.oramod"))
mods.Add(Pair.New(Path.GetFileNameWithoutExtension(m), m));
// 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.New(pair.Key, pair.Value));
foreach (var m in Directory.GetFiles(supportPath, "*.oramod"))
mods.Add(Pair.New(Path.GetFileNameWithoutExtension(m), m));
foreach (var file in directory.EnumerateFiles("*.oramod"))
mods.Add(Pair.New(Path.GetFileNameWithoutExtension(file.Name), file.FullName));
}
catch (Exception e)
{
Console.WriteLine("Failed to enumerate mod search path {0}: {1}", path, e.Message);
}
}
return mods;
}
@@ -102,12 +103,11 @@ namespace OpenRA
}
}
Dictionary<string, Manifest> GetInstalledMods(string customModPath)
Dictionary<string, Manifest> GetInstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
{
var ret = new Dictionary<string, Manifest>();
var candidates = GetCandidateMods();
if (customModPath != null)
candidates = candidates.Append(Pair.New(Path.GetFileNameWithoutExtension(customModPath), customModPath));
var candidates = GetCandidateMods(searchPaths)
.Concat(explicitPaths.Select(p => Pair.New(Path.GetFileNameWithoutExtension(p), p)));
foreach (var pair in candidates)
{

View File

@@ -222,9 +222,19 @@ namespace OpenRA
public interface ILoadScreen : IDisposable
{
/// <summary>Initializes the loadscreen with yaml data from the LoadScreen block in mod.yaml.</summary>
void Init(ModData m, Dictionary<string, string> info);
/// <summary>Called at arbitrary times during mod load to rerender the loadscreen.</summary>
void Display();
bool RequiredContentIsInstalled();
/// <summary>
/// Called before loading the mod assets.
/// Returns false if mod loading should be aborted (e.g. switching to another mod instead).
/// </summary>
bool BeforeLoad();
/// <summary>Called when the engine expects to connect to a server/replay or load the shellmap.</summary>
void StartGame(Arguments args);
}
}

View File

@@ -153,8 +153,8 @@ namespace OpenRA
public class GameSettings
{
[Desc("Load a specific mod on startup. Shipped ones include: ra, cnc and d2k")]
public string Mod = "modchooser";
[Desc("Load a specific mod on startup.")]
public string Mod = null;
public string PreviousMod = "ra";
public string Platform = "Default";

View File

@@ -109,12 +109,23 @@ namespace OpenRA.Mods.Common.LoadScreens
GC.SuppressFinalize(this);
}
public bool RequiredContentIsInstalled()
public bool BeforeLoad()
{
// If a ModContent section is defined then we need to make sure that the
// required content is installed or switch to the defined content installer.
if (!modData.Manifest.Contains<ModContent>())
return true;
var content = modData.Manifest.Get<ModContent>();
return content.Packages
var contentInstalled = content.Packages
.Where(p => p.Value.Required)
.All(p => p.Value.TestFiles.All(f => File.Exists(Platform.ResolvePath(f))));
if (contentInstalled)
return true;
Game.InitializeMod(content.ContentInstallerMod, new Arguments());
return false;
}
}
}

View File

@@ -58,7 +58,7 @@ namespace OpenRA.Mods.Common.LoadScreens
sprite.Sheet.Dispose();
}
public bool RequiredContentIsInstalled()
public bool BeforeLoad()
{
return true;
}

View File

@@ -85,6 +85,7 @@ namespace OpenRA
public readonly string InstallPromptMessage;
public readonly string QuickDownload;
public readonly string HeaderMessage;
public readonly string ContentInstallerMod = "modchooser";
[FieldLoader.LoadUsing("LoadPackages")]
public readonly Dictionary<string, ModPackage> Packages = new Dictionary<string, ModPackage>();

View File

@@ -30,10 +30,10 @@ namespace OpenRA.Server
// Special case handling of Game.Mod argument: if it matches a real filesystem path
// then we use this to override the mod search path, and replace it with the mod id
var modArgument = arguments.GetValue("Game.Mod", null);
string customModPath = null;
var explicitModPaths = new string[0];
if (modArgument != null && (File.Exists(modArgument) || Directory.Exists(modArgument)))
{
customModPath = modArgument;
explicitModPaths = new[] { modArgument };
arguments.ReplaceValue("Game.Mod", Path.GetFileNameWithoutExtension(modArgument));
}
@@ -43,7 +43,8 @@ namespace OpenRA.Server
var settings = Game.Settings.Server;
var mod = Game.Settings.Game.Mod;
var mods = new InstalledMods(customModPath);
var modSearchPaths = new[] { Path.Combine(".", "mods"), Path.Combine("^", "mods") };
var mods = new InstalledMods(modSearchPaths, explicitModPaths);
// HACK: The engine code *still* assumes that Game.ModData is set
var modData = Game.ModData = new ModData(mods[mod], mods);

View File

@@ -47,19 +47,20 @@ namespace OpenRA
if (args.Length == 0)
{
PrintUsage(new InstalledMods(null), null);
PrintUsage(new InstalledMods(new string[0], new string[0]), null);
return;
}
var modId = args[0];
string customModPath = null;
var explicitModPaths = new string[0];
if (File.Exists(modId) || Directory.Exists(modId))
{
customModPath = modId;
explicitModPaths = new[] { modId };
modId = Path.GetFileNameWithoutExtension(modId);
}
var mods = new InstalledMods(customModPath);
var modSearchPaths = new[] { Path.Combine(".", "mods"), Path.Combine("^", "mods") };
var mods = new InstalledMods(modSearchPaths, explicitModPaths);
if (!mods.Keys.Contains(modId))
{
PrintUsage(mods, null);