OnFreeableContainerReleased
OnFreeableContainerReleased is called at the end of FreeableLootContainer.Release, after the crate has been freed and the releasing player's mission event and analytics have been processed.
void OnFreeableContainerReleased(FreeableLootContainer freeableLootContainer, BasePlayer ply)Parameters
| Parameter | Type | Description |
|---|---|---|
freeableLootContainer | FreeableLootContainer | The FreeableLootContainer instance that was just released. This is the freeable crate entity (for example the underwater freeable crate); it is the same object that fired the release. Null-check it before reading net, ShortPrefabName or position. |
ply | BasePlayer | The player responsible for the release, or null when the release was not caused by a connected non-NPC player. The game only records analytics and mission progress when this is a real, connected, non-NPC player. |
Return value
The return value is ignored. The hook is patched with continue behavior, so returning null or any other value does not change the outcome of Release, which has already completed by the time the hook runs.
How it works
This hook fires inside FreeableLootContainer.Release, after the container has already been released by the game. Rust uses FreeableLootContainer for crates that stay locked until a player interacts with them in a specific way, such as the underwater freeable crate. The release happens first, then the game runs its own mission-event and analytics block for the releasing player, and finally Oxide calls OnFreeableContainerReleased.
The first parameter is the container entity itself, useful for its net.ID, ShortPrefabName, owner and world position. The second is the player who triggered the release; it is null whenever the release was not performed by a connected, non-NPC player, so always null-check it. Because the game has already finished releasing at this point, this hook is for observation and follow-up logic, not for blocking the release.
This is a rare event tied to a specific entity type, so the overhead of a handler is negligible. You cannot narrow the call by declaring a different parameter type here in a useful way - declare the exact FreeableLootContainer and BasePlayer types and guard against a null player inside the method.
Example plugin: Announce and reward underwater freeable crate releases
using System.Linq;
using UnityEngine;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Freeable Container Reward", "RustPluginGenerator", "1.0.0")]
[Description("Announces freeable crate releases and optionally rewards the player who freed them")]
public class FreeableContainerReward : RustPlugin
{
private const string PermAnnounce = "freeablecontainerreward.announce";
private PluginConfig config;
private readonly Dictionary<ulong, double> cooldowns = new Dictionary<ulong, double>();
private class PluginConfig
{
[JsonProperty("Broadcast release to chat")]
public bool Broadcast { get; set; } = true;
[JsonProperty("Give reward item")]
public bool GiveReward { get; set; } = true;
[JsonProperty("Reward item shortname")]
public string RewardShortname { get; set; } = "scrap";
[JsonProperty("Reward amount")]
public int RewardAmount { get; set; } = 50;
[JsonProperty("Cooldown seconds per player")]
public double CooldownSeconds { get; set; } = 60.0;
}
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>
{
["Released"] = "{0} freed a crate at {1}",
["Rewarded"] = "You received {0} x{1} for freeing the crate",
["OnCooldown"] = "Reward cooldown active, freeing waited"
}, 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(PermAnnounce, this);
}
private void OnFreeableContainerReleased(FreeableLootContainer freeableLootContainer, BasePlayer ply)
{
if (freeableLootContainer == null || ply == null) return;
if (!ply.IsConnected || ply.IsNpc) return;
double now = UnityEngine.Time.realtimeSinceStartup;
double last;
if (cooldowns.TryGetValue(ply.userID.Get(), out last) && now - last < config.CooldownSeconds)
return;
cooldowns[ply.userID.Get()] = now;
if (config.Broadcast)
{
string pos = FormatPosition(freeableLootContainer.transform.position);
Server.Broadcast(Msg("Released", null, ply.displayName, pos));
}
if (!config.GiveReward) return;
if (ItemManager.FindItemDefinition(config.RewardShortname) == null)
{
PrintWarning("Reward shortname '" + config.RewardShortname + "' is not a valid item");
return;
}
Item item = ItemManager.CreateByName(config.RewardShortname, Math.Max(1, config.RewardAmount));
if (item == null) return;
ply.GiveItem(item);
ply.ChatMessage(Msg("Rewarded", ply.UserIDString, item.info.displayName.english, item.amount));
}
private static string FormatPosition(UnityEngine.Vector3 position)
{
return string.Format("{0:0},{1:0}", position.x, position.z);
}
}
}The plugin listens for OnFreeableContainerReleased and confirms that a connected, non-NPC player caused the release. It broadcasts a message through the language file with the player's display name and the crate position, then gives an optional reward item if enabled in the config. A per-player cooldown (CooldownSeconds) prevents both spam and repeat rewards from rapid releases, tracked in a Dictionary<ulong, double> against Time.realtimeSinceStartup. All player-facing text goes through the lang file, and the reward item is validated on load by checking ItemManager.FindItemDefinition so a typo in the config does not produce a null item at runtime.
Common use cases
Reward the player who freed the crate
Give a scrap or item reward, or call into an economy plugin, when a player releases a freeable container. Guard on the cooldown dictionary so repeated events do not stack rewards.
Track freeable crate activity for admin stats
Record the releasing player, crate position and timestamp into an Oxide data file so admins can review who is farming underwater crates and how often.
Broadcast a server-wide notification
Announce in chat that a crate was freed and where, which is useful on PvE or lightly populated servers where the event is rare and notable.
Pitfalls
The player can be null
The game skips its own mission and analytics block when the player is null, NPC, or disconnected, but still calls the hook. Always check
ply != nullandply.IsConnectedbefore using it.This cannot block the release
The hook runs after the container has already been released, and its return value is ignored. Use
OnFreeableContainerReleaseStartedif you need to intervene before the release completes.Do not confuse it with generic crate hooks
OnCrateSpawned,OnCrateLandedand similar hooks fire for other crate types. Only FreeableLootContainer instances, such as the underwater freeable crate, trigger this one, so do not expect it for airdrops or hackable crates.
Build a plugin with OnFreeableContainerReleased
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 OnFreeableContainerReleased
Generate