diff --git a/MinecraftUSkinEditor/Classes/FileTypes/GRFFile.cs b/MinecraftUSkinEditor/Classes/FileTypes/GRFFile.cs index 8404d2e0..12bd5381 100644 --- a/MinecraftUSkinEditor/Classes/FileTypes/GRFFile.cs +++ b/MinecraftUSkinEditor/Classes/FileTypes/GRFFile.cs @@ -7,8 +7,87 @@ namespace PckStudio.Classes.FileTypes { public class GRFFile { - private GRFTag _root = null; - public GRFTag RootTag => _root; + public static readonly string[] ValidGameRules = new string[] + { + "MapOptions", + "ApplySchematic", + "GenerateStructure", + "GenerateBox", + "PlaceBlock", + "PlaceContainer", + "PlaceSpawner", + "BiomeOverride", + "StartFeature", + "AddItem", + "AddEnchantment", + "WeighedTreasureItem", + "RandomItemSet", + "DistributeItems", + "WorldPosition", + "LevelRules", + "NamedArea", + "ActiveChunkArea", + "TargetArea", + "ScoreRing", + "ThermalArea", + "PlayerBoundsVolume", + "Killbox", + "BlockLayer", + "UseBlock", + "CollectItem", + "CompleteAll", + "UpdatePlayer", + "OnGameStartSpawnPositions", + "OnInitialiseWorld", + "SpawnPositionSet", + "PopulateContainer", + "DegradationSequence", + "RandomDissolveDegrade", + "DirectionalDegrade", + "GrantPermissions", + "AllowIn", + "LayerGeneration", + "LayerAsset", + "AnyCombinationOf", + "CombinationDefinition", + "Variations", + "BlockDef", + "LayerSize", + "UniformSize", + "RandomizeSize", + "LinearBlendSize", + "LayerShape", + "BasicShape", + "StarShape", + "PatchyShape", + "RingShape", + "SpiralShape", + "LayerFill", + "BasicLayerFill", + "CurvedLayerFill", + "WarpedLayerFill", + "LayerTheme", + "NullTheme", + "FilterTheme", + "ShaftsTheme", + "BasicPatchesTheme", + "BlockStackTheme", + "RainbowTheme", + "TerracottaTheme", + "FunctionPatchesTheme", + "SimplePatchesTheme", + "CarpetTrapTheme", + "MushroomBlockTheme", + "TextureTheme", + "SchematicTheme", + "BlockCollisionException", + "Powerup", + "Checkpoint", + "CustomBeacon", + "ActiveViewArea", + }; + + public readonly GameRule Root = null; public int Crc => _crc; public bool IsWorld => _isWorld; @@ -23,14 +102,22 @@ namespace PckStudio.Classes.FileTypes ZlibRleCrc = 3, } - public class GRFTag - { - private GRFTag _parent = null; - private Dictionary _parameters = new Dictionary(); + /// + /// Initializes a new GRFFile as a non-world grf file + /// + public GRFFile() : this(-1, false) + {} - /// - /// Contains all valid Parameter names - /// + public GRFFile(int crc, bool isWolrd) + { + Root = new GameRule("__ROOT__", null); + _crc = crc; + _isWorld = isWolrd; + } + + public class GameRule + { + /// Contains all valid Parameter names public static readonly string[] ValidParameters = new string[] { "plus_x", @@ -139,116 +226,35 @@ namespace PckStudio.Classes.FileTypes "beam_length", }; - public static readonly string[] ValidGameRules = new string[] - { - "MapOptions", - "ApplySchematic", - "GenerateStructure", - "GenerateBox", - "PlaceBlock", - "PlaceContainer", - "PlaceSpawner", - "BiomeOverride", - "StartFeature", - "AddItem", - "AddEnchantment", - "WeighedTreasureItem", - "RandomItemSet", - "DistributeItems", - "WorldPosition", - "LevelRules", - "NamedArea", - "ActiveChunkArea", - "TargetArea", - "ScoreRing", - "ThermalArea", - "PlayerBoundsVolume", - "Killbox", - "BlockLayer", - "UseBlock", - "CollectItem", - "CompleteAll", - "UpdatePlayer", - "OnGameStartSpawnPositions", - "OnInitialiseWorld", - "SpawnPositionSet", - "PopulateContainer", - "DegradationSequence", - "RandomDissolveDegrade", - "DirectionalDegrade", - "GrantPermissions", - "AllowIn", - "LayerGeneration", - "LayerAsset", - "AnyCombinationOf", - "CombinationDefinition", - "Variations", - "BlockDef", - "LayerSize", - "UniformSize", - "RandomizeSize", - "LinearBlendSize", - "LayerShape", - "BasicShape", - "StarShape", - "PatchyShape", - "RingShape", - "SpiralShape", - "LayerFill", - "BasicLayerFill", - "CurvedLayerFill", - "WarpedLayerFill", - "LayerTheme", - "NullTheme", - "FilterTheme", - "ShaftsTheme", - "BasicPatchesTheme", - "BlockStackTheme", - "RainbowTheme", - "TerracottaTheme", - "FunctionPatchesTheme", - "SimplePatchesTheme", - "CarpetTrapTheme", - "MushroomBlockTheme", - "TextureTheme", - "SchematicTheme", - "BlockCollisionException", - "Powerup", - "Checkpoint", - "CustomBeacon", - "ActiveViewArea", - }; - public string Name { get; set; } = string.Empty; - public GRFTag Parent => _parent; - public Dictionary Parameters => _parameters; - public List Tags { get; set; } = new List(); - public GRFTag(string name, GRFTag parent) + public GameRule Parent { get; } = null; + public Dictionary Parameters { get; } = new Dictionary(); + public List SubRules { get; } = new List(); + + public GameRule(string name, GameRule parent) { Name = name; - _parent = parent; + Parent = parent; } - public GRFTag AddTag(string gameRuleName) => AddTag(gameRuleName, false); + public GameRule AddRule(string gameRuleName) => AddRule(gameRuleName, false); - /// - /// Adds a new tag to its child tags - /// + /// Adds a new gamerule /// Game rule to add /// Wether to check the given game rule /// The Added GRFTag - public GRFTag AddTag(string gameRuleName, bool validate) + public GameRule AddRule(string gameRuleName, bool validate) { if (validate && !ValidGameRules.Contains(gameRuleName)) return null; - var tag = new GRFTag(gameRuleName, this); - Tags.Add(tag); + var tag = new GameRule(gameRuleName, this); + SubRules.Add(tag); return tag; } - public GRFTag AddTag(string gameRuleName, params KeyValuePair[] parameters) + public GameRule AddRule(string gameRuleName, params KeyValuePair[] parameters) { - var tag = AddTag(gameRuleName); // should never return null unless its called with the validate bool set to true + var tag = AddRule(gameRuleName); // should never return null unless its called with the validate bool set to true foreach(var param in parameters) { tag.Parameters[param.Key] = param.Value; @@ -257,29 +263,15 @@ namespace PckStudio.Classes.FileTypes } } - public GRFTag AddTag(string gameRuleName) - => AddTag(gameRuleName, false); - - public GRFTag AddTag(string gameRuleName, bool validate) - => _root.AddTag(gameRuleName, validate); - - public GRFTag AddTag(string gameRuleName, params KeyValuePair[] parameters) - => _root.AddTag(gameRuleName, parameters); - - public GRFFile(int crc, bool isWolrd) - { - _root = new GRFTag("__ROOT__", null); - _crc = crc; - _isWorld = isWolrd; - } - - /// - /// Initializes a new GRFFile as a non-world grf file - /// - public GRFFile() : this(-1, false) - { - } + public void AddGameRules(IEnumerable gameRules) => Root.SubRules.AddRange(gameRules); + + public GameRule AddRule(string gameRuleName) + => AddRule(gameRuleName, false); + public GameRule AddRule(string gameRuleName, bool validate) + => Root.AddRule(gameRuleName, validate); + public GameRule AddRule(string gameRuleName, params KeyValuePair[] parameters) + => Root.AddRule(gameRuleName, parameters); } } diff --git a/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileReader.cs b/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileReader.cs index 2f25702b..18ba7adf 100644 --- a/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileReader.cs +++ b/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileReader.cs @@ -11,17 +11,18 @@ namespace PckStudio.Classes.IO.GRF { internal class GRFFileReader : StreamDataReader { - internal List TagNames; - internal GRFFile _file; + private IList StringLookUpTable; + private GRFFile _file; + public static GRFFile Read(Stream stream) { - return new GRFFileReader().read(stream); + return new GRFFileReader().ReadFromStream(stream); } private GRFFileReader() : base(false) { } - private GRFFile read(Stream stream) + private GRFFile ReadFromStream(Stream stream) { stream = ReadHeader(stream); ReadBody(stream); @@ -82,8 +83,10 @@ namespace PckStudio.Classes.IO.GRF private void ReadBody(Stream stream) { - ReadTagNames(stream); - ReadRootTag(stream); + ReadStringLookUpTable(stream); + string Name = GetString(stream); + Console.WriteLine($"[{nameof(GRFFile)}] Root Name: {Name}"); + ReadGameRuleHierarchy(stream, _file.Root); } private Stream DecompressZLX(Stream compressedStream) @@ -98,44 +101,34 @@ namespace PckStudio.Classes.IO.GRF return outputstream; } - private void ReadTagNames(Stream stream) + private void ReadStringLookUpTable(Stream stream) { int name_count = ReadInt(stream); - TagNames = new List(name_count); + StringLookUpTable = new List(name_count); for (int i = 0; i < name_count; i++) { string s = ReadString(stream); - TagNames.Add(s); - //Console.WriteLine(s); + StringLookUpTable.Add(s); } } - private void ReadRootTag(Stream stream) - { - string Name = GetTagName(stream); - Console.WriteLine($"[GRFFileReader] root_name: {Name}"); - _file.RootTag.Tags = ReadGRFTreeHierarchy(stream, _file.RootTag).ToList(); - } - - private IEnumerable ReadGRFTreeHierarchy(Stream stream, GRFFile.GRFTag parent) + private void ReadGameRuleHierarchy(Stream stream, GRFFile.GameRule parent) { + _ = parent ?? throw new ArgumentNullException(nameof(parent)); int count = ReadInt(stream); for (int i = 0; i < count; i++) { - string parameterName = GetTagName(stream); - int parameterCount = ReadInt(stream); - var tag = new GRFFile.GRFTag(parameterName, parent); - for (int j = 0; j < parameterCount; j++) + (string Name, int Count) parameter = (GetString(stream), ReadInt(stream)); + var rule = parent.AddRule(parameter.Name); + for (int j = 0; j < parameter.Count; j++) { - tag.Parameters.Add(GetTagName(stream), ReadString(stream)); + rule.Parameters.Add(GetString(stream), ReadString(stream)); } - tag.Tags = ReadGRFTreeHierarchy(stream, tag).ToList(); - yield return tag; + ReadGameRuleHierarchy(stream, rule); } - yield break; } - private string GetTagName(Stream stream) => TagNames[ReadInt(stream)]; + private string GetString(Stream stream) => StringLookUpTable[ReadInt(stream)]; private string ReadString(Stream stream) { diff --git a/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileWriter.cs b/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileWriter.cs index 6d22a754..1e29e026 100644 --- a/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileWriter.cs +++ b/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileWriter.cs @@ -20,7 +20,7 @@ namespace PckStudio.Classes.IO.GRF public static void Write(in Stream stream, GRFFile grfFile, GRFFile.eCompressionType compressionType) { - new GRFFileWriter(grfFile, compressionType).write(stream); + new GRFFileWriter(grfFile, compressionType).WriteToStream(stream); } private GRFFileWriter(GRFFile grfFile, GRFFile.eCompressionType compressionType) : base(false) @@ -30,10 +30,18 @@ namespace PckStudio.Classes.IO.GRF throw new NotImplementedException("World grf saving is currently unsupported"); _grfFile = grfFile; LUT = new List(); - PrepareLookUpTable(_grfFile.RootTag, LUT); + PrepareLookUpTable(_grfFile.Root, LUT); } - private void write(Stream stream) + private void PrepareLookUpTable(GRFFile.GameRule tag, List LUT) + { + if (!LUT.Contains(tag.Name)) LUT.Add(tag.Name); + tag.SubRules.ForEach(tag => PrepareLookUpTable(tag, LUT)); + foreach (var param in tag.Parameters) + if (!LUT.Contains(param.Key)) LUT.Add(param.Key); + } + + private void WriteToStream(Stream stream) { WriteHeader(stream); using (var uncompressed_stream = new MemoryStream()) @@ -107,8 +115,9 @@ namespace PckStudio.Classes.IO.GRF private void WriteBody(Stream stream) { WriteTagLookUpTable(stream); - WriteRuleNameAndCount(stream, _grfFile.RootTag.Name, _grfFile.RootTag.Tags.Count); - WriteGameRules(stream, _grfFile.RootTag.Tags); + SetString(stream, _grfFile.Root.Name); + WriteInt(stream, _grfFile.Root.SubRules.Count); + WriteGameRuleHierarchy(stream, _grfFile.Root); } private void WriteTagLookUpTable(Stream stream) @@ -117,41 +126,25 @@ namespace PckStudio.Classes.IO.GRF LUT.ForEach( s => WriteString(stream, s) ); } - private void PrepareLookUpTable(GRFFile.GRFTag tag, List LUT) + private void WriteGameRuleHierarchy(Stream stream, GRFFile.GameRule rule) { - if (!LUT.Contains(tag.Name)) LUT.Add(tag.Name); - tag.Tags.ForEach( tag => PrepareLookUpTable(tag, LUT)); - foreach (var param in tag.Parameters) - if (!LUT.Contains(param.Key)) LUT.Add(param.Key); - } - - private void WriteGameRules(Stream stream, List tags) - { - foreach(var tag in tags) - { - WriteRuleNameAndCount(stream, tag.Name, tag.Parameters.Count); - foreach (var param in tag.Parameters) WriteParameter(stream, param); - WriteInt(stream, tag.Tags.Count); - WriteGameRules(stream, tag.Tags); - } - } - - private void WriteRuleNameAndCount(Stream stream, string name, int count) - { - WriteRuleName(stream, name); - WriteInt(stream, count); + SetString(stream, rule.Name); + WriteInt(stream, rule.Parameters.Count); + foreach (var param in rule.Parameters) WriteParameter(stream, param); + WriteInt(stream, rule.SubRules.Count); + rule.SubRules.ForEach(subrule => WriteGameRuleHierarchy(stream, subrule)); } private void WriteParameter(Stream stream, KeyValuePair param) { - WriteRuleName(stream, param.Key); + SetString(stream, param.Key); WriteString(stream, param.Value); } - private void WriteRuleName(Stream stream, string name) + private void SetString(Stream stream, string s) { - int i = LUT.IndexOf(name); - if (i == -1) throw new Exception("No index found for: " + name); + int i = LUT.IndexOf(s); + if (i == -1) throw new Exception(nameof(s)); WriteInt(stream, i); } diff --git a/MinecraftUSkinEditor/Forms/Editor/GRFEditor.cs b/MinecraftUSkinEditor/Forms/Editor/GRFEditor.cs index 12ab3162..230694fa 100644 --- a/MinecraftUSkinEditor/Forms/Editor/GRFEditor.cs +++ b/MinecraftUSkinEditor/Forms/Editor/GRFEditor.cs @@ -56,47 +56,46 @@ namespace PckStudio.Forms.Editor private void OnLoad(object sender, EventArgs e) { RPC.SetPresence("GRF Editor", "Editing a GRF File"); - loadGRFTreeView(GrfTreeView.Nodes, _file.RootTag); + loadGRFTreeView(GrfTreeView.Nodes, _file.Root); } private void OnExit(object sender, FormClosingEventArgs e) { - RPC.SetPresence("Sitting alone", "Program by PhoenixARC"); + RPC.SetPresence("An Open Source .PCK File Editor", "Program by PhoenixARC"); Dispose(); } - private void loadGRFTreeView(TreeNodeCollection root, GRFFile.GRFTag parentTag) + private void loadGRFTreeView(TreeNodeCollection root, GRFFile.GameRule parentRule) { - foreach (var tag in parentTag.Tags) + foreach (var rule in parentRule.SubRules) { - TreeNode node = new TreeNode(tag.Name); - node.Tag = tag; + TreeNode node = new TreeNode(rule.Name); + node.Tag = rule; root.Add(node); - loadGRFTreeView(node.Nodes, tag); + loadGRFTreeView(node.Nodes, rule); } } private void GrfTreeView_AfterSelect(object sender, TreeViewEventArgs e) { - if (e.Node == null || !(e.Node.Tag is GRFFile.GRFTag)) return; - ReloadParameterTreeView(); + if (e.Node is TreeNode t && t.Tag is GRFFile.GameRule) + ReloadParameterTreeView(); } private void ReloadParameterTreeView() { GrfParametersTreeView.Nodes.Clear(); - if (GrfTreeView.SelectedNode == null || !(GrfTreeView.SelectedNode.Tag is GRFFile.GRFTag)) return; - var grfTag = GrfTreeView.SelectedNode.Tag as GRFFile.GRFTag; - foreach (var Pair in grfTag.Parameters) + if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GameRule rule) + foreach (var param in rule.Parameters) { - GrfParametersTreeView.Nodes.Add(new TreeNode($"{Pair.Key}: {Pair.Value}") { Tag = Pair}); + GrfParametersTreeView.Nodes.Add(new TreeNode($"{param.Key}: {param.Value}") { Tag = param}); } } private void addDetailContextMenuItem_Click(object sender, EventArgs e) { - if (GrfTreeView.SelectedNode == null || !(GrfTreeView.SelectedNode.Tag is GRFFile.GRFTag)) return; - var grfTag = GrfTreeView.SelectedNode.Tag as GRFFile.GRFTag; + if (GrfTreeView.SelectedNode == null || !(GrfTreeView.SelectedNode.Tag is GRFFile.GameRule)) return; + var grfTag = GrfTreeView.SelectedNode.Tag as GRFFile.GameRule; AddParameter prompt = new AddParameter(); if (prompt.ShowDialog() == DialogResult.OK) { @@ -112,9 +111,9 @@ namespace PckStudio.Forms.Editor private void removeToolStripMenuItem_Click(object sender, EventArgs e) { - if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GRFTag grfTag && + if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GameRule rule && GrfParametersTreeView.SelectedNode is TreeNode paramNode && paramNode.Tag is KeyValuePair pair && - grfTag.Parameters.ContainsKey(pair.Key) && grfTag.Parameters.Remove(pair.Key)) + rule.Parameters.ContainsKey(pair.Key) && rule.Parameters.Remove(pair.Key)) { ReloadParameterTreeView(); return; @@ -130,13 +129,13 @@ namespace PckStudio.Forms.Editor private void GrfDetailsTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e) { - if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GRFTag grfTag && + if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GameRule rule && GrfParametersTreeView.SelectedNode is TreeNode paramNode && paramNode.Tag is KeyValuePair param) { AddParameter prompt = new AddParameter(param.Key, param.Value, false); if (prompt.ShowDialog() == DialogResult.OK) { - grfTag.Parameters[prompt.ParameterName] = prompt.ParameterValue; + rule.Parameters[prompt.ParameterName] = prompt.ParameterValue; ReloadParameterTreeView(); } } @@ -144,10 +143,10 @@ namespace PckStudio.Forms.Editor private void addGameRuleToolStripMenuItem_Click(object sender, EventArgs e) { - bool isValidNode = GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GRFTag; - GRFFile.GRFTag parentTag = isValidNode - ? GrfTreeView.SelectedNode.Tag as GRFFile.GRFTag - : _file.RootTag; + bool isValidNode = GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GameRule; + GRFFile.GameRule parentRule = isValidNode + ? GrfTreeView.SelectedNode.Tag as GRFFile.GameRule + : _file.Root; TreeNodeCollection root = isValidNode ? GrfTreeView.SelectedNode.Nodes @@ -156,12 +155,12 @@ namespace PckStudio.Forms.Editor using (RenamePrompt prompt = new RenamePrompt("")) { prompt.OKButton.Text = "Add"; - if (MessageBox.Show($"Add Game Rule to {parentTag.Name}", "Attention", + if (MessageBox.Show($"Add Game Rule to {parentRule.Name}", "Attention", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes && prompt.ShowDialog() == DialogResult.OK && !string.IsNullOrWhiteSpace(prompt.NewText)) { - var tag = parentTag.AddTag(prompt.NewText); + var tag = parentRule.AddRule(prompt.NewText); TreeNode node = new TreeNode(tag.Name); node.Tag = tag; root.Add(node); @@ -171,16 +170,16 @@ namespace PckStudio.Forms.Editor private void removeGameRuleToolStripMenuItem_Click(object sender, EventArgs e) { - if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GRFTag tag && removeTag(tag)) + if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GameRule tag && removeTag(tag)) t.Remove(); } - private bool removeTag(GRFFile.GRFTag tag) + private bool removeTag(GRFFile.GameRule rule) { - _ = tag.Parent ?? throw new ArgumentNullException(nameof(tag.Parent)); - foreach (var subTag in tag.Tags.ToList()) + _ = rule.Parent ?? throw new ArgumentNullException(nameof(rule.Parent)); + foreach (var subTag in rule.SubRules.ToList()) return removeTag(subTag); - return tag.Parent.Tags.Remove(tag); + return rule.Parent.SubRules.Remove(rule); } private void GrfTreeView_KeyDown(object sender, KeyEventArgs e) diff --git a/MinecraftUSkinEditor/MainForm.cs b/MinecraftUSkinEditor/MainForm.cs index 0fe6c2d6..25156a8a 100644 --- a/MinecraftUSkinEditor/MainForm.cs +++ b/MinecraftUSkinEditor/MainForm.cs @@ -904,14 +904,14 @@ namespace PckStudio InitializeTexturePack(packId, packVersion, packName, res); var gameRuleFile = new PCKFile.FileData("GameRules.grf", 7); var grfFile = new GRFFile(); - grfFile.AddTag("MapOptions", + grfFile.AddRule("MapOptions", new KeyValuePair("seed", "0"), new KeyValuePair("baseSaveName", string.Empty), new KeyValuePair("flatworld", "false"), new KeyValuePair("texturePackId", packId.ToString()) ); - grfFile.AddTag("LevelRules") - .AddTag("UpdatePlayer", + grfFile.AddRule("LevelRules") + .AddRule("UpdatePlayer", new KeyValuePair("yRot", "0"), new KeyValuePair("xRot", "0"), new KeyValuePair("spawnX", "0"),