CanTeleportDeepSea
CanTeleportDeepSea is called when a TriggerDeepSeaPortal checks whether an entity may pass through a deep sea portal. Returning a tuple of bool and a phrase replaces the game's own answer.
object CanTeleportDeepSea(BaseEntity entity, DeepSeaPortal portal)Parameters
| Parameter | Type | Description |
|---|---|---|
entity | BaseEntity | The BaseEntity being checked for teleport eligibility. It can be any entity, not only a player, so cast with `as BasePlayer` and null-check before using player-specific members. |
portal | DeepSeaPortal | The DeepSeaPortal component that owns the trigger. Use its `PortalMode` to distinguish entrance from exit behavior when you need different rules per direction. |
Return value
Return null to let the original portal logic decide. Return a ValueTuple<bool, Translate.Phrase> (written as (bool, Translate.Phrase)) and the method returns it immediately: the bool becomes the allow/deny answer and the phrase is the message used on denial. Any other return type is ignored and the vanilla path continues.
How it works
The hook fires inside TriggerDeepSeaPortal.CanEntityTeleport, the method the portal trigger runs before it decides to move an entity. Because the check runs for whatever entity touched the trigger, entity is typed as BaseEntity; in practice players are the common case, but vehicles, dropped items or NPCs can reach the same path. portal is the DeepSeaPortal instance, and its PortalMode value (Entrance or Exit) tells you which direction the entity is trying to travel.
The game's own result is a pair: a boolean that says whether teleporting is allowed, and a Translate.Phrase used as the failure message shown to the player when the answer is negative. Declaring the hook with exactly the given parameter types catches every call. Returning null lets the vanilla logic run unchanged; this is the safe default for any server that only wants to observe. There is no separate event hook here, so this hook is the only way to intercept deep sea portal travel.
Keep the handler cheap: the code runs on the trigger path and can fire repeatedly if an entity stands on the portal.
Example plugin: Block portal teleport for players with a cooldown
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Deep Sea Portal Cooldown", "RustPluginGenerator", "1.0.0")]
[Description("Enforces a per-player cooldown between deep sea portal trips.")]
public class DeepSeaPortalCooldown : RustPlugin
{
private const string PermBypass = "deepseaportalcooldown.bypass";
private PluginConfig config;
private readonly Dictionary<ulong, DateTime> cooldowns = new Dictionary<ulong, DateTime>();
private Timer sweepTimer;
private class PluginConfig
{
[JsonProperty("Cooldown seconds")]
public int CooldownSeconds = 300;
}
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Cooldown"] = "You cannot use the deep sea portal for another {0} seconds."
}, this);
}
private string Msg(string key, string userId = null, params object[] args)
=> string.Format(lang.GetMessage(key, this, userId), args);
private void Init()
{
permission.RegisterPermission(PermBypass, this);
}
private void OnServerInitialized()
{
sweepTimer = timer.Every(120f, SweepCooldowns);
}
private void Unload()
{
sweepTimer?.Destroy();
cooldowns.Clear();
}
private object CanTeleportDeepSea(BaseEntity entity, DeepSeaPortal portal)
{
if (entity == null || portal == null) return null;
var player = entity as BasePlayer;
if (player == null || player.IsNpc) return null;
if (permission.UserHasPermission(player.UserIDString, PermBypass)) return null;
DateTime readyAt;
if (!cooldowns.TryGetValue(player.userID.Get(), out readyAt)) return null;
double remaining = (readyAt - DateTime.UtcNow).TotalSeconds;
if (remaining <= 0) return null;
var phrase = new Translate.Phrase("portal.cooldown", Msg("Cooldown", player.UserIDString, Math.Ceiling(remaining)));
return (false, phrase);
}
private void OnDeepSeaTeleport(BasePlayer player)
{
if (player == null || player.IsNpc) return;
cooldowns[player.userID.Get()] = DateTime.UtcNow.AddSeconds(config.CooldownSeconds);
}
private void SweepCooldowns()
{
if (cooldowns.Count == 0) return;
var now = DateTime.UtcNow;
var expired = new List<ulong>();
foreach (var entry in cooldowns)
{
if (entry.Value <= now) expired.Add(entry.Key);
}
for (int i = 0; i < expired.Count; i++) cooldowns.Remove(expired[i]);
}
}
}The plugin tracks a per-player cooldown and denies portal travel when it is not expired. CanTeleportDeepSea casts the entity to BasePlayer, looks the cooldown up in a dictionary keyed by user ID, and either returns null so the vanilla answer stands, or returns a tuple with false and a phrase built from the message file so the player sees a readable denial. On a successful pass recorded through OnDeepSeaTeleport, the cooldown timestamp is set to the configured number of seconds. A repeating timer clears expired entries so the dictionary cannot grow without bound, and both the timer and the data are released in Unload.
Common use cases
Team-only portals
Deny travel unless the player belongs to the configured team or has an auth permission, using
entity as BasePlayerandpermission.UserHasPermission.Cooldown between trips
Store a per-player timestamp and return a denial tuple until the configured interval has passed, preventing instant back-and-forth hopping.
Block vehicles and loot
Return a denial when the entity is a ModularCar or DroppedItemContainer so only players travel through the portal.
Pitfalls
Returning a tuple of the wrong shape
The check accepts only a non-null value of type
(bool, Translate.Phrase). Returning a bare bool does nothing, because Oxide discards values that do not match the expected type.Entity is not always a player
entityis typedBaseEntity. Casting withas BasePlayerand forgetting the null check will throw when a vehicle or item triggers the portal.Denial phrase is what players see
The second tuple member is the message shown on refusal. Returning an empty phrase produces a blank notification, so build it from your language file.
Build a plugin with CanTeleportDeepSea
Describe what should happen and get a compiled plugin that uses this hook.
Edit the prompt on the next screen. 2 free plugins a month, no card.
Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.
Build a plugin with CanTeleportDeepSea
Generate