Skip to content
Merged
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
18 changes: 13 additions & 5 deletions EXILED/Exiled.Events/EventArgs/Player/SpawningEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

namespace Exiled.Events.EventArgs.Player
{
using System;
using System.Runtime.CompilerServices;

using Exiled.API.Features;
using Exiled.API.Features.Roles;
using Exiled.Events.EventArgs.Interfaces;
using PlayerRoles;

using UnityEngine;

/// <summary>
Expand All @@ -31,15 +33,15 @@ public class SpawningEventArgs : IPlayerEvent
/// <param name="rotation">
/// <inheritdoc cref="HorizontalRotation" />
/// </param>
/// <param name="oldRole">
/// the spawned player's old <see cref="PlayerRoleBase">role</see>.
/// <param name="newRole">
/// the spawned player's new <see cref="PlayerRoleBase">role</see>.
/// </param>
public SpawningEventArgs(Player player, Vector3 position, float rotation, PlayerRoleBase oldRole)
public SpawningEventArgs(Player player, Vector3 position, float rotation, PlayerRoleBase newRole)
{
Player = player;
Position = position;
HorizontalRotation = rotation;
OldRole = Role.Create(oldRole);
NewRole = Role.Create(newRole);
}

/// <summary>
Expand All @@ -66,6 +68,12 @@ public SpawningEventArgs(Player player, Vector3 position, float rotation, Player
/// <summary>
/// Gets the player's old <see cref="PlayerRoleBase">role</see>.
/// </summary>
[Obsolete("Removed because the method is no longer provide OldRole since version 14.0. Use Player.Role instead")]
public Role OldRole { get; }

/// <summary>
/// Gets the player's new <see cref="PlayerRoleBase">role</see>.
/// </summary>
public Role NewRole { get; }
}
}
86 changes: 55 additions & 31 deletions EXILED/Exiled.Events/Patches/Events/Player/Spawning.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@

namespace Exiled.Events.Patches.Events.Player
{
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

using API.Features;
using Exiled.API.Features;
using Exiled.API.Features.Pools;
using Exiled.Events.Attributes;
using Exiled.Events.EventArgs.Player;

using Exiled.Events.EventArgs.Server;
using HarmonyLib;

using PlayerRoles;
using PlayerRoles.FirstPersonControl;
using PlayerRoles.FirstPersonControl.Spawnpoints;

using UnityEngine;

using static HarmonyLib.AccessTools;
Expand All @@ -27,43 +28,66 @@ namespace Exiled.Events.Patches.Events.Player
/// Adds the <see cref="Handlers.Player.Spawning"/> event.
/// Fix for spawning in void.
/// </summary>
[HarmonyPatch]
[EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.Spawning))]
[HarmonyPatch(typeof(RoleSpawnpointManager), nameof(RoleSpawnpointManager.SetPosition))]
internal static class Spawning
{
private static MethodInfo TargetMethod()
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
return Method(TypeByName("PlayerRoles.FirstPersonControl.Spawnpoints.RoleSpawnpointManager").GetNestedTypes(all)[1], "<Init>b__2_0");
}
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.Pool.Get(instructions);

private static bool Prefix(ReferenceHub hub, PlayerRoleBase prevRole, PlayerRoleBase newRole)
{
if (newRole.ServerSpawnReason == RoleChangeReason.Destroyed || !Player.TryGet(hub, out Player player))
return true;
int offset = -1;

// Locate the call to `Transform.position` setter to determine where to insert new instructions.
MethodInfo setPositionMethod = PropertySetter(typeof(Transform), nameof(Transform.position));
int index = newInstructions.FindIndex(instr =>
instr.opcode == OpCodes.Callvirt && instr.operand as MethodInfo == setPositionMethod) + offset;

Vector3 oldPosition = hub.transform.position;
float oldRotation = (prevRole as IFpcRole)?.FpcModule.MouseLook.CurrentVertical ?? 0;
// Declare the `SpawningEventArgs` local variable.
LocalBuilder ev = generator.DeclareLocal(typeof(SpawningEventArgs));

if (newRole is IFpcRole fpcRole)
newInstructions.InsertRange(
index,
new[]
{
if (newRole.ServerSpawnFlags.HasFlag(RoleSpawnFlags.UseSpawnpoint) && fpcRole.SpawnpointHandler != null && fpcRole.SpawnpointHandler.TryGetSpawnpoint(out Vector3 position, out float horizontalRot))
{
oldPosition = position;
oldRotation = horizontalRot;
}
// Load `ReferenceHub` (argument 0) and get `Player`.
new CodeInstruction(OpCodes.Ldarg_0), // Load `hub` (first argument passed to the method).
new CodeInstruction(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), // Call Player.Get(hub) to get the Player instance.

SpawningEventArgs ev = new(player, oldPosition, oldRotation, prevRole);
// Load `position` (local variable 2).
new CodeInstruction(OpCodes.Ldloc_2),

Handlers.Player.OnSpawning(ev);
// Load `rotation` (local variable 3).
new CodeInstruction(OpCodes.Ldloc_3),

player.Position = ev.Position;
fpcRole.FpcModule.MouseLook.CurrentHorizontal = ev.HorizontalRotation;
}
else
{
Handlers.Player.OnSpawning(new(player, oldPosition, oldRotation, prevRole));
}
// Load `newRole` (argument 1).
new CodeInstruction(OpCodes.Ldarg_1), // Load `newRole` from argument 1.

// Create a new instance of `SpawningEventArgs`.
new CodeInstruction(OpCodes.Newobj, GetDeclaredConstructors(typeof(SpawningEventArgs))[0]),

// Duplicate the object to store it and pass it around.
new CodeInstruction(OpCodes.Dup), // Duplicate the `SpawningEventArgs` object.
new CodeInstruction(OpCodes.Stloc, ev.LocalIndex), // Store the duplicated object in a local variable.

// Call `Handlers.Player.OnSpawning`.
new CodeInstruction(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnSpawning))),

// Modify `position` from `ev.Position`.
new CodeInstruction(OpCodes.Ldloc, ev.LocalIndex), // Load the `SpawningEventArgs` object stored in the local variable.
new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(SpawningEventArgs), nameof(SpawningEventArgs.Position))), // Get the `Position` property from `SpawningEventArgs`.
new CodeInstruction(OpCodes.Stloc_2), // Store the position value back in the local variable 2 (`position`).

// Modify `rotation` from `ev.HorizontalRotation`.
new CodeInstruction(OpCodes.Ldloc, ev.LocalIndex), // Load the `SpawningEventArgs` object again.
new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(SpawningEventArgs), nameof(SpawningEventArgs.HorizontalRotation))), // Get the `HorizontalRotation` property from `SpawningEventArgs`.
new CodeInstruction(OpCodes.Stloc_3), // Store the rotation value back in the local variable 3 (`rotation`).
});

for (int z = 0; z < newInstructions.Count; z++)
yield return newInstructions[z];

return false;
ListPool<CodeInstruction>.Pool.Return(newInstructions);
}
}
}