Skip to content

Commit

Permalink
Modules and API Revamp (#2779)
Browse files Browse the repository at this point in the history
* fix docs typos

* remote debugging

* Call me god bb (breaking changes lmao)

* Probably fixed null refs on CI

* Initialize all dispatchers

* Fixed and changed all the tracking logic and structure

* Items and Pickups are ready to use.
Added `TrackablesGC` and fixed Tracker.

* Fixed custom roles and role assigners.

* Commands fixes and adjustments

* fixed typos

* Update programs.yml

* Fixed all configs related issues

* stylecop fixes

* Added `DynamicTypeGenerator`

* Fix stylecop

* Improved `ConfigSubsystem`, implemented by `ConfigManager::LoadConfigSubsystem`, adding standalone configs, adding support for multiple individual configs in the same path with other configs, such as `ModulePointer` and module configs.

* Useless code

* Removed unused using directive

* Optimized attachments generation

* Added more methods to type generator

* `ConfigSubsystem` revamp and improvements, implemented new configs for `PlayerState` objects. Configs will rely on classes marked as `ConfigAttribute`, if a class doesn't exist or the `PlayerState::Config` is null or not initialized and no suitable class is found, a dynamic type will be generated along with the relative configuration file named as the type of the `PlayerState` + a "-Config" suffix, placed in the CustomModule's instance config directory, and will be (de)serialized by generating the relative dynamic time each time until the `PlayerState::Config` gets initialized from the user/dev. This adds support for uninitialized configs and handles the exception in the best manner.

* stylecop brr

* I was kidding

* Removed useless warnings

* Removed debug log

* And another one

* Added more descriptions to serializable properties.

* More descriptions added

* [Trackables Fix] Custom Modules Commands (#2780)

* Starting New Custom Modules commands

* Finish all gamemode commands

* Starting Custom Teams

* Custom Team Commands finished

* Custom Escape Commands

* Parent Commands Desc Change

* Fixed `ImplementConfigs` and `ApplyConfig` methods. Made some adjustments to modules commands.

* bump

---------

Co-authored-by: NaoUnderscore <github@naounderscore.dev>
Co-authored-by: xNexusACS <83370388+xNexusACS@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 15, 2024
1 parent e0385b2 commit 8e59c29
Show file tree
Hide file tree
Showing 141 changed files with 5,052 additions and 2,201 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/programs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
run: dotnet publish ${{ matrix.proj_name }} -r ${{ matrix.target }} -c release -o builds/${{ matrix.target }} --self-contained true

- name: Upload ${{ matrix.proj_name }}@${{ matrix.target }} build
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.proj_name }}-${{ matrix.target }}
path: builds/${{ matrix.target }}
2 changes: 1 addition & 1 deletion EXILED.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<PropertyGroup>
<!-- This is the global version and is used for all projects that don't have a version -->
<Version Condition="$(Version) == ''">9.0.0-beta.2</Version>
<Version Condition="$(Version) == ''">9.0.0-beta.3</Version>
<!-- Enables public beta warning via the PUBLIC_BETA constant -->
<PublicBeta>false</PublicBeta>

