Skip to content
RustPluginGenerator

OnApartmentRoomCheckedout

OnApartmentRoomCheckedout is called after ApartmentBuilding.TryCheckout successfully assigns an apartment room to a player. It fires once per completed checkout, after the room state has already been changed.

Signature
void OnApartmentRoomCheckedout(BasePlayer player, ApartmentRoom playerApartment, ApartmentBuilding apartmentBuilding)

Parameters

Hook parameters
ParameterTypeDescription
playerBasePlayerThe BasePlayer who checked out the apartment room. Null-check it before using it.
playerApartmentApartmentRoomThe ApartmentRoom instance that was checked out. Useful for its position, owner and room data.
apartmentBuildingApartmentBuildingThe ApartmentBuilding entity that owns the room. Comes from the TryCheckout call as `this`.

Return value

The return value is ignored. Whatever you return from OnApartmentRoomCheckedout, including null, has no effect on the checkout, because the game returns true to the caller immediately after calling the hook.

How it works

This hook fires inside ApartmentBuilding.TryCheckout, after the analytics call and after the internal Checkout(playerApartment) method has run. That means the room has already been assigned to the player when your plugin sees the call, so any logic you run here treats the checkout as done. It does not fire when TryCheckout returns false (for example when the player cannot afford the room or the room is not available).

The player parameter is the buyer, playerApartment is the room, and apartmentBuilding is the building entity the checkout was requested on. Use it for logging, notifications, reward grants, tag creation or external stats.

Declaring a more specific type is not useful here because Oxide already passes a BasePlayer and the concrete room and building types. The hook is synchronous and the return value is ignored, so you cannot use it to block or cancel a checkout. If you need to reject the purchase, use CanAffordApartmentMasterKey or a pre-check hook instead.

Keep the handler fast: it can run for many players on a busy server, so avoid heavy queries and do not spam Puts/PrintWarning.

Example plugin: Log and announce apartment checkouts

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

namespace Oxide.Plugins
{
    [Info("Apartment Checkout Logger", "RustPluginGenerator", "1.0.0")]
    [Description("Logs and announces successful apartment room checkouts.")]
    public class ApartmentCheckoutLogger : RustPlugin
    {
        private const string PermAdmin = "apartmentcheckoutlogger.admin";

        private PluginConfig config;
        private CheckoutData data;
        private Timer saveTimer;

        private class PluginConfig
        {
            [JsonProperty("Announce in global chat")]
            public bool AnnounceInChat { get; set; } = true;

            [JsonProperty("Save checkout log to data file")]
            public bool PersistToDisk { get; set; } = true;

            [JsonProperty("Save interval in seconds")]
            public float SaveInterval { get; set; } = 120f;
        }

        private class CheckoutEntry
        {
            [JsonProperty("Player name")]
            public string PlayerName { get; set; }

            [JsonProperty("Player id")]
            public string PlayerId { get; set; }

            [JsonProperty("Timestamp (UTC)")]
            public string Timestamp { get; set; }

            [JsonProperty("Room position")]
            public string Position { get; set; }
        }

        private class CheckoutData
        {
            [JsonProperty("Checkouts")]
            public List<CheckoutEntry> Checkouts { get; set; } = new List<CheckoutEntry>();
        }

        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>
            {
                ["Announce"] = "{0} just checked out an apartment room.",
                ["AdminNotice"] = "{0} ({1}) checked out a room at {2}."
            }, 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(PermAdmin, this);
        }

        private void OnServerInitialized()
        {
            data = Interface.Oxide.DataFileSystem.ReadObject<CheckoutData>(Name) ?? new CheckoutData();
            if (config.SaveInterval > 0f)
                saveTimer = timer.Every(config.SaveInterval, SaveData);
        }

        private void Unload()
        {
            if (saveTimer != null) saveTimer.Destroy();
            SaveData();
        }

        private void SaveData()
        {
            if (!config.PersistToDisk || data == null) return;
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }

        private void OnApartmentRoomCheckedout(BasePlayer player, ApartmentRoom playerApartment, ApartmentBuilding apartmentBuilding)
        {
            if (player == null || playerApartment == null) return;

            Vector3 position = playerApartment.transform != null ? playerApartment.transform.position : Vector3.zero;
            string posText = string.Format("{0:F0},{1:F0},{2:F0}", position.x, position.y, position.z);

            if (data != null)
            {
                data.Checkouts.Add(new CheckoutEntry
                {
                    PlayerName = player.displayName,
                    PlayerId = player.UserIDString,
                    Timestamp = DateTime.UtcNow.ToString("u"),
                    Position = posText
                });
            }

            if (config.AnnounceInChat)
                Server.Broadcast(Msg("Announce", null, player.displayName));

            foreach (var admin in BasePlayer.activePlayerList)
            {
                if (admin == null || !admin.IsAdmin) continue;
                SendReply(admin, Msg("AdminNotice", admin.UserIDString, player.displayName, player.UserIDString, posText));
            }
        }
    }
}

The plugin listens for OnApartmentRoomCheckedout and records each successful room checkout. It broadcasts a configurable chat message and, if enabled, writes an entry to oxide/data/ApartmentCheckoutLogger.json with the player name, user id and the room's position. The data is written once on Unload and on a periodic save timer, so the hook itself never touches the filesystem. A permission apartmentcheckoutlogger.admin is registered for potential admin commands, and all player-facing text goes through the language file so server owners can translate it. Because the return value is ignored, the plugin only observes; it cannot cancel a purchase.

Common use cases

  • Track apartment sales

    Record every checkout with player, timestamp and room position so you can report on rental usage across wipes.

  • Grant a welcome kit

    Give the new tenant a small item pack or a message when they take a room, using ItemManager.CreateByName and player.GiveItem.

  • Notify staff

    Send a message to online admins (BasePlayer.activePlayerList with IsAdmin) when a room is checked out, so they can monitor the building.

Pitfalls

  • Fires after the checkout

    The room has already been assigned when the hook runs, so you cannot use it to veto the purchase. Use a pre-check hook if you need to block the action.

  • Failed checkouts do not call the hook

    TryCheckout returns false early on invalid requests, so failed purchases never reach OnApartmentRoomCheckedout. Do not rely on it for validation counts.

  • Do not spam PrintWarning

    This can fire often on a busy server. Log only meaningful events and write to disk on Unload or a timer rather than inside the hook.

Build a plugin with OnApartmentRoomCheckedout

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

Prompt: Track apartment sales using the OnApartmentRoomCheckedout 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 OnApartmentRoomCheckedout

Generate