Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR than i made #9

Merged
merged 11 commits into from
Aug 6, 2024
9 changes: 4 additions & 5 deletions EXILED/Exiled.API/Extensions/MirrorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,12 +422,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.WriteByte(1); // Value
/// writer.WriteUInt(17); // EditIndex
/// });
/// </code>
/// </example>
Expand Down
2 changes: 1 addition & 1 deletion EXILED/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/Exiled.API/Features/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ public class Player : TypeCastObject<Player>, IEntity, IWorldSpace
/// 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 readonly HashSet<EActor> componentsInChildren = new();
Expand Down Expand Up @@ -2357,21 +2367,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
5 changes: 5 additions & 0 deletions EXILED/Exiled.API/Features/Recontainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public static class Recontainer
/// </summary>
public static bool IsCassieBusy => Base.CassieBusy;

/// <summary>
/// Gets a value about how many generator have been activated.
/// </summary>
public static int EngagedGeneratorCount => Base._prevEngaged;

/// <summary>
/// Gets or sets a value indicating whether the containment zone is open.
/// </summary>
Expand Down
16 changes: 16 additions & 0 deletions EXILED/Exiled.API/Features/Roles/FpcRole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
namespace Exiled.API.Features.Roles
{
using System.Collections.Generic;
using System.Reflection;

using Exiled.API.Features.Pools;

using HarmonyLib;
using PlayerRoles;
using PlayerRoles.FirstPersonControl;

Expand All @@ -24,6 +26,7 @@ namespace Exiled.API.Features.Roles
/// </summary>
public abstract class FpcRole : Role
{
private static FieldInfo enableFallDamageField;
private bool isUsingStamina = true;

/// <summary>
Expand Down Expand Up @@ -55,6 +58,19 @@ public RelativePosition RelativePosition
set => FirstPersonController.FpcModule.Motor.ReceivedPosition = value;
}

/// <summary>
/// Gets or sets a value indicating whether if the player should get <see cref="Enums.DamageType.Falldown"/> damage.
/// </summary>
public bool IsFallDamageEnable
{
get => FirstPersonController.FpcModule.Motor._enableFallDamage;
set
{
enableFallDamageField ??= AccessTools.Field(typeof(FpcMotor), nameof(FpcMotor._enableFallDamage));
louis1706 marked this conversation as resolved.
Show resolved Hide resolved
enableFallDamageField.SetValue(FirstPersonController.FpcModule.Motor, value);
}
}

/// <summary>
/// Gets or sets a value indicating whether if a rotation is detected on the player.
/// </summary>
Expand Down
27 changes: 15 additions & 12 deletions EXILED/Exiled.Events/Patches/Events/Player/Escaping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
namespace Exiled.Events.Patches.Events.Player
{
#pragma warning disable SA1402 // File may only contain a single type
#pragma warning disable IDE0060

using System;
using System.Collections.Generic;
Expand All @@ -23,7 +22,7 @@ namespace Exiled.Events.Patches.Events.Player
using EventArgs.Player;
using Exiled.Events.Attributes;
using HarmonyLib;

using PlayerRoles.FirstPersonControl;
using Respawning;

using static HarmonyLib.AccessTools;
Expand Down Expand Up @@ -128,18 +127,22 @@ private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructi
{
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.Pool.Get(instructions);

int e = 0;
LocalBuilder fpcRole = generator.DeclareLocal(typeof(FpcStandardRoleBase));

// replace HumanRole to FpcStandardRoleBase
newInstructions.Find(x => x.opcode == OpCodes.Isinst).operand = typeof(FpcStandardRoleBase);

// after this index all invalid exit are considered Custom
int customExit = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldarg_0);
for (int i = 0; i < newInstructions.Count; i++)
{
CodeInstruction codeInstruction = newInstructions[i];
if (codeInstruction.opcode == OpCodes.Ldc_I4_0)
{
e++;
if (e > 3)
{
newInstructions[i].opcode = OpCodes.Ldc_I4_5;
}
}
OpCode opcode = newInstructions[i].opcode;
if (opcode == OpCodes.Stloc_0)
newInstructions[i] = new(OpCodes.Stloc_S, fpcRole.LocalIndex);
else if (opcode == OpCodes.Ldloc_0)
newInstructions[i] = new(OpCodes.Ldloc_S, fpcRole.LocalIndex);
else if (opcode == OpCodes.Ldc_I4_0 && i > customExit)
newInstructions[i].opcode = OpCodes.Ldc_I4_5;
}

for (int z = 0; z < newInstructions.Count; z++)
Expand Down
4 changes: 2 additions & 2 deletions EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructi
LocalBuilder evLocalReporting = generator.DeclareLocal(typeof(LocalReportingEventArgs));
LocalBuilder evReportingCheater = generator.DeclareLocal(typeof(ReportingCheaterEventArgs));

int offset = -1;
int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Newarr) + offset;
int offset = 2;
int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldarg_S && instruction.operand == (object)4) + offset;

Label ret = generator.DefineLabel();

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

namespace Exiled.Events.Patches.Generic
{
using System;

using Exiled.API.Extensions;
using Exiled.API.Features;
using HarmonyLib;
using InventorySystem.Configs;
using UnityEngine;

/// <summary>
/// Patches the <see cref="InventoryLimits.GetAmmoLimit(ItemType, ReferenceHub)"/> delegate.
/// Sync <see cref="Player.SetAmmoLimit(API.Enums.AmmoType, ushort)"/>.
/// Changes <see cref="ushort.MaxValue"/> to <see cref="ushort.MinValue"/>.
/// </summary>
[HarmonyPatch(typeof(InventoryLimits), nameof(InventoryLimits.GetAmmoLimit), new Type[] { typeof(ItemType), typeof(ReferenceHub) })]
internal static class GetCustomAmmoLimit
{
#pragma warning disable SA1313
private static void Postfix(ItemType ammoType, ReferenceHub player, ref ushort __result)
{
if (!Player.TryGet(player, out Player ply) || !ply.CustomAmmoLimits.TryGetValue(ammoType.GetAmmoType(), out ushort limit))
return;

__result = (ushort)Mathf.Clamp(limit + __result - InventoryLimits.GetAmmoLimit(null, ammoType), ushort.MinValue, ushort.MaxValue);
}
}
}
Loading
Loading