#region Copyright & License Information /* * Copyright (c) The OpenRA Developers and Contributors * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using OpenRA.FileSystem; using OpenRA.Graphics; using OpenRA.Video; using OpenRA.Widgets; using FS = OpenRA.FileSystem.FileSystem; namespace OpenRA { public sealed class ModData : IDisposable { public readonly Manifest Manifest; public readonly ObjectCreator ObjectCreator; public readonly WidgetLoader WidgetLoader; public readonly MapCache MapCache; public readonly IPackageLoader[] PackageLoaders; public readonly ISoundLoader[] SoundLoaders; public readonly ISpriteLoader[] SpriteLoaders; public readonly ITerrainLoader TerrainLoader; public readonly ISpriteSequenceLoader SpriteSequenceLoader; public readonly IVideoLoader[] VideoLoaders; public readonly HotkeyManager Hotkeys; public ILoadScreen LoadScreen { get; } public CursorProvider CursorProvider { get; private set; } public FS ModFiles; public IReadOnlyFileSystem DefaultFileSystem => ModFiles; readonly Lazy defaultRules; public Ruleset DefaultRules => defaultRules.Value; readonly Lazy> defaultTerrainInfo; public IReadOnlyDictionary DefaultTerrainInfo => defaultTerrainInfo.Value; public ModData(Manifest mod, InstalledMods mods, bool useLoadScreen = false) { Languages = Array.Empty(); // Take a local copy of the manifest Manifest = new Manifest(mod.Id, mod.Package); ObjectCreator = new ObjectCreator(Manifest, mods); PackageLoaders = ObjectCreator.GetLoaders(Manifest.PackageFormats, "package"); ModFiles = new FS(mod.Id, mods, PackageLoaders); ModFiles.LoadFromManifest(Manifest); Manifest.LoadCustomData(ObjectCreator); if (useLoadScreen) { LoadScreen = ObjectCreator.CreateObject(Manifest.LoadScreen.Value); LoadScreen.Init(this, Manifest.LoadScreen.ToDictionary(my => my.Value)); LoadScreen.Display(); } WidgetLoader = new WidgetLoader(this); MapCache = new MapCache(this); SoundLoaders = ObjectCreator.GetLoaders(Manifest.SoundFormats, "sound"); SpriteLoaders = ObjectCreator.GetLoaders(Manifest.SpriteFormats, "sprite"); VideoLoaders = ObjectCreator.GetLoaders(Manifest.VideoFormats, "video"); var terrainFormat = Manifest.Get(); var terrainLoader = ObjectCreator.FindType(terrainFormat.Type + "Loader"); var terrainCtor = terrainLoader?.GetConstructor(new[] { typeof(ModData) }); if (terrainLoader == null || !terrainLoader.GetInterfaces().Contains(typeof(ITerrainLoader)) || terrainCtor == null) throw new InvalidOperationException($"Unable to find a terrain loader for type '{terrainFormat.Type}'."); TerrainLoader = (ITerrainLoader)terrainCtor.Invoke(new[] { this }); var sequenceFormat = Manifest.Get(); var sequenceLoader = ObjectCreator.FindType(sequenceFormat.Type + "Loader"); var sequenceCtor = sequenceLoader?.GetConstructor(new[] { typeof(ModData) }); if (sequenceLoader == null || !sequenceLoader.GetInterfaces().Contains(typeof(ISpriteSequenceLoader)) || sequenceCtor == null) throw new InvalidOperationException($"Unable to find a sequence loader for type '{sequenceFormat.Type}'."); SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this }); Hotkeys = new HotkeyManager(ModFiles, Game.Settings.Keys, Manifest); defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this)); defaultTerrainInfo = Exts.Lazy(() => { var items = new Dictionary(); foreach (var file in Manifest.TileSets) { var t = TerrainLoader.ParseTerrain(DefaultFileSystem, file); items.Add(t.Id, t); } return (IReadOnlyDictionary)new ReadOnlyDictionary(items); }); initialThreadId = Environment.CurrentManagedThreadId; } // HACK: Only update the loading screen if we're in the main thread. readonly int initialThreadId; internal void HandleLoadingProgress() { if (LoadScreen != null && IsOnMainThread) LoadScreen.Display(); } internal bool IsOnMainThread => Environment.CurrentManagedThreadId == initialThreadId; public void InitializeLoaders(IReadOnlyFileSystem fileSystem) { // all this manipulation of static crap here is nasty and breaks // horribly when you use ModData in unexpected ways. ChromeMetrics.Initialize(this); ChromeProvider.Initialize(this); TranslationProvider.Initialize(this, fileSystem); Game.Sound.Initialize(SoundLoaders, fileSystem); CursorProvider = new CursorProvider(this); } public IEnumerable Languages { get; } public Map PrepareMap(string uid) { LoadScreen?.Display(); if (MapCache[uid].Status != MapStatus.Available) throw new InvalidDataException($"Invalid map uid: {uid}"); Map map; using (new Support.PerfTimer("Map")) map = new Map(this, MapCache[uid].Package); // Reinitialize all our assets InitializeLoaders(map); map.Sequences.LoadSprites(); // Load music with map assets mounted using (new Support.PerfTimer("Map.Music")) foreach (var entry in map.Rules.Music) entry.Value.Load(map); return map; } public List[] GetRulesYaml() { return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s)).ToArray(); } public void Dispose() { LoadScreen?.Dispose(); MapCache.Dispose(); ObjectCreator?.Dispose(); Manifest.Dispose(); } } public interface ILoadScreen : IDisposable { /// Initializes the loadscreen with yaml data from the LoadScreen block in mod.yaml. void Init(ModData m, Dictionary info); /// Called at arbitrary times during mod load to rerender the loadscreen. void Display(); /// /// Called before loading the mod assets. /// Returns false if mod loading should be aborted (e.g. switching to another mod instead). /// bool BeforeLoad(); /// Called when the engine expects to connect to a server/replay or load the shellmap. void StartGame(Arguments args); } }