OnDroppedItemCombined
OnDroppedItemCombined is called when a dropped item stack lands on or merges with another compatible dropped item on the ground. It fires after the stackability check passes but before the two item amounts are summed.
void OnDroppedItemCombined(DroppedItem droppedItem)Parameters
| Parameter | Type | Description |
|---|---|---|
droppedItem | DroppedItem | The DroppedItem entity that is receiving the stacking merge. Its item container holds the resulting stack once the merge completes, so read amounts after a short delay or via OnItemStacked rather than immediately relying on the pre-merge value. |
Return value
The hook is called only for its side effects; the return value is ignored. The merge proceeds regardless of what you return, so blocking a combine must be done in CanCombineDroppedItem, not here.
How it works
The hook fires from DroppedItem.OnDroppedOn, the method that runs when one dropped item is dropped onto another. The game first bails out if the incoming item is null, if CanCombineDroppedItem was blocked by a plugin, if either pile has NeverCombine set, or if item.CanStack(di.item) returns false. Only when all of that passes does Oxide call OnDroppedItemCombined, passing the target DroppedItem. The merge then adds the two amounts and either keeps a single stack or splits into multiple piles depending on MaxStackable().
Because the call happens before the amounts are combined, droppedItem.item.amount at hook time still reflects the target pile's pre-merge count. Use the hook to detect ground merges for logging, anti-dupe checks, stacking XP, or cleanup of nearby piles. Declaring the parameter as DroppedItem (the only valid type here) rather than a base type avoids method-not-found issues; there is no more specific subtype to narrow to. Keep the body cheap — this can run every time a player drops loot onto an existing pile, which happens frequently during fights and farming.
Example plugin: Log ground pile merges for admins with a cooldown
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("DroppedItemMergeLog", "RustPluginGenerator", "1.0.0")]
[Description("Counts ground pile merges and prints a periodic summary for admins.")]
public class DroppedItemMergeLog : RustPlugin
{
private const string PermView = "droppeditemmergelog.view";
private PluginConfig config;
private Timer summaryTimer;
private int totalMerges;
private Vector3 lastMergePosition;
private readonly Dictionary<string, int> shortnameCounts = new Dictionary<string, int>();
private class PluginConfig
{
[JsonProperty("Report interval (seconds)")]
public float ReportInterval = 120f;
[JsonProperty("Print to console when interval elapses")]
public bool PrintToConsole = 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>
{
["MergeReport"] = "Ground merges since last report: {0}. Most common: {1}.",
["Reset"] = "Merge counter reset.",
["NoPerm"] = "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(PermView, this);
}
private void OnServerInitialized()
{
StartSummaryTimer();
}
private void Unload()
{
summaryTimer?.Destroy();
summaryTimer = null;
}
private void StartSummaryTimer()
{
summaryTimer?.Destroy();
float interval = Mathf.Max(10f, config.ReportInterval);
summaryTimer = timer.Every(interval, FlushReport);
}
private void OnDroppedItemCombined(DroppedItem droppedItem)
{
if (droppedItem == null || droppedItem.item == null || droppedItem.item.info == null) return;
totalMerges++;
lastMergePosition = droppedItem.transform.position;
string shortname = droppedItem.item.info.shortname;
shortnameCounts.TryGetValue(shortname, out int current);
shortnameCounts[shortname] = current + 1;
}
private void FlushReport()
{
if (totalMerges == 0) return;
string topShortname = "none";
int topCount = 0;
foreach (var kvp in shortnameCounts)
{
if (kvp.Value > topCount)
{
topCount = kvp.Value;
topShortname = kvp.Key;
}
}
if (config.PrintToConsole)
{
Puts(string.Format("Ground merges since last report: {0}. Most common: {1} ({2}x) at {3}.",
totalMerges, topShortname, topCount, lastMergePosition));
}
foreach (var player in BasePlayer.activePlayerList)
{
if (player != null && permission.UserHasPermission(player.UserIDString, PermView))
{
player.ChatMessage(Msg("MergeReport", player.UserIDString, totalMerges, topShortname));
}
}
totalMerges = 0;
shortnameCounts.Clear();
}
[ChatCommand("mergelog.reset")]
private void CmdReset(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermView))
{
player.ChatMessage(Msg("NoPerm", player.UserIDString));
return;
}
totalMerges = 0;
shortnameCounts.Clear();
player.ChatMessage(Msg("Reset", player.UserIDString));
}
}
}The plugin records every ground merge it sees and prints a periodic summary so admins can spot suspicious stacking (for example, a player dumping loot to build large piles). OnDroppedItemCombined captures the target pile's short prefab name and item position, increments a counter, and stores the last merge location. A repeating timer prints a single line to the console with the merge count and the highest-frequency item shortname. A permission check gates an optional chat command that resets the counter, so only authorized staff can clear the stats. No game behavior is modified; the return value is ignored by the game.
Common use cases
Anti-dupe verification
Track how often ground piles merge and correlate with player inventories to spot patterns where net item counts increase unexpectedly during heavy loot drops.
Loot density monitoring
Count merges in a region to detect players dumping resources in the open, which often indicates stashing or an intended trade spot.
Themed event triggers
Fire a chat announcement or effect when a specific item shortname (such as scrap or a rare resource) merges, without blocking the merge itself.
Pitfalls
Return value does nothing
The game ignores whatever you return here. To prevent a merge, use
CanCombineDroppedItemand return a non-null value; returning fromOnDroppedItemCombinedhas no effect on stacking.Amounts are pre-merge
droppedItem.item.amountinside the hook still reflects the target pile before the incoming item is added. If you need the combined total, schedule aNextTickor read it inOnItemStacked.Frequent invocation under load
Players dropping loot onto existing piles triggers this hook often during combat and farming. Avoid
Puts, chat broadcasts, or database writes directly inside it; aggregate and flush on a timer.
Build a plugin with OnDroppedItemCombined
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 OnDroppedItemCombined
Generate