Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions EXILED/Exiled.API/Enums/FirearmType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace Exiled.API.Enums
{
using System;

/// <summary>
/// Represents a firearm.
/// </summary>
Expand All @@ -18,6 +20,7 @@ namespace Exiled.API.Enums
/// <seealso cref="Extensions.ItemExtensions.GetWeaponAmmoType(FirearmType)"/>
/// <seealso cref="Extensions.ItemExtensions.TryGetAttachments(FirearmType, uint, out System.Collections.Generic.IEnumerable{Structs.AttachmentIdentifier})"/>
/// <seealso cref="Features.Items.Firearm.FirearmType"/>
[Flags]
public enum FirearmType
{
/// <summary>
Expand Down
33 changes: 33 additions & 0 deletions EXILED/Exiled.API/Extensions/IEnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// -----------------------------------------------------------------------
// <copyright file="IEnumerableExtensions.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.API.Extensions
{
using System;
using System.Collections.Generic;

/// <summary>
/// A set of extensions for <see cref="IEnumerable{T}"/>.
/// </summary>
public static class IEnumerableExtensions
{
/// <summary>
/// Perform an action on each element of a collection.
/// </summary>
/// <typeparam name="T">Type of <see cref="IEnumerable{T}"/> elements.</typeparam>
/// <param name="enumerable"><see cref="IEnumerable{T}"/> in this collection, the elements will perform actions.</param>
/// <param name="action">Action that needs to be performed.</param>
public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
if (enumerable is null || action is null)
return;

foreach (T e in enumerable)
action(e);
}
Comment on lines +18 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that already a things ?

@PUDGE133 PUDGE133 Sep 1, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that already a things ?

This method is defined only for arrays and sheets. I've made it available to all IEnumerable<T> collections.
Снимок экрана 2026-09-01 122609
Снимок экрана 2026-09-01 122619
Снимок экрана 2026-09-01 122639

}
}
48 changes: 48 additions & 0 deletions EXILED/Exiled.API/Extensions/PlayerPermissionsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// -----------------------------------------------------------------------
// <copyright file="PlayerPermissionsExtensions.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.API.Extensions
{
using System.Collections.Generic;

/// <summary>
/// A set of extensions for <see cref="PlayerPermissions"/>.
/// </summary>
public static class PlayerPermissionsExtensions
{
/// <summary>
/// Checks whether the current permissions contain any of the permissions specified in the mask.
/// </summary>
/// <param name="playerPermissions">The current permissions to check.</param>
/// <param name="mask">The mask of permissions to test against.</param>
/// <returns><see langword="true"/> if the current permissions contain at least one permission from the mask; otherwise, <see langword="false"/>.</returns>
public static bool HasAnyPermission(this PlayerPermissions playerPermissions, PlayerPermissions mask)
{
return (playerPermissions & mask) != 0;
}

/// <summary>
/// Checks whether the current permissions contain any of the permissions specified in the collection.
/// </summary>
/// <param name="playerPermissions">The current permissions to check.</param>
/// <param name="collectionPlayerPermissions">The collection of permissions to test against.</param>
/// <returns><see langword="true"/> if the current permissions contain at least one permission from the collection; otherwise, <see langword="false"/>.</returns>
public static bool HasAnyPermission(this PlayerPermissions playerPermissions, IEnumerable<PlayerPermissions> collectionPlayerPermissions)
{
if (collectionPlayerPermissions is null)
return false;

foreach (PlayerPermissions perm in collectionPlayerPermissions)
{
if (playerPermissions.HasAnyPermission(perm))
return true;
}

return false;
}
}
}
75 changes: 75 additions & 0 deletions EXILED/Exiled.API/Extensions/RandomExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// -----------------------------------------------------------------------
// <copyright file="RandomExtensions.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.API.Extensions
{
using System;

/// <summary>
/// A set of extensions for <see cref="Random"/>.
/// </summary>
public static class RandomExtensions
{
/// <summary>
/// Generate a random float.
/// </summary>
/// <param name="rnd"><see cref="Random"/> object.</param>
/// <param name="min">Minimum value.</param>
/// <param name="max">Maximum value.</param>
/// <returns>Random value between minimum and maximum.</returns>
public static float NextFloat(this Random rnd, float min, float max)
{
return (float)((rnd.NextDouble() * (max - min)) + min);
}

/// <summary>
/// Generate a random float.
/// </summary>
/// <param name="rnd"><see cref="Random"/> object.</param>
/// <param name="min">Minimum value.</param>
/// <param name="max">Maximum value.</param>
/// <returns>Random value between minimum and maximum.</returns>
public static float NextFloat(this Random rnd, double min, float max)
{
return (float)((rnd.NextDouble() * (max - min)) + min);
}

/// <summary>
/// Generate a random float.
/// </summary>
/// <param name="rnd"><see cref="Random"/> object.</param>
/// <param name="min">Minimum value.</param>
/// <param name="max">Maximum value.</param>
/// <returns>Random value between minimum and maximum.</returns>
public static float NextFloat(this Random rnd, float min, double max)
{
return (float)((rnd.NextDouble() * (max - min)) + min);
}

/// <summary>
/// Generate a random float.
/// </summary>
/// <param name="rnd"><see cref="Random"/> object.</param>
/// <param name="min">Minimum value.</param>
/// <param name="max">Maximum value.</param>
/// <returns>Random value between minimum and maximum.</returns>
public static float NextFloat(this Random rnd, double min, double max)
{
return (float)((rnd.NextDouble() * (max - min)) + min);
}
Comment on lines +17 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't Unity Random already handle some of these ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't Unity Random already handle some of these ?

Because not everyone likes Random from unity.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because not everyone likes Random from unity.

??? What is wrong with unity randoms?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because not everyone likes Random from unity.

??? What is wrong with unity randoms?

What difference does it make to you? What changes for you? Use the randomness you want.


/// <summary>
/// Generate a random bool.
/// </summary>
/// <param name="rnd"><see cref="Random"/> object.</param>
/// <returns>Random boolean value.</returns>
public static bool NextBool(this Random rnd)
{
return (rnd.Next() & 1) == 0;
}
Comment on lines +70 to +73

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this is very badly optimised

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this is very badly optimised

I don't see any particular problem here, but you can do it like this:
(rnd.Next() & 1) == 0

}
}
35 changes: 35 additions & 0 deletions EXILED/Exiled.API/Extensions/TimeSpanExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// -----------------------------------------------------------------------
// <copyright file="TimeSpanExtensions.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.API.Extensions
{
using System;

/// <summary>
/// A set of extensions for <see cref="TimeSpan"/>.
/// </summary>
public static class TimeSpanExtensions
{
/// <summary>
/// Converts a TimeSpan object to a human-readable format.
/// </summary>
/// <param name="timeSpan"><see cref="TimeSpan"/> object.</param>
/// <returns>A <see cref="TimeSpan"/> object in string representation.</returns>
public static string ToHumanReadable(this TimeSpan timeSpan)
{
if (timeSpan.TotalHours < 1)
return timeSpan.ToString(@"mm\:ss");

if (timeSpan.TotalDays < 1)
return timeSpan.ToString(@"hh\:mm\:ss");

string daysPart = timeSpan.Days == 1 ? "1 day" : $"{timeSpan.Days} days";
string timePart = timeSpan.ToString(@"hh\:mm\:ss");
return $"{daysPart}, {timePart}";
}
Comment on lines +22 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does it handle differences like language/ 24h or 12h Am/Pm

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does it handle differences like language/ 24h or 12h Am/Pm

Normal people use the 24-hour clock. In any case, I didn't intend for this method to be universal. If someone needs it, they can always add their own extension method to this class with a different name or a different overload. I don't see any problem with that.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normal people use the 24-hour clock. In any case, I didn't intend for this method to be universal. If someone needs it, they can always add their own extension method to this class with a different name or a different overload. I don't see any problem with that.

It's an API meant for many people to use. A lot of this code is pretty pointless or not helpful to many people.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normal people use the 24-hour clock. In any case, I didn't intend for this method to be universal. If someone needs it, they can always add their own extension method to this class with a different name or a different overload. I don't see any problem with that.

It's an API meant for many people to use. A lot of this code is pretty pointless or not helpful to many people.

Write your own method and add it. Be sure to take into account the 250 official languages. Keep in mind that each language has its own vocabulary and grammar. I used one specific method for 99% of tasks. But you can add your own.

}
}
54 changes: 54 additions & 0 deletions EXILED/Exiled.API/Extensions/Vector3Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// -----------------------------------------------------------------------
// <copyright file="Vector3Extensions.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.API.Extensions
{
using Exiled.API.Enums;
using Exiled.API.Features;

using UnityEngine;

/// <summary>
/// A set of extensions for <see cref="Vector3"/> that provide conversions between world space and
/// room‑relative local space.
/// </summary>
public static class Vector3Extensions
{
/// <summary>
/// Converts a world position to a position relative to the specified room's local coordinate system.
/// </summary>
/// <param name="worldPos">The world‑space position to convert.</param>
/// <param name="room">The room whose local space will be used as the reference.</param>
/// <returns>
/// The position expressed in the room's local space.
/// If the room is the <see cref="RoomType.Surface"/>, the original world position is returned unchanged.
/// </returns>
public static Vector3 FromWorldToRelativePos(this Vector3 worldPos, Room room)
{
if (room.Type == RoomType.Surface)
return worldPos;
return room.Transform.InverseTransformPoint(worldPos);
}

/// <summary>
/// Converts a position relative to the specified room's local space back to world space.
/// </summary>
/// <param name="relativePos">The local‑space position to convert.</param>
/// <param name="room">The room whose local space was used as the reference.</param>
/// <returns>
/// The position expressed in world space.
/// If the room is the <see cref="RoomType.Surface"/>, the original local position is returned unchanged
/// (since surface uses world coordinates directly).
/// </returns>
public static Vector3 FromRelativeToWorldPos(this Vector3 relativePos, Room room)
{
if (room.Type == RoomType.Surface)
return relativePos;
Comment on lines +49 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surface is a functional room technically i only see the purpose of this for Unknown Room

Or if you do it with the surface let's do it to all non dynamic rooms (i prefer to only do it on null that would be unknown)

Surface and pocket

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surface is a functional room technically i only see the purpose of this for Unknown Room

Or if you do it with the surface let's do it to all non dynamic rooms (i prefer to only do it on null that would be unknown)

Surface and pocket

I don't understand what you didn't like about this code.

return room.Transform.TransformPoint(relativePos);
}
}
}
33 changes: 32 additions & 1 deletion EXILED/Exiled.API/Features/Doors/Door.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,28 @@ public static Door Get(DoorVariant doorVariant)
public static T Get<T>(DoorVariant doorVariant)
where T : Door => Get(doorVariant) as T;

/// <summary>
/// Gets the door object associated with a specific <see cref="ButtonVariant"/>.
/// </summary>
/// <param name="buttonVariant">The base-game <see cref="ButtonVariant"/>.</param>
/// <returns>A <see cref="Door"/> wrapper object.</returns>
public static Door Get(ButtonVariant buttonVariant)
{
if (buttonVariant is null || buttonVariant.ParentDoor is null)
return null;

return Get(buttonVariant.ParentDoor);
}

/// <summary>
/// Gets the <see cref="Door"/> by <see cref="ButtonVariant"/>.
/// </summary>
/// <param name="buttonVariant">The <see cref="ButtonVariant"/> to convert into an door.</param>
/// <typeparam name="T">The specified <see cref="Door"/> type.</typeparam>
/// <returns>The door wrapper for the given <see cref="ButtonVariant"/>.</returns>
public static T Get<T>(ButtonVariant buttonVariant)
where T : Door => Get(buttonVariant) as T;

/// <summary>
/// Gets a <see cref="Door"/> given the specified <see cref="DoorType"/>.
/// </summary>
Expand Down Expand Up @@ -378,7 +400,16 @@ public static T Get<T>(string name)
/// </summary>
/// <param name="gameObject">The base-game <see cref="UnityEngine.GameObject"/>.</param>
/// <returns>The <see cref="Door"/> with the given name or <see langword="null"/> if not found.</returns>
public static Door Get(GameObject gameObject) => gameObject is null ? null : Get(gameObject.GetComponentInParent<DoorVariant>());
public static Door Get(GameObject gameObject)
{
if (gameObject != null)
{
// ParentDoor requires enabling "unsafe code"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird comment?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird comment?

I will remove this comment.

return Get(gameObject.GetComponentInParent<DoorVariant>() ?? gameObject.GetComponent<ButtonVariant>()?.ParentDoor);
}

return null;
}

/// <summary>
/// Returns the closest <see cref="Door"/> to the given <paramref name="position"/>.
Expand Down
34 changes: 32 additions & 2 deletions EXILED/Exiled.API/Features/Items/Firearm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public static IReadOnlyDictionary<Player, Dictionary<FirearmType, AttachmentIden
public new BaseFirearm Base { get; }

/// <summary>
/// Gets a primaty magazine for current firearm.
/// Gets a primary magazine for current firearm.
/// </summary>
public PrimaryMagazine PrimaryMagazine { get; }

Expand All @@ -156,7 +156,7 @@ public static IReadOnlyDictionary<Player, Dictionary<FirearmType, AttachmentIden
public BarrelMagazine BarrelMagazine { get; }

/// <summary>
/// Gets a primaty magazine for current firearm.
/// Gets a primary magazine for current firearm.
/// </summary>
public HitscanHitregModuleBase HitscanHitregModule { get; }

Expand Down Expand Up @@ -189,6 +189,36 @@ public int MagazineAmmo
set => PrimaryMagazine.Ammo = value;
}

