Skip to content

@nextvm/housing

First-party housing module. Property definitions, ownership tracking, apartment instancing via routing buckets, banking integration for purchases.

Install

bash
pnpm add @nextvm/housing
typescript
modules: ['@nextvm/housing']

Dependencies

typescript
dependencies: ['player', 'banking']

Config

FieldTypeDefaultDescription
minPropertyPriceint >= 050000Lower bound applied when seed properties are loaded

Property registry

PropertyDefinition:

typescript
interface PropertyDefinition {
  id: string
  label: string
  type: 'apartment' | 'house' | 'business' | 'warehouse'
  entrance: { x: number; y: number; z: number }
  price: number
  maxOccupants: number
}

The module seeds three example properties on startup:

IDTypePrice
apt_eclipse_3apartment75000
apt_tinsel_42apartment90000
house_richmanhouse1250000

Define more via defineProperty():

typescript
import { PropertyRegistry, defineProperty } from '@nextvm/housing'

const registry = new PropertyRegistry()
registry.define(defineProperty({
  id: 'shop_legion',
  label: 'Legion Square Shop',
  type: 'business',
  entrance: { x: 191, y: -806, z: 30 },
  price: 350000,
  maxOccupants: 6,
}))

State

housingState:

FieldTypeDefault
ownedPropertyIdsstring[][]
currentInstanceId`stringnull`

RPC procedures

ProcedureTypeInputDescription
getMyPropertiesqueryReturns the calling character's owned properties
listPropertiesqueryReturns every defined property
getNearbyPropertiesquery{ x, y, z, radius? }Find properties within radius meters
purchasemutation{ propertyId }Buy a property (debits banking, adds to ownership)
enterPropertymutation{ propertyId }Move into the apartment instance
leavePropertymutationReturn to the main world

Apartment instancing

enterProperty uses RoutingService.createInstance() to spin up a private routing bucket per character:

typescript
const instance = routing.createInstance({
  label: `housing_${propertyId}_${charId}`,
  players: [source],
})

Other players in their own instances cannot see or hear the player inside their apartment. leaveProperty calls routing.resetPlayer() to return the player to bucket 0.

Banking integration

@nextvm/housing consumes @nextvm/banking via the adapter pattern. The interface is defined in the consumer (housing):

typescript
// modules/housing/src/banking-adapter.ts
export interface BankingAdapter {
  removeMoney(
    charId: number,
    type: 'cash' | 'bank',
    amount: number,
    reason?: string,
  ): Promise<number>
}

The purchase flow debits the bank balance via the adapter and only records ownership if the debit succeeded — atomic from the player's perspective.

HousingExports

typescript
import type { HousingExports } from '@nextvm/housing'

const housing = ctx.inject<HousingExports>('housing')
const owned = housing.getOwned(charId)

See also

Released under the LGPL-3.0 License.