Expand Down
2 changes: 1 addition & 1 deletion Exiled.API/Extensions/ItemExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ public static IEnumerable<AttachmentIdentifier> GetAttachmentIdentifiers(this Fi
if (type.GetBaseCode() > code)
code = type.GetBaseCode();

if (!Firearm.ItemTypeToFirearmInstance.TryGetValue(type, out Firearm firearm))
if (Item.Create(type.GetItemType()) is not Firearm firearm)
throw new ArgumentException($"Couldn't find a Firearm instance matching the ItemType value: {type}.");

firearm.Base.ApplyAttachmentsCode(code, true);
Expand Down
87 changes: 82 additions & 5 deletions Exiled.API/Extensions/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
namespace Exiled.API.Extensions
{
using System;
using System.Collections.Generic;
using System.Reflection;

using HarmonyLib;

using LiteNetLib.Utils;

/// <summary>
Expand Down Expand Up @@ -51,19 +51,96 @@ public static void InvokeStaticEvent(this Type type, string eventName, object[]
}

/// <summary>
/// Copy all properties from the source class to the target one.
/// Copies all properties from the source object to the target object, performing a deep copy if necessary.
/// </summary>
/// <param name="target">The target object.</param>
/// <param name="target">The target object to copy properties to.</param>
/// <param name="source">The source object to copy properties from.</param>
public static void CopyProperties(this object target, object source)
/// <param name="deepCopy">Indicates whether to perform a deep copy of properties that are of class type.</param>
/// <exception cref="InvalidTypeException">Thrown when the target and source types do not match.</exception>
public static void CopyProperties(this object target, object source, bool deepCopy = false)
{
Type type = target.GetType();

if (type != source.GetType())
throw new InvalidTypeException("Target and source type mismatch!");

foreach (PropertyInfo sourceProperty in type.GetProperties())
type.GetProperty(sourceProperty.Name)?.SetValue(target, sourceProperty.GetValue(source, null), null);
{
if (sourceProperty.CanWrite)
{
object value = sourceProperty.GetValue(source, null);

if (deepCopy && value is not null && sourceProperty.PropertyType.IsClass &&
sourceProperty.PropertyType != typeof(string))
{
object targetValue = Activator.CreateInstance(sourceProperty.PropertyType);
CopyProperties(targetValue, value, true);
sourceProperty.SetValue(target, targetValue, null);
}
else
{
sourceProperty.SetValue(target, value, null);
}
}
}
}

/// <summary>
/// Removes the generic type suffix (e.g., '`1' from `List`1`) from a type name if it exists.
/// </summary>
/// <param name="typeName">The name of the type, which may include a generic suffix.</param>
/// <returns>The type name without the generic suffix if it was present; otherwise, returns the original type name.</returns>
public static string RemoveGenericSuffix(this string typeName)
{
int indexOfBacktick = typeName.IndexOf('`');
return indexOfBacktick >= 0 ? typeName.Substring(0, indexOfBacktick) : typeName;
}

/// <summary>
/// Gets the first non-generic base type of a given type.
/// </summary>
/// <param name="type">The type for which to find the first non-generic base type.</param>
/// <returns>The first non-generic base type, or null if none is found.</returns>
public static Type GetFirstNonGenericBaseType(this Type type)
{
Type baseType = type.BaseType;

while (baseType != null && baseType.IsGenericType)
baseType = baseType.BaseType;

return baseType;
}

/// <summary>
/// Retrieves the names and values of all properties of an object based on the specified binding flags.
/// </summary>
/// <param name="obj">The object whose properties are to be retrieved.</param>
/// <param name="bindingFlags">Optional. Specifies the binding flags to use for retrieving properties. Default is <see cref="BindingFlags.Instance"/> and <see cref="BindingFlags.Public"/>.</param>
/// <returns>A dictionary containing property names as keys and their respective values as values.</returns>
public static Dictionary<string, object> GetPropertiesWithValue(this object obj, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public)
{
Dictionary<string, object> propertyValues = new();

obj.GetType().GetProperties(bindingFlags)
.ForEach(property => propertyValues.Add(property.Name, property.GetValue(obj, null)));

return propertyValues;
}

/// <summary>
/// Retrieves the names and values of all fields of an object based on the specified binding flags.
/// </summary>
/// <param name="obj">The object whose fields are to be retrieved.</param>
/// <param name="bindingFlags">Optional. Specifies the binding flags to use for retrieving fields. Default is <see cref="BindingFlags.Instance"/> and <see cref="BindingFlags.Public"/>.</param>
/// <returns>A dictionary containing field names as keys and their respective values as values.</returns>
public static Dictionary<string, object> GetFieldsWithValue(this object obj, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public)
{
Dictionary<string, object> propertyValues = new();

obj.GetType().GetFields(bindingFlags)
.ForEach(field => propertyValues.Add(field.Name, field.GetValue(obj)));

return propertyValues;
}
}
}
25 changes: 25 additions & 0 deletions Exiled.API/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ public static string ToSnakeCase(this string str, bool shouldReplaceSpecialChars
return shouldReplaceSpecialChars ? Regex.Replace(snakeCaseString, @"[^0-9a-zA-Z_]+", string.Empty) : snakeCaseString;
}