/// <summary>
/// Gets or sets a value indicating whether the magazine is attached from the weapon. Setter will attach the magazine, but it will be empty. Weapons that do not have a detachable magazine return false by default. For example, a revolver.
/// </summary>
public bool IsMagazineAttached
{
get
{
if (PrimaryMagazine is NormalMagazine normalMag)
return normalMag.MagazineInserted;

// Weapons that do not have a detachable magazine return false by default. For example, a revolver.
return false;
}

set
{
if (PrimaryMagazine is NormalMagazine normalMag)
normalMag.MagazineInserted = value;
}
}

/// <summary>
/// Gets or sets a value indicating whether the magazine is attached from the weapon.
/// </summary>
public bool IsMagazineDeattached
{
get => !IsMagazineAttached;
set => IsMagazineAttached = !value;
}
Comment on lines +213 to +220

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is useless

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is useless

If you think this section of code is useless, then let's think this code is useless too:
image

@Mrhootyhoot1 Mrhootyhoot1 Sep 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you think this section of code is useless, then let's think this code is useless too:

It may be useless, but it is not beneficial to remove it because that would be a breaking change. There is also no reason to add new junk code.

@PUDGE133 PUDGE133 Sep 6, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you think this section of code is useless, then let's think this code is useless too:

It may be useless, but it is not beneficial to remove it because that would be a breaking change. There is also no reason to add new junk code.

