diff --git a/MonkeyLoader/Components/Entity.cs b/MonkeyLoader/Components/Entity.cs
index d2676eb..c4a1477 100644
--- a/MonkeyLoader/Components/Entity.cs
+++ b/MonkeyLoader/Components/Entity.cs
@@ -1,4 +1,5 @@
-using System;
+using MonkeyLoader.Meta.Tagging;
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -12,7 +13,7 @@ namespace MonkeyLoader.Components
/// that instances can belong to.
///
/// The type of the entity.
- public abstract class Entity : IEntity
+ public abstract class Entity : IEntity, ITaggable
where TEntity : Entity
{
///
@@ -20,6 +21,9 @@ public abstract class Entity : IEntity
TEntity IEntity.Self => (TEntity)this;
+ ///
+ public TagCollection Tags { get; } = [];
+
///
/// Creates a new entity instance, using this as the
/// Entity for the
diff --git a/MonkeyLoader/Configuration/ConfigAccessLevelTag.cs b/MonkeyLoader/Configuration/ConfigAccessLevelTag.cs
new file mode 100644
index 0000000..dc4c129
--- /dev/null
+++ b/MonkeyLoader/Configuration/ConfigAccessLevelTag.cs
@@ -0,0 +1,50 @@
+using MonkeyLoader.Meta.Tagging;
+
+namespace MonkeyLoader.Configuration
+{
+ ///
+ /// Defines the possible access levels for config sections or their items.
+ ///
+ public enum ConfigAccessLevel
+ {
+ ///
+ /// The default level of access.
+ /// This config section or item should always be shown to users.
+ ///
+ Regular,
+
+ ///
+ /// Access for advanced users.
+ /// This config section or item should only be shown to advanced users.
+ ///
+ Advanced,
+
+ ///
+ /// Internal access of the mod only.
+ /// This config section or item should never be shown to users.
+ ///
+ Internal
+ }
+
+ ///
+ /// Defines the tag.
+ ///
+ ///
+ /// This tag should be used for config sections or their items to define when or how they can be accessed.
+ ///
+ public sealed class ConfigAccessLevelTag : DataTag
+ {
+ ///
+ public override string Description => "Tags a config section or its items with an access level.";
+
+ ///
+ public override string Id => nameof(ConfigAccessLevel);
+
+ ///
+ /// Creates a new instance of this tag with the given access .
+ ///
+ /// The access level to store.
+ public ConfigAccessLevelTag(ConfigAccessLevel level) : base(level)
+ { }
+ }
+}
\ No newline at end of file
diff --git a/MonkeyLoader/Configuration/ConfigKeyComponent.cs b/MonkeyLoader/Configuration/ConfigKeyComponent.cs
index 4b4adf8..fc3d313 100644
--- a/MonkeyLoader/Configuration/ConfigKeyComponent.cs
+++ b/MonkeyLoader/Configuration/ConfigKeyComponent.cs
@@ -1,4 +1,5 @@
using MonkeyLoader.Components;
+using MonkeyLoader.Meta.Tagging;
namespace MonkeyLoader.Configuration
{
@@ -17,6 +18,9 @@ public static class ConfigKeyComponent
public static void Add(this IEntity configKey, IConfigKeyComponent component)
where TKey : class, IDefiningConfigKey, IEntity
=> configKey.Components.Add(component);
+
+ public static void Add(this IDefiningConfigKey configKey, ITag tag)
+ => configKey.Tags.Add(tag);
}
///
diff --git a/MonkeyLoader/Configuration/DefiningConfigKey.cs b/MonkeyLoader/Configuration/DefiningConfigKey.cs
index 00d05bf..9808b03 100644
--- a/MonkeyLoader/Configuration/DefiningConfigKey.cs
+++ b/MonkeyLoader/Configuration/DefiningConfigKey.cs
@@ -11,6 +11,7 @@
using System.Diagnostics.CodeAnalysis;
using MonkeyLoader.Meta;
using EnumerableToolkit;
+using MonkeyLoader.Meta.Tagging;
namespace MonkeyLoader.Configuration
{
@@ -80,7 +81,7 @@ public bool HasChanges
public string Id => AsUntyped.Id;
///
- public bool InternalAccessOnly { get; }
+ public bool InternalAccessOnly => (Tags[nameof(ConfigAccessLevel)].OfType().FirstOrDefault()?.Data ?? ConfigAccessLevel.Regular) is ConfigAccessLevel.Internal;
///
public bool IsDefiningKey => true;
@@ -89,10 +90,10 @@ public bool HasChanges
IIdentifiable INestedIdentifiable.Parent => Section;
- ///
///
/// Add a priority component to this config key or set a value during initialization.
///
+ ///
public int Priority
{
get => Components.TryGet(out var priorityComponent) ? priorityComponent.Priority : 0;
@@ -164,7 +165,8 @@ public DefiningConfigKey(string id, string? description = null, Func? compute
if (valueValidator is not null)
Components.Add(new ConfigKeyValidator(valueValidator));
- InternalAccessOnly = internalAccessOnly;
+ if (internalAccessOnly)
+ Tags.Add(new ConfigAccessLevelTag(ConfigAccessLevel.Internal));
_canAlwaysHaveChanges = !ValueType.IsValueType
&& !(typeof(INotifyPropertyChanged).IsAssignableFrom(ValueType) || typeof(INotifyCollectionChanged).IsAssignableFrom(ValueType));
@@ -437,7 +439,7 @@ event ConfigKeyChangedEventHandler? IDefiningConfigKey.Changed
/// Defines the definition for a config item.
///
public interface IDefiningConfigKey : ITypedConfigKey, IEntity,
- INestedIdentifiable, IPrioritizable
+ INestedIdentifiable, IPrioritizable, ITaggable
{
///
/// Gets the config this item belongs to.
diff --git a/MonkeyLoader/Meta/IMonkey.cs b/MonkeyLoader/Meta/IMonkey.cs
index fc7932f..6bea3ff 100644
--- a/MonkeyLoader/Meta/IMonkey.cs
+++ b/MonkeyLoader/Meta/IMonkey.cs
@@ -1,6 +1,7 @@
using HarmonyLib;
using MonkeyLoader.Configuration;
using MonkeyLoader.Logging;
+using MonkeyLoader.Meta.Tagging;
using MonkeyLoader.Patching;
using System;
using System.Collections.Generic;
@@ -35,7 +36,7 @@ public interface IEarlyMonkey : IMonkey
/// Defines the interface for all (early)
/// monkeys.
///
- public interface IMonkey : IRun, IShutdown, IComparable, INestedIdentifiable, IAuthorable
+ public interface IMonkey : IRun, IShutdown, IComparable, INestedIdentifiable, IAuthorable, ITaggable
{
///
/// Gets the name of the assembly this monkey is defined in.
@@ -68,7 +69,7 @@ public interface IMonkey : IRun, IShutdown, IComparable, INestedIdentif
public bool Enabled { get; set; }
///
- /// Gets the this monkey's toggle
+ /// Gets the this monkey's toggle
/// if it can be disabled.
///
/// The toggle config item if this monkey can be disabled; otherwise, null.
diff --git a/MonkeyLoader/Meta/Mod.cs b/MonkeyLoader/Meta/Mod.cs
index ffe550c..77a1513 100644
--- a/MonkeyLoader/Meta/Mod.cs
+++ b/MonkeyLoader/Meta/Mod.cs
@@ -21,7 +21,6 @@ namespace MonkeyLoader.Meta
public abstract partial class Mod : IConfigOwner, IShutdown, ILoadedNuGetPackage, IComparable,
INestedIdentifiableOwner, INestedIdentifiableOwner,
IIdentifiableOwner, IIdentifiableOwner, IAuthorable
-
{
///
/// The file extension for mods' assemblies.
diff --git a/MonkeyLoader/Meta/MonkeyTogglesConfigSection.cs b/MonkeyLoader/Meta/MonkeyTogglesConfigSection.cs
index 1262664..df7452e 100644
--- a/MonkeyLoader/Meta/MonkeyTogglesConfigSection.cs
+++ b/MonkeyLoader/Meta/MonkeyTogglesConfigSection.cs
@@ -12,7 +12,7 @@ namespace MonkeyLoader.Meta
///
public sealed class MonkeyTogglesConfigSection : ExpandoConfigSection
{
- private readonly Dictionary> _togglesByMonkey = new();
+ private readonly Dictionary> _togglesByMonkey = [];
///
public override string Description => "Contains toggles for the Monkeys of a mod which support disabling.";
@@ -44,8 +44,11 @@ internal MonkeyTogglesConfigSection(Mod mod)
public static ITypedConfigKey GetTemplateKey(IMonkey monkey)
=> new ConfigKey(monkey.Id);
+ ///
+ /// Gets or creates the toggle config item for the given (early) monkey.
+ ///
///
- /// The default for toggles created with this method is true.
+ /// The default for toggles created with this method is always true.
///
///
public IDefiningConfigKey GetToggle(IMonkey monkey)
diff --git a/MonkeyLoader/Meta/Tagging/DataTag.cs b/MonkeyLoader/Meta/Tagging/DataTag.cs
new file mode 100644
index 0000000..9d37bc4
--- /dev/null
+++ b/MonkeyLoader/Meta/Tagging/DataTag.cs
@@ -0,0 +1,27 @@
+namespace MonkeyLoader.Meta.Tagging
+{
+ ///
+ /// Implements an abstract base class for any s.
+ ///
+ /// The type of data associated with this tag type.
+ public abstract class DataTag : Tag, IDataTag
+ {
+ ///
+ public T Data { get; }
+
+ object? IDataTag.Data => Data;
+
+ ///
+ /// Creates a new instance of this tag type storing the given .
+ ///
+ /// The data associated with this instance.
+ protected DataTag(T data)
+ {
+ Data = data;
+ }
+
+ ///
+ public override string ToString()
+ => $"Data Tag: {Id} - {(Data is null ? "null" : Data.ToString())}";
+ }
+}
\ No newline at end of file
diff --git a/MonkeyLoader/Meta/Tagging/GenericTag.cs b/MonkeyLoader/Meta/Tagging/GenericTag.cs
new file mode 100644
index 0000000..a30af10
--- /dev/null
+++ b/MonkeyLoader/Meta/Tagging/GenericTag.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+
+namespace MonkeyLoader.Meta.Tagging
+{
+ ///
+ /// Represents a generic tag that can have any Id and Data.
+ ///
+ ///
+ public sealed class GenericTag : DataTag
+ {
+ private static readonly Dictionary _descriptionsById = new(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ public override string Description { get; }
+
+ ///
+ public override string Id { get; }
+
+ ///
+ /// Creates a new generic tag instance with the given and .
+ ///
+ /// The unique identifier of this type of tag.
+ /// The data associated with this instance.
+ public GenericTag(string id, T data) : base(data)
+ {
+ Id = id;
+
+ if (!_descriptionsById.TryGetValue(id, out var description))
+ {
+ description = $"GenericTag<{typeof(T).CompactDescription()}> with id: {id}";
+ _descriptionsById.Add(id.ToLower(), description);
+ }
+
+ Description = description;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MonkeyLoader/Meta/Tagging/IDataTag.cs b/MonkeyLoader/Meta/Tagging/IDataTag.cs
new file mode 100644
index 0000000..5724121
--- /dev/null
+++ b/MonkeyLoader/Meta/Tagging/IDataTag.cs
@@ -0,0 +1,24 @@
+namespace MonkeyLoader.Meta.Tagging
+{
+ ///
+ /// Defines the non-generic interface for the data tags of s.
+ ///
+ public interface IDataTag : ITag
+ {
+ ///
+ /// Gets the boxed data associated with this tag instance.
+ ///
+ public object? Data { get; }
+ }
+
+ ///
+ /// Defines the generic interface for the data tags of s.
+ ///
+ public interface IDataTag : IDataTag
+ {
+ ///
+ /// Gets the data associated with this tag instance.
+ ///
+ public new T Data { get; }
+ }
+}
\ No newline at end of file
diff --git a/MonkeyLoader/Meta/Tagging/ITag.cs b/MonkeyLoader/Meta/Tagging/ITag.cs
new file mode 100644
index 0000000..218500e
--- /dev/null
+++ b/MonkeyLoader/Meta/Tagging/ITag.cs
@@ -0,0 +1,29 @@
+namespace MonkeyLoader.Meta.Tagging
+{
+ ///
+ /// Defines the interface for the presence tags of s.
+ ///
+ public interface ITag
+ {
+ ///
+ /// Gets the description for this type of tag.
+ ///
+ public string Description { get; }
+
+ ///
+ /// Gets the unique identifier of this type of tag.
+ ///
+ ///
+ /// Case should be ignored when using this.
+ ///
+ public string Id { get; }
+
+ ///
+ /// Gets the name for this type of tag.
+ ///
+ ///
+ /// Implementations may default to the Id.
+ ///
+ public string Name { get; }
+ }
+}
\ No newline at end of file
diff --git a/MonkeyLoader/Meta/Tagging/ITaggable.cs b/MonkeyLoader/Meta/Tagging/ITaggable.cs
new file mode 100644
index 0000000..05340f8
--- /dev/null
+++ b/MonkeyLoader/Meta/Tagging/ITaggable.cs
@@ -0,0 +1,13 @@
+namespace MonkeyLoader.Meta.Tagging
+{
+ ///
+ /// Defines the interface for object that support tagging using s.
+ ///
+ public interface ITaggable
+ {
+ ///
+ /// Gets the tags of this object.
+ ///
+ public TagCollection Tags { get; }
+ }
+}
\ No newline at end of file
diff --git a/MonkeyLoader/Meta/Tagging/Tag.cs b/MonkeyLoader/Meta/Tagging/Tag.cs
new file mode 100644
index 0000000..87a119e
--- /dev/null
+++ b/MonkeyLoader/Meta/Tagging/Tag.cs
@@ -0,0 +1,24 @@
+namespace MonkeyLoader.Meta.Tagging
+{
+ ///
+ /// Implements an abstract base class for any s.
+ ///
+ public abstract class Tag : ITag
+ {
+ ///
+ public abstract string Description { get; }
+
+ ///
+ public abstract string Id { get; }
+
+ ///
+ /// By default: The Id of this tag.
+ ///
+ ///
+ public virtual string Name => Id;
+
+ ///
+ public override string ToString()
+ => $"Presence Tag: {Id}";
+ }
+}
\ No newline at end of file
diff --git a/MonkeyLoader/Meta/Tagging/TagCollection.cs b/MonkeyLoader/Meta/Tagging/TagCollection.cs
new file mode 100644
index 0000000..fd5941d
--- /dev/null
+++ b/MonkeyLoader/Meta/Tagging/TagCollection.cs
@@ -0,0 +1,145 @@
+using EnumerableToolkit;
+using NuGet.Packaging;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MonkeyLoader.Meta.Tagging
+{
+ ///
+ /// Stores an 's s.
+ ///
+ public sealed class TagCollection : IEnumerable>>
+ {
+ private readonly Dictionary> _tagsById = new(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Gets the total number of s stored in this collection.
+ ///
+ public int Count => _tagsById.Values.Sum(static tagSet => tagSet.Count);
+
+ ///
+ /// Gets the number of distinct Ids of s stored in this collection.
+ ///
+ public int IdCount => _tagsById.Count;
+
+ ///
+ /// Gets the distinct Ids of s stored in this collection.
+ ///
+ public IEnumerable Ids => _tagsById.Keys;
+
+ ///
+ /// Gets all s stored in this collection.
+ ///
+ public IEnumerable Instances => _tagsById.Values.SelectMany(static set => set);
+
+ ///
+ /// Gets or sets the tags associated with the given .
+ ///
+ ///
+ /// This will return an empty enumerable instead of throwing
+ /// when there is no with the given .
+ ///
+ /// The id of the tags to get or replace. Case is ignored.
+ /// All tags with the given .
+ public IEnumerable this[string id]
+ {
+ get => _tagsById.TryGetValue(id, out var tagSet) ? tagSet.AsSafeEnumerable() : [];
+
+ set
+ {
+ if (!value.Any())
+ {
+ _tagsById.Remove(id);
+ return;
+ }
+
+ var tagSet = GetOrCreateTagSetById(id);
+ tagSet.Clear();
+ tagSet.AddRange(value);
+ }
+ }
+
+ ///
+ /// Ensures that the given is included in this collection.
+ ///
+ /// The tag to include in this collection.
+ /// true if the was added; false if it was already present.
+ public bool Add(ITag tag)
+ => GetOrCreateTagSetById(tag.Id).Add(tag);
+
+ ///
+ /// Ensures that the given are included in this collection.
+ ///
+ /// The tags to include in this collection.
+ public void AddRange(IEnumerable tags)
+ {
+ foreach (var tag in tags)
+ Add(tag);
+ }
+
+ ///
+ /// Tests whether any s with the given are stored in this collection.
+ ///
+ /// The id of the tags to check for. Case is ignored.
+ /// true if this collections stores any s with the given ; otherwise, false.
+ public bool AnyWithId(string id)
+ => _tagsById.ContainsKey(id);
+
+ ///
+ /// Removes all s from this collection.
+ ///
+ public void Clear()
+ => _tagsById.Clear();
+
+ ///
+ /// Tests whether the given is stored in this collection.
+ ///
+ /// The tag to check for.
+ /// true if the given is stored in this collection; otherwise, false.
+ public bool Contains(ITag tag)
+ {
+ if (!_tagsById.TryGetValue(tag.Id, out var tagSet))
+ return false;
+
+ return tagSet.Contains(tag);
+ }
+
+ ///
+ public IEnumerator>> GetEnumerator()
+ => _tagsById.Select(static tagPair => new KeyValuePair>(tagPair.Key, tagPair.Value.AsSafeEnumerable())).GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ /// Ensures that the given is not stored in this collection.
+ ///
+ /// The tag to exclude from this collection.
+ /// true if the tag was removed; false if it wasn't stored in this collection.
+ public bool Remove(ITag tag)
+ {
+ if (!_tagsById.TryGetValue(tag.Id, out var tagSet))
+ return false;
+
+ var removed = tagSet.Remove(tag);
+
+ if (tagSet.Count is 0)
+ _tagsById.Remove(tag.Id);
+
+ return removed;
+ }
+
+ private HashSet GetOrCreateTagSetById(string id)
+ {
+ if (!_tagsById.TryGetValue(id, out var tagSet))
+ {
+ tagSet = [];
+ _tagsById.Add(id, tagSet);
+ }
+
+ return tagSet;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MonkeyLoader/Patching/MonkeyBase.cs b/MonkeyLoader/Patching/MonkeyBase.cs
index 6512123..fd4f747 100644
--- a/MonkeyLoader/Patching/MonkeyBase.cs
+++ b/MonkeyLoader/Patching/MonkeyBase.cs
@@ -3,6 +3,7 @@
using MonkeyLoader.Configuration;
using MonkeyLoader.Logging;
using MonkeyLoader.Meta;
+using MonkeyLoader.Meta.Tagging;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -148,6 +149,9 @@ internal set
///
public bool ShutdownRan { get; private set; } = false;
+ ///
+ public TagCollection Tags { get; } = [];
+
///
public Type Type { get; }