Skip to content
RustPluginGenerator

OnApartmentRoomPurchased

OnApartmentRoomPurchased is called after a player successfully buys an apartment room in an ApartmentBuilding, once the scrap has been taken and the room has been assigned to them.

Signature
void OnApartmentRoomPurchased(ApartmentRoom apartmentRoom, BasePlayer player, ApartmentSize size, ApartmentBuilding apartmentBuilding)

Parameters

Hook parameters
ParameterTypeDescription
apartmentRoomApartmentRoomThe ApartmentRoom instance that was just handed to the player. Use it as a stable identifier for the rented unit and to read its runtime state after the sale.
playerBasePlayerThe BasePlayer who paid for the room. Its inventory has already been charged for the scrap cost by the time the hook fires.
sizeApartmentSizeThe ApartmentSize that was purchased. Compare it against other size values to price rewards, log tier differences, or scale grant amounts.
apartmentBuildingApartmentBuildingThe ApartmentBuilding that owns the room. Useful for grouping purchases by building and for broadcasting to nearby players.

Return value

The return value is ignored. PurchaseRoom returns void and the hook is called as the final statement of the method, so returning null, true, or any object will not change the purchase, the scrap charge, or the room assignment.

How it works

This hook fires at the end of ApartmentBuilding.PurchaseRoom, after the scrap cost has been removed from the player's inventory, the room has been given to them via the building's internal give-room step, and the RENT_APARTMENT achievement has been processed. It is a pure notification: the transaction is already complete and nothing you return affects it.

All four parameters describe the same completed purchase from different angles. apartmentRoom is the rented unit itself, player is the buyer, size is the purchased tier, and apartmentBuilding is the parent structure. For most plugins the buyer and the size are the fields you act on: granting a bonus, announcing the rent to a team or chat, or writing an audit entry to a data file.

Because the hook only fires on the purchase path, declaring the parameters exactly as given is enough; there is no useful narrowing to a subtype here since ApartmentRoom and ApartmentBuilding have no relevant subclasses for this event. Avoid heavy work in the handler, but a chat message, a timer, or a small data write is fine.

If the player is disconnected or has been destroyed by the time you react, null-check before touching player.

Example plugin: Announce apartment purchases and grant a scrap refund rebate

ApartmentPurchaseAnnouncer.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Apartment Purchase Announcer", "RustPluginGenerator", "1.0.0")]
    [Description("Announces apartment room purchases and grants a configurable scrap rebate")]
    public class ApartmentPurchaseAnnouncer : RustPlugin
    {
        private const string PermRebate = "apartmentpurchaseannouncer.rebate";
        private const string ScrapShortname = "scrap";

        private PluginConfig config;

        private class PluginConfig
        {
            [JsonProperty("Broadcast purchases globally")]
            public bool Broadcast { get; set; } = true;

            [JsonProperty("Rebate percentage of scrap cost")]
            public int RebatePercent { get; set; } = 10;

            [JsonProperty("Rebate requires permission")]
            public bool RebateRequiresPermission { get; set; } = false;
        }

        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>
            {
                ["Broadcast"] = "{0} just rented {1} apartment space ({2} scrap).",
                ["Rebate"] = "Your apartment rebate: {0} scrap.",
                ["RebateDenied"] = "No rebate this time. Ask an admin about the rental rebate permission."
            }, 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(PermRebate, this);
        }

        private void OnApartmentRoomPurchased(ApartmentRoom apartmentRoom, BasePlayer player, ApartmentSize size, ApartmentBuilding apartmentBuilding)
        {
            if (player == null || player.inventory == null) return;

            int cost = GetScrapCost(size);

            if (config.Broadcast)
            {
                string building = apartmentBuilding != null ? $"#{apartmentBuilding.net.ID.Value}" : "unknown building";
                Server.Broadcast(Msg("Broadcast", null, player.displayName, size.ToString(), building, cost));
            }

            if (config.RebatePercent <= 0) return;

            if (config.RebateRequiresPermission && !permission.UserHasPermission(player.UserIDString, PermRebate))
            {
                SendReply(player, Msg("RebateDenied", player.UserIDString));
                return;
            }

            int rebate = Mathf.Clamp(Mathf.RoundToInt(cost * (config.RebatePercent / 100f)), 0, cost);
            if (rebate <= 0) return;

            Item item = ItemManager.CreateByName(ScrapShortname, rebate);
            if (item == null) return;

            player.GiveItem(item);
            SendReply(player, Msg("Rebate", player.UserIDString, rebate));
        }

        private static int GetScrapCost(ApartmentSize size)
        {
            switch (size)
            {
                case ApartmentSize.Small: return 25;
                case ApartmentSize.Medium: return 50;
                case ApartmentSize.Large: return 100;
                default: return 0;
            }
        }
    }
}

The plugin configures a rebate percentage and a broadcast toggle. When OnApartmentRoomPurchased fires, it validates the player reference, formats the tier name and a short building identifier, and broadcasts a chat line to the server when announcements are enabled. It then computes the rebate as a percentage of the tier's scrap cost (a simple table keyed by ApartmentSize) and awards it with ItemManager.CreateByName("scrap") delivered through GiveItem, which drops the item at the player's feet if their inventory is full. All player-facing text goes through the language file so admins can translate it. Rebate amounts are clamped so a misconfigured percentage cannot create negative items.

Common use cases

  • Server-wide rental announcements

    Broadcast that a player rented a specific apartment tier so other players know the building is filling up. Pair it with a distance check against the apartment building for local-only announcements.

  • Loyalty rebates on rent

    Refund a configurable percentage of the scrap cost as a reward for repeat renters. Track totals in a data file keyed by UserIDString and grant milestone bonuses.

  • Admin audit logging

    Append buyer id, tier, room identity, and timestamp to an oxide/data file so staff can review apartment traffic without tailing server logs.

Pitfalls

  • The purchase is already final

    By the time this hook runs, scrap has been taken and the room assigned. Do not try to cancel the sale here; if you need to block a purchase, look at the pre-purchase hooks instead.

  • Player may be invalid

    Check that player and its inventory are not null before granting items. A disconnect racing the purchase flow can leave a stale reference.

  • Do not assume full inventory space

    GiveItem drops unclaimed items at the player's position when containers are full. For refunds, communicate that scrap may land on the ground, or cap the payout to what fits.

Build a plugin with OnApartmentRoomPurchased

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Server-wide rental announcements using the OnApartmentRoomPurchased 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 OnApartmentRoomPurchased

Generate