No, no, no... you've had enough of this "critical code." You shouldn't have added it and then been afraid to remove it. As long as these and similar properties exist in the code, the code I've submitted will comply with Exiled standards.

This is excellent code; it reduces the cognitive load when reading it in plugins. There is no problem with it.


/// <summary>
/// Gets or sets the amount of ammo in the firearm barrel.
/// </summary>
Expand Down
6 changes: 3 additions & 3 deletions EXILED/Exiled.API/Features/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public static T DebugObject<T>(T @object)
/// Server must have exiled_debug config enabled.
/// </summary>
/// <param name="message">The message to be sent.</param>
public static void Debug(string message)
public static void Debug(string message = "")
{
Assembly callingAssembly = Assembly.GetCallingAssembly();
#if DEBUG
Expand All @@ -98,7 +98,7 @@ public static void Debug(string message)
/// Sends a <see cref="Discord.LogLevel.Warn"/> level messages to the game console.
/// </summary>
/// <param name="message">The message to be sent.</param>
public static void Warn(string message) => Send($"[{Assembly.GetCallingAssembly().GetName().Name}] {message}", Discord.LogLevel.Warn, ConsoleColor.Magenta);
public static void Warn(string message = "") => Send($"[{Assembly.GetCallingAssembly().GetName().Name}] {message}", Discord.LogLevel.Warn, ConsoleColor.Magenta);

/// <summary>
/// Sends a <see cref="Discord.LogLevel.Error"/> level messages to the game console.
Expand All @@ -114,7 +114,7 @@ public static void Debug(string message)
/// It's recommended to send any messages in the catch block of a try/catch as errors with the exception string.
/// </summary>
/// <param name="message">The message to be sent.</param>
public static void Error(string message) => Send($"[{Assembly.GetCallingAssembly().GetName().Name}] {message}", Discord.LogLevel.Error, ConsoleColor.DarkRed);
public static void Error(string message = "") => Send($"[{Assembly.GetCallingAssembly().GetName().Name}] {message}", Discord.LogLevel.Error, ConsoleColor.DarkRed);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would anyone call a method meant to print to the console without passing a string to the method to be printed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would anyone call a method meant to print to the console without passing a string to the method to be printed?

Have you ever split console logs to make them easier to read?


/// <summary>
/// Sends a log message to the game console.
Expand Down
Loading
Loading