# :house: Housing
Documentation related to the jo_housing script.

:::: tabs
::: tab BUY
[Buy it on the shop](https://jumpon-studios.com/redm/housing)
:::

::: tab PREVIEW
<iframe width="560" height="315" src="https://www.youtube.com/embed/1A_ubufvh80?si=Vrw1RxNKarTFS2_6" title="RedM - Housing" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
:::
::::

## 1. Installation
jo_housing works on all frameworks compatible with jo_libs ([the list](/jo_libs/modules/framework-bridge/#compatible-frameworks)).

To install jo_housing :
- Download the library: [jo_libs](https://github.com/Jump-On-Studios/RedM-jo_libs/releases/latest/download/jo_libs.zip)
- Unzip the folder and drop it in your resource folder
- Download jo_housing from your [account](https://jumpon-studios.com/account)
- Unzip the folder and drop both `jo_housing` **AND** `jo_housing_interiors` in your resource folder
- Add those two ensure in your server.cfg :
  - `ensure jo_libs`
  - `ensure jo_housing` (`jo_housing_interiors` will be automatically ensured)
- This script uses the [raw keys](/jo_libs/modules/raw-keys/) module, if you have any problem with the prompts you should [set the keyboard layout](/jo_libs/modules/raw-keys/client#setting-keyboard-layout)
- If you want to use [house key item](#items-and-pricing), **you must add House Key Item** in your inventory: 

:::: tabs

::: tab Example : VORP Framework
Add this item configuration into your `items` database table:
```SQL
INSERT INTO `items` (`item`, `label`, `limit`, `can_remove`, `type`, `usable`, `groupId`, `desc`, `weight`)
VALUES ('house_key', 'House key', '10', '1', 'item_standard', '0', '1', 'A house key', '0.10');
```
:::

::: tab Example : RSG Framework V2
Add this item configuration to your RSG `items` file:
```lua
house_key = { 
    name = "house_key", 
    label = "A house key", 
    weight = 0, 
    type = "item", 
    image = "your_key_image.png", 
    unique = true, 
    useable = false, 
    shouldClose = false, 
    description = "A house key" 
}
```
:::

::::

Congratulation, the **Housing** script is ready to be used!
:::warning
Be sure you have [oxmysql](https://overextended.dev/oxmysql) ensure in your server.cfg
:::
:::tip 💡 No SQL File
The script automatically creates all necessary database tables during its first startup
:::
:::danger 🔗 Script requirements
**Stable, Wagon and Wardrobe** are not included. You have to link them with your other scripts.
:::

## 2. Usage

### Create / Update / Delete houses

The housing system provides an admin interface to manage properties on your server.

> [!NOTE] 
> `jo_housing` supports three distinct types of properties: MLOs, Zones and Shells. MLO and Zone houses are physically part of the game world, so they now share the same outside flow for buying, door interactions, inside/outside detection and build mode. In contrast, Shell houses use pre-defined propset interiors that exist separately from the main map. When a player enters a Shell house, they are teleported from the front door to an instance of the interior, which the script spawns on demand.

:::tip 🔒 Permission Control (By job, grade,etc.)
By default, any player can use the `/houseManager` command to create and manage houses.  
You can restrict access by using the [`canUseHouseManagerCommand`](#canusehousemanagercommand) filter to implement your own permission system.
:::

:::: tabs

::: tab 🔨 Creating a Shell House

1. Use the command `/houseManager` to open the housing management menu
2. Select **Create a new house**, then choose the `Shell` type
3. Fill in the house details :
   - **Name**: Give your house a descriptive name
   - **Category**: Choose from available interior categories (Houses, Shacks, Manors, etc.)
   - **Rooms Amount**: Select the number of rooms for your chosen category
   - **Interior**: Pick a specific interior
   - **Shell Location**: Place the interior shell in the world
   - **Front Door Location**: Move the entrance marker
   - **Contract Type**: Choose between one-time sale or rent
     - If rent: Select daily or weekly rent periods
   - **Price**: Set a `jo.pricing` price group. Payment options can use money, gold, role currency or items, and multiple alternatives can be combined with `OR`
   - **House Tax**: For sale houses only, enable recurring tax and configure its price group and period in days
   - **Features**:
     - **Stable**: Enable/disable horse storage
       - Stable Location: Place the stable interaction marker
       - Stable Spawn Location: Place where horses will spawn
     - **Storage**: Enable/disable item storage with weight/slot limits
     - **Wagon**: Enable/disable wagon storage  
       - Wagon Location: Place the wagon interaction marker
       - Wagon Spawn Location: Place where wagons will spawn
     - **Dressing Room**: Enable/disable wardrobe functionality

4. **Visit Mode**: Press the visit mode key (default: R) to preview the interior
5. **Validation**: All required fields must be completed:
   - House name cannot be empty
   - Front door location must be set
   - Valid interior must be selected
   - Shell location must be placed
   - If stable enabled: Both stable location and spawn location required
   - If wagon enabled: Both wagon location and spawn location required
   - Prices must be non-negative
6. Press the "Create House" key when all requirements are met

> [!TIP] 💡 Multiple houses on the same location
> If you create multiple houses with the same front door location, all will be merged into one location menu. This allows you to virtually create 'buildings' with multiple flats with only one entry!

:::

::: tab 🔨 Creating a MLO House
1. Use the command `/houseManager` to open the housing management menu
2. Select **Create a new house**, then choose the `MLO` type
3. Fill in the house details :
   - **Name**: Give your house a descriptive name
   - **Interior**: Go inside the house you want to create and press `Enter` to set the interior
   - **Add doors**: While hovering this item, you can walk around the house and register the various doors. Each door you add will be treated as an access point for the property, showing the interaction prompt and marker (if enabled) when a player gets close. These registered doors are also used by the dedicated outside door menu and the quick open / close prompt.
   - **Contract Type**: Choose between one-time sale or rent
     - If rent: Select daily or weekly rent periods
   - **Price**: Set a `jo.pricing` price group. Payment options can use money, gold, role currency or items, and multiple alternatives can be combined with `OR`
   - **House Tax**: For sale houses only, enable recurring tax and configure its price group and period in days
   - **Features**:
     - **Stable**: Enable/disable horse storage
       - Stable Location: Place the stable interaction marker
       - Stable Spawn Location: Place where horses will spawn
     - **Storage**: Enable/disable item storage with weight/slot limits
     - **Wagon**: Enable/disable wagon storage  
       - Wagon Location: Place the wagon interaction marker
       - Wagon Spawn Location: Place where wagons will spawn
     - **Dressing Room**: Enable/disable wardrobe functionality

4. **Validation**: All required fields must be completed:
   - House name cannot be empty
   - House interior must be set
   - At least one door must be registered
   - If stable enabled: Both stable location and spawn location required
   - If wagon enabled: Both wagon location and spawn location required
   - Prices must be non-negative
5. Press the "Create House" key when all requirements are met (you'll need to go outside the house first)
:::

::: tab 🔨 Creating a Zone House
1. Use the command `/houseManager` to open the housing management menu
2. Select **Create a new house**, then choose the `Zone` type
3. Fill in the house details :
   - **Name**: Give your house a descriptive name
   - **Zone**: Use the zone creator to add points and define the "inside" zone. This same zone is later reused for inside / outside checks, build mode safety and furniture placement validation.
   - **Add doors**: While hovering this item, you can walk around the house and register the various doors. Each door you add will be treated as an access point for the property, showing the interaction prompt and marker (if enabled) when a player gets close. These registered doors are also used by the dedicated outside door menu and the quick open / close prompt.
   - **Contract Type**: Choose between one-time sale or rent
     - If rent: Select daily or weekly rent periods
   - **Price**: Set a `jo.pricing` price group. Payment options can use money, gold, role currency or items, and multiple alternatives can be combined with `OR`
   - **House Tax**: For sale houses only, enable recurring tax and configure its price group and period in days
   - **Features**:
     - **Stable**: Enable/disable horse storage
       - Stable Location: Place the stable interaction marker
       - Stable Spawn Location: Place where horses will spawn
     - **Storage**: Enable/disable item storage with weight/slot limits
     - **Wagon**: Enable/disable wagon storage  
       - Wagon Location: Place the wagon interaction marker
       - Wagon Spawn Location: Place where wagons will spawn
     - **Dressing Room**: Enable/disable wardrobe functionality

4. **Validation**: All required fields must be completed:
   - House name cannot be empty
   - House zone must be set
   - At least one door must be registered
   - If stable enabled: Both stable location and spawn location required
   - If wagon enabled: Both wagon location and spawn location required
   - Prices must be non-negative
5. Press the "Create House" key when all requirements are met (you'll need to go outside the house first)
:::

::: tab ✏️ Updating a House
**Updating a House:**
1. Use the command `/houseManager` to open the housing management menu
2. Select "Manage houses"
3. Choose the house you want to modify
4. Press the "Edit House" prompt key (default: E)
5. **Edit Mode Restrictions**:
   -   `[Shell houses]` : Category, rooms amount, interior type and house type cannot be changed
   - `[MLO houses]` : House type and interior cannot be changed
   - `[Zone houses]` : House type cannot be changed
   - All other properties can be modified
   - Visit mode is available to preview changes
6. Modify any editable house details as needed
7. Press "Update House" to save your changes (only saves if changes were made)
:::

::: tab 🗑️ Deleting a House
**Deleting a House:**
1. Use the command `/houseManager` to open the housing management menu
2. Select "Manage houses"
3. Choose the house you want to delete
4. Press the "Delete House" prompt key (default: X)
5. The house will be permanently deleted along with all associated data
:::

::::

### Buying a house

Players can purchase or rent available houses throughout your server.

:::: tabs

::: tab 🔍 Finding Available Houses
**Finding Available Houses:**
1. Approach a house door with the help of the map blips
2. Press the front door prompt key (default: E) to open the contextual menu
3. If several properties share the same entrance, first choose the unit you want to inspect
:::

::: tab 💰 Purchasing a House

**Purchasing a House:**
1. For `Shell` houses, the location menu lists the available units at that entrance
2. For unowned `MLO` and `Zone` houses, the script opens a dedicated buy menu
3. Available properties will show their sale or rent price
4. Press the "Buy House" prompt key (default: ENTER) to purchase with money
5. Alternatively, press the "Buy with Gold" key (default: G) if enabled
6. For rental properties:
   - Select the number of days/weeks to rent using the slider
   - Press the appropriate key to pay with money or gold
7. Once purchased, the house switches to its owner flow:
   - `Shell`: shell entry and interior menu flow
   - `MLO` / `Zone`: dedicated door menu and quick door prompt

:::

::: tab 📅 Rental Information
**Rental Information:**
- Rentals must be renewed before they expire
- The house menu shows your current rental status:
  - 🟢 Green: Plenty of time remaining
  - 🟠 Orange: Rent due soon (warning period)
  - 🔴 Red: Grace period (overdue but not yet evicted)
- If rent expires beyond the grace period, the house will return to the market
:::

::: tab 🧾 House Tax
**House Tax:**

House tax is an optional recurring payment for houses configured as **for sale**. Rental houses are never taxed because they already use the rent lifecycle.

Tax is configured by an administrator when creating or editing a house:
- **House Tax:** Enable or disable the tax for the sale house
- **Tax Price:** Price charged for one tax period; multiple payment alternatives are supported
- **Tax Period:** Number of days covered by one payment

Once a taxed house is owned:
1. The owner can open the house menu and select **Pay house tax**.
2. The menu displays the date until which the tax is paid (`Tax Paid Until`).
3. The owner can pay one or more periods at once, up to the configured advance limit.
4. Early payments extend the existing paid period. Late payments start from the current date, so missed periods are not charged twice.

The default tax settings are:
- `50 money` per period
- `7 days` per period
- `24 hours` warning period
- `3 days` grace period after the due date
- `14 days` maximum paid-ahead balance

The house menu displays the current tax status:
- 🟢 **Green:** Tax is paid and the due date is not approaching
- 🟠 **Orange:** Tax is due within the warning period
- 🔴 **Red:** The due date has passed and the owner is within the grace period
- **Eviction suspended:** An external filter temporarily prevents eviction
- **Eviction processed:** The grace period has expired and the house has returned to the market

Tax status is enforced when the resource starts and when relevant house menus are opened. If the grace period expires, the owner is notified when online, occupants are removed, furniture and resale listings are cleared, and the house becomes available again. A new buyer or transferee receives a fresh paid period and never inherits the previous owner's tax debt.

For example, a server can temporarily suspend tax evictions for protected characters or during a server event:

```lua
exports.jo_housing:registerFilter('canEvictForUnpaidTax', function(canEvict, house, ownerIdentifier, ownerCharId)
    if exports.your_staff_system:isTaxProtected(ownerIdentifier, ownerCharId) then
        return false
    end

    return canEvict
end)
```
:::

::::

### Using a house

Once you own a house, you can use its features and customize its interior.

:::: tabs

::: tab 🚪 Entering Your House
**Entering Your House:**
1. Approach your house's entrance

**For `[Shell houses]` :**

2. Press the "Enter House" prompt key (default: ENTER)
3. You'll be teleported inside your private shell instance
4. Inside the shell, use the entrance prompts to open the interior menu or leave the house

**For `[MLO houses]` and `[Zone houses]`:**

2. Press the front door menu key (default: E) to open the dedicated door menu
3. Depending on your access, this menu can show actions such as:
   - Open / Close door
   - Manage my house
   - Enter build mode
   - Knock on house
4. If you are close to an eligible registered door, you can also use the quick door prompt (default: ENTER) to directly open or close the nearest door without opening the menu
5. The quick door prompt label automatically switches between "Open door" and "Close door"
6. Door states are synchronized between players
7. Access to `MLO` / `Zone` doors depends on ownership, accessibility mode, access list permissions and `Config.enableKeyMode`
:::

::: tab 🛠️ Build Mode
**Build Mode:**
1. Open the menu that matches your house type
   - `Shell`: use the interior menu near the entrance from inside the house
   - `MLO` / `Zone`: use the dedicated door menu
2. Select "Enter build mode"
3. For `MLO` and `Zone` houses, this option is only shown to eligible players, and it only becomes available while you are physically inside the house bounds
4. In build mode, you can:
   - Press the "Add Furnitures" key (default: A) to purchase and place furniture
   - Press the "Edit Furnitures" key (default: E) to move, duplicate or delete existing furniture
   - Set locations for dressing rooms and storage if your house has these features
5. Furniture placement is validated according to the house type:
   - `Shell`: around the spawned shell interior
   - `MLO`: inside the registered interior
   - `Zone`: inside the defined zone
6. If you leave the bounds of an `MLO` or `Zone` house while building or editing, build mode closes automatically
7. `Zone` houses display their zone boundaries during build mode to make placement clearer
8. Press the "Leave Build Mode" key (default: R) to exit
:::

::: tab 🔐 Managing Access
**Managing Access:**

The access management options depend on your server's `Config.enableKeyMode` setting:

**When Key Mode is Disabled (`Config.enableKeyMode = false`):**
1. Open the house menu for your property
2. Select "Manage my house"
3. Choose "Manage Access"
4. Set your access type:
   - "Only me": Only you can enter
   - "List": Only players you've added to the access list can enter or use `MLO` / `Zone` door interactions
   - "Everyone": Any player can enter your house and use `MLO` / `Zone` door interactions
5. If using the "List" option, you can add or remove players from your access list
6.  After adding a player, you can select them to manage their specific permissions:
    * Can access storage
    * Can access wardrobe
    * Can add furniture
    * Can edit furniture
7. Access and permission updates are synchronized immediately for online players, even if they are currently outside the house

---

**When Key Mode is Enabled (`Config.enableKeyMode = true`):**
1. Open the house menu for your property
2. Select "Manage my house"
3. Choose "Manage Access"
4. Set your access type:
   - "Anyone with the key": Anyone who has a key to your house can enter
   - "Everyone": Any player can enter your house and toggle `MLO` / `Zone` doors
5. Additional key management options:
   - **Buy new key**: Purchase additional keys to give to other players
   - **Change lock**: Change your house lock, making all existing keys obsolete (you'll receive a new key)
6. For owned `MLO` and `Zone` houses, door toggling follows the same key rules: a valid key is required unless the house is set to `Everyone`

If an access or permission change is invalid or not allowed, the player receives an error notification.
:::

::: tab 🏠 Using House Features
**Using House Features:**
- **Storage:** Approach your storage location and press the prompt key (default: E)
- **Dressing Room\* :** Approach your dressing location and press the prompt key (default: E)
- **Stable\* :** Approach the stable marker outside your house to access your horses
- **Wagon\* :** Approach the wagon marker outside your house to access your wagons

\* _Integration Required_ <br>
_This script does not have built-in solutions for dressing, stable and wagon functionality. You need to integrate with your existing resources using the [external handler functions](#external-handlers)._

:::

::: tab 👥 Inviting Players
**Inviting Players:**
1. Visitors can knock on an owned house from the front door or from the `MLO` / `Zone` door menu
2. For `Shell` houses, eligible players inside receive a notification and can directly welcome the visitor in
3. For `MLO` and `Zone` houses, eligible players are notified that someone is at the door and should go to the door to let them in
4. Alternatively, add the visitor to your access list for permanent access or give them a key if `Config.enableKeyMode` is `true`
:::

::: tab 📤 Transferring Ownership
**Transferring Ownership:**
1. Open the house menu for your property
2. Select "Manage my house"
3. Choose "Transfer House"
4. Enter the player's server ID
5. The house ownership will transfer to that player
:::

::::

:::tip 💡 Key Mode Configuration
The `Config.enableKeyMode` setting fundamentally changes how house access is managed. Choose the mode that best fits your server's desired gameplay style.

##### 🔑 Key Mode Enabled
When `Config.enableKeyMode = true`, house access is tied to a **physical key item**, creating a more immersive experience.

-   **Ownership:** Players receive a key in their inventory upon purchasing a house.
-   **Sharing:** Owners can buy additional keys to give to friends or other players.
-   **Security:** An owner can **change the locks** at any time. This action makes all previously issued keys for that house obsolete and provides the owner with a new one.
-   **Requirement:** A player **must** have the corresponding key in their inventory to enter the house. For owned `MLO` and `Zone` houses, the same rule also applies to door toggling unless the house accessibility is set to `Everyone`.

---

##### 👤 Access List Mode
When `Config.enableKeyMode = false`, access is managed digitally through an in-game menu, offering a simpler, hassle-free system.

-   **No Items:** This mode does not use physical key items.
-   **Management:** Owners manage a digital **access list** for their property via the house menu.
-   **Permissions:** The owner can instantly grant or revoke access to any player.
-   **Granular Controls:** For each player on the list, the owner can set specific permissions:
    -   Can access storage
    -   Can access wardrobe
    -   Can add furniture
    -   Can edit furniture
-   **Requirement:** Players on the access list can enter without needing any specific item. On `MLO` and `Zone` houses, the same access rules also govern the door menu and quick door prompt.
:::

## 3. Interiors Shells Gallery

Browse through all available interior options for the Shell houses.
This gallery showcases the complete collection of interiors you can choose from when creating houses of type `Shell` using the `/houseManager` command.

:::details The gallery
| Interior ID | Category | Rooms | Add-on |
| --- | --- | --- | --- |
| `jo_tum_genstore` | flat | 1 | more_interiors |
| `jo_tum_gunsmith` | flat | 1 | more_interiors |
| `jo_val_res_01_f` | flat | 1 | more_interiors |
| `jo_val_res_a` | flat | 1 | more_interiors |
| `jo_mac_house01` | flat | 1 | more_interiors |
| `jo_rad_house_01` | flat | 4 | main |
| `jo_new_barber` | flat | 1 | main |
| `jo_rho_genstore` | flat | 1 | main |
| `jo_dow_house` | flat | 2 | main |
| `jo_new_gamble` | house | 1 | main |
| `jo_ple_house01` | house | 1 | main |
| `jo_pro_worker_house` | house | 1 | main |
| `jo_rdl_house_01` | house | 1 | more_interiors |
| `jo_shack_sk2` | house | 1 | more_interiors |
| `jo_sil_house` | house | 1 | main |
| `jo_the_ticketoffice` | house | 1 | main |
| `jo_adl_cabin01` | house | 1 | more_interiors |
| `jo_ben_shack` | house | 1 | more_interiors |
| `jo_bpl_swampcabin` | house | 1 | more_interiors |
| `jo_brb_dockhouse` | house | 1 | more_interiors |
| `jo_van_farmhouse01` | house | 1 | main |
| `jo_ven_shack01` | house | 1 | main |
| `jo_ldj_shack01` | house | 1 | more_interiors |
| `jo_maco_shack01` | house | 1 | more_interiors |
| `jo_man_cabin01` | house | 1 | main |
| `jo_rho_gunsmith` | house | 2 | more_interiors |
| `jo_shack_rarefish` | house | 2 | more_interiors |
| `jo_str_doctor` | house | 2 | more_interiors |
| `jo_str_genst` | house | 2 | main |
| `jo_tum_saloon` | house | 2 | main |
| `jo_van_saloon` | house | 2 | more_interiors |
| `jo_din_house` | house | 2 | main |
| `jo_lnn_lonnie_shack` | house | 2 | more_interiors |
| `jo_mfr_cottage01` | house | 2 | main |
| `jo_pai_house` | house | 3 | main |
| `jo_shack_b_b` | house | 3 | main |
| `jo_shack_b_c` | house | 3 | more_interiors |
| `jo_tax_house` | house | 3 | more_interiors |
| `jo_val_doctor` | house | 3 | more_interiors |
| `jo_car_house` | house | 3 | main |
| `jo_dai_farmhouse01` | house | 3 | more_interiors |
| `jo_shack_b_a` | house | 4 | main |
| `jo_che_cabin1` | house | 4 | more_interiors |
| `jo_cro_house` | house | 4 | more_interiors |
| `jo_han_house` | house | 4 | main |
| `jo_str_whore` | house | 5 | more_interiors |
| `jo_abe_farmhouse` | house | 5 | main |
| `jo_cat_house_01` | house | 5 | more_interiors |
| `jo_bee_house` | house | 6 | more_interiors |
| `jo_new_man_bronte` | manor | 10 | main |
| `jo_bra_mansion` | manor | 12 | more_interiors |
| `jo_new_man_gala` | manor | 12 | more_interiors |
| `jo_sod_house` | rock_shack | 1 | main |
| `jo_agu_hut01` | rock_shack | 1 | more_interiors |
| `jo_bur_calaboose` | rock_shack | 1 | more_interiors |
| `jo_lar_house` | rock_shack | 1 | main |
| `jo_mic_hideout` | rock_shack | 1 | more_interiors |
| `jo_twi_shack01` | rock_shack | 3 | main |
| `jo_aur_shack01` | shack | 1 | main |
| `jo_qua_shack01` | shack | 1 | main |
| `jo_qua_shack02` | shack | 1 | main |
| `jo_shack_ai` | shack | 1 | more_interiors |
| `jo_shack_bv` | shack | 1 | main |
| `jo_shack_dr` | shack | 1 | main |
| `jo_shack_it_01` | shack | 1 | main |
| `jo_shack_it_02` | shack | 1 | more_interiors |
| `jo_shack_lt` | shack | 1 | more_interiors |
| `jo_shack_pl` | shack | 1 | main |
| `jo_shack_rc` | shack | 1 | main |
| `jo_shack_sc` | shack | 1 | main |
| `jo_shack_sw` | shack | 1 | more_interiors |
| `jo_six_cabin` | shack | 1 | main |
| `jo_swc_shack01` | shack | 1 | main |
| `jo_tan_house` | shack | 1 | main |
| `jo_but_shack` | shack | 1 | main |
| `jo_cat_shack01` | shack | 1 | more_interiors |
| `jo_che_shack1` | shack | 1 | main |
| `jo_col_bunkhouse` | shack | 1 | main |
| `jo_grh_house` | shack | 1 | more_interiors |
| `jo_gri_g2_shack` | shack | 1 | main |
| `jo_shack_cwb` | shack | 2 | main |
| `jo_vet_house` | shack | 2 | main |
| `jo_eme_genstore` | shack | 2 | more_interiors |
| `jo_roc_house` | shack | 3 | main |
| `jo_wat_cabin` | shack | 3 | more_interiors |
| `jo_new_carriagehouse` | worker | 1 | main |
| `jo_cal_tobacco` | worker | 1 | main |
| `jo_cin_tor` | worker | 1 | more_interiors |
| `jo_van_fenceshop` | worker | 1 | more_interiors |
| `jo_mfr_blacksmith01` | worker | 1 | main |
| `jo_agu_boiler` | worker | 2 | main |
| `jo_bra_cornmill` | worker | 2 | more_interiors |
| `jo_swa_depot01` | worker | 3 | main |
| `jo_dov_lab` | worker | 3 | more_interiors |
| `jo_mp006_moonshine01` | worker | 5 | more_interiors |
:::

## 4. Script Configuration
The configuration files are located in the `config` folder within the resource. This folder contains two subfolders:

- **`_default.lock/`** - Contains the default configuration files. **Do not edit these files directly** as they may be overwritten during updates.
- **`custom/`** - This is where you place your custom configuration files to override the defaults.

### How to customize the configuration

1. Navigate to `jo_housing/config/_default.lock/`
2. Copy the file you want to modify (e.g., `prices.lua`)
3. Paste it into `jo_housing/config/custom/`
4. Edit the copied file in the `custom` folder

The script automatically loads files from `custom/` first, and only uses `_default.lock/` as a fallback. This system ensures your customizations are preserved when updating the script.

### Default configuration files

`jo_housing/config/_default.lock/blips.lua`

```lua
-- ===================================
-- Blips configuration
-- ===================================
-- Find other colors here : https://github.com/femga/rdr3_discoveries/tree/master/useful_info_from_rpfs/blip_modifiers

Config.blips = { -- Blip icons for houses on the map
    forSale = {  -- set to false to disable
        sprite = "blip_proc_home",
        color = "BLIP_MODIFIER_DEBUG_BLUE"
    },
    forRent = { -- set to false to disable
        sprite = "blip_proc_home",
        color = "BLIP_MODIFIER_DEBUG_YELLOW"
    },
    boughtByMe = {
        sprite = "blip_proc_home_locked",
        color = "BLIP_MODIFIER_DEBUG_GREEN"
    },
    boughtBySomeoneElse = {
        sprite = "blip_proc_home_locked",
        color = "BLIP_MODIFIER_DEBUG_RED"
    },
    rentedByMe = {
        sprite = "blip_proc_home_locked",
        color = "BLIP_MODIFIER_DEBUG_GREEN"
    },
    rentedBySomeoneElse = {
        sprite = "blip_proc_home_locked",
        color = "BLIP_MODIFIER_DEBUG_RED"
    }
}
```

`jo_housing/config/_default.lock/furnitures.lua`

```lua
-- ===================================
-- Furniture Configuration
-- ===================================

-- Available furniture categories: beds, carpets, cabinets, chairs, tables, desks, fireplaces, lamps, plants, sofas, wall_decorations, decorations, bathroom, kitchen, food, curtains

-- Need to build or adjust a price table quickly?
-- Use the official Price Generator here:
--   https://docs.jumpon-studios.com/DeveloperResources/price-generator

-- Prices accept any PriceGroupInput, including PriceGroupClass instances.
-- A flat { money = ..., gold = ... } table creates alternative payment options.
Config.furnituresCategoriesPrices = {
    default = { -- Default pricing for furniture categories
        money = 100,
        gold = 1
    },
    beds = { -- example : Pricing for bed category furniture
        money = 50,
        gold = 2
    }
}

-- Specific pricing per furniture model
Config.furnituresPrices = {
    p_bath02x = { -- Specific pricing per furniture model
        money = 200,
        gold = 10
    }
}

-- Add actions when near a specific furniture
Config.furnituresActions = {
    [`p_bath02bx`] = {
        prompt = "Use bath",
        distance = 2.0,
        action = function()
            -- example : print("hello bathtub")
        end
    },
    [`p_stove01x`] = {
        prompt = "Use stove",
        distance = 2.0,
        action = function()
            -- example : print("hello stove")
        end
    }
}
```

`jo_housing/config/_default.lock/global.lua`

```lua
-- ===================================
-- General Settings
-- ===================================

Config.enableKeyMode = false                   -- Enable physical house keys that players must carry to access their houses
Config.allowPayingInGold = true                -- Allow prices to contain gold costs
Config.allowPayingInRol = false                -- Allow prices to contain ROL costs
Config.allowPayingInItems = true               -- Allow prices to contain item costs
Config.allowPriceOR = true                     -- Allow several alternative payment options for user-defined prices
Config.knockNotificationDuration = 5000        -- Duration (in ms) that knock notifications are displayed to house owners
Config.showInsideDoorMarker = true             -- While inside a house, show a marker on the ground near the entrance door
Config.distanceOutsideMarker = 10.0            -- Show a marker on the ground for each house near you (outside) (use 0 to turn OFF)
Config.showMloDoorsMarkers = false             -- Show markers on the ground for MLO and Zone house doors
Config.openManagerCommandName = "houseManager" -- The name of the command to open the house manager (ex: /houseManager)
Config.closeAllDoorsOnRestart = false          -- Wether to close all MLO and Zone  houses doors on server or script restart. If true, all doors will be closed. If false, doors will stay in the state they were before restart.
Config.doorStateOnCreation = "forceClose"      -- Door state for new MLO and Zone houses: "forceClose", "forceOpen" or "keepState".
Config.allowListHouseForResale = true          -- Allow owners to list owned sale houses back on the market at a custom price. The next buyer pays the previous owner.
Config.allowSellBackToMarket = true            -- Allow owners to sell sale houses back to the market immediately.
Config.allowOwnershipTransfer = true           -- Allow owners to transfer their house to another player.

-- Default value for storage inventory
Config.storageConfig = {
    acceptWeapons = true,
    ignoreStackLimit = false
}

Config.resellHousePercentage = 0.5 -- Refund percentage when selling a house back to the market, relative to the house initial price : 0.0 to 1.0

-- ===================================
-- Distance Settings
-- ===================================

Config.distanceShowHousePrompt = 2.0            -- Distance (in meters) to show house interaction prompts
Config.distanceShowStablePrompt = 2.0           -- Distance (in meters) to show stable interaction prompts
Config.distanceShowWagonPrompt = 2.0            -- Distance (in meters) to show wagon interaction prompts

Config.distanceShowInteriorDoorPrompt = 2.5     -- Distance (in meters) to show interior door prompts
Config.distanceShowInteriorStoragePrompt = 1.0  -- Distance (in meters) to show storage interaction prompts
Config.distanceShowInteriorDressingPrompt = 1.0 -- Distance (in meters) to show dressing room prompts

-- ===================================
-- External Handlers
-- ===================================

-- ⚠️ Stable, Wagon and Wardrobe are not included. You have to link them with your other scripts.

Config.openWardrobe = function()
    -- Add your Wardrobe handling code here
    -- This could call another resource's export for wardrobe management
    -- ex : exports["jo_clothingstore"]:openWardrobe()
    print("Config.openWardrobe must be configured to integrate with your wardrobe system")
end

Config.openStable = function(stableLocation, stableSpawnLocation)
    -- Add your stable handling code here
    -- This could call another resource's export for stable management
    -- TriggerEvent("your_stable_resource:openStable")
    print("Config.openStable must be configured to integrate with your stable system")
end

Config.openWagon = function(wagonLocation, wagonSpawnLocation)
    -- Add your wagon handling code here
    -- This could call another resource's export for wagon management
    -- TriggerEvent("your_wagon_resource:openWagon")
    print("Config.openWagon must be configured to integrate with your wagon system")
end
```

`jo_housing/config/_default.lock/interiors.lua`

```lua
-- ===================================
-- Interior Configuration
-- ===================================

Config.mloMaxFurnitures = 100 -- MLO and Zone Houses max furnitures inside

-- Available interior categories: shack, rock_shack, house, flat, manor, worker
-- Default furniture limit for interior categories
Config.interiorsCategoriesMaxFurnitures = {
    default = 100, -- Default furniture limit for interior categories
    manor = 200    -- example : Furniture limit for manor category interiors
}

-- Specific furniture limits per interior ID
Config.interiorsMaxFurnitures = {
    jo_pai_house = 100,
}

-- Interior IDs to hide from selection (commented examples included)
Config.interiorsBlacklist = {
    -- jo_pai_house = true,
    -- jo_new_gamble = true
}
```

`jo_housing/config/_default.lock/items.lua`

```lua
-- ===================================
-- Items and Prices
-- ===================================
-- Need to build or adjust a price table quickly?
-- Use the official Price Generator here:
--   https://docs.jumpon-studios.com/DeveloperResources/price-generator

Config.items = {
    houseKey = "house_key" -- Item name for house key in your inventory
}

-- PriceGroupInput: each top-level currency is an alternative payment option.
-- Use jo.pricing.newGroup(...) if you prefer declaring a PriceGroupClass directly.
Config.newKeyPrice = {
    money = 100, -- Cost in money to create a new house key
    gold = 1     -- Cost in gold to create a new house key
}

Config.changeLockPrice = {
    money = 150, -- Cost in money to change house lock
    gold = 2     -- Cost in gold to change house lock
}
```

`jo_housing/config/_default.lock/keys.lua`

```lua
-- ===================================
-- Key Bindings
-- ===================================

-- Find all usable keys here : https://docs.jumpon-studios.com/jo_libs/modules/raw-keys/client#keys

Config.keys = {

    openHouseMenu = "E",  -- Open house menu at front door
    enterSubMenu = "E",   -- Enter submenus
    buyDollar = "ENTER",  -- Purchase the house with the selected price
    switchPrice = "B",    -- Switch between available payment options
    knockOnHouse = "K",   -- Knock on someone's door
    enterHouse = "ENTER", -- Enter owned house
    leaveHouse = "R",     -- Exit house
    manageHouse = "M",    -- Open house management menu

    enterVisitMode = "R",     -- Enter visit mode to preview interior
    hideMenu = "C",           -- Hide menu
    editHouse = "E",          -- Edit existing house
    deleteHouse = "R",        -- Delete house
    removeOwner = "X",        -- Remove house owner

    setStorageLocation = "O",   -- Set storage location in build mode
    setDressingLocation = "K",  -- Set dressing room location in build mode
    openStorage = "E",          -- Open house storage
    openDressing = "E",         -- Open dressing room
    furnitureAction = "E",      -- Use furniture action

    addFurnitures = "A",        -- Add furniture to house
    editFurnitures = "E",       -- Edit existing furniture
    listFurnitures = "L",       -- Open the list furnitures menu
    focusFurniture = "F",       -- Focus selected furniture
    refreshFurnituresList = "G", -- Refresh placed furnitures list
    duplicateFurniture = "C",   -- Duplicate selected furniture
    deleteFurniture = "DELETE", -- Delete selected furniture
    buyFurniture = "ENTER",     -- Purchase furniture
    searchFurniture = "F",      -- Search furniture in catalog
    leaveBuildMode = "R",       -- Exit build mode

    addPlayer = "ENTER", -- Add player to house access list
    removePlayer = "R",  -- Remove player from access list
    enter = "ENTER",     -- Generic enter/confirm key

    -- ------------ ⚠️ USE NATIVE KEYS BELOW ⚠️ --------------

    -- Find all controls here : https://github.com/femga/rdr3_discoveries/tree/master/Controls

    addPointKey = `INPUT_CONTEXT_RT`,            -- Add point to zone
    heightUpKey = `INPUT_GAME_MENU_TAB_LEFT`,    -- Increase height of zone
    heightDownKey = `INPUT_GAME_MENU_TAB_RIGHT`, -- Decrease height of zone
    createZoneKey = `INPUT_FRONTEND_ACCEPT`,     -- Create zone
    deletePointKey = `INPUT_FRONTEND_RS`         -- Delete point of zone
}
```

`jo_housing/config/_default.lock/lang.lua`

```lua
-- * ==================================================
-- * GENERAL UI & COMMON TERMS
-- * ==================================================
Lang.saveButton = "Save"
Lang.notApplicable = "N/A"
Lang.status = "Status"
Lang.facilities = "Facilities"
Lang.stable = "Stable"
Lang.wagon = "Wagon"
Lang.dressing = "Dressing"
Lang.storage = "Storage"
Lang.noFacilities = "None"
Lang.contractType = "Contract Type"
Lang.contractTypeSale = "Sale"
Lang.contractTypeRent = "Rent"
Lang.rentPeriodType = "Rent Period"
Lang.rentPeriodDaily = "Daily"
Lang.rentPeriodWeekly = "Weekly"
Lang.interiorType = "Interior Type"
Lang.price = "Price"
Lang.switchPrice = "Switch price"
Lang.setHousePrice = "Set house price"
Lang.buyHouse = "Buy house"
Lang.rentHouse = "Rent house"
Lang.payRent = "Pay rent"
Lang.buyAndPlaceFurniture = "Buy & Place Furniture"
Lang.confirm = "Confirm"
Lang.rent = "Rent"
Lang.availableLabel = "Available"
Lang.unavailableLabel = "Unavailable"
Lang.nameLabel = "Name"
Lang.categoryLabel = "Category"
Lang.roomsAmountLabel = "Rooms amount"
Lang.interiorLabel = "Interior"
Lang.priceLabel = "Price"
Lang.stableLabel = "Stable"
Lang.storageLabel = "Storage"
Lang.maxWeightLabel = "Max Weight"
Lang.maxSlotsLabel = "Max Slots"

Lang.setZone = "Set zone"
Lang.zoneSetLabel = "Zone set"
Lang.zoneNotSetLabel = "Zone not set"
Lang.zoneNotSetError = "You must set a zone"
Lang.shouldBeOutsideZoneError = "You must be outside the zone area"
Lang.wagonLabel = "Wagon"
Lang.dressingLabel = "Dressing"
Lang.daysSuffix = "day(s)"
Lang.weeksSuffix = "week(s)"
Lang.daySuffix = "day"
Lang.daysSuffixPlural = "days"
Lang.hourSuffix = "hour"
Lang.hoursSuffixPlural = "hours"
Lang.status = "Status"
Lang.unknown = "Unknown"
Lang.priceOrSeparator = " or " -- Separates the alternatives of an "or" price group in a text line
Lang.houseTax = "House Tax"
Lang.taxPaidUntil = "Tax Paid Until"
Lang.ownedSince = "Owner Since"
Lang.payHouseTax = "Pay house tax"
Lang.furnituresPlaced = "Furnitures placed : %d / %d"
Lang.maxFurnituresAllowed = "Maximum furnitures allowed "
Lang.keyItemDescription = "%s's key" -- Example: "Cozy cottage's key, %s will be replaced by the house name"
Lang.yes = "Yes"
Lang.no = "No"

-- * ==================================================
-- * MENU TITLES & SUBTITLES
-- * ==================================================
Lang.editHouseTitle = "Edit House"
Lang.newHouseTitle = "New house"
Lang.editHouseSubtitle = "Edit the house"
Lang.newHouseSubtitle = "Create a new house"
Lang.manageYourHouseSubtitle = "Manage your house"
Lang.manageMyHouseTitle = "Manage my house"
Lang.enterBuildModeTitle = "Enter build mode"
Lang.leaveHouseTitle = "Leave house"
Lang.furnituresCategoriesSubtitle = "Furnitures categories"
Lang.manageHouseSubtitle = "Manage House"
Lang.sellHouse = "Sell Back to Market"
Lang.sellHouseDescription = "Give up this house now and get back part of what you paid"
Lang.sellHouseConfirmTitle = "Sell this house back to the market?"
Lang.sellHouseConfirmDescription = "The house will become available again, and all furnitures will be removed."
Lang.sellHouseRefund = "Refund amount:"
Lang.sellHouseButton = "Sell back for"
Lang.listHouseForResale = "List House for Resale"
Lang.listHouseForResaleDescription = "Set a resale price and make this house available to buyers"
Lang.listHouseForResaleTitle = "List this house for resale"
Lang.listHouseForResaleModalDescription = "The house will return to the market at the prices you set. When another player buys it, the payment they choose will be paid to you. Choose whether the furniture stays with the house or is removed before listing."
Lang.resaleFurnituresLabel = "Furnitures"
Lang.resaleWithFurnitures = "List with furnitures"
Lang.resaleWithoutFurnitures = "Remove all furnitures"
Lang.listHouseForResaleButton = "List for resale"
Lang.cancelButton = "Cancel"
Lang.leaveRentedHouse = "Leave Rented House"
Lang.leaveRentedHouseDescription = "Give up this house and remove all furnitures"
Lang.leaveRentedHouseConfirmTitle = "Leave this rented house?"
Lang.leaveRentedHouseConfirmDescription = "All furnitures will be removed. You will not get any refund."
Lang.leaveRentedHouseButton = "Leave house"
Lang.manageAccessTitle = "Manage Access"
Lang.payRentTitle = "Pay Rent"
Lang.payHouseTaxTitle = "Pay House Tax"
Lang.transferHouseTitle = "Transfer House"
Lang.transferHouseDescription = "Transfer this house to another player"
Lang.manageAccessSubtitle = "Manage access"
Lang.whoHasAccessTitle = "Who has access"
Lang.addPlayerTitle = "Add player"
Lang.housesLotTitle = "Houses lot"
Lang.houseManagerTitle = "House Manager"
Lang.manageRealEstatesSubtitle = "Manage your real estates"
Lang.createNewHouseTitle = "Create a new house"
Lang.manageHousesTitle = "Manage houses"
Lang.manageExistingHousesSubtitle = "Manage existing houses"
Lang.openHouseMenuInterior = "Open House Menu"
Lang.houseType = "House Type"
Lang.houseTypeSubtitle = "Choose house type"
Lang.shellTypeDescription = "Choose among various shell models to define the house's interior."
Lang.mloTypeDescription = "Define the area of the house using an MLO interior id."
Lang.mloTypeNativeOnly = "Use ONLY for native MLOs."
Lang.mloTypeCustomPreferZone = "For custom MLOs, prefer Zone."
Lang.zoneTypeDescription = "Define the area of the house using a zone."
Lang.buildModeDescription = "Add or edit furnitures and setup storage and dressing location"
Lang.buildModeNotInside = "Build mode is only accessible while inside your house"

-- * ==================================================
-- * PROMPTS & LABELS (UI Interactions)
-- * ==================================================
Lang.enterVisitMode = "Enter Visit Mode"
Lang.leaveVisitMode = "Leave Visit Mode"
Lang.hideMenu = "Hide menu"
Lang.showMenu = "Show menu"
Lang.setHouseName = "Set House Name"
Lang.placeMarker = "Place marker here"
Lang.placeShell = "Place house shell"
Lang.upsertHouse = "Save house"
Lang.setStorageMaxWeight = "Set Storage Max Weight"
Lang.setStorageMaxSlot = "Set Storage Max Slots"
Lang.placeFrontDoorLabel = "Place Front Door"
Lang.placeShellLabel = "Place Shell"
Lang.contractTypeLabel = "Contract Type"
Lang.oneTimeSaleLabel = "One time sale"
Lang.rentLabel = "Rent"
Lang.rentPeriodTypeLabel = "Rent Period"
Lang.dailyRentLabel = "Daily"
Lang.weeklyRentLabel = "Weekly"
Lang.houseTaxLabel = "House tax"
Lang.taxPriceLabel = "Tax price"
Lang.taxPeriodInDaysLabel = "Tax period"
Lang.setHouseTaxPrice = "Set house tax price"
Lang.setHouseTaxPeriod = "Set tax period (in days)"
Lang.placeStableLabel = "Place Stable"
Lang.placeWagonLabel = "Place Wagon"
Lang.updateHouseLabel = "Update House"
Lang.createHouseLabel = "Create House"
Lang.accessibilityEveryone = "Everyone"
Lang.accessibilityAnyoneWithAKey = "Anyone with a key"
Lang.accessibilityList = "List"
Lang.accessibilityOnlyMe = "Only me"
Lang.housesNearbyTitle = "Houses (%s)"
Lang.numberOfUnits = "%s Unit(s)"
Lang.houseNumber = "House #%s"
Lang.noHousesAvailable = "No houses available"
Lang.placeStableSpawnLabel = "Place Stable Spawn"
Lang.placeWagonSpawnLabel = "Place Wagon Spawn"
Lang.pressToWelcome = "Press ~%s~ to welcome it"
Lang.addFurnitures = "Add Furnitures"
Lang.editFurnitures = "Edit Furnitures"
Lang.listFurnitures = "List placed furnitures"
Lang.placedFurnituresList = "Placed furnitures"
Lang.focusFurniture = "Focus this furniture"
Lang.refreshFurnituresList = "Refresh list"
Lang.noFurnitures = "No furnitures"
Lang.setDressingLocation = "Set Dressing Location"
Lang.setStorageLocation = "Set Storage Location"
Lang.leaveBuildMode = "Leave Build Mode"
Lang.moveFurniture = "Move Furniture"
Lang.duplicateFurniture = "Duplicate Furniture"
Lang.deleteFurniture = "Delete Furniture"
Lang.leaveEditFurnitures = "Leave Edit Mode"
Lang.leaveHouse = "Leave House"
Lang.openStorage = "Open Storage"
Lang.openDressing = "Open Dressing"
Lang.addPlayer = "Add Player"
Lang.removePlayer = "Remove Player"
Lang.housePromptGroupLabel = "Nearby Houses"
Lang.openHouseMenuPrompt = "Open House Menu"
Lang.knockOnHouse = "Knock on House"
Lang.enterHouse = "Enter House"
Lang.editHouse = "Edit House"
Lang.manageHouse = "Manage House"
Lang.visitHouse = "Visit House"
Lang.editHousePrompt = "Edit House"
Lang.deleteHousePrompt = "Delete House"
Lang.removeOwnerPrompt = "Remove Owner"
Lang.manageKeySubTitle = "Manage keys"
Lang.buyNewKeyTitle = "Buy a new key"
Lang.changeKeylockTitle = "Change lock"
Lang.search = "Search..."
Lang.accessStablePrompt = "Access Stable"
Lang.accessWagonPrompt = "Access Wagon"
Lang.managePlayerPermission = "Manage player permissions"
Lang.canOpenStorage = "Can open storage"
Lang.canOpenWardrobe = "Can open wardrobe"
Lang.canAddFurnitures = "Can add furnitures"
Lang.canEditFurnitures = "Can edit furnitures"
Lang.removeDoor = "Remove this door"

Lang.door = "Door"
Lang.doorsCount = "%s door(s) added"
Lang.addDoor = "Add this door"
Lang.addDoors = "Add Doors"
Lang.setMloInterior = "Set Interior"
Lang.openDoor = "Open the door"
Lang.closeDoor = "Close the door"

-- * HOUSEMANAGER - UPSERT ITEM DESCRIPTIONS
-- * ==================================================
Lang.upsertDoorDescription = "Remove this door from the house."
Lang.upsertHouseNameDescription = "Set the name displayed for this house."
Lang.upsertHouseTypeDescription = "Shows the type of house being configured."
Lang.upsertMloInteriorDescription = "Set the MLO interior where the house is located."
Lang.upsertAddDoorsDescription = "Add nearby doors that belong to this house."
Lang.upsertSaveDescription = "Save the house configuration."
Lang.upsertCategoryDescription = "Filter the available interiors by category."
Lang.upsertRoomsAmountDescription = "Filter the available interiors by room count."
Lang.upsertInteriorDescription = "Select the interior model to use for the house."
Lang.upsertShellLocationDescription = "Place the house shell at your current location."
Lang.upsertFrontDoorDescription = "Place the house front door at your current location."
Lang.upsertZoneDescription = "Define the zone that contains the house."
Lang.upsertContractTypeDescription = "Choose whether the house is sold or rented."
Lang.upsertRentPeriodDescription = "Choose how often rent is charged."
Lang.upsertPriceDescription = "Set the sale price or rent amount."
Lang.upsertHouseTaxDescription = "Enable or disable house taxes."
Lang.upsertTaxPriceDescription = "Set the amount charged for house taxes."
Lang.upsertTaxPeriodDescription = "Set how many days are between tax payments."
Lang.upsertStableDescription = "Enable or disable a stable for this house."
Lang.upsertStableLocationDescription = "Place the stable at your current location."
Lang.upsertStableSpawnDescription = "Place the stable spawn point at your current location."
Lang.upsertStorageDescription = "Enable or disable storage for this house."
Lang.upsertStorageWeightDescription = "Set the maximum storage weight."
Lang.upsertStorageSlotsDescription = "Set the maximum number of storage slots."
Lang.upsertWagonDescription = "Enable or disable a wagon for this house."
Lang.upsertWagonLocationDescription = "Place the wagon at your current location."
Lang.upsertWagonSpawnDescription = "Place the wagon spawn point at your current location."
Lang.upsertDressingDescription = "Enable or disable a dressing for this house."

-- * ==================================================
-- * INPUT PROMPTS (User text input)
-- * ==================================================
Lang.inputHouseName = "House name :"
Lang.inputStorageMaxWeight = "Storage maximum weight (kg):"
Lang.inputStorageMaxSlots = "Storage maximum slots:"
Lang.playerSource = "Player Source ID"
Lang.playerName = "Player Name"
Lang.searchFurnitures = "Search furniture by prop name"

-- * ==================================================
-- * NOTIFICATIONS & MESSAGES
-- * ==================================================
-- ° Success Messages
Lang.houseUpdated = "House updated!"
Lang.houseCreated = "House created!"
Lang.positionSet = "Position Set!"
Lang.playerRemovedSuccessfully = "Player removed successfully."
Lang.houseBought = "House bought successfully!"
Lang.houseSoldSuccessfully = "House sold back to market successfully!"
Lang.houseListedForResaleSuccessfully = "House listed for resale successfully!"
Lang.resalePaymentReceived = "You received a payment for your house resale!"
Lang.houseBoughtWithKey = "House bought, you received x1 house key !"
Lang.furnitureBought = "Furniture bought and placed!"
Lang.errorBuyingFurniture = "An error occurred while buying the furniture. Your payment was refunded."
Lang.houseTransferSuccess = "House transferred successfully."
Lang.accessibilitySaved = "Accessibility settings saved."
Lang.houseDeleted = "House deleted successfully."
Lang.rentPaidSuccessfully = "Rent paid successfully!"
Lang.taxPaidSuccessfully = "House tax paid successfully!"
Lang.mloInteriorSet = "House interior set successfully!"

-- ° Error Messages
Lang.cantPlaceMarkerWhileVisiting = "You cannot place a marker while in Visit Mode"
Lang.cantPlaceShellWhileVisiting = "You cannot place the shell while in Visit Mode"
Lang.nameFieldCreateHouseError = "The house name is required"
Lang.frontdoorLocationFieldCreateHouseError = "The front door location is required"
Lang.shellLocationFieldCreateHouseError = "The interior's shell should be placed"
Lang.stableLocationFieldCreateHouseError = "The stable location is required when stable is enabled"
Lang.stableSpawnLocationFieldCreateHouseError = "Stable spawn location is required when stable is enabled"
Lang.wagonLocationFieldCreateHouseError = "The wagon location is required when wagon is enabled"
Lang.wagonSpawnLocationFieldCreateHouseError = "Wagon spawn location is required when wagon is enabled"
Lang.priceNumberError = "Price should be a number"
Lang.valueNumberError = "Value should be a number"
Lang.weightNumberError = "Weight should be a number"
Lang.slotsNumberError = "Slots should be a number"
Lang.selectValidInteriorError = "Please select a valid interior first."
Lang.interiorTypeRequiredError = "An interior type must be selected."
Lang.negativePriceError = "Prices cannot be negative."
Lang.resalePriceRequired = "Set at least one resale price."
Lang.negativeStorageValuesError = "Storage weight and slots cannot be negative."
Lang.errorRemovingPlayer = "Error removing player."
Lang.errorAddingPlayer = "Error adding player."
Lang.noPermissionBuyHouse = "You don't have permission to buy this house."
Lang.houseAlreadyOwned = "This house is already owned."
Lang.noPermissionEnterBuildMode = "You don't have permission to enter build mode."
Lang.periodExceedMaxAllowedDays = "Period exceeds maximum allowed days."
Lang.periodExceedMaxAllowedWeeks = "Period exceeds maximum allowed weeks."
Lang.cannotPaywith = "You cannot pay with this payment option."
Lang.invalidPrice = "Invalid price."
Lang.invalidPaymentMethod = "Invalid payment method."
Lang.errorBuyingHouse = "Error buying house."
Lang.noPermissionEnterHouse = "You don't have permission to enter this house."
Lang.noPermissionBuyFurniture = "You don't have permission to buy furniture."
Lang.noPermissionPlaceFurniture = "You don't have permission to place furniture."
Lang.noPermissionEditFurnitures = "You don't have permission to edit furnitures."
Lang.notHouseOwner = "You are not the owner of this house."
Lang.maxFurnituresAmountReached = "Maximum amount of furnitures reached for this house type."
Lang.noPermissionSetDressingLocation = "You don't have permission to set the dressing location."
Lang.dressingNotEnable = "Dressing is not enabled for this house."
Lang.noPermissionSetStorageLocation = "You don't have permission to set the storage location."
Lang.storageNotEnable = "Storage is not enabled for this house."
Lang.noPermissionTransferHouse = "You don't have permission to transfer this house."
Lang.houseTransferFailed = "Failed to transfer house."
Lang.noPermissionOpenHouseStorage = "You don't have permission to open the house storage."
Lang.noPermissionOpenHouseWardrobe = "You don't have permission to open the house wardrobe."
Lang.noPermissionAddPlayerToHouse = "You don't have permission to add a player to this house."
Lang.houseNotFound = "House not found."
Lang.houseHasNoOwner = "This house has no owner."
Lang.houseOwnerRemoved = "House owner removed."
Lang.houseOwnerRemoveFailed = "Failed to remove house owner."
Lang.noPermissionRemovePlayerFromHouse = "You don't have permission to remove this player from the house."
Lang.invalidInput = "Invalid input provided."
Lang.noPermissionUpsertHouse = "You don't have permission to create or update houses."
Lang.noPermissionDeleteHouse = "You don't have permission to delete houses."
Lang.invalidHouseData = "Invalid house data provided."
Lang.noPermissionMoveFurniture = "You don't have permission to move furniture."
Lang.noPermissionDeleteFurniture = "You don't have permission to delete furniture."
Lang.noPermissionUpdateAccessibility = "You don't have permission to update house accessibility."
Lang.errorSavingAccessibility = "Error saving accessibility settings."
Lang.managePlayerPermissions = "Permissions"
Lang.managePlayerPermissionsSubtitle = "Set specific permissions for this player"

Lang.playerPermissionsSaved = "Player permissions saved successfully"
Lang.errorSavingPlayerPermissions = "Error saving player permissions"
Lang.houseNotForRent = "This house is not for rent."
Lang.invalidRentPeriodCount = "Invalid number of rent periods selected."
Lang.errorPayingRent = "An error occurred while paying rent."
Lang.cannotExtendRentYet = "Can't extend rent yet"
Lang.houseNotTaxed = "This house has no tax to pay."
Lang.errorPayingTax = "An error occurred while paying the house tax."
Lang.cannotPayTaxYet = "The house tax is already paid far enough ahead."
Lang.invalidTaxPeriodCount = "Invalid number of tax periods selected."
Lang.taxPriceRequired = "A tax price is required when the house tax is enabled."
Lang.invalidTaxPeriod = "The tax period must be a whole number of days, at least 1."
Lang.dontHaveKey = "You don't have the key to enter this house"
Lang.keyModeNotEnabled = "Key mode is not enabled for this server"
Lang.lockChangedSuccessfully = "Lock changed successfully! You should buy a new key."
Lang.errorChangingLock = "Error changing the house lock."
Lang.newKeyBoughtSuccessfully = "New key purchased successfully!"
Lang.errorBuyingNewKey = "Error purchasing new key."
Lang.notAllowed = "Not allowed"
Lang.notInInteriorError = "You must be inside a house to do this."
Lang.noDoorNearbyError = "No door nearby."
Lang.doorNotRegisteredError = "This door is not registered in the system."
Lang.doorAlreadyAddedError = "This door is already added to the house."
Lang.mloInteriorNotSetError = "House interior not set for this house."
Lang.mloDoorsRequiredError = "At least one door must be added."
Lang.shouldBeOutsideError = "You should leave the house to do this."
Lang.cannotBeUsedOnThisHouse = "Cannot be used on this house"
Lang.furnitureNotAllowedHere = "Furniture not allowed here"
Lang.furnitureModelLoadFailed = "This furniture is unavailable because its model could not be loaded."

-- ° Informational Messages
Lang.knockingOnHouse = "Someone is knocking..."
Lang.goToDoor = "Go to the door"
Lang.rentDueSoon = "Rent due within the next %s"
Lang.rentOverdueTolerance = "%s left to pay rent before the house is put back on the market"
Lang.rentPaidDuration = "Paid for %s"
Lang.availableForRent = "Available for rent"
Lang.rentExceededTolerancePeriod = "Rent expired, eviction processed"
Lang.taxDueSoon = "Tax due within the next %s"
Lang.taxOverdueTolerance = "%s left to pay the house tax before the house is put back on the market"
Lang.taxOverdueEvictionBlocked = "House tax overdue, eviction currently suspended"
Lang.taxPaidDuration = "Tax paid for %s"
Lang.taxExceededGracePeriod = "Tax unpaid, eviction processed"
Lang.evictedForUnpaidTax = "You lost %s: the house tax was left unpaid"
Lang.doorAdded = "Door added to the house"
Lang.doorStateOpened = "Door is now open"
Lang.doorStateClosed = "Door is now closed"

-- * ==================================================
-- * INTERIORS CATEGORIES
-- * ==================================================

Lang.int_cat_shack = "Shacks"
Lang.int_cat_rock_shack = "Rock Shacks"
Lang.int_cat_house = "Houses"
Lang.int_cat_flat = "Flats"
Lang.int_cat_manor = "Manors"
Lang.int_cat_worker = "Workers"

-- * ==================================================
-- * FURNITURES CATEGORIES
-- * ==================================================

Lang.fur_cat_beds = "Beds"
Lang.fur_cat_carpets = "Carpets"
Lang.fur_cat_cabinets = "Cabinets"
Lang.fur_cat_chairs = "Chairs"
Lang.fur_cat_tables = "Tables"
Lang.fur_cat_desks = "Desks"
Lang.fur_cat_fireplaces = "Fireplaces"
Lang.fur_cat_lamps = "Lamps"
Lang.fur_cat_plants = "Plants"
Lang.fur_cat_sofas = "Sofas"
Lang.fur_cat_wall_decorations = "Wall Decorations"
Lang.fur_cat_decorations = "Decorations"
Lang.fur_cat_bathroom = "Bathroom"
Lang.fur_cat_kitchen = "Kitchen"
Lang.fur_cat_food = "Food"
Lang.fur_cat_curtains = "Curtains"

-- * ==================================================
-- * Input NUI
-- * ==================================================

Lang.inputNuiPaymentOptions = "Payment options"
Lang.inputNuiOrExplanation = "Set how the price can be paid"
Lang.inputNuiOption = "Option"
Lang.inputNuiInvalid = "invalid"
Lang.inputNuiAddPaymentOption = "Add another payment option"

Lang.inputNuiRequirements = "Requirements"
Lang.inputNuiDuplicateOption = "Duplicate option"
Lang.inputNuiRemoveOption = "Remove option"
Lang.inputNuiAddRequirementToOption = "Add a requirement to this option."
Lang.inputNuiAddRequirement = "Add requirement"
Lang.inputNuiAddCost = "Add"
Lang.inputNuiAlreadyInOption = "is already in this option."

Lang.inputNuiItem = "Item"
Lang.inputNuiQuantity = "Qty"
Lang.inputNuiKeep = "Keep"
Lang.inputNuiAmount = "Amount"
Lang.inputNuiRemoveRequirement = "Remove requirement"

Lang.inputNuiPreview = "Preview"
Lang.inputNuiNoOptionAvailable = "No option available"

Lang.inputNuiCurrentDraft = "Current draft:"
Lang.inputNuiPrice = "Price:"
Lang.inputNuiPlayerPays = "Player pays:"
Lang.inputNuiPriceInvalid = "This price cannot be confirmed until every option is valid."

Lang.inputNuiMoney = "Money"
Lang.inputNuiGold = "Gold"
Lang.inputNuiRol = "ROL"

Lang.inputNuiItemNameRequired = "Item name is required."
Lang.inputNuiQuantityPositive = "Quantity must be greater than 0."
Lang.inputNuiMoneyNegative = "Money cannot be negative."
Lang.inputNuiAmountPositive = "Amount must be greater than 0."
Lang.inputNuiOptionRequirement = "This payment option needs at least one requirement."
Lang.inputNuiOptionInvalidRequirements = "Fix invalid requirements in this option."

Lang.inputNuiMissingItem = "missing item"
Lang.inputNuiKept = "kept"
Lang.inputNuiNoRequirementSet = "No requirement set"
Lang.inputNuiFree = "Free"
```

`jo_housing/config/_default.lock/rent.lua`

```lua
-- ===================================
-- Rent Configuration
-- ===================================

Config.rentMaxDays = 10                      -- Maximum number of days players can rent at once (daily rent)
Config.rentMaxWeek = 2                       -- Maximum number of weeks players can rent at once (weekly rent)
Config.extraDaysAllowedBeforeExpulsion = 1.0 -- Grace period (in days) after rent expires before eviction
Config.allowRentExtensionAnytime = false     -- Allow players to extend rent before it expires
```

`jo_housing/config/_default.lock/tax.lua`

```lua
-- ===================================
-- House Tax Configuration
-- ===================================
-- A recurring tax on houses for sale, enabled per house in the creation menu.
-- Rented houses are never taxed, they already have the rent cycle.
--
-- A payment covers a number of days: the house is then "paid until" that date, which the
-- owner sees in the house menu. Once the date is passed, a grace period starts. If it runs
-- out, the owner loses the house, its furnitures are removed and it goes back on sale.
--
-- With the values below, a player buying on July 1st:
--   July 1st   pays, the house is paid until July 8th
--   July 7th   status turns orange in the house menu, one day left
--   July 8th   date passed, status turns red, 3 days left to pay
--   July 11th  nothing paid: eviction, furnitures removed, house back on sale
--
-- Paying late never costs extra: a payment always counts from the day it is made, so the
-- owner never owes two periods at once.

Config.defaultTaxPrice = { { money = 50 } } -- Price suggested when enabling the tax on a house. It only pre-fills the creation menu,
Config.defaultTaxPeriodInDays = 7           -- Days covered by one payment. Set per house in the creation menu.
Config.taxGracePeriodInDays = 3             -- Days left to pay once the date is passed, before eviction
Config.taxWarningPeriodInHours = 24         -- How long before the date the status turns orange

-- How far ahead the tax can be paid. Owners can pay several times in a row to build up
-- credit, up to this limit.
-- Example with Config.defaultTaxPeriodInDays = 7  :
--   7    no early payment, only once the date is reached
--   14   up to 2 payments ahead
--   30   up to 4 payments ahead
-- Keep it at twice Config.defaultTaxPeriodInDays  or more, otherwise early payment is impossible.
Config.taxMaxDaysPaidAhead = 14

-- ===================================
-- Hooks
-- ===================================
-- Filters, for what the settings above cannot express. Both must return a value.
--   houseTaxPrice           change the amount charged (discounts, tax-free events)
--   canPayHouseTax          allow or deny a payment
--   canEvictForUnpaidTax    return false to suspend an eviction, the owner keeps the house
-- Actions, to react to what happened:
--   houseTaxPaid            a tax payment went through
--   houseTaxEvicted         an owner just lost their house
--
-- DOCUMENTATION : https://docs.jumpon-studios.com/RedM/housing
```

## 5. FAQ

:::details It's raining in my house !
Weather effects (rain, snow) can appear inside interiors due to your framework's weather synchronization system. To fix this, you need to disable weather sync when players enter houses and re-enable it when they leave.

**Solution:**
Use the housing script's server actions to control your framework's weather sync:

```lua
-- Disable weather sync when entering a house
exports.jo_housing:registerAction('houseEntered', function(source, house, isVisiting)
    -- Weather sync from kibook
    TriggerClientEvent('weathersync:setSyncEnabled', source, false)
end)

-- Re-enable weather sync when leaving a house  
exports.jo_housing:registerAction('houseLeft', function(source, house)
    -- Weather sync from kibook
    TriggerClientEvent('weathersync:setSyncEnabled', source, true)
end)
```

Reference: [houseEntered action](#server-houseentered) and [houseLeft action](#server-houseleft)
:::

:::details Can I add custom interiors ?
Yes! You can add your own custom interiors using the `addInteriors` filter. This allows you to integrate custom MLO interiors or propsets into the housing system.

**How to add custom interiors:**

```lua
-- In your server-side resource or in jo_housing itself
exports.jo_housing:registerFilter('addInteriors', function(customInteriors)
    -- Add your custom interiors
    customInteriors.my_custom_cabin = {
        entries = {
            vec4(2.022125, -0.556265, 1.696404, 91.094)  -- Main door spawn point (relative to interior origin)
        },
        propset = "my_custom_cabin",     -- Interior propset/MLO name
        category = "shack",              -- Interior category (house, shack, manor, flat, rock_shack, worker)
        numberRoom = 2,                  -- Number of rooms in the interior
        insideDoors = {                  -- Optional: Interior working doors configuration
            { 
                model = "p_door04x", 
                position = vec3(0.062589, -4.6552873, 0.738253) 
            }
        }
    }
    
    customInteriors.luxury_penthouse = {
        entries = {
            vec4(0.0, 0.0, 2.0, 180.0)  -- Main door spawn point
        },
        propset = "luxury_penthouse_mlo",
        category = "manor",
        numberRoom = 8,
        -- insideDoors is optional - omit if no working interior doors needed
    }
    
    return customInteriors
end)
```

**Configuration Tips:**
- Set furniture limits for your custom interiors in [`Config.interiorsMaxFurnitures`](#interior-configuration)
- Use `/houseManager` visit mode to test and preview your interior placement
- Interior categories affect available furniture limits and UI organization

Reference: [addInteriors filter](#addinteriors) in the Developer section
:::

:::details Can I add custom furnitures (props) ?
Yes! You can add custom furniture categories and props using the `addFurnitures` filter. This allows you to expand the available furniture options beyond the default categories.

**How to add custom furniture:**

```lua
-- In your server-side resource or in jo_housing itself
exports.jo_housing:registerFilter('addFurnitures', function(customFurnitures)
    -- Add your custom furniture categories
    customFurnitures.custom_electronics = {
        "p_tv01x",
        "p_radio01x", 
        "p_lamp_handheld01x",
        "p_phonograph01x"
    }
    
    customFurnitures.outdoor_furniture = {
        "p_bench05x",
        "p_campfire01x",
        "p_tent01x",
        "p_waterbarrel01x"
    }
    
    customFurnitures.western_decor = {
        "p_spittoon01x",
        "p_horseshoe01x",
        "p_banjo01x"
    }
    
    return customFurnitures
end)
```

**Don't forget to configure pricing:**
Add pricing for your custom categories in your `overwriteConfig.lua`:

```lua
-- Configure pricing for custom furniture categories
Config.furnituresCategoriesPrices.custom_electronics = {
    money = 250,
    gold = 3
}

Config.furnituresCategoriesPrices.outdoor_furniture = {
    money = 75,
    gold = 1
}

-- You can also set specific prices for individual props
Config.furnituresPrices.p_tv01x = {
    money = 500,
    gold = 5
}
```

Reference: [addFurnitures filter](#addfurnitures) and [Furniture Configuration](#furniture-configuration) in the Developer section
:::

:::details Can I disable the Build Mode ?
If you set the interior’s max furniture to `0` (zero), players won’t be able to add any furniture inside, effectively disabling build mode for that interior.

**Example:**

```lua
-- Available interior categories: shack, rock_shack, house, flat, manor, worker
-- Default furniture limit for interior categories
Config.interiorsCategoriesMaxFurnitures = {
    default = 0,      -- Disable build mode for all interiors
    manor = 200         -- Except for the manor category
}

-- Specific furniture limits per interior ID
Config.interiorsMaxFurnitures = {
    jo_pai_house = 0, -- Disable build mode only for jo_pai_house
}
```

:::

:::details The prompts are not working !
If prompts aren't appearing or responding, this is usually related to key binding or keyboard layout issues. Here are the most common solutions:

**Primary Solution - Keyboard Layout:**
This script uses the [raw keys](/jo_libs/modules/raw-keys/) module. If you have problems with prompts, [set the keyboard layout](/jo_libs/modules/raw-keys/client#setting-keyboard-layout) properly.

**Additional Troubleshooting Steps:**

1. **Check for Key Conflicts:**
   - Other resources might be using the same keys
   - Review your [`Config.keys`](#key-bindings) settings in `overwriteConfig.lua`
   - Try changing conflicting keys to different bindings

2. **Verify Distance Settings:**
   - Make sure you're close enough to interact with objects
   - Check [`Config.distanceShowHousePrompt`](#distance-settings) and related distance settings
   - Default house prompt distance is 2.0 meters

3. **Framework Compatibility:**
   - Ensure your framework is [compatible with jo_libs](/jo_libs/modules/framework-bridge/#compatible-frameworks)
   - Check that jo_libs is properly installed and started before jo_housing
:::

## 6. For Developers

### Actions

Actions are one of the two types of Hooks. They provide a way for running a function at a specific point in the execution of scripts. Callback functions for an Action do not return anything back to the calling Action hook. They are the counterpart to Filters.
  
Below is a complete list of all available actions in the jo_housing script.

#### `[Server]` accessibilityChanged
Triggered when house accessibility settings are changed.

```lua
-- @param source - serverID of the house owner
-- @param house - the house object
-- @param accessibilityType - new accessibility type ("everyone", "list", or "onlyMe")
exports.jo_housing:registerAction('accessibilityChanged', function(source, house, accessibilityType)
    -- Your code here
end)
```

#### `[Server]` buildModeEntered
Triggered when a player enters build mode in their house.

```lua
-- @param source - serverID of the player
exports.jo_housing:registerAction('buildModeEntered', function(source)
    -- Your code here
end)
```

#### `[Server]` dressingLocationSet
Triggered when a dressing room location is set in a house.

```lua
-- @param source - serverID of the player
-- @param house - the house object
-- @param coords - coordinates of the dressing location
exports.jo_housing:registerAction('dressingLocationSet', function(source, house, coords)
    -- Your code here
end)
```

#### `[Server]` furnitureBought
Triggered when a player purchases furniture for their house.

```lua
-- @param source - serverID of the player
-- @param house - the house object where furniture was bought
-- @param furniture - the furniture object that was purchased
-- @param price - resolved price paid for the furniture
-- @param priceIndex - selected price option index
exports.jo_housing:registerAction('furnitureBought', function(source, house, furniture, price, priceIndex)
    -- Your code here
end)
```

#### `[Server]` furnitureDeleted
Triggered when furniture is deleted from a house.
```lua
-- @param source - serverID of the player deleting the furniture
-- @param house - the house object containing the furniture
-- @param furnitureId - ID of the furniture that was deleted
exports.jo_housing:registerAction('furnitureDeleted', function(source, house, furnitureId)
    -- Your code here
end)
```

#### `[Server]` furnitureMoved
Triggered when furniture is moved within a house.

```lua
-- @param source - serverID of the player
-- @param house - the house object containing the furniture
-- @param furnitureId - ID of the furniture that was moved
exports.jo_housing:registerAction('furnitureMoved', function(source, house, furnitureId)
    -- Your code here
end)
```

#### `[Server]` houseBought
Triggered when a player successfully purchases a house.

```lua
-- @param source - serverID of the buyer
-- @param house - the house object that was purchased
-- @param price - resolved price paid for the house
-- @param priceIndex - selected price option index
exports.jo_housing:registerAction('houseBought', function(source, house, price, priceIndex)
    -- Your code here
end)
```

#### `[Server]` houseCreated
Triggered when a new house is created .

```lua
-- @param source - serverID of the player who created the house
-- @param house - the newly created house object
exports.jo_housing:registerAction('houseCreated', function(source, house)
    -- Your code here
end)
```

#### `[Server]` houseDeleted
Triggered when a house is deleted .

```lua
-- @param source - serverID of the player who deleted the house
-- @param house - the house object that was deleted
exports.jo_housing:registerAction('houseDeleted', function(source, house)
    -- Your code here
end)
```

#### `[Server]` houseEntered
Triggered when a player enters a house.

```lua
-- @param source - serverID of the player
-- @param house - the house object being entered
-- @param isVisiting - boolean indicating if the player is visiting
exports.jo_housing:registerAction('houseEntered', function(source, house, isVisiting)
    -- Your code here
end)
```

#### `[Server]` houseKeyBought
Triggered when a new house key is purchased.

```lua
-- @param source - serverID of the player buying the key
-- @param house - the house object
-- @param price - resolved price paid for the key
-- @param priceIndex - selected price option index
exports.jo_housing:registerAction('houseKeyBought', function(source, house, price, priceIndex)
    -- Your code here
end)
```

#### `[Server]` houseLeft
Triggered when a player leaves a house.

```lua
-- @param source - serverID of the player
-- @param house - the house object being left
exports.jo_housing:registerAction('houseLeft', function(source, house)
    -- Your code here
end)
```

#### `[Server]` houseLocationMenuOpened
Triggered when a player opens the house location menu.

```lua
-- @param source - serverID of the player
-- @param houses - array of nearby houses
exports.jo_housing:registerAction('houseLocationMenuOpened', function(source, houses)
    -- Your code here
end)
```

#### `[Server]` houseListedForResale
Triggered when an owned house is listed for resale.

```lua
-- @param source - serverID of the player listing the house
-- @param house - the house object being listed
-- @param groupOrReason - resale price group or the reason for the listing
exports.jo_housing:registerAction('houseListedForResale', function(source, house, groupOrReason)
    -- Your code here
end)
```

#### `[Server]` houseLockChanged
Triggered when a house lock is changed.

```lua
-- @param source - serverID of the house owner
-- @param house - the house object
-- @param price - resolved price paid for the lock change
-- @param priceIndex - selected price option index
-- @param success - boolean indicating if lock change was successful
exports.jo_housing:registerAction('houseLockChanged', function(source, house, price, priceIndex, success)
    -- Your code here
end)
```

#### `[Server]` houseStorageOpened
Triggered when a player opens house storage.

```lua
-- @param source - serverID of the player
-- @param house - the house object
-- @param invId - inventory ID that was opened
exports.jo_housing:registerAction('houseStorageOpened', function(source, house, invId)
    -- Your code here
end)
```

#### `[Server]` houseTransfered
Triggered when house ownership is transferred to another player.

```lua
-- @param source - serverID of the current owner
-- @param toPlayerSrc - serverID of the new owner
-- @param house - the house object being transferred
-- @param transferDone - boolean indicating if transfer was successful
exports.jo_housing:registerAction('houseTransfered', function(source, toPlayerSrc, house, transferDone)
    -- Your code here
end)
```

#### `[Server]` houseOwnerChanged
Triggered every time the owner of a house changes, whatever the reason. Use it to react to any ownership change in one place, for example to empty the house storage (inventory ID `jo_housing_<houseId>`).

| `reason` | When |
| --- | --- |
| `bought` | A player bought the house (including a house listed for resale) |
| `rented` | A player rented the house |
| `transferred` | The owner transferred the house to another player |
| `sold` | The owner sold the house back to the market |
| `rentCancelled` | The tenant ended the rental |
| `listedForResale` | The owner listed the house for resale |
| `adminRemoved` | An admin removed the owner |
| `rentExpired` | The rental expired and the grace period is over |
| `taxEvicted` | The owner was evicted for unpaid tax |

```lua
-- @param house - the house object
-- @param previousOwner - the previous owner ({ identifier, charid, ... }), or false if the house had no owner
-- @param newOwner - the new owner ({ identifier, charid, ... }), or false if the house has no owner anymore
-- @param reason - why the owner changed (see the table above)
exports.jo_housing:registerAction('houseOwnerChanged', function(house, previousOwner, newOwner, reason)
    if reason == "rentExpired" then
        local invId = "jo_housing_" .. house.id
        -- Empty the storage invId here
    end
end)
```

:::tip
An expired rental is processed the next time the house status is checked (house menu opened, rent status requested or rent extension attempted), not at the exact end of the grace period.
:::

#### `[Server]` houseOwnerRemoved
Triggered when an admin removes the owner of a house.

```lua
-- @param source - serverID of the player or script removing the owner
-- @param house - the house object
-- @param ownerRemoved - boolean indicating if the owner was successfully removed
exports.jo_housing:registerAction('houseOwnerRemoved', function(source, house, ownerRemoved)
    -- Your code here
end)
```

#### `[Server]` houseSold
Triggered when an owner sells or leaves a house.

```lua
-- @param source - serverID of the player selling the house
-- @param house - the house object that was sold
-- @param refundPrice - price refunded to the previous owner
exports.jo_housing:registerAction('houseSold', function(source, house, refundPrice)
    -- Your code here
end)
```

#### `[Server]` houseTaxEvicted
Triggered after an owner loses a house because its tax remained unpaid beyond the grace period.

```lua
-- @param house - the house object that was evicted
-- @param ownerIdentifier - identifier of the previous owner
-- @param ownerCharId - character ID of the previous owner
-- @param ownerSource - serverID of the previous owner, or false if offline
exports.jo_housing:registerAction('houseTaxEvicted', function(house, ownerIdentifier, ownerCharId, ownerSource)
    -- Your code here
end)
```

#### `[Server]` houseTaxPaid
Triggered after a house-tax payment has been processed.

```lua
-- @param source - serverID of the player paying the tax
-- @param house - the taxed house
-- @param price - resolved total price for the selected number of periods
-- @param priceIndex - selected price option index
-- @param numPeriods - number of tax periods selected
-- @param success - boolean indicating if the payment was recorded successfully
exports.jo_housing:registerAction('houseTaxPaid', function(source, house, price, priceIndex, numPeriods, success)
    -- Your code here
end)
```

#### `[Server]` houseUpdated
Triggered when an existing house is updated .

```lua
-- @param source - serverID of the player who updated the house
-- @param house - the updated house object
-- @param changeset - table containing the changes made
exports.jo_housing:registerAction('houseUpdated', function(source, house, changeset)
    -- Your code here
end)
```

#### `[Server]` houseWardrobeOpened
Triggered when a player opens house wardrobe.

```lua
-- @param source - serverID of the player
-- @param house - the house object
exports.jo_housing:registerAction('houseWardrobeOpened', function(source, house)
    -- Your code here
end)

#### `[Server]` knockedOnHouse
Triggered when someone knocks on a house door.

```lua
-- @param source - serverID of the player knocking
-- @param house - the house object being knocked on
-- @param foundSources - array of player sources who were notified (propset: eligible players inside the house, MLO/Zone: notified targets excluding the knocker)
exports.jo_housing:registerAction('knockedOnHouse', function(source, house, foundSources)
    -- Your code here
end)
```

##### Rental & Keys Actions

#### `[Server]` playerAddedToHouse
Triggered when a player is added to a house's access list.

```lua
-- @param source - serverID of the house owner
-- @param house - the house object
-- @param playerSrc - serverID of the player being added
-- @param playerName - name of the player being added
exports.jo_housing:registerAction('playerAddedToHouse', function(source, house, playerSrc, playerName)
    -- Your code here
end)
```

#### `[Server]` playerPermissionsChanged
Triggered when the permissions for a player on the access list are changed.

```lua
-- @param source - serverID of the house owner
-- @param house - the house object
-- @param accessibilityId - ID of the access entry being changed
-- @param permissions - table containing the new permissions
exports.jo_housing:registerAction('playerPermissionsChanged', function(source, house, accessibilityId, permissions)
    -- Your code here
end)
```

#### `[Server]` playerRemovedFromHouse
Triggered when a player is removed from a house's access list.

```lua
-- @param source - serverID of the house owner
-- @param house - the house object
-- @param accessibilityId - ID of the access entry that was removed
-- @param success - boolean indicating if removal was successful
exports.jo_housing:registerAction('playerRemovedFromHouse', function(source, house, accessibilityId, success)
    -- Your code here
end)
```

#### `[Server]` storageLocationSet
Triggered when a storage location is set in a house.

```lua
-- @param source - serverID of the player
-- @param house - the house object
-- @param coords - coordinates of the storage location
exports.jo_housing:registerAction('storageLocationSet', function(source, house, coords)
    -- Your code here
end)
```

##### Access Management Actions

#### `[Server]` rentPaid
Triggered when rent is paid for a house.

```lua
-- @param source - serverID of the player paying rent
-- @param house - the house object
-- @param numPeriods - number of periods paid for
-- @param isDaily - boolean indicating if it's daily or weekly rent
-- @param totalPrice - total amount paid
-- @param priceIndex - selected price option index
-- @param success - boolean indicating if payment was successful
exports.jo_housing:registerAction('rentPaid', function(source, house, numPeriods, isDaily, totalPrice, priceIndex, success)
    -- Your code here
end)
```

#### `[Client]` interiorsInitialized
Triggered after the shared interior list has been initialized and custom interiors have been added.

```lua
jo.hook.registerAction('interiorsInitialized', function()
    -- Your code here
end)
```

### Filters

[Filters](/DeveloperResources/filters) allow you to modify data or control permissions during script execution. Introduced in `v1.2.0`, filters provide a **synchronous** way to intercept and modify script behavior at specific points, unlike events which are asynchronous.
  
Below is a complete list of all available filters in the jo_housing script.

#### `[Server]` addFurnitures
Allows you to add custom furniture categories and items to the housing system.

```lua
-- @param customFurnitures - empty table by default
-- @return table - table with custom furniture categories
exports.jo_housing:registerFilter('addFurnitures', function(customFurnitures)
    -- Add your custom furniture categories
    customFurnitures.custom_electronics = {
        "p_tv01x",
        "p_radio01x", 
        "p_lamp_handheld01x"
    }
    
    customFurnitures.outdoor_furniture = {
        "p_bench05x",
        "p_campfire01x",
        "p_tent01x"
    }
    
    return customFurnitures
end)
```

:::tip 💡 Custom Furniture Pricing
Don't forget to configure pricing for your custom categories in [`Config.furnituresCategoriesPrices`](#furniture-configuration) or specific items in [`Config.furnituresPrices`](#furniture-configuration).

```lua
-- In your overwriteConfig.lua
Config.furnituresCategoriesPrices.custom_electronics = {
    money = 250,
    gold = 3
}

Config.furnituresCategoriesPrices.outdoor_furniture = {
    money = 75,
    gold = 1
}
```
:::

#### `[Server]` addInteriors
Allows you to add custom interior configurations to the housing system.

```lua
-- @param customInteriors - empty table by default
-- @return table - table with custom interior configurations
exports.jo_housing:registerFilter('addInteriors', function(customInteriors)
    -- Add your custom interiors
    customInteriors.my_custom_cabin = {
        entries = {
            vec4(2.022125, -0.556265, 1.696404, 91.094)  -- Main door spawn point (relative to interior origin)
        },
        propset = "my_custom_cabin",     -- Interior propset/MLO name
        category = "shack",              -- Interior category (house, shack, manor, flat, rock_shack, worker)
        numberRoom = 2,                  -- Number of rooms in the interior
        insideDoors = {                  -- Optional: Interior working doors configuration
            { 
                model = "p_door04x", 
                position = vec3(0.062589, -4.6552873, 0.738253) 
            },
            { 
                model = "p_door04x", 
                position = vec3(-1.473662, -4.661302, 0.738253) 
            },
        }
    }
    
    customInteriors.luxury_penthouse = {
        entries = {
            vec4(0.0, 0.0, 2.0, 180.0)  -- Main door spawn point (relative to interior origin)
        },
        propset = "luxury_penthouse_mlo",
        category = "manor",
        numberRoom = 8,
        -- insideDoors is optional - omit if no working interior doors needed
    }
    
    return customInteriors
end)
```

**Interior Properties:**

| Property      | Type     | Required | Description                                                                                                                               |
| ------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `entries`     | `table`  | ✅        | Array containing the main door spawn point (vec4 with heading). Only the first entry is used, coordinates are relative to interior origin |
| `propset`     | `string` | ✅        | Interior propset/MLO resource name                                                                                                        |
| `category`    | `string` | ✅        | Interior category: `house`, `shack`, `manor`, `flat`, `rock_shack`, `worker`                                                              |
| `numberRoom`  | `number` | ✅        | Number of rooms in the interior                                                                                                           |
| `insideDoors` | `table`  | ❌        | Optional array of interior doors with `model` and `position` properties                                                                   |

:::tip 💡 Interior Configuration
- Configure furniture limits for your custom interiors using [`Config.interiorsMaxFurnitures`](#interior-configuration) or category limits with [`Config.interiorsCategoriesMaxFurnitures`](#interior-configuration)
- Test your interior coordinates thoroughly - use `/houseManager` visit mode to preview placement
- Interior categories affect available furniture limits and UI organization

```lua
-- In your overwriteConfig.lua
Config.interiorsMaxFurnitures.my_custom_cabin = 150
Config.interiorsCategoriesMaxFurnitures.luxury = 300  -- If you add a custom category
```
:::

#### `[Server]` canAddPlayerToHouse
Controls who can add players to a house's access list.

```lua
-- @param canAdd - boolean indicating if the action is allowed by default
-- @param source - serverID of the player adding someone
-- @param houseId - ID of the house
-- @param playerSrc - serverID of the player being added
-- @param playerName - name of the player being added
exports.jo_housing:registerFilter('canAddPlayerToHouse', function(canAdd, source, houseId, playerSrc, playerName)
    return canAdd
end)
```

#### `[Server]` canBuyFurniture
Controls who can buy furniture.

```lua
-- @param canBuy - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param furniture - furniture model/object data
-- @param houseId - ID of the house
-- @param price - resolved price for the furniture
-- @param priceIndex - selected price option index
-- @param categoryKey - category key of the furniture
exports.jo_housing:registerFilter('canBuyFurniture', function(canBuy, source, furniture, houseId, price, priceIndex, categoryKey)
    return canBuy
end)
```

#### `[Server]` canBuyHouse
Controls who can buy a house.

```lua
-- @param canBuy - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house being purchased
-- @param price - resolved price for the house
-- @param priceIndex - selected price option index
-- @param period - for rentals, number of periods the player is paying for
exports.jo_housing:registerFilter('canBuyHouse', function(canBuy, source, houseId, price, priceIndex, period)
    return canBuy
end)
```

#### `[Server]` canDeleteFurniture
Controls who can delete furniture from a house.

```lua
-- @param canDelete - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param furnitureId - ID of the furniture
-- @param houseId - ID of the house
exports.jo_housing:registerFilter('canDeleteFurniture', function(canDelete, source, furnitureId, houseId)
    return canDelete
end)
```

#### `[Server]` canDeleteHouse
Controls who can delete houses.

```lua
-- @param canDelete - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseData - table containing house data
exports.jo_housing:registerFilter('canDeleteHouse', function(canDelete, source, houseData)
    return canDelete
end)
```

#### `[Server]` canEnterBuildMode
Controls who can enter build mode in a house.

```lua
-- @param canEnter - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house
exports.jo_housing:registerFilter('canEnterBuildMode', function(canEnter, source, houseId)
    return canEnter
end)
```

#### `[Server]` canEnterHouse
Controls who can enter a house.
Door toggling for owned `MLO` and `Zone` houses is handled separately by `canToggleHouseDoor`.

```lua
-- @param canEnter - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house being entered
exports.jo_housing:registerFilter('canEnterHouse', function(canEnter, source, houseId)
    return canEnter
end)
```

#### `[Server]` canEvictForUnpaidTax
Controls whether an owner can be evicted when a house tax remains unpaid beyond the grace period. Return `false` to suspend the eviction; the owner keeps the house and the client receives an eviction-suspended status.

```lua
-- @param canEvict - boolean indicating if the eviction is allowed by default
-- @param house - the house object that is overdue
-- @param ownerIdentifier - identifier of the owner
-- @param ownerCharId - character ID of the owner
exports.jo_housing:registerFilter('canEvictForUnpaidTax', function(canEvict, house, ownerIdentifier, ownerCharId)
    return canEvict
end)
```

#### `[Server]` canGetPlayersAccessibilityList
Controls who can retrieve the list of players with access to a house.

```lua
-- @param canGet - boolean indicating if the action is allowed by default
-- @param source - serverID of the player requesting the list
-- @param houseId - ID of the house
exports.jo_housing:registerFilter('canGetPlayersAccessibilityList', function(canGet, source, houseId)
    return canGet
end)
```

#### `[Server]` canListHouseForResale
Controls who can list a house for resale.

```lua
-- @param canList - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house
exports.jo_housing:registerFilter('canListHouseForResale', function(canList, source, houseId)
    return canList
end)
```

#### `[Server]` canMoveFurniture
Controls who can move furniture in a house.

```lua
-- @param canMove - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param furnitureId - ID of the furniture
-- @param houseId - ID of the house
-- @param relPos - new relative position for the furniture
-- @param relRot - new relative rotation for the furniture
exports.jo_housing:registerFilter('canMoveFurniture', function(canMove, source, furnitureId, houseId, relPos, relRot)
    return canMove
end)
```

#### `[Server]` canOpenHouseLocationMenu
Controls who can open the house location menu.

```lua
-- @param canOpen - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houses - array of nearby houses
exports.jo_housing:registerFilter('canOpenHouseLocationMenu', function(canOpen, source, houses)
    return canOpen
end)
```

#### `[Server]` canOpenHouseStorage
Controls who can open a house's storage.

```lua
-- @param canOpenStorage - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house
exports.jo_housing:registerFilter('canOpenHouseStorage', function(canOpenStorage, source, houseId)
    return canOpenStorage
end)
```

#### `[Server]` canOpenHouseWardrobe
Controls who can open a house's wardrobe.

```lua
-- @param canOpen - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house
exports.jo_housing:registerFilter('canOpenHouseWardrobe', function(canOpen, source, houseId)
    return canOpen
end)
```

#### `[Server]` canPayHouseTax
Controls whether an owner can pay the tax of a house after the payment has been validated and the price has been resolved.

```lua
-- @param canPay - boolean indicating if the payment is allowed by default
-- @param source - serverID of the player paying the tax
-- @param houseId - ID of the house
-- @param price - resolved total price for the selected number of periods
-- @param priceIndex - selected price option index
-- @param numPeriods - number of tax periods selected
exports.jo_housing:registerFilter('canPayHouseTax', function(canPay, source, houseId, price, priceIndex, numPeriods)
    return canPay
end)
```

#### `[Server]` canPlaceFurniture
Controls who can place furniture in a house.

```lua
-- @param canPlace - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param furniture - furniture model/object data
-- @param houseId - ID of the house
-- @param relPos - relative position where furniture will be placed
-- @param relRot - relative rotation of the furniture
-- @param price - resolved price for the furniture
-- @param priceIndex - selected price option index
-- @param categoryKey - category key of the furniture
exports.jo_housing:registerFilter('canPlaceFurniture', function(canPlace, source, furniture, houseId, relPos, relRot, price, priceIndex, categoryKey)
    return canPlace
end)
```

#### `[Server]` canRemoveHouseOwner
Controls who can remove the owner from a house.

```lua
-- @param canRemove - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house
exports.jo_housing:registerFilter('canRemoveHouseOwner', function(canRemove, source, houseId)
    return canRemove
end)
```

#### `[Server]` canRemovePlayerFromHouse
Controls who can remove players from a house's access list.

```lua
-- @param canRemove - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house
-- @param accessibilityId - ID of the access entry to remove
exports.jo_housing:registerFilter('canRemovePlayerFromHouse', function(canRemove, source, houseId, accessibilityId)
    return canRemove
end)
```

#### `[Client]` canSeeBlipForHouse
Controls if a blip should be created for a specific house on the client.
This filter is evaluated locally for each house after the global `canSeeHousesBlips` filter has allowed blips.

```lua
-- @param canSee - boolean indicating if the blip is visible by default
-- @param house - house object
jo.hook.registerFilter('canSeeBlipForHouse', function(canSee, house)
    if not canSee then return false end

    -- Example: only show blips for houses owned by the current player
    return house:isOwner()
end)
```

#### `[Server]` canSeeHousesBlips
Controls if the player can see housing blips at all.
This filter is called once when the player's housing data is initialized, before houses are sent to the client.

```lua
-- @param canSee - boolean indicating if housing blips are visible by default
-- @param source - serverID of the player
exports.jo_housing:registerFilter('canSeeHousesBlips', function(canSee, source)
    return canSee
end)
```

#### `[Server]` canSellHouse
Controls who can sell a house.

```lua
-- @param canSell - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house
exports.jo_housing:registerFilter('canSellHouse', function(canSell, source, houseId)
    return canSell
end)
```

#### `[Server]` canSetDressingLocation
Controls who can set a dressing room location inside a house.

```lua
-- @param canSet - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house
-- @param coords - coordinates for the dressing location
exports.jo_housing:registerFilter('canSetDressingLocation', function(canSet, source, houseId, coords)
    return canSet
end)
```

#### `[Server]` canSetStorageLocation
Controls who can set a storage location inside a house.

```lua
-- @param canSet - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house
-- @param coords - coordinates for the storage location
exports.jo_housing:registerFilter('canSetStorageLocation', function(canSet, source, houseId, coords)
    return canSet
end)
```

#### `[Server]` canToggleHouseDoor
Controls who can open or close an owned `MLO` or `Zone` house door.
This filter is evaluated by the dedicated server door-toggle callback, separately from `canEnterHouse`.

```lua
-- @param canToggle - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house
exports.jo_housing:registerFilter('canToggleHouseDoor', function(canToggle, source, houseId)
    return canToggle
end)
```

#### `[Server]` canTransferHouse
Controls who can transfer house ownership.

```lua
-- @param canTransfer - boolean indicating if the action is allowed by default
-- @param source - serverID of the current owner
-- @param toPlayerSrc - serverID of the player receiving the house
-- @param houseId - ID of the house being transferred
exports.jo_housing:registerFilter('canTransferHouse', function(canTransfer, source, toPlayerSrc, houseId)
    return canTransfer
end)
```

#### `[Server]` canUpdateAccessibility
Controls who can update a house's accessibility settings.

```lua
-- @param canUpdate - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param accessibilityType - type of access ("everyone", "list", or "onlyMe")
-- @param houseId - ID of the house
exports.jo_housing:registerFilter('canUpdateAccessibility', function(canUpdate, source, accessibilityType, houseId)
    return canUpdate
end)
```

#### `[Server]` canUpdatePlayerPermissions
Controls who can update permissions for a player on a house's access list.

```lua
-- @param canUpdate - boolean indicating if the action is allowed by default
-- @param source - serverID of the player making the changes
-- @param houseId - ID of the house
-- @param accessibilityId - ID of the access entry being changed
-- @param permissions - table of the new permissions
exports.jo_housing:registerFilter('canUpdatePlayerPermissions', function(canUpdate, source, houseId, accessibilityId, permissions)
    return canUpdate
end)
```

#### `[Server]` canUpsertHouse
Controls who can create or update houses.

```lua
-- @param canUpsert - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
-- @param houseId - ID of the house (nil for creation)
-- @param changesetOrHouseData - changes to apply or full house data for creation
exports.jo_housing:registerFilter('canUpsertHouse', function(canUpsert, source, houseId, changesetOrHouseData)
    return canUpsert
end)
```

#### `[Server]` canUseHouseManagerCommand
Controls who can use the `/houseManager` command to create and manage houses.

```lua
-- @param canUse - boolean indicating if the action is allowed by default
-- @param source - serverID of the player
exports.jo_housing:registerFilter('canUseHouseManagerCommand', function(canUse, source)
    -- Example: Only allow admins
    local isAdmin = exports.your_permission_system:isAdmin(source)
    return isAdmin
end)
```

#### `[Server]` houseTaxPrice
Allows you to adjust the total price charged for a house-tax payment, for example to apply discounts or tax-free events. The returned value is validated again before the payment is taken.

```lua
-- @param price - resolved total price for the selected number of periods
-- @param source - serverID of the player paying the tax
-- @param house - the taxed house
-- @param numPeriods - number of tax periods selected
-- @param priceIndex - selected price option index
exports.jo_housing:registerFilter('houseTaxPrice', function(price, source, house, numPeriods, priceIndex)
    return price
end)
```

#### `[Server]` resalePaymentAmount
Allows you to modify the amount paid to the previous owner when a resale listing is bought.

```lua
-- @param amount - default resale payment amount
-- @param previousOwnerSource - serverID of the previous owner, or false if offline
-- @param previousOwnerIdentifier - identifier of the previous owner
-- @param previousOwnerCharId - character ID of the previous owner
-- @param buyerSource - serverID of the buyer
-- @param house - house object
-- @param priceIndex - selected price option index
exports.jo_housing:registerFilter('resalePaymentAmount', function(amount, previousOwnerSource, previousOwnerIdentifier, previousOwnerCharId, buyerSource, house, priceIndex)
    return amount
end)
```

:::details Example of job lock for VORP
```lua
AllowedJobs = {
    houser = true,
    houseManager = true,
}

exports.jo_housing:registerFilter('canUseHouseManagerCommand', function(canUse, source)
    local job = jo.framework:getJob(source)
    if AllowedJobs[job] then
      return canUse
    else
      jo.notif.rightError("You don't have the right job to use the house manager")
      return false
    end
end)
```
:::
