Refactored GRFFile Implementation

This commit is contained in:
miku-666
2022-08-09 02:47:58 +02:00
parent 0c9e98f92b
commit b0e840fa04
5 changed files with 193 additions and 216 deletions

View File

@@ -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<string, string> _parameters = new Dictionary<string, string>();
/// <summary>
/// Initializes a new GRFFile as a non-world grf file
/// </summary>
public GRFFile() : this(-1, false)
{}
/// <summary>
/// Contains all valid Parameter names
/// </summary>
public GRFFile(int crc, bool isWolrd)
{
Root = new GameRule("__ROOT__", null);
_crc = crc;
_isWorld = isWolrd;
}
public class GameRule
{
/// <summary> Contains all valid Parameter names </summary>
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<string, string> Parameters => _parameters;
public List<GRFTag> Tags { get; set; } = new List<GRFTag>();
public GRFTag(string name, GRFTag parent)
public GameRule Parent { get; } = null;
public Dictionary<string, string> Parameters { get; } = new Dictionary<string, string>();
public List<GameRule> SubRules { get; } = new List<GameRule>();
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);
/// <summary>
/// Adds a new tag to its child tags
/// </summary>
/// <summary>Adds a new gamerule</summary>
/// <param name="gameRuleName">Game rule to add</param>
/// <param name="validate">Wether to check the given game rule</param>
/// <returns>The Added GRFTag</returns>
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<string,string>[] parameters)
public GameRule AddRule(string gameRuleName, params KeyValuePair<string,string>[] 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<string, string>[] parameters)
=> _root.AddTag(gameRuleName, parameters);
public GRFFile(int crc, bool isWolrd)
{
_root = new GRFTag("__ROOT__", null);
_crc = crc;
_isWorld = isWolrd;
}
/// <summary>
/// Initializes a new GRFFile as a non-world grf file
/// </summary>
public GRFFile() : this(-1, false)
{
}
public void AddGameRules(IEnumerable<GameRule> 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<string, string>[] parameters)
=> Root.AddRule(gameRuleName, parameters);
}
}

View File

@@ -11,17 +11,18 @@ namespace PckStudio.Classes.IO.GRF
{
internal class GRFFileReader : StreamDataReader
{
internal List<string> TagNames;
internal GRFFile _file;
private IList<string> 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<string>(name_count);
StringLookUpTable = new List<string>(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<GRFFile.GRFTag> 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)
{

View File

@@ -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<string>();
PrepareLookUpTable(_grfFile.RootTag, LUT);
PrepareLookUpTable(_grfFile.Root, LUT);
}
private void write(Stream stream)
private void PrepareLookUpTable(GRFFile.GameRule tag, List<string> 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<string> 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<GRFFile.GRFTag> 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<string, string> 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);
}

View File

@@ -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<string, string> 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<string, string> 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)

View File

@@ -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<string, string>("seed", "0"),
new KeyValuePair<string, string>("baseSaveName", string.Empty),
new KeyValuePair<string, string>("flatworld", "false"),
new KeyValuePair<string, string>("texturePackId", packId.ToString())
);
grfFile.AddTag("LevelRules")
.AddTag("UpdatePlayer",
grfFile.AddRule("LevelRules")
.AddRule("UpdatePlayer",
new KeyValuePair<string, string>("yRot", "0"),
new KeyValuePair<string, string>("xRot", "0"),
new KeyValuePair<string, string>("spawnX", "0"),