Skip to content

Commit bcd6493

Browse files
authored
Merge pull request #4 from Carnagion/development
Merge v2.0.0 into stable
2 parents fd59f53 + cf265df commit bcd6493

28 files changed

+1026
-87
lines changed

.editorconfig

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
[*]
33
charset = utf-8
44
end_of_line = crlf
5-
trim_trailing_whitespace = true
5+
trim_trailing_whitespace = false
66
insert_final_newline = false
77
indent_style = space
88
indent_size = 4
@@ -48,12 +48,6 @@ dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d
4848
dotnet_naming_rule.unity_serialized_field_rule.severity = warning
4949
dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style
5050
dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols
51-
dotnet_naming_rule.unity_serialized_field_rule_1.import_to_resharper = True
52-
dotnet_naming_rule.unity_serialized_field_rule_1.resharper_description = Unity serialized field
53-
dotnet_naming_rule.unity_serialized_field_rule_1.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef
54-
dotnet_naming_rule.unity_serialized_field_rule_1.severity = warning
55-
dotnet_naming_rule.unity_serialized_field_rule_1.style = lower_camel_case_style
56-
dotnet_naming_rule.unity_serialized_field_rule_1.symbols = unity_serialized_field_symbols_1
5751
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
5852
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
5953
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
@@ -79,10 +73,6 @@ dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities
7973
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds =
8074
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field
8175
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance
82-
dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_accessibilities = *
83-
dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_kinds =
84-
dotnet_naming_symbols.unity_serialized_field_symbols_1.resharper_applicable_kinds = unity_serialised_field
85-
dotnet_naming_symbols.unity_serialized_field_symbols_1.resharper_required_modifiers = instance
8676
dotnet_separate_import_directive_groups = true
8777
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:none
8878
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
@@ -121,17 +111,12 @@ resharper_csharp_int_align_fix_in_adjacent = false
121111
resharper_csharp_keep_blank_lines_in_code = 1
122112
resharper_csharp_keep_blank_lines_in_declarations = 1
123113
resharper_csharp_max_line_length = 0
124-
resharper_csharp_naming_rule.constants = AaBb
125-
resharper_csharp_naming_rule.private_constants = aaBb
126-
resharper_csharp_naming_rule.private_static_fields = aaBb
127-
resharper_csharp_naming_rule.private_static_readonly = aaBb
128-
resharper_csharp_naming_rule.static_readonly = AaBb
129114
resharper_csharp_stick_comment = false
130115
resharper_csharp_wrap_arguments_style = chop_if_long
131116
resharper_csharp_wrap_before_binary_opsign = true
132117
resharper_csharp_wrap_extends_list_style = chop_if_long
118+
resharper_csharp_wrap_lines = false
133119
resharper_csharp_wrap_parameters_style = chop_if_long
134-
resharper_enforce_line_ending_style = true
135120
resharper_indent_nested_fixed_stmt = true
136121
resharper_indent_nested_foreach_stmt = true
137122
resharper_indent_nested_for_stmt = true

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2022 Carnagion
3+
Copyright (c) 2022 Indraneel Mahendrakumar
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Mod.cs renamed to Modding/Mod.cs

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
using JetBrains.Annotations;
99

10+
using Godot.Modding.Patching;
1011
using Godot.Serialization;
1112

1213
namespace Godot.Modding
@@ -24,9 +25,10 @@ public sealed record Mod
2425
public Mod(Metadata metadata)
2526
{
2627
this.Meta = metadata;
27-
this.Assemblies = this.LoadAssemblies();
28-
this.Data = this.LoadData();
2928
this.LoadResources();
29+
this.Data = this.LoadData();
30+
this.Patches = this.LoadPatches();
31+
this.Assemblies = this.LoadAssemblies();
3032
}
3133

3234
/// <summary>
@@ -46,9 +48,17 @@ public IEnumerable<Assembly> Assemblies
4648
}
4749

