Skip to content
This repository has been archived by the owner on Jan 13, 2023. It is now read-only.

Commit

Permalink
Better Ragdoll API (#191)
Browse files Browse the repository at this point in the history
* Create new Ragdoll create methods

* Move Map.Ragdoll too Ragdoll.List

* Update RagdollList.cs

doc

* Rename Delete to Destroy for consistency

* Unsubscribe ragdoll events

* Add roleType to error

Co-authored-by: louis1706 <louismonneyron5@yahoo.com>
Co-authored-by: undid-iridium <francis.x.irizarry@gmail.com>
Co-authored-by: Pietro <iopietro@hotmail.it>
  • Loading branch information
4 people authored Jan 2, 2023
1 parent 8d70878 commit 3668be1
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 206 deletions.
13 changes: 1 addition & 12 deletions Exiled.API/Features/Map.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,13 @@ public static class Map
/// </summary>
internal static readonly List<PocketDimensionTeleport> TeleportsValue = new(8);

/// <summary>
/// A list of <see cref="Ragdoll"/>s on the map.
/// </summary>
internal static readonly List<Ragdoll> RagdollsValue = new();

/// <summary>
/// A list of <see cref="AdminToy"/>s on the map.
/// </summary>
internal static readonly List<AdminToy> ToysValue = new();

private static readonly ReadOnlyCollection<PocketDimensionTeleport> ReadOnlyTeleportsValue = TeleportsValue.AsReadOnly();
private static readonly ReadOnlyCollection<Locker> ReadOnlyLockersValue = LockersValue.AsReadOnly();
private static readonly ReadOnlyCollection<Ragdoll> ReadOnlyRagdollsValue = RagdollsValue.AsReadOnly();
private static readonly ReadOnlyCollection<AdminToy> ReadOnlyToysValue = ToysValue.AsReadOnly();

private static TantrumEnvironmentalHazard tantrumPrefab;
Expand Down Expand Up @@ -122,11 +116,6 @@ public static Scp939AmnesticCloudInstance AmnesticCloudPrefab
/// </summary>
public static ReadOnlyCollection<Locker> Lockers => ReadOnlyLockersValue;

/// <summary>
/// Gets all <see cref="Ragdoll"/> objects.
/// </summary>
public static ReadOnlyCollection<Ragdoll> Ragdolls => ReadOnlyRagdollsValue;

/// <summary>
/// Gets all <see cref="AdminToy"/> objects.
/// </summary>
Expand Down Expand Up @@ -354,7 +343,7 @@ internal static void ClearCache()
Item.BaseToItem.Clear();
TeleportsValue.Clear();
LockersValue.Clear();
RagdollsValue.Clear();
Ragdoll.BasicRagdollToRagdoll.Clear();
Firearm.ItemTypeToFirearmInstance.Clear();
Firearm.BaseCodesValue.Clear();
Firearm.AvailableAttachmentsValue.Clear();
Expand Down
2 changes: 1 addition & 1 deletion Exiled.API/Features/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2853,7 +2853,7 @@ public void RandomTeleport(Type type)
nameof(TeslaGate) => TeslaGate.List.ElementAt(Random.Range(0, TeslaGate.BaseTeslaGateToTeslaGate.Count)),
nameof(Player) => Dictionary.Values.ElementAt(Random.Range(0, Dictionary.Count)),
nameof(Pickup) => Pickup.BaseToPickup.ElementAt(Random.Range(0, Pickup.BaseToPickup.Count)).Value,
nameof(Ragdoll) => Map.RagdollsValue[Random.Range(0, Map.RagdollsValue.Count)],
nameof(Ragdoll) => Ragdoll.List.ElementAt(Random.Range(0, Ragdoll.BasicRagdollToRagdoll.Count)),
nameof(Locker) => Map.GetRandomLocker(),
nameof(Generator) => Generator.List.ElementAt(Random.Range(0, Generator.Scp079GeneratorToGenerator.Count)),
nameof(Window) => Window.List.ElementAt(Random.Range(0, Window.BreakableWindowToWindow.Count)),
Expand Down
164 changes: 103 additions & 61 deletions Exiled.API/Features/Ragdoll.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace Exiled.API.Features

using Enums;
using Exiled.API.Extensions;
using Interactables.Interobjects.DoorUtils;
using MapGeneration;
using Mirror;
using PlayerRoles;
using PlayerRoles.PlayableScps.Scp049.Zombies;
Expand All @@ -31,61 +33,24 @@ namespace Exiled.API.Features
public class Ragdoll
{
/// <summary>
/// Initializes a new instance of the <see cref="Ragdoll"/> class.
/// A <see cref="Dictionary{TKey,TValue}"/> containing all known <see cref="BasicRagdoll"/>s and their corresponding <see cref="Ragdoll"/>.
/// </summary>
/// <param name="player">The ragdoll's <see cref="Player">owner</see>.</param>
/// <param name="handler">The player's <see cref="DamageHandlerBase"/>.</param>
/// <param name="canBeSpawned">A value that represents whether the ragdoll can be spawned.</param>
public Ragdoll(Player player, DamageHandlerBase handler, bool canBeSpawned = false)
{
if (player.Role.Base is not IRagdollRole ragdollRole)
return;

GameObject modelRagdoll = ragdollRole.Ragdoll.gameObject;

if (modelRagdoll == null || !Object.Instantiate(modelRagdoll).TryGetComponent(out BasicRagdoll ragdoll))
return;

ragdoll.NetworkInfo = new RagdollData(player.ReferenceHub, handler, modelRagdoll.transform.localPosition, modelRagdoll.transform.localRotation);

Base = ragdoll;

Map.RagdollsValue.Add(this);

if (canBeSpawned)
Spawn();
}
internal static readonly Dictionary<BasicRagdoll, Ragdoll> BasicRagdollToRagdoll = new(250);

/// <summary>
/// Initializes a new instance of the <see cref="Ragdoll"/> class.
/// </summary>
/// <param name="networkInfo">The ragdoll's <see cref="RagdollData"/>.</param>
/// <param name="canBeSpawned">A value that represents whether the ragdoll can be spawned.</param>
public Ragdoll(RagdollData networkInfo, bool canBeSpawned = false)
/// <param name="ragdoll">The encapsulated <see cref="BasicRagdoll"/>.</param>
internal Ragdoll(BasicRagdoll ragdoll)
{
if (networkInfo.RoleType.GetRoleBase() is not IRagdollRole ragdollRole)
return;

GameObject modelRagdoll = ragdollRole.Ragdoll.gameObject;

if (modelRagdoll == null || !Object.Instantiate(modelRagdoll).TryGetComponent(out BasicRagdoll ragdoll))
return;

ragdoll.NetworkInfo = networkInfo;

Base = ragdoll;

Map.RagdollsValue.Add(this);

if (canBeSpawned)
Spawn();
BasicRagdollToRagdoll.Add(ragdoll, this);
}

/// <summary>
/// Initializes a new instance of the <see cref="Ragdoll"/> class.
/// Gets a <see cref="IEnumerable{T}"/> of <see cref="Ragdoll"/> which contains all the <see cref="Ragdoll"/> instances.
/// </summary>
/// <param name="ragdoll">The encapsulated <see cref="BasicRagdoll"/>.</param>
internal Ragdoll(BasicRagdoll ragdoll) => Base = ragdoll;
public static IEnumerable<Ragdoll> List => BasicRagdollToRagdoll.Values;

/// <summary>
/// Gets or sets the <see cref="BasicRagdoll"/>s clean up time.
Expand Down Expand Up @@ -268,43 +233,120 @@ public Vector3 Scale
/// </summary>
internal static HashSet<BasicRagdoll> IgnoredRagdolls { get; set; } = new();

/// <summary>
/// Creates a new ragdoll.
/// </summary>
/// <param name="networkInfo">The data associated with the ragdoll.</param>
/// <returns>The ragdoll.</returns>
/// <exception cref="ArgumentException">Provided RoleType is not a valid ragdoll role (Spectator, Scp079, etc).</exception>
/// <exception cref="InvalidOperationException">Unable to create a ragdoll.</exception>
public static Ragdoll Create(RagdollData networkInfo)
{
if (networkInfo.RoleType.GetRoleBase() is not IRagdollRole ragdollRole)
throw new ArgumentException($"Provided RoleType '{networkInfo.RoleType}' is not a valid ragdoll role.");

GameObject modelRagdoll = ragdollRole.Ragdoll.gameObject;

if (modelRagdoll == null || !Object.Instantiate(modelRagdoll).TryGetComponent(out BasicRagdoll ragdoll))
throw new InvalidOperationException($"Unable to create a ragdoll of type {networkInfo.RoleType}.");

ragdoll.NetworkInfo = networkInfo;

return new(ragdoll)
{
Position = networkInfo.StartPosition,
Rotation = networkInfo.StartRotation,
};
}

/// <summary>
/// Creates a new ragdoll.
/// </summary>
/// <param name="roleType">The <see cref="RoleTypeId"/> of the ragdoll.</param>
/// <param name="name">The name of the ragdoll.</param>
/// <param name="damageHandler">The damage handler responsible for the ragdoll's death.</param>
/// <param name="owner">The optional owner of the ragdoll.</param>
/// <returns>The ragdoll.</returns>
public static Ragdoll Create(RoleTypeId roleType, string name, DamageHandlerBase damageHandler, Player owner = null)
=> Create(new(owner?.ReferenceHub ?? Server.Host.ReferenceHub, damageHandler, roleType, default, default, name, NetworkTime.time));

/// <summary>
/// Creates a new ragdoll.
/// </summary>
/// <param name="roleType">The <see cref="RoleTypeId"/> of the ragdoll.</param>
/// <param name="name">The name of the ragdoll.</param>
/// <param name="deathReason">The reason the ragdoll died.</param>
/// <param name="owner">The optional owner of the ragdoll.</param>
/// <returns>The ragdoll.</returns>
public static Ragdoll Create(RoleTypeId roleType, string name, string deathReason, Player owner = null)
=> Create(roleType, name, new CustomReasonDamageHandler(deathReason), owner);

/// <summary>
/// Creates and spawns a new ragdoll.
/// </summary>
/// <param name="networkInfo">The data associated with the ragdoll.</param>
/// <returns>The ragdoll.</returns>
public static Ragdoll CreateAndSpawn(RagdollData networkInfo)
{
Ragdoll doll = Create(networkInfo);
doll.Spawn();

return doll;
}

/// <summary>
/// Creates and spawns a new ragdoll.
/// </summary>
/// <param name="roleType">The <see cref="RoleTypeId"/> of the ragdoll.</param>
/// <param name="name">The name of the ragdoll.</param>
/// <param name="damageHandler">The damage handler responsible for the ragdoll's death.</param>
/// <param name="position">The position of the ragdoll.</param>
/// <param name="rotation">The rotation of the ragdoll.</param>
/// <param name="owner">The optional owner of the ragdoll.</param>
/// <returns>The ragdoll.</returns>
public static Ragdoll CreateAndSpawn(RoleTypeId roleType, string name, DamageHandlerBase damageHandler, Vector3 position, Quaternion rotation, Player owner = null)
=> CreateAndSpawn(new(owner?.ReferenceHub ?? Server.Host.ReferenceHub, damageHandler, roleType, position, rotation, name, NetworkTime.time));

/// <summary>
/// Creates and spawns a new ragdoll.
/// </summary>
/// <param name="roleType">The <see cref="RoleTypeId"/> of the ragdoll.</param>
/// <param name="name">The name of the ragdoll.</param>
/// <param name="deathReason">The reason the ragdoll died.</param>
/// <param name="position">The position of the ragdoll.</param>
/// <param name="rotation">The rotation of the ragdoll.</param>
/// <param name="owner">The optional owner of the ragdoll.</param>
/// <returns>The ragdoll.</returns>
public static Ragdoll CreateAndSpawn(RoleTypeId roleType, string name, string deathReason, Vector3 position, Quaternion rotation, Player owner = null)
=> CreateAndSpawn(roleType, name, new CustomReasonDamageHandler(deathReason), position, rotation, owner);

/// <summary>
/// Gets the <see cref="Ragdoll"/> belonging to the <see cref="BasicRagdoll"/>, if any.
/// </summary>
/// <param name="ragdoll">The <see cref="BasicRagdoll"/> to get.</param>
/// <returns>A <see cref="Ragdoll"/> or <see langword="null"/> if not found.</returns>
public static Ragdoll Get(BasicRagdoll ragdoll) => Map.Ragdolls.FirstOrDefault(rd => rd.Base == ragdoll);
public static Ragdoll Get(BasicRagdoll ragdoll) => BasicRagdollToRagdoll.TryGetValue(ragdoll, out Ragdoll doll)
? doll
: new Ragdoll(ragdoll);

/// <summary>
/// Gets the <see cref="IEnumerable{T}"/> of <see cref="Ragdoll"/> belonging to the <see cref="Player"/>, if any.
/// </summary>
/// <param name="player">The <see cref="Player"/> to get.</param>
/// <returns>A <see cref="IEnumerable{T}"/> of <see cref="Ragdoll"/>.</returns>
public static IEnumerable<Ragdoll> Get(Player player) => Map.Ragdolls.Where(rd => rd.Owner == player);
public static IEnumerable<Ragdoll> Get(Player player) => Ragdoll.List.Where(rd => rd.Owner == player);

/// <summary>
/// Gets the <see cref="IEnumerable{T}"/> of <see cref="Ragdoll"/> belonging to the <see cref="IEnumerable{T}"/> of <see cref="Player"/>, if any.
/// </summary>
/// <param name="players">The <see cref="Player"/>s to get.</param>
/// <returns>A <see cref="IEnumerable{T}"/> of <see cref="Ragdoll"/>.</returns>
public static IEnumerable<Ragdoll> Get(IEnumerable<Player> players) => players.SelectMany(pl => Map.Ragdolls.Where(rd => rd.Owner == pl));
public static IEnumerable<Ragdoll> Get(IEnumerable<Player> players) => players.SelectMany(pl => Ragdoll.List.Where(rd => rd.Owner == pl));

/// <summary>
/// Spawns a <see cref="Ragdoll"/> on the map.
/// Destroys the ragdoll.
/// </summary>
/// <param name="player">The ragdoll's <see cref="Player"/> owner.</param>
/// <param name="handler">The ragdoll's <see cref="DamageHandlerBase"/>.</param>
/// <returns>The created <see cref="Ragdoll"/>.</returns>
public static Ragdoll Spawn(Player player, DamageHandlers.DamageHandlerBase handler) => new(player, handler, true);

/// <summary>
/// Deletes the ragdoll.
/// </summary>
public void Delete()
{
Object.Destroy(GameObject);
Map.RagdollsValue.Remove(this);
}
public void Destroy() => Object.Destroy(GameObject);

/// <summary>
/// Spawns the ragdoll.
Expand Down
2 changes: 1 addition & 1 deletion Exiled.CustomRoles/API/Features/CustomRole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ private void OnInternalDying(DyingEventArgs ev)
if (Check(ev.Player))
{
CustomRoles.Instance.StopRagdollPlayers.Add(ev.Player);
_ = new Ragdoll(new RagdollData(ev.Player.ReferenceHub, ev.DamageHandler, Role, ev.Player.Position, Quaternion.Euler(ev.Player.Rotation), ev.Player.DisplayNickname, NetworkTime.time), true);
Ragdoll.CreateAndSpawn(new RagdollData(ev.Player.ReferenceHub, ev.DamageHandler, Role, ev.Player.Position, Quaternion.Euler(ev.Player.Rotation), ev.Player.DisplayNickname, NetworkTime.time));
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions Exiled.Events/Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace Exiled.Events
using HarmonyLib;
using PlayerRoles;
using PlayerRoles.FirstPersonControl.Thirdperson;
using PlayerRoles.Ragdolls;
using PluginAPI.Events;
using UnityEngine.SceneManagement;

Expand Down Expand Up @@ -94,6 +95,9 @@ public override void OnEnabled()

AnimatedCharacterModel.OnFootstepPlayed += Handlers.Player.OnMakingNoise;

RagdollManager.OnRagdollSpawned += Handlers.Internal.RagdollList.OnSpawnedRagdoll;
RagdollManager.OnRagdollRemoved += Handlers.Internal.RagdollList.OnRemovedRagdoll;

ServerConsole.ReloadServerName();

EventManager.RegisterEvents<Handlers.Warhead>(this);
Expand Down Expand Up @@ -125,6 +129,9 @@ public override void OnDisabled()

AnimatedCharacterModel.OnFootstepPlayed -= Handlers.Player.OnMakingNoise;

RagdollManager.OnRagdollSpawned -= Handlers.Internal.RagdollList.OnSpawnedRagdoll;
RagdollManager.OnRagdollRemoved -= Handlers.Internal.RagdollList.OnRemovedRagdoll;

EventManager.UnregisterEvents<Handlers.Warhead>(this);
EventManager.UnregisterEvents<Handlers.Player>(this);
}
Expand Down
30 changes: 30 additions & 0 deletions Exiled.Events/Handlers/Internal/RagdollList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// -----------------------------------------------------------------------
// <copyright file="RagdollList.cs" company="Exiled Team">
// Copyright (c) Exiled Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.Events.Handlers.Internal
{
using Exiled.API.Features;
using PlayerRoles.Ragdolls;

/// <summary>
/// Handles adding and removing from <see cref="Ragdoll.BasicRagdollToRagdoll"/>.
/// </summary>
internal static class RagdollList
{
/// <summary>
/// Called after a ragdoll is spawned. Hooked to <see cref="RagdollManager.OnRagdollSpawned"/>.
/// </summary>
/// <param name="ragdoll">The spawned ragdoll.</param>
public static void OnSpawnedRagdoll(BasicRagdoll ragdoll) => Ragdoll.Get(ragdoll);

/// <summary>
/// Called before a ragdoll is destroyed. Hooked to <see cref="RagdollManager.OnRagdollRemoved"/>.
/// </summary>
/// <param name="ragdoll">The destroyed ragdoll.</param>
public static void OnRemovedRagdoll(BasicRagdoll ragdoll) => Ragdoll.BasicRagdollToRagdoll.Remove(ragdoll);
}
}
30 changes: 0 additions & 30 deletions Exiled.Events/Patches/Events/Player/SpawningRagdoll.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,36 +105,6 @@ private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructi
new(OpCodes.Callvirt, PropertyGetter(typeof(SpawningRagdollEventArgs), nameof(SpawningRagdollEventArgs.Info))),
});

