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
44 changes: 44 additions & 0 deletions SecretAPI/Enums/RoomSafetyFailReason.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace SecretAPI.Enums
{
using System;
using MapGeneration;
using SecretAPI.Extensions;
using UnityEngine;

/// <summary>
/// Reasons why <see cref="RoomExtensions.IsSafeToTeleport"/> should fail.
/// </summary>
[Flags]
public enum RoomSafetyFailReason
{
/// <summary>
/// No fail.
/// </summary>
None = 0,

/// <summary>
/// Room safety check will fail if warhead has gone off and room is not part of <see cref="FacilityZone.Surface"/>.
/// </summary>
Warhead = 1,

/// <summary>
/// Room safety check will fail if decontamination has gone off and room is part of <see cref="FacilityZone.LightContainment"/>.
/// </summary>
Decontamination = 2,

/// <summary>
/// Room safety check will fail if room has <see cref="TeslaGate"/>.
/// </summary>
Tesla = 4,

/// <summary>
/// Room safety check will fail if the listed room fails a <see cref="Physics.Raycast(Vector3, Vector3, out RaycastHit, float, int)"/> check.
/// </summary>
MissingFloor = 8,

/// <summary>
/// Room safety check will fail if the listed room is part of <see cref="RoomExtensions.KnownUnsafeRooms"/>.
/// </summary>
KnownBad = 16,
}
}
68 changes: 62 additions & 6 deletions SecretAPI/Extensions/RoomExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
namespace SecretAPI.Extensions
{
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using LabApi.Features.Wrappers;
using MapGeneration;
using PlayerRoles.FirstPersonControl;
using PlayerRoles.PlayableScps.Scp106;
using SecretAPI.Enums;
using UnityEngine;

/// <summary>
/// Extensions related to rooms.
/// </summary>
public static class RoomExtensions
{
private static readonly List<RoomName> KnownUnsafeRooms =
private const float RaycastDistance = 2;

/// <summary>
/// Gets a list of <see cref="RoomName"/> that will be denied by <see cref="RoomSafetyFailReason.KnownBad"/>.
/// </summary>
public static List<RoomName> KnownUnsafeRooms { get; } =
[
RoomName.HczTesla, // Instant death
RoomName.EzEvacShelter, // Stuck permanently
RoomName.EzCollapsedTunnel, // Stuck permanently
RoomName.HczWaysideIncinerator, // Death
Expand All @@ -23,19 +31,67 @@ public static class RoomExtensions
/// Gets whether a room is safe to teleport to. Will consider decontamination, warhead, teslas and void rooms.
/// </summary>
/// <param name="room">The room to check.</param>
/// <param name="failReasons">Reasons why the safety check should fail.</param>
/// <returns>Whether the room is safe to teleport to.</returns>
public static bool IsSafeToTeleport(this Room room)
public static bool IsSafeToTeleport(this Room room, RoomSafetyFailReason failReasons)
{
if (Warhead.IsDetonated && room.Zone != FacilityZone.Surface)
if (failReasons.HasFlag(RoomSafetyFailReason.Warhead) && Warhead.IsDetonated && room.Zone != FacilityZone.Surface)
return false;

if (Decontamination.IsDecontaminating && room.Zone == FacilityZone.LightContainment)
if (failReasons.HasFlag(RoomSafetyFailReason.Decontamination) && Decontamination.IsDecontaminating && room.Zone == FacilityZone.LightContainment)
return false;

if (failReasons.HasFlag(RoomSafetyFailReason.Tesla) && room.Name == RoomName.HczTesla)
return false;

if (KnownUnsafeRooms.Contains(room.Name))
return false;

return Physics.Raycast(room.Position, Vector3.down, out _, 2);
if (failReasons.HasFlag(RoomSafetyFailReason.MissingFloor) && !Physics.Raycast(room.Position, Vector3.down, out _, RaycastDistance, FpcStateProcessor.Mask))
return false;

return true;
}

/// <summary>
/// Tries to get a location to teleport a <see cref="Player"/> to.
/// </summary>
/// <param name="player">The player to attempt to get a teleport position from.</param>
/// <param name="position">The position found if any, otherwise null.</param>
/// <param name="zone">If set to anything other than <see cref="FacilityZone.None"/> will only attempt to find in that zone.</param>
/// <param name="defaultRadius">The default radius allowed nea the found spot.</param>
/// <returns>Whether a valid teleport position was correctly found.</returns>
public static bool TryGetTeleportLocation(this Player player, [NotNullWhen(true)] out Vector3? position, FacilityZone zone = FacilityZone.None, float defaultRadius = Scp106PocketExitFinder.RaycastRange)
{
position = null;
return player.RoleBase is IFpcRole fpc && TryGetTeleportLocation(fpc, out position, zone);
}

/// <summary>
/// Tries to get a location to teleport a <see cref="IFpcRole"/> to.
/// </summary>
/// <param name="fpc">The <see cref="IFpcRole"/> to attempt to get a teleport position from.</param>
/// <param name="position">The position found if any, otherwise null.</param>
/// <param name="zone">If set to anything other than <see cref="FacilityZone.None"/> will only attempt to find in that zone.</param>
/// <param name="defaultRadius">The default radius allowed nea the found spot.</param>
/// <returns>Whether a valid teleport position was correctly found.</returns>
public static bool TryGetTeleportLocation(this IFpcRole fpc, [NotNullWhen(true)] out Vector3? position, FacilityZone zone = FacilityZone.None, float defaultRadius = Scp106PocketExitFinder.RaycastRange)
{
position = null;

IEnumerable<Pose> poses = zone == FacilityZone.None
? SafeLocationFinder.GetLocations(null, null)
: Scp106PocketExitFinder.GetPosesForZone(zone);

if (!poses.TryGetRandomValue(out Pose pose))
return false;

float radius = defaultRadius;
if (Room.TryGetRoomAtPosition(pose.position, out Room? room))
radius = Scp106PocketExitFinder.GetRaycastRange(room.Zone);

position = SafeLocationFinder.GetSafePosition(pose.position, pose.forward, radius, fpc.FpcModule.CharController);
return true;
}
}
}