Remove runtime mod merging. Closes #3421.

This commit is contained in:
Paul Chote
2013-10-06 12:44:20 +13:00
parent 4d893cb1f2
commit 6d6d1e230b
35 changed files with 143 additions and 186 deletions

View File

@@ -76,11 +76,13 @@ NEW:
Added modifier support to hotkeys. Added modifier support to hotkeys.
Fixed a desync related to projectile contrails. Fixed a desync related to projectile contrails.
Fixed corrupted replays (which would immediately desync). Fixed corrupted replays (which would immediately desync).
Removed runtime mod merging.
Build system and packages: Build system and packages:
Added GeoIP to Makefile so it is installed properly. Added GeoIP to Makefile so it is installed properly.
Added desktop shortcut creation support to the Makefile and Windows installer. Added desktop shortcut creation support to the Makefile and Windows installer.
COPYING and CHANGELOG are now shipped on all platforms. COPYING and CHANGELOG are now shipped on all platforms.
Fixed 'make docs' crashing when the game assets are not installed. Fixed 'make docs' crashing when the game assets are not installed.
Renamed Game.Mods launch argument to Game.Mod.
Mod / Custom map compatibility: Mod / Custom map compatibility:
Mods can now include traits from TD and D2K in RA. Mods can now include traits from TD and D2K in RA.
New sections MapFolders and Translations added to mod.yaml. New sections MapFolders and Translations added to mod.yaml.
@@ -99,6 +101,7 @@ NEW:
Removed traits from World: SpatialBins. Removed traits from World: SpatialBins.
Added InvalidTargets property to weapons. Added InvalidTargets property to weapons.
Added modifier support for build palette hotkeys. 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: 20130915:
All mods: All mods:

View File

@@ -29,8 +29,8 @@ or build it from the command-line with MSBuild.
Copy both the native DLLs from .\packaging\windows Copy both the native DLLs from .\packaging\windows
and the CLI images from .\thirdparty to the main folder. and the CLI images from .\thirdparty to the main folder.
Run the game with `OpenRA.Game.exe Game.Mods=ra` for Red Alert Run the game with `OpenRA.Game.exe Game.Mod=ra` for Red Alert
or `OpenRA.Game.exe Game.Mods=cnc` for Command & Conquer or `OpenRA.Game.exe Game.Mod=cnc` for Command & Conquer
Debian/Ubuntu Debian/Ubuntu
------------- -------------

View File

@@ -50,8 +50,8 @@ namespace OpenRA.Editor
FileSystem.LoadFromManifest(Game.modData.Manifest); FileSystem.LoadFromManifest(Game.modData.Manifest);
Rules.LoadRules(Game.modData.Manifest, new Map()); Rules.LoadRules(Game.modData.Manifest, new Map());
var mod = Game.modData.Manifest.Mods[0]; var mod = Game.modData.Manifest.Mod;
Text = "{0} Mod Version: {1} - OpenRA Editor".F(Mod.AllMods[mod].Title, Mod.AllMods[mod].Version); Text = "{0} Mod Version: {1} - OpenRA Editor".F(mod.Title, mod.Version);
loadedMapName = null; loadedMapName = null;
}; };

View File

@@ -18,8 +18,9 @@ namespace OpenRA.FileFormats
public class Manifest public class Manifest
{ {
public readonly Mod Mod;
public readonly string[] public readonly string[]
Mods, Folders, MapFolders, Rules, ServerTraits, Folders, MapFolders, Rules, ServerTraits,
Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout, Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
Weapons, Voices, Notifications, Music, Movies, Translations, TileSets, Weapons, Voices, Notifications, Music, Movies, Translations, TileSets,
ChromeMetrics, PackageContents; ChromeMetrics, PackageContents;
@@ -30,12 +31,13 @@ namespace OpenRA.FileFormats
public readonly Dictionary<string, Pair<string,int>> Fonts; public readonly Dictionary<string, Pair<string,int>> Fonts;
public readonly int TileSize = 24; public readonly int TileSize = 24;
public Manifest(string[] mods) public Manifest(string mod)
{ {
Mods = mods; var path = new [] { "mods", mod, "mod.yaml" }.Aggregate(Path.Combine);
var yaml = new MiniYaml(null, mods var yaml = new MiniYaml(null, MiniYaml.FromFile(path)).NodesDict;
.Select(m => MiniYaml.FromFile("mods{0}{1}{0}mod.yaml".F(Path.DirectorySeparatorChar, m)))
.Aggregate(MiniYaml.MergeLiberal)).NodesDict; Mod = FieldLoader.Load<Mod>(yaml["Metadata"]);
Mod.Id = mod;
// TODO: Use fieldloader // TODO: Use fieldloader
Folders = YamlList(yaml, "Folders"); Folders = YamlList(yaml, "Folders");

View File

@@ -21,7 +21,6 @@ namespace OpenRA.FileFormats
public string Description; public string Description;
public string Version; public string Version;
public string Author; public string Author;
public string Requires;
public static readonly Dictionary<string, Mod> AllMods = ValidateMods(Directory.GetDirectories("mods").Select(x => x.Substring(5)).ToArray()); public static readonly Dictionary<string, Mod> AllMods = ValidateMods(Directory.GetDirectories("mods").Select(x => x.Substring(5)).ToArray());
@@ -45,12 +44,5 @@ namespace OpenRA.FileFormats
} }
return ret; return ret;
} }
public string[] WithPrerequisites()
{
return Id.Iterate(m => AllMods[m].Requires)
.TakeWhile(m => m != null)
.ToArray();
}
} }
} }

