Описание
# PayTp Mod Documentation (v2)
> - Updated on **2026-09-19**
> - Let's [translate](https://github.com/DwellersMC/Pay-To-Teleport/issues/11) **PayTp** together!
> - 中文文档请看[**这里**](https://github.com/DwellersMC/Pay-To-Teleport/blob/main/README.cn.md)
**To be noted, due to huge API changes and the fact that Minecraft is no longer using **obfuscation** for source codes, patches later than **v1.2.0** will **NOT** support versions below **26.1**.* **For **data migration**, please check [here](https://github.com/DwellersMC/Pay-To-Teleport/blob/latest/docs/MIGRATION.md).*
## Overview
**PayTp** is a lightweight Fabric mod for Minecraft that allows players to teleport by paying a certain amount of in-game currency (items).
It supports flexible teleportation modes, multi-language localization, and fully customizable cost rules.
---
## Features
- **Editable command names**
- Cross-dimension teleport to a specified location
- Player teleport request system
- Home and Back
- Beacon waypoint (Warp) feature with a categorized, paginated SGUI browser
- Fully customizable JEXL teleportation price and distance algorithm
- Ender Chest / Shulker Box payment support
- **Cloth Config** API support (client-side)
- Can be used as a **server-side only** mod
Most features can be **disabled** by setting their corresponding command names to **an empty string**.
For example, changing `teleport.coordinateCommand` in the config file from `ptp` to **empty** will disable the coordinate teleport function.
The in-game help guide will automatically adapt.
---
## Commands
*All displays show the default command names, where indicates required parameters and () indicates optional parameters*
Every `` argument accepts and suggests online player names only; entity selectors such as `@a`, `@p`, and `@s` are not supported.
The `/ptp` ` ` text is a non-selectable format hint rather than a current-coordinate suggestion; `~` and `^` relative coordinates remain supported.
Waypoint names may contain Unicode and special characters without quotes as long as they contain no whitespace. A name containing spaces must be enclosed in double quotes in every waypoint command. Quoted names without spaces remain valid as well. Examples: `主城`, `主城#1`, and `"Main City"`. Create requires the type before the name, such as `/ptpwarp create server 主城`; delete keeps the optional suffix form `/ptpwarp delete "主城" forced`. There are no legacy duplicate command branches. With the cursor immediately after `teleport`, `delete`, `rename`, `invite`, or `exclude`, the server lists complete applicable waypoint choices such as `"樱花谷"`; this is a choice list, not prefix matching after typing part of a name. Online players are then completed after `invite/exclude`. Clicking a command in `/ptphelp` inserts only its executable command prefix and never copies placeholder text such as `` or ``.
| Command | Description |
|----------------------------------------------------------|------------------------------------------------------------------------------|
| `/ptphelp` | Get command guide for PayTp |
| `/ptp (dimension) ` | Teleport to specified coordinates (in a specific dimension) |
| `/ptpto ` | Send request to teleport to a player |
| `/ptphere ` | Send request to a player to teleport to you |
| `/ptpaccept (player)` | Accept a teleport request (from a specific player) |
| `/ptpdeny (player)` | Deny a teleport request (from a specific player) |
| `/ptpcancel (player)` | Cancel a pending teleport request (to a specific player) |
| `/ptpback` | Return to the previous location |
| `/ptphome` | Teleport to your home (if configured) |
| `/ptphome set` | Set your home to your current position |
| `/ptpwarp` | Open the categorized and paginated server-side waypoint SGUI. |
| `/ptpwarp teleport ` | Teleport to the specified waypoint |
| `/ptpwarp create (private/public/server) ` | Create a waypoint with an explicit type; `server` is permission-controlled. |
| `/ptpwarp delete (forced)` | Delete your waypoint; `forced` is permission-controlled. |
| `/ptpwarp rename ` | Rename a waypoint you created. |
| `/ptpwarp invite ` | Invite a player to one of your private waypoints. |
| `/ptpwarp exclude ` | Remove an invited player from a private waypoint. |
| `/ptpwarp list (all/public/owned/invited/server) (page)` | Filter and page through server waypoints. |
---
## Configuration
### Configuration File Location:
```
~/config/paytp.json
```
### Example Structure:
```json
{
"general": {
"language": "en_us",
"helpCommand": "ptphelp",
"safeTeleport": false,
"safeTeleportRange": 5,
"effect": {
"particleEffect": true,
"soundEffect": true
}
},
"teleport": {
"coordinateCommand": "ptp",
"allowCrossDim": true
},
"request": {
"requestCommand": {
"toCommand": "ptpto",
"hereCommand": "ptphere",
"acceptCommand": "ptpaccept",
"denyCommand": "ptpdeny",
"cancelCommand": "ptpcancel"
},
"expireTime": 10
},
"home": {
"homeCommand": "ptphome",
"setRespawnPoint": false
},
"back": {
"backCommand": "ptpback",
"maxBackStack": 10
},
"warp": {
"warpCommand": "ptpwarp",
"serverWarpPermission": 2,
"autoDeleteInactiveWarps": true,
"maxInactiveTicks": 100,
"checkPeriodTicks": 20
},
"price": {
"currencyItem": "minecraft:diamond",
"minPrice": 1,
"maxPrice": 64,
"algorithm": "// Available variables:\n//\n// Variable | Available methods\n// -------- | -----------------------------------------------------------\n// from | .x(), .y(), .z(), .dimension()\n// to | .x(), .y(), .z(), .dimension()\n// context | .coordinate(), .home(), .back(), .request(), .warp()\n// player | .uuid(), .name()\n// callback | .onSuccess(), .onFailure()\n//\n// Java\u0027s built-in Math methods are available through the \"math\" namespace.\n// Minecraft commands are available through minecraft:execute(\"command\").\n// Full system shell access is available through the \"shell\" namespace.\n\nvar basePrice \u003d 1;\nvar baseRadius \u003d 10.0;\nvar pricePerBlock \u003d 0.01;\nvar crossDimensionMultiplier \u003d 1.5;\nvar homeMultiplier \u003d 0.5;\nvar backMultiplier \u003d 0.8;\nvar warpMultiplier \u003d 0.5;\nvar netherCoordinateScale \u003d 8.0;\n\nvar crossDimension \u003d from.dimension() !\u003d to.dimension();\nvar deltaX \u003d from.x() - to.x();\nvar deltaY \u003d from.y() - to.y();\nvar deltaZ \u003d from.z() - to.z();\n\nif (crossDimension) {\n if (from.dimension() \u003d\u003d \"minecraft:the_end\") {\n deltaX \u003d from.x();\n deltaY \u003d from.y();\n deltaZ \u003d from.z();\n } else if (to.dimension() \u003d\u003d \"minecraft:the_end\") {\n deltaX \u003d to.x();\n deltaY \u003d to.y();\n deltaZ \u003d to.z();\n } else if (from.dimension() \u003d\u003d \"minecraft:the_nether\") {\n deltaX \u003d from.x() * netherCoordinateScale - to.x();\n deltaY \u003d from.y() * netherCoordinateScale - to.y();\n deltaZ \u003d from.z() * netherCoordinateScale - to.z();\n } else if (to.dimension() \u003d\u003d \"minecraft:the_nether\") {\n deltaX \u003d from.x() - to.x() / netherCoordinateScale;\n deltaY \u003d from.y() - to.y() / netherCoordinateScale;\n deltaZ \u003d from.z() - to.z() / netherCoordinateScale;\n }\n}\n\nvar distance \u003d math:sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ);\nvar multiplier \u003d crossDimension ? crossDimensionMultiplier : 1.0;\n\nif (context.home() !\u003d null) {\n multiplier \u003d multiplier * homeMultiplier;\n} else if (context.back() !\u003d null) {\n multiplier \u003d multiplier * backMultiplier;\n} else if (context.warp() !\u003d null) {\n multiplier \u003d multiplier * warpMultiplier;\n}\n\nvar distanceBeyondBase \u003d distance \u003e baseRadius ? distance - baseRadius : 0;\n\ncallback.onSuccess() +\u003d () -\u003e {\n minecraft:execute(\n \"effect give \" + player.name() + \" minecraft:weakness 1 1\"\n );\n};\n\ncallback.onFailure() +\u003d () -\u003e {\n minecraft:execute(\"say hi\");\n};\n\nmath:round((basePrice + distanceBeyondBase * pricePerBlock) * multiplier).intValue();",
"deduction": {
"allowEnderChest": true,
"prioritizeEnderChest": true,
"allowShulkerBox": false,
"prioritizeShulkerBox": false
}
}
}
```
---
## Configuration Details
### General Settings
| Field | Type | Description |
|---------------------|-----------|-----------------------------------------------------------------------------------------------------------|
| `language` | `string` | Automatically discovered bundled language locale (for example `en_us`); affects messages, SGUI, and help text. |
| `helpCommand` | `string` | Command used to display the PayTp guide (default `/ptphelp`). |
| `safeTeleport` | `boolean` | Move an unsafe destination to the nearest safe position; defaults to `false`. |
| `safeTeleportRange` | `int` | Maximum horizontal and vertical safe-position search range; defaults to `5` and must be from `1` to `64`. |
#### Adding a Language
Add `.json` under `src/main/resources/assets/pay-to-teleport/lang/`. The locale filename must match `[a-z0-9][a-z0-9_-]*`, and every file must contain a non-blank `paytp.language.name` used by the Mod Menu selector. PayTp discovers all matching JSON files from the mod container automatically; adding a language does not require editing a Java enum, Gson adapter, or UI registry. `en_us.json` is required as the fallback. Missing keys in another language fall back to English and produce a one-time server warning.
#### Teleport Effects (`general.effect`)
| Field | Type | Description |
|------------------|-----------|------------------------------------------------|
| `particleEffect` | `boolean` | Enable teleport particles; defaults to `true`. |
| `soundEffect` | `boolean` | Enable teleport sounds; defaults to `true`. |
---
### Coordinate Teleport
| Field | Type | Description |
|---------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------|
| `coordinateCommand` | `string` | Coordinate teleport command (default `/ptp`). |
| `allowCrossDim` | `boolean` | Whether any teleport method may cross dimensions; defaults to `true`. When disabled, the dimension argument is not registered or displayed. |
---
### Teleport Request System
#### Request Commands
| Field | Type | Description |
|-----------------|----------|-----------------------------------------------------------------------------------------|
| `toCommand` | `string` | Command to request teleporting to the target player (default `/ptpto`). |
| `hereCommand` | `string` | Command to request the target player to teleport to your location (default `/ptphere`). |
| `acceptCommand` | `string` | Command to accept a request (default `/ptpaccept`). |
| `denyCommand` | `string` | Command to deny a request (default `/ptpdeny`). |
| `cancelCommand` | `string` | Command to cancel a sent request (default `/ptpcancel`). |
#### Configuration
| Field | Type | Description |
|--------------|--------|--------------------------------------------------------------------------------|
| `expireTime` | `int` | Request expiration time in seconds; defaults to `10` and must be non-negative. |
---
### Home System
| Field | Type | Description |
|-------------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `homeCommand` | `string` | Command to teleport home (default `/ptphome`). |
| `setRespawnPoint` | `boolean` | Also move the player's respawn point when setting home; defaults to `false`. When the player respawns at Home, the vanilla bed stand-up position resolver is always used; if it finds no valid position, the player is moved to the world spawn point. |
---
### Back System
| Field | Type | Description |
|----------------|----------|-------------------------------------------------------------------------------------|
| `backCommand` | `string` | Command to return to previous location (default `/ptpback`). |
| `maxBackStack` | `int` | Maximum saved historical positions; defaults to `10` and must be greater than zero. |
---
### Waypoint System
| Field | Type | Description |
|------------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------------|
| `warpCommand` | `string` | Command name to teleport to a waypoint (default `/ptpwarp`). |
| `serverWarpPermission` | `int` | Minecraft permission level required to create server waypoints and force-delete waypoints; ranges from `0` to `4` and defaults to `2`. |
| `autoDeleteInactiveWarps` | `boolean` | Delete a waypoint after its beacon exceeds the inactivity timeout; defaults to `true`. When disabled, it remains unavailable until the beacon reactivates. |
| `maxInactiveTicks` | `int` | Beacon inactivity timeout in ticks; defaults to `100` and must be non-negative. |
| `checkPeriodTicks` | `int` | Waypoint-to-beacon check interval in ticks; defaults to `20` and must be greater than zero. |
Running `/ptpwarp` without arguments opens a six-filter SGUI for all, server, owned, public, invited, and locked waypoints. The filters are arranged vertically in the first column as a water bucket, netherite ingot, gold ingot, iron ingot, copper ingot, and empty bucket; the second column is a cyan stained-glass divider. In the remaining 7×6 region, the top row contains Previous, the summary book, and Next, the middle 7×4 area displays 28 waypoints per page, and the bottom row places Refresh on the left and Close on the right. Every usable waypoint is an emerald block, while inactive and locked waypoints are redstone blocks. Attempting a locked waypoint reports that access has not been granted instead of claiming the waypoint does not exist. Locked entries do not reveal their owner or coordinates. Every click resolves the waypoint again through the same access, activity, safety, cross-dimension, price, and payment path used by `/ptpwarp teleport `, so a waypoint deleted or invalidated while the menu is open cannot be used from stale menu data. The SGUI uses vanilla container packets and requires no client mod.
---
### Cost Calculation Settings
#### Currency
| Field | Type | Description |
|----------------|----------|------------------------------------------------------------|
| `currencyItem` | `string` | A valid currency item ID; defaults to `minecraft:diamond`. |
#### Price Range and Algorithm
| Field | Type | Description |
|-------------|----------|------------------------------------------------------------------------------------------------------------------------------------|
| `minPrice` | `int` | Final lower bound for non-negative prices; defaults to `1` and must satisfy `0 {
minecraft:execute("tell " + player.name() + " Teleport completed");
};
callback.onSuccess() -= audit;
callback.onFailure() += () -> {
minecraft:execute("scoreboard players add " + player.name() + " tp_failed 1");
};
```
Callbacks execute in registration order. Removing one requires the same lambda object; repeating
the same lambda expression creates a different object. A callback error is logged without stopping
later callbacks. The price script runs before destination validation so that an unavailable world,
missing safe destination, or disabled cross-dimension teleport can invoke `callback.onFailure()`;
`to` therefore represents the originally requested destination. No payment is taken until all
destination checks pass. A `maxPrice` of `0` bypasses the price script, so no callbacks are
registered.
`crossDimension` is intentionally not provided. Determine it inside the script:
```jexl
var crossDimension = from.dimension() != to.dimension();
```
### Return Value and Strict Mode
- The last expression must return an `int`. Integer literals such as `10` already satisfy this requirement.
- Returning a negative integer cancels the payment and teleport. This allows an algorithm or
Minecraft command result to deliberately stop the operation without causing a script error.
- JEXL runs in strict mode, so undefined variables and invalid expressions are treated as errors.
### Math Methods
> [!TIP]
> Use `math` for distance, multiplier, rounding, and boundary calculations directly inside the price algorithm without invoking an external process.
The `math` namespace exposes Java's built-in `Math` methods.
| Method | Return type | Description |
|------------------------------|-------------|--------------------------------------------------|
| `math:sqrt(value)` | `double` | Returns the square root of a value. |
| `math:max(first, second)` | numeric | Returns the greater of two values. |
| `math:min(first, second)` | numeric | Returns the smaller of two values. |
| `math:round(value)` | `long` | Returns the closest integer value. |
| `math:pow(base, exponent)` | `double` | Raises `base` to the supplied power. |
| `math:abs(value)` | numeric | Returns the absolute value. |
Methods may return a numeric type other than `int`. Convert the final result explicitly when
necessary:
```jexl
math:round(rawPrice).intValue();
```
### Minecraft Commands
> [!WARNING]
> The `minecraft` namespace can execute every command registered with the server, including commands added by other mods. Commands run as the server console with `ALL_PERMISSIONS`, so they can perform destructive or administrative operations such as `op`, `ban`, `data`, and `stop`. Only use algorithms from fully trusted sources.
| Method | Return type | Description |
|------------------------------|-------------|-----------------------------------------------------------------------------------------------|
| `minecraft:execute(command)` | `int` | Executes a Minecraft command. The leading `/` is optional and command feedback is suppressed. |
The server—not the teleported player—is the command source, so `@s` cannot be used to refer to the
player. Use the algorithm's `player.name()` when a command needs a player target. Commands involving
locations or dimensions should specify their target, coordinates, and dimension explicitly instead
of relying on player-relative command context:
```jexl
minecraft:execute("scoreboard players add " + player.name() + " teleport_count 1");
minecraft:execute("effect give " + player.name() + " minecraft:regeneration 5 0");
10;
```
During configuration validation, `minecraft:execute(...)` returns `0` without running the command,
so loading or editing an algorithm cannot modify server state.
### Shell Commands
> [!CAUTION]
> The `shell` namespace executes arbitrary commands with the same operating-system permissions as the Minecraft server. Only use algorithms from fully trusted sources. Shell commands are synchronous and therefore block the server thread until they finish. They are also executed during algorithm validation, including config loading, editing, and importing.
| Method | Return type | Description |
|--------------------------|---------------|--------------------------------------------------------------------------------|
| `shell:execute(command)` | `ShellResult` | Executes through `/bin/sh -c` on Unix-like systems or `cmd.exe /c` on Windows. |
| `shell:run(command)` | `string` | Returns standard output, or throws an error when the exit code is non-zero. |
| `shell:runInt(command)` | `int` | Requires standard output to contain exactly one valid integer. |
`ShellResult` exposes `exitCode`, `stdout`, and `stderr`:
```jexl
var result = shell:execute("python3 /opt/paytp/price.py");
result.exitCode == 0 ? result.stdoutInt() : 0;
```
For a price algorithm, `runInt` is usually the simplest form:
```jexl
shell:runInt("python3 /opt/paytp/price.py '" + player.name() + "'");
```
### Example Algorithm
```jexl
// Available variables:
//
// Variable | Available methods
// -------- | -----------------------------------------------------------
// from | .x(), .y(), .z(), .dimension()
// to | .x(), .y(), .z(), .dimension()
// context | .coordinate(), .home(), .back(), .request(), .warp()
// player | .uuid(), .name()
// callback | .onSuccess(), .onFailure()
//
// Java's built-in Math methods are available through the "math" namespace.
// Minecraft commands are available through minecraft:execute("command").
// Full system shell access is available through the "shell" namespace.
var basePrice = 1;
var baseRadius = 10.0;
var pricePerBlock = 0.01;
var crossDimensionMultiplier = 1.5;
var homeMultiplier = 0.5;
var backMultiplier = 0.8;
var warpMultiplier = 0.5;
var netherCoordinateScale = 8.0;
var crossDimension = from.dimension() != to.dimension();
var deltaX = from.x() - to.x();
var deltaY = from.y() - to.y();
var deltaZ = from.z() - to.z();
if (crossDimension) {
if (from.dimension() == "minecraft:the_end") {
deltaX = from.x();
deltaY = from.y();
deltaZ = from.z();
} else if (to.dimension() == "minecraft:the_end") {
deltaX = to.x();
deltaY = to.y();
deltaZ = to.z();
} else if (from.dimension() == "minecraft:the_nether") {
deltaX = from.x() * netherCoordinateScale - to.x();
deltaY = from.y() * netherCoordinateScale - to.y();
deltaZ = from.z() * netherCoordinateScale - to.z();
} else if (to.dimension() == "minecraft:the_nether") {
deltaX = from.x() - to.x() / netherCoordinateScale;
deltaY = from.y() - to.y() / netherCoordinateScale;
deltaZ = from.z() - to.z() / netherCoordinateScale;
}
}
var distance = math:sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ);
var multiplier = crossDimension ? crossDimensionMultiplier : 1.0;
if (context.home() != null) {
multiplier = multiplier * homeMultiplier;
} else if (context.back() != null) {
multiplier = multiplier * backMultiplier;
} else if (context.warp() != null) {
multiplier = multiplier * warpMultiplier;
}
callback.onSuccess() += () -> {
minecraft:execute("effect give " + player.name() + " minecraft:weakness 1 1");
};
var distanceBeyondBase = distance > baseRadius ? distance - baseRadius : 0;
math:round((basePrice + distanceBeyondBase * pricePerBlock) * multiplier).intValue();
```
The default algorithm additionally handles Nether coordinate scaling and The End. It is written into a newly generated configuration file and can be used as a complete starting template.
### Validation and Final Price
1. If `maxPrice` is `0`, the algorithm is not executed and every teleport costs `0`.
2. A negative script result cancels the payment and teleport without being clamped.
3. Otherwise, the script result is forcibly clamped to the inclusive range `[minPrice, maxPrice]`.
4. The script is compiled and test-executed when the configuration is validated. In Mod Menu, invalid input is marked red and prevents saving.
5. If an algorithm fails during an actual teleport, `calculatePrice` returns `-1`; PayTp logs the error, cancels the teleport, and notifies the player that the payment process failed.
---
## Cloth Config Support
If the **Cloth Config API** is installed on a client, all settings can be adjusted through Mod Menu. The screen edits only that client's local `config/paytp.json`; it cannot modify a remote dedicated server. Saving validates and atomically writes UTF-8 JSON but does not hot-reload a running world or server. Leave and reopen a single-player/LAN world, or restart the dedicated server whose file was edited, to apply changes. The price algorithm can be edited in a dedicated multi-line editor or imported from a `.jexl` file; invalid algorithms cannot be saved.
---
## Compatibility & Deployment
| Type | Supported |
|--------------------------|----------------------------------------------|
| Fabric Loader | Yes |
| Server Only | Yes; unmodified vanilla clients can connect |
| Client UI (Cloth Config) | Optional; local configuration only |
| Multi-language Support | Automatically discovered bundled JSON locales |
| Minecraft Version | 26.1+1.21.4 ~ 1.21.11 (legacy) |
PB4 SGUI is bundled inside the PayTp server JAR and communicates through vanilla container packets. PayTp registers only vanilla Brigadier argument types, so neither waypoint names nor the SGUI introduce a client installation requirement.
---
## Credits
This mod is inspired by early economy-style teleport plugins. The request logic references the **Teleport Command** mod. The waypoint logic references the **Beacon Waypoint** mod.
Developed using Fabric API and fully compatible with vanilla saves.
Feel free to submit issues or pull requests on GitHub to improve configuration and calculation algorithms.