565 lines
24 KiB
Markdown
565 lines
24 KiB
Markdown
# TraderX – Server Configuration Guide
|
||
|
||
*Written from a read-through of the TraderX source (`TheDmitri/TraderX`, `main`, mod version string `1.0.1`). Everything below describes what the code actually does, not what the README promises. Where the code is inconsistent or a feature is only partially implemented, it is called out.*
|
||
|
||
---
|
||
|
||
## 1. Directory layout
|
||
|
||
All paths are under the server **profile** folder (`$profile:`), i.e. wherever your `-profiles=` startup parameter points. TraderX creates the folders it needs on first start.
|
||
|
||
```
|
||
<profiles>/TraderX/
|
||
├── TraderXConfig/
|
||
│ ├── TraderXGeneralSettings.json ← traders (NPCs), licenses, accepted item states
|
||
│ ├── TraderXCurrencySettings.json ← currency types and denominations
|
||
│ ├── TraderXDynamicTranslationSettings.json ← notification / error strings
|
||
│ ├── TraderXDebugSettings.json ← debug mode + test suites
|
||
│ ├── TraderXTXSafeZoneSettings.json ← safe zone module
|
||
│ ├── SourceConfig.json ← which product/category format to load
|
||
│ ├── Products/ prod_<name>_NNN.json ← LEGACY format (one file per product)
|
||
│ ├── Categories/ cat_<name>_NNN.json ← LEGACY format (one file per category)
|
||
│ ├── Source/ products.csv, categories.csv, presets.csv ← CSV format
|
||
│ ├── Compiled/ Products_compiled.json, Categories_compiled.json ← COMPILED (auto-generated)
|
||
│ ├── Backup/ *.backup ← last successful compile
|
||
│ ├── TraderXVehicleParking/
|
||
│ │ ├── parking_config.json
|
||
│ │ └── parkings_<npcId>.json
|
||
│ └── DebugTestCases/ *.json
|
||
├── TraderXDatabase/ ← runtime state, do NOT hand-edit while server is up
|
||
│ ├── Stock/ <productId>.json ← current stock per product
|
||
│ └── PlayerLicenses/ playerLicense_<steam64>.json
|
||
├── TraderXPresets/ presets_<id>.json ← player-saved attachment presets
|
||
└── TraderXLogs/
|
||
├── Config/LoggingSettings.json
|
||
├── Config/ConfigReport.log ← CSV validation report
|
||
└── Logs/TraderX_<date>.log
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Startup / load order (important for "my edit did nothing")
|
||
|
||
On server start, `TraderXModule.InitializeModule()` does, in order:
|
||
|
||
1. Converts old **TraderPlus V1** configs if found (`TraderPlusPriceConfig.json`, `TraderPlusGeneralConfig.json`).
|
||
2. Loads `SourceConfig.json` → decides LEGACY / CSV / COMPILED.
|
||
3. Loads products, then categories, then vehicle parkings.
|
||
4. Applies "stock behavior at restart" to every limited-stock product.
|
||
5. Loads `TraderXGeneralSettings.json` and spawns NPCs.
|
||
|
||
### ⚠ The compiled files always win
|
||
|
||
`TraderXProductRepository.LoadAllProducts()` and `LoadAllCategories()` check for `Compiled/Products_compiled.json` / `Categories_compiled.json` **first, regardless of `preferredFormat`**. If those files exist and are non-empty, the `Products/` and `Categories/` folders are ignored completely.
|
||
|
||
So: if you edit legacy JSON files and nothing changes, check whether a `Compiled/` folder exists. Either delete it (it will be regenerated only in CSV/COMPILED mode) or switch to editing the CSV source.
|
||
|
||
### Fallback chain
|
||
|
||
```
|
||
Compiled JSON → (empty/corrupt) → Legacy Products/ + Categories/
|
||
```
|
||
|
||
In CSV mode, `Source/*.csv` is compiled to `Compiled/*.json` before this chain runs. If validation fails, the previous `Backup/` copy is restored.
|
||
|
||
---
|
||
|
||
## 3. `SourceConfig.json`
|
||
|
||
```json
|
||
{
|
||
"preferredFormat": "LEGACY",
|
||
"autoMigrate": false,
|
||
"allowCompilation": true
|
||
}
|
||
```
|
||
|
||
| Field | Values | Meaning |
|
||
|---|---|---|
|
||
| `preferredFormat` | `"LEGACY"`, `"CSV"`, `"COMPILED"` (case-insensitive) | Which editing workflow you use. Default `LEGACY`. |
|
||
| `autoMigrate` | bool | One-shot conversion on next start. CSV mode: legacy JSON → CSV. COMPILED mode: legacy → CSV → compiled. The mod sets this back to `false` automatically after a successful migration. Original files are **not** deleted. |
|
||
| `allowCompilation` | bool | CSV mode only. `false` skips CSV → compiled (effectively disables CSV loading; not recommended). |
|
||
|
||
**Recommended workflows**
|
||
|
||
- **Small server / few items:** stay on `LEGACY`, edit one JSON per product. Make sure no `Compiled/` folder exists.
|
||
- **Large item lists / spreadsheet editing:** set `"CSV"` + `"autoMigrate": true` once, then edit `Source/products.csv` and `Source/categories.csv` in Excel/LibreOffice. Every restart recompiles. Check `TraderXLogs/Config/ConfigReport.log` after each restart.
|
||
- **Web editor:** https://traderpluseditor.dmitri-dev-area.dev exports the legacy per-file format.
|
||
|
||
---
|
||
|
||
## 4. Products
|
||
|
||
### 4.1 Legacy file: `TraderXConfig/Products/<productId>.json`
|
||
|
||
```json
|
||
{
|
||
"className": "SNAFU_MKahles",
|
||
"coefficient": 0.66874,
|
||
"maxStock": 5,
|
||
"tradeQuantity": 18,
|
||
"buyPrice": 110,
|
||
"sellPrice": 30,
|
||
"stockSettings": 0,
|
||
"attachments": [],
|
||
"variants": []
|
||
}
|
||
```
|
||
|
||
#### ⚠ File name **is** the product ID and it is validated
|
||
|
||
`TraderXProductId.IsValidUUID()` rejects (and **skips**, with an error in the log) any file that does not match:
|
||
|
||
```
|
||
prod_<name>_NNN.json
|
||
```
|
||
|
||
- must start with `prod_`
|
||
- `<name>` = letters, digits, underscores (other characters are sanitized to `_`)
|
||
- must end with `_` + **exactly three digits**, `001`–`999`
|
||
- minimum total length 9 chars
|
||
|
||
Valid: `prod_akm_001.json`, `prod_snafu_mkahles_001.json`
|
||
**Invalid (silently skipped):** `prod_snafu_mkahles.json`, `akm.json`, `prod_akm_1.json`, `prod_akm_1000.json`
|
||
|
||
The `productId` inside the file, if present, is **ignored** in legacy mode; the ID always comes from the filename. (In `Compiled/` files the `productId` field is used.)
|
||
|
||
Log lines to grep for: `Skipping invalid UUID filename` and `Invalid product ID`.
|
||
|
||
#### Field reference
|
||
|
||
| Field | Type | Meaning |
|
||
|---|---|---|
|
||
| `className` | string | DayZ class name. Compared case-insensitively. |
|
||
| `buyPrice` | int | Price the player pays. `-1` = cannot be bought (item is sell-only). |
|
||
| `sellPrice` | int | Price the player receives. `-1` = cannot be sold (buy-only). |
|
||
| `maxStock` | int | `-1` = unlimited. `0` or more = limited stock, tracked in `TraderXDatabase/Stock/<productId>.json`. |
|
||
| `coefficient` | float | Dynamic price factor. **Only used when `maxStock != -1` and `coefficient != 1.0`**. See §4.2. |
|
||
| `tradeQuantity` | int (bitfield) | How full the item is when bought / must be when sold. See §4.3. |
|
||
| `stockSettings` | int (bitfield) | De-stock rate + restart behavior. See §4.4. |
|
||
| `attachments` | string[] | Product IDs (`prod_…`) that can be attached at purchase (customize page / presets). Each must be its own product file. |
|
||
| `variants` | string[] | Product IDs shown as colour/skin variants of this product. |
|
||
|
||
### 4.2 Dynamic pricing (how `coefficient` actually works)
|
||
|
||
From `TraderXPriceCalculation`:
|
||
|
||
```
|
||
if maxStock == -1 or coefficient == 1.0:
|
||
price = round(basePrice × quantity × stateMultiplier)
|
||
|
||
else, per unit, with s = current trader stock of this product:
|
||
unitPrice = coefficient ^ (s − 1) × basePrice × stateMultiplier
|
||
(s is clamped to 1 if 0)
|
||
buy: s increases by 1 for each additional unit in the cart
|
||
sell: s increases by 1 for each additional unit sold
|
||
total = round(Σ unitPrice), minimum 1
|
||
```
|
||
|
||
Consequences:
|
||
|
||
- **`coefficient` does nothing if `maxStock` is `-1`.** Unlimited stock ⇒ flat price. If you want dynamic prices you need a finite `maxStock`.
|
||
- With stock `s = 1` the price equals `basePrice`. Every additional unit in stock multiplies by `coefficient`.
|
||
Example: `buyPrice 100`, `coefficient 0.9`: stock 1 → 100, stock 2 → 90, stock 5 → 65.6, stock 10 → 38.7.
|
||
- Lower coefficient = steeper price drop as the trader fills up. `0.9`–`0.98` is a sensible range; `0.66` (as in the sample above) collapses the price to ~5 % by stock 8.
|
||
- The same formula is used for buy and sell, so a trader that is out of stock (s = 0 → treated as 1) sells at full `sellPrice`.
|
||
- `stateMultiplier` (item condition) for sales: pristine 1.0, worn 0.8, damaged 0.6, badly damaged 0.4, ruined 0 (refused). Whether worn/damaged/badly-damaged items are accepted at all is set in `TraderXGeneralSettings.json → acceptedStates` (§6). Note: `TraderXStates.coefficientWorn/Damaged/BadlyDamaged` exist in the settings file but the pricing code uses the hard-coded `TraderXItemState` multipliers above.
|
||
|
||
### 4.3 `tradeQuantity` bitfield
|
||
|
||
Decoded at trade time by `TraderXTradeQuantity`:
|
||
|
||
```
|
||
bits 0–2 sellMode
|
||
bits 3–5 buyMode
|
||
bits 6–18 sellValue (13 bits, 0–8191)
|
||
bits 19–31 buyValue
|
||
```
|
||
|
||
| Mode value | Buy meaning | Sell meaning |
|
||
|---|---|---|
|
||
| 0 | `BUY_EMPTY` – item spawns empty | `SELL_NO_MATTER` – any fill level accepted, paid pro-rata |
|
||
| 1 | – | `SELL_EMPTY` – must be empty |
|
||
| 2 | `BUY_FULL` – item spawns full | `SELL_FULL` – must be full |
|
||
| 3 | `BUY_COEFFICIENT` – full × (buyValue/100) | `SELL_COEFFICIENT` – max × (sellValue/100) |
|
||
| 4 | `BUY_STATIC` – exactly `buyValue` units | `SELL_STATIC` – exactly `sellValue` units |
|
||
|
||
`tradeQuantity = sellMode | (buyMode << 3) | (sellValue << 6) | (buyValue << 19)`
|
||
|
||
**Common values**
|
||
|
||
| Value | Meaning |
|
||
|---|---|
|
||
| `0` | buy empty, sell any |
|
||
| `2` | buy empty, sell only full |
|
||
| `16` | buy full, sell any |
|
||
| `18` | **buy full, sell full** (most common for mags, food, etc.) |
|
||
| `78644644` | (README example) buy static 150, sell static 22 – for stackables like mushrooms |
|
||
|
||
Quick decode in bash:
|
||
|
||
```bash
|
||
tq=18; echo "sell=$((tq&7)) buy=$(((tq>>3)&7)) sellVal=$(((tq>>6)&0x1FFF)) buyVal=$((tq>>19))"
|
||
```
|
||
|
||
> ⚠ **Known inconsistency:** the CSV compiler (`TraderXBitfieldPacker`) packs values with a *different* layout (sell value in bits 6–15 ×1000, buy value in bits 16–31 ×1000) than the runtime decoder above. For `FULL`/`EMPTY`/`NO_MATTER` modes this doesn't matter (values are 0). For `COEFFICIENT`/`STATIC` products created via CSV, the resulting quantities may be wrong. If you need STATIC/COEFFICIENT quantities, verify in-game, or set `tradeQuantity` directly in legacy JSON using the runtime layout.
|
||
|
||
### 4.4 `stockSettings` bitfield
|
||
|
||
```
|
||
bits 0–6 destockCoefficient × 100 (0–100)
|
||
bits 7–8 stockBehaviorAtRestart (0, 1, 2)
|
||
```
|
||
|
||
`stockSettings = round(destock × 100) | (behavior << 7)`
|
||
|
||
| `stockBehaviorAtRestart` | Effect on server start |
|
||
|---|---|
|
||
| 0 | keep stock as saved |
|
||
| 1 | reset stock to `maxStock` |
|
||
| 2 | random stock between 0 and `maxStock` |
|
||
|
||
`destockCoefficient` (0.0–1.0): additionally, on every restart, `stock = round(stock × (1 − destock))`. Applied *after* the behavior above.
|
||
|
||
| Example | Value |
|
||
|---|---|
|
||
| keep, no destock | `0` |
|
||
| reset to max every restart | `128` |
|
||
| random every restart | `256` |
|
||
| keep, lose 25 % per restart | `25` |
|
||
| reset to max + destock 10 % (nets 90 %) | `138` |
|
||
|
||
Only applies when `maxStock != -1`.
|
||
|
||
### 4.5 Stock database
|
||
|
||
`TraderXDatabase/Stock/<productId>.json`:
|
||
|
||
```json
|
||
{ "productId": "prod_akm_001", "stock": 3 }
|
||
```
|
||
|
||
Created automatically the first time a limited-stock product is touched. Buying decreases stock, selling increases it (capped at `maxStock` – trader refuses with "Trader stock is full"). Safe to delete the whole `Stock/` folder while the server is **down** to reset everything to 0 (or to `maxStock` if `stockBehaviorAtRestart = 1`).
|
||
|
||
### 4.6 Attachments and variants
|
||
|
||
- Every entry in `attachments` / `variants` is a **product ID**, not a class name. `["prod_mag_akm_30rnd_001", "prod_pso1optic_001"]`
|
||
- The referenced product must exist (be a valid file). Missing references are simply dropped at runtime; the CSV validator reports them as errors.
|
||
- Attachment products need their own `buyPrice`; the customize page adds their price to the base item.
|
||
- Attachments are also stock-checked: an out-of-stock attachment blocks the purchase ("Attachment out of stock").
|
||
- Variants are shown as selectable alternatives on the purchase page; each variant is a full product with its own price/stock.
|
||
|
||
---
|
||
|
||
## 5. Categories
|
||
|
||
### 5.1 Legacy file: `TraderXConfig/Categories/<categoryId>.json`
|
||
|
||
File name rule is the same as products, but with the `cat_` prefix and **lowercase** letters only in the middle part:
|
||
|
||
```
|
||
cat_<name>_NNN.json e.g. cat_assault_rifles_001.json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"isVisible": true,
|
||
"icon": "",
|
||
"categoryName": "Assault Rifles",
|
||
"licensesRequired": [],
|
||
"productIds": [
|
||
"prod_akm_001",
|
||
"prod_m4a1_001"
|
||
]
|
||
}
|
||
```
|
||
|
||
| Field | Meaning |
|
||
|---|---|
|
||
| `isVisible` | `false` hides the category from the catalog (products stay usable as attachments/variants). |
|
||
| `icon` | Path to an image passed to `ImageWidget.LoadImageFile()`, e.g. `"TraderX/datasets/images/car.edds"` or a `set:… image:…` string. Empty = no icon. |
|
||
| `categoryName` | Display name. Also used for `GetCategoryIdByName` lookups (case-insensitive). |
|
||
| `licensesRequired` | Array of **license IDs** (the `licenseId` UUID from `TraderXGeneralSettings.json`, not the name). Player must hold all of them or the card is greyed out. |
|
||
| `productIds` | Ordered list of product IDs shown in this category. A product can appear in multiple categories. |
|
||
|
||
Categories are assigned to traders in `TraderXGeneralSettings.json → traders[].categoriesId`.
|
||
|
||
---
|
||
|
||
## 6. `TraderXGeneralSettings.json`
|
||
|
||
Generated with defaults on first start (a Chernarus example trader camp). Structure:
|
||
|
||
```json
|
||
{
|
||
"version": "1.0.1",
|
||
"serverID": "<auto-generated uuid>",
|
||
"licenses": [
|
||
{
|
||
"licenseId": "<uuid>",
|
||
"licenseName": "Car License",
|
||
"description": "Allows you to drive a car."
|
||
}
|
||
],
|
||
"acceptedStates": {
|
||
"acceptWorn": true,
|
||
"acceptDamaged": true,
|
||
"acceptBadlyDamaged": true,
|
||
"coefficientWorn": 0.75,
|
||
"coefficientDamaged": 0.5,
|
||
"coefficientBadlyDamaged": 0.25
|
||
},
|
||
"traders": [
|
||
{
|
||
"npcId": 0,
|
||
"className": "SurvivorM_Boris",
|
||
"givenName": "Boris",
|
||
"role": "Weapons",
|
||
"position": [11596.6, 58.7, 14630.3],
|
||
"orientation": [180.0, 0.0, 0.0],
|
||
"categoriesId": ["cat_assault_rifles_001", "cat_pistols_001"],
|
||
"currenciesAccepted": ["EUR"],
|
||
"loadouts": [
|
||
{ "className": "TTSKOJacket_Camo", "quantity": -1, "slotName": "Body", "attachments": [] },
|
||
{ "className": "TTSKOPants", "quantity": -1, "slotName": "Legs", "attachments": [] },
|
||
{
|
||
"className": "AKM", "quantity": -1, "slotName": "Hand",
|
||
"attachments": [ { "className": "Mag_AKM_30Rnd", "quantity": 30 } ]
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"traderObjects": [
|
||
{ "className": "bldr_Misc_RoadBarrier", "position": [11537.2, 59.0, 14726.1], "orientation": [55.0, 0.0, 0.0] }
|
||
]
|
||
}
|
||
```
|
||
|
||
### `licenses[]`
|
||
Define the license catalogue here. `licenseId` must be unique; generate any UUID. Reference the **ID** from `categories[].licensesRequired`.
|
||
|
||
There is **no in-game UI or admin command to grant licenses** in this codebase. Licenses are granted by editing `TraderXDatabase/PlayerLicenses/playerLicense_<steam64>.json`:
|
||
|
||
```json
|
||
{
|
||
"steamId": "76561198000000000",
|
||
"playerName": "Someone",
|
||
"licenses": [
|
||
{ "licenseId": "<same uuid as in GeneralSettings>", "licenseName": "Car License", "description": "" }
|
||
]
|
||
}
|
||
```
|
||
|
||
The file is read when the player joins; edit while they're offline.
|
||
|
||
### `acceptedStates`
|
||
Controls which conditions the trader will **buy**. Ruined is never accepted. The `coefficient*` values are stored but not used by the current pricing code (see §4.2) – condition discounts are 0.8 / 0.6 / 0.4.
|
||
|
||
### `traders[]`
|
||
|
||
| Field | Meaning |
|
||
|---|---|
|
||
| `npcId` | Integer, must be unique. Used for parking file names (`parkings_<npcId>.json`) and session tracking. |
|
||
| `className` | Any `PlayerBase`-derived class (e.g. `SurvivorM_Boris`, `SurvivorF_Linda`) **or** a static object that inherits `BuildingBase`. Anything else logs "obj was not created" and is skipped. |
|
||
| `givenName`, `role` | Shown in the action prompt (`Trade – Boris (Weapons)`) and in the menu header. |
|
||
| `position`, `orientation` | World coordinates. `orientation` is `[yaw, pitch, roll]`. |
|
||
| `categoriesId` | Category IDs this trader sells. Order = display order. |
|
||
| `currenciesAccepted` | Names from `TraderXCurrencySettings.json → currencyTypes[].currencyName`. Empty = all currencies. |
|
||
| `loadouts` | Clothing/items for the NPC. `slotName` = DayZ inventory slot (`Body`, `Legs`, `Feet`, `Headgear`, `Vest`, `Back`, `Gloves`, `Mask`, `Eyewear`, `Shoulder`, `Melee`…), `"Hand"` = in hands, empty string = into cargo. `quantity -1` = don't touch. Attachments spawn onto the item (magazines are loaded into weapons). |
|
||
|
||
NPCs are invulnerable (`SetAllowDamage(false)`).
|
||
|
||
### `traderObjects[]`
|
||
Kept for TraderPlus V1 compatibility and written by the converter, but **no code in this version spawns them**. Use your map's object spawner (init.c / DayZ Editor loader) for decoration.
|
||
|
||
---
|
||
|
||
## 7. `TraderXCurrencySettings.json`
|
||
|
||
```json
|
||
{
|
||
"version": "1.0.1",
|
||
"currencyTypes": [
|
||
{
|
||
"currencyName": "EUR",
|
||
"currencies": [
|
||
{ "className": "TraderX_Money_Euro100", "value": 100 },
|
||
{ "className": "TraderX_Money_Euro50", "value": 50 },
|
||
{ "className": "TraderX_Money_Euro10", "value": 10 },
|
||
{ "className": "TraderX_Money_Euro5", "value": 5 },
|
||
{ "className": "TraderX_Money_Euro1", "value": 1 }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
- Each `currencyType` is an independent wallet. A trader with `currenciesAccepted: ["EUR"]` counts only EUR items.
|
||
- `value` is per unit; stackable money items are multiplied by their quantity. Always include a `value: 1` denomination or change can't be given.
|
||
- Any item class can be money (nails, bullets, custom `TraderX_Money_*` items from the Workshop build). The `TraderX_Money_*` classes are **not** defined in this repo's `config.cpp`; they ship with the Workshop package.
|
||
- Note: the default generator has a bug and puts the USD denominations into the EUR type. Check the generated file before use.
|
||
|
||
Money is counted by walking the player's whole inventory (including nested containers).
|
||
|
||
---
|
||
|
||
## 8. Vehicles
|
||
|
||
A product is treated as a vehicle if its class inherits `Transport` (cars, boats). Vehicles:
|
||
|
||
- can only be bought/sold one at a time;
|
||
- on purchase spawn at a free parking spot for that trader; no free spot ⇒ "No parking available";
|
||
- on sale the player must be near the vehicle, and it must not be occupied or engine-running.
|
||
|
||
### `TraderXVehicleParking/parkings_<npcId>.json`
|
||
|
||
```json
|
||
{
|
||
"traderId": "3",
|
||
"positions": [
|
||
{ "position": [11620.0, 58.9, 14690.0], "rotation": [50.0, 0.0, 0.0], "size": [3.0, 2.0, 6.0] },
|
||
{ "position": [11625.0, 58.9, 14694.0], "rotation": [50.0, 0.0, 0.0], "size": [3.0, 2.0, 6.0] }
|
||
]
|
||
}
|
||
```
|
||
|
||
`size` = width, height, length of the box that must be free of non-whitelisted objects. `traderId` = the trader's `npcId` as a string; the file name must match.
|
||
|
||
### `TraderXVehicleParking/parking_config.json`
|
||
|
||
```json
|
||
{ "whitelistedObjects": ["Land_Grass", "Land_Stone", "Land_Dirt", "Land_Sand", "Land_Gravel", "ClutterCutter", "Particle", "EffectSound", "Land_Underground", "Terrain"] }
|
||
```
|
||
|
||
Object class-name substrings that do **not** count as "blocking" a parking spot. Add your barrier/prop classes here if spots are always reported as occupied.
|
||
|
||
---
|
||
|
||
## 9. CSV format (`Source/`)
|
||
|
||
Files are **tab-delimited** (despite the `.csv` extension), first row = header, header names case-insensitive. Multiple packs are supported: `10_vanilla.csv`, `50_community_weapons.csv` merge in filename order (numeric prefix = priority). Quotes `"…"` are stripped and allow tabs inside a field.
|
||
|
||
### `products.csv` columns
|
||
|
||
| Column | Notes |
|
||
|---|---|
|
||
| `productKey` | Becomes the product ID. Use the `prod_<name>_NNN` form to stay compatible with legacy export. |
|
||
| `className` | |
|
||
| `buyPrice`, `sellPrice` | `-1` = not tradeable that direction |
|
||
| `maxStock` | `-1` = unlimited |
|
||
| `buyQtyMode` | `EMPTY`, `FULL`, `COEFFICIENT`, `STATIC` |
|
||
| `buyQtyValue` | for COEFFICIENT (0–1) / STATIC |
|
||
| `sellQtyMode` | `NO_MATTER`, `EMPTY`, `FULL`, `COEFFICIENT`, `STATIC` |
|
||
| `sellQtyValue` | |
|
||
| `destockCoefficient` | 0.0–1.0 |
|
||
| `stockBehaviorAtRestart` | 0 / 1 / 2 |
|
||
| `attachments` | `;`-separated productKeys |
|
||
| `variants` | `;`-separated productKeys |
|
||
| `notes` | ignored by the game |
|
||
|
||
> `coefficient` (price coefficient) is **not** a CSV column in this version – the compiler uses the class default (`1.0`). If you rely on dynamic pricing, use legacy JSON or edit `Compiled/Products_compiled.json` directly (COMPILED mode).
|
||
|
||
### `categories.csv` columns
|
||
`categoryKey`, `categoryName`, `icon`, `isVisible` (1/0/true/false), `licensesRequired` (`;`-separated license IDs), `productKeys` (`;`-separated), `notes`.
|
||
|
||
### `presets.csv` columns
|
||
`presetKey`, `productKey`, `presetName`, `attachments` (`;`-separated productKeys), `notes` – server-defined attachment presets.
|
||
|
||
### Validation
|
||
Every restart writes `TraderXLogs/Config/ConfigReport.log`. Errors (duplicate keys, empty className, unknown product references, out-of-range values) abort compilation and restore the last good `Backup/`. Warnings do not.
|
||
|
||
---
|
||
|
||
## 10. Other settings files
|
||
|
||
### `TraderXDynamicTranslationSettings.json`
|
||
Every player-facing notification string (`successfullyBought`, `insufficientFunds`, `traderStockFull`, …). `%1`, `%2` are placeholders. Edit for localisation or flavour.
|
||
|
||
### `TraderXLogs/Config/LoggingSettings.json`
|
||
```json
|
||
{ "logLevel": 0, "refreshRateInSeconds": 60 }
|
||
```
|
||
`logLevel`: 0 Debug, 1 Info, 2 Warn, 3 Error. Debug is very verbose (every price calculation); use 1 in production.
|
||
|
||
### `TraderXDebugSettings.json`
|
||
`enableDebugMode`, `runTestsOnStartup` + per-suite flags, `authorizedAdmins` (Steam64 list). Leave `runTestsOnStartup: false` on a live server – the tests create and destroy items.
|
||
|
||
### `TraderXTXSafeZoneSettings.json` (TraderXSafeZone module)
|
||
```json
|
||
{
|
||
"version": "1.0.0",
|
||
"notificationTimer": 10,
|
||
"notificationTitle": "SafeZone",
|
||
"msgOnEnteringZone": "You entered the safezone!",
|
||
"msgOnLeavingZone": "You have left the safezone!",
|
||
"leaveZoneCountdown": 30,
|
||
"msgOnLeavingZoneCountdown": "You are leaving the safezone in %1 seconds",
|
||
"adminSteamUIDs": ["76561198000000000"],
|
||
"enableAfkDisconnect": false,
|
||
"kickAfterDelay": 300,
|
||
"cleanupTimer": 1200,
|
||
"objectsToDelete": ["Paper"],
|
||
"locations": [
|
||
{ "id": 1, "isActive": true, "name": "Trader Camp", "position": [11600, 58, 14650], "radius": 150,
|
||
"isEntitiesCleanActive": true, "allowedAnimals": [] }
|
||
]
|
||
}
|
||
```
|
||
Inside an active zone: no damage, melee, fire, traps, zombies; `objectsToDelete` are removed every `cleanupTimer` seconds; AFK kick after `kickAfterDelay` seconds if enabled. Admins in `adminSteamUIDs` are exempt.
|
||
|
||
---
|
||
|
||
## 11. Bulk-editing tips (Linux)
|
||
|
||
Set every `prod_*.json` in the folder to unlimited stock and coefficient 0.9:
|
||
|
||
```bash
|
||
cd ~/serverprofile/TraderX/TraderXConfig/Products
|
||
sed -i -E 's/("maxStock":\s*)[-0-9.]+/\1-1/; s/("coefficient":\s*)[-0-9.]+/\10.90/' prod_*.json
|
||
```
|
||
|
||
(Reminder: with `maxStock: -1` the coefficient is ignored – see §4.2.)
|
||
|
||
Find product files that will be rejected by the filename validator:
|
||
|
||
```bash
|
||
ls | grep -Ev '^prod_[A-Za-z0-9_]+_[0-9]{3}\.json$'
|
||
```
|
||
|
||
Rename them by appending `_001`:
|
||
|
||
```bash
|
||
for f in prod_*.json; do
|
||
[[ $f =~ _[0-9]{3}\.json$ ]] || mv -- "$f" "${f%.json}_001.json"
|
||
done
|
||
```
|
||
|
||
Then update every category's `productIds` to the new names (they must match exactly), e.g.:
|
||
|
||
```bash
|
||
sed -i -E 's/("prod_[a-z0-9_]+)"/\1_001"/g' ../Categories/*.json # review before running
|
||
```
|
||
|
||
Reset all stock (server stopped):
|
||
|
||
```bash
|
||
rm -f ~/serverprofile/TraderX/TraderXDatabase/Stock/*.json
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Troubleshooting checklist
|
||
|
||
| Symptom | Check |
|
||
|---|---|
|
||
| Product missing from trader | Filename matches `prod_<name>_NNN.json`; product ID listed in a category's `productIds`; category listed in the trader's `categoriesId`; no stale `Compiled/` folder. |
|
||
| Category greyed out with lock | `licensesRequired` contains license IDs the player doesn't have in `PlayerLicenses/`. |
|
||
| Prices never change | `maxStock` is `-1` or `coefficient` is `1.0`. |
|
||
| Price drops to 1 | `coefficient` too low for your `maxStock`. |
|
||
| "Trader stock is full" on sell | stock ≥ `maxStock`; raise `maxStock` or set `-1`. |
|
||
| Vehicle "No parking available" | No `parkings_<npcId>.json` for that trader, or all spots blocked – add blocking classes to `parking_config.json`. |
|
||
| NPC not spawning | `className` must inherit `PlayerBase` or `BuildingBase`; look for "obj was not created" in the log. |
|
||
| Edits ignored | `Compiled/` folder present while editing legacy files. |
|
||
| Can't pay despite having money | Trader's `currenciesAccepted` doesn't include the currency type holding those items. |
|
||
|
||
Log file: `TraderXLogs/Logs/TraderX_<date>.log`. Startup logs the format used (`Configuration format selected: …`) and product/category counts (`LoadAllProducts - Completed. Total products loaded: N`).
|