View File

@@ -255,18 +255,6 @@ namespace OpenRA
} }
} }
public static Dictionary<String, Mod> 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; static Modifiers modifiers;
public static Modifiers GetModifierKeys() { return modifiers; } public static Modifiers GetModifierKeys() { return modifiers; }
internal static void HandleModifierKeys(Modifiers mods) { modifiers = mods; } internal static void HandleModifierKeys(Modifiers mods) { modifiers = mods; }
@@ -328,7 +316,8 @@ namespace OpenRA
Console.WriteLine("Available mods:"); Console.WriteLine("Available mods:");
foreach(var mod in Mod.AllMods) foreach(var mod in Mod.AllMods)
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version); 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) 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 // Clear static state if we have switched mods
LobbyInfoChanged = () => {}; LobbyInfoChanged = () => {};
@@ -353,17 +342,18 @@ namespace OpenRA
if (orderManager != null) if (orderManager != null)
orderManager.Dispose(); orderManager.Dispose();
// Discard any invalid mods, set RA as default // Fall back to RA if the mod doesn't exist
var mm = mods.Where( m => Mod.AllMods.ContainsKey( m ) ).ToArray(); if (!Mod.AllMods.ContainsKey(mod))
if (mm.Length == 0) mm = new[] { "ra" }; mod = "ra";
Console.WriteLine("Loading mods: {0}", mm.JoinWith(","));
Settings.Game.Mods = mm; Console.WriteLine("Loading mod: {0}", mod);
Settings.Game.Mod = mod;
Sound.StopMusic(); Sound.StopMusic();
Sound.StopVideo(); Sound.StopVideo();
Sound.Initialize(); Sound.Initialize();
modData = new ModData(mm); modData = new ModData(mod);
Renderer.InitializeFonts(modData.Manifest); Renderer.InitializeFonts(modData.Manifest);
modData.InitializeLoaders(); modData.InitializeLoaders();
@@ -480,8 +470,7 @@ namespace OpenRA
public static void CreateServer(ServerSettings settings) public static void CreateServer(ServerSettings settings)
{ {
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, modData);
Settings.Game.Mods, settings, modData);
} }
public static int CreateLocalServer(string map) public static int CreateLocalServer(string map)
@@ -494,8 +483,7 @@ namespace OpenRA
AllowPortForward = false AllowPortForward = false
}; };
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, modData);
Settings.Game.Mods, settings, modData);
return server.Port; return server.Port;
} }
@@ -509,16 +497,19 @@ namespace OpenRA
{ {
try try
{ {
var mod = CurrentMods.First().Value.Id; var mod = Game.modData.Manifest.Mod;
var dirPath = "{1}maps{0}{2}".F(Path.DirectorySeparatorChar, Platform.SupportDir, mod); var dirPath = new [] { Platform.SupportDir, "maps", mod.Id }.Aggregate(Path.Combine);
if(!Directory.Exists(dirPath)) if (!Directory.Exists(dirPath))
Directory.CreateDirectory(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)); Console.Write("Trying to download map to {0} ... ".F(mapPath));
WebClient webClient = new WebClient(); WebClient webClient = new WebClient();
webClient.DownloadFile(Game.Settings.Game.MapRepository + mapHash, mapPath); webClient.DownloadFile(Game.Settings.Game.MapRepository + mapHash, mapPath);
Game.modData.AvailableMaps.Add(mapHash, new Map(mapPath)); Game.modData.AvailableMaps.Add(mapHash, new Map(mapPath));
Console.WriteLine("done"); Console.WriteLine("done");
return true; return true;
} }
catch (WebException e) catch (WebException e)

View File

@@ -124,7 +124,7 @@ namespace OpenRA.GameRules
public class GameSettings public class GameSettings
{ {
public string[] Mods = { "ra" }; public string Mod = "ra";
public bool ShowShellmap = true; public bool ShowShellmap = true;

View File

@@ -50,13 +50,13 @@ namespace OpenRA
return dirsWithMaps.Concat(Directory.GetFiles(dir, "*.oramap")); return dirsWithMaps.Concat(Directory.GetFiles(dir, "*.oramap"));
} }
public ModData(params string[] mods) public ModData(string mod)
{ {
Languages = new string[0]; Languages = new string[0];
Manifest = new Manifest(mods); Manifest = new Manifest(mod);
ObjectCreator = new ObjectCreator(Manifest); ObjectCreator = new ObjectCreator(Manifest);
LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(Manifest.LoadScreen.Value); LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(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(); LoadScreen.Display();
WidgetLoader = new WidgetLoader(this); WidgetLoader = new WidgetLoader(this);
@@ -149,9 +149,7 @@ namespace OpenRA
Dictionary<string, Map> FindMaps() Dictionary<string, Map> FindMaps()
{ {
var paths = Manifest.MapFolders.SelectMany(f => FindMapsIn(f)); var paths = Manifest.MapFolders.SelectMany(f => FindMapsIn(f));
var ret = new Dictionary<string, Map>(); var ret = new Dictionary<string, Map>();
foreach (var path in paths) foreach (var path in paths)
{ {
try try
@@ -177,7 +175,7 @@ namespace OpenRA
public interface ILoadScreen public interface ILoadScreen
{ {
void Init(Dictionary<string, string> info); void Init(Manifest m, Dictionary<string, string> info);
void Display(); void Display();
void StartGame(); void StartGame();
} }

View File

@@ -21,49 +21,47 @@ namespace OpenRA.Network
public readonly int State = 0; public readonly int State = 0;
public readonly int Players = 0; public readonly int Players = 0;
public readonly string Map = null; 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 readonly int TTL = 0;
public Dictionary<string, string> 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() public bool CanJoin()
{ {
//"waiting for players" // "waiting for players"
if (State != 1) if (State != 1)
return false; return false;
// Mods won't match if there are a different number if (!CompatibleVersion())
if (Game.CurrentMods.Count != Mods.Count())
return false; return false;
// Don't have the map locally // Don't have the map locally
if (!Game.modData.AvailableMaps.ContainsKey(Map)) // TODO: We allow joining, then drop on game start if the map isn't available
if (!Game.Settings.Game.AllowDownloading) if (!Game.modData.AvailableMaps.ContainsKey(Map) && !Game.Settings.Game.AllowDownloading)
return false; return false;
return CompatibleVersion(); return true;
} }
public bool CompatibleVersion() public bool CompatibleVersion()
{ {
return UsefulMods.All(m => Game.CurrentMods.ContainsKey(m.Key) // Invalid game listing - we require one entry of id@version
&& AreVersionsCompatible(m.Value, Game.CurrentMods[m.Key].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;
} }
} }
} }

View File

@@ -16,7 +16,8 @@ namespace OpenRA.Network
{ {
public class HandshakeRequest public class HandshakeRequest
{ {
public string[] Mods; public string Mod;
public string Version;
public string Map; public string Map;
public string Serialize() public string Serialize()
@@ -36,7 +37,8 @@ namespace OpenRA.Network
public class HandshakeResponse public class HandshakeResponse
{ {
public string[] Mods; public string Mod;
public string Version;
public string Password; public string Password;
[FieldLoader.Ignore] public Session.Client Client; [FieldLoader.Ignore] public Session.Client Client;
@@ -44,7 +46,7 @@ namespace OpenRA.Network
{ {
var data = new List<MiniYamlNode>(); var data = new List<MiniYamlNode>();
data.Add( new MiniYamlNode( "Handshake", null, 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))); data.Add(new MiniYamlNode("Client", FieldSaver.Save(Client)));
return data.WriteToString(); return data.WriteToString();

View File

@@ -20,7 +20,7 @@ namespace OpenRA.Network
readonly SyncReport syncReport; readonly SyncReport syncReport;
readonly FrameData frameData = new FrameData(); 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 Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } }
public World world; public World world;

View File

@@ -34,7 +34,8 @@ namespace OpenRA.Network
void StartSavingReplay(byte[] initialContent) void StartSavingReplay(byte[] initialContent)
{ {
var filename = chooseFilename(); 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)) if (!Directory.Exists(dir))
Directory.CreateDirectory(dir); Directory.CreateDirectory(dir);

View File

@@ -28,7 +28,7 @@ namespace OpenRA.Network
{ {
try try
{ {
var session = new Session(Game.Settings.Game.Mods); var session = new Session();
var ys = MiniYaml.FromString(data); var ys = MiniYaml.FromString(data);
foreach (var y in ys) foreach (var y in ys)
@@ -123,10 +123,9 @@ namespace OpenRA.Network
{ {
public string ServerName; public string ServerName;
public string Map; 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 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 AllowCheats = false;
public bool Dedicated; public bool Dedicated;
public string Difficulty; public string Difficulty;
@@ -140,12 +139,6 @@ namespace OpenRA.Network
public string GameUid; public string GameUid;
} }
public Session(string[] mods)
{
this.GlobalSettings.Mods = mods.ToArray();
this.GlobalSettings.GameUid = Guid.NewGuid().ToString();
}
public string Serialize() public string Serialize()
{ {
var clientData = new List<MiniYamlNode>(); var clientData = new List<MiniYamlNode>();

View File

@@ -178,8 +178,8 @@ namespace OpenRA.Network
{ {
if (r.Frame == frame) 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); 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", "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", "Sync for net frame {0} -------------", r.Frame);
Log.Write("sync", "SharedRandom: {0} (#{1})", r.SyncedRandom, r.TotalCount); Log.Write("sync", "SharedRandom: {0} (#{1})", r.SyncedRandom, r.TotalCount);

View File

@@ -119,9 +119,14 @@ namespace OpenRA.Network
case "HandshakeRequest": case "HandshakeRequest":
{ {
var request = HandshakeRequest.Deserialize(order.TargetString); 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 // 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.modData.AvailableMaps.ContainsKey(request.Map))
{ {
if (Game.Settings.Game.AllowDownloading) if (Game.Settings.Game.AllowDownloading)
@@ -144,7 +149,8 @@ namespace OpenRA.Network
var response = new HandshakeResponse() var response = new HandshakeResponse()
{ {
Client = info, Client = info,
Mods = localMods, Mod = mod.Id,
Version = mod.Version,
Password = orderManager.Password Password = orderManager.Password
}; };

View File

@@ -95,7 +95,7 @@ namespace OpenRA.Server
t.GameEnded(this); 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"); Log.AddChannel("server", "server.log");
@@ -117,7 +117,7 @@ namespace OpenRA.Server
foreach (var trait in modData.Manifest.ServerTraits) foreach (var trait in modData.Manifest.ServerTraits)
serverTraits.Add(modData.ObjectCreator.CreateObject<ServerTrait>(trait)); serverTraits.Add(modData.ObjectCreator.CreateObject<ServerTrait>(trait));
LobbyInfo = new Session(mods); LobbyInfo = new Session();
LobbyInfo.GlobalSettings.RandomSeed = randomSeed; LobbyInfo.GlobalSettings.RandomSeed = randomSeed;
LobbyInfo.GlobalSettings.Map = settings.Map; LobbyInfo.GlobalSettings.Map = settings.Map;
LobbyInfo.GlobalSettings.ServerName = settings.Name; LobbyInfo.GlobalSettings.ServerName = settings.Name;
@@ -127,10 +127,7 @@ namespace OpenRA.Server
foreach (var t in serverTraits.WithInterface<INotifyServerStart>()) foreach (var t in serverTraits.WithInterface<INotifyServerStart>())
t.ServerStarted(this); t.ServerStarted(this);
Log.Write("server", "Initial mods: "); Log.Write("server", "Initial mod: {0}", ModData.Manifest.Mod.Id);
foreach (var m in LobbyInfo.GlobalSettings.Mods)
Log.Write("server", "- {0}", m);
Log.Write("server", "Initial map: {0}", LobbyInfo.GlobalSettings.Map); Log.Write("server", "Initial map: {0}", LobbyInfo.GlobalSettings.Map);
new Thread(_ => new Thread(_ =>
@@ -226,9 +223,11 @@ namespace OpenRA.Server
// Dispatch a handshake order // Dispatch a handshake order
var request = new HandshakeRequest() var request = new HandshakeRequest()
{ {
Map = LobbyInfo.GlobalSettings.Map, Mod = ModData.Manifest.Mod.Id,
Mods = LobbyInfo.GlobalSettings.Mods.Select(m => "{0}@{1}".F(m, Mod.AllMods[m].Version)).ToArray() Version = ModData.Manifest.Mod.Version,
Map = LobbyInfo.GlobalSettings.Map
}; };
DispatchOrdersToClient(newConn, 0, 0, new ServerOrder("HandshakeRequest", request.Serialize()).Serialize()); DispatchOrdersToClient(newConn, 0, 0, new ServerOrder("HandshakeRequest", request.Serialize()).Serialize());
} }
catch (Exception e) catch (Exception e)
@@ -282,13 +281,7 @@ namespace OpenRA.Server
else else
client.Color = HSLColor.FromRGB(255, 255, 255); client.Color = HSLColor.FromRGB(255, 255, 255);
// Check that the client has compatible mods if (ModData.Manifest.Mod.Id != handshake.Mod)
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)
{ {
Log.Write("server", "Rejected connection from {0}; mods do not match.", Log.Write("server", "Rejected connection from {0}; mods do not match.",
newConn.socket.RemoteEndPoint); newConn.socket.RemoteEndPoint);
@@ -298,10 +291,7 @@ namespace OpenRA.Server
return; return;
} }
var validVersion = mods.Select(m => Pair.New(m.Split('@')[0], m.Split('@')[1])).All( if (ModData.Manifest.Mod.Version != handshake.Version && !LobbyInfo.GlobalSettings.AllowVersionMismatch)
kv => kv.Second == Game.CurrentMods[kv.First].Version);
if (!validVersion && !LobbyInfo.GlobalSettings.AllowVersionMismatch)
{ {
Log.Write("server", "Rejected connection from {0}; Not running the same version.", Log.Write("server", "Rejected connection from {0}; Not running the same version.",
newConn.socket.RemoteEndPoint); newConn.socket.RemoteEndPoint);
@@ -338,13 +328,14 @@ namespace OpenRA.Server
// Send initial ping // Send initial ping
SendOrderTo(newConn, "Ping", Environment.TickCount.ToString()); 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); 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) + SendMessage("{0} is running an unversioned development build, ".F(client.Name) +
"and may desynchronize the game state if they have incompatible rules."); "and may desynchronize the game state if they have incompatible rules.");

View File

@@ -47,9 +47,9 @@ namespace OpenRA
{ {
Log.AddChannel("exception", "exception.log"); 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); Log.Write("exception", "{0} Mod at Version {1}", mod.Title, mod.Version);
} }

View File

@@ -225,24 +225,6 @@ namespace OpenRA.Widgets
public static Action Once( Action a ) { return () => { if (a != null) { a(); a = null; } }; } 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) public static string ChooseInitialMap(string map)
{ {
var availableMaps = Game.modData.AvailableMaps; var availableMaps = Game.modData.AvailableMaps;

View File

@@ -70,9 +70,7 @@ namespace OpenRA.Irc
var command = split[0]; var command = split[0];
if (command.EqualsIC("VERSION")) if (command.EqualsIC("VERSION"))
{ {
var mod = Game.CurrentMods.Values.FirstOrDefault(); var mod = Game.modData.Manifest.Mod;
if (mod == null)
return;
Instance.CtcpRespond(l.Prefix.Nickname, command, "{0}: {1}".F(mod.Title, mod.Version)); Instance.CtcpRespond(l.Prefix.Nickname, command, "{0}: {1}".F(mod.Title, mod.Version));
} }
}; };

View File

@@ -35,7 +35,7 @@ namespace OpenRA.Lint
try try
{ {
var options = args.Where(a => a.StartsWith("-")); 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"); 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)); FieldLoader.UnknownFieldAction = (s, f) => EmitError("FieldLoader: Missing field `{0}` on `{1}`".F(s, f.Name));
AppDomain.CurrentDomain.AssemblyResolve += FileSystem.ResolveAssembly; AppDomain.CurrentDomain.AssemblyResolve += FileSystem.ResolveAssembly;
Game.modData = new ModData(mods); Game.modData = new ModData(mod);
Rules.LoadRules(Game.modData.Manifest, new Map()); Rules.LoadRules(Game.modData.Manifest, new Map());
foreach (var customPassType in Game.modData.ObjectCreator foreach (var customPassType in Game.modData.ObjectCreator

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Mods.Cnc
Renderer r; Renderer r;
NullInputHandler nih = new NullInputHandler(); NullInputHandler nih = new NullInputHandler();
public void Init(Dictionary<string, string> info) public void Init(Manifest m, Dictionary<string, string> info)
{ {
loadInfo = info; loadInfo = info;
@@ -63,6 +63,8 @@ namespace OpenRA.Mods.Cnc
brightBlock = new Sprite(s, new Rectangle(320, 0, 16, 35), TextureChannel.Alpha); brightBlock = new Sprite(s, new Rectangle(320, 0, 16, 35), TextureChannel.Alpha);
dimBlock = new Sprite(s, new Rectangle(336, 0, 16, 35), TextureChannel.Alpha); dimBlock = new Sprite(s, new Rectangle(336, 0, 16, 35), TextureChannel.Alpha);
versionText = m.Mod.Version;
} }
bool setup; bool setup;
@@ -93,7 +95,6 @@ namespace OpenRA.Mods.Cnc
loadingPos = new float2((bounds.Width - loadingFont.Measure(loadingText).X) / 2, barY); loadingPos = new float2((bounds.Width - loadingFont.Measure(loadingText).X) / 2, barY);
versionFont = r.Fonts["Regular"]; versionFont = r.Fonts["Regular"];
versionText = WidgetUtils.ActiveModVersion();
var versionSize = versionFont.Measure(versionText); var versionSize = versionFont.Measure(versionText);
versionPos = new float2(bounds.Width - 107 - versionSize.X / 2, 115 - versionSize.Y / 2); versionPos = new float2(bounds.Width - 107 - versionSize.X / 2, 115 - versionSize.Y / 2);

View File

@@ -30,7 +30,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
var mpe = world.WorldActor.Trait<CncMenuPaletteEffect>(); var mpe = world.WorldActor.Trait<CncMenuPaletteEffect>();
mpe.Fade(CncMenuPaletteEffect.EffectType.Desaturated); mpe.Fade(CncMenuPaletteEffect.EffectType.Desaturated);
menu.Get<LabelWidget>("VERSION_LABEL").GetText = WidgetUtils.ActiveModVersion; menu.Get<LabelWidget>("VERSION_LABEL").Text = Game.modData.Manifest.Mod.Version;
bool hideButtons = false; bool hideButtons = false;
menu.Get("MENU_BUTTONS").IsVisible = () => !hideButtons; menu.Get("MENU_BUTTONS").IsVisible = () => !hideButtons;

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
{ {
try 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, "scores.mix"));
FileSystem.Mount(Path.Combine(path, "transit.mix")); FileSystem.Mount(Path.Combine(path, "transit.mix"));

View File

@@ -28,7 +28,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
.Fade(CncMenuPaletteEffect.EffectType.Desaturated); .Fade(CncMenuPaletteEffect.EffectType.Desaturated);
rootMenu = widget.Get("MENU_BACKGROUND"); rootMenu = widget.Get("MENU_BACKGROUND");
rootMenu.Get<LabelWidget>("VERSION_LABEL").GetText = WidgetUtils.ActiveModVersion; rootMenu.Get<LabelWidget>("VERSION_LABEL").Text = Game.modData.Manifest.Mod.Version;
// Menu buttons // Menu buttons
var mainMenu = widget.Get("MAIN_MENU"); var mainMenu = widget.Get("MAIN_MENU");

View File

@@ -28,7 +28,7 @@ namespace OpenRA.Mods.RA
Sprite stripe, logo; Sprite stripe, logo;
string[] messages; string[] messages;
public void Init(Dictionary<string, string> info) public void Init(Manifest m, Dictionary<string, string> info)
{ {
this.info = info; this.info = info;

View File

@@ -9,13 +9,14 @@
#endregion #endregion
using System.Collections.Generic; using System.Collections.Generic;
using OpenRA.FileFormats;
using OpenRA.Widgets; using OpenRA.Widgets;
namespace OpenRA.Mods.RA namespace OpenRA.Mods.RA
{ {
public class NullLoadScreen : ILoadScreen public class NullLoadScreen : ILoadScreen
{ {
public void Init(Dictionary<string, string> info) {} public void Init(Manifest m, Dictionary<string, string> info) {}
public void Display() public void Display()
{ {

View File

@@ -50,6 +50,7 @@ namespace OpenRA.Mods.RA.Server
lastPing = Environment.TickCount; lastPing = Environment.TickCount;
isBusy = true; isBusy = true;
var mod = server.ModData.Manifest.Mod;
Action a = () => Action a = () =>
{ {
try try
@@ -60,14 +61,13 @@ namespace OpenRA.Mods.RA.Server
using (var wc = new WebClient()) using (var wc = new WebClient())
{ {
wc.Proxy = null; wc.Proxy = null;
wc.DownloadData(
wc.DownloadData(
server.Settings.MasterServer + url.F( server.Settings.MasterServer + url.F(
server.Settings.ExternalPort, Uri.EscapeUriString(server.Settings.Name), server.Settings.ExternalPort, Uri.EscapeUriString(server.Settings.Name),
(int)server.State, (int)server.State,
server.LobbyInfo.Clients.Where(c1 => c1.Bot == null).Count(), server.LobbyInfo.Clients.Where(c1 => c1.Bot == null).Count(),
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.LobbyInfo.GlobalSettings.Map,
server.Map.PlayerCount)); server.Map.PlayerCount));

View File

@@ -99,14 +99,16 @@ namespace OpenRA.Mods.RA.Widgets.Logic
template = panel.Get<ScrollItemWidget>("ASSET_TEMPLATE"); template = panel.Get<ScrollItemWidget>("ASSET_TEMPLATE");
PopulateAssetList(); 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<ButtonWidget>("EXPORT_BUTTON").OnClick = () => panel.Get<ButtonWidget>("EXPORT_BUTTON").OnClick = () =>
{ {
var ExtractGameFiles = new string[][] var ExtractGameFiles = new string[][]
{ {
new string[] {"--extract", WidgetUtils.ActiveModId(), palette, "--userdir"}, new string[] {"--extract", modID, palette, "--userdir"},
new string[] {"--extract", WidgetUtils.ActiveModId(), "{0}.shp".F(spriteImage.Image), "--userdir"}, new string[] {"--extract", modID, "{0}.shp".F(spriteImage.Image), "--userdir"},
}; };
var ExportToPng = new string[][] var ExportToPng = new string[][]
@@ -131,11 +133,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var ExtractGameFilesList = new List<string[]>(); var ExtractGameFilesList = new List<string[]>();
var ExportToPngList = new List<string[]>(); var ExportToPngList = new List<string[]>();
ExtractGameFilesList.Add(new string[] { "--extract", WidgetUtils.ActiveModId(), palette, "--userdir"} ); ExtractGameFilesList.Add(new string[] { "--extract", modID, palette, "--userdir"} );
foreach (var shp in AvailableShps) 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 } ); ExportToPngList.Add(new string[] { "--png", Platform.SupportDir+shp, Platform.SupportDir+palette } );
Console.WriteLine(Platform.SupportDir+shp); Console.WriteLine(Platform.SupportDir+shp);
} }
@@ -148,7 +150,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
{ {
{ "ExtractGameFiles", ExtractGameFiles }, { "ExtractGameFiles", ExtractGameFiles },
{ "ExportToPng", ExportToPng }, { "ExportToPng", ExportToPng },
{ "ImportFromPng", ImportFromPng} { "ImportFromPng", ImportFromPng }
}; };
Ui.OpenWindow("CONVERT_ASSETS_PANEL", args); Ui.OpenWindow("CONVERT_ASSETS_PANEL", args);

View File

@@ -51,7 +51,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
// Save the package to a temp file // Save the package to a temp file
var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 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<DownloadProgressChangedEventArgs> onDownloadProgress = i => Action<DownloadProgressChangedEventArgs> onDownloadProgress = i =>
{ {

View File

@@ -27,7 +27,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var versionLabel = Ui.Root.GetOrNull<LabelWidget>("VERSION_LABEL"); var versionLabel = Ui.Root.GetOrNull<LabelWidget>("VERSION_LABEL");
if (versionLabel != null) if (versionLabel != null)
versionLabel.GetText = WidgetUtils.ActiveModVersion; versionLabel.Text = Game.modData.Manifest.Mod.Version;
widget.Get<ButtonWidget>("MAINMENU_BUTTON_JOIN").OnClick = () => OpenGamePanel("JOINSERVER_BG"); widget.Get<ButtonWidget>("MAINMENU_BUTTON_JOIN").OnClick = () => OpenGamePanel("JOINSERVER_BG");
widget.Get<ButtonWidget>("MAINMENU_BUTTON_CREATE").OnClick = () => OpenGamePanel("CREATESERVER_BG"); widget.Get<ButtonWidget>("MAINMENU_BUTTON_CREATE").OnClick = () => OpenGamePanel("CREATESERVER_BG");

View File

@@ -26,10 +26,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var modList = panel.Get<ScrollPanelWidget>("MOD_LIST"); var modList = panel.Get<ScrollPanelWidget>("MOD_LIST");
var loadButton = panel.Get<ButtonWidget>("LOAD_BUTTON"); var loadButton = panel.Get<ButtonWidget>("LOAD_BUTTON");
loadButton.OnClick = () => LoadMod(currentMod.Id, onSwitch); 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<ButtonWidget>("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; panel.Get<ButtonWidget>("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
currentMod = Mod.AllMods[Game.modData.Manifest.Mods[0]]; currentMod = Game.modData.Manifest.Mod;
// Mod list // Mod list
var modTemplate = modList.Get<ScrollItemWidget>("MOD_TEMPLATE"); var modTemplate = modList.Get<ScrollItemWidget>("MOD_TEMPLATE");
@@ -47,13 +47,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic
void LoadMod(string mod, Action onSwitch) void LoadMod(string mod, Action onSwitch)
{ {
var mods = Mod.AllMods[mod].WithPrerequisites();
Game.RunAfterTick(() => Game.RunAfterTick(() =>
{ {
Ui.CloseWindow(); Ui.CloseWindow();
onSwitch(); onSwitch();
Game.InitializeWithMods(mods); Game.InitializeWithMod(mod);
}); });
} }
} }

View File

@@ -28,11 +28,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic
panel.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; panel.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
var rl = panel.Get<ScrollPanelWidget>("REPLAY_LIST"); var rl = panel.Get<ScrollPanelWidget>("REPLAY_LIST");
var dir = new[] { Platform.SupportDir, "Replays", WidgetUtils.ActiveModId(), WidgetUtils.ActiveModVersion() }.Aggregate(Path.Combine);
var template = panel.Get<ScrollItemWidget>("REPLAY_TEMPLATE"); var template = panel.Get<ScrollItemWidget>("REPLAY_TEMPLATE");
var mod = Game.modData.Manifest.Mod;
var dir = new[] { Platform.SupportDir, "Replays", mod.Id, mod.Version }.Aggregate(Path.Combine);
rl.RemoveChildren(); rl.RemoveChildren();
if (Directory.Exists(dir)) if (Directory.Exists(dir))
{ {

View File

@@ -149,17 +149,15 @@ namespace OpenRA.Mods.RA.Widgets.Logic
return (game == null) ? null : Game.modData.FindMapByUid(game.Map); return (game == null) ? null : Game.modData.FindMapByUid(game.Map);
} }
static string GenerateModLabel(KeyValuePair<string,string> mod) public static string GenerateModLabel(GameServer s)
{ {
if (Mod.AllMods.ContainsKey(mod.Key)) Mod mod;
return "{0} ({1})".F(Mod.AllMods[mod.Key].Title, mod.Value); 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 "Unknown mod: {0}".F(s.Mods);
{
return s.UsefulMods.Select(m => GenerateModLabel(m)).JoinWith("\n");
} }
bool Filtered(GameServer game) bool Filtered(GameServer game)
@@ -237,7 +235,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
ip.GetText = () => game.Address; ip.GetText = () => game.Address;
var version = item.Get<LabelWidget>("VERSION"); var version = item.Get<LabelWidget>("VERSION");
version.GetText = () => GenerateModsLabel(game); version.GetText = () => GenerateModLabel(game);
version.IsVisible = () => !game.CompatibleVersion(); version.IsVisible = () => !game.CompatibleVersion();
var location = item.Get<LabelWidget>("LOCATION"); var location = item.Get<LabelWidget>("LOCATION");

View File

@@ -176,12 +176,12 @@ namespace OpenRA.Utility
public static void ConvertTmpToPng(string[] args) public static void ConvertTmpToPng(string[] args)
{ {
var mods = args[1].Split(','); var mod = args[1];
var theater = args[2]; var theater = args[2];
var templateNames = args.Skip(3); var templateNames = args.Skip(3);
var shadowIndex = new int[] { 3, 4 }; var shadowIndex = new int[] { 3, 4 };
var manifest = new Manifest(mods); var manifest = new Manifest(mod);
FileSystem.LoadFromManifest(manifest); FileSystem.LoadFromManifest(manifest);
var tileset = manifest.TileSets.Select(a => new TileSet(a)) var tileset = manifest.TileSets.Select(a => new TileSet(a))
@@ -225,10 +225,10 @@ namespace OpenRA.Utility
public static void ExtractFiles(string[] args) public static void ExtractFiles(string[] args)
{ {
var mods = args[1].Split(','); var mod = args[1];
var files = args.Skip(2); var files = args.Skip(2);
var manifest = new Manifest(mods); var manifest = new Manifest(mod);
FileSystem.LoadFromManifest(manifest); FileSystem.LoadFromManifest(manifest);
foreach (var f in files) foreach (var f in files)

View File

@@ -9,7 +9,7 @@ AdvertiseOnline="False"
Map="ba403f6bcb4cae934335b78be42f714992b3a71a" Map="ba403f6bcb4cae934335b78be42f714992b3a71a"
while true; do 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.Name=$Name Server.ListenPort=$ListenPort Server.ExternalPort=$ExternalPort \
Server.AdvertiseOnline=$AdvertiseOnline Server.Map=$Map Server.AdvertiseOnline=$AdvertiseOnline Server.Map=$Map
done done