index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldnull);

List<Label> labels = newInstructions[index].ExtractLabels();

newInstructions.RemoveRange(index, 2);

newInstructions.InsertRange(
index,
new[]
{
// pop the gameObject in the stack
new CodeInstruction(OpCodes.Pop).WithLabels(labels),

// Ragdoll newRagdoll = new Ragdoll(ragdoll)
new CodeInstruction(OpCodes.Ldloc_1),
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(API.Features.Ragdoll))[2]),
new(OpCodes.Dup),
new(OpCodes.Stloc_S, newRagdoll.LocalIndex),

// NetworkServer.Spawn(newRagdoll.GameObject, null)
new(OpCodes.Callvirt, PropertyGetter(typeof(API.Features.Ragdoll), nameof(API.Features.Ragdoll.GameObject))),
new(OpCodes.Ldnull),
new(OpCodes.Call, Method(typeof(NetworkServer), nameof(NetworkServer.Spawn), new[] { typeof(GameObject), typeof(NetworkConnection) })),

// Map.RagdollsValue.Add(newRagdoll)
new(OpCodes.Ldsfld, Field(typeof(Map), nameof(Map.RagdollsValue))),
new(OpCodes.Ldloc_S, newRagdoll.LocalIndex),
new(OpCodes.Callvirt, Method(typeof(List<API.Features.Ragdoll>), nameof(List<API.Features.Ragdoll>.Add))),
});

newInstructions[newInstructions.Count - 1].WithLabels(ret);

for (int z = 0; z < newInstructions.Count; z++)
Expand Down
Loading

0 comments on commit 3668be1

Please sign in to comment.