4850
/// <summary>
49-
/// The XML data of the <see cref="Mod"/>, combined into a single <see cref="XmlNode"/> as its children.
51+
/// The XML data of the <see cref="Mod"/> if any, combined into a single <see cref="XmlDocument"/>.
52+
/// </summary>
53+
public XmlDocument? Data
54+
{
55+
get;
56+
}
57+
58+
/// <summary>
59+
/// The patches applied by the <see cref="Mod"/>.
5060
/// </summary>
51-
public XmlNode? Data
61+
public IEnumerable<IPatch> Patches
5262
{
5363
get;
5464
}
@@ -62,7 +72,7 @@ private IEnumerable<Assembly> LoadAssemblies()
6272
: Enumerable.Empty<Assembly>();
6373
}
6474

65-
private XmlNode? LoadData()
75+
private XmlDocument? LoadData()
6676
{
6777
IEnumerable<XmlDocument> documents = this.LoadDocuments().ToArray();
6878
if (!documents.Any())
@@ -71,11 +81,16 @@ private IEnumerable<Assembly> LoadAssemblies()
7181
}
7282

7383
XmlDocument data = new();
74-
data.InsertBefore(data.CreateXmlDeclaration("1.0", "UTF-8", null), data.DocumentElement);
84+
85+
XmlElement root = data.CreateElement("Data");
86+
data.AppendChild(root);
87+
data.InsertBefore(data.CreateXmlDeclaration("1.0", "UTF-8", null), root);
88+
7589
documents
7690
.SelectMany(document => document.Cast<XmlNode>())
77-
.Where(node => node.NodeType is not XmlNodeType.XmlDeclaration)
78-
.ForEach(node => data.AppendChild(node));
91+
.Where(node => node is not XmlDeclaration)
92+
.ForEach(node => root.AppendChild(data.ImportNode(node, true)));
93+
7994
return data;
8095
}
8196

@@ -105,11 +120,30 @@ private void LoadResources()
105120
return;
106121
}
107122

108-
foreach (string resourcePath in System.IO.Directory.GetFiles(resourcesPath, "*.pck", SearchOption.AllDirectories))
123+
string? invalidResourcePath = System.IO.Directory.GetFiles(resourcesPath, "*.pck", SearchOption.AllDirectories).FirstOrDefault(resourcePath => !ProjectSettings.LoadResourcePack(resourcePath));
124+
if (invalidResourcePath is not null)
125+
{
126+
throw new ModLoadException(this.Meta.Directory, $"Error loading resource pack at {invalidResourcePath}");
127+
}
128+
}
129+
130+
private IEnumerable<IPatch> LoadPatches()
131+
{
132+
string patchesPath = $"{this.Meta.Directory}{System.IO.Path.DirectorySeparatorChar}Patches";
133+
134+
if (!System.IO.Directory.Exists(patchesPath))
135+
{
136+
yield break;
137+
}
138+
139+
Serializer serializer = new();
140+
XmlDocument document = new();
141+
foreach (string patchPath in System.IO.Directory.GetFiles(patchesPath, "*.xml", SearchOption.AllDirectories))
109142
{
110-
if (!ProjectSettings.LoadResourcePack(resourcePath))
143+
document.Load(patchPath);
144+
if (document.DocumentElement is not null)
111145
{
112-
throw new ModLoadException(this.Meta.Directory, $"Error loading resource pack at {resourcePath}");
146+
yield return serializer.Deserialize(document.DocumentElement) as IPatch ?? throw new ModLoadException(this.Meta.Directory, $"Invalid patch at {patchPath}");
113147
}
114148
}
115149
}
File renamed without changes.

ModLoader.cs renamed to Modding/ModLoader.cs

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System.Collections.Generic;
22
using System.Linq;
33
using System.Reflection;
4+
using System.Xml;
45

56
using JetBrains.Annotations;
67

