OnPhoneDialTimedOut
OnPhoneDialTimedOut is called on the server after a phone call attempt rings out without being answered and the caller's phone has already been reset to the idle state.
void OnPhoneDialTimedOut(PhoneController activeCallTo, PhoneController phoneController, BasePlayer currentPlayer)Parameters
| Parameter | Type | Description |
|---|---|---|
activeCallTo | PhoneController | The PhoneController of the receiver that was being dialled when the timeout fired. Null when nobody answered, so always null-check before using it. |
phoneController | PhoneController | The PhoneController of the phone that was doing the dialling (the caller's handset). Use it to get the owner, position or phone number of the caller. |
currentPlayer | BasePlayer | The BasePlayer who placed the call, taken from activeCallTo.currentPlayer. Null if the receiving phone had no player behind it, so treat it as optional. |
Return value
The original method returns void and the hook's return value is ignored, so returning null or any value changes nothing. The call has already been terminated before the hook runs.
How it works
The hook fires inside PhoneController.TimeOutDialing, which runs when a dialled call keeps ringing until the dial timeout expires. By the time Oxide calls the hook the game has already played the answering message on the receiving phone, set the caller's Telephone.CallState to Idle and cleared the active call, so the call is over regardless of what your plugin does.
activeCallTo is the receiving PhoneController (the phone that was being called) and is typically non-null but may have no player attached; phoneController is the caller's handset; currentPlayer is a shortcut to activeCallTo.currentPlayer and is null when the receiving phone has no player. Use phoneController to identify the caller (for example through GetOwnerPlayer() or the phone entity position) rather than currentPlayer, since the latter describes the receiving side.
The hook is only patched into TimeOutDialing, so there is no variant fire per dial attempt. Declaring BasePlayer for currentPlayer narrows nothing here; Oxide simply skips the call if the argument is not a BasePlayer (null), which for this hook means you will not be notified of unanswered calls to unattended phones. Keep the body cheap; it can fire for many phones on a busy server.
Example plugin: Notify callers when their dial times out
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Phone Timeout Notice", "RustPluginGenerator", "1.0.0")]
[Description("Notifies callers when their dial times out and can broadcast missed calls to the server.")]
public class PhoneTimeoutNotice : RustPlugin
{
private const string PermNotify = "phonetimeoutnotice.notify";
private const string PermToggle = "phonetimeoutnotice.toggle";
private class PluginConfig
{
[JsonProperty("Broadcast missed calls to everyone")]
public bool BroadcastMissedCalls { get; set; } = false;
}
private PluginConfig config;
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>
{
["Timeout"] = "Your call was not answered.",
["Broadcast"] = "{0} called an unanswered phone at {1}.",
["ToggleOn"] = "Missed call broadcasts are now on.",
["ToggleOff"] = "Missed call broadcasts are now off.",
["NoPermission"] = "You do not have permission to use this command."
}, 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(PermNotify, this);
permission.RegisterPermission(PermToggle, this);
}
private void OnPhoneDialTimedOut(PhoneController activeCallTo, PhoneController phoneController, BasePlayer currentPlayer)
{
if (phoneController == null) return;
// Prefer the initiating player when available, otherwise fall back to the entity owner
BasePlayer caller = currentPlayer;
if (caller == null)
{
BaseEntity phoneEntity = phoneController.GetComponent<BaseEntity>();
if (phoneEntity != null)
{
caller = BasePlayer.FindByID(phoneEntity.OwnerID);
}
}
if (caller == null || !caller.IsConnected) return;
if (permission.UserHasPermission(caller.UserIDString, PermNotify))
{
caller.ChatMessage(Msg("Timeout", caller.UserIDString));
}
if (config.BroadcastMissedCalls)
{
Vector3 pos = phoneController.transform.position;
Server.Broadcast(Msg("Broadcast", null, caller.displayName, $"({Mathf.RoundToInt(pos.x)}, {Mathf.RoundToInt(pos.z)})"));
}
}
[ChatCommand("phonetimeouts")]
private void CmdPhoneTimeouts(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermToggle))
{
player.ChatMessage(Msg("NoPermission", player.UserIDString));
return;
}
config.BroadcastMissedCalls = !config.BroadcastMissedCalls;
SaveConfig();
player.ChatMessage(Msg(config.BroadcastMissedCalls ? "ToggleOn" : "ToggleOff", player.UserIDString));
}
}
}The plugin listens for OnPhoneDialTimedOut and uses the provided currentPlayer as the caller when available; otherwise it falls back to the owner of the phone entity. For permission holders it sends a localised chat message; players without the permission get no direct notice, which keeps the notice to a chosen group. A separate chat command lets a permission holder toggle a server broadcast that announces unanswered calls to everyone online. The config file stores only the broadcast toggle, and messages live in the language file so servers can translate them.
Common use cases
Log missed calls for staff
Store the caller and the dialled phone number in a data file so admins can review who is spamming calls or failing to reach specific numbers.
Cooldown nag for repeat dialers
Track timeouts per player ID in memory and reply with a short message after several unanswered calls, discouraging spam dialling.
Sound or effect feedback
Play a distinct effect near the caller's position when their call rings out, giving audible confirmation the call ended.
Pitfalls
Hook is skipped for unattended receivers
If the receiving phone has no player, the
currentPlayerargument is null, which means an Oxide hook method declaring BasePlayer is not called at all. Derive the caller fromphoneControllerinstead of depending oncurrentPlayer.Call state is already reset
The game sets the caller's state to Idle and clears the active call before the hook fires, so do not try to cancel, retry or extend the call from here; nothing you return can change the outcome.
PhoneController may be stale
Do not cache the controller references across frames; by the time a timer runs the phone may be destroyed. Fetch the owner player immediately and null-check it.
Build a plugin with OnPhoneDialTimedOut
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 OnPhoneDialTimedOut
Generate