Skip to content

Rental Stations

A separate path for shop ownership: admins place persistent leasable shop slots at fixed map positions. Players rent or bid for the slot, fill it with their own items, and lose access when the rental window expires.

Why have this in addition to player-created shops? Hand-curated marketplace hotspots: spawn plaza, dungeon lobby, trade hub. Server owners control which spots are valuable; players compete for them through a fixed price or an auction. Creates a natural economy sink + gives late-game players something to spend on.


/kssa createrental <displayName> [pricePerDay] [maxDays]
  • Player F-keys vacant slot -> RentalRentConfirmPage opens
  • Day slider 1..maxDays, live total = days * pricePerDay
  • Confirm -> cost withdrawn, slot is theirs for X days
/kssa createrentalauction <displayName> [minBid] [bidIncrement] [durationMinutes] [rentalDays]
  • Player F-keys vacant slot -> RentalBidPage opens
  • Live countdown, current high bid, bid history (last 6), bid slider
  • Bids are not charged at bid time - only the winner pays at finalisation
  • After auction_ends_at, scheduler tick (every 60s) finalises:
    • Winner has funds -> charge + start rental for rentalDays
    • Winner can’t pay -> auction restarts (no charge to anyone)
    • No bids -> onEmptyAuction config decides (RESTART / VACANT / DELETE)

┌─────────────────┐
│ /kssa createrental│
│ -> Vacant Shell │
└────────┬────────┘
│ player F-keys + rents/bids
│ (auction: bidding round, then finalise)
┌─────────────────┐
│ Rented (PLAYER) │
│ - renter NPC │
│ - editor access │
│ - listing free │
└────────┬────────┘
│ rented_until elapsed (60s tick)
│ OR /kssa forceexpirerental
│ OR /ksshop releaserental
┌──────────────────────────────┐
│ expireSlot │
│ - mail items + balance │
│ - despawn renter NPC │
│ - delete renter shop row │
│ - clear slot runtime state │
│ - re-arm auction if mode=AUC │
│ - recreate Vacant Shell │
│ - spawn Vacant Shell NPC │
└──────────────┬────────────────┘
(back to top)

A “shell” ShopData row of type=ADMIN with rentalSlotId set, open=false, listed_until=0. Functions as a placeholder NPC - F-keying it opens the rent/bid page instead of the normal browse.

The shell:

  • Has the slot’s display name suffixed with [100g/day] or [AUCTION 500g] so passers-by see the state without F-key
  • Uses npc.defaultSkinUsername as its skin
  • Despawns when the slot is rented; respawns when the slot expires

If a bid arrives in the last auctionAntiSnipingSeconds seconds (default 30s), the auction extends by that many seconds. Watching players see a chat broadcast: Auction extended - new end: mm:ss. Set 0 to disable.

When auction_ends_at - now <= auctionEndingSoonSeconds (default 60s), one world-chat broadcast fires: Auction <slot> ends in 60s! Current bid: X Gold. Once-per-round flag prevents spam. Set 0 to disable.

Money is not withdrawn when a bid is placed - simpler design + no refund-on-outbid gymnastics. The downside: the winner could spend the money before finalisation. If withdraw fails at finalise time, the auction restarts and the would-be winner gets a chat: Your auction win was forfeited - insufficient funds.

When a new bid replaces the current high bidder, the displaced bidder gets a chat ping: You have been outbid on '<slot>'. Current high bid: X. <time> remaining.. Offline players get nothing - they weren’t financially committed.

When onEmptyAuction = "RESTART" (default), the auction also auto-rearms after a rental on the slot ends. So the cycle is:

Auction round 1 -> Winner -> Rental -> Expiry -> Auction round 2 -> ...

Set onEmptyAuction = "VACANT" to require manual admin re-arm between rounds, or "DELETE" to remove the slot after the first rental ends.


RentalService uses per-slot synchronized locks (Map<UUID, Object>). Two players can’t race to rent the same vacant slot - the first through the lock wins; the second gets Already rented error.


Once the rental starts:

  • Slot’s NPC switches to the renter’s skin + their ShopData
  • Renter uses /ksshop edit (or F-key as owner) to fill the shop with their items, set prices, etc.
  • Listing in the public directory is automatic when rentalShopsListFree = true (default) - the rental price covers visibility
  • NPC nameplate shows <ShopName> (6d 22h left) so renters + visitors see the remaining time at a glance
  • Editor settings tab also shows Rental: 6d 22h remaining
  • /ksshop myrentals lists all of the player’s active rentals with EXTEND + RELEASE EARLY options

/ksshop myrentals -> EXTEND on a row -> opens RentConfirmPage in extend mode. Adds days to the existing rented_until, clamped to extendMaxDays per call. Uses the same pricePerDay as the original rental (for FIXED) or 0 (for AUCTION winners - they already paid the bid for the full window).

/ksshop myrentals -> RELEASE EARLY -> confirm overlay. Items + balance mailed back. Gold refund per releaseEarlyGoldRefundFraction (default 0 = no refund, commitment cost). NPC despawns + vacant shell respawns + auction (if applicable) auto-rearms.


/kssa forceexpirerental <slotId>

Runs the normal expiry path immediately. Renter gets full mailbox refund. For AUCTION slots, also auto-rearms a new round.

Useful for testing the expiry flow without waiting for the actual rented_until timestamp.

/kssa deleterental <slotId>

Removes the slot row entirely. Active renter (if any) gets mailbox refund first via the expiry path. NPC despawns. Slot disappears from /kssa listrentals.

/kssa listrentals

Chat output with each slot’s UUID, mode, state, renter (if any), remaining time. Use the UUIDs as input for the other commands.


NodeEffect
ks.rental.rentAllows renting fixed-price slots
ks.rental.bidAllows bidding in auctions
ks.rental.limit.NMax N concurrent rentals (overrides maxConcurrentRentalsDefault)

Limit nodes are wildcard-guarded - OPs fall back to the config default unless a wildcard is explicitly denied. See Permissions.


Two tables back the system:

  • shop_rental_slots - one row per slot. Holds config (mode, max_days, price/auction params) + runtime state (rented_by, rented_until, auction_ends_at, current_high_bidder, ending_soon_broadcast). rented_until is the source of truth for rental expiry.
  • shop_rental_bids - bid history. One row per bid. Indexed by (slot_id, timestamp DESC). Auction restart does not delete bid rows - they’re a permanent ledger.

shop_shops.rental_slot_id and shop_shops.rental_expires_at are denormalised mirrors on the renter’s shop row, used for fast joins in browse / nameplate code paths.

For DB-edit testing: stop server, edit the source-of-truth column (shop_rental_slots.rented_until), start server. The mirror columns get repopulated on the next rental operation.


See Configuration -> rentalStations for the full block.