7-
using Godot.Modding.Utility.Extensions;
8+
using Godot.Utility;
9+
using Godot.Utility.Extensions;
810

911
namespace Godot.Modding
1012
{
@@ -14,7 +16,7 @@ namespace Godot.Modding
1416
[PublicAPI]
1517
public static class ModLoader
1618
{
17-
private static readonly Dictionary<string, Mod> loadedMods = new();
19+
private static readonly OrderedDictionary<string, Mod> loadedMods = new();
1820

1921
/// <summary>
2022
/// All the <see cref="Mod"/>s that have been loaded at runtime.
@@ -28,43 +30,83 @@ public static IReadOnlyDictionary<string, Mod> LoadedMods
2830
}
2931

3032
/// <summary>
31-
/// Loads a <see cref="Mod"/> from <paramref name="modDirectoryPath"/> and runs all methods marked with <see cref="ModStartupAttribute"/> in its assemblies if specified.
33+
/// Loads a <see cref="Mod"/> from <paramref name="modDirectoryPath"/>, applies its patches if any, and runs all methods marked with <see cref="ModStartupAttribute"/> in its assemblies if specified.
3234
/// </summary>
3335
/// <param name="modDirectoryPath">The directory path containing the <see cref="Mod"/>'s metadata, assemblies, data, and resource packs.</param>
3436
/// <param name="executeAssemblies">Whether any code in any assemblies of the loaded <see cref="Mod"/> gets executed.</param>
3537
/// <returns>The <see cref="Mod"/> loaded from <paramref name="modDirectoryPath"/>.</returns>
3638
/// <remarks>This method only loads a <see cref="Mod"/> individually, and does not check whether it has been loaded with all dependencies and in the correct load order. To load multiple <see cref="Mod"/>s in a safe and orderly manner, <see cref="LoadMods"/> should be used.</remarks>
3739
public static Mod LoadMod(string modDirectoryPath, bool executeAssemblies = true)
3840
{
41+
// Load mod
3942
Mod mod = new(Mod.Metadata.Load(modDirectoryPath));
40-
ModLoader.loadedMods.Add(mod.Meta.Id, mod);
43+
44+
// Cache XML data of loaded mods for repeat enumeration later
45+
XmlElement[] data = ModLoader.LoadedMods.Values
46+
.Select(loadedMod => loadedMod.Data?.DocumentElement)
47+
.Append(mod.Data?.DocumentElement)
48+
.NotNull()
49+
.ToArray();
50+
51+
// Apply mod patches
52+
mod.Patches.ForEach(patch => data.ForEach(patch.Apply));
53+
54+
// Execute mod assemblies
4155
if (executeAssemblies)
4256
{
4357
ModLoader.StartupMod(mod);
4458
}
59+
60+
// Register mod as fully loaded
61+
ModLoader.loadedMods.Add(mod.Meta.Id, mod);
62+
4563
return mod;
4664
}
4765

4866
/// <summary>
49-
/// Loads <see cref="Mod"/>s from <paramref name="modDirectoryPaths"/> and runs all methods marked with <see cref="ModStartupAttribute"/> in their assemblies if specified.
67+
/// Loads <see cref="Mod"/>s from <paramref name="modDirectoryPaths"/>, applies their patches if any, runs all methods marked with <see cref="ModStartupAttribute"/> in their assemblies if specified.
5068
/// </summary>
5169
/// <param name="modDirectoryPaths">The directory paths to load the <see cref="Mod"/>s from, containing each <see cref="Mod"/>'s metadata, assemblies, data, and resource packs.</param>
5270
/// <param name="executeAssemblies">Whether any code in any assemblies of the loaded <see cref="Mod"/>s gets executed.</param>
5371
/// <returns>An <see cref="IEnumerable{T}"/> of the loaded <see cref="Mod"/>s in the correct load order. <see cref="Mod"/>s that could not be loaded due to issues will not be contained in the sequence.</returns>
5472
/// <remarks>This method loads multiple <see cref="Mod"/>s after sorting them according to the load order specified in their metadata. To load a <see cref="Mod"/> individually without regard to its dependencies and load order, <see cref="LoadMod"/> should be used.</remarks>
5573
public static IEnumerable<Mod> LoadMods(IEnumerable<string> modDirectoryPaths, bool executeAssemblies = true)
5674
{
57-
List<Mod> mods = ModLoader.SortModMetadata(ModLoader.FilterModMetadata(ModLoader.LoadModMetadata(modDirectoryPaths)))
58-
.Select(metadata => new Mod(metadata))
75+
// Cache XML data of loaded mods for repeat enumeration later
76+
List<XmlElement> data = ModLoader.LoadedMods.Values
77+
.Select(mod => mod.Data?.DocumentElement)
78+
.NotNull()
5979
.ToList();
60-
mods.ForEach(mod => ModLoader.loadedMods.Add(mod.Meta.Id, mod));
61-
if (executeAssemblies)
80+
81+
List<Mod> mods = new();
82+
foreach (Mod.Metadata metadata in ModLoader.SortModMetadata(ModLoader.FilterModMetadata(ModLoader.LoadModMetadata(modDirectoryPaths))))
6283
{
63-
mods.ForEach(ModLoader.StartupMod);
84+
// Load mod
85+
Mod mod = new(metadata);
86+
mods.Add(mod);
87+
88+
// Apply mod patches
89+
XmlElement? root = mod.Data?.DocumentElement;
90+
if (root is not null)
91+
{
92+
data.Add(root);
93+
}
94+
mod.Patches.ForEach(patch => data.ForEach(patch.Apply));
95+
}
96+
foreach (Mod mod in mods)
97+
{
98+
// Execute mod assemblies
99+
if (executeAssemblies)
100+
{
101+
ModLoader.StartupMod(mod);
102+
}
103+
104+
// Register mod as fully loaded
105+
ModLoader.loadedMods.Add(mod.Meta.Id, mod);
64106
}
65107
return mods;
66108
}
67-
109+
68110
private static void StartupMod(Mod mod)
69111
{
70112
// Invoke all static methods annotated with [Startup] along with the supplied parameters (if any)
File renamed without changes.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.Xml;
2+
3+
using JetBrains.Annotations;
4+
5+
using Godot.Serialization;
6+
7+
namespace Godot.Modding.Patching
8+
{
9+
/// <summary>
10+
/// An <see cref="IPatch"/> that removes an attribute from an <see cref="XmlElement"/>.
11+
/// </summary>
12+
[PublicAPI]
13+
public class AttributeRemovePatch : IPatch
14+
{
15+
/// <summary>
16+
/// Initialises a new <see cref="AttributeRemovePatch"/> with the specified parameters.
17+
/// </summary>
18+
/// <param name="attribute">The name of the attribute to remove.</param>
19+
public AttributeRemovePatch(string attribute)
20+
{
21+
this.Attribute = attribute;
22+
}
23+
24+
[UsedImplicitly]
25+
private AttributeRemovePatch()
26+
{
27+
}
28+
29+
/// <summary>
30+
/// The name of the attribute to remove.
31+
/// </summary>
32+
[Serialize]
33+
public string Attribute
34+
{
35+
get;
36+
[UsedImplicitly]
37+
private set;
38+
} = null!;
39+
40+
/// <summary>
41+
/// Removes <see cref="Attribute"/> from <paramref name="data"/> if <paramref name="data"/> is an <see cref="XmlElement"/>.
42+
/// </summary>
43+
/// <param name="data">The <see cref="XmlNode"/> to apply the patch on.</param>
44+
public void Apply(XmlNode data)
45+
{
46+
if (data is XmlElement element)
47+
{
48+
element.RemoveAttribute(this.Attribute);
49+
}
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)