/// <summary>
/// Converts a <see cref="string"/> to kebab case convention.
/// </summary>
/// <param name="input">Input string.</param>
/// <returns>A string converted to kebab case.</returns>
public static string ToKebabCase(this string input) => Regex.Replace(input, "([a-z])([A-Z])", "$1_$2").ToLower();

/// <summary>
/// Converts a <see cref="string"/> from snake_case convention.
/// </summary>
Expand Down Expand Up @@ -204,5 +211,23 @@ public static string GetHashedUserId(this string userId)
byte[] hash = Sha256.ComputeHash(textData);
return BitConverter.ToString(hash).Replace("-", string.Empty);
}

/// <summary>
/// Encrypts a value using SHA-256 and returns a hexadecimal hash string.
/// </summary>
/// <param name="value">The value to encrypt.</param>
/// <returns>A hexadecimal hash string of the encrypted value.</returns>
public static string GetHashedValue(this string value)
{
byte[] bytes = Encoding.UTF8.GetBytes(value);
byte[] hashBytes = Sha256.ComputeHash(bytes);

StringBuilder hashStringBuilder = new();

foreach (byte b in hashBytes)
hashStringBuilder.Append(b.ToString("x2"));

return hashStringBuilder.ToString();
}
}
}
41 changes: 28 additions & 13 deletions Exiled.API/Features/Attributes/ConfigAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,39 @@ namespace Exiled.API.Features.Attributes
using System;

/// <summary>
/// This attribute determines whether the class which is being applied to should be treated as <see cref="EConfig"/>.
/// This attribute determines whether the class which is being applied to should be treated as <see cref="ConfigSubsystem"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ConfigAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="ConfigAttribute"/> class.
/// </summary>
public ConfigAttribute()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ConfigAttribute"/> class.
/// </summary>
/// <param name="folder"><inheritdoc cref="Folder"/></param>
/// <param name="name"><inheritdoc cref="Name"/></param>
/// <param name="isParent"><inheritdoc cref="IsParent"/></param>
public ConfigAttribute(string folder, string name, bool isParent = false)
/// <param name="folder">
/// The folder where the config file is stored.
/// This value is used to determine the path of the configuration file.
/// </param>
/// <param name="name">
/// The name of the configuration file.
/// This is the unique name used to identify the configuration file.
/// </param>
/// <param name="isParent">
/// A value indicating whether this configuration acts as a parent config.
/// If <see langword="true"/>, this config will manage child configurations.
/// </param>
/// <param name="isStandAlone">
/// A value indicating whether this configuration is stand-alone and should not manage or be managed from other configurations.
/// </param>
public ConfigAttribute(
string folder = null,
string name = null,
bool isParent = false,
bool isStandAlone = false)
{
Folder = folder;
Name = name;
IsParent = isParent;
IsStandAlone = isStandAlone;
}

/// <summary>
Expand All @@ -46,8 +56,13 @@ public ConfigAttribute(string folder, string name, bool isParent = false)
public string Name { get; }

/// <summary>
/// Gets a value indicating whether the class on which this attribute is being applied to should be treated as parent <see cref="EConfig"/>.
/// Gets a value indicating whether the class on which this attribute is being applied to should be treated as parent <see cref="ConfigSubsystem"/>.
/// </summary>
public bool IsParent { get; }

/// <summary>
/// Gets a value indicating whether the config is individual.
/// </summary>
public bool IsStandAlone { get; }
}
}
Loading

0 comments on commit 8e59c29

Please sign in to comment.