OnPhoneDial
OnPhoneDial is called on the PhoneController.CallPhone path after a dialed number resolves to an existing telephone, but before the call state changes to Dialing. Returning a non-null value cancels the dial.
object OnPhoneDial(PhoneController phoneController, PhoneController telephone, BasePlayer currentPlayer)Parameters
| Parameter | Type | Description |
|---|---|---|
phoneController | PhoneController | The PhoneController of the player who is placing the call. Compare it against telephone to detect self-dials or key off its owner. |
telephone | PhoneController | The targeted PhoneController resolved from the dialed number, or null if no telephone with that number exists. Only a non-null value reaches the hook. |
currentPlayer | BasePlayer | The BasePlayer initiating the call. Use it for permission checks, distance checks, team comparisons and to send chat feedback. |
Return value
Returning null (or having no hook) lets the dial continue into the normal idle and receive checks. Returning any non-null value (including false) aborts the original CallPhone path immediately: no Dialing state is set and the last dialed number is not stored.
How it works
The hook fires when a player submits a number on a telephone and the game has already resolved that number to a PhoneController (TelephoneManager.GetTelephone). It runs before the target's idle and receive state is validated and before SetPhoneState(Telephone.CallState.Dialing) and lastDialedNumber are written, so it is the correct place to approve or reject a call. The first argument is the caller's controller, the second is the resolved target, and currentPlayer is the player who dialed. Because the target is resolved before the hook, telephone is never null when the hook runs; a dial of an unknown number follows a different path and never reaches it. Declaring telephone or phoneController as a narrower type is of little use here since the resolved type is always PhoneController. Note that blocking here does not send any message to the caller - the game simply returns, so tell the player yourself. The hook also fires only on the initiating side; the receiving phone's behavior is governed by the answer and call-start path.
Example plugin: Restrict phone calls to teammates with a permission
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Phone Team Dial", "RustPluginGenerator", "1.0.0")]
[Description("Restricts telephone dials to teammates and optionally requires a permission")]
public class PhoneTeamDial : RustPlugin
{
private const string PermUse = "phoneteamdial.use";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Require permission to dial")]
public bool RequirePermission { get; set; } = true;
[JsonProperty("Block calls outside the caller's team")]
public bool TeamOnly { get; set; } = true;
}
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>
{
["NoPermission"] = "You do not have permission to use the telephone network.",
["NotTeam"] = "You can only call members of your own team."
}, this);
}
private string Msg(string key, string userId = null) => lang.GetMessage(key, this, userId);
private void Init()
{
permission.RegisterPermission(PermUse, this);
}
private object OnPhoneDial(PhoneController phoneController, PhoneController telephone, BasePlayer currentPlayer)
{
if (currentPlayer == null || telephone == null) return null;
if (config.RequirePermission && !permission.UserHasPermission(currentPlayer.UserIDString, PermUse))
{
currentPlayer.ChatMessage(Msg("NoPermission", currentPlayer.UserIDString));
return false;
}
if (!config.TeamOnly) return null;
BaseEntity entity = telephone.baseEntity;
if (entity == null) return null;
BasePlayer target = BasePlayer.FindByID(entity.OwnerID);
if (target == null || target.userID == currentPlayer.userID) return null;
if (IsSameTeam(currentPlayer, target)) return null;
currentPlayer.ChatMessage(Msg("NotTeam", currentPlayer.UserIDString));
return false;
}
private bool IsSameTeam(BasePlayer player, BasePlayer other)
{
if (player.currentTeam == 0 || player.currentTeam != other.currentTeam) return false;
var team = RelationshipManager.ServerInstance?.FindTeam(player.currentTeam);
return team != null && team.members != null && team.members.Contains(other.userID);
}
}
}The plugin reads two config values: RequirePermission controls whether players need phoneteamdial.use to dial at all, and TeamOnly controls whether calls outside the caller's team are blocked. OnPhoneDial resolves the target's owner with BasePlayer.FindByID(telephone.baseEntity.OwnerID) and compares it against the caller. Same-player dials are always allowed so players can test their own phones. Team membership is checked through RelationshipManager.ServerInstance?.FindTeam; team id 0, a missing team, or a missing members list all count as not being on the same team. Blocked dials return false to stop the call and send a localized chat message explaining why, while allowed dials return null. Permissions are checked against currentPlayer.UserIDString.
Common use cases
Team-only telephone network
Force calls to stay inside a team so cross-map call spam and information leaks through random number dialing are impossible.
Paid or permission-gated calling
Check a permission such as
phonevip.callbefore allowing dials, and point players to a store or command that grants it.Call cooldown and anti-spam
Track a per-player timestamp in a dictionary and reject dials that arrive too soon, stopping rapid redial scripts from occupying lines.
Pitfalls
The hook does not run for unknown numbers
Numbers without a registered telephone never reach OnPhoneDial, so you cannot use it to block or log failed lookups. Use the dial-fail hooks for that side.
No feedback is sent on cancel
Returning a value only stops the dial path. Without your own
ChatMessageorSendReplythe caller sees nothing happen and may assume the plugin is broken.getOwnerPlayer can be null
Entities without a parent player return null, so always null-check the resolved owner before comparing UserIDs or team ids.
Build a plugin with OnPhoneDial
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 OnPhoneDial
Generate