diff --git a/OpenRA.FileFormats/Graphics/ShpReader.cs b/OpenRA.FileFormats/Graphics/ShpReader.cs index 2ccf5d76e1..0795b2696a 100644 --- a/OpenRA.FileFormats/Graphics/ShpReader.cs +++ b/OpenRA.FileFormats/Graphics/ShpReader.cs @@ -62,9 +62,9 @@ namespace OpenRA.FileFormats int recurseDepth = 0; - public ShpReader( Stream stream ) + public ShpReader(Stream stream) { - using( var reader = new BinaryReader( stream ) ) + using (var reader = new BinaryReader(stream)) { ImageCount = reader.ReadUInt16(); reader.ReadUInt16(); @@ -73,60 +73,60 @@ namespace OpenRA.FileFormats Height = reader.ReadUInt16(); reader.ReadUInt32(); - for( int i = 0 ; i < ImageCount ; i++ ) - headers.Add( new ImageHeader( reader ) ); + for (int i = 0 ; i < ImageCount ; i++) + headers.Add(new ImageHeader(reader)); - new ImageHeader( reader ); // end-of-file header - new ImageHeader( reader ); // all-zeroes header + new ImageHeader(reader); // end-of-file header + new ImageHeader(reader); // all-zeroes header var offsets = headers.ToDictionary(h => h.Offset, h =>h); - for( int i = 0 ; i < ImageCount ; i++ ) + for (int i = 0 ; i < ImageCount ; i++) { var h = headers[ i ]; - if( h.Format == Format.Format20 ) - h.RefImage = headers[ i - 1 ]; + if (h.Format == Format.Format20) + h.RefImage = headers[i - 1]; - else if( h.Format == Format.Format40 ) - if( !offsets.TryGetValue( h.RefOffset, out h.RefImage ) ) - throw new InvalidDataException( "Reference doesnt point to image data {0}->{1}".F(h.Offset, h.RefOffset) ); + else if (h.Format == Format.Format40) + if (!offsets.TryGetValue(h.RefOffset, out h.RefImage)) + throw new InvalidDataException("Reference doesnt point to image data {0}->{1}".F(h.Offset, h.RefOffset)); } - foreach( ImageHeader h in headers ) - Decompress( stream, h ); + foreach (ImageHeader h in headers) + Decompress(stream, h); } } - public ImageHeader this[ int index ] + public ImageHeader this[int index] { - get { return headers[ index ]; } + get { return headers[index]; } } - void Decompress( Stream stream, ImageHeader h ) + void Decompress(Stream stream, ImageHeader h) { - if( recurseDepth > ImageCount ) - throw new InvalidDataException( "Format20/40 headers contain infinite loop" ); + if (recurseDepth > ImageCount) + throw new InvalidDataException("Format20/40 headers contain infinite loop"); - switch( h.Format ) + switch(h.Format) { case Format.Format20: case Format.Format40: { - if( h.RefImage.Image == null ) + if (h.RefImage.Image == null) { ++recurseDepth; - Decompress( stream, h.RefImage ); + Decompress(stream, h.RefImage); --recurseDepth; } - h.Image = CopyImageData( h.RefImage.Image ); + h.Image = CopyImageData(h.RefImage.Image); Format40.DecodeInto(ReadCompressedData(stream, h), h.Image); break; } case Format.Format80: { - var imageBytes = new byte[ Width * Height ]; - Format80.DecodeInto( ReadCompressedData( stream, h ), imageBytes ); + var imageBytes = new byte[Width * Height]; + Format80.DecodeInto(ReadCompressedData(stream, h), imageBytes); h.Image = imageBytes; break; } @@ -135,11 +135,11 @@ namespace OpenRA.FileFormats } } - static byte[] ReadCompressedData( Stream stream, ImageHeader h ) + static byte[] ReadCompressedData(Stream stream, ImageHeader h) { stream.Position = h.Offset; - // Actually, far too big. There's no length field with the correct length though :( - var compressedLength = (int)( stream.Length - stream.Position ); + // TODO: Actually, far too big. There's no length field with the correct length though :( + var compressedLength = (int)(stream.Length - stream.Position); var compressedBytes = new byte[ compressedLength ]; stream.Read( compressedBytes, 0, compressedLength ); @@ -147,11 +147,11 @@ namespace OpenRA.FileFormats return compressedBytes; } - byte[] CopyImageData( byte[] baseImage ) + byte[] CopyImageData(byte[] baseImage) { - var imageData = new byte[ Width * Height ]; - for( int i = 0 ; i < Width * Height ; i++ ) - imageData[ i ] = baseImage[ i ]; + var imageData = new byte[Width * Height]; + for (int i = 0 ; i < Width * Height ; i++) + imageData[i] = baseImage[i]; return imageData; } diff --git a/OpenRA.FileFormats/Manifest.cs b/OpenRA.FileFormats/Manifest.cs index 1db3f1bbf1..989574cb62 100644 --- a/OpenRA.FileFormats/Manifest.cs +++ b/OpenRA.FileFormats/Manifest.cs @@ -9,6 +9,7 @@ #endregion using System.Collections.Generic; +using System.IO; using System.Linq; namespace OpenRA.FileFormats @@ -20,7 +21,8 @@ namespace OpenRA.FileFormats public readonly string[] Mods, Folders, Packages, Rules, ServerTraits, Sequences, Cursors, Chrome, Assemblies, ChromeLayout, - Weapons, Voices, Notifications, Music, Movies, TileSets, ChromeMetrics; + Weapons, Voices, Notifications, Music, Movies, TileSets, + ChromeMetrics, PackageContents; public readonly MiniYaml LoadScreen; public readonly Dictionary> Fonts; public readonly int TileSize = 24; @@ -29,7 +31,7 @@ namespace OpenRA.FileFormats { Mods = mods; var yaml = new MiniYaml(null, mods - .Select(m => MiniYaml.FromFile("mods/" + m + "/mod.yaml")) + .Select(m => MiniYaml.FromFile("mods{0}{1}{0}mod.yaml".F(Path.DirectorySeparatorChar, m))) .Aggregate(MiniYaml.MergeLiberal)).NodesDict; // TODO: Use fieldloader @@ -49,6 +51,7 @@ namespace OpenRA.FileFormats Movies = YamlList(yaml, "Movies"); TileSets = YamlList(yaml, "TileSets"); ChromeMetrics = YamlList(yaml, "ChromeMetrics"); + PackageContents = YamlList(yaml, "PackageContents"); LoadScreen = yaml["LoadScreen"]; Fonts = yaml["Fonts"].NodesDict.ToDictionary(x => x.Key, diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 3e8a69fa0f..28b59218cb 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -238,7 +238,7 @@ namespace OpenRA public static Dictionary CurrentMods { - get { return Mod.AllMods.Where( k => modData.Manifest.Mods.Contains( k.Key )).ToDictionary( k => k.Key, k => k.Value ); } + get { return Mod.AllMods.Where(k => modData.Manifest.Mods.Contains(k.Key)).ToDictionary(k => k.Key, k => k.Value); } } static Modifiers modifiers; diff --git a/OpenRA.Game/GameRules/Rules.cs b/OpenRA.Game/GameRules/Rules.cs index bf0d6fd4d5..2b6842ff01 100755 --- a/OpenRA.Game/GameRules/Rules.cs +++ b/OpenRA.Game/GameRules/Rules.cs @@ -25,6 +25,7 @@ namespace OpenRA public static Dictionary Music; public static Dictionary Movies; public static Dictionary TileSets; + public static Dictionary PackageContents; public static void LoadRules(Manifest m, Map map) { @@ -35,6 +36,7 @@ namespace OpenRA Notifications = LoadYamlRules(m.Notifications, map.Notifications, (k, _) => new SoundInfo(k.Value)); Music = LoadYamlRules(m.Music, new List(), (k, _) => new MusicInfo(k.Key, k.Value)); Movies = LoadYamlRules(m.Movies, new List(), (k, v) => k.Value.Value); + PackageContents = LoadYamlRules(m.PackageContents, new List(), (k, v) => k.Value.Value); TileSets = new Dictionary(); foreach (var file in m.TileSets) @@ -46,7 +48,7 @@ namespace OpenRA static Dictionary LoadYamlRules(string[] files, List dict, Func, T> f) { - var y = files.Select(a => MiniYaml.FromFile(a)).Aggregate(dict,MiniYaml.MergeLiberal); + var y = files.Select(a => MiniYaml.FromFile(a)).Aggregate(dict, MiniYaml.MergeLiberal); var yy = y.ToDictionary( x => x.Key, x => x.Value ); return y.ToDictionary(kv => kv.Key.ToLowerInvariant(), kv => f(kv, yy)); } diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index f5c298d4b7..51d9f11fb8 100755 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -29,14 +29,14 @@ namespace OpenRA public SheetBuilder SheetBuilder; public SpriteLoader SpriteLoader; - public ModData( params string[] mods ) + public ModData(params string[] mods) { - Manifest = new Manifest( mods ); - ObjectCreator = new ObjectCreator( Manifest ); + Manifest = new Manifest(mods); + ObjectCreator = new ObjectCreator(Manifest); LoadScreen = ObjectCreator.CreateObject(Manifest.LoadScreen.Value); LoadScreen.Init(Manifest.LoadScreen.NodesDict.ToDictionary(x => x.Key, x => x.Value.Value)); LoadScreen.Display(); - WidgetLoader = new WidgetLoader( this ); + WidgetLoader = new WidgetLoader(this); } public void LoadInitialAssets(bool enumMaps) diff --git a/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs index 60154c5f2d..cc6f67b072 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs @@ -9,6 +9,7 @@ #endregion using System; +using System.Collections.Generic; using System.IO; using System.Linq; using OpenRA.FileFormats; @@ -22,30 +23,27 @@ namespace OpenRA.Mods.RA.Widgets.Logic { Widget panel; - ShpImageWidget spriteImage; - TextFieldWidget filenameInput; - SliderWidget frameSlider; - ButtonWidget playButton; - ButtonWidget pauseButton; + static ShpImageWidget spriteImage; + static TextFieldWidget filenameInput; + static SliderWidget frameSlider; + static ButtonWidget playButton; + static ButtonWidget pauseButton; + static ScrollPanelWidget assetList; + static ScrollItemWidget template; + + public enum SourceType { Folders, Packages } + public static SourceType AssetSource = SourceType.Folders; [ObjectCreator.UseCtor] public AssetBrowserLogic(Widget widget, Action onExit, World world) { panel = widget; - var assetList = panel.Get("ASSET_LIST"); - var template = panel.Get("ASSET_TEMPLATE"); - - assetList.RemoveChildren(); - foreach (var folder in FileSystem.FolderPaths) - { - if (Directory.Exists(folder)) - { - var shps = Directory.GetFiles(folder, "*.shp"); - foreach (var shp in shps) - AddAsset(assetList, shp, template); - } - } + var sourceDropdown = panel.Get("SOURCE_SELECTOR"); + sourceDropdown.OnMouseDown = _ => ShowSourceDropdown(sourceDropdown); + sourceDropdown.GetText = () => AssetSource == SourceType.Folders ? "Folders" + : AssetSource == SourceType.Packages ? "Packages" : "None"; + sourceDropdown.Disabled = !Rules.PackageContents.Keys.Any(); spriteImage = panel.Get("SPRITE"); @@ -93,10 +91,14 @@ namespace OpenRA.Mods.RA.Widgets.Logic LoadAsset(filenameInput.Text); }; + assetList = panel.Get("ASSET_LIST"); + template = panel.Get("ASSET_TEMPLATE"); + PopulateAssetList(); + panel.Get("CLOSE_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; } - void AddAsset(ScrollPanelWidget list, string filepath, ScrollItemWidget template) + static void AddAsset(ScrollPanelWidget list, string filepath, ScrollItemWidget template) { var sprite = Path.GetFileNameWithoutExtension(filepath); @@ -108,7 +110,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic list.AddChild(item); } - bool LoadAsset(string filename) + static bool LoadAsset(string filename) { if (filename == null) return false; @@ -120,5 +122,48 @@ namespace OpenRA.Mods.RA.Widgets.Logic frameSlider.Ticks = spriteImage.FrameCount+1; return true; } + + public static bool ShowSourceDropdown(DropDownButtonWidget dropdown) + { + var options = new Dictionary() + { + { "Folders", SourceType.Folders }, + { "Packages", SourceType.Packages }, + }; + + Func setupItem = (o, itemTemplate) => + { + var item = ScrollItemWidget.Setup(itemTemplate, + () => AssetSource == options[o], + () => { AssetSource = options[o]; PopulateAssetList(); }); + item.Get("LABEL").GetText = () => o; + return item; + }; + + dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 500, options.Keys, setupItem); + return true; + } + + public static void PopulateAssetList() + { + assetList.RemoveChildren(); + + if (AssetSource == SourceType.Folders) + { + foreach (var folder in FileSystem.FolderPaths) + { + if (Directory.Exists(folder)) + { + var shps = Directory.GetFiles(folder, "*.shp"); + foreach (var shp in shps) + AddAsset(assetList, shp, template); + } + } + } + + if (AssetSource == SourceType.Packages) + foreach (var hiddenFile in Rules.PackageContents.Keys) + AddAsset(assetList, hiddenFile, template); + } } } diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index a306881e58..9b6a4fa3fb 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -29,6 +29,8 @@ Packages: ~scores2.mix ~transit.mix +PackageContents: + Rules: mods/cnc/rules/defaults.yaml mods/cnc/rules/system.yaml diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index de3390ecfb..62442e1ebd 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -21,6 +21,8 @@ Packages: ~main.mix conquer.mix +PackageContents: + Rules: mods/d2k/rules/system.yaml mods/d2k/rules/defaults.yaml diff --git a/mods/ra/chrome/assetbrowser.yaml b/mods/ra/chrome/assetbrowser.yaml index cd07b7bc53..2e3243059c 100644 --- a/mods/ra/chrome/assetbrowser.yaml +++ b/mods/ra/chrome/assetbrowser.yaml @@ -83,6 +83,7 @@ Background@ASSETBROWSER_BG: Height:25 Text:Change Palette Font:Bold + Disabled: yes Button@IMPORT_BUTTON: X:PARENT_RIGHT - 200 Y:PARENT_BOTTOM - 270 @@ -90,6 +91,7 @@ Background@ASSETBROWSER_BG: Height:25 Text:Import from PNG Font:Bold + Disabled: yes Button@EXTRACT_BUTTON: X:PARENT_RIGHT - 200 Y:PARENT_BOTTOM - 235 @@ -97,6 +99,7 @@ Background@ASSETBROWSER_BG: Height:25 Text:Extract to Folder Font:Bold + Disabled: yes Button@EXPORT_BUTTON: X:PARENT_RIGHT - 200 Y:PARENT_BOTTOM - 200 @@ -104,6 +107,7 @@ Background@ASSETBROWSER_BG: Height:25 Text:Export as PNG Font:Bold + Disabled: yes Button@CLOSE_BUTTON: X:PARENT_RIGHT - 200 Y:PARENT_BOTTOM - 115 diff --git a/mods/ra/mix/conquer.yaml b/mods/ra/mix/conquer.yaml new file mode 100644 index 0000000000..91f479af5c --- /dev/null +++ b/mods/ra/mix/conquer.yaml @@ -0,0 +1,227 @@ +# conquer.mix filename list for the game asset browser +#appear1.aud: +#beepy6.aud: +#briefing.aud: +#clock1.aud: +#country1.aud: +#country4.aud: +#keystrok.aud: +#mapwipe2.aud: +#mapwipe5.aud: +#scold1.aud: +#sfx4.aud: +#toney10.aud: +#toney4.aud: +#toney7.aud: +#type.fnt: +#alibackh.pcx: +#sovback.pcx: +120mm.shp: +1tnk.shp:light tank +2tnk.shp:medium tank +3tnk.shp:heavy tank +4tnk.shp:mammoth tank +50cal.shp: +afld.shp: +afldmake.shp: +agun.shp: +agunmake.shp: +apc.shp: +apwr.shp: +apwramke.shp: +armor.shp: +art-exp1.shp: +arty.shp: +atek.shp: +atekmake.shp: +atomicdn.shp: +atomicup.shp: +atomsfx.shp: +badr.shp: +bar3bhr.shp: +bar3blue.shp: +bar3red.shp: +bar3rhr.shp: +barb.shp: +barl.shp: +barr.shp: +barrmake.shp: +bio.shp: +biomake.shp: +bomb.shp: +bomblet.shp: +brik.shp: +brl3.shp: +burn-l.shp: +burn-m.shp: +burn-s.shp: +ca.shp: +chronbox.shp: +countrya.shp: +countrye.shp: +credsa.shp: +credsahr.shp: +credsu.shp: +credsuhr.shp: +cycl.shp: +dd.shp: +deviator.shp: +dog.shp: +dogbullt.shp: +dollar.shp: +dome.shp: +domemake.shp: +dragon.shp: +earth.shp: +ebtn-dn.shp: +electdog.shp: +empulse.shp: +fact.shp: +factmake.shp: +fb1.shp: +fb2.shp: +fball1.shp: +fcom.shp: +fenc.shp: +fire1.shp: +fire2.shp: +fire3.shp: +fire4.shp: +fix.shp: +fixmake.shp: +flagfly.shp: +flak.shp: +flmspt.shp: +fpls.shp: +fpower.shp: +frag1.shp: +ftnk.shp: +ftur.shp: +fturmake.shp: +gap.shp: +gapmake.shp: +gpsbox.shp: +gun.shp: +gunfire.shp: +gunmake.shp: +h2o_exp1.shp: +h2o_exp2.shp: +h2o_exp3.shp: +harv.shp: +heli.shp: +hind.shp: +hisc1-hr.shp: +hisc2-hr.shp: +hiscore1.shp: +hiscore2.shp: +hosp.shp: +hospmake.shp: +hpad.shp: +hpadmake.shp: +invulbox.shp: +invun.shp: +iron.shp: +ironmake.shp: +jeep.shp: +kenn.shp: +kennmake.shp: +litning.shp: +lrotor.shp: +lst.shp: +mcv.shp: +mgg.shp: +mgun.shp: +mhq.shp: +mig.shp: +mine.shp: +minigun.shp: +minp.shp: +minpmake.shp: +minv.shp: +minvmake.shp: +miss.shp: +missile.shp: +missile2.shp: +mlrs.shp: +mnly.shp: +mrj.shp: +napalm1.shp: +napalm2.shp: +napalm3.shp: +orca.shp: +parabomb.shp: +parabox.shp: +parach.shp: +patriot.shp: +pbox.shp: +pboxmake.shp: +pdox.shp: +pdoxmake.shp: +piff.shp: +piffpiff.shp: +powr.shp: +powrmake.shp: +pt.shp: +pumpmake.shp: +radarfrm.shp: +rapid.shp: +rrotor.shp: +sam.shp: +samfire.shp: +sammake.shp: +sbag.shp: +scrate.shp: +select.shp: +repair.shp: +shadow.shp: +silo.shp: +silomake.shp: +smig.shp: +smoke_m.shp: +smokey.shp: +smokland.shp: +sonarbox.shp: +speed.shp: +spen.shp: +spenmake.shp: +sputdoor.shp: +sputnik.shp: +ss.shp: +ssam.shp: +stealth2.shp: +stek.shp: +stekmake.shp: +stnk.shp: +syrd.shp: +syrdmake.shp: +tent.shp: +tentmake.shp: +time.shp: +timehr.shp: +tquake.shp: +tran.shp: +truk.shp: +tsla.shp: +tslamake.shp: +turr.shp: +twinkle1.shp: +twinkle2.shp: +twinkle3.shp: +u2.shp: +v19.shp: +v2.shp: +v2rl.shp: +veh-hit1.shp: +veh-hit2.shp: +wake.shp: +wcrate.shp: +weap.shp: +weap2.shp: +weapmake.shp: +wood.shp: +wwcrate.shp: +yak.shp: +#trans.icn: +#ali-tran.wsa: +#mltiplyr.wsa: +#sov-tran.wsa: \ No newline at end of file diff --git a/mods/ra/mix/hires.yaml b/mods/ra/mix/hires.yaml new file mode 100644 index 0000000000..61bf1bcb3e --- /dev/null +++ b/mods/ra/mix/hires.yaml @@ -0,0 +1,135 @@ +# hires.mix filename list for the game asset browser +1tnkicon.shp: +2tnkicon.shp: +3tnkicon.shp: +4tnkicon.shp: +afldicon.shp: +agunicon.shp: +apcicon.shp: +apwricon.shp: +artyicon.shp: +atekicon.shp: +atomicon.shp: +badricon.shp: +barricon.shp: +brikicon.shp: +btn-dn.shp: +btn-pl.shp: +btn-st.shp: +btn-up.shp: +c1.shp: +c2.shp: +caicon.shp: +camicon.shp: +chan.shp: +clock.shp: +dd-bkgnd.shp: +dd-botm.shp: +dd-crnr.shp: +dd-edge.shp: +dd-left.shp: +dd-right.shp: +dd-top.shp: +ddicon.shp: +delphi.shp: +dogicon.shp: +domeicon.shp: +domficon.shp: +e1.shp: +e1icon.shp: +e2.shp: +e2icon.shp: +e3.shp: +e3icon.shp: +e4.shp: +e4icon.shp: +e5.shp: +e6.shp: +e6icon.shp: +e7.shp: +e7icon.shp: +einstein.shp: +facficon.shp: +facticon.shp: +fencicon.shp: +fixicon.shp: +fturicon.shp: +gapicon.shp: +gnrl.shp: +gpssicon.shp: +gunicon.shp: +harvicon.shp: +hboxicon.shp: +heliicon.shp: +hindicon.shp: +hpadicon.shp: +infxicon.shp: +ironicon.shp: +jeepicon.shp: +kennicon.shp: +lsticon.shp: +map.shp: +mcvicon.shp: +medi.shp: +mediicon.shp: +mggicon.shp: +migicon.shp: +mnlyicon.shp: +mrjicon.shp: +msloicon.shp: +natoradr.shp: +nradrfrm.shp: +pbmbicon.shp: +pboxicon.shp: +pdoxicon.shp: +pinficon.shp: +pips.shp: +power.shp: +powerbar.shp: +powricon.shp: +procicon.shp: +pticon.shp: +pulse.shp: +repair.shp: +samicon.shp: +sbagicon.shp: +sell.shp: +side1na.shp: +side1us.shp: +side2na.shp: +side2us.shp: +side3na.shp: +side3us.shp: +#sidebar.shp:will crash +siloicon.shp: +smigicon.shp: +sonricon.shp: +speficon.shp: +spenicon.shp: +spy.shp: +spyicon.shp: +ssicon.shp: +stekicon.shp: +strip.shp: +stripdn.shp: +stripna.shp: +stripup.shp: +stripus.shp: +syrdicon.shp: +syrficon.shp: +tabs.shp: +tenticon.shp: +thf.shp: +thficon.shp: +tranicon.shp: +trukicon.shp: +tslaicon.shp: +u2icon.shp: +uradrfrm.shp: +ussrradr.shp: +v2rlicon.shp: +warpicon.shp: +weaficon.shp: +weapicon.shp: +yakicon.shp: +#mouse.shp:Dune II format \ No newline at end of file diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index f7137f7323..64df95e7cf 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -29,6 +29,10 @@ Packages: ~movies1.mix ~movies2.mix +PackageContents: + mods/ra/mix/conquer.yaml + mods/ra/mix/hires.yaml + Rules: mods/ra/rules/defaults.yaml mods/ra/rules/system.yaml