Skip to content
RustPluginGenerator

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.

Signature
void OnDroppedItemCombined(DroppedItem droppedItem)

Parameters

Hook parameters
ParameterTypeDescription
droppedItemDroppedItemThe 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

DroppedItemMergeLog.cs
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 CanCombineDroppedItem and return a non-null value; returning from OnDroppedItemCombined has no effect on stacking.

  • Amounts are pre-merge

    droppedItem.item.amount inside the hook still reflects the target pile before the incoming item is added. If you need the combined total, schedule a NextTick or read it in OnItemStacked.

  • 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.

Prompt: Anti-dupe verification using the OnDroppedItemCombined hook, with a config file and a permission
Generate this plugin

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