diff --git a/CHANGELOG b/CHANGELOG index 020c5ad84b..d22a02f661 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -76,11 +76,13 @@ NEW: Added modifier support to hotkeys. Fixed a desync related to projectile contrails. Fixed corrupted replays (which would immediately desync). + Removed runtime mod merging. Build system and packages: Added GeoIP to Makefile so it is installed properly. Added desktop shortcut creation support to the Makefile and Windows installer. COPYING and CHANGELOG are now shipped on all platforms. Fixed 'make docs' crashing when the game assets are not installed. + Renamed Game.Mods launch argument to Game.Mod. Mod / Custom map compatibility: Mods can now include traits from TD and D2K in RA. New sections MapFolders and Translations added to mod.yaml. @@ -99,6 +101,7 @@ NEW: Removed traits from World: SpatialBins. Added InvalidTargets property to weapons. Added modifier support for build palette hotkeys. + The Requires: option for inheriting from a parent mod has been removed. Mods can directly reference the parent mod files instead. 20130915: All mods: diff --git a/INSTALL b/INSTALL index fa17ff51bc..e52528c8d7 100644 --- a/INSTALL +++ b/INSTALL @@ -29,8 +29,8 @@ or build it from the command-line with MSBuild. Copy both the native DLLs from .\packaging\windows and the CLI images from .\thirdparty to the main folder. -Run the game with `OpenRA.Game.exe Game.Mods=ra` for Red Alert -or `OpenRA.Game.exe Game.Mods=cnc` for Command & Conquer +Run the game with `OpenRA.Game.exe Game.Mod=ra` for Red Alert +or `OpenRA.Game.exe Game.Mod=cnc` for Command & Conquer Debian/Ubuntu ------------- diff --git a/OpenRA.Editor/Form1.cs b/OpenRA.Editor/Form1.cs index 64ce2de9ec..aeed1619eb 100644 --- a/OpenRA.Editor/Form1.cs +++ b/OpenRA.Editor/Form1.cs @@ -50,8 +50,8 @@ namespace OpenRA.Editor FileSystem.LoadFromManifest(Game.modData.Manifest); Rules.LoadRules(Game.modData.Manifest, new Map()); - var mod = Game.modData.Manifest.Mods[0]; - Text = "{0} Mod Version: {1} - OpenRA Editor".F(Mod.AllMods[mod].Title, Mod.AllMods[mod].Version); + var mod = Game.modData.Manifest.Mod; + Text = "{0} Mod Version: {1} - OpenRA Editor".F(mod.Title, mod.Version); loadedMapName = null; }; diff --git a/OpenRA.FileFormats/Manifest.cs b/OpenRA.FileFormats/Manifest.cs index 43515a02e1..2fefa43439 100644 --- a/OpenRA.FileFormats/Manifest.cs +++ b/OpenRA.FileFormats/Manifest.cs @@ -18,8 +18,9 @@ namespace OpenRA.FileFormats public class Manifest { + public readonly Mod Mod; public readonly string[] - Mods, Folders, MapFolders, Rules, ServerTraits, + Folders, MapFolders, Rules, ServerTraits, Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout, Weapons, Voices, Notifications, Music, Movies, Translations, TileSets, ChromeMetrics, PackageContents; @@ -30,12 +31,13 @@ namespace OpenRA.FileFormats public readonly Dictionary> Fonts; public readonly int TileSize = 24; - public Manifest(string[] mods) + public Manifest(string mod) { - Mods = mods; - var yaml = new MiniYaml(null, mods - .Select(m => MiniYaml.FromFile("mods{0}{1}{0}mod.yaml".F(Path.DirectorySeparatorChar, m))) - .Aggregate(MiniYaml.MergeLiberal)).NodesDict; + var path = new [] { "mods", mod, "mod.yaml" }.Aggregate(Path.Combine); + var yaml = new MiniYaml(null, MiniYaml.FromFile(path)).NodesDict; + + Mod = FieldLoader.Load(yaml["Metadata"]); + Mod.Id = mod; // TODO: Use fieldloader Folders = YamlList(yaml, "Folders"); diff --git a/OpenRA.FileFormats/Mod.cs b/OpenRA.FileFormats/Mod.cs index 08133b4f4a..afcae53c70 100644 --- a/OpenRA.FileFormats/Mod.cs +++ b/OpenRA.FileFormats/Mod.cs @@ -21,7 +21,6 @@ namespace OpenRA.FileFormats public string Description; public string Version; public string Author; - public string Requires; public static readonly Dictionary AllMods = ValidateMods(Directory.GetDirectories("mods").Select(x => x.Substring(5)).ToArray()); @@ -45,12 +44,5 @@ namespace OpenRA.FileFormats } return ret; } - - public string[] WithPrerequisites() - { - return Id.Iterate(m => AllMods[m].Requires) - .TakeWhile(m => m != null) - .ToArray(); - } } } diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 025d1414df..be6bb2ac93 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -255,18 +255,6 @@ namespace OpenRA } } - public static Dictionary CurrentMods - { - get - { - // Initialization hasn't completed yet - if (Mod.AllMods == null || modData == null) - return null; - - return Mod.AllMods.Where(k => modData.Manifest.Mods.Contains(k.Key)).ToDictionary(k => k.Key, k => k.Value); - } - } - static Modifiers modifiers; public static Modifiers GetModifierKeys() { return modifiers; } internal static void HandleModifierKeys(Modifiers mods) { modifiers = mods; } @@ -328,7 +316,8 @@ namespace OpenRA Console.WriteLine("Available mods:"); foreach(var mod in Mod.AllMods) Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version); - InitializeWithMods(Settings.Game.Mods); + + InitializeWithMod(Settings.Game.Mod); if (Settings.Server.DiscoverNatDevices) { @@ -338,7 +327,7 @@ namespace OpenRA } } - public static void InitializeWithMods(string[] mods) + public static void InitializeWithMod(string mod) { // Clear static state if we have switched mods LobbyInfoChanged = () => {}; @@ -353,17 +342,18 @@ namespace OpenRA if (orderManager != null) orderManager.Dispose(); - // Discard any invalid mods, set RA as default - var mm = mods.Where( m => Mod.AllMods.ContainsKey( m ) ).ToArray(); - if (mm.Length == 0) mm = new[] { "ra" }; - Console.WriteLine("Loading mods: {0}", mm.JoinWith(",")); - Settings.Game.Mods = mm; + // Fall back to RA if the mod doesn't exist + if (!Mod.AllMods.ContainsKey(mod)) + mod = "ra"; + + Console.WriteLine("Loading mod: {0}", mod); + Settings.Game.Mod = mod; Sound.StopMusic(); Sound.StopVideo(); Sound.Initialize(); - modData = new ModData(mm); + modData = new ModData(mod); Renderer.InitializeFonts(modData.Manifest); modData.InitializeLoaders(); @@ -480,8 +470,7 @@ namespace OpenRA public static void CreateServer(ServerSettings settings) { - server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), - Settings.Game.Mods, settings, modData); + server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, modData); } public static int CreateLocalServer(string map) @@ -494,8 +483,7 @@ namespace OpenRA AllowPortForward = false }; - server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), - Settings.Game.Mods, settings, modData); + server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, modData); return server.Port; } @@ -509,16 +497,19 @@ namespace OpenRA { try { - var mod = CurrentMods.First().Value.Id; - var dirPath = "{1}maps{0}{2}".F(Path.DirectorySeparatorChar, Platform.SupportDir, mod); - if(!Directory.Exists(dirPath)) + var mod = Game.modData.Manifest.Mod; + var dirPath = new [] { Platform.SupportDir, "maps", mod.Id }.Aggregate(Path.Combine); + if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath); - var mapPath = "{1}{0}{2}".F(Path.DirectorySeparatorChar, dirPath, mapHash+".oramap"); + + var mapPath = Path.Combine(dirPath, mapHash+".oramap"); Console.Write("Trying to download map to {0} ... ".F(mapPath)); + WebClient webClient = new WebClient(); webClient.DownloadFile(Game.Settings.Game.MapRepository + mapHash, mapPath); Game.modData.AvailableMaps.Add(mapHash, new Map(mapPath)); Console.WriteLine("done"); + return true; } catch (WebException e) diff --git a/OpenRA.Game/GameRules/Settings.cs b/OpenRA.Game/GameRules/Settings.cs index 2bfcfb42ab..52b9a87725 100644 --- a/OpenRA.Game/GameRules/Settings.cs +++ b/OpenRA.Game/GameRules/Settings.cs @@ -124,7 +124,7 @@ namespace OpenRA.GameRules public class GameSettings { - public string[] Mods = { "ra" }; + public string Mod = "ra"; public bool ShowShellmap = true; diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 422914251e..1414d405de 100755 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -50,13 +50,13 @@ namespace OpenRA return dirsWithMaps.Concat(Directory.GetFiles(dir, "*.oramap")); } - public ModData(params string[] mods) + public ModData(string mod) { Languages = new string[0]; - Manifest = new Manifest(mods); + Manifest = new Manifest(mod); ObjectCreator = new ObjectCreator(Manifest); LoadScreen = ObjectCreator.CreateObject(Manifest.LoadScreen.Value); - LoadScreen.Init(Manifest.LoadScreen.NodesDict.ToDictionary(x => x.Key, x => x.Value.Value)); + LoadScreen.Init(Manifest, Manifest.LoadScreen.NodesDict.ToDictionary(x => x.Key, x => x.Value.Value)); LoadScreen.Display(); WidgetLoader = new WidgetLoader(this); @@ -149,9 +149,7 @@ namespace OpenRA Dictionary FindMaps() { var paths = Manifest.MapFolders.SelectMany(f => FindMapsIn(f)); - var ret = new Dictionary(); - foreach (var path in paths) { try @@ -177,7 +175,7 @@ namespace OpenRA public interface ILoadScreen { - void Init(Dictionary info); + void Init(Manifest m, Dictionary info); void Display(); void StartGame(); } diff --git a/OpenRA.Game/Network/GameServer.cs b/OpenRA.Game/Network/GameServer.cs index fed810d5fb..2cd7eb7754 100644 --- a/OpenRA.Game/Network/GameServer.cs +++ b/OpenRA.Game/Network/GameServer.cs @@ -21,49 +21,47 @@ namespace OpenRA.Network public readonly int State = 0; public readonly int Players = 0; public readonly string Map = null; - public readonly string[] Mods = { }; + + // Retained name compatibility with the master server + public readonly string Mods = ""; public readonly int TTL = 0; - public Dictionary UsefulMods - { - get - { - return Mods - .Where(v => v.Contains('@')) - .ToDictionary(v => v.Split('@')[0], v => v.Split('@')[1]); - } - } - - static bool AreVersionsCompatible(string a, string b) - { - if (Game.Settings.Debug.IgnoreVersionMismatch) - return true; - - return a == b; - } - public bool CanJoin() { - //"waiting for players" + // "waiting for players" if (State != 1) return false; - // Mods won't match if there are a different number - if (Game.CurrentMods.Count != Mods.Count()) + if (!CompatibleVersion()) return false; // Don't have the map locally - if (!Game.modData.AvailableMaps.ContainsKey(Map)) - if (!Game.Settings.Game.AllowDownloading) - return false; + // TODO: We allow joining, then drop on game start if the map isn't available + if (!Game.modData.AvailableMaps.ContainsKey(Map) && !Game.Settings.Game.AllowDownloading) + return false; - return CompatibleVersion(); + return true; } public bool CompatibleVersion() { - return UsefulMods.All(m => Game.CurrentMods.ContainsKey(m.Key) - && AreVersionsCompatible(m.Value, Game.CurrentMods[m.Key].Version)); + // Invalid game listing - we require one entry of id@version + var modVersion = Mods.Split('@'); + if (modVersion.Length != 2) + return false; + + var mod = Game.modData.Manifest.Mod; + + // Different mod + // TODO: Allow mod switch when joining server + if (modVersion[0] != mod.Id) + return false; + + // Same mod, but different version + if (modVersion[1] != mod.Version && !Game.Settings.Debug.IgnoreVersionMismatch) + return false; + + return true; } } } diff --git a/OpenRA.Game/Network/Handshake.cs b/OpenRA.Game/Network/Handshake.cs index 2d96a9d2d7..1fae372484 100644 --- a/OpenRA.Game/Network/Handshake.cs +++ b/OpenRA.Game/Network/Handshake.cs @@ -16,7 +16,8 @@ namespace OpenRA.Network { public class HandshakeRequest { - public string[] Mods; + public string Mod; + public string Version; public string Map; public string Serialize() @@ -36,7 +37,8 @@ namespace OpenRA.Network public class HandshakeResponse { - public string[] Mods; + public string Mod; + public string Version; public string Password; [FieldLoader.Ignore] public Session.Client Client; @@ -44,7 +46,7 @@ namespace OpenRA.Network { var data = new List(); data.Add( new MiniYamlNode( "Handshake", null, - new string[]{ "Mods", "Password" }.Select( p => FieldSaver.SaveField(this, p) ).ToList() ) ); + new string[]{ "Mod", "Version", "Password" }.Select( p => FieldSaver.SaveField(this, p) ).ToList() ) ); data.Add(new MiniYamlNode("Client", FieldSaver.Save(Client))); return data.WriteToString(); diff --git a/OpenRA.Game/Network/OrderManager.cs b/OpenRA.Game/Network/OrderManager.cs index e60229c50e..497b1031f1 100755 --- a/OpenRA.Game/Network/OrderManager.cs +++ b/OpenRA.Game/Network/OrderManager.cs @@ -20,7 +20,7 @@ namespace OpenRA.Network readonly SyncReport syncReport; readonly FrameData frameData = new FrameData(); - public Session LobbyInfo = new Session(Game.Settings.Game.Mods); + public Session LobbyInfo = new Session(); public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } } public World world; diff --git a/OpenRA.Game/Network/ReplayRecorderConnection.cs b/OpenRA.Game/Network/ReplayRecorderConnection.cs index 2a231c596a..3e6f6a8c31 100644 --- a/OpenRA.Game/Network/ReplayRecorderConnection.cs +++ b/OpenRA.Game/Network/ReplayRecorderConnection.cs @@ -34,7 +34,8 @@ namespace OpenRA.Network void StartSavingReplay(byte[] initialContent) { var filename = chooseFilename(); - var dir = new[] { Platform.SupportDir, "Replays", WidgetUtils.ActiveModId(), WidgetUtils.ActiveModVersion() }.Aggregate(Path.Combine); + var mod = Game.modData.Manifest.Mod; + var dir = new[] { Platform.SupportDir, "Replays", mod.Id, mod.Version }.Aggregate(Path.Combine); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index 0431c43e85..ca635729bb 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -28,7 +28,7 @@ namespace OpenRA.Network { try { - var session = new Session(Game.Settings.Game.Mods); + var session = new Session(); var ys = MiniYaml.FromString(data); foreach (var y in ys) @@ -123,10 +123,9 @@ namespace OpenRA.Network { public string ServerName; public string Map; - public string[] Mods = { "ra" }; // mod names - public int OrderLatency = 3; // net tick frames (x 120 = ms) + public int OrderLatency = 3; // net tick frames (x 120 = ms) public int RandomSeed = 0; - public bool FragileAlliances = false; // Allow diplomatic stance changes after game start. + public bool FragileAlliances = false; // Allow diplomatic stance changes after game start. public bool AllowCheats = false; public bool Dedicated; public string Difficulty; @@ -140,12 +139,6 @@ namespace OpenRA.Network public string GameUid; } - public Session(string[] mods) - { - this.GlobalSettings.Mods = mods.ToArray(); - this.GlobalSettings.GameUid = Guid.NewGuid().ToString(); - } - public string Serialize() { var clientData = new List(); diff --git a/OpenRA.Game/Network/SyncReport.cs b/OpenRA.Game/Network/SyncReport.cs index 5270b0427f..3a17f5680f 100755 --- a/OpenRA.Game/Network/SyncReport.cs +++ b/OpenRA.Game/Network/SyncReport.cs @@ -178,8 +178,8 @@ namespace OpenRA.Network { if (r.Frame == frame) { + var mod = Game.modData.Manifest.Mod; Log.Write("sync", "Player: {0} ({1} {2} {3})", Game.Settings.Player.Name, Platform.CurrentPlatform, Environment.OSVersion, Platform.RuntimeVersion); - var mod = Game.CurrentMods.First().Value; Log.Write("sync", "Game ID: {0} (Mod: {1} at Version {2})", orderManager.LobbyInfo.GlobalSettings.GameUid, mod.Title, mod.Version); Log.Write("sync", "Sync for net frame {0} -------------", r.Frame); Log.Write("sync", "SharedRandom: {0} (#{1})", r.SyncedRandom, r.TotalCount); diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index 3fcca0cd13..6d261cbae7 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -119,9 +119,14 @@ namespace OpenRA.Network case "HandshakeRequest": { var request = HandshakeRequest.Deserialize(order.TargetString); - var localMods = orderManager.LobbyInfo.GlobalSettings.Mods.Select(m => "{0}@{1}".F(m, Mod.AllMods[m].Version)).ToArray(); + + // TODO: Switch to the server's mod if we have it + // Otherwise send the handshake with our current settings and let the server reject us + var mod = Game.modData.Manifest.Mod; // Check that the map exists on the client + // TODO: This will behave badly if joining a server with a different mod + // This needs to occur *after* joining the server if (!Game.modData.AvailableMaps.ContainsKey(request.Map)) { if (Game.Settings.Game.AllowDownloading) @@ -144,7 +149,8 @@ namespace OpenRA.Network var response = new HandshakeResponse() { Client = info, - Mods = localMods, + Mod = mod.Id, + Version = mod.Version, Password = orderManager.Password }; diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 23433f5b57..547699f1e9 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -95,7 +95,7 @@ namespace OpenRA.Server t.GameEnded(this); } - public Server(IPEndPoint endpoint, string[] mods, ServerSettings settings, ModData modData) + public Server(IPEndPoint endpoint, ServerSettings settings, ModData modData) { Log.AddChannel("server", "server.log"); @@ -117,7 +117,7 @@ namespace OpenRA.Server foreach (var trait in modData.Manifest.ServerTraits) serverTraits.Add(modData.ObjectCreator.CreateObject(trait)); - LobbyInfo = new Session(mods); + LobbyInfo = new Session(); LobbyInfo.GlobalSettings.RandomSeed = randomSeed; LobbyInfo.GlobalSettings.Map = settings.Map; LobbyInfo.GlobalSettings.ServerName = settings.Name; @@ -127,10 +127,7 @@ namespace OpenRA.Server foreach (var t in serverTraits.WithInterface()) t.ServerStarted(this); - Log.Write("server", "Initial mods: "); - foreach (var m in LobbyInfo.GlobalSettings.Mods) - Log.Write("server", "- {0}", m); - + Log.Write("server", "Initial mod: {0}", ModData.Manifest.Mod.Id); Log.Write("server", "Initial map: {0}", LobbyInfo.GlobalSettings.Map); new Thread(_ => @@ -226,9 +223,11 @@ namespace OpenRA.Server // Dispatch a handshake order var request = new HandshakeRequest() { - Map = LobbyInfo.GlobalSettings.Map, - Mods = LobbyInfo.GlobalSettings.Mods.Select(m => "{0}@{1}".F(m, Mod.AllMods[m].Version)).ToArray() + Mod = ModData.Manifest.Mod.Id, + Version = ModData.Manifest.Mod.Version, + Map = LobbyInfo.GlobalSettings.Map }; + DispatchOrdersToClient(newConn, 0, 0, new ServerOrder("HandshakeRequest", request.Serialize()).Serialize()); } catch (Exception e) @@ -282,13 +281,7 @@ namespace OpenRA.Server else client.Color = HSLColor.FromRGB(255, 255, 255); - // Check that the client has compatible mods - var mods = handshake.Mods; - var validMod = mods.All(m => m.Contains('@')) && // valid format - mods.Count() == Game.CurrentMods.Count() && // same number - mods.Select(m => Pair.New(m.Split('@')[0], m.Split('@')[1])).All(kv => Game.CurrentMods.ContainsKey(kv.First)); - - if (!validMod) + if (ModData.Manifest.Mod.Id != handshake.Mod) { Log.Write("server", "Rejected connection from {0}; mods do not match.", newConn.socket.RemoteEndPoint); @@ -298,10 +291,7 @@ namespace OpenRA.Server return; } - var validVersion = mods.Select(m => Pair.New(m.Split('@')[0], m.Split('@')[1])).All( - kv => kv.Second == Game.CurrentMods[kv.First].Version); - - if (!validVersion && !LobbyInfo.GlobalSettings.AllowVersionMismatch) + if (ModData.Manifest.Mod.Version != handshake.Version && !LobbyInfo.GlobalSettings.AllowVersionMismatch) { Log.Write("server", "Rejected connection from {0}; Not running the same version.", newConn.socket.RemoteEndPoint); @@ -338,13 +328,14 @@ namespace OpenRA.Server // Send initial ping SendOrderTo(newConn, "Ping", Environment.TickCount.ToString()); - if (File.Exists("{0}motd_{1}.txt".F(Platform.SupportDir, LobbyInfo.GlobalSettings.Mods[0]))) + var motdPath = Path.Combine(Platform.SupportDir, "motd_{0}.txt".F(ModData.Manifest.Mod.Id)); + if (File.Exists(motdPath)) { - var motd = File.ReadAllText("{0}motd_{1}.txt".F(Platform.SupportDir, LobbyInfo.GlobalSettings.Mods[0])); + var motd = System.IO.File.ReadAllText(motdPath); SendOrderTo(newConn, "Message", motd); } - if (mods.Any(m => m.Contains("{DEV_VERSION}"))) + if (handshake.Mod == "{DEV_VERSION}") SendMessage("{0} is running an unversioned development build, ".F(client.Name) + "and may desynchronize the game state if they have incompatible rules."); diff --git a/OpenRA.Game/Support/Program.cs b/OpenRA.Game/Support/Program.cs index 899763d1bd..7d85451537 100644 --- a/OpenRA.Game/Support/Program.cs +++ b/OpenRA.Game/Support/Program.cs @@ -47,9 +47,9 @@ namespace OpenRA { Log.AddChannel("exception", "exception.log"); - if (Game.CurrentMods != null) + if (Game.modData != null) { - var mod = Game.CurrentMods.First().Value; + var mod = Game.modData.Manifest.Mod; Log.Write("exception", "{0} Mod at Version {1}", mod.Title, mod.Version); } diff --git a/OpenRA.Game/Widgets/WidgetUtils.cs b/OpenRA.Game/Widgets/WidgetUtils.cs index ae4a42fb91..0ab460b639 100644 --- a/OpenRA.Game/Widgets/WidgetUtils.cs +++ b/OpenRA.Game/Widgets/WidgetUtils.cs @@ -225,24 +225,6 @@ namespace OpenRA.Widgets public static Action Once( Action a ) { return () => { if (a != null) { a(); a = null; } }; } - public static string ActiveModVersion() - { - var mod = Game.modData.Manifest.Mods[0]; - return Mod.AllMods[mod].Version; - } - - public static string ActiveModTitle() - { - var mod = Game.modData.Manifest.Mods[0]; - return Mod.AllMods[mod].Title; - } - - public static string ActiveModId() - { - var mod = Game.modData.Manifest.Mods[0]; - return Mod.AllMods[mod].Id; - } - public static string ChooseInitialMap(string map) { var availableMaps = Game.modData.AvailableMaps; diff --git a/OpenRA.Irc/IrcClient.cs b/OpenRA.Irc/IrcClient.cs index 306678c305..ff2eb91334 100644 --- a/OpenRA.Irc/IrcClient.cs +++ b/OpenRA.Irc/IrcClient.cs @@ -70,9 +70,7 @@ namespace OpenRA.Irc var command = split[0]; if (command.EqualsIC("VERSION")) { - var mod = Game.CurrentMods.Values.FirstOrDefault(); - if (mod == null) - return; + var mod = Game.modData.Manifest.Mod; Instance.CtcpRespond(l.Prefix.Nickname, command, "{0}: {1}".F(mod.Title, mod.Version)); } }; diff --git a/OpenRA.Lint/YamlChecker.cs b/OpenRA.Lint/YamlChecker.cs index 08e12090ab..f917921793 100644 --- a/OpenRA.Lint/YamlChecker.cs +++ b/OpenRA.Lint/YamlChecker.cs @@ -35,7 +35,7 @@ namespace OpenRA.Lint try { var options = args.Where(a => a.StartsWith("-")); - var mods = args.Where(a => !options.Contains(a)).ToArray(); + var mod = args.Where(a => !options.Contains(a)).First(); var verbose = options.Contains("-v") || options.Contains("--verbose"); @@ -44,7 +44,7 @@ namespace OpenRA.Lint FieldLoader.UnknownFieldAction = (s, f) => EmitError("FieldLoader: Missing field `{0}` on `{1}`".F(s, f.Name)); AppDomain.CurrentDomain.AssemblyResolve += FileSystem.ResolveAssembly; - Game.modData = new ModData(mods); + Game.modData = new ModData(mod); Rules.LoadRules(Game.modData.Manifest, new Map()); foreach (var customPassType in Game.modData.ObjectCreator diff --git a/OpenRA.Mods.Cnc/CncLoadScreen.cs b/OpenRA.Mods.Cnc/CncLoadScreen.cs index 3ee853fa18..d63688c7eb 100644 --- a/OpenRA.Mods.Cnc/CncLoadScreen.cs +++ b/OpenRA.Mods.Cnc/CncLoadScreen.cs @@ -29,7 +29,7 @@ namespace OpenRA.Mods.Cnc Renderer r; NullInputHandler nih = new NullInputHandler(); - public void Init(Dictionary info) + public void Init(Manifest m, Dictionary info) { loadInfo = info; @@ -63,6 +63,8 @@ namespace OpenRA.Mods.Cnc brightBlock = new Sprite(s, new Rectangle(320, 0, 16, 35), TextureChannel.Alpha); dimBlock = new Sprite(s, new Rectangle(336, 0, 16, 35), TextureChannel.Alpha); + + versionText = m.Mod.Version; } bool setup; @@ -93,7 +95,6 @@ namespace OpenRA.Mods.Cnc loadingPos = new float2((bounds.Width - loadingFont.Measure(loadingText).X) / 2, barY); versionFont = r.Fonts["Regular"]; - versionText = WidgetUtils.ActiveModVersion(); var versionSize = versionFont.Measure(versionText); versionPos = new float2(bounds.Width - 107 - versionSize.X / 2, 115 - versionSize.Y / 2); diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameMenuLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameMenuLogic.cs index 2cd9679375..23caa5283b 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameMenuLogic.cs +++ b/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameMenuLogic.cs @@ -30,7 +30,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic var mpe = world.WorldActor.Trait(); mpe.Fade(CncMenuPaletteEffect.EffectType.Desaturated); - menu.Get("VERSION_LABEL").GetText = WidgetUtils.ActiveModVersion; + menu.Get("VERSION_LABEL").Text = Game.modData.Manifest.Mod.Version; bool hideButtons = false; menu.Get("MENU_BUTTONS").IsVisible = () => !hideButtons; diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncInstallMusicLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/CncInstallMusicLogic.cs index 8938ea67a4..f06d5d119b 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncInstallMusicLogic.cs +++ b/OpenRA.Mods.Cnc/Widgets/Logic/CncInstallMusicLogic.cs @@ -29,7 +29,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic { try { - var path = new string[] { Platform.SupportDir, "Content", WidgetUtils.ActiveModId() }.Aggregate(Path.Combine); + var path = new string[] { Platform.SupportDir, "Content", Game.modData.Manifest.Mod.Id }.Aggregate(Path.Combine); FileSystem.Mount(Path.Combine(path, "scores.mix")); FileSystem.Mount(Path.Combine(path, "transit.mix")); diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncMenuLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/CncMenuLogic.cs index db2eb75928..c87ec96f06 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncMenuLogic.cs +++ b/OpenRA.Mods.Cnc/Widgets/Logic/CncMenuLogic.cs @@ -28,7 +28,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic .Fade(CncMenuPaletteEffect.EffectType.Desaturated); rootMenu = widget.Get("MENU_BACKGROUND"); - rootMenu.Get("VERSION_LABEL").GetText = WidgetUtils.ActiveModVersion; + rootMenu.Get("VERSION_LABEL").Text = Game.modData.Manifest.Mod.Version; // Menu buttons var mainMenu = widget.Get("MAIN_MENU"); diff --git a/OpenRA.Mods.RA/DefaultLoadScreen.cs b/OpenRA.Mods.RA/DefaultLoadScreen.cs index 377b6fd912..72b70d7b59 100644 --- a/OpenRA.Mods.RA/DefaultLoadScreen.cs +++ b/OpenRA.Mods.RA/DefaultLoadScreen.cs @@ -28,7 +28,7 @@ namespace OpenRA.Mods.RA Sprite stripe, logo; string[] messages; - public void Init(Dictionary info) + public void Init(Manifest m, Dictionary info) { this.info = info; diff --git a/OpenRA.Mods.RA/NullLoadScreen.cs b/OpenRA.Mods.RA/NullLoadScreen.cs index 27b0aa5e03..ccadac17c4 100644 --- a/OpenRA.Mods.RA/NullLoadScreen.cs +++ b/OpenRA.Mods.RA/NullLoadScreen.cs @@ -9,13 +9,14 @@ #endregion using System.Collections.Generic; +using OpenRA.FileFormats; using OpenRA.Widgets; namespace OpenRA.Mods.RA { public class NullLoadScreen : ILoadScreen { - public void Init(Dictionary info) {} + public void Init(Manifest m, Dictionary info) {} public void Display() { diff --git a/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs b/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs index 93ee72ad17..1844c612a7 100644 --- a/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs +++ b/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs @@ -50,6 +50,7 @@ namespace OpenRA.Mods.RA.Server lastPing = Environment.TickCount; isBusy = true; + var mod = server.ModData.Manifest.Mod; Action a = () => { try @@ -60,14 +61,13 @@ namespace OpenRA.Mods.RA.Server using (var wc = new WebClient()) { wc.Proxy = null; - - wc.DownloadData( + wc.DownloadData( server.Settings.MasterServer + url.F( server.Settings.ExternalPort, Uri.EscapeUriString(server.Settings.Name), (int)server.State, server.LobbyInfo.Clients.Where(c1 => c1.Bot == null).Count(), server.LobbyInfo.Clients.Where(c1 => c1.Bot != null).Count(), - Game.CurrentMods.Select(f => "{0}@{1}".F(f.Key, f.Value.Version)).JoinWith(","), + "{0}@{1}".F(mod.Id, mod.Version), server.LobbyInfo.GlobalSettings.Map, server.Map.PlayerCount)); diff --git a/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs index a0a7470ccb..3499d592f3 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs @@ -99,14 +99,16 @@ namespace OpenRA.Mods.RA.Widgets.Logic template = panel.Get("ASSET_TEMPLATE"); PopulateAssetList(); - var palette = (WidgetUtils.ActiveModId() == "d2k") ? "d2k.pal" : "egopal.pal"; + // TODO: Horrible hack + var modID = Game.modData.Manifest.Mod.Id; + var palette = (modID == "d2k") ? "d2k.pal" : "egopal.pal"; panel.Get("EXPORT_BUTTON").OnClick = () => { var ExtractGameFiles = new string[][] { - new string[] {"--extract", WidgetUtils.ActiveModId(), palette, "--userdir"}, - new string[] {"--extract", WidgetUtils.ActiveModId(), "{0}.shp".F(spriteImage.Image), "--userdir"}, + new string[] {"--extract", modID, palette, "--userdir"}, + new string[] {"--extract", modID, "{0}.shp".F(spriteImage.Image), "--userdir"}, }; var ExportToPng = new string[][] @@ -131,11 +133,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic var ExtractGameFilesList = new List(); var ExportToPngList = new List(); - ExtractGameFilesList.Add(new string[] { "--extract", WidgetUtils.ActiveModId(), palette, "--userdir"} ); + ExtractGameFilesList.Add(new string[] { "--extract", modID, palette, "--userdir"} ); foreach (var shp in AvailableShps) { - ExtractGameFilesList.Add(new string[] { "--extract", WidgetUtils.ActiveModId(), shp, "--userdir" } ); + ExtractGameFilesList.Add(new string[] { "--extract", modID, shp, "--userdir" } ); ExportToPngList.Add(new string[] { "--png", Platform.SupportDir+shp, Platform.SupportDir+palette } ); Console.WriteLine(Platform.SupportDir+shp); } @@ -148,7 +150,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic { { "ExtractGameFiles", ExtractGameFiles }, { "ExportToPng", ExportToPng }, - { "ImportFromPng", ImportFromPng} + { "ImportFromPng", ImportFromPng } }; Ui.OpenWindow("CONVERT_ASSETS_PANEL", args); diff --git a/OpenRA.Mods.RA/Widgets/Logic/DownloadPackagesLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/DownloadPackagesLogic.cs index 400f8778e6..8473d0a868 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/DownloadPackagesLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/DownloadPackagesLogic.cs @@ -51,7 +51,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic // Save the package to a temp file var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - var dest = new string[] { Platform.SupportDir, "Content", Game.modData.Manifest.Mods[0] }.Aggregate(Path.Combine); + var dest = new string[] { Platform.SupportDir, "Content", Game.modData.Manifest.Mod.Id }.Aggregate(Path.Combine); Action onDownloadProgress = i => { diff --git a/OpenRA.Mods.RA/Widgets/Logic/MainMenuButtonsLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/MainMenuButtonsLogic.cs index 3c6d4a7f32..7bcf434fb5 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/MainMenuButtonsLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/MainMenuButtonsLogic.cs @@ -27,7 +27,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic var versionLabel = Ui.Root.GetOrNull("VERSION_LABEL"); if (versionLabel != null) - versionLabel.GetText = WidgetUtils.ActiveModVersion; + versionLabel.Text = Game.modData.Manifest.Mod.Version; widget.Get("MAINMENU_BUTTON_JOIN").OnClick = () => OpenGamePanel("JOINSERVER_BG"); widget.Get("MAINMENU_BUTTON_CREATE").OnClick = () => OpenGamePanel("CREATESERVER_BG"); diff --git a/OpenRA.Mods.RA/Widgets/Logic/ModBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ModBrowserLogic.cs index 0efff78e0a..fb6d96927d 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ModBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ModBrowserLogic.cs @@ -26,10 +26,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic var modList = panel.Get("MOD_LIST"); var loadButton = panel.Get("LOAD_BUTTON"); loadButton.OnClick = () => LoadMod(currentMod.Id, onSwitch); - loadButton.IsDisabled = () => currentMod.Id == Game.CurrentMods.Keys.First(); + loadButton.IsDisabled = () => currentMod.Id == Game.modData.Manifest.Mod.Id; panel.Get("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; - currentMod = Mod.AllMods[Game.modData.Manifest.Mods[0]]; + currentMod = Game.modData.Manifest.Mod; // Mod list var modTemplate = modList.Get("MOD_TEMPLATE"); @@ -47,13 +47,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic void LoadMod(string mod, Action onSwitch) { - var mods = Mod.AllMods[mod].WithPrerequisites(); - Game.RunAfterTick(() => { Ui.CloseWindow(); onSwitch(); - Game.InitializeWithMods(mods); + Game.InitializeWithMod(mod); }); } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs index c9e34d0937..e55fe67f53 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs @@ -28,11 +28,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic panel.Get("CANCEL_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; var rl = panel.Get("REPLAY_LIST"); - - var dir = new[] { Platform.SupportDir, "Replays", WidgetUtils.ActiveModId(), WidgetUtils.ActiveModVersion() }.Aggregate(Path.Combine); - var template = panel.Get("REPLAY_TEMPLATE"); + var mod = Game.modData.Manifest.Mod; + var dir = new[] { Platform.SupportDir, "Replays", mod.Id, mod.Version }.Aggregate(Path.Combine); + rl.RemoveChildren(); if (Directory.Exists(dir)) { diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs index 9adb9855ee..1302d153c6 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs @@ -149,17 +149,15 @@ namespace OpenRA.Mods.RA.Widgets.Logic return (game == null) ? null : Game.modData.FindMapByUid(game.Map); } - static string GenerateModLabel(KeyValuePair mod) + public static string GenerateModLabel(GameServer s) { - if (Mod.AllMods.ContainsKey(mod.Key)) - return "{0} ({1})".F(Mod.AllMods[mod.Key].Title, mod.Value); + Mod mod; + var modVersion = s.Mods.Split('@'); - return "Unknown Mod: {0}".F(mod.Key); - } + if (modVersion.Length == 2 && Mod.AllMods.TryGetValue(modVersion[0], out mod)) + return "{0} ({1})".F(mod.Title, modVersion[1]); - public static string GenerateModsLabel(GameServer s) - { - return s.UsefulMods.Select(m => GenerateModLabel(m)).JoinWith("\n"); + return "Unknown mod: {0}".F(s.Mods); } bool Filtered(GameServer game) @@ -237,7 +235,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic ip.GetText = () => game.Address; var version = item.Get("VERSION"); - version.GetText = () => GenerateModsLabel(game); + version.GetText = () => GenerateModLabel(game); version.IsVisible = () => !game.CompatibleVersion(); var location = item.Get("LOCATION"); diff --git a/OpenRA.Utility/Command.cs b/OpenRA.Utility/Command.cs index 76abcf1eb4..9d738ef85d 100644 --- a/OpenRA.Utility/Command.cs +++ b/OpenRA.Utility/Command.cs @@ -176,12 +176,12 @@ namespace OpenRA.Utility public static void ConvertTmpToPng(string[] args) { - var mods = args[1].Split(','); + var mod = args[1]; var theater = args[2]; var templateNames = args.Skip(3); var shadowIndex = new int[] { 3, 4 }; - var manifest = new Manifest(mods); + var manifest = new Manifest(mod); FileSystem.LoadFromManifest(manifest); var tileset = manifest.TileSets.Select(a => new TileSet(a)) @@ -225,10 +225,10 @@ namespace OpenRA.Utility public static void ExtractFiles(string[] args) { - var mods = args[1].Split(','); + var mod = args[1]; var files = args.Skip(2); - var manifest = new Manifest(mods); + var manifest = new Manifest(mod); FileSystem.LoadFromManifest(manifest); foreach (var f in files) diff --git a/launch-dedicated.sh b/launch-dedicated.sh index 2b92fb5ac2..0e4ef4fc8f 100755 --- a/launch-dedicated.sh +++ b/launch-dedicated.sh @@ -9,7 +9,7 @@ AdvertiseOnline="False" Map="ba403f6bcb4cae934335b78be42f714992b3a71a" while true; do - mono --debug OpenRA.Game.exe Game.Mods=$Mod Server.Dedicated=$Dedicated Server.DedicatedLoop=$DedicatedLoop \ + mono --debug OpenRA.Game.exe Game.Mod=$Mod Server.Dedicated=$Dedicated Server.DedicatedLoop=$DedicatedLoop \ Server.Name=$Name Server.ListenPort=$ListenPort Server.ExternalPort=$ExternalPort \ Server.AdvertiseOnline=$AdvertiseOnline Server.Map=$Map done \ No newline at end of file