Market
The Market replaces the vanilla personal store. Every character owns a store of up to 50 rows, one item per row. The player puts items from the inventory into the store, gives each row a price in one currency, then opens the store (title over the head). Other players buy in person (click the seller) or from the Market search window. Zen and jewel sales are paid into the store wallet; the seller takes them out with Collect. WCoinC, WCoinP and GoblinPoint sales go straight to the seller's account.
Player Side#
| Action | How |
|---|---|
| Open the search window | S key, or type /market. The S key does nothing in Illusion Temple and Castle Siege |
| Open My Store | Store button in the inventory window, or /personalshop |
| Open another player's store | Click the seller, or /purchase <name> |
| Offline store | Go Offline button in My Store (see Offline Store) |
File Location#
Data/Plugins/Market.xml
A missing file is not an error: the Market runs enabled with built-in defaults (see Currency - the built-in price caps differ from the shipped file).
Changes need a GameServer restart. The plugin reload does not re-read this file.
Dependencies#
| Depends On | Purpose |
|---|---|
| ItemMove.xml | AllowStore="0" items cannot go into a store |
| Common.dat | TradeItemBlock* rules also block listing. WriteMarketLog turns the Market log on. OnlineRewardOfflineSystems decides if offline stores earn online rewards |
| MapManager.xml | Map numbers for <BlockedMaps>. Safe zone tiles for the safe-zone checks |
| ItemStack.xml | Single jewels paid out by Collect stack up to the jewel's stack size |
| CashShop | WCoinC / WCoinP / GoblinPoint balances (CashShopData table) |
| DataServer | Column Character.MarketStore and table dbo.MarketSaleLog, created by DataServer/SQL/Migrations/0002_market.sql |
Database Setup#
The Market needs database schema 2. Apply DataServer/SQL/Migrations/0002_market.sql (the Axiom Server Manager applies it for you). The script:
- adds
Character.MarketStore(store rows + wallet, saved with the inventory in the same transaction) - creates
dbo.MarketSaleLog(one row per sale) - drops the vanilla
dbo.PShopItemValuetable (no longer used)
Configuration Sections#
<Market Enabled="1" MaxRows="50" OpenMinLevel="6" SearchSafeZoneOnly="1" StoreViewRange="10"
SearchPageSize="20" SearchCooldownMs="500" SaleLogKeep="50">
<BlockedMaps></BlockedMaps>
<Offline Enabled="1" SafeZoneOnly="1" MaxMinutes="1440" CostPerHour="0"/>
<Currency Id="1" Enabled="1" Commission="0" Min="1" Max="0"/>
...
</Market>
Market#
| Attribute | Type | Range | Default | Effect |
|---|---|---|---|---|
Enabled | 0/1 | - | 1 | 0 = Market off. Store, search, buy, withdraw and collect are all refused |
MaxRows | int | 1-50 | 50 | Store rows per character |
OpenMinLevel | int | - | 6 | Character level needed to open a store |
SearchSafeZoneOnly | 0/1 | - | 1 | 1 = the search window (search and buying from it) only works inside a safe zone |
StoreViewRange | int | 0+ | 10 | Tiles a buyer may stand from the seller to open the store in person. 0 = anywhere on the same map |
SearchPageSize | int | 5-25 | 20 | Maximum rows per search page. The window asks for as many as it shows |
SearchCooldownMs | int | 100+ | 500 | Minimum milliseconds between two searches of one player |
SaleLogKeep | int | 1-50 | 50 | Sales kept per seller for the Sold tab |
Out-of-range values are clamped to the nearest limit.
BlockedMaps#
Comma-separated map numbers where a store cannot be opened. Spaces are ignored.
<BlockedMaps>0, 2, 3</BlockedMaps>
These maps are always blocked, even with an empty list:
| Map | Numbers |
|---|---|
| Chaos Castle 1-7 | 18-23, 53 |
| Illusion Temple 1-6 | 45-50 |
| Silent (Custom Arena map) | 40 |
Offline#
| Attribute | Type | Range | Default | Effect |
|---|---|---|---|---|
Enabled | 0/1 | - | 1 | 0 = no offline store |
SafeZoneOnly | 0/1 | - | 1 | 1 = going offline only works inside a safe zone |
MaxMinutes | int | 0-43200 | 1440 | Minutes a store may stay offline. 0 = no limit. 43200 = 30 days |
CostPerHour | zen | 0+ | 0 | Zen per hour. 0 = free |
Currency#
One element per currency. A currency with no element keeps its built-in rule.
| Id | Currency | Paid to |
|---|---|---|
| 1 | Zen | Store wallet |
| 2 | Jewel of Bless | Store wallet |
| 3 | Jewel of Soul | Store wallet |
| 4 | Jewel of Chaos | Store wallet |
| 5 | WCoinC | Seller's account |
| 6 | WCoinP | Seller's account |
| 7 | GoblinPoint | Seller's account |
| Attribute | Type | Range | Default | Effect |
|---|---|---|---|---|
Id | int | 1-7 | - | Currency. Other values are skipped |
Enabled | 0/1 | - | 1 | 0 = players cannot pick this currency for a listing |
Commission | int | 0-100 | 0 | Percent the server keeps from each sale. The seller gets the rest |
Min | int | 1+ | 1 | Lowest allowed price |
Max | int | 0+ | 0 | Highest allowed price. 0 = hard maximum |
Jewel prices count single jewels. A bundle counts as 10, 20 or 30 by its level.
Hard maximum (used when Max is 0 or too high):
| Currency | Hard maximum |
|---|---|
| Zen | 9,000,000,000,000,000,000 |
| Jewels and coins | 2,147,483,647 |
Built-in rule when the element (or the whole file) is missing: Enabled 1, Commission 0, Min 1, Max = zen hard maximum / 10,000 for jewels / 1,000,000 for coins. Note that an element with Max="0" gives the hard maximum, not these values.
Important Behavior#
Store rows#
- Items go into the store and come out only while the store is closed. Prices also change only while it is closed.
- A row may sit in the store with no price (unlisted). Only priced rows are shown to buyers.
- Opening needs:
OpenMinLevel, a map that is not blocked, at least one priced row, no trade / duel / other window open, and no item lock (/lock). - Logging out closes the store (unless it went offline). Rows and wallet stay saved. The player reopens it next time.
- The store closes by itself when the last priced row is sold.
- Lowering
MaxRowsdoes not delete anything. Rows above the new limit are not sold or shown to buyers, but the owner still sees them and can take them out.
What cannot be listed#
Refused when putting the item in, when pricing it and again at the moment of sale:
AllowStore="0"in ItemMove.xml- event items and rental (time-limited) items
- lucky items when
TradeItemBlockLucky = 1in Common.dat - excellent / set / harmony items blocked by the
TradeItemBlock*rules in Common.dat (GMs bypass these) - pentagrams with errtels in their sockets
- items bound to the character (item extension binding, such as a bound inscription)
- any item while the player has an item lock (
/lock) on
When a rule changes, rows that are no longer allowed become unlisted the next time the owner opens the store. The item stays in the store.
Buying#
- In person: same map, within
StoreViewRangetiles of the seller. - From search: inside a safe zone when
SearchSafeZoneOnly = 1. Distance does not matter. - Search only lists stores on the same GameServer. Own rows never show in search.
- Search filters: name text, currency, minimum item level, excellent, ancient, socket, luck, skill, option. Sort: newest, price low-high, price high-low. When a store changes, every open search page that shows it refreshes.
- The buyer must have room in the inventory. If the row changed after the buyer saw it (sold, repriced, withdrawn) the buy is refused.
- One store action or buy per 150 ms per player.
Jewel payment#
The buyer must pay the exact price from single jewels and bundles. Bundles are used first (30, 20, 10), then singles. If the exact amount cannot be made from what the buyer holds, the buy is refused, even if the total is higher. Example: price 15, buyer holds one bundle of 20 and nothing else = refused.
Wallet and Collect#
- Zen and jewel sales go into the store wallet (price minus commission).
- Collect moves zen to the character (up to the zen cap) and creates jewels in the inventory, bundles first, then singles. If the inventory fills up, the rest stays in the wallet.
- A sale is refused when it would push the seller's wallet past its cap (zen cap, or 2,147,483,647 jewels per type).
Coin sales (escrow)#
WCoinC, WCoinP and GoblinPoint sales run through the DataServer:
- The DataServer takes the coins from the buyer and writes a
MarketSaleLogrow withStatus = 0(pending). - The GameServer moves the item and tells the DataServer to pay the seller (
Status = 1). - If the buyer or seller left, the row changed, or the buyer's inventory is full, the buyer is refunded (
Status = 2) and the item stays in the store. - If the DataServer does not answer within 15 seconds, the row is released. A late answer is refunded.
The DataServer checks every 60 seconds for rows still at Status = 0 after 10 minutes (GameServer crash, link drop):
| Item is found... | Result |
|---|---|
| in the seller's store | Buyer refunded, Status = 2 |
| in the buyer's inventory | Seller paid, Status = 1 |
| anywhere else | Status = 3, red log line. A GM must settle it by hand |
Sale log#
Each seller sees the last SaleLogKeep sales in the Sold tab. Older sold rows are deleted. Pending (Status 0) and stuck (Status 3) rows are never deleted: they are the proof of payment.
To show listings and sales on a website, see Website: Market.
Offline Store#
Go Offline in My Store logs the player out. The character stays in the world with the store open and keeps selling, like /offattack.
Needs: Offline Enabled = 1, the store already open with priced rows, a safe zone when SafeZoneOnly = 1, MuHelper and /offattack not running, no coin purchase in progress, and enough zen for the first hour.
- The first hour is paid when going offline, then every hour. The cost is taken from the wallet zen first, then the character's zen.
- Online-time rewards are paused while offline unless
OnlineRewardOfflineSystems = 1in Common.dat.
The offline store ends when:
- the player logs in again
- the store sells out or is closed
MaxMinutespasses- the hourly cost cannot be paid
- a GM disconnects the character, or the server shuts down
Rows and wallet are saved in every case.
Old personal store items#
On the first login after the update, items left in the vanilla 8x4 personal store grid move into free store rows, unlisted. If the store is full, the rest stay in the old grid and a log line is written.
Market log#
WriteMarketLog in GameServerInfo - Common.dat (default 1) writes every deposit, withdraw, price change, open/close, sale, collect and coin escrow step to the Market log folder. Refused requests are logged as [Market][Reject][<action>] with the reason (max 10 lines per second per player).
Examples#
<Market Enabled="1" MaxRows="50" OpenMinLevel="50" SearchSafeZoneOnly="1" StoreViewRange="10"
SearchPageSize="20" SearchCooldownMs="500" SaleLogKeep="50">
<BlockedMaps>30</BlockedMaps>
<Offline Enabled="1" SafeZoneOnly="1" MaxMinutes="1440" CostPerHour="0"/>
<Currency Id="1" Enabled="1" Commission="5" Min="1000" Max="0"/>
<Currency Id="2" Enabled="1" Commission="5" Min="1" Max="10000"/>
<Currency Id="3" Enabled="1" Commission="5" Min="1" Max="10000"/>
<Currency Id="4" Enabled="1" Commission="5" Min="1" Max="10000"/>
<Currency Id="5" Enabled="0"/>
<Currency Id="6" Enabled="0"/>
<Currency Id="7" Enabled="0"/>
</Market>
Offline stores cost 1,000,000 zen per hour, 12 hours max. Search works anywhere.
<Market Enabled="1" SearchSafeZoneOnly="0">
<Offline Enabled="1" SafeZoneOnly="1" MaxMinutes="720" CostPerHour="1000000"/>
</Market>
<Market Enabled="1" MaxRows="20" OpenMinLevel="100" StoreViewRange="0" SaleLogKeep="20">
<BlockedMaps>0, 2, 3</BlockedMaps>
<Offline Enabled="0"/>
</Market>