diff --git a/Features/Enums/ToolGunObjectType.cs b/Features/Enums/ToolGunObjectType.cs
index b1b1b9d..e414ff9 100644
--- a/Features/Enums/ToolGunObjectType.cs
+++ b/Features/Enums/ToolGunObjectType.cs
@@ -10,5 +10,6 @@ public enum ToolGunObjectType
Capybara = 5,
Schematic = 6,
Scp079Camera = 7,
- ShootingTarget = 8
+ ShootingTarget = 8,
+ Teleporter = 9,
}
diff --git a/Features/Objects/TeleporterObject.cs b/Features/Objects/TeleporterObject.cs
new file mode 100644
index 0000000..33636a0
--- /dev/null
+++ b/Features/Objects/TeleporterObject.cs
@@ -0,0 +1,140 @@
+using GameCore;
+using LabApi.Features.Wrappers;
+using Mirror;
+using ProjectMER.Features.Serializable;
+using UnityEngine;
+using MEC;
+
+namespace ProjectMER.Features.Objects;
+
+public class TeleporterObject : MonoBehaviour
+{
+ // TODO:
+ // Implement OnTeleporting event
+ // Implement conditional teleport
+ // Implement pickup teleport
+ // Implement chance-based target select.
+
+ ///
+ /// Gets a indicating when this teleporter will next be usable.
+ ///
+ public DateTime WhenWillBeUsable { get; private set; }
+
+ ///
+ /// Gets a value indicating whether this teleporter is currently usable.
+ ///
+ public bool IsUsable => DateTime.Now > WhenWillBeUsable;
+
+ ///
+ /// Gets or sets the base for this object.
+ ///
+ public SerializableTeleporter Base { get; set; }
+
+ ///
+ /// Gets or sets the global position of the object.
+ ///
+ public Vector3 Position
+ {
+ get => transform.position;
+ set
+ {
+ transform.position = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the global rotation of the object.
+ ///
+ public Quaternion Rotation
+ {
+ get => transform.rotation;
+ set
+ {
+ transform.rotation = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the global euler angles of the object.
+ ///
+ public Vector3 EulerAngles
+ {
+ get => Rotation.eulerAngles;
+ set => Rotation = Quaternion.Euler(value);
+ }
+
+ ///
+ /// Gets or sets the scale of the object.
+ ///
+ public Vector3 Scale
+ {
+ get => transform.localScale;
+ set
+ {
+ transform.localScale = value;
+ }
+ }
+
+ ///
+ /// Gets a Dictionary to access teleporters by their ID.
+ ///
+ internal static List Teleporters { get; private set; } = new ();
+
+ private TeleporterObject GetTarget()
+ {
+ return Teleporters.FirstOrDefault(t => t.Base.Id == Base.Targets.RandomItem());
+ }
+
+ private bool TryGetTarget(out TeleporterObject teleporterObject)
+ {
+ teleporterObject = GetTarget();
+
+ return teleporterObject != null;
+ }
+
+ private void OnTriggerEnter(Collider other)
+ {
+ if (!IsUsable || !CanBeTeleported(other) || !TryGetTarget(out TeleporterObject target))
+ {
+ return;
+ }
+
+ Player? player = Player.Get(other.gameObject);
+ if (player is null)
+ {
+ return;
+ }
+
+ WhenWillBeUsable = DateTime.Now.AddSeconds(Base.Cooldown);
+ target.WhenWillBeUsable = DateTime.Now.AddSeconds(target.Base.Cooldown);
+
+
+ Timing.CallDelayed(0.05f, () =>
+ {
+ player.Position = target.Position;
+ try
+ {
+ player.LookRotation = target.EulerAngles;
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e);
+ }
+ });
+ }
+
+ private void Start()
+ {
+ Teleporters.Add(this);
+ }
+
+ private void OnDestroy()
+ {
+ Teleporters.Remove(this);
+ }
+
+ private bool CanBeTeleported(Collider collider)
+ {
+ return true;
+ }
+}
diff --git a/Features/Serializable/MapSchematic.cs b/Features/Serializable/MapSchematic.cs
index 5973ef6..8cbd231 100644
--- a/Features/Serializable/MapSchematic.cs
+++ b/Features/Serializable/MapSchematic.cs
@@ -37,6 +37,8 @@ public MapSchematic(string mapName)
public Dictionary ShootingTargets { get; set; } = [];
public Dictionary Schematics { get; set; } = [];
+
+ public Dictionary Teleporters { get; set; } = [];
public List SpawnedObjects = [];
@@ -51,6 +53,7 @@ public MapSchematic Merge(MapSchematic other)
Schematics.AddRange(other.Schematics);
Scp079Cameras.AddRange(other.Scp079Cameras);
ShootingTargets.AddRange(other.ShootingTargets);
+ Teleporters.AddRange(other.Teleporters);
return this;
}
@@ -81,6 +84,7 @@ public void Reload()
Schematics.ForEach(kVP => SpawnObject(kVP.Key, kVP.Value));
Scp079Cameras.ForEach(kVP => SpawnObject(kVP.Key, kVP.Value));
ShootingTargets.ForEach(kVP => SpawnObject(kVP.Key, kVP.Value));
+ Teleporters.ForEach(kVP => SpawnObject(kVP.Key, kVP.Value));
}
public void SpawnObject(string id, T serializableObject) where T : SerializableObject
@@ -136,13 +140,16 @@ public bool TryAddElement(string id, T serializableObject) where T : Serializ
if (Schematics.TryAdd(id, serializableObject))
return true;
-
+
if (Scp079Cameras.TryAdd(id, serializableObject))
return true;
if (ShootingTargets.TryAdd(id, serializableObject))
return true;
-
+
+ if (Teleporters.TryAdd(id, serializableObject))
+ return true;
+
return false;
}
@@ -168,6 +175,9 @@ public bool TryRemoveElement(string id)
if (Schematics.Remove(id))
return true;
+
+ if (Teleporters.Remove(id))
+ return true;
if (Scp079Cameras.Remove(id))
return true;
diff --git a/Features/Serializable/SerializableTeleporter.cs b/Features/Serializable/SerializableTeleporter.cs
new file mode 100644
index 0000000..7269bb5
--- /dev/null
+++ b/Features/Serializable/SerializableTeleporter.cs
@@ -0,0 +1,140 @@
+using AdminToys;
+using LabApi.Features.Wrappers;
+using Mirror;
+using PlayerRoles;
+using ProjectMER.Features.Extensions;
+using ProjectMER.Features.Interfaces;
+using ProjectMER.Features.Objects;
+using UnityEngine;
+using PrimitiveObjectToy = AdminToys.PrimitiveObjectToy;
+
+namespace ProjectMER.Features.Serializable;
+
+public class SerializableTeleporter : SerializableObject, IIndicatorDefinition
+{
+ // TODO:
+ // Add proper ID management
+ private int teleporterId;
+
+ ///
+ /// Gets or sets the teleporter ID for this teleporter.
+ ///
+ public int Id
+ {
+ get => teleporterId;
+ set
+ {
+ teleporterId = value;
+ }
+ }
+
+ public List Targets { get; set; } = new List();
+
+ public float Cooldown { get; set; } = 10f;
+
+ public override GameObject SpawnOrUpdateObject(Room? room = null, GameObject? instance = null)
+ {
+ GameObject primitive = instance == null ? GameObject.CreatePrimitive(PrimitiveType.Cube) : instance;
+ Vector3 position = room.GetAbsolutePosition(Position);
+ Quaternion rotation = room.GetAbsoluteRotation(Rotation);
+ _prevIndex = Index;
+
+ primitive.transform.SetPositionAndRotation(position, rotation);
+ primitive.transform.localScale = Scale;
+
+ if (!primitive.TryGetComponent(out TeleporterObject teleporter))
+ {
+ teleporter = primitive.AddComponent();
+ }
+
+ teleporter.Base = this;
+
+ if (primitive.TryGetComponent(out BoxCollider collider))
+ {
+ collider.isTrigger = true;
+ }
+
+ return primitive;
+ }
+
+ public GameObject SpawnOrUpdateIndicator(Room room, GameObject? instance = null)
+ {
+ PrimitiveObjectToy root;
+ PrimitiveObjectToy trigger;
+ PrimitiveObjectToy cylinder;
+ PrimitiveObjectToy arrowY;
+ PrimitiveObjectToy arrowX;
+ PrimitiveObjectToy arrow;
+
+ Vector3 position = room.GetAbsolutePosition(Position - new Vector3(0, 0.5f, 0));
+ Quaternion rotation = room.GetAbsoluteRotation(Rotation);
+
+ if (instance == null)
+ {
+ root = UnityEngine.Object.Instantiate(PrefabManager.PrimitiveObjectPrefab);
+ root.NetworkPrimitiveFlags = PrimitiveFlags.None;
+ root.name = "Indicator";
+ root.transform.position = position;
+
+ trigger = UnityEngine.Object.Instantiate(PrefabManager.PrimitiveObjectPrefab);
+ trigger.NetworkPrimitiveFlags = PrimitiveFlags.Visible;
+ trigger.name = "Trigger";
+ trigger.NetworkPrimitiveType = PrimitiveType.Cube;
+ trigger.transform.localScale = Scale;
+ trigger.transform.position = position + new Vector3(0, 0.5f, 0);
+ trigger.transform.parent = root.transform;
+
+ cylinder = GameObject.Instantiate(PrefabManager.PrimitiveObjectPrefab, root.transform);
+ cylinder.transform.localPosition = Vector3.zero;
+ cylinder.NetworkPrimitiveType = PrimitiveType.Cylinder;
+ cylinder.NetworkPrimitiveFlags = PrimitiveFlags.Visible;
+ cylinder.transform.localScale = new Vector3(1f, 0.001f, 1f);
+
+ arrowY = UnityEngine.Object.Instantiate(PrefabManager.PrimitiveObjectPrefab);
+ arrowY.NetworkPrimitiveFlags = PrimitiveFlags.None;
+ arrowY.name = "Arrow Y Axis";
+ arrowY.transform.parent = root.transform;
+
+ arrowX = UnityEngine.Object.Instantiate(PrefabManager.PrimitiveObjectPrefab);
+ arrowX.NetworkPrimitiveFlags = PrimitiveFlags.None;
+ arrowX.name = "Arrow X Axis";
+ arrowX.transform.parent = arrowY.transform;
+
+ arrow = GameObject.Instantiate(PrefabManager.PrimitiveObjectPrefab, arrowX.transform);
+ arrow.transform.localPosition = root.transform.forward;
+ arrow.NetworkPrimitiveType = PrimitiveType.Cube;
+ arrow.NetworkPrimitiveFlags = PrimitiveFlags.Visible;
+ arrow.transform.localScale = new Vector3(0.1f, 0.1f, 1f);
+ }
+ else
+ {
+ root = instance.GetComponent();
+
+ trigger = root.transform.Find("Trigger").GetComponent();
+ arrowY = root.transform.Find("Arrow Y Axis").GetComponent();
+ arrowX = arrowY.transform.Find("Arrow X Axis").GetComponent();
+
+ trigger.transform.localScale = Scale;
+ }
+
+ root.transform.position = position;
+ arrowY.transform.localPosition = Vector3.up * 1.6f;
+ arrowY.transform.localEulerAngles = new Vector3(0f, rotation.eulerAngles.y, 0f);
+ arrowX.transform.localPosition = Vector3.zero;
+ arrowX.transform.localEulerAngles = new Vector3(-rotation.eulerAngles.x, 0f, 0f);
+
+ foreach (PrimitiveObjectToy primitive in root.GetComponentsInChildren())
+ {
+ if (Targets.Count > 0)
+ {
+ primitive.NetworkMaterialColor = new Color(0.11f, 0.98f, 0.92f, 0.5f);
+ }
+ else
+ {
+ primitive.NetworkMaterialColor = new Color(1f, 1f, 1f, 0.25f);
+ }
+ }
+
+ return root.gameObject;
+ }
+}
\ No newline at end of file
diff --git a/Features/ToolGun/ToolGunHandler.cs b/Features/ToolGun/ToolGunHandler.cs
index 0181426..89da760 100644
--- a/Features/ToolGun/ToolGunHandler.cs
+++ b/Features/ToolGun/ToolGunHandler.cs
@@ -47,6 +47,12 @@ public static void CreateObject(Vector3 position, ToolGunObjectType objectType,
break;
}
+ case SerializableTeleporter _:
+ {
+ serializableObject.Position = position + Vector3.up * 0.5f;
+ break;
+ }
+
case SerializableSchematic serializableSchematic:
{
serializableObject.Position = position;
diff --git a/Features/ToolGun/ToolGunItem.cs b/Features/ToolGun/ToolGunItem.cs
index 1ee812c..4af0899 100644
--- a/Features/ToolGun/ToolGunItem.cs
+++ b/Features/ToolGun/ToolGunItem.cs
@@ -27,6 +27,7 @@ public class ToolGunItem
{ ToolGunObjectType.Schematic, typeof(SerializableSchematic) },
{ ToolGunObjectType.Scp079Camera, typeof(SerializableScp079Camera) },
{ ToolGunObjectType.ShootingTarget, typeof(SerializableShootingTarget) },
+ { ToolGunObjectType.Teleporter, typeof(SerializableTeleporter) },
};
private ToolGunObjectType _selectedObjectToSpawn;