diff --git a/OpenRA.Game/GameRules/Ruleset.cs b/OpenRA.Game/GameRules/Ruleset.cs index 0f7e299076..ff1be28daf 100644 --- a/OpenRA.Game/GameRules/Ruleset.cs +++ b/OpenRA.Game/GameRules/Ruleset.cs @@ -148,7 +148,9 @@ namespace OpenRA return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences); } - public static Ruleset LoadFromMap(ModData modData, Map map) + public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet, + MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications, + MiniYaml mapMusic, MiniYaml mapSequences) { var m = modData.Manifest; var dr = modData.DefaultRules; @@ -156,27 +158,27 @@ namespace OpenRA Ruleset ruleset = null; Action f = () => { - var actors = MergeOrDefault("Manifest,Rules", map, m.Rules, map.RuleDefinitions, dr.Actors, + var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors, k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value)); - var weapons = MergeOrDefault("Manifest,Weapons", map, m.Weapons, map.WeaponDefinitions, dr.Weapons, + var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons, k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value)); - var voices = MergeOrDefault("Manifest,Voices", map, m.Voices, map.VoiceDefinitions, dr.Voices, + var voices = MergeOrDefault("Voices", fileSystem, m.Voices, mapVoices, dr.Voices, k => new SoundInfo(k.Value)); - var notifications = MergeOrDefault("Manifest,Notifications", map, m.Notifications, map.NotificationDefinitions, dr.Notifications, + var notifications = MergeOrDefault("Notifications", fileSystem, m.Notifications, mapNotifications, dr.Notifications, k => new SoundInfo(k.Value)); - var music = MergeOrDefault("Manifest,Music", map, m.Music, map.NotificationDefinitions, dr.Music, + var music = MergeOrDefault("Music", fileSystem, m.Music, mapMusic, dr.Music, k => new MusicInfo(k.Key, k.Value)); // TODO: Add support for merging custom tileset modifications - var ts = modData.DefaultTileSets[map.Tileset]; + var ts = modData.DefaultTileSets[tileSet]; // TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object - var sequences = map.SequenceDefinitions == null ? modData.DefaultSequences[map.Tileset] : - new SequenceProvider(map, modData, ts, map.SequenceDefinitions); + var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] : + new SequenceProvider(fileSystem, modData, ts, mapSequences); // TODO: Add support for custom voxel sequences ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences); diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 6fd195351e..f0b1293c1d 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -296,7 +296,8 @@ namespace OpenRA { try { - return Ruleset.LoadFromMap(modData, this); + return Ruleset.Load(modData, this, Tileset, RuleDefinitions, WeaponDefinitions, + VoiceDefinitions, NotificationDefinitions, MusicDefinitions, SequenceDefinitions); } catch (Exception e) { diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index a2301bdb1a..a05bd954f3 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -25,7 +25,7 @@ namespace OpenRA { public sealed class MapCache : IEnumerable, IDisposable { - public static readonly MapPreview UnknownMap = new MapPreview(null, MapGridType.Rectangular, null); + public static readonly MapPreview UnknownMap = new MapPreview(null, null, MapGridType.Rectangular, null); public readonly IReadOnlyDictionary MapLocations; readonly Cache previews; @@ -41,7 +41,7 @@ namespace OpenRA this.modData = modData; var gridType = Exts.Lazy(() => modData.Manifest.Get().Type); - previews = new Cache(uid => new MapPreview(uid, gridType.Value, this)); + previews = new Cache(uid => new MapPreview(modData, uid, gridType.Value, this)); sheetBuilder = new SheetBuilder(SheetType.BGRA); // Enumerate map directories diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index cbc0e79612..f6142301d5 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -53,10 +53,11 @@ namespace OpenRA public readonly bool downloading; } - public class MapPreview : IDisposable + public class MapPreview : IDisposable, IReadOnlyFileSystem { static readonly CPos[] NoSpawns = new CPos[] { }; MapCache cache; + ModData modData; public readonly string Uid; public IReadOnlyPackage Package { get; private set; } @@ -65,6 +66,8 @@ namespace OpenRA public string Title { get; private set; } public string Type { get; private set; } public string Author { get; private set; } + public string TileSet { get; private set; } + public MapPlayers Players { get; private set; } public int PlayerCount { get; private set; } public CPos[] SpawnPoints { get; private set; } public MapGridType GridType { get; private set; } @@ -75,6 +78,10 @@ namespace OpenRA public MapVisibility Visibility { get; private set; } public bool SuitableForInitialMap { get; private set; } + Lazy rules; + public Ruleset Rules { get { return rules != null ? rules.Value : null; } } + public bool InvalidCustomRules { get; private set; } + Download download; public long DownloadBytes { get; private set; } public int DownloadPercentage { get; private set; } @@ -101,9 +108,11 @@ namespace OpenRA generatingMinimap = false; } - public MapPreview(string uid, MapGridType gridType, MapCache cache) + public MapPreview(ModData modData, string uid, MapGridType gridType, MapCache cache) { this.cache = cache; + this.modData = modData; + Uid = uid; Title = "Unknown Map"; Type = "Unknown"; @@ -145,6 +154,8 @@ namespace OpenRA Title = temp.Value; if (yaml.TryGetValue("Type", out temp)) Type = temp.Value; + if (yaml.TryGetValue("Tileset", out temp)) + TileSet = temp.Value; if (yaml.TryGetValue("Author", out temp)) Author = temp.Value; if (yaml.TryGetValue("Bounds", out temp)) @@ -188,9 +199,9 @@ namespace OpenRA MiniYaml playerDefinitions; if (yaml.TryGetValue("Players", out playerDefinitions)) { - var players = new MapPlayers(playerDefinitions.Nodes).Players; - PlayerCount = players.Count(x => x.Value.Playable); - SuitableForInitialMap = EvaluateUserFriendliness(players); + Players = new MapPlayers(playerDefinitions.Nodes); + PlayerCount = Players.Players.Count(x => x.Value.Playable); + SuitableForInitialMap = EvaluateUserFriendliness(Players.Players); } } catch (Exception) @@ -198,11 +209,41 @@ namespace OpenRA Status = MapStatus.Unavailable; } + rules = Exts.Lazy(() => + { + try + { + var ruleDefinitions = LoadRuleSection(yaml, "Rules"); + var weaponDefinitions = LoadRuleSection(yaml, "Weapons"); + var voiceDefinitions = LoadRuleSection(yaml, "Voices"); + var musicDefinitions = LoadRuleSection(yaml, "Music"); + var notificationDefinitions = LoadRuleSection(yaml, "Notifications"); + var sequenceDefinitions = LoadRuleSection(yaml, "Sequences"); + return Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions, + voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions); + } + catch + { + InvalidCustomRules = true; + } + + return Ruleset.LoadDefaultsForTileSet(modData, TileSet); + }); + if (p.Contains("map.png")) using (var dataStream = p.GetStream("map.png")) Preview = new Bitmap(dataStream); } + MiniYaml LoadRuleSection(Dictionary yaml, string section) + { + MiniYaml node; + if (!yaml.TryGetValue(section, out node)) + return null; + + return node; + } + bool EvaluateUserFriendliness(Dictionary players) { if (Status != MapStatus.Available || !Visibility.HasFlag(MapVisibility.Lobby)) @@ -367,5 +408,42 @@ namespace OpenRA if (deleteFromPackage != null) deleteFromPackage.Delete(Package.Name); } + + Stream IReadOnlyFileSystem.Open(string filename) + { + // Explicit package paths never refer to a map + if (!filename.Contains("|") && Package.Contains(filename)) + return Package.GetStream(filename); + + return modData.DefaultFileSystem.Open(filename); + } + + bool IReadOnlyFileSystem.TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename) + { + // Packages aren't supported inside maps + return modData.DefaultFileSystem.TryGetPackageContaining(path, out package, out filename); + } + + bool IReadOnlyFileSystem.TryOpen(string filename, out Stream s) + { + // Explicit package paths never refer to a map + if (!filename.Contains("|")) + { + s = Package.GetStream(filename); + if (s != null) + return true; + } + + return modData.DefaultFileSystem.TryOpen(filename, out s); + } + + bool IReadOnlyFileSystem.Exists(string filename) + { + // Explicit package paths never refer to a map + if (!filename.Contains("|") && Package.Contains(filename)) + return true; + + return modData.DefaultFileSystem.Exists(filename); + } } }