Skip to content

Commit

Permalink
Merge branch 'dev' into attribute-event
Browse files Browse the repository at this point in the history
  • Loading branch information
Misfiy authored Jul 2, 2024
2 parents 9ae3d38 + 79a268c commit 14e56a7
Show file tree
Hide file tree
Showing 35 changed files with 383 additions and 174 deletions.
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-alpha.1</Version>
<Version Condition="$(Version) == ''">9.0.0-alpha.2</Version>
<!-- Enables public beta warning via the PUBLIC_BETA constant -->
<PublicBeta>false</PublicBeta>

Expand Down
8 changes: 4 additions & 4 deletions Exiled.API/Extensions/MirrorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,11 +407,11 @@ public static void SendFakeTargetRpc(Player target, NetworkIdentity behaviorOwne
/// <example>
/// EffectOnlySCP207.
/// <code>
/// MirrorExtensions.SendCustomSync(player, player.ReferenceHub.networkIdentity, typeof(PlayerEffectsController), (writer) => {
/// writer.WriteUInt64(1ul); // DirtyObjectsBit
/// writer.WriteUInt32(1); // DirtyIndexCount
/// MirrorExtensions.SendFakeSyncObject(player, player.NetworkIdentity, typeof(PlayerEffectsController), (writer) => {
/// writer.WriteULong(1ul); // DirtyObjectsBit
/// writer.WriteUInt(1); // DirtyIndexCount
/// writer.WriteByte((byte)SyncList&lt;byte&gt;.Operation.OP_SET); // Operations
/// writer.WriteUInt32(17); // EditIndex
/// writer.WriteUInt(17); // EditIndex
/// writer.WriteByte(1); // Value
/// });
/// </code>
Expand Down
20 changes: 2 additions & 18 deletions Exiled.API/Features/Core/Generic/EnumClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,6 @@ public string Name
/// <param name="value">The value to convert.</param>
public static implicit operator EnumClass<TSource, TObject>(TSource value) => values[value];

/// <summary>
/// Implicitly converts the <see cref="EnumClass{TSource, TObject}"/> to <typeparamref name="TObject"/>.
/// </summary>
/// <param name="value">The value to convert.</param>
public static implicit operator TObject(EnumClass<TSource, TObject> value) => value;

/// <summary>
/// Casts the specified <paramref name="value"/> to the corresponding type.
/// </summary>
Expand All @@ -108,11 +102,7 @@ public string Name
/// </summary>
/// <param name="values">The enum values to be cast.</param>
/// <returns>The cast object.</returns>
public static IEnumerable<TObject> Cast(IEnumerable<TSource> values)
{
foreach (TSource value in values)
yield return EnumClass<TSource, TObject>.values[value];
}
public static IEnumerable<TObject> Cast(IEnumerable<TSource> values) => values.Select(value => EnumClass<TSource, TObject>.values[value]);

/// <summary>
/// Retrieves an array of the values of the constants in a specified <see cref="EnumClass{TSource, TObject}"/>.
Expand Down Expand Up @@ -170,13 +160,7 @@ public static bool SafeCast(IEnumerable<TSource> values, out IEnumerable<TObject
/// </summary>
/// <param name="obj">The object to be parsed.</param>
/// <returns>The corresponding <typeparamref name="TObject"/> object instance, or <see langword="null"/> if not found.</returns>
public static TObject Parse(string obj)
{
foreach (TObject value in values.Values.Where(value => string.Compare(value.Name, obj, true) == 0))
return value;

return null;
}
public static TObject Parse(string obj) => values.Values.FirstOrDefault(value => string.Compare(value.Name, obj, StringComparison.OrdinalIgnoreCase) == 0);

/// <summary>
/// Converts the <see cref="EnumClass{TSource, TObject}"/> instance to a human-readable <see cref="string"/> representation.
Expand Down
18 changes: 9 additions & 9 deletions Exiled.API/Features/Core/StaticActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ public static StaticActor CreateNewInstance<T>()
/// Creates a new instance of the <see cref="StaticActor"/>.
/// </summary>
/// <param name="type">The type of the <see cref="StaticActor"/>.</param>
/// <returns>The created <see cref="StaticActor"/> instance, or <see langword="null"/> if not found.</returns>
/// <returns>The created or already existing <see cref="StaticActor"/> instance.</returns>
public static StaticActor CreateNewInstance(Type type)
{
EObject @object = CreateDefaultSubobject<StaticActor>(type);
EObject @object = Get(type) ?? CreateDefaultSubobject<StaticActor>(type);
@object.Name = "__" + type.Name + " (StaticActor)";

if (Server.Host.GameObject)
Expand Down Expand Up @@ -125,19 +125,19 @@ protected override void PostInitialize()
{
base.PostInitialize();

if (Get(GetType()))
if (Get(GetType()) != this)
{
Log.Warn($"Found a duplicated instance of a StaticActor with type {GetType().Name} in the Actor {Name} that will be ignored");
NotifyInstanceRepeated();
return;
}

if (!IsInitialized)
{
Log.Debug($"Start() StaticActor with type {GetType().Name} in the Actor {Name}");
PostInitialize_Static();
IsInitialized = true;
}
if (IsInitialized)
return;

Log.Debug($"Start() StaticActor with type {GetType().Name} in the Actor {Name}");
PostInitialize_Static();
IsInitialized = true;
}

/// <inheritdoc/>
Expand Down
2 changes: 1 addition & 1 deletion Exiled.API/Features/CustomStats/CustomStaminaStat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class CustomStaminaStat : StaminaStat
public float Clamp(float value) => CustomMaxValue == default ? Mathf.Clamp01(value) : Mathf.Clamp(value, 0, MaxValue);

/// <summary>
/// Overiding NW Method to sync Player percentage of Stamina.
/// Overriding NW Method to sync Player percentage of Stamina.
/// </summary>
/// <param name="writer">The writer.</param>
public override void WriteValue(NetworkWriter writer)
Expand Down
2 changes: 1 addition & 1 deletion Exiled.API/Features/Items/Ammo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class Ammo : Item, IWrapper<AmmoItem>
/// <summary>
/// Gets the absolute maximum amount of ammo that may be held at one time, if ammo is forcefully given to the player (regardless of worn armor or server configuration).
/// <para>
/// For accessing the maximum amount of ammo that may be held based on worn armor and server settings, see <see cref="Player.GetAmmoLimit(AmmoType)"/>.
/// For accessing the maximum amount of ammo that may be held based on worn armor and server settings, see <see cref="Player.GetAmmoLimit(AmmoType, bool)"/>.
/// </para>
/// </summary>
public const ushort AmmoLimit = ushort.MaxValue;
Expand Down
173 changes: 166 additions & 7 deletions Exiled.API/Features/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ public class Player : GameEntity
/// A list of the player's items.
/// </summary>
internal readonly List<Item> ItemsValue = new(8);

/// <summary>
/// A dictionary of custom item category limits.
/// </summary>
internal Dictionary<ItemCategory, sbyte> CustomCategoryLimits = new();

/// <summary>
/// A dictionary of custom ammo limits.
/// </summary>
internal Dictionary<AmmoType, ushort> CustomAmmoLimits = new();
#pragma warning restore SA1401

private ReferenceHub referenceHub;
Expand Down Expand Up @@ -2643,21 +2653,170 @@ public bool DropAmmo(AmmoType ammoType, ushort amount, bool checkMinimals = fals

/// <summary>
/// Gets the maximum amount of ammo the player can hold, given the ammo <see cref="AmmoType"/>.
/// This method factors in the armor the player is wearing, as well as server configuration.
/// For the maximum amount of ammo that can be given regardless of worn armor and server configuration, see <see cref="ServerConfigSynchronizer.AmmoLimit"/>.
/// </summary>
/// <param name="type">The <see cref="AmmoType"/> of the ammo to check.</param>
/// <returns>The maximum amount of ammo this player can carry. Guaranteed to be between <c>0</c> and <see cref="ServerConfigSynchronizer.AmmoLimit"/>.</returns>
public int GetAmmoLimit(AmmoType type) =>
InventorySystem.Configs.InventoryLimits.GetAmmoLimit(type.GetItemType(), referenceHub);
/// <param name="ignoreArmor">If the method should ignore the armor the player is wearing.</param>
/// <returns>The maximum amount of ammo this player can carry.</returns>
public ushort GetAmmoLimit(AmmoType type, bool ignoreArmor = false)
{
if (ignoreArmor)
{
if (CustomAmmoLimits.TryGetValue(type, out ushort limit))
return limit;

ItemType itemType = type.GetItemType();
return ServerConfigSynchronizer.Singleton.AmmoLimitsSync.FirstOrDefault(x => x.AmmoType == itemType).Limit;
}

return InventorySystem.Configs.InventoryLimits.GetAmmoLimit(type.GetItemType(), referenceHub);
}

/// <summary>
/// Gets the maximum amount of ammo the player can hold, given the ammo <see cref="AmmoType"/>.
/// This limit will scale with the armor the player is wearing.
/// For armor ammo limits, see <see cref="Armor.AmmoLimits"/>.
/// </summary>
/// <param name="ammoType">The <see cref="AmmoType"/> of the ammo to check.</param>
/// <param name="limit">The <see cref="ushort"/> number that will define the new limit.</param>
public void SetAmmoLimit(AmmoType ammoType, ushort limit)
{
CustomAmmoLimits[ammoType] = limit;

ItemType itemType = ammoType.GetItemType();
int index = ServerConfigSynchronizer.Singleton.AmmoLimitsSync.FindIndex(x => x.AmmoType == itemType);
MirrorExtensions.SendFakeSyncObject(this, ServerConfigSynchronizer.Singleton.netIdentity, typeof(ServerConfigSynchronizer), writer =>
{
writer.WriteULong(2ul);
writer.WriteUInt(1);
writer.WriteByte((byte)SyncList<ServerConfigSynchronizer.AmmoLimit>.Operation.OP_SET);
writer.WriteInt(index);
writer.WriteAmmoLimit(new() { Limit = limit, AmmoType = itemType, });
});
}

/// <summary>
/// Reset a custom <see cref="AmmoType"/> limit.
/// </summary>
/// <param name="ammoType">The <see cref="AmmoType"/> of the ammo to reset.</param>
public void ResetAmmoLimit(AmmoType ammoType)
{
if (!HasCustomAmmoLimit(ammoType))
{
Log.Error($"{nameof(Player)}.{nameof(ResetAmmoLimit)}(AmmoType): AmmoType.{ammoType} does not have a custom limit.");
return;
}

CustomAmmoLimits.Remove(ammoType);

ItemType itemType = ammoType.GetItemType();
int index = ServerConfigSynchronizer.Singleton.AmmoLimitsSync.FindIndex(x => x.AmmoType == itemType);
MirrorExtensions.SendFakeSyncObject(this, ServerConfigSynchronizer.Singleton.netIdentity, typeof(ServerConfigSynchronizer), writer =>
{
writer.WriteULong(2ul);
writer.WriteUInt(1);
writer.WriteByte((byte)SyncList<ServerConfigSynchronizer.AmmoLimit>.Operation.OP_SET);
writer.WriteInt(index);
writer.WriteAmmoLimit(ServerConfigSynchronizer.Singleton.AmmoLimitsSync[index]);
});
}

/// <summary>
/// Check if the player has a custom limit for a specific <see cref="AmmoType"/>.
/// </summary>
/// <param name="ammoType">The <see cref="AmmoType"/> to check.</param>
/// <returns>If the player has a custom limit for the specific <see cref="AmmoType"/>.</returns>
public bool HasCustomAmmoLimit(AmmoType ammoType) => CustomAmmoLimits.ContainsKey(ammoType);

/// <summary>
/// Gets the maximum amount of an <see cref="ItemCategory"/> the player can hold, based on the armor the player is wearing, as well as server configuration.
/// </summary>
/// <param name="category">The <see cref="ItemCategory"/> to check.</param>
/// <param name="ignoreArmor">If the method should ignore the armor the player is wearing.</param>
/// <returns>The maximum amount of items in the category that the player can hold.</returns>
public int GetCategoryLimit(ItemCategory category) =>
InventorySystem.Configs.InventoryLimits.GetCategoryLimit(category, referenceHub);
public sbyte GetCategoryLimit(ItemCategory category, bool ignoreArmor = false)
{
int index = InventorySystem.Configs.InventoryLimits.StandardCategoryLimits.Where(x => x.Value >= 0).OrderBy(x => x.Key).ToList().FindIndex(x => x.Key == category);

if (ignoreArmor && index != -1)
{
if (CustomCategoryLimits.TryGetValue(category, out sbyte customLimit))
return customLimit;

return ServerConfigSynchronizer.Singleton.CategoryLimits[index];
}

sbyte limit = InventorySystem.Configs.InventoryLimits.GetCategoryLimit(category, referenceHub);

return limit == -1 ? (sbyte)1 : limit;
}

/// <summary>
/// Set the maximum amount of an <see cref="ItemCategory"/> the player can hold. Only works with <see cref="ItemCategory.Keycard"/>, <see cref="ItemCategory.Medical"/>, <see cref="ItemCategory.Firearm"/>, <see cref="ItemCategory.Grenade"/> and <see cref="ItemCategory.SCPItem"/>.
/// This limit will scale with the armor the player is wearing.
/// For armor category limits, see <see cref="Armor.CategoryLimits"/>.
/// </summary>
/// <param name="category">The <see cref="ItemCategory"/> to check.</param>
/// <param name="limit">The <see cref="int"/> number that will define the new limit.</param>
public void SetCategoryLimit(ItemCategory category, sbyte limit)
{
int index = InventorySystem.Configs.InventoryLimits.StandardCategoryLimits.Where(x => x.Value >= 0).OrderBy(x => x.Key).ToList().FindIndex(x => x.Key == category);

if (index == -1)
{
Log.Error($"{nameof(Player)}.{nameof(SetCategoryLimit)}(ItemCategory, sbyte): Cannot set category limit for ItemCategory.{category}.");
return;
}

CustomCategoryLimits[category] = limit;

MirrorExtensions.SendFakeSyncObject(this, ServerConfigSynchronizer.Singleton.netIdentity, typeof(ServerConfigSynchronizer), writer =>
{
writer.WriteULong(1ul);
writer.WriteUInt(1);
writer.WriteByte((byte)SyncList<sbyte>.Operation.OP_SET);
writer.WriteInt(index);
writer.WriteSByte(limit);
});
}

/// <summary>
/// Reset a custom <see cref="ItemCategory"/> limit. Only works with <see cref="ItemCategory.Keycard"/>, <see cref="ItemCategory.Medical"/>, <see cref="ItemCategory.Firearm"/>, <see cref="ItemCategory.Grenade"/> and <see cref="ItemCategory.SCPItem"/>.
/// </summary>
/// <param name="category">The <see cref="ItemCategory"/> of the category to reset.</param>
public void ResetCategoryLimit(ItemCategory category)
{
int index = InventorySystem.Configs.InventoryLimits.StandardCategoryLimits.Where(x => x.Value >= 0).OrderBy(x => x.Key).ToList().FindIndex(x => x.Key == category);

if (index == -1)
{
Log.Error($"{nameof(Player)}.{nameof(ResetCategoryLimit)}(ItemCategory, sbyte): Cannot reset category limit for ItemCategory.{category}.");
return;
}

if (!HasCustomCategoryLimit(category))
{
Log.Error($"{nameof(Player)}.{nameof(ResetCategoryLimit)}(ItemCategory): ItemCategory.{category} does not have a custom limit.");
return;
}

CustomCategoryLimits.Remove(category);

MirrorExtensions.SendFakeSyncObject(this, ServerConfigSynchronizer.Singleton.netIdentity, typeof(ServerConfigSynchronizer), writer =>
{
writer.WriteULong(1ul);
writer.WriteUInt(1);
writer.WriteByte((byte)SyncList<sbyte>.Operation.OP_SET);
writer.WriteInt(index);
writer.WriteSByte(ServerConfigSynchronizer.Singleton.CategoryLimits[index]);
});
}

/// <summary>
/// Check if the player has a custom limit for a specific <see cref="ItemCategory"/>.
/// </summary>
/// <param name="category">The <see cref="ItemCategory"/> to check.</param>
/// <returns>If the player has a custom limit for the specific <see cref="ItemCategory"/>.</returns>
public bool HasCustomCategoryLimit(ItemCategory category) => CustomCategoryLimits.ContainsKey(category);

/// <summary>
/// Adds an item of the specified type with default durability(ammo/charge) and no mods to the player's inventory.
Expand Down
Loading

0 comments on commit 14e56a7

Please sign in to comment.