diff --git a/OpenRA.Server/Server.cs b/OpenRA.Server/Server.cs index 4c2f7e89d7..a722ce8e43 100644 --- a/OpenRA.Server/Server.cs +++ b/OpenRA.Server/Server.cs @@ -347,8 +347,36 @@ namespace OpenRA.Server SyncLobbyInfo(); return true; }}, - { "addmod", + { "addpkg", s => + { + if (GameStarted) + { + DispatchOrdersToClient(conn, 0, + new ServerOrder( conn.PlayerIndex, "Chat", + "You can't change packages after the game has started" ).Serialize() ); + } + + Console.WriteLine("** Added package: `{0}`", s); + try + { + lobbyInfo.GlobalSettings.Packages = + lobbyInfo.GlobalSettings.Packages.Concat( new string[] { + MakePackageString(s)}).ToArray(); + SyncLobbyInfo(); + return true; + } + catch + { + Console.WriteLine("That went horribly wrong."); + DispatchOrdersToClient(conn, 0, + new ServerOrder( conn.PlayerIndex, "Chat", + "Adding the package failed." ).Serialize() ); + return true; + } + }}, + { "addmod", + s => { if (GameStarted) { @@ -360,9 +388,8 @@ namespace OpenRA.Server Console.WriteLine("** Added mod: `{0}`", s); try { - lobbyInfo.GlobalSettings.Packages = - lobbyInfo.GlobalSettings.Packages.Concat( new string[] { - MakePackageString(s)}).ToArray(); + lobbyInfo.GlobalSettings.Mods = + lobbyInfo.GlobalSettings.Mods.Concat( new[] { s } ).ToArray(); SyncLobbyInfo(); return true; } diff --git a/OpenRa.FileFormats/Session.cs b/OpenRa.FileFormats/Session.cs index 1d0ddd45bc..4bbf324ea1 100644 --- a/OpenRa.FileFormats/Session.cs +++ b/OpenRa.FileFormats/Session.cs @@ -31,7 +31,32 @@ namespace OpenRa.FileFormats { public string Map = "scm12ea.ini"; public string[] Packages = {}; // filename:sha1 pairs. + public string[] Mods = { "ra" }; // mod names public int OrderLatency = 3; } } + + public class Manifest + { + public readonly string[] Packages = { }; + public readonly string[] LegacyRules = { }; + public readonly string[] Rules = { }; + public readonly string[] Sequences = { }; + public readonly string[] Assemblies = { }; + + public Manifest(string[] mods) + { + var yaml = mods + .Select(m => MiniYaml.FromFile("mods/" + m + "/mod.yaml")) + .Aggregate(MiniYaml.Merge); + + Packages = YamlList(yaml, "Packages"); + LegacyRules = YamlList(yaml, "LegacyRules"); + Rules = YamlList(yaml, "Rules"); + Sequences = YamlList(yaml, "Sequences"); + Assemblies = YamlList(yaml, "Assemblies"); + } + + static string[] YamlList(Dictionary ys, string key) { return ys[key].Nodes.Keys.ToArray(); } + } } diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index 3aee607451..b4c66de043 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -32,7 +32,7 @@ namespace OpenRa if (name != null) { - Info = Rules.ActorInfo[name.ToLowerInvariant()]; + Info = Rules.Info[name.ToLowerInvariant()]; Health = this.GetMaxHP(); foreach (var trait in Info.Traits.WithInterface()) diff --git a/OpenRa.Game/Chrome.cs b/OpenRa.Game/Chrome.cs index 2196a243ae..5fff31f78f 100644 --- a/OpenRa.Game/Chrome.cs +++ b/OpenRa.Game/Chrome.cs @@ -111,7 +111,7 @@ namespace OpenRa panelSprites = Graphics.Util.MakeArray(8, n => ChromeProvider.GetImage(renderer, "panel", n.ToString())); - tabSprites = Rules.ActorInfo.Values + tabSprites = Rules.Info.Values .Where(u => u.Traits.Contains()) .ToDictionary( u => u.Name, @@ -122,7 +122,7 @@ namespace OpenRa u => u.Key, u => SpriteSheetBuilder.LoadAllSprites(u.Value.Image)[0]); - var groups = Rules.ActorInfo.Values.Select( x => x.Category ).Distinct().Where( g => g != null ).ToList(); + var groups = Rules.Info.Values.Select( x => x.Category ).Distinct().Where( g => g != null ).ToList(); tabImageNames = groups.Select( (g, i) => Pair.New(g, @@ -758,7 +758,7 @@ namespace OpenRa void StartProduction( string item ) { - var unit = Rules.ActorInfo[item]; + var unit = Rules.Info[item]; Sound.Play(unit.Traits.Contains() ? "abldgin1.aud" : "train1.aud"); Game.controller.AddOrder(Order.StartProduction(Game.LocalPlayer, item)); } @@ -766,7 +766,7 @@ namespace OpenRa void HandleBuildPalette(string item, bool isLmb) { var player = Game.LocalPlayer; - var unit = Rules.ActorInfo[item]; + var unit = Rules.Info[item]; var queue = player.PlayerActor.traits.Get(); var producing = queue.AllItems(unit.Category).FirstOrDefault( a => a.Item == item ); @@ -851,7 +851,7 @@ namespace OpenRa rgbaRenderer.DrawSprite(tooltipSprite, p, PaletteType.Chrome); rgbaRenderer.Flush(); - var info = Rules.ActorInfo[unit]; + var info = Rules.Info[unit]; var buildable = info.Traits.Get(); renderer.DrawText2(buildable.Description, p.ToInt2() + new int2(5,5), Color.White); @@ -888,7 +888,7 @@ namespace OpenRa if( a[ 0 ] == '@' ) return "any " + a.Substring( 1 ); else - return Rules.ActorInfo[ a.ToLowerInvariant() ].Traits.Get().Description; + return Rules.Info[ a.ToLowerInvariant() ].Traits.Get().Description; } void DrawSupportPowers() diff --git a/OpenRa.Game/Game.cs b/OpenRa.Game/Game.cs index 5abe4ea83b..d5ecd68496 100644 --- a/OpenRa.Game/Game.cs +++ b/OpenRa.Game/Game.cs @@ -50,13 +50,19 @@ namespace OpenRa public static void ChangeMap(string mapName) { + var manifest = new Manifest(LobbyInfo.GlobalSettings.Mods); + chat.AddLine(Color.White, "Debug", "Map change {0} -> {1}".F(Game.mapName, mapName)); Game.changePending = false; Game.mapName = mapName; SheetBuilder.Initialize(renderer); SpriteSheetBuilder.Initialize(); FileSystem.UnmountTemporaryPackages(); - Rules.LoadRules(mapName, usingAftermath); + + foreach (var pkg in manifest.Packages) + FileSystem.MountTemporary(new Package(pkg)); + + Rules.LoadRules(mapName, manifest); world = null; // trying to access the old world will NRE, rather than silently doing it wrong. world = new World(); @@ -75,11 +81,7 @@ namespace OpenRa players[i] = new Player(i, LobbyInfo.Clients.FirstOrDefault(a => a.Index == i)); } - var sequenceFiles = usingAftermath - ? new[] { "sequences.xml", "sequences-aftermath.xml" } - : new[] { "sequences.xml" }; - - SequenceProvider.Initialize(sequenceFiles); + SequenceProvider.Initialize(manifest.Sequences); viewport = new Viewport(clientSize, Game.world.Map.Offset, Game.world.Map.Offset + Game.world.Map.Size, renderer); skipMakeAnims = true; diff --git a/OpenRa.Game/GameRules/ActorInfo.cs b/OpenRa.Game/GameRules/ActorInfo.cs index 369b6d0bf5..3ceff0ef88 100644 --- a/OpenRa.Game/GameRules/ActorInfo.cs +++ b/OpenRa.Game/GameRules/ActorInfo.cs @@ -52,13 +52,19 @@ namespace OpenRa.GameRules return node; } - // todo: use mod metadata to do this - static Pair[] ModAssemblies = - { - Pair.New( typeof(ITraitInfo).Assembly, typeof(ITraitInfo).Namespace ), - Pair.New( Assembly.LoadFile(Path.GetFullPath(@"mods\ra\OpenRa.Mods.RA.dll")), "OpenRa.Mods.RA" ), - Pair.New( Assembly.LoadFile(Path.GetFullPath(@"mods\aftermath\OpenRa.Mods.Aftermath.dll")), "OpenRa.Mods.Aftermath" ) - }; + static Pair[] ModAssemblies; + public static void LoadModAssemblies(Manifest m) + { + var asms = new List>(); + + // all the core stuff is in this assembly + asms.Add(Pair.New(typeof(ITraitInfo).Assembly, typeof(ITraitInfo).Namespace)); + + // add the mods + foreach (var a in m.Assemblies) + asms.Add(Pair.New(Assembly.LoadFile(Path.GetFullPath(a)), Path.GetFileNameWithoutExtension(a))); + ModAssemblies = asms.ToArray(); + } static ITraitInfo LoadTraitInfo(string traitName, MiniYaml my) { diff --git a/OpenRa.Game/GameRules/Rules.cs b/OpenRa.Game/GameRules/Rules.cs index a682475293..bd4f52c8c1 100755 --- a/OpenRa.Game/GameRules/Rules.cs +++ b/OpenRa.Game/GameRules/Rules.cs @@ -20,28 +20,20 @@ namespace OpenRa public static AftermathInfo Aftermath; public static TechTree TechTree; - public static Dictionary ActorInfo; + public static Dictionary Info; - public static void LoadRules(string mapFileName, bool useAftermath) + public static void LoadRules(string map, Manifest m) { - if (useAftermath) - AllRules = new IniFile( - FileSystem.Open(mapFileName), - FileSystem.Open("aftermathUnits.ini"), - FileSystem.Open("units.ini"), - FileSystem.Open("aftrmath.ini"), - FileSystem.Open("rules.ini")); - else - AllRules = new IniFile( - FileSystem.Open(mapFileName), - FileSystem.Open("units.ini"), - FileSystem.Open("rules.ini")); + var legacyRules = m.LegacyRules.Reverse().ToList(); + legacyRules.Insert(0, map); + AllRules = new IniFile(legacyRules.Select(a => FileSystem.Open(a)).ToArray()); General = new GeneralInfo(); FieldLoader.Load(General, AllRules.GetSection("General")); + // dirty hack. all of this needs to either die or go to traitinfos Aftermath = new AftermathInfo(); - if (useAftermath) + if (AllRules.GetSection("Aftermath", true).Count() > 0) FieldLoader.Load(Aftermath, AllRules.GetSection("Aftermath")); LoadCategories( @@ -62,15 +54,12 @@ namespace OpenRa SupportPowerInfo = new InfoLoader( Pair.New>("SupportPower", _ => new SupportPowerInfo())); - var yamlRules = MiniYaml.Merge( MiniYaml.FromFile( "ra.yaml" ), MiniYaml.FromFile( "defaults.yaml" ) ); - if( useAftermath ) - yamlRules = MiniYaml.Merge( MiniYaml.FromFile( "aftermath.yaml" ), yamlRules ); + var yamlRules = m.Rules.Reverse().Select(a => MiniYaml.FromFile(a)).Aggregate(MiniYaml.Merge); - yamlRules = MiniYaml.Merge( MiniYaml.FromFile( "[mod]Separate buildqueue for defense.yaml" ), yamlRules ); - - ActorInfo = new Dictionary(); + ActorInfo.LoadModAssemblies(m); + Info = new Dictionary(); foreach( var kv in yamlRules ) - ActorInfo.Add(kv.Key.ToLowerInvariant(), new ActorInfo(kv.Key.ToLowerInvariant(), kv.Value, yamlRules)); + Info.Add(kv.Key.ToLowerInvariant(), new ActorInfo(kv.Key.ToLowerInvariant(), kv.Value, yamlRules)); TechTree = new TechTree(); } diff --git a/OpenRa.Game/GameRules/TechTree.cs b/OpenRa.Game/GameRules/TechTree.cs index 25c0d87af6..7abac04ac7 100755 --- a/OpenRa.Game/GameRules/TechTree.cs +++ b/OpenRa.Game/GameRules/TechTree.cs @@ -11,7 +11,7 @@ namespace OpenRa.GameRules public TechTree() { - foreach( var info in Rules.ActorInfo.Values ) + foreach( var info in Rules.Info.Values ) { var pi = info.Traits.GetOrDefault(); if (pi != null) @@ -62,7 +62,7 @@ namespace OpenRa.GameRules public IEnumerable AllBuildables(Player player, params string[] categories) { - return Rules.ActorInfo.Values + return Rules.Info.Values .Where( x => x.Name[ 0 ] != '^' ) .Where( x => categories.Contains( x.Category ) ) .Where( x => x.Traits.Contains() ); @@ -72,7 +72,7 @@ namespace OpenRa.GameRules { var builtAt = info.Traits.Get().BuiltAt; if( builtAt.Length != 0 ) - return builtAt.Select( x => Rules.ActorInfo[ x.ToLowerInvariant() ] ); + return builtAt.Select( x => Rules.Info[ x.ToLowerInvariant() ] ); else return producesIndex[ info.Category ]; } diff --git a/OpenRa.Game/Orders/PlaceBuildingOrderGenerator.cs b/OpenRa.Game/Orders/PlaceBuildingOrderGenerator.cs index d8f712fceb..9996b680f6 100644 --- a/OpenRa.Game/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRa.Game/Orders/PlaceBuildingOrderGenerator.cs @@ -8,7 +8,7 @@ namespace OpenRa.Orders { readonly Actor Producer; readonly string Building; - BuildingInfo BuildingInfo { get { return Rules.ActorInfo[ Building ].Traits.Get(); } } + BuildingInfo BuildingInfo { get { return Rules.Info[ Building ].Traits.Get(); } } public PlaceBuildingOrderGenerator(Actor producer, string name) { @@ -42,7 +42,7 @@ namespace OpenRa.Orders public void Tick() { - var producing = Producer.traits.Get().CurrentItem( Rules.ActorInfo[ Building ].Category ); + var producing = Producer.traits.Get().CurrentItem( Rules.Info[ Building ].Category ); if (producing == null || producing.Item != Building || producing.RemainingTime != 0) Game.controller.CancelInputMode(); } diff --git a/OpenRa.Game/Orders/UnitOrderGenerator.cs b/OpenRa.Game/Orders/UnitOrderGenerator.cs index 30599a7fe7..38c6b09009 100644 --- a/OpenRa.Game/Orders/UnitOrderGenerator.cs +++ b/OpenRa.Game/Orders/UnitOrderGenerator.cs @@ -68,7 +68,7 @@ namespace OpenRa.Orders else return Cursor.MoveBlocked; case "DeployMcv": - var factBuildingInfo = Rules.ActorInfo["fact"].Traits.Get(); + var factBuildingInfo = Rules.Info["fact"].Traits.Get(); if (Game.world.CanPlaceBuilding("fact", factBuildingInfo, a.Location - new int2(1, 1), a)) return Cursor.Deploy; else diff --git a/OpenRa.Game/SupportPower.cs b/OpenRa.Game/SupportPower.cs index b9f41c28f0..4544451316 100644 --- a/OpenRa.Game/SupportPower.cs +++ b/OpenRa.Game/SupportPower.cs @@ -50,7 +50,7 @@ namespace OpenRa var buildings = Rules.TechTree.GatherBuildings(Owner); var effectivePrereq = Info.Prerequisite .Select( a => a.ToLowerInvariant() ) - .Where( a => Rules.ActorInfo[a].Traits.Get().Owner.Contains( Owner.Race )); + .Where( a => Rules.Info[a].Traits.Get().Owner.Contains( Owner.Race )); IsAvailable = Info.TechLevel > -1 && effectivePrereq.Any() diff --git a/OpenRa.Game/Traits/McvDeploy.cs b/OpenRa.Game/Traits/McvDeploy.cs index d2b48a9042..9876423e14 100644 --- a/OpenRa.Game/Traits/McvDeploy.cs +++ b/OpenRa.Game/Traits/McvDeploy.cs @@ -24,7 +24,7 @@ namespace OpenRa.Traits { if( order.OrderString == "DeployMcv" ) { - var factBuildingInfo = Rules.ActorInfo[ "fact" ].Traits.Get(); + var factBuildingInfo = Rules.Info[ "fact" ].Traits.Get(); if( Game.world.CanPlaceBuilding( "fact", factBuildingInfo, self.Location - new int2( 1, 1 ), self ) ) { self.CancelActivity(); diff --git a/OpenRa.Game/Traits/PlaceBuilding.cs b/OpenRa.Game/Traits/PlaceBuilding.cs index 9f7c391a2a..114302dfde 100755 --- a/OpenRa.Game/Traits/PlaceBuilding.cs +++ b/OpenRa.Game/Traits/PlaceBuilding.cs @@ -17,7 +17,7 @@ namespace OpenRa.Traits Game.world.AddFrameEndTask( _ => { var queue = self.traits.Get(); - var unit = Rules.ActorInfo[ order.TargetString ]; + var unit = Rules.Info[ order.TargetString ]; var producing = queue.CurrentItem(unit.Category); if( producing == null || producing.Item != order.TargetString || producing.RemainingTime != 0 ) return; diff --git a/OpenRa.Game/Traits/ProductionQueue.cs b/OpenRa.Game/Traits/ProductionQueue.cs index cc8c575dda..bab9f9e0db 100755 --- a/OpenRa.Game/Traits/ProductionQueue.cs +++ b/OpenRa.Game/Traits/ProductionQueue.cs @@ -33,7 +33,7 @@ namespace OpenRa.Traits { case "StartProduction": { - var unit = Rules.ActorInfo[ order.TargetString ]; + var unit = Rules.Info[ order.TargetString ]; var ui = unit.Traits.Get(); var time = ui.Cost * Rules.General.BuildSpeed /* todo: country-specific build speed bonus */ @@ -65,7 +65,7 @@ namespace OpenRa.Traits } case "PauseProduction": { - var producing = CurrentItem( Rules.ActorInfo[ order.TargetString ].Category ); + var producing = CurrentItem( Rules.Info[ order.TargetString ].Category ); if( producing != null && producing.Item == order.TargetString ) producing.Paused = ( order.TargetLocation.X != 0 ); break; @@ -95,7 +95,7 @@ namespace OpenRa.Traits public void CancelProduction( string itemName ) { - var category = Rules.ActorInfo[itemName].Category; + var category = Rules.Info[itemName].Category; var queue = production[ category ]; if (queue.Count == 0) return; @@ -126,7 +126,7 @@ namespace OpenRa.Traits public void BuildUnit( string name ) { - var newUnitType = Rules.ActorInfo[ name ]; + var newUnitType = Rules.Info[ name ]; var producerTypes = Rules.TechTree.UnitBuiltAt( newUnitType ); Actor producer = null; diff --git a/aftermathUnits.ini b/mods/aftermath/aftermathUnits.ini old mode 100755 new mode 100644 similarity index 100% rename from aftermathUnits.ini rename to mods/aftermath/aftermathUnits.ini diff --git a/aftrmath.ini b/mods/aftermath/aftrmath.ini old mode 100755 new mode 100644 similarity index 100% rename from aftrmath.ini rename to mods/aftermath/aftrmath.ini diff --git a/mods/aftermath/mod.yaml b/mods/aftermath/mod.yaml new file mode 100644 index 0000000000..250ece7728 --- /dev/null +++ b/mods/aftermath/mod.yaml @@ -0,0 +1,17 @@ +# Red Alert: The Aftermath -- Package Manifest +# Requires classic RA mod + +Packages: + +LegacyRules: + mods/aftermath/aftrmath.ini: More or less original Aftermath rules file. + mods/aftermath/aftrmathUnits.ini: OpenRA patches + +Rules: + mods/aftermath/rules.yaml: OpenRA actorinfos + +Sequences: + mods/aftermath/sequences.xml: Additional aftermath + +Assemblies: + mods/aftermath/OpenRa.Mods.Aftermath.dll: Traits used \ No newline at end of file diff --git a/aftermath.yaml b/mods/aftermath/rules.yaml similarity index 100% rename from aftermath.yaml rename to mods/aftermath/rules.yaml diff --git a/sequences-aftermath.xml b/mods/aftermath/sequences.xml similarity index 100% rename from sequences-aftermath.xml rename to mods/aftermath/sequences.xml diff --git a/[mod]Separate buildqueue for defense.yaml b/mods/ra-ng/defense-queue.yaml old mode 100755 new mode 100644 similarity index 100% rename from [mod]Separate buildqueue for defense.yaml rename to mods/ra-ng/defense-queue.yaml diff --git a/mods/ra-ng/mod.yaml b/mods/ra-ng/mod.yaml new file mode 100644 index 0000000000..a216eb87e4 --- /dev/null +++ b/mods/ra-ng/mod.yaml @@ -0,0 +1,12 @@ +# Red Alert: Next Generation - Rules Manifest + +Packages: + +LegacyRules: + +Rules: + mods/ra-ng/defense-queue.yaml + +Sequences: + +Assemblies: diff --git a/campaignUnits.ini b/mods/ra/campaignUnits.ini old mode 100755 new mode 100644 similarity index 100% rename from campaignUnits.ini rename to mods/ra/campaignUnits.ini diff --git a/defaults.yaml b/mods/ra/defaults.yaml similarity index 100% rename from defaults.yaml rename to mods/ra/defaults.yaml diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml new file mode 100644 index 0000000000..1396a12edc --- /dev/null +++ b/mods/ra/mod.yaml @@ -0,0 +1,17 @@ +# Classic Red Alert Mod -- Package Manifest + +Packages: + +LegacyRules: + mods/ra/rules.ini: More or less original Red Alert rules file. + mods/ra/units.ini: OpenRA patches + +Rules: + mods/ra/defaults.yaml: Basic stuff + mods/ra/rules.yaml: OpenRA actorinfos + +Sequences: + mods/ra/sequences.xml: Original animation sequences + +Assemblies: + mods/ra/OpenRa.Mods.RA.dll: Traits used \ No newline at end of file diff --git a/rules.ini b/mods/ra/rules.ini similarity index 100% rename from rules.ini rename to mods/ra/rules.ini diff --git a/ra.yaml b/mods/ra/rules.yaml similarity index 100% rename from ra.yaml rename to mods/ra/rules.yaml diff --git a/sequences.xml b/mods/ra/sequences.xml similarity index 100% rename from sequences.xml rename to mods/ra/sequences.xml diff --git a/trees.ini b/mods/ra/trees.ini similarity index 100% rename from trees.ini rename to mods/ra/trees.ini diff --git a/units.ini b/mods/ra/units.ini similarity index 100% rename from units.ini rename to mods/ra/units.ini diff --git a/regen-yaml.cmd b/regen-yaml.cmd index 296337b094..99ae91c99e 100755 --- a/regen-yaml.cmd +++ b/regen-yaml.cmd @@ -1,2 +1,6 @@ -RulesConverter\bin\debug\RulesConverter.exe units.ini rules.ini trees.ini campaignUnits.ini ra.yaml -RulesConverter\bin\debug\RulesConverter.exe aftermathUnits.ini aftrmath.ini aftermath.yaml \ No newline at end of file +pushd mods\ra\ +..\..\RulesConverter\bin\debug\RulesConverter.exe units.ini rules.ini trees.ini campaignUnits.ini rules.yaml +popd +pushd mods\aftermath\ +RulesConverter\bin\debug\RulesConverter.exe aftermathUnits.ini aftrmath.ini rules.yaml +popd \ No newline at end of file