PlayPort

The manual - the panel, the core plugin, and every module in it

Built 2026-10-14 from the 22 markdown files in this repository: MANUAL.md, manual/ and manual/reference/.
The markdown is the source of truth - Appendix D is generated from pkg/src/ppcore/ModuleIndex.java and checked by harness/gates/manual-gate.js - so read this PDF for the manual and the repository for the manual that is held to the code.

Contents

The PlayPort manual

PlayPort is two things installed as one, and this manual covers both:

Both are documented here for the person who operates them: the owner of the machine, the admin who installs it, and the staff who use the panel day to day. This is not the developer or release document - those are README.md (README.md) (the product tour and the build), RELEASE.md (RELEASE.md) (how a release is cut and proved), and HARNESS.md (HARNESS.md) (the verification harness).

Where to start. If you have never run it, read the quick start and then chapter 01. If you already run a panel and want one feature explained, use the chapter index below - every chapter is self-contained.


The shape of the product

        your browser                                              the internet
             |                                                          |
             v                                                          v
   +--------------------+                                    +---------------------+
   |  the panel :8080   |  <-- login session, the whole UI    | sites on their own  |
   |  (one exe, the     |                                    | ports, HTTP + TLS   |
   |   control plane)   |                                    | (Let's Encrypt)     |
   +--------------------+                                    +---------------------+
      |      |      |                                              ^
      |      |      +--> game servers it created (Minecraft, Steam, manual) on their own ports
      |      |              |  console, files, backups, schedules, worlds
      |      |              +--> with the core plugin installed: the in-game product
      |      |
      |      +--> MySQL/MariaDB :3306 - a database per site or per server
      |
      +--> :8100 model and agent API, :8101 brain API  (loopback unless you say otherwise,
                                                        API key required whatever the bind)

Everything lives in one data folder next to the exe by default (playport-data/), so the whole installation is copyable, backupable and deletable - see Appendix C.


Quick start (in ten minutes)

  1. Put PlayPort.exe in a folder you can write to. A folder inside your user profile or on a data drive is fine; Program Files without elevation is not (the panel falls back to %LOCALAPPDATA%\PlayPort there and says so).
  2. Run it. Double-click, or PlayPort.exe --open to open the browser for you. The console window is the panel's own log and must stay open - closing it stops the panel and the servers it runs.
  3. Open <http://127.0.0.1:8080/> and create the first account (that account is the admin). The first-run screen only exists until the account does.
  4. Make a game server: Game Servers -> + New server -> Minecraft Vanilla/Paper (pick a version, accept the EULA) or SteamCMD (pick a game, or type an App ID). Press Start.
  5. Install the core plugin into it if you want the in-game half: the server's Plugins tab has the embedded core plugin and can install it in one click - see chapter 07.
  6. Host a website: Websites -> + Host a website -> static, PHP, or a reverse proxy to an app you already run. Then its Settings page has the TLS panel for a Let's Encrypt certificate.
  7. Open the port you want reachable from the internet, on your router and in Windows Firewall (add-firewall.ps1 does the Windows half once, with admin rights). Reachability is checkable in the panel rather than guessed - see chapter 11.

Chapters

Each chapter is one self-contained document in manual/ (manual/). They are written to be read in any order; where a topic spans two of them, both say so and link across.

ChapterWhat it covers
01 - Install and first runInstalling, uninstalling and updating the panel; the portable vs %LOCALAPPDATA% data roots; command-line flags; the first admin account; what the machine needs (Windows, memory, disk, a Java runtime for Minecraft); ports and firewall; the first game server
02 - The panelSigning in, accounts, roles and per-user permissions; the sidebar page by page; the dashboard and its stat cards; the health board; the unrecognised-crash queue; the activity list and the panel log; settings; branding and theme; search; the phone layout. Installing, updating and removing the program itself is the command line's job - the panel's installation card reports it and says so (chapter 01)
03 - Game serversCreating, configuring and running a server: Minecraft (Vanilla/Paper and versions), SteamCMD games, manual processes; the game template catalog; Modrinth modpacks; the Java runtime; the config editor; worlds; start/stop/restart policy; copying a server
04 - Files, console, backups and schedulesThe file manager, the live console and log feed, selective backups (categories, sizes, retention, restore, off-machine copies), schedules and their timers, saved console commands, storage accounting
05 - Websites and HTTPSApp hosting: static sites, PHP/FastCGI (no IIS or nginx), reverse-proxy sites, importing a framework project and building it, custom domains, TLS (Let's Encrypt via built-in ACME v2, self-signed, uploaded PFX), Cloudflare tunnels for public URLs without open ports, the honest limits
06 - DatabasesThe bundled MySQL/MariaDB server, creating a database and its user, wiring one to a website or a game server, backups of databases, and how credentials are stored
07 - The core plugin, from the panelThe Plugins tab: installing and updating the embedded core plugin into a server, per-server module switches and settings, the pack/addon system, and the panel's editor surfaces for core content (kits, crates, quests, jobs, enchantments, drops, spawners, dungeons, worldgen, cooking, menus)
08 - The core plugin, in gameWhat the plugin is and how it thinks: the module model, the command surface, config.yml, per-module <command> check, permissions and groups, profiles/templates, hooks into other plugins, and the server-side behaviour an operator must know about
09 - Networks and exposureMinecraft networks (Velocity + hub + games + limbo, the bridge plugin, one-button start/stop), adding a server to a network, network settings in one place, and playit.gg for a public address without opening ports
10 - Mail, AI and the help assistantEmail hosting for a website's PHP mail (and the DKIM signing behind it), the local AI models the panel runs on itself, the tiers that decide which model answers and the free cloud behind them, other machines (two panels using each other's hardware), looking things up, the agent API other tools use, the brain (what this machine has learned), the help assistant and the hands it is allowed to use, and alerts
11 - Operations, the API and troubleshootingRunning a panel in production: health and diagnose screens, backups of the panel itself, the security model, the REST API reference, the ports/firewall reference, and what to do when something breaks
12 - The module referenceEvery module the plugin ships, one entry each, grouped by what they have in common: what the module does, how to turn it on (the switch, and whatever else it needs first - a section, a world, a group, a first command), and what you can configure, with the keys named by their path in config.yml and the shipped values. The registry itself - ids, labels, and the order /playport status prints - is Appendix D

Conventions used throughout.


Appendix A - the command line

One executable is the panel, the installer, the uninstaller and the updater. PlayPort.exe --help prints the same list.

Running the panel

FlagWhat it does
--openOpen the panel in your default browser on startup
--port NPanel port (default 8080)
--data DIRData directory. Wins over every other location setting
--portable / --appdataKeep data in playport-data/ next to the exe (default) / in %LOCALAPPDATA%\PlayPort. Remembered next to the exe
--bind-allListen on all interfaces instead of loopback only
--loopbackListen on loopback only (the default)
--stopAsk running panel(s) to shut down gracefully, without killing a server mid-save
--versionPrint the version (and the build's check line) and exit

Installing, updating and removing

FlagWhat it does
--installInstall the running exe to a permanent location (--dir names it), with shortcuts
--uninstallRemove the installed copy (--keep-data keeps the data folder, --purge-data deletes it)
--updateUpdate an installed copy from a file (--from) or URL (--from-url / --update-url)
--planPrint what an install would do as JSON, and change nothing
--finish-installThe second half of an install started from inside a running panel
--move-data / --no-move-dataMove the existing data folder into the install, or leave it
--keep-oldKeep the previous installation's files instead of replacing them
--no-shortcuts / --no-desktopDo not create Start-menu / desktop shortcuts
--no-launch / --relaunchDo not start the panel when the install finishes / restart it as the last step
--yes, -yAssume yes for confirmation prompts (scripted installs)

Appendix B - ports, addresses and the network path

PlayPort listens in more than one place, and they are different doors on purpose: publishing one must not publish the others.

PortWho talks to itWhat proves who they areWhat it reaches
8080 (changeable)a person, in a browsera login sessionthe whole panel
8100 (Settings: Model and agent port)a program, e.g. another AI toolan API keymodels, the tool loop, the brain over HTTP
8101 (Settings: Brain port)a programan API keywhat this machine has learned
3306a website, a game server, or youa database user and passwordthe bundled MySQL/MariaDB server
one port per sitea browser on the internetnothing (it is a website)that site's folder, or the app behind a reverse proxy
443 (per site, tlsPort)a browsera TLS certificatethe same site over HTTPS
one port per game servergame clientsthe game's own protocolthat server

Both program ports bind loopback only unless Reachable from the network is on, and they demand an API key whatever the bind. The panel port deliberately does not answer /v1: a client pointed at it is told which port to use and which key to present, rather than getting a 404 that reads like the feature was removed. The panel has a Ports screen (and /api/ports) that names all of them with what each one is for - that screen is the authority; this table is the summary.

Appendix C - where everything lives on disk

By default the exe keeps everything in a folder beside itself, so the installation is one folder:

PathWhat it holds
playport-data/settings.json, auth.jsonthe panel's settings and its accounts, sessions and API keys
playport-data/instances/one folder per game server: its files, instance.json, worlds, backups
playport-data/sites/one folder per website, and the site registry
playport-data/steamcmd/the official SteamCMD bootstrap and the games it installed
playport-data/ (the rest)MariaDB's data, mail, peers, networks, databases, the crash queue, playport.log

The exact set of files grows with the features you switch on; the panel's own Settings page names the data root it is using, and PlayPort.exe --help documents --data. Switching between the portable folder and %LOCALAPPDATA%\PlayPort is remembered next to the exe (playport-data-mode.txt), and --data DIR overrides both.

Appendix D - the core plugin's modules

The plugin is one jar made of modules, each with its own config section, its own commands and its own switch. This is the registry as the plugin itself reports it - /playport status in game, and the Plugins screen in the panel, print the same list in this order. Chapter 08 explains the model; chapter 07 explains how to install and configure them from the panel; chapter 12 documents every one of them in turn - what it does, how to turn it on, and what you can configure.

idmodule
permsGroups and permissions (replaces a permissions plugin)
econEconomy, /pay, /sell and a salary for time played
heartsPvP hearts (lifesteal): won and lost only in combat
essHomes, warps, kits, teleport requests, spawn and /back
claimsLand claims with earned claim blocks
minesPrison mines that refill themselves
ranksEarned rank ladder (drives permission groups)
staffStaff tools: bans, mutes, warnings, vanish, freeze, reports
chatChat: private messages, ignore, broadcasts, alerts, mail
ahAuction house: players sell to each other with a chest GUI
enchEnchantments and item editing, with permission locks
servertime, weather, ping, tps, player lookups and maintenance
adminheal, feed, god, fly, give, teleport and jails
cosnicks, nameplates, hats, sitting, holograms, portals, armour stands and heads
factionsfactions: land claims, power, allies and faction chat
beaconsfaction beacons, reinforced obsidian and siege charges
spawnersspawner stacking, egg changing, levels, heads
shopbuy and sell shop with a GUI
perftps, mspt, hotspots, lag cleanup, restart scheduler
itemscustom items and short codes for symbols
customenchcustom enchantments
modelscustom models, model data and pack building
hoppersbought chunk hoppers that feed nearby chests
rtprandom teleport to safe, unclaimed ground
potionsthrowable custom debuff potions with a shop
familiarscosmetic pets, including Blockbench model pets
arenaboss arenas, custom bosses and damage-based loot
npcNPCs: clickable characters that greet and act
tradeplayer trading: two windows, both must confirm
guardcheat detection: conservative checks, alerts only
vaultsprivate vaults (/pv)
kothKing of the hill: capture zones, timers and rewards
teamsteams: shared storage, a home and a chat for a group
harvestchain mining, tree felling and fast farming
elevatorelevators: jump for the floor above, sneak for the one below
fishingcustom catches, a fishing level and the fish market
skyblockskyblock: an island world, island levels, challenges and upgrades
plotsplots: a plot world with a road grid, claiming and sharing
minigamesHide and seek and tap the target: two rounds, one engine
bridgenetwork bridge: /hub, /server and network staff commands
questsquests: goals, gates and rewards earned by playing
toolstools that level with use: stat points, abilities and skins
dungeonsblueprints: mark a region with a wand, save it, stamp it
uioperator-authored menus: pages, buttons and actions
worldgenWorld generation: the worlds this plugin owns are made from config
disastersworld events: storms, quakes, meteors and tornadoes
seasonsseasons: a calendar, day length, crop growth and temperature
votesVote sites: a listener they call, vote points, streaks, rewards and vote parties
servicethe only outbound client, off until switched on
analyticsstaff snapshot of who is online and how long
anvilcap anvil repair cost
attachcommandbind a command to the item in hand
bansguia click menu of players and of bans, for staff
bountyput money on a player's head
buttona click button, and the one-time reward button
colorsprint the colour codes
compasspoint the compass at a player or spawn
cookingcooking stations: a block that performs recipes
cratescrates: keys, placed crates and weighted rewards
donategive the item in hand to another player
doorsdoors that move as a set: a selection, a direction, a distance
dropsextra mob drops from config
editorthe in-game editor: settings and kits without a file
effectapply a potion effect
enchantlockblock named enchantments from being applied
flagsworld flags: pvp, explode, mob-spawn, workbenches, enderchest
flyctimed flight charges
giftpetgive ownership of a nearby tamed mob
killallremove mobs in a world
kiteditorsave the hotbar as a kit in config
limitercap living entities per chunk
lockiplock an account to the IP it last used
lootchestsplaced loot chests that refill from a template
marketa rotating black-market listing
merchantopen a simple villager trade window
notargetmobs ignore a player
ordersplayer buy orders: money held, items handed over
parkourparkour courses: plates, timers and a leaderboard
patroljump staff to a random online player
revivedowned players can be revived instead of dying
rewardone-time join reward
saveallsave worlds and plugin data
securityscan inventories for contraband
shootfire a projectile from a player
shulkeropen a shulker from the hand without placing it
silentchestopen a container without the lid animation
skinremember a requested skin name
soundplay a sound for a player
stackmerge dropped items in a radius
streamstreamer list, live tag, watch time and drops
tagschat tags a player can equip
topteleport to the highest block above you
voiceproximity chat as a stand-in for voice range
warpsignsigns that warp when clicked
actionsthe action engine: typed actions and conditions over config
dialogsnative dialogs on 1.21.7+, a menu window on anything older
dsignsigns whose lines refresh from server placeholders
hookshooks into other plugins, or this core's own rule
icsignsigns that run a named action list when clicked
prewardsrewards claimed against minutes played
schematicsschematic save and paste (WorldEdit's format, with or without WorldEdit)
jobsjobs: professions that pay a wage as you work

Appendix E - the other documentation in this repository

FileWhat it is for
README.md (README.md)The product tour: every feature in the order it was built, with the reasoning kept
RELEASE.md (RELEASE.md)How a release is produced and proved, and what each of its steps reads
HARNESS.md (HARNESS.md)The verification harness: gates, live suites, lanes
EULA.md (EULA.md), LICENSE (LICENSE)Licensing and commercial use
pkg/PARITY.md (pkg/PARITY.md)What the core plugin deliberately does and does not copy from the plugins it replaces
pkg/PROVENANCE.md (pkg/PROVENANCE.md)Where the core plugin's content came from
pkg/notes/ (pkg/notes/)The work queue and design notes - developer-facing, one file per subject

Chapter 01. Install and first run

This chapter takes you from a downloaded PlayPort.exe to a running panel with an admin account, a first game server and a data folder you chose on purpose. It is the only chapter you need before the rest of the manual makes sense. Everything an established panel owner needs later - servers, websites, the core plugin, the API - is in the chapters that follow.

Read ../MANUAL.md first if you have not: it has the quick start, the ports table (Appendix B), where everything lives on disk (Appendix C) and the command-line reference (Appendix A).


What PlayPort is, in two halves

The panel is one Windows executable. It is the control plane: it creates and supervises game servers, hosts websites, runs a bundled MySQL/MariaDB server, takes backups, runs schedules and serves the web interface you drive it from. It is not a service and it installs no runtime - it targets the .NET Framework that is already inside Windows 10 and 11.

The core plugin (PlayPortCore) is a Minecraft plugin jar the panel installs into the game servers it runs. It is the in-game half of the product - economy, permissions, ranks, homes, claims, shops, quests, dungeons and about a hundred more switchable modules. Chapter 07 installs it; chapter 08 explains it. You do not need it to run a panel, and nothing in this chapter depends on it.

There is nothing else to install. Websites, PHP, HTTPS certificates, the database server, Java and the Steam client are all fetched by the panel itself, at the moment you first use them - chapter 05, chapter 06 and chapter 03 cover each of those downloads and where they land.

What the machine needs

Windows10 or 11, 64-bit. The panel runs as an ordinary user; administrator rights are needed once for the firewall/URL-ACL step below, and for nothing else
DiskThe program is about 3 MB. The data is the number that matters: several GB per Minecraft server, more for a modpack or a Steam game, and the MariaDB data directory grows with your databases. See Where data goes below
MemoryGame servers set their own limits per server (chapter 03); the panel itself is small
NetworkLoopback only, by default. Nothing is reachable from the LAN or the internet until you ask for it
JavaNot required for the panel. Minecraft servers need a Java runtime, and the panel installs the right one from Mojang's own metadata - see chapter 03

The panel samples CPU, RAM, disk and (where nvidia-smi exists) the GPU for its dashboard. Nothing it does requires a GPU; a machine with no NVIDIA card simply reports that it cannot see one rather than reporting an idle one.

Where data goes: the two folders

An installation is two folders, and separating them is the point of the installer.

The program folder holds PlayPort.exe and, if the panel was installed rather than unzipped, a record of the install: playport-install.json. That file is what makes an exe an install. Delete it and the same binary is portable again.

The data folder holds everything else: settings.json, auth.json, the server folders under instances/, the website folders under sites/, SteamCMD, MariaDB's data, backups, the log. It is the folder that grows to gigabytes, and it is the one you should put on a drive with room.

Which folder the panel uses is decided in this order (Store.cs's DefaultRoot):

  1. --data DIR - always wins. Use it for a one-off, or to point a copy at data you moved by hand.
  2. The data folder recorded in playport-install.json, for an installed copy. This is the folder you chose during install, and it deliberately outranks the folder rules below.
  3. --portable or --appdata - typed by you, so they win over the remembered choice. --portable means playport-data/ next to the exe; --appdata means %LOCALAPPDATA%\PlayPort.
  4. The remembered choice: playport-data-mode.txt next to the exe, written whenever you pass --portable or --appdata, so a mode switch survives restarts.
  5. With none of the above: portable if the exe's folder is writable, otherwise %LOCALAPPDATA%\PlayPort. The test is a real write of a probe file (.pp-write-test) next to the exe, not a guess about paths the panel should not be writing to. A copy run from Program Files without elevation therefore falls back to %LOCALAPPDATA%.

On every start the panel prints which of these it chose, and why:

Data root: D:\PlayPortData  (installed - the folder you chose; install again to move it)
Data root: D:\playport\playport-data  (portable - next to the exe)
Data root: C:\Users\you\AppData\Local\PlayPort  (the old %LOCALAPPDATA% location - run "PlayPort.exe" --install to move it onto a drive with room)

That third line is worth acting on: %LOCALAPPDATA% lives on the system drive, and a panel there grows a C: drive while another drive sits idle. --install is the one command that moves it.

Installing

PlayPort.exe --install

Without arguments the installer prints what it is about to do and asks Install to <folder>? [Y/n]. --yes skips the prompt for scripted installs, --plan prints the same decision as JSON and changes nothing, and --no-launch installs without starting the panel afterwards.

There is no installer wizard window: the one exe is the installer, the panel, the uninstaller and the updater, and the command line above is the whole installer interface.

What it prints is the decision, not a promise:

Install PlayPort 0.3.1 (build 20260921-125250)
  program: C:\Users\you\AppData\Local\Programs\PlayPort
  data:    D:\PlayPortData
  on that drive: 412 GB free
  moving:  1.9 GB of existing data
             1.9 GB  C:\Users\you\AppData\Local\PlayPort  (appdata)

How the folders are chosen

The program folder defaults to %LOCALAPPDATA%\Programs\PlayPort - per-user, no elevation, no UAC, which is where a user-scope install belongs. The exception is a system drive that is short on room: under 25 GB free, or under 20% free, and the installer puts the program on whichever fixed drive has the most free space instead, naming the numbers in its reason. Override it with --dir DIR.

The data folder defaults to a data folder inside the program folder. Override it with --data DIR on the install command. Choose it deliberately: this is the folder holding every world, every website and every database.

The installer refuses two things outright, with the reason printed:

What it writes

Moving existing data on install

If the machine already has a data root with something in it - the portable playport-data/ folder, %LOCALAPPDATA%\PlayPort, or whatever the current install record names - the installer offers to move it into the folder you chose. The move is verified: it copies, counts files and bytes on both sides, and only then deletes the source. If the counts disagree, nothing is deleted and it says where your old data still is:

  the move did not complete: the copy does not have everything: 41023/41024 files, 1.8 GB of 1.9 GB
  nothing was deleted - the data is still at C:\Users\you\AppData\Local\PlayPort
  point the panel at it with:  "C:\...\Programs\PlayPort\PlayPort.exe" --data "C:\Users\you\AppData\Local\PlayPort"

--no-move-data leaves existing data exactly where it is.

If a panel is running from the folder being moved, the move is queued instead. A running panel holds MariaDB's data directory, server jars and session files open, so moving them under it is the one thing that must not happen. The installer writes the job to playport-install-job.json, starts the installed copy with --finish-install --open, and that copy - at boot, before it reads any settings - waits for the old panel to stop and then does the move. If the move fails there, it says so and the panel starts against the new (empty) folder, with your old data named so you can point at it with --data.

Uninstalling

PlayPort.exe --uninstall                    # asks first: "Uninstall? [y/N] "
PlayPort.exe --uninstall --yes --keep-data   # what the quiet Apps & features entry runs
PlayPort.exe --uninstall --purge-data        # deletes the data folder too, because you said so

An uninstall is a statement about the program, so:

Running the panel

Double-click PlayPort.exe, or from a shell:

PlayPort.exe --open                 # start and open the browser
PlayPort.exe --port 8081            # a second panel beside the first
PlayPort.exe --bind-all --open      # reachable from the LAN (needs the URL ACL below)

The console window

The console window is the panel. It is not a launcher that hands off to a service: it prints the URLs, the data root, the model and brain ports, and then every log line. Closing it stops the panel and stops the servers it runs. A normal start looks like this:

PlayPort 0.3.1 - game server + web hosting panel (single executable)
Data root: D:\playport\playport-data  (portable - next to the exe)
Starting panel on port 8080 …
Panel URL:  http://127.0.0.1:8080/
Stop this panel: PlayPort.exe --stop  (no admin needed - the panel stops itself)
  (from Git Bash use: bash stop-panels.sh 8080)
Model API:  http://127.0.0.1:8100/v1  (API key)
Brain API:  http://127.0.0.1:8101/  (API key)

The boot lines are also written to playport.log in the data root, which is what to hand over when something goes wrong at startup.

Servers marked to start with the panel (autoStart) are started, websites that were enabled are rebound, and a playit.gg agent you left running is restarted - each of those in the background, so a single bad server cannot stop the panel from coming up.

First run: the admin account

Open <http://127.0.0.1:8080/>. Until an account exists, the panel shows the First-run setup card: Username, Password (min 6), Repeat password, and Create admin. That account is the admin, and the form only exists until it does - the first login is made for you, so you land inside the panel.

Two things worth knowing about this screen (Api.cs's Setup):

Afterwards the card is the ordinary Sign in form. Accounts, roles and per-user permissions are chapter 02's subject.

Making it reachable (loopback, URL ACLs, firewall)

The panel listens on 127.0.0.1 only by default, and so do the model and brain ports. That is why a fresh install is safe to leave running: nothing on your network, let alone the internet, can reach it. When you do want it reachable:

  1. Panel bind: Settings → Bind. Or start with --bind-all.
  2. URL ACL, once, with admin rights. Windows reserves HTTP prefixes per user; without a reservation the panel can bind loopback and nothing else. add-urlacl.ps1 adds them for the ports it names, in every prefix form http.sys checks separately - http://+:PORT/, http://*:PORT/, http://127.0.0.1:PORT/, http://localhost:PORT/ and http://<LAN-IP>:PORT/. A + reservation alone does not cover the loopback and IP forms, and it uses the sddl= form of the command because user=Everyone fails on non-English Windows.
  3. Firewall, once, with admin rights: add-firewall.ps1 adds inbound TCP rules for ports 80, 8080 and 8081. These are port rules rather than program rules on purpose - HTTP sites are served by http.sys in the kernel (PID 4), so a rule scoped to PlayPort.exe never matches their traffic. HTTPS on :443 is PlayPort's own socket, which is why that one works with a program rule.

Both scripts are samples, not settings: they hard-code the ports they touch and the URL-ACL script also names one LAN IP. Read them and edit the lists before running them. The panel prints the exact netsh http add urlacl line itself when it cannot bind all interfaces.

The panel also has a readiness screen that checks reachability from outside rather than assuming it - see chapter 11.

Stopping the panel without an admin prompt

The panel is its own kill switch, so none of these needs elevation:

PlayPort.exe --stop              # any running panel
PlayPort.exe --stop --port 8080  # specifically that one
bash stop-panels.sh 8080         # the same thing in plain curl, for Git Bash

A running panel advertises itself in a small control file (%LOCALAPPDATA%\PlayPort\panels\<port>.json) holding its port, pid, data root and a per-run secret, and it answers a shutdown request on loopback only with that secret. It then stops its servers and websites gracefully and exits - so --stop never cuts a save short.

One Git Bash trap. From Git Bash, running --stop on the very binary that is still running fails with Permission denied (MSYS will not start an image that is already loaded). cmd.exe, PowerShell and bash stop-panels.sh are unaffected; use the script.

Two panels, one machine

Starting the exe again on a port that is already panel-led is detected rather than ignored. With a console to answer on you are asked:

! A PlayPort panel is ALREADY RUNNING on port 8080 (pid 12345).
  Starting a second copy would only fail - your sites/servers are still being served
  by the existing window.
  [K] Stop the existing panel and take over, [Q] quit this copy? (K/Q) [K]:

K (the default) asks the running panel to stop, waits for the port and takes over. Q changes nothing and prints where the running panel is and how to start a second one alongside it (--port 8081). With no console to answer on - a double-click, a script - it takes over, because that is what launching the exe again means. The same handover is what makes updating work: drop in the new exe, run it, and it replaces the old panel.

If the port is held by something that is not a PlayPort panel, the bind fails and the panel says so and exits 1, naming the workaround:

! Could not listen on port 8080: ...
  Is another (non-PlayPort) program using that port? Try: PlayPort.exe --port 8081
  Binding all interfaces may need:  netsh http add urlacl url=http://+:8080/ user=Everyone

A fatal startup error pauses ("press any key") when the exe was double-clicked, so the window stays up with the reason in it instead of vanishing.

There is a third case, and the panel handles it without you: an HTTP reservation owned by something else can make a port answer 503 to everything. PlayPort notices, moves itself to the next free candidate port, saves that choice, and prints how to get the old port back:

  The panel moved to port 8081 instead:  http://127.0.0.1:8081/
  The choice is saved, so this stays your address until you change it.
  To claim 8080 back, remove the reservation once, as admin:
    netsh http delete urlacl url=http://+:8080/

Your first game server

Game Servers → + New server → pick Minecraft Vanilla, Minecraft Paper, SteamCMD or Manual process. For Minecraft, pick a version and accept the EULA; for SteamCMD, pick a game from the catalog or type an App ID; for manual, name an executable. Then press Start and watch the console.

That is the whole of the first-run experience. What each type downloads, how Java is chosen, how config and worlds are managed, and what the modpack and template catalogs offer are all chapter 03; the console, file manager, backups and schedules are chapter 04.

Updating PlayPort

There is no self-updater, on purpose: an update is a newer PlayPort.exe.

PlayPort.exe --update --from "D:\Downloads\PlayPort.exe"
PlayPort.exe --update --from-url https://example.com/PlayPort.exe

Both run inside the installed copy (so the record of the install and the data folder are the ones being updated). The sequence is deliberate:

  1. The new file is asked --version before anything is replaced. A file that does not answer is refused and the installed copy is left untouched - "a truncated copy" and "a broken build" look identical until something asks.
  2. Panels running from that install are asked to stop (no other panel on the machine is touched), because Windows will not let a running exe be replaced.
  3. The old exe is moved aside, the new one copied in, and then the new one is run as the verification. If it does not run, the old one is put back.
  4. Your data is not touched, and the panel says so: Updated. Your data (D:\PlayPortData) was not touched.
  5. The Apps & features entry is updated so Windows' idea of the installed version matches the panel's.
  6. If a panel was running (or you passed --relaunch) the updated panel is started again.

An --update-url recorded at install time is used when you pass no source, so a fleet of panels can be updated with the same URL. The panel's own Settings page also offers updating from a file; that path is chapter 02's.

PlayPort.exe --version is how you check what you are running, and it is the same question the updater asks:

PlayPort 0.3.1 (build 20260921-125250)
installed in C:\Users\you\AppData\Local\Programs\PlayPort, data in D:\PlayPortData

Line one is machine-readable and is the only line the updater reads; line two is for you. If the build was made without its checks passing, line two says so (· checks skipped: ...) instead of looking like every other build.

Moving an installation to another machine

Because the two folders are all there is:

  1. Stop the panel (--stop), then copy the data folder to the new machine's drive.
  2. Copy PlayPort.exe there and either run it with --data <that folder> or run --install --data <that folder> to record the location properly.
  3. Nothing is keyed to the old machine's paths except what the applications inside the data folder wrote for themselves (a game server's own config, a website's absolute paths). Check a server's start command and a site's document root if they named absolute paths.

If you would rather not move by hand, copy the install folder with its playport-install.json too and the new machine reads the same install record - but fix the two paths inside it if the drives differ, or just install again.

What can go wrong at this stage

What you seeWhat it is
! A PlayPort panel is ALREADY RUNNING on port 8080There is nothing to fix. Answer K to take over or Q and use --port 8081 for a second panel
! Could not listen on port 8080Another program holds the port, or the URL ACL is missing. Both workarounds are printed on the spot
The panel moved ports by itself after a 503An HTTP reservation owns that port. The new port is saved; the netsh http delete urlacl line is printed to claim the old one back
The exe's folder is not writableThe panel falls back to %LOCALAPPDATA%\PlayPort and says so. --install with --dir and --data is the durable fix
Install refused: not enough roomThe target drive cannot hold the program plus headroom, or cannot hold the data that would move onto it. --dir another drive, or --no-move-data
Install refused: the install folder is inside the data folderPick two folders that do not contain each other
The data move did not completeNothing was deleted. The old folder is named, and --data <old folder> points the panel at it
--stop says Permission deniedYou are in Git Bash and that image is running. bash stop-panels.sh <port>
First run shows "cannot read its accounts file"The accounts file is damaged and the panel will not overwrite it. See chapter 11
The window vanishes when double-clickedIt should not: fatal startup errors pause. If it truly closes, the reason is in playport.log in the data root

Files behind this chapter

FeatureSource
Install, uninstall, update, the install record, the verified data move, shortcuts, Apps & features entrysrc/Install.cs
The command-line parser, the boot sequence, the start banner, duplicate-panel detection, the fatal-error pausesrc/Program.cs
The data-root rules (priority order, playport-data-mode.txt, the write probe) and the panel's settingssrc/Store.cs
Panel name, version, build stamp and the check line --version reportssrc/AppInfo.cs
First-run setup, sign-in, and the damaged-accounts casesrc/Api.cs (Setup, /api/needsetup), src/Store.cs (Auth), web/app.js
URL ACLs and firewall rules for reaching the panel from another machineadd-urlacl.ps1, add-firewall.ps1
Stopping a panel without elevationsrc/Api.cs (/api/panel/shutdown), stop-panels.sh
The retired installer wizard window (compiled, but nothing calls it; a copy is kept in attic/installer/Setup.cs)src/Setup.cs

Chapter 02. The panel

This chapter is the panel as an operator lives in it: signing in, who may do what, what every page in the sidebar is for, the dashboard and its health board, the settings page, and the small pieces that run underneath all of them (search, the activity list, the panel's own log). Each feature page is documented in its own chapter - this one tells you what exists, where it lives and what it is telling you, and points at the chapter that owns the detail.

The panel is one Windows executable serving a web interface on its own port (8080 unless you changed it) and talking to everything else on the machine: game servers, websites, the database, the model ports. It is a local product: by default it listens on 127.0.0.1 only, and the person using it is either at this machine or on the same network.

Signing in

Open the panel's address in a browser - http://127.0.0.1:8080/ on the machine itself, or http://<lan-ip>:8080/ from another device once the panel listens beyond loopback (see Binding, ports and reachability below).

First run. Until an account exists, the panel shows First-run setup: create the administrator account for this PlayPort. It asks for a Username, a Password (min 6) and Repeat password; the button is Create admin. That account is the owner of the machine's panel and the only one that can change panel-wide settings, so make its password a real one. The first-run form disappears for good once the account exists.

Every day after that the card says Sign in - username, password, Sign in - and the session it hands back lasts 30 days. The browser keeps the session token in its local storage, so closing the tab does not sign you out; Sign out at the bottom of the sidebar does, and it is the right thing to do on a shared machine.

When a session ends behind you. The panel answers a request with Not logged in once the token is no longer valid, and the interface reacts by throwing up the sign-in card with the message Session expired - log in again. Any form you had open is gone at that point, so save work before leaving a tab open for a month.

Wrong passwords are rate limited: eight failed attempts for the same username inside a minute and that name is refused for the rest of the minute. This is per username, and it is deliberately simple - it stops a script, and it does not lock a person out for a typo.

One failure mode worth knowing about. The panel will not replace an accounts file it could not read. If auth.json in the data root is damaged, sign-in switches off entirely and the card says PlayPort cannot read its accounts file, with the sentence from the panel itself: it names the file and says the panel kept a copy rather than overwriting your account hashes. There is no form to fill in, because zero accounts is also what a brand-new panel looks like, and letting the first-run form appear here would let anybody who can reach the panel create a new owner over the real one. The way out is stated on screen: stop PlayPort, delete or rename auth.json, start it again, and the first-run screen creates a new administrator.

Accounts, roles and what each account may touch

Users in the sidebar is the account list. Each row has the account name, its role (a dropdown you can change), how many resources are Shared with them, when it was created, and three buttons: Permissions, Reset password and Delete. + Add user opens a small form: Username, Password (min 6) and a Role with the three roles spelled out as the panel spells them:

RoleWhat it means
adminFull control: every server, every site, panel settings, users, and the machine-wide pages
moderatorManages all servers and websites, and sees the machine-wide health and readiness screens, but cannot change panel settings
userOnly their own servers and websites, plus anything explicitly shared with them

Three rules the panel enforces rather than trusts:

Sharing one server or site with somebody else is the Permissions button, and it is the capability model in one dialog. Each server and each website gets four checkboxes, in this order:

CapabilityWhat it adds
ViewSee it, its status, its logs, its metrics. Watching a console is a view
Start/stopStart, stop, restart, kill, send console commands
EditChange settings, backups, schedules, certificates
FilesBrowse, upload, download, rename and delete files

They imply each other downwards - Files implies Start/stop implies View, and Edit implies Start/stop - because a permission model nobody can explain is a permission model nobody checks. Nothing implies Files: file access is the one that can become code execution. Ownership and staff roles always win, so a grant only ever adds access and can never take it away. Ticking nothing for a resource stores nothing; ticking a capability for an account whose role is already admin or moderator is kept, but the dialog says plainly that the role already grants everything and the grants are only kept in case the role changes back.

Programs do not sign in; they present a key. Everything outside the browser - another PlayPort, an AI tool, a script - reaches the model and agent port with an API key rather than an account, and those keys are created on the Agents page. The three doors are listed in Appendix B; the agent side of it is chapter 10.

Where all of this is stored. Accounts, password hashes, roles, grants and sessions live in one file in the data root: auth.json. Passwords are stored as PBKDF2 hashes with a per-account salt, never in the clear. Panel-wide settings live beside it in settings.json (see Appendix C). Both are the panel's own files, and both are in a backup of the data root - the panel ships no separate account database.

The sidebar, and what every page is for

The sidebar is grouped into three labelled sections and always in this order. The icon rail collapses to icons on a narrow window; the search box stays at the top of it.

WORKSPACE

PageWhat it isDetail in
OverviewThe dashboard: machine numbers, a health popover, and the two lists of what you runthis chapter
Game serversEvery server: Minecraft (Vanilla/Paper and versions), SteamCMD games, manual processes, and Minecraft networks03
WebsitesEvery hosted website: static, PHP and reverse-proxy sites, with domains and TLS05
EmailMail hosting for your sites: domains, mailboxes, delivery, and every message the panel has captured10
ActivityOne list of every long-running thing the panel is doing, plus the unrecognised-crash queuethis chapter

LOCAL AI

PageWhat it isDetail in
BuddyThe assistant as a full conversation page (the floating bubble is the same assistant for quick questions)10
ModelsThe AI this machine runs itself: what it can run, what it is running, and the guard limits10
AgentsThe API keys other tools use, and what each key is allowed to reach10

ADMINISTRATION

PageWhat it isDetail in
UsersAccounts, roles and per-resource permissionsthis chapter
SettingsPanel settings, components, readiness, tunnels, databases, backup destinations, storage and alertsthis chapter

At the bottom of the sidebar: the colour theme picker (Colours), the Future effects toggle, and the account strip - your name, your role, and Sign out.

The dashboard (Overview)

The Overview page is deliberately two things: what the machine is doing right now, and what you run.

The stat cards, left to right:

CardWhat the number is
CPUThe machine's CPU load, as a percentage
MemoryThe machine's memory use, with GB free of the total underneath
Serversrunning / total game servers
Websitesrunning / total websites
UptimeHow long this panel process has been up, hh:mm:ss, labelled panel up

Two charts under the cards - CPU and Memory - drawn from the machine samples the panel takes in the background. They are history, not a live gauge: the samples it has collected, scaled to the highest value in the recent stretch, with the scale marked top-left. A panel that was just restarted shows waiting for data… until it has a few.

Two tables, the same ones the feature pages use, so the dashboard is a way in rather than a second place to maintain: Game servers and Websites, each summarised by status. A fresh install says so in words - No game servers yet - create one - rather than showing an empty table.

The System health popover sits in the page's toolbar (staff only, and why is in the next section). Re-check re-runs it immediately.

The banners at the top of the dashboard

Some failures are not about a server or a site but about the panel's own memory of what you chose, and those get a banner at the top of the Overview page. There are four kinds, and they are worded differently on purpose, because each one sends you somewhere different:

System health: one screen of truth

The health board is the panel checking its own machine once and giving each part a verdict in words. It is staff only - it reports on the machine rather than on one tenant's corner of it, and /api/health refuses it to a plain user rather than drawing an empty screen. On the dashboard it is the System health popover; on Settings the same report is what fills the Components rows (MariaDB's state, for example, is a health row before it is a download button), so the two screens cannot disagree about whether a piece of this machine is working.

Four verdicts, and the last one matters as much as the first:

VerdictMeaning
okWorking. One line, no explanation
warnWorking now, and something will bite later - a certificate inside its renewal window, a backup older than a week
failBroken right now
offDeliberately not set up, so nothing is wrong. A fresh panel does not read as a broken one

The board opens with one line of summary and a count, for example Everything is running, with 2 thing(s) worth watching with 14 ok · 2 watch · 0 down · 1 off beside it. Under that, one row per part: a state dot, the part's name, and its current state in a sentence. A row that is healthy says one line and stops; a row that is not gets the next step printed under it in words rather than a red dot, and a part the panel can fix on the spot carries a small button instead of a sentence telling you which page to visit. Some checks are worth knowing about by name:

Unrecognised crashes (the review queue)

The panel explains server failures from a library of known patterns, offline and instantly: it can say I do not recognise this but it never invents a cause. Anything it cannot name still gets filed, and that filing is the review queue - it appears on the Activity page, and only when there is something in it, so it stays out of the way until a crash needs a human.

The queue groups crashes by a signature of the "errorish" lines in the log, so a crash loop becomes one entry with a count instead of hundreds of entries. Each entry carries the failure's own most distinctive line, when it was first and last seen, which server (and its type and version) produced it, and the tail of that log. The actions on the panel are:

The queue is held in unknown-crashes.json in the data root, capped at 60 entries of up to 40 lines each, and nothing in it leaves the machine - it is a filing cabinet, not a service. Its normal life cycle is: a crash lands here, a person reads the log, a rule is added, and the entry is re-checked until it is understood.

Activity: everything the panel is doing

Activity is one list of every long-running thing: installs, updates, builds, starts, stops, Java runtime downloads, database work, site work, template downloads and network operations. Each row shows a kind, a title, a state pill, when it started (and how long it took if it has finished), any detail line the job carries, and a progress bar while it is live.

The panel's own log

Everything the panel does that an operator might need later goes into one file in the data root: playport.log. Each line is stamped, and the API writes the whole stack there when a request fails, keeping the response itself to one sentence - so a 500 in the browser is not a dead end. The console window that opened with the panel prints the same stream, which is why closing that window stops the panel (see chapter 01). Per-server consoles and log files are a different thing entirely and are chapter 04.

Settings

Settings is one page of cards. Any signed-in account can open it, but the fields are disabled unless you are an admin, and the staff-only panels and their contents are only drawn for the roles that can use them: Server readiness, Databases, Backup destinations, Storage and Alerts are admin-and-moderator surfaces (the two database and backup panels are admin). In the order the page draws them:

Components - the pieces the panel downloads and runs on this machine: MariaDB (databases for sites and game servers, ~90 MB), cloudflared (public URL without opening a port, ~60 MB), and playit.gg (a public TCP/UDP address for a game port, ~5 MB). Each row has a state, what it is for, its size, and one small button that is the honest next action - Install, Start, Stop, or Enter secret key where the agent needs linking. Installing from here is admin-only, and a download that previously failed says so on the row instead of offering the button again blindly.

Panel & defaults - the settings that belong to the panel itself:

FieldWhat it doesTakes effect
Data root (read-only)Where this panel keeps everythingit is a report, not an input
Admin portThe panel's own port (default 8080)after a panel restart
Bind127.0.0.1 only (safe) or All interfacesafter a panel restart
Java executable for Minecraft serversA specific java.exe; blank means use PATHnext server start
Keep this many backups per kindThe default retention for backupsnext backup
Website bind address (blank = all interfaces)Which interfaces websites listen onwebsites rebind when you save

There is a Reaching this panel from the LAN explainer next to those fields, because the two binds are different questions: the panel's bind decides who can reach the control panel, and the website bind decides who can reach websites. A specific IP (for example 192.168.1.225) is what makes sites reachable on your network, and on Windows it needs admin rights or a URL ACL once. Only an admin gets a Save button; everyone else reads the note that says so.

Server readiness - the checks that answer is this machine actually reachable from the internet? Run checks measures it and the result is kept for the session so the page's own refresh does not re-probe the network. What each result means is the network path in chapter 11.

Tunnels - the Cloudflare side: quick tunnels and named tunnels for your sites (chapter 05).

Databases (admin) - the bundled MySQL/MariaDB server: install, start, stop, and the database list (chapter 06).

Backup destinations (admin) - the folders and shares that backups can also be copied to, for backups that leave the machine (chapter 04).

This installation - see below.

Storage and Alerts - the two panels handed to this page by its ops module. Storage totals the data root on disk (cached, with a Re-measure button): game servers, websites, backups, Java runtimes, the database and SteamCMD, the volume's free space, a per-server and per-site table, and the largest folders in the data root - which is the answer to what is filling my C: drive. Alerts is where you are told when something breaks: a Discord/Slack webhook URL, an email address, three events to subscribe to (A server crashed, Auto-restart gave up, An off-machine backup copy failed), a data volume this % full threshold with 0 = off, a de-duplication window (repeat the same alert after this many minutes, 0 = always), Save alerts and Send a test. Both panels are staff-only; alerts are chapter 10's subject.

This installation (a report, not a button)

The This installation card answers the questions a settings page should, and it does nothing. It shows where this copy is running, whether it is an installed copy or a portable one, where its data root is and how big it is, which other folders on this machine hold PlayPort data (and whether they are empty or abandoned), whether an install or move is still queued, and the port story - including, on a machine where the panel had to move off its configured port, the one admin command that gives it back.

Installing, updating and removing this program are done from a console, not from a web page:

PlayPort.exe --install   |   --update   |   --uninstall

That is deliberate, and it is the whole reason the card is a report: installing is a decision about a machine's folders, and the only front for it is a console somebody typed into. There is no in-panel Update button to look for - the flags, and what each one does, are in Appendix A and chapter 01.

Search (Ctrl+K)

The sidebar has a search box - Search everything, or press Ctrl+K anywhere in the panel. One query covers game servers, websites, databases, mail domains and mailboxes, users, backup archives and server console history, grouped by kind, one click from the destination page. Esc closes the overlay.

Two rules make it trustworthy rather than clever, and both are worth knowing when you rely on it:

A term shorter than two characters returns nothing but a hint to type more.

Appearance

Two controls sit at the bottom of the sidebar, and both are stored in your browser, not on the server, so they are per-browser rather than per-account:

The product's own name and mark (the controller whose middle is a server rack, green on one side and violet on the other) are fixed, drawn once and shared by the boot screen, the sign-in card and the sidebar, so the three can never drift apart.

The phone layout

The panel is usable from a phone or a small window on the same network. What changes at a narrow width: a single-column card layout, the sidebar reducing to an icon rail (and its labels, account strip and theme controls hidden), a horizontally scrolling nav, sub-menu tabs that stick under the top bar, form rows that stack, and modals that become full-width sheets. Wide tables scroll inside their own container rather than forcing the page sideways, so there is never a page-level horizontal scrollbar. Inside the server picker, the category rail becomes a horizontally scrolling chip row so the template tiles get the full width.

Reaching it from a phone needs the panel to listen beyond loopback and the machine's LAN address - http://<lan-ip>:<port>/ - which is the Bind setting above plus the network's own firewall.

Binding, ports and reachability

This is the short version; Appendix B is the table and chapter 11 is the network path in full.

Files behind this chapter

FeatureSource
The HTTP server, sessions, WebSockets, asset serving (including no-store on interface files)src/Http.cs
Route dispatch; login, logout, setup, /api/me, /api/needsetup, /api/version; users, roles and grants; settings; install state; search; health; storage; alertssrc/Api.cs
Accounts, password hashing, sessions, the login rate limit, roles, grants and Access.Allows; the settings model, its defaults and the data-root rulesrc/Store.cs
The health board: every check, the four verdicts and the summarysrc/Health.cs
Crash explanations from known patterns (the rule library the review queue feeds)src/Diagnose.cs
The unrecognised-crash queue: signature grouping, the cap, re-checking, unknown-crashes.jsonsrc/Unknowns.cs
Search: what it covers and the permission/disk rulessrc/Search.cs
Panel identity, version, build id and the build's check linesrc/AppInfo.cs
Machine CPU/RAM sampling and the LAN addresssrc/Sys.cs
The installer/updater engine the command line drivessrc/Install.cs
The shell, the router, sign-in and first-run, the dashboard, the users page, the settings form, the install reportweb/app.js
The sidebar, the search overlay, the first-run/sign-in card, the assistant bubbleweb/index.html
The health board and the Storage and Alerts panelsweb/ops.js
The dashboard's render merging (why a poll does not tear the page down)web/paint.js
Layout, palettes and the phone rulesweb/app.css
The installer window's palette (the same colours as web/app.css)src/Theme.cs

Chapter 03. Game servers

This chapter is about the server side of the panel: creating a game server, choosing what it runs, giving it the right Java, starting and stopping it, and knowing what the panel does when it dies. It covers the four families of server (Minecraft, SteamCMD games, manual processes and modpacks), the template catalog, the Java runtime the panel fetches for you, and the restart policy. The console, the file manager, backups and schedules have their own chapter (04); installing the core plugin into a server is 07 and its in-game behaviour is 08; Minecraft networks (a proxy in front of several servers) are 09.

Everything here assumes you are signed in and can reach Game Servers. The panel port, the data root and the command-line flags are in chapter 01 and Appendix A.

What a game server is in PlayPort

One game server is one instance: its own folder under playport-data/instances/<id>/, its own port, its own console, its own install. Instances do not share a game directory, and nothing about one server can change another - which is what makes two servers on one machine behave like two servers on two machines.

The panel creates and runs four kinds of instance, and they differ in where the files come from rather than in how they are run:

KindWhere the files come fromWho sets the start command
Minecraft (vanilla, paper, purpur, fabric, forge, neoforge)the server software's own publisher (Mojang, PaperMC, Purpur, FabricMC, the Forge/NeoForge installers)the panel, from the loader
ModpackModrinth (.mrpack) plus the loader it namesthe panel, from the loader
SteamCMD (steam)Valve's SteamCMD, with the App ID you choseyou - the panel installs files, it does not know how your game boots
Manual (manual)you, by uploading filesyou

Networks add two more types the picker shows under Networks - velocity (a proxy) and limbo (NanoLimbo, a tiny lobby/AFK server) - and those are chapter 09.

Creating a server

Game Servers -> + New server opens the picker. It is one catalog with a search box (Search games, modpacks…) and six categories:

Clicking a tile opens the second step: the settings that instance needs. Every create asks for the instance's own values (a name, memory, its port, its Java runtime) and, where they apply, the ones this chapter explains: the Minecraft EULA, the start executable and arguments, auto-restart and auto-start, a starter pack, a core-plugin profile, and the worlds the server should be made of. Creating an instance is not instant for the non-manual kinds: the panel starts an install job, the instance appears immediately in the installing state, and its console is where the install narrates itself.

The EULA is asked once and enforced twice. Minecraft servers refuse to start until eula.txt says eula=true, so the panel writes that file as part of the install and again at the last moment before every launch (in BOM-less UTF-8, because Java reads a byte-order mark as part of the line and refuses the file). The second write is the one that matters in practice: a server installed before you ticked the box - or installed by a template, a pack or a test - used to print Failed to load eula.txt on every start while the hint told you to tick a box that had no effect. Now "tick the box, then press Start again" actually works, which is what the panel's own diagnosis tells you to do.

Minecraft

Pick the software, then pick the Minecraft version. What happens on install depends on which one:

On 1.17 and newer, Forge and NeoForge servers are not launched with -jar: they boot through the argument file their installer wrote. The panel detects that and uses it, so a pack that installs perfectly does not then refuse to start.

SteamCMD games

SteamCMD is Valve's own command-line client, and this is the same shape AMP's SteamCMD module has: the panel installs and updates files, and the way your game boots is yours to set.

The game template catalog (CubeCoders' AMP library)

The Templates category is the catalog AMP itself ships, read from CubeCoders' public AMPTemplates repository. The panel does not vendor or clone it: it downloads the repository index once, parses each template manifest, and keeps a normalised copy in playport-data/templates/catalog.json. After that first fetch the picker works offline, and the button under the list (Fetch templates, then Refresh) re-pulls from GitHub when you want something newer. The fetch runs as a job, so a slow first download shows its own progress.

A template is what makes this better than a bare App-ID list, because it carries everything needed to run a server: the display name and description, the dedicated-server Steam App ID, the Windows executable and command line, the working directory, the default port, whether an anonymous install is allowed, and the header image. Templates for services this panel already provides (MySQL, MariaDB, PostgreSQL, Redis and friends) or that are not games (language runtimes, code-server) are skipped on purpose, so the list stays a game list.

Two things to check before you trust one:

Modpacks (Modrinth)

The Modpacks category browses Modrinth's public API - free-text search and paging, not a fixed top list - and shows only packs that can run a dedicated server (Modrinth marks client-only packs as server-side unsupported, and those are filtered out). Each tile shows the pack's loader and the newest supported Minecraft version.

Installing a pack downloads the .mrpack, puts every mod where its loader expects it (mods/), applies the pack's overrides, and installs the loader the pack names (Fabric, Forge, or NeoForge) so that the panel's normal Minecraft launch path works. The exact loader build and Minecraft version are recorded on the instance, so a later update reinstalls the same build instead of silently moving your server onto a newer one. Java is required before the loader installer can run, and the install fails fast with "click Install Java in Settings -> Runtime, then re-run Install" rather than after downloading hundreds of mods.

Starter packs, the core plugin and the worlds a server owns

A fresh Minecraft server is not useful on its own, so the create form can apply a starter pack:

The same form can ask for the worlds the server should be made of - a name, a preset and an optional seed per world, chosen from the presets the bundled plugin ships. A world is the one part of a Minecraft server that cannot be fixed afterwards: the plugin can adopt land that already exists, but adopting keeps whatever generated it, so a world the server made itself is ordinary vanilla terrain no matter which preset you name later. The list therefore travels with the create, the install writes it into the plugin's panel-worlds.yml, and the plugin makes those worlds on the first start. A row the validator refuses is refused by name in the new server's own console (! world request refused: <why>), so a typo arrives as a sentence rather than as a world that quietly is not there.

The Java runtime

Minecraft needs Java, and the panel answers "which Java" from Mojang's own published metadata rather than from a table somebody maintains: it reads the version manifest, asks for the version's own document, and records the majorVersion there. A Minecraft release published after this build therefore gets the right runtime, and a version whose requirement changes is picked up the next time it is resolved. Every answer is cached in playport-data/java-requirements.json, so the picker, the create flow and the start path all keep working with no internet at all; when a version has never been resolved and the metadata cannot be reached, a rule derived from the version number answers instead, and the panel says which of the two it used rather than pretending they are the same thing.

The panel installs Temurin JRE runtimes from Adoptium on demand - Java 8, 17, 21 and 25 - about 45 MB each, into four folders under the data root: playport-data/jre8, jre17, jre21, jre25. Finding a runtime works from the outside in: PLAYPORT_JRE if you set it, then a bundled runtime, then the usual vendors' install folders (Java, Eclipse Adoptium, Microsoft, Zulu, Amazon Corretto, BellSoft…), then JAVA_HOME, then whatever java is on PATH.

Pressing Start once is enough. If the runtime a server needs is missing, the panel downloads it there and then, the instance sits in Waiting for the Java runtime this server needs, and the start goes ahead by itself when the download finishes. A panel-wide sweep also re-resolves the requirement for every Minecraft instance and fetches a newer runtime when one is needed, so a server that moves to a newer Minecraft version does not start failing on a class-version error.

Running a server

The instance page has one tab per subject: Overview, Console, Game, Players, Plugins (or Mods, depending on what the server can load), Files, Backups, Schedules and Settings. Start, Stop and Restart live on the overview and in the list.

Restart policy

Three separate things, and it is worth knowing which one you are turning on:

Editing the server's own config

The Game tab is an editor for server.properties, which is where every Minecraft owner ends up and is normally the worst interface in the stack: dozens of keys, no labels, no ranges, and a server that rewrites the whole file on boot (losing your comments and reordering your keys). The panel carries a schema - a label, a group, a sentence of help, a type, a range and a default for every key it knows - so the page draws real controls instead of a text box. The groups are Identity, Gameplay, World, Performance and safety, Remote access, Resource pack and Other keys in this file.

Two behaviours worth knowing:

The core plugin's own settings are a different surface and are documented in chapter 07 (the panel side) and chapter 08 (config.yml and the in-game editor). Anything Java-based whose file does not exist yet says so and points you at the Files tab.

Players: whitelist, operators and bans

The Players tab is the four JSON files nobody wants to hand-edit (whitelist.json, ops.json, banned-players.json, banned-ips.json) plus who is online right now, in one list. Every entry in those files is the player's Mojang id in dashed form; get it wrong and the entry silently does nothing, so the panel never guesses:

What is on disk, and what is safe to move

playport-data/instances/<id>/
  instance.json    the instance's whole configuration (including a Steam password if you set one)
  console.log      the console history the panel shows
  server/          the game's own files - the server jar, worlds, plugins/mods, server.properties
  backups/         this server's archives

Copy the instance folder while the server is stopped and you have moved the server; that is also what the panel's own backup does. session.lock and playport.lock are deliberately not part of a copy.

Copying a server in one click

Copy on an instance makes a clone: the configuration, the mods, the plugins, the worlds, the starter pack and the core-plugin profile come across, and the clone is put on its own free port - a clone that immediately dies with "address already in use" would be worse than no clone at all - with the port rewritten in the copy's own server config. What is deliberately not copied: the backups (that would double your disk use for nothing), the logs (they describe a different server), the crash history, the schedules and saved commands, the panel's per-server link token and saved plugin settings (each server gets its own), and the database link - a copy that came up pointing at the original's database would write the original's player records from a second server, two servers and one set of balances with nothing on screen saying so.

Gotchas worth knowing up front

Files behind this chapter

FeatureSource
The instance model, its config, start/stop/kill, installs, the port picker, copyingsrc/Instances.cs
The Minecraft software this build can create, and what each one can loadsrc/Loaders.cs
Minecraft server jars (Vanilla/Paper) and the loader installerssrc/Instances.cs (RunInstall, MinecraftSource.GetServerUrl), src/Loaders.cs
Which Java a Minecraft version needs (Mojang's metadata, the cache, the fallback rule)src/JavaReq.cs
Downloading and finding a Java runtime (Temurin JRE 8/17/21/25, PLAYPORT_JRE)src/Instances.cs (InstallJava, EnsureJava, DetectJava, JavaSweep)
SteamCMD: the catalog, the bootstrap, app_update, progress, the login and the timerssrc/Steam.cs, src/Instances.cs
The game template catalog (CubeCoders' AMPTemplates)src/Templates.cs
Modrinth modpackssrc/Modpack.cs
Starter packs (the core plugin's templates, and the mod-loader packs)src/Starters.cs
Worlds asked for at create time, and the core-plugin profilesrc/Instances.cs (Create), src/Core.cs
The server.properties editor and its schemasrc/GameCfg.cs
Players: whitelist, operators, banssrc/Players.cs
Live status (server list ping)src/McPing.cs
The half-built world a kill would destroysrc/WorldFix.cs
Restart warnings, auto-restart and crash-loop limitssrc/Instances.cs (StartWarning, TickWarnings, the exit handler)
The create form, the picker, the instance tabs and their fieldsweb/app.js, web/game.js

Chapter 04. Files, console, backups and schedules

This chapter is the day-to-day of running a server: what the server is telling you, what is on its disk, what a backup actually contains, and what the panel does on a timer. It assumes the server already exists - creating one, its versions, its Java runtime and its start/stop policy are chapter 03. Site files (and the ZIP import that only a website takes) are chapter 05, database dumps are chapter 06, and the panel's own state - the thing you would restore the panel from - is chapter 11.

A server page has these tabs, and three of them are this chapter:

Overview · Console · Game · Players · Plugins/Mods · Files · Backups · Schedules · Settings

Which of them you can see depends on your account, and the gating is per tab: Files and Plugins/Mods need the files permission, while Backups, Schedules and Settings need edit. A staff member with only view rights cannot reach any of them, and the server refuses the request as well - hiding the tab is convenience, not the security boundary. See chapter 02.


The console

Console is the server's own output, streamed to the browser as it is printed, with a box at the bottom that sends a line straight into the server's stdin.

What it holds, and for how long. The panel keeps a rolling in-memory buffer of the last 3000 lines and appends every one of them to a real log file beside the server's configuration:

<data>/instances/<id>/console.log          the server's console, one line per entry
<data>/instances/<id>/console.log.old      the previous file, kept when the log passes 8 MB

Each line is stamped [HH:mm:ss] text. The file is capped by rotating it to console.log.old once it exceeds 8 MB, so there is always a bounded amount of history on disk and one generation behind it. The browser buffer is memory only: restarting the panel empties what you can scroll back through, not what is on disk - the file is where the words live.

Lines the panel writes itself are mixed into the same stream, and they are marked so a reader can tell them apart from the server's own output:

MarkerWhat it is
! ...a problem the panel met - the failure sentence, kept for whoever opens the console next
>> ...the panel's own answer or commentary, echoed into the console so it lands where the owner is already looking
[HH:mm:ss] ...the panel's log line for an action it took (start, backup, copy, prune)

Those prefixes matter beyond readability: they are what the log reader uses to drop the panel's own earlier commentary before a reading is handed to a model, so a model cannot read its own previous guess as evidence (see the log feed).

Sending a command. Type into the box at the bottom of the console and press enter. Sending to a stopped server is refused - there is nothing to write to - rather than silently queued.

Saved commands. Below the box there is a save typed command button. Name a command once (save-all, whitelist add Steve, a maintenance broadcast) and it becomes a one-click chip on the console, stored with that server. The limits are enforced by name rather than by a silent truncation:


The log feed and crash explanations

When a server dies, the panel does not just say "it stopped".

The rules run first, offline. The moment a server enters an error or crashed state, the tail of its console is scanned by a rule-based analyzer - no AI, no API key, nothing leaves the machine - and the result is printed as a plain-language cause with a suggested fix. It knows the failures people actually hit: a missing or wrong Java version (with the exact java.exe to install), the port already being in use (it names the other server on it), the EULA not having been accepted, an out-of-memory limit, a world saved by a newer version, a config file that will not parse, and mod/plugin dependency clashes. The banner offers What happened? / Why did it crash?, and the console has Scan again and Look further back for a cause that scrolled past the window.

The one failure a console cannot explain is a half-made world folder, so the panel reads the folder too and puts the finding where the crash is: what shape it is in, how much land is in it, and - only when there is a level.dat, no world-gen settings and no chunk ever generated - one button that sets the folder aside as world.broken-<timestamp> so the server can generate it properly. A world with land in it is never moved by the panel; it says so instead of offering. Once the folder is fine the fatal finding is dropped rather than left accusing a repaired server.

The log feed, before any model sees anything. A Minecraft console is mostly not errors: a plugin list, a lag monitor, a spark banner, a warning about sun.misc.Unsafe and a wall of at java.base/... frames. The panel distils the window first - it keeps the exception, what the server said it could not do, the Caused by chain and a few frames per exception, and drops the banners, the repeated warnings, the JVM plumbing and the panel's own earlier lines. Two rules keep it honest:

You can see exactly what a model would be handed, with no model and no panel:

PlayPort.exe --feed path\to\console.log

That prints the kept lines, the counts and the same summary sentence the page shows. Which model answers, and what the tiers and the free cloud do, is chapter 10.

When no rule matches, the evidence is not thrown away: a trimmed copy of that server's log is kept and listed under Activity -> Unrecognised crashes (admin/moderator only, since a log can belong to any client's server). That review queue, and what the panel does with it, is chapter 02.

The bot that reads a log for a person is the same machinery: Help answers questions about this machine (the assistant, its hands and what it may touch are chapter 10).


The file manager

Files on a server page browses the server folder; Files on a site page browses the site folder. It is the same manager, rooted at the thing you opened, and it needs the files permission.

ActionHow
Browseopen the tab; folders sort before files, each name-sorted, with size and modified time
Downloadclick a file (the download carries the file's own name as an attachment)
Edit / replaceopen the write action, which writes the file's bytes as sent
UploadUpload - a game server's files arrive this way, or from a modpack
New folderNew folder, which asks for the name
Renamerename a file or a folder, including moving it by naming a new path
Deletedelete a file, or a folder and everything under it

What it will not do. Every path goes through one resolver that resolves it against the root and refuses anything that lands outside - ../ traversal, an absolute path, a name that escapes the folder. A refusal is answered as Invalid path, and the manager never touches a file outside the server or site folder it is rooted at. The same guard is what stops a crafted archive from writing outside the folder on restore (see below).

Failures are translated. The manager's own errors are the ones a buyer meets most - a folder the panel may not read, a file another program holds open. The raw Windows sentence goes to the panel log (where Diagnose reads it) and the browser is given the class of failure instead, so "Access to the path 'D:\...' is denied." never becomes the toast.

Two things to know before you rely on it.

A site's Files page is the one with the ZIP import and the framework build - that is chapter 05; a game server's Files tab deliberately offers upload instead.


Backups

Backups is where you decide what an archive contains, where it goes, and what is kept. Its whole point is that "back up my server" should mean "the things I would be upset to lose" unless you say otherwise: a full zip of a modded server is routinely many gigabytes and most of it is regenerable.

The plan: what a backup contains

The tab opens with six categories, each showing what it would actually take - measured, not published as advice:

CategoryWhat it takes
Player datainventories, ender chests, XP, stats and advancements - the thing people notice losing
Server settingsserver.properties, ops/whitelist/bans, and the root-level config files the server and its plugins read
Worldthe map: terrain, blocks, entities, every dimension
Plugins and modsthe installed jars and their own configuration folders
Logs and crash reportsconsole history and crash reports (the panel keeps its own copy too)
Everythingthe entire server folder, exactly as it is on disk, caches and all

Three one-click starting points are offered next to the numbers - Smallest (player data and settings), Standard (player data, settings, world, plugins - the default for a new server) and Everything - each shown with what it would cost before you commit to it.

Extra paths can be added by hand, and they behave exactly like a built-in rule: myworld/ takes a folder, notes.txt takes one file, *.dat is a glob. A path that no longer exists is named in the plan and in the server log - an entry that has been renamed should be visible, not silently missing from every archive from then on.

How matching works (worth knowing, because it decides what a rule of yours really takes): paths are relative to the server folder, case-insensitive, with / separators. world/ is a prefix - the folder and everything under it. *.yml is a glob over one path segment, so it does not cross a / and a root-level config stays separate from a plugin's own config folder. * is the whole folder. A file matching no rule is simply not archived. Nothing outside the server folder is ever followed, and a symbolic link or junction is skipped rather than traversed - a link to C:\ is not part of a backup.

The selection is remembered on the server, so the button and a schedule agree by default; a schedule may override it (below). The measured sizes come from one metadata-only walk - nothing inside a file is read - and the archive is written by the same walk, so the number shown is the number of files it takes. The measured plan is held for twenty seconds while the tab polls (Instances.BackupInfo's cache) and re-measured as soon as you change the selection - note that README.md describes this as a thirty-second cache, which is not the value in the code.

Making one, and where it lands

Press the backup button (or let a schedule do it) and the panel writes:

<data>/instances/<id>/backups/<tag>-<stamp>.zip          the archive
<data>/instances/<id>/backups/<tag>-<stamp>.zip.meta.json  where copies of it went

The tag is manual for the button and scheduled for a schedule, and the list shows it back to you in plain words from the archive's own contents. A backup refuses to start while the server is running - the log says so in as many words ("would corrupt the world") - so the archive is always taken from a stopped server. The archive is written to a .tmp name and moved into place when complete, so a failed or interrupted backup never leaves a half-archive where a good one used to be.

Every archive carries a small manifest of what it holds (_playport-backup.json: the tag, the creation time, the server, its type and version, the categories and extra paths chosen, and the file and byte counts). That is why the list can say "player data, server settings" instead of making you remember, and why an archive written before the plan existed is reported as everything - because that is exactly what it is.

Retention: what is kept, and what is not

Retention is counted per kind of backup, not per server. That is deliberate: the moment you add an hourly player-data snapshot beside a weekly full archive, one shared limit means the small ones evict the big one - and the thing you were protecting is the thing that goes. Counting by tag keeps both promises. The count is the panel's Backup keep setting (5 by default in this build), and a prune prints each archive it removes into the server's console.

Restoring

Restore writes back exactly the files the archive contains, over the server folder, creating directories as needed - and anything the archive does not contain is left exactly as it is now. The console says which of the two cases you are in before it writes anything ("player data, server settings (files outside the archive are left alone)"), because that distinction is the whole risk:

Downloading is the safe option when you are unsure: you get the file and nothing touches the server folder.

Backups that leave the machine

A backup sitting on the same disk as the server it protects is the failure mode that catches people, so each server can also copy every archive to one or more destinations as it is written. Destinations themselves are set up in Settings and are admin-only, because they hold credentials and decide where a customer's world ends up.

DestinationWhat it is for
Folder or sharea second disk, a USB drive, or a UNC path such as \\nas\backups, or a drive letter
S3-compatible bucketCloudflare R2, Backblaze B2, Wasabi, MinIO, Supabase, Filebase or AWS itself - signed by the panel with SigV4, no SDK and no extra runtime
Public file hostan anonymous upload (gofile) for handing one archive to somebody - the link is public, it expires on the host's terms, and it cannot be listed, pruned or read back

Details that matter:

Backups on other machines. The tab lists what each destination actually holds - name, size, when, and whether that archive is also on this machine - with per-file Download and Delete. An archive that exists only on a destination can still be downloaded: the panel fetches it back first and then serves it, so a world can be recovered from a bucket through a browser. If you click Download for an archive that is neither here nor reachable, the panel tells you which destinations hold it instead of a bare "not found".


Schedules

Schedules is a per-server list of timers and the actions they fire. Each schedule is one of two shapes:

The actions a schedule can fire are start, stop, restart, backup, command (with the command text) and update. A backup action may carry its own contents selection, which is how "player data and settings nightly, everything weekly" is one server with three schedules. Schedules are stored with the server (instance.json), including the last time each one fired, and the Overview shows the next time each is due.

What actually happens on the clock, precisely, because the difference bites:

Warning the players first. A restart or stop schedule can carry a warning time in minutes. When it comes due and the server is actually running, the panel announces it over the server's own console at the full wait, again at one minute and again at ten seconds - which is when players finish what they are doing - and then performs the action. A restart at 04:00 with no warning is the single most common complaint about a server; a schedule that wants one asks for it. If the server is not running there is nobody to tell and the action happens at once.

Two behaviours that exist so a cancelled restart cannot surprise you: editing or deleting a schedule inside its warning window cancels the pending action (the console records "Scheduled restart cancelled before it ran."), and a second warning for the same schedule replaces the first rather than queueing two restarts behind each other.

When a schedule fails it is not silent: the action's own outcome lands in the server's console (with the ! prefix for a failure) and, for the long ones, in Activity as a job. Jobs the panel knows how to redo offer Retry; a failed off-machine backup copy raises an alert. The failure of a schedule to fire (a stopped panel, a clock change) is not reported, because "it did not fire on time" has no event to report - which is the honest reason to keep the interval shape for anything you depend on, and to check the Overview's next-run time after a big clock change.

Server start/stop policy is not here. Auto-start on panel launch, auto-restart after a crash, the crash-loop budget and the plain-English crash banner are the server's own settings - see chapter 03 - and a schedule is the timer on top of them.


Storage accounting and alerts

Settings -> Storage answers "where did the disk go" without a file manager, and Settings -> Alerts is what tells you when a line is crossed.

Storage totals the data root - game servers, websites, backups, Java runtimes, the database, SteamCMD - together with the volume's free space and a breakdown of the largest children of a folder, biggest first, plus the loose files at the top level. Per server it shows three numbers, which is the reason the screen exists: the server folder, its archives, and the total - because a server whose world is 30 GB and whose archives add another 60 GB is a backup-retention problem, not a world problem. Measurements are cached for about twenty seconds so opening the screen does not walk tens of thousands of files, there is a Re-measure that throws the cache away, and a pathological tree reports itself as truncated rather than hanging the panel (the walk stops after 400,000 files).

Alerts push a webhook (Discord/Slack-compatible JSON) and/or email when:

The same event is held back for a configurable window (ten minutes by default) so a crash loop does not send the same line every few seconds; 0 means tell me every time. With mail in capture mode the messages are readable on the Email page, which is how the wiring can be proved without anything leaving the machine. Alert delivery, mail and the model configuration are chapter 10.


What keeps a server in its box

Two guarantees come from the way the panel starts a server, and both are worth knowing before you point the panel at a game it did not write.

The consequence for a game the panel did not write is that its launcher and its children share one memory budget and one lifetime with the panel. That is the intended trade: a server that outlives the panel is a server nobody is watching, and its port would be taken from the next launch. Stopping a server by hand from the panel gives Minecraft the game's own stop command and waits for the save (StopWaitMs, 40 seconds in this build); everything else gets a short grace period and is then force-stopped. Closing the panel window itself stops every server the same way, gracefully, rather than leaving them running with nothing watching them.


Gotchas worth knowing before they cost you a world


Files behind this chapter

FeatureSource
The console ring, its log file and rotation, reading lines backsrc/Instances.cs (PushLog, ReadLines, RecentLines, LogPath)
Saved console commands ("snippets") and their limitssrc/Instances.cs (SnippetList, UpsertSnippet, DeleteSnippet)
The log feed that distils a window before a model sees itsrc/LogFeed.cs, src/Program.cs (--feed)
Plain-English crash explanations, and the model pathsrc/Ai.cs (Explain), src/Unknowns.cs, src/Diagnose.cs
The file manager (browse, download, write, upload, new folder, rename, delete)src/Api.cs (FileOps, FileFail), src/Util.cs (Resolve)
The Files/Backups/Schedules/Console pagesweb/app.js
Backup contents, categories, matching rules and the manifestsrc/Backup.cs
Creating, listing, restoring, deleting an archive; retention per tagsrc/Instances.cs (Backup, ListBackups, BackupInfo, Restore, PruneBackups)
Destinations (folder, S3, public host) and their copiessrc/Instances.cs (Mirror, MirrorOne, RemoteList, Pull), src/Dest.cs
Schedules: the timer, the actions, the warning, next-runsrc/Instances.cs (TickSchedules, ExecuteSchedule, UpsertSchedule, NextRunMs, StartWarning)
Activity/jobs and one-click retrysrc/Jobs.cs
Storage measurement, breakdown, cache and truncationsrc/Disk.cs, src/Instances.cs (StorageRows)
Alerts (events, dedupe window, webhook/email)src/Alerts.cs, src/Store.cs
The memory cap and kill-on-closesrc/Sandbox.cs, src/Instances.cs (Sandbox.Attach, CapForRamMB)

Chapter 05. Websites and HTTPS

PlayPort hosts websites on the same machine as your game servers: static files, PHP, or a reverse proxy in front of an application you run yourself. Each site gets its own port, its own folder on disk, its own file manager and - if you want it - its own TLS certificate and a public URL without opening a single router port. This chapter is the app-hosting half of the product; the game-server half is chapter 03, MySQL for these sites is chapter 06, and the mail a PHP site can send is chapter 10.

Two facts decide almost every complaint in this chapter, so they are first:


What a site is

A site is a record in playport-data/sites.json plus a folder. The record (src/Sites.cs) holds exactly what the panel's pages show:

FieldMeaningDefault
namewhat the site is called in the list-
typestatic, php or proxystatic
portthe port its HTTP listener bindsfirst free site port (8081 upward)
targetthe upstream URL, for a proxy site-
rootserve an existing folder instead of the site's ownempty (use the managed folder)
ownerwhich account owns it (client accounts see only their own)the creator
enabledlistening or nottrue
https, tlsPort, domain, tlsModeTLS, see HTTPSfalse, 443, empty, le
tlsRenew, tlsRenewDaysautomatic renewal of Let's Encrypt certstrue, 30

The managed folder is playport-data/sites/<site-id>/. Importing an existing folder does not copy it: root points the site at the folder where it already is, and PlayPort serves it in place. That is the setting to use for a web app you develop elsewhere.

The site's own page has two tabs - Files and Settings - plus three buttons that matter in this chapter: Preview site (a disabled site, served on a throwaway loopback port so you can look at it before it is public), Open site ↗ (a link to the loopback URL, drawn inert with a reason when the site is switched off), and Diagnose external access.

Creating a site

Websites → + Host a website (or the + New server-style picker on a fresh panel):

  1. Name - what the list shows.
  2. Type - Static files, PHP (FastCGI) or Reverse proxy.
  3. Port - leave it empty and the panel takes the next free site port.
  4. Proxy target (upstream) - only for a proxy site, e.g. http://127.0.0.1:9000.
  5. Note - free text, shown beside the site.
  6. Import an existing folder (optional) - an absolute path. Empty means the site's own managed folder, and for a new static or PHP site the panel writes a small default page there so the site shows something immediately rather than a 404. Filling it in serves that folder instead, untouched.

If the folder you import is an un-built frontend project (it has a package.json, no dist/ or build/), PlayPort starts npm install + npm run build in the background and points the site at the build output - see Building a frontend project.

The site list's row actions are enable/disable and delete; deleting asks separately whether to delete the folder and all files (the Delete the site folder and all files checkbox in the danger zone). Deleting a site does not delete a folder you imported from elsewhere, because the panel never owned it.

Static sites

Static is the default and the simplest. Requests are resolved inside the site's folder (Util.Resolve refuses anything that would leave it), and:

Import ZIP… in the Files tab unpacks an archive into the site root (1 byte to 512 MB). A single common top-level folder is stripped automatically (dist-2023/ becomes the root), and every entry - files and folders - is checked for containment, so an archive cannot write outside the site.

Building a frontend project

Point a site at a source folder and the panel detects the framework from package.json and builds it. The table it uses is in src/Sites.cs:

Detected from package.jsonOutput folder it servesThe quirk it tells you about
vitedist-
nextouta normal Next build needs a Node server - only a static export (output: 'export') is servable here
nuxt.output/public, diststatic generation needs nuxi generate
svelte, @sveltejs/kitdist, buildSvelteKit needs @sveltejs/adapter-static to be static
solid-jsdist-
@angular/coredist, dist/<project>Angular nests its output; the panel takes the first folder with an index.html
react-scriptsbuild-
astro, @vue/cli-service, parceldist-
any Node project with a build scriptdist, build, outnames the script it found

The build runs with the Node runtime PlayPort already has: a bundled .runtime/node-*/node.exe next to the exe, then node.exe on PATH. npm install is retried with --legacy-peer-deps when strict peer resolution refuses a tree, and each step has a five-minute timeout. If no Node is found the site is not built and nothing is guessed: the site says it is an un-built source project and names the two ways forward (build it yourself and point the site at the output, or install Node.js). A build that finishes yet finds no index.html in any expected output folder is an error, not a silent "serving nothing".

This builds frontends, not backends. A Django/FastAPI/Go/.NET/Express process is a program you run and put a reverse-proxy site in front of - see Hosting an application you run yourself.

PHP sites

A PHP site is served by the official php-cgi.exe, downloaded by the panel on first use and spoken to directly over FastCGI. There is no IIS, no nginx, no Apache and nothing to configure:

  1. Create the site as type PHP (FastCGI). The panel writes an index.php default page.
  2. Put files in it (the Files tab, an Import ZIP…, or by serving an existing folder).
  3. The first request downloads PHP (~35 MB) and starts the worker: expect that visit to take a moment, after which the page reports the PHP version it is using.

What the panel installs and how it runs, so you can reason about your site:

An important detail read from the FastCGI environment: SCRIPT_FILENAME is the real absolute path of the script, while DOCUMENT_ROOT is the site's managed folder (playport-data/sites/<id>). For a site serving its own folder the two agree; for a site pointed at an imported folder they do not, and a framework that builds absolute paths from DOCUMENT_ROOT can disagree with the file that ran.

Hosting WordPress or another real app: upload or import it into the site folder, then use the MySQL database panel on the same page for its database (chapter 06) and fill in the app's own configuration as its documentation says. The site serves index.php automatically.

The honest limit. A PHP site runs with the panel's own privileges and open_basedir is not set, so PHP code in one site can read any file the panel can read - including the panel's own data folder. Treat a website you host for somebody else as code you are choosing to trust, or host it on a separate panel. The contained fix (a per-site open_basedir plus upload_tmp_dir and session.save_path inside the site) is written down in the repository's README.md under Honest limitation: hosted PHP is not filesystem-isolated.

Reverse-proxy sites

A Reverse proxy site forwards every request to its Proxy target and returns the answer. This is how a website becomes the public address of an application: the app listens on a local port, the site listens on the site port, and the site does the forwarding.

What it forwards: the method, the path and query, the request body, the User-Agent and Content-Type, and an X-Forwarded-For header (the visitor's address, or an existing one if the request already carried it). It does not follow redirects from the upstream (AllowAutoRedirect = false), it waits up to 60 seconds to connect and up to 120 seconds to read or write, and an upstream that cannot be reached answers 502 Bad Gateway with the target named, which is what a visitor sees while your app is restarting.

What it does not do, and this is worth knowing before you put an app behind it (SiteApp.Proxy in src/Sites.cs): the response the visitor receives keeps the upstream's status code, its body and its Content-Type, and no other response header. Set-Cookie, Location, Cache-Control, CORS headers and the like are not passed back, and the request's Host header is not rewritten. In practice that means an app that keeps a session with a cookie will not keep it through this proxy, and an app that answers 302 will have its redirect turned into a body with no Location. Nothing here rewrites HTML or rewrites URLs. If your app needs those headers, put a real reverse proxy (nginx, Caddy) in front of it, or serve the app as PHP or static output and let PlayPort serve it directly.

Hosting an application you run yourself

The pattern that works today:

  1. Add the program under Game Servers as a Manual server (or as any instance type that knows how to start it). It gets a console, a log, a working directory and auto-restart - chapter 03.
  2. Note the port it listens on.
  3. Create a Reverse proxy site whose target is http://127.0.0.1:<that port>.

Then the website is the front door and the process is managed like everything else in the panel.

About the App behind this website card. A site's Settings page draws a card offering Run this folder as an app, which promises to install, build and run the folder's project as a managed process. Read against this checkout: the panel's own site API (src/Api.cs) implements the actions toggle, files, tls, preview, importzip and diagnose - there is no app action, and no site object carries the appInstance/appKind fields the card reads. So in this tree that button answers unknown action 'app' and the card stays in its "what this could do" state. Use the Manual server plus reverse-proxy pattern above until that route exists.

Domains, ports and the network path

Four things have to line up before a stranger can open http://your-domain/, and they line up in this order:

  1. The site's listener must bind an address the network can reach. Site bind defaults to 127.0.0.1 (loopback only). Change it in Settings → Websites - an empty value means all interfaces - and the panel rebinds every enabled site for you. This is the single most common reason "it works on the machine and nowhere else".
  2. Windows must let the port through. When the panel runs as administrator it adds an inbound firewall rule itself as each listener binds (Util.EnsureFirewallRule). Without admin rights, add-firewall.ps1 does the same for ports 80, 8080 and 8081 - and only those, so a site on any other port needs its own rule (New-NetFirewallRule), or run the panel elevated once.
  3. http.sys must allow the prefix if you bind a site (or the panel) to all interfaces without running as administrator. add-urlacl.ps1 reserves the prefix forms for ports 80, 8080 and 8081 (+, *, 127.0.0.1, localhost, and a LAN IP) for everyone. Two notes before you run it: it is Windows-specific, and the LAN IP inside it is a fixed 192.168.1.225 - edit that line, or add your own address, rather than assuming the script knows your network.
  4. Your router must forward the port to this machine's LAN address if the traffic comes from the internet. That is the one step no panel can do for you (and the reason Cloudflare tunnels exist - see Public access without opening ports).

Sites do not need a URL ACL for HTTPS. HTTPS is served by the panel's own raw socket server (TlsHost in src/Tls.cs), not by http.sys, so it works on any port with any certificate and needs no administrator reservation - which is why a tlsPort of 8443 is a perfectly normal thing to use.

Diagnose external access answers this chain for you instead of leaving you to guess. It reports the site's status and message, the detected LAN address, the machine's public IP (looked up live), the current Site bind, and a list of concrete hints: the site is not running, the port is shadowed by another program's HTTP reservation, the bind is loopback-only, another site owns that HTTPS port, the firewall has no rule for the port, and what to forward on the router or which tunnel to use instead.

Port collisions you should know about

HTTPS

Everything about TLS is on the site's Settings page, in one panel, because a certificate is only useful when HTTPS is switched on with it.

  1. Domain (TLS / PHP) - the name the certificate is for, e.g. example.com. Let's Encrypt needs this; a self-signed certificate will fall back to localhost if you leave it out.
  2. HTTPS (also serves TLS) - tick it, and set HTTPS port (default 443). The site keeps serving HTTP as well; both listeners run at once.
  3. Pick one of the four buttons:
ButtonWhat it doesWhen to use it
Get Let's Encrypt certissues a real certificate through the panel's built-in ACME v2 client, using an http-01 challenge served at /.well-known/acme-challenge/<token>a domain you control, with port 80 reachable from the internet
LE staging (test)the same against Let's Encrypt's staging directorytrying the flow out without spending your production rate limits
Self-signedgenerates a certificate and its key in-process, valid 365 days, no external toolLAN, an IP address, offline machines - the browser will warn once, and the site's banner says so
Upload PFX…installs a .pfx/.p12 you already have; it asks for the passworda certificate from anywhere else

The certificate card above the buttons shows the domain, whether the certificate is installed (or that the key is missing), its expiry date and its issuer.

Automatic renewal applies to Let's Encrypt certificates only, and that is a deliberate rule: a self-signed or uploaded certificate is never replaced behind your back. With Renew automatically on, the panel renews inside the renew when this many days remain window (30 by default, capped at 60), never more often than once every six hours, and writes what it is doing to Last status - including the honest reason it is waiting (HTTP-01 requires this site's HTTP port to be 80) when the site is not on port 80. Every attempt, success and failure is logged.

Where the certificate lives: playport-data/sites/<site-id>/ssl/ - cert.der and privkey.xml for the panel to load, fullchain.pem as the real chain Let's Encrypt returned, meta.json with the issuer, the domain and the expiry. The Let's Encrypt account key is playport-data/acme/account.json; if that file cannot be read, the panel keeps a copy aside and says so rather than silently registering a new account (Let's Encrypt counts accounts per address).

Getting a LetsEncrypt certificate needs the CA to reach port 80 on the domain - which means the site's HTTP listener has to be the one answering on 80, on a machine the internet can reach, before you press the button.

Public access without opening ports

Tunnels put a site on the internet without forwarding anything, because everything in PlayPort already listens locally.

Install it once: Settings → Cloudflare tunnels → Install cloudflared. The panel downloads the official Windows binary (cloudflared-windows-amd64.exe, about 60 MB, from Cloudflare's own GitHub release) into playport-data/cloudflared/. The site's card offers the same install button when it is missing.

Quick tunnel (per site): on the site's Public access - Cloudflare tunnel card, start the tunnel. Within seconds the card shows a live https://<random>.trycloudflare.com address. It needs no account, no DNS and no open port - and it is temporary: anyone with the URL can reach the site until you stop the tunnel or close the panel, the hostname changes every time you start it, and the panel kills every quick tunnel when it exits.

Named tunnel (stable hostnames): Settings → Cloudflare tunnels → Generate named-tunnel config writes playport-data/cloudflared/config.yml routing every enabled site that has a domain to its local port, with a commented-out entry for the panel itself. The one-time setup is on you, in a terminal, and the generated file repeats it verbatim:

cloudflared.exe tunnel login
cloudflared.exe tunnel create playport
cloudflared.exe tunnel route dns playport <domain>     # once per domain
cloudflared.exe tunnel --config config.yml run playport

The other tunnel is playit.gg, which fronts raw TCP and UDP - what a game port needs rather than a website - and it is documented in chapter 09. If you want the panel itself reachable from outside, both tunnels can publish the panel's own port; put something in front of it, because its login is not a firewall.

Where a site's data lives

PathWhat it holds
playport-data/sites.jsonthe list of sites (the panel keeps a damaged copy aside rather than starting with no websites)
playport-data/sites/<site-id>/a site's own folder: its files, and its ssl/ when it has a certificate
the folder you importeduntouched, where it already was
playport-data/php/php-cgi.exe, the base php.ini and the per-site php-<id>.ini
playport-data/cloudflared/the cloudflared binary and the generated named-tunnel config.yml

Backups of a site are part of the backup system described in chapter 04: the Files tab is the same file manager as a game server's, and a site's folder can be backed up, zipped and copied off the machine the same way.

When a site does not answer

What you seeWhat it isWhat to do
503 Service Unavailable from Windows on every request, while the site says runninganother program's http.sys reservation owns the port, so Windows answers before PlayPort doesgive the site another port, or as administrator: netsh http delete urlacl url=http://+:<port>/
Works on this machine, times out anywhere elsethe site is bound to loopbackSettings → Websites, clear Site bind (all interfaces), and open the port
error: HTTPS enabled but no certificateHTTPS is ticked with no certificate installedissue or upload one, or switch HTTPS off
error: HTTPS port N is already used by another sitetwo sites share a tlsPortchange one site's HTTPS port
error on bind, with a plain sentencethe port is taken, or Windows refused the bindthe site's message names the port; the raw error is in the panel log
A visitor sees a plain 500 page with a reference like A1B2C3a request threw - the panel shows the visitor nothing about the machinethe site's page carries the same reference with the owner's sentence, and the whole error is in the panel log under that reference
Your app behind a proxy loses its login, or a redirect turns into a blank pageonly Content-Type comes back from the upstream (see Reverse-proxy sites)keep session state in the URL, or put a full reverse proxy in front
502 Bad Gateway with your target namedthe upstream is not listeningstart the app; check its port; the proxy restarts nothing for you
404 on a route that works in the appthe SPA fallback needs a root index.html that pulls in a script, and asset-like paths are never rewrittencheck the build output is what the site actually serves
PHP site answers 500 on the very first visit onlynow fixed by waiting for the worker's port - if it still happens, the worker is not startingthe site message names the reason (Could not start php-cgi: …); the panel log has the whole error
A certificate did not renewrenewal only rewrites Let's Encrypt certificates, needs port 80, and retries at most every six hoursread Last status on the site; the panel log has the raw ACME error

The app-hosting API, in one table

Everything the pages do is an API call, so a script can do it too. Authentication and the full route reference are chapter 11; these are the site routes (src/Api.cs):

Method and pathWhat it does
GET /api/siteslist the sites you may see
POST /api/sitescreate one (name, type, port, target, root, TLS fields)
GET, PUT, DELETE /api/sites/<id>read, edit, or delete one ({"wipe":true} deletes its folder)
POST /api/sites/<id>/toggleenable or disable
GET, PUT /api/sites/<id>/files?path=…the file manager
PUT /api/sites/<id>/importzipupload a ZIP to unpack (1 byte to 512 MB)
POST /api/sites/<id>/preview, DELETE …/previewserve a disabled site on a throwaway loopback port
POST /api/sites/<id>/tls{"op":"le"} or {"op":"self"}, plus {"dir":"stage"} for staging
PUT /api/sites/<id>/tls?password=…install a PFX (the request body is the file)
GET /api/sites/<id>/diagnosethe external-access report above
GET /api/tunnelthe cloudflared binary's state and every running quick tunnel

Files behind this chapter

FeatureSource
The site record, its listener, static/PHP/proxy request handling, the SPA fallback, framework detection, the ZIP import, preview, certificate renewal, external-access diagnosissrc/Sites.cs
Servicing HTTPS on any port without an administrator URL ACLsrc/Tls.cs
The FastCGI client, the PHP download, the generated php.ini, the per-site workersrc/Fcgi.cs
Let's Encrypt (ACME v2, http-01), self-signed certificates, where certificates are storedsrc/Acme.cs, src/Der.cs
Cloudflare tunnels: the binary, quick tunnels, the named-tunnel config.ymlsrc/Tunnel.cs
The site pages and their labelsweb/app.js, web/ops.js
The firewall and URL-ACL helpersadd-firewall.ps1, add-urlacl.ps1, Util.EnsureFirewallRule in src/Util.cs
The live proof that a PHP site serves POST and GET and that a self-signed HTTPS site answerswebtest.sh

Chapter 06. Databases

PlayPort runs one managed MariaDB 11.4 server (MySQL-compatible) for the whole machine, and hands out one isolated database + user to any website or game server that needs one. This chapter covers installing and starting that server, creating a database, giving its credentials to a site or a game server, where the credentials live, what backups do and do not cover, and every failure the panel knows how to explain.

If you only want the core plugin to keep its player records in SQL, you do not need most of this: that is one button on Plugins → PlayPort Core → Use a database, and it is described under giving a game server a database. Nothing else in this chapter is required for it.

The ports and the data-root layout this chapter refers to are summarised in the manual's appendices; the other chapters are linked where a topic crosses over.


What the panel runs

PathWhat it holds
playport-data/mariadb/mariadb-11.4.5-winx64/bin/MariaDB's own programs: mariadbd.exe, mysql.exe, mariadb-install-db.exe
playport-data/mariadb/data/every database's actual data - this is the folder that grows
playport-data/databases.jsonthe panel's record: every database it handed out, and its root password

Where you meet it in the panel:

ScreenWhat it does
Settings → Componentsthe MariaDB row: Install (~90 MB), Start, Stop - one download button in one place
Settings → Databases (admin)every database the panel has issued, with a Drop per row
the dashboard's Database rowthe one-line state: running on port 3306, how many databases, or why not
a site's page → MySQL databasecreate a database for that site and read its credentials
a server's page → MySQL database (manual)the same for a server, for a plugin or tool that is not the core plugin
Storage (Settings)counts everything under mariadb/ as database bytes

Installing and starting it

  1. Settings → Components → the MariaDB row → Install. This is a background job ("Install MariaDB" on the Activity page), so the button returns immediately; the panel re-reads the state until the server is up.
  2. If you are on a site's or a server's own database panel and there is no server yet, an admin gets the same button there - the panel does not send you to another page to start a download.
  3. Press Start (or install, which starts it) and the dashboard's Database row turns green: "MariaDB is running on port 3306."
  4. Stop is on the same row. The panel also stops it cleanly on shutdown (a SHUTDOWN command first, then a kill if it does not go), so a normal exit does not corrupt a data folder.

The install does three things in order: download and unpack the zip, run mariadb-install-db.exe --datadir=… to create the data folder, then (on the first start, because it cannot be changed while the server is down) generate a random root password, set it, and write it to databases.json. That root password is the panel's own; you should not need it, and it is a secret (see where the credentials live).

The port is 3306 and comes from the panel's own record (databases.json), not from a settings field: there is no supported way to move MariaDB to another port in this panel. If something else already holds 3306, read that failure below - the panel tells the two cases apart.

Creating a database and its user

From a site's page or a server's page (or POST /api/db/issue, which is what both panels call):

  1. Open the site or server and find the MySQL database panel (a server's is titled MySQL database (manual) and says which case it is for).
  2. Press Create database. The panel runs, in one transaction against MariaDB:
   CREATE DATABASE IF NOT EXISTS `<name>` CHARACTER SET utf8mb4;
   CREATE USER IF NOT EXISTS '<name>'@'localhost' IDENTIFIED BY '<random>';
   CREATE USER IF NOT EXISTS '<name>'@'%'         IDENTIFIED BY '<random>';
   GRANT ALL PRIVILEGES ON `<name>`.* TO '<name>'@'localhost';
   GRANT ALL PRIVILEGES ON `<name>`.* TO '<name>'@'%';
   FLUSH PRIVILEGES;
  1. The credentials appear in the panel: host 127.0.0.1, port 3306, user, pass. They stay visible on that page afterwards (the panel stores them), so you can come back for them.

The naming is not arbitrary - it comes from Db.cs:

What the user may do. GRANT ALL - but only on its own database. It cannot read another database, cannot create or drop databases, and cannot touch MariaDB's own tables. That is the whole point of the design: "Creates an isolated database + user with access to that database only - safe to hand the credentials to a client" (the panel's own words on the create button).

Who may do it. Admins and moderators can create a database for any site or server. A normal user can only do it for a site or a game server they own; the API answers Not your site. / Not your instance. otherwise. Dropping follows the same rule (an admin, or the owner of that database).

A press creates exactly one database. A server may legitimately have more than one - one per plugin or tool that wants its own - and each is listed on that resource's panel with its own Delete button.

Where the credentials live (and what that means)

Everything is in one file: playport-data/databases.json.

{ "rootPass": "pp-…", "port": 3306,
  "issued": { "<resource id>": [ { "db": "gs_hub_1a2b", "user": "gs_hub_1a2b",
                                   "pass": "pp-…", "port": 3306, "kind": "instance",
                                   "resource": "<instance id>", "owner": "<user id>" } ] } }

Three things follow, and they matter more than the JSON does:

  1. This file is the only place a database's password exists. The panel shows a password when it creates it and stores it here; MariaDB keeps only a hash. If the file is lost and you have no backup of it, the credentials cannot be recovered - the panel says so in as many words when it cannot read the file: "PlayPort started without its record of the databases it handed out and of the MariaDB root password it uses." Include it in whatever you back the panel up with.
  2. The panel repairs credentials rather than duplicating them. Linking the same server again, or restoring an older databases.json, does not mint a second database: the panel re-issues that user, its password and its grants, and then proves the credentials log in (SELECT 1 as that user) before it writes anything. The data is never touched - it is an account operation on a database that already exists.
  3. Any panel login is close to full access to every database on the machine. The status the panel's own Databases page reads includes the MariaDB root password, because that is how the panel reconnects to it. Treat databases.json as a secret file, and treat panel accounts accordingly (see chapter 11 for the security model and roles).

Giving a website a database

  1. Open the site, scroll to MySQL database, press Create database.
  2. Copy the host, port, user and pass into the site's own configuration - the panel says it plainly: "Use these in your site config (e.g. WordPress wp-config.php) or the game server's database plugin."
  3. The panel does not edit the site's files. There is no automatic wp-config.php writer; a PHP or static site finds its credentials wherever its own installer put them. If you install WordPress, the installer asks for exactly these four values.
  4. If you own the site, you see its databases. An admin sees every database on the machine under Settings → Databases.

Sites and mail both live on this machine and talk to MariaDB over loopback, so nothing has to be opened in the firewall for a site to use its database. Website hosting itself is chapter 05.

Giving a game server a database

There are two different questions here, and the panel keeps them apart on purpose:

1. A database for the PlayPort core plugin (the common case, and the only one that needs no copying). On the server's page: Plugins → PlayPort Core → Use a database. That one button:

What the plugin then does with those tables - and the /playport sql commands that report the routing it is actually running under - is chapter 08; the panel side of it is chapter 07.

This route needs a server the core plugin can run on at all: a Bukkit-family server (Paper, Purpur, Spigot) with the plugin installed. Otherwise the panel says why instead of offering the button - "The PlayPort core plugin only runs on a Bukkit-family server (Paper, Purpur, Spigot)." or "This server does not have the PlayPort core plugin installed yet."

2. A database for a different plugin or an external tool. The server page's MySQL database (manual) panel does only that: it hands you credentials to paste wherever they belong. The panel's hint says exactly this when you are there, and a database created that way is reused by the core plugin's button later rather than duplicated - both are keyed to that server.

Seeing what exists, and dropping a database

For scripts, the routes behind these screens are GET /api/db (status: installed, state, message, port, how many databases), POST /api/db with {"op":"install"|"start"|"stop"} (admin only; install returns at once and runs as a named job), GET /api/db/all (admin), GET /api/db/owner/<ownerId>, POST /api/db/issue with {"kind":"site"|"instance","id":…,"name":…} and DELETE /api/db/<database>. Chapter 11 has the full API reference, including what each one requires.

Backups and restore

This is the part most often assumed wrongly, so it is stated plainly:

  playport-data/mariadb/mariadb-11.4.5-winx64/bin/mariadb-dump.exe \
      --host=127.0.0.1 --port=3306 -u <user> -p<pass> <database> > <database>.sql

(MariaDB 11 ships that program under the name mariadb-dump.exe; it is the tool MySQL calls mysqldump. Check the bin folder for the exact name in your copy.) Restoring is the mirror image with mysql.exe.

When something goes wrong

Every sentence below is one the panel itself writes - they are the texts to match a screen against.

Install failures (Util.PlainDbError, chosen by which class of fault it was):

What you seeWhat it means and what to do
"MariaDB could not be downloaded - the panel fetches about 90 MB from archive.mariadb.org, and this machine could not reach it."no internet, DNS, a proxy or a scanner in the middle. Fix the connection or the blocker, install again
"The download server for MariaDB answered with an error."it reached the server and the server said no - usually temporary; retry in a few minutes
"There is not enough free space to install MariaDB (about 90 MB to download, and roughly 500 MB once it is unpacked)."free space on the drive the data folder is on
"MariaDB is not fully installed in <dir> - the download was incomplete, or something is already sitting where MariaDB's folder goes."delete the mariadb folder and mariadb.zip in the panel's data folder, then install again - this is the class a retry alone cannot fix
"Windows blocked MariaDB's files in <dir> (access denied)."another MariaDB, antivirus holding the folder, or a data folder the panel cannot write to
"A MariaDB file is locked by another program in <dir>."stop the other MariaDB (or the scanner), then install again
"MariaDB's data folder could not be prepared in <dir>."mariadb-install-db exited with an error; the log has its message. Check the folder is writable and has ~500 MB free

Start failures:

When the port is already taken. The panel asks the port before it starts anything, and the answer decides what you are told:

A forgotten root password. It is in playport-data/databases.json (rootPass). If the file is gone, reset it once as described above with --skip-grant-tables.

A database that grows past the disk. Its bytes are inside playport-data/mariadb/data/, counted by Storage; the panel's disk alerts fire on the drive, not per database. Dump what you need and prune, or move the whole panel data root to a bigger drive (see chapter 01 for --data).

Connecting from another machine. You cannot, and that is deliberate: the panel starts MariaDB bound to 127.0.0.1 and there is no setting that changes it, because these databases exist for the sites and servers on this machine. If you need SQL from elsewhere, run your own database server for that. If you need to look inside these, use MariaDB's client programs on this machine - a client started from inside the panel's own folder can also create and inspect tables directly.


Files behind this chapter

FeatureSource
the managed MariaDB: download, unpack, mariadb-install-db, start/stop/adopt, GRANT/DROP, the per-owner ledger, databases.jsonsrc/Db.cs
linking a game server's database (the JDBC driver, db-link.txt, share groups, the two-database rule)src/SqlLink.cs
the routes: /api/db, /api/db/all, /api/db/issue, /api/db/owner/<ownerId>, DELETE /api/db/<db>, permissionssrc/Api.cs (DbRoute)
the plain-language install/start failures and the fault classes behind themsrc/Util.cs (PlainDbError, MachineFaults)
the dashboard's Database rowsrc/Health.cs
the Storage breakdown's database figuresrc/Disk.cs
the admin Databases panel, the site/server MySQL database panel, credentials display and drop confirmationsweb/app.js (loadDbAll, loadDbPanel)
the Components row that installs/starts/stops MariaDB, and the install job it followsweb/ops.js, src/Jobs.cs
the credential record's save-failure wording ("the database record")src/Util.cs (SaveFail), src/Db.cs (SaveIssued)

Chapter 07. The core plugin, from the panel

The core plugin is one jar - PlayPortCore - that is a survival server, a hub, a prison or a lifesteal SMP depending on which template profile it is given. It is not downloaded from anywhere: the panel carries it inside the exe, and the Plugins tab of any Paper-family server can install it in one press. This chapter is the panel half of that story - installing it, updating it, the settings form, the profile picker, and the eighteen editor surfaces that write core content. What the modules do in game, their commands, their config.yml and their permissions are chapter 08; the module list itself is Appendix D.

The jar the panel already has

There is nothing to fetch and nothing to upload. The exe embeds the built jar under the fixed resource name playport-core.jar, so the panel never has to know the plugin's version to find it. Two details matter to an operator:

A build made from a checkout that has never run build-plugin.sh has no jar inside it. The panel says so in one sentence - This build of PlayPort has no core plugin inside it - and tells you to run build-plugin.sh and rebuild the panel, or drop playport-core.jar next to the exe. Nothing else breaks; the rest of the product does not need the plugin.

Installing it into a server

  1. Open the server and choose the Plugins tab.
  2. Find the PlayPort Core card. It is drawn as a card rather than a search result, tagged included, because there is nothing to search for and nothing to download.
  3. If the build ships template profiles, the card carries a profile picker beside the button - the template this server should run (see below). Leave it alone to accept the server's own answer.
  4. Press Install (on a server that already has it, the same button reads Apply).
  5. Restart the server. The install writes the jar and the link files; a running server loads a plugin only at start, so the card and the console both say Restart the server to load the core plugin.

What the install needs: a server that loads plugins. The card refuses a server that cannot with the reason rather than failing silently - Vanilla loads neither plugins nor mods (Create a Paper or Purpur server for plugins), a NanoLimbo lobby loads nothing at all, and a Fabric/Forge/NeoForge server takes mods, so the core plugin - a Bukkit plugin - is refused there. A manual/process server gets a sentence saying the panel does not know what runs inside it. The install is POST /api/instances/<id>/core, and the card's state is GET on the same route; the panel is the only caller.

Three rules the installer keeps, because getting them wrong is how a plugin update eats somebody's work:

Which template a server runs

A template is a configuration of the one jar, not a list of downloads, and that is the whole design: pkg/profiles/*.yml are the shipped templates the panel installs from. The profile in effect is resolved in this order, and the order is the point - the operator's intent beats the pack's implication:

  1. what was picked for this server (its stored CoreProfile),
  2. otherwise what the pack it was created from implies - a pack is core-<profile> and its only item is the bundled jar, so a prison pack lands on the prison template without anyone choosing it twice,
  3. otherwise nothing, and the plugin runs on its own defaults. That is the honest answer rather than a guess, and the log says it in those words: no template profile, so the plugin runs on its own defaults (name this server after a gamemode, or pick one, to give it a template).

Servers made before templates existed resolve through the older paper-* pack ids, and a name like Survival Hub still resolves to the hub template, so the template is refused while nothing has been built yet rather than leaving half a network behind.

The profile picker writes through the same install path: choosing a template and pressing Apply re-installs the jar with that profile, and an empty choice means "what this server's template would choose".

Updating it

There is no separate updater, and there does not need to be. The card always shows the version the panel is shipping, and when the copy installed on that server is a different version it adds a tag reading this server has <version> - which is what "an update is available" looks like here. Press Install/Apply on that card and:

Updating the panel is what changes the shipped jar: a new PlayPort.exe carries the core plugin its build produced, and every server then reports the older version until it is updated - see chapter 01 for getting a new panel in place.

The card can only tell you what the server says, and the server only says it if the two are linked. The link is written by the install and it works in both directions:

DirectionHowWhat it carries
panel → pluginpanel-settings.txt in the plugin's folderthe settings an operator saved in the panel. The plugin watches the file's timestamp and applies it live. It is the top layer, above the operator's config.yml, because a value typed into the panel's form has to be the value in effect or the form is lying
plugin → panela small JSON status report, posted to the panel's POST /api/plugin/report with a per-server tokenthe profile, the module states, the list of settings the panel may edit, the content counts each editor needs, any warnings, and the last profile export

Both files are deliberately plain text - key=value for settings, JSON for the report - and every failure in the link is soft. No panel, no token file, a refused connection, a malformed line: the plugin runs exactly as it did before, because none of it is load-bearing for the game server. The only trace is a line in the console and a panel: section in /playport status.

That softness has one visible consequence, and it is the sentence you will see most often in this chapter: a card that has nothing to show says so instead of showing an empty form. Before a server has ever reported, the settings button and the whole editor row are present but inert, each with its own reason - PlayPort Core has not reported to this panel yet, so there is nothing to read. Start the server once (or give it a minute if it is already running) and this opens.

The settings surface

Settings on the editor row is the plugin's own catalogue, not a panel-invented list. The form is built from the keys the plugin reported, so a setting added or renamed in the plugin turns up in the form without the panel being touched, and a setting the panel has never heard of cannot appear as a dead field. Saving it writes the overlay panel-settings.txt, and the plugin applies it live - which is why a change here does not need a restart, while a change made by hand in config.yml takes effect on the plugin's own reload or the next start.

Two kinds of switch live in that catalogue, and both are worth knowing by name because every module page in chapter 08 refers to them:

If the form is empty on a server that has reported, the card says what to do rather than leaving you to guess: run /playport check in the console, which names anything the plugin could not read. The same command is the authority on plugin-side config problems - see chapter 08.

The editor row

Everything that writes core content is on one row of the card, and the rule that draws it is worth stating because it is what makes the row trustworthy:

On a server with the plugin installed the row is always drawn, every editor is on it, and an editor the server cannot serve is inert - present, greyed, and saying why when it is pressed.

That replaced a row of eighteen "show this button if" ternaries, where this server is not running that module and this panel cannot do that were the same picture. The reason an inert button gives names the switch: The jobs module is off on this server and nothing is loaded in it, so there is nothing to edit here. Turn on modules.jobs.enabled and restart the server.

Every editor opens from the same payload - the plugin's report plus the installed-file list - and every one of them writes a file the plugin merges with its own config within a minute. None of them invents a format: what is written is the same shape config.yml holds, so a record made in the browser and one typed into the file are the same thing to the server, and the in-game commands cannot tell which of the two they are reading.

ButtonWhat it editsThe file it writes
Settingsthe plugin's own settings catalogue, including the module switchespanel-settings.txt
NPCsthe dialogue and dress of the NPCs an owner edited in the browserpanel-npcs.txt
Modelsmodels.items.<id> (Blockbench models and what they are worn as)panel-models.yml
Skillsarena.skills.<id> (what a boss ability does)panel-skills.yml
Dungeonsdungeons.dungeons.<id>, dungeons.themes.<id>, dungeons.loot.<id>, dungeons.waves.<id>, dungeons.messages.<id>panel-dungeons.yml
Questsquests.defs.<id> - a goal, a gate and a rewardpanel-quests.yml
Kitsess.kits.list.<id> - items, permission, cooldown, one-timepanel-kits.yml
Spawnersthe spawner tables: spawners.prices, spawners.charges, spawners.spawnable, spawners.tools.crowbars, spawners.traitspanel-spawners.yml
Cratescrates.<name>.<key> - a weighted pool and the rules around it (limits, key name, window, announcement, sound, firework)panel-crates.yml
Drop tablesarena.drops.<table> and arena.bosses.<id>.drops - what a boss is worthpanel-drops.yml
Enchantscustomench.enchantments.<id> - name, colour, max level, rarity, item groups, effectspanel-enchants.yml
Menusui.pages.<id> - a title, rows, and a slots.<n> map of buttons with their actionspanel-uis.yml
Alertsthe guard module's cheat alerts, read-only, with a badge for the ones nobody has looked at(none - it reads)
Fishingthe fishing module's own records as its report carries them(see note below)
Cookingcooking.stations.<id> and cooking.recipes.<id> - a station block, its recipes, their stages and rewardspanel-cooking.yml
Jobsjobs.list.<id> - name, icon, permission, max level, gate, pay table, levelspanel-jobs.yml
Worldswhat the server already owns (read-only) and a short add-only list of worlds it has been asked forpanel-worlds.yml
LuckPermsthe perms surface: what the module found, on a server that has LuckPerms(none - it reads)

Four notes on that table, each of which changes what you can expect:

Save as profile… is the other direction of the profile picker and sits beside it. It asks the plugin to write this server's currently merged settings out as profile-<name>.yml - which is why the button is inert until the plugin has reported: a profile is the merged, layered config, and that exists only inside the running server. The card then reads the last export back from the server (the file it wrote, how many settings it carried, how much library content it deliberately left out, and, when it was refused, the sentence saying why), so a page reopened tomorrow says the same thing.

Player records: files or a database

Under the core card is a Player records card that answers two questions that look like one. A file or a database is one press. Which records does the whole network share is asked once per group of records, because one answer for a whole server cannot be right: a balance wants to be the same everywhere a player plays, a rank belongs to the ladder that gave it out, and lifesteal hearts belong to the world that took them. The seven groups are money, playtime, hearts, rank, combat, claims and identity, and the module list beside them comes from the plugin's own report (panel-sql.txt), never from a list in the panel - so a module that declares a new group of data shows up the next time its server loads.

A server with no database reads as one sentence about a file, which is the whole truth about it: The plugin's own players.yml - nothing here is shared with another server. Nothing a module keeps for itself is on this list at all: islands, plots, chests and arenas are per-server files, and no switch here reaches them. The database itself is chapter 06.

Packs: the plugin install plus the settings a fresh server needs

Installing the jar is only half of "plug and play", and the panel's starter packs are the other half. A pack (src/Starters.cs) is a named set of items whose one plugin item is playport-core, plus the non-download half in src/Presets.cs:

The same install also writes panel-worlds.yml when the server was created with worlds it should own, so the plugin makes them at first start rather than the panel guessing. The pack's note on the Plugins tab is where the remaining human steps are listed; read it before you hand the server to players.

What goes wrong

What you seeWhat it isWhat to do
The card is there but every editor is inert, saying the plugin has not reportedthe plugin is installed but has never run, or ran without a linkstart the server once; if it is running, give it a minute
this server has <version> tag on the cardthe installed jar is not the one the panel shipspress Install and restart the server
This build of PlayPort has no core plugin inside ita panel built from a checkout that never ran build-plugin.shrun build-plugin.sh, rebuild the panel, or drop playport-core.jar next to the exe
The install is refused with a sentence about plugins or modsthe server does not load plugins (Vanilla, limbo, Fabric/Forge)create or convert the server to Paper/Purpur
A settings change did not take effectthe plugin has not applied the overlay yet, or the setting is one the plugin reads once at bootwait for the next poll; if it is a boot-time value, restart the server
An editor's save was accepted but nothing changed in gamethe file is merged within a minute, and content a module never reloads may need a restartcheck /playport check, then restart
A hand-made playport-core.jar sits beside the exea loose file wins over the embedded copydelete it, or accept that every install uses it
A pack's plugin looks installed but does not runthe server was started before the install finished, or the jar is older than the server's Javarestart; check the console for the plugin's own refusal

Files behind this chapter

FeatureSource
The bundled jar, version, install/update, profiles, the three install rulessrc/Core.cs
Plugin/mod search and the install path the core plugin shares with Modrinth itemssrc/Plugins.cs
Starter packs and their pack ids (core-<profile>, core-only)src/Starters.cs
server.properties tuning and the one-time gamerulessrc/Presets.cs
Player records in a file or a databasesrc/SqlLink.cs
The panel's core routes (/api/instances/<id>/core, /core/npcs, /plugins/core/luckperms) and POST /api/plugin/reportsrc/Api.cs
The link files, the settings overlay, every panel-* content filepkg/src/ppcore/Panel.java, pkg/src/ppcore/ConfigVocab.java
The module switches and feature flagspkg/config.yml, pkg/PARITY.md
The Plugins tab, the core card, the editor row, the database card, every editor modalweb/app.js, web/core-kits.js, web/core-crates.js, web/core-drops.js, web/core-dungeons.js, web/core-editors.js, web/core-enchants.js, web/core-jobs.js, web/core-quests.js, web/core-spawners.js, web/core-uis.js, web/core-worldgen.js, web/core-cooking.js

Chapter 08. The core plugin, in game

This chapter is the core plugin as an operator meets it inside a running server: what it is, how its modules are switched and configured, the commands you type, the permissions it wants, what <command> check actually proves, and what it writes into the server's folder. Installing and updating the jar, and the panel's own Plugins tab, are chapter 07.

Read ../MANUAL.md first if you have not: it has the conventions this manual uses and Appendix D, which is the registry of every module id and label the plugin reports. This chapter deliberately does not repeat that table.


What it is, and the three rules it is built on

PlayPortCore is one Paper/Spigot plugin jar. It is the in-game half of PlayPort: economy, permissions and groups, ranks, homes and warps and kits, claims, mines, PvP hearts, a shop, an auction house, quests, jobs, dungeons, spawners, custom items and enchantments, cosmetics, teams, factions, minigames, and about a hundred other things - see Appendix D.

A template is a configuration of this one jar rather than a list of downloads. The panel drops the jar into plugins/ and writes a profile beside its config, and the same jar becomes a survival server, a prison, a lifesteal SMP or a hub.

Three rules hold everywhere in it, and they are worth knowing before you promise anything to players:

The module model, in operator terms

Everything the plugin does lives in a module. A module owns:

The single most important consequence: a module that is off claims nothing at all. Its commands do not exist for players, its listeners are not registered, its timers do not run, and it reads nothing. That is deliberate - it is what lets you run this jar beside EssentialsX, CMI or LuckPerms and take only the pieces you want - and it is also why a switched-off module can look like a deleted one. The plugin says so when it can: a module whose file still holds work a player would miss prints a line at boot naming the file and the count, because from inside the game "off" and "gone" look identical.

/playport status is the screen for this. It prints the template profile in effect, then one line per module, then every command currently registered. The same summary goes into the console at boot, with the panel link and the fair-play line (server.banner, default true), and debugConfig: true adds a line listing every module's switch - which is the quickest way to see what a template left on. The three states a module can be in are, in the plugin's own words:

statemeaning
onthe switch is on and nothing took its job
off in configmodules.<id>.enabled: false
yielded to <Plugin>another plugin on this server already owns that job (see the yield table below)

The registry is generated from the source, not kept by hand. The ids and labels /playport status prints are read off each module's own source by a tool the plugin build runs before it compiles, so a module cannot be missing from the list while its code is in the jar. A module that runs without being in that table logs a warning naming it and telling you to regenerate the table - which is a build problem rather than something you can fix from the server console.

The commands you will actually type

/playport (aliases /pp and /core) is the platform's own command. It is registered with no permission of its own; each verb checks what it needs, so a player can type it and get a refusal rather than "unknown command".

commandwhat it doesneeds
/playport help [module|page]the commands that really exist, grouped by module. /playport help all prints one line per module; a long answer is paged (/playport help 2). /playport help <module> is one module in full, with each command's description-
/playport statusthe template profile, every module's state, and the commands in use-
/playport check [module]every setting that would do nothing - your values, and any key in config.yml no live module readsppcore.check
/playport placeholdersevery {name} and %name% token in this server's config, grouped by the section that draws it, and any a module does not fillppcore.check
/playport profilethe profile in effect, who chose it, and which profiles are available-
/playport profile <name|off>switch to a profile, or drop it and run on config.yml aloneppcore.admin
/playport panelthe link to the panel, and every setting the panel supplied (these win over config.yml)-
/playport panel reportsend one status report to the panel now-
/playport fairthe no-purchases policy, in plain terms-
/playport import [plugin|list]carry settings over from another plugin's config into this one's file. /playport import alone lists the importers; <plugin> without confirm only reports what it would takeppcore.admin
/playport sql [status|groups|data|test|tables|import|reload]the database link, if you gave this server one (chapter 06)ppcore.admin
/playport saveflush every module's own data to disk, leaving the server as it isppcore.admin
/playport reloadre-read config.yml and the profile, and apply it to the modules that keep settings in memoryppcore.admin

Server operators also get /dimensions (alias /dims): the worlds this plugin owns, adopting one, and regenerating one. It needs ppcore.dimensions.admin.

Three things worth knowing before you use them. save and reload both touch every module's own files at once, and save uses each module's flush half rather than its teardown half - saving does not cancel a running fight, dismiss pets or drop anybody's permissions. And the importer has a dry run on purpose: /playport import <plugin> reports what it would carry, the confirm word is what writes it, import.enabled (default true) is the switch, and it never overwrites a value you set in config.yml - the report names the paths it skipped for that reason.

How commands appear (and why nothing is declared in the plugin's descriptor)

The plugin declares no commands in its plugin.yml on purpose. A command declared there is claimed by the server even when the module that owns it is switched off, which is exactly the interference this plugin is built to avoid. Commands are registered at runtime instead, and a disabled module registers nothing.

What that gives you, from config.yml:

keyeffect
interop.commandNamespace: "pp"every command this plugin registers gets the prefix, so /home becomes /pphome. The one-line way to run alongside EssentialsX or CMI
commands.<id>.name: <word>rename one command, by the command's own id. /playport help all prints the names in use, so a rename is checkable against what players will actually type
commands.<id>.enabled: falsethat command is not registered at all

Three behaviours worth expecting:

/playport help is built from the registration itself, not from a written list, which is why a switched-off module cannot appear in it. It names those modules at the end instead - "no such command" and "somebody turned that off" are different answers, and only one of them is worth a support ticket.

Permissions and groups

Every command carries a permission node of the shape ppcore.<module>.<verb> - ppcore.ah.use, ppcore.ah.sell, ppcore.ah.admin, ppcore.arena.spawnboss, ppcore.staff.teleport, ppcore.jobs.use, ppcore.ui.admin and so on. The platform's own nodes are:

nodewhat it unlocks
ppcore.check/playport check, /playport placeholders, and the <command> check verb on any module's command
ppcore.admin/playport save, /playport reload, /playport profile <name|off>, /playport sql ...
ppcore.dimensions.admin/dimensions

The plugin's perms module can be your permission system: it owns groups, their nodes and the default group, and the earned-rank ladder drives group membership. If you would rather keep the permission plugin you already run, two things happen by themselves: perms stands down when LuckPerms, PermissionsEx, GroupManager or CMI is installed, and the hooks layer keeps answering questions through Vault's permission service when it is there.

config.yml: what it is, and which layer wins

The plugin writes plugins/PlayPortCore/config.yml on first start; it is a complete copy of the shipped defaults, heavily commented. The plugin never rewrites it, so your edits survive every upgrade and a re-applied template cannot overwrite them by accident.

Four layers are in play, highest first:

  1. What you edit in the panel's Plugins tab. These are applied to the running server within a minute, and /playport panel lists exactly which settings came from the panel.
  2. Your edit in config.yml. Because the file starts as the shipped defaults, "an edit" means a value that differs from the shipped default - a line you never touched does not block the profile below it.
  3. profile-<name>.yml, selected by the profile: key. This is what a template installs.
  4. The plugin's built-in defaults.

Set profile: "" to run on config.yml alone, or use /playport profile off for the same thing at runtime. Delete config.yml and any profile-<name>.yml and restart to get the shipped file back.

The file's shape, in the order an operator usually meets it:

sectionwhat it is
profilewhich template profile to load as defaults (empty means none)
interopcommandNamespace, yieldToInstalledPlugins, takeOverBuiltInCommands, forceModules, and per-plugin yield overrides
hooksdelegations to other plugins, each with its own switch (economy, permissions, levels, bedrock)
modules.<id>.enabledthe switch for one module
modules.<id>.features.<name>the finer switches inside a module (homes without warps, one kind of crate, and so on)
commands.<id>that command's name and enabled
worlds.*, pack, packDir, profileDirthe plugin's own plumbing rather than any module's
one section per modulethat module's settings, using its id as the section name

A key the plugin does not read is not harmless, and that is the subject of the next section.

<command> check, and /playport check

This is the feature that keeps a config honest, and it is the one to run after every edit.

Every module's command answers <command> check - /shop check, /arena check, /plot check, /f check. The report is answered for every module in one place, so no module can quietly lack it. A clean answer names what was looked at:

[PlayPort] ess: config checks out - 12 kit(s), 4 warp(s).

A finding is a setting that would do nothing, which is the most expensive kind of wrong a config file can be, and it comes in two halves:

/playport check (no argument) is the server-wide version: every live module's own problems, then every key of config.yml that no part of the plugin reads, each with a hint in the words of your file - there is no module called 'shpos', not a key shpos reads, nothing reads this section. That union is what makes the guarantee platform-wide: a key is inert only when nobody reads it, which is a claim that can be made out loud. /playport check needs ppcore.check.

Two honest edges:

Templates and profiles

A profile is a small YAML file, profile-<name>.yml, in the plugin's folder. It carries the switches a template needs - which modules are off, and the values that make this server a hub or a prison - and nothing else. The panel applies one when it applies a template, and the plugin ships several of its own in pkg/profiles/ (hub, prison, lifesteal, skyblock, factions among them).

The operator-facing verbs, and the one subtlety:

/playport profile                 what is in effect, who chose it, what is available
/playport profile prison          switch to it now
/playport profile off             drop it and run on config.yml alone

Hooks and the plugins beside you

Two mechanisms, one question each.

interop.yield - a plugin owning a whole job. The built-in table is long by design: EssentialsX, CMI, LuckPerms, PermissionsEx, GroupManager, GriefPrevention, Towny, Lands, WorldGuard, PlotSquared, RealisticSeasons, VotingPlugin and other vote listeners, Iris and other generator plugins, lifesteal plugins, PrisonRanksX, quest plugins, MineResetLite, auction-house plugins, enchant plugins, factions plugins, shop plugins, spawner plugins, ItemsAdder/Nexo/Oraxen/MMOItems, hopper plugins, random-teleport plugins, potion plugins, pet plugins, boss-arena plugins, NPC plugins, trading plugins, capture-the-hill plugins, fishing plugins, team plugins, cheat-detection plugins, chain-harvest plugins, elevator plugins, skyblock plugins, hide-and-seek plugins, tool-progression plugins and dungeon-wand plugins. The reasoning is the same in every row: two plugins holding the same job is worse than either alone - two anticheats is how a server gets false bans, two capture plugins double-pay every capture, two island plugins are two answers to "where is my island".

hooks - one question with a better answer when a real API is present. hooks.economy (Vault economy), hooks.permissions (Vault permissions), hooks.levels (mcMMO), hooks.bedrock (floodgate). With the switch on and the plugin installed, the answer comes from that plugin; with it absent or the switch off, this core answers the same question its own way. /hooks prints every row and where its answer came from, /hooks <row> details one, and /hooks has <node> answers for the caller, naming the path it went through. There is deliberately no combat switch: the combat-log plugins each publish a different API this jar does not link to, and a combat tag is a rule (hearts.combatLog) rather than a feature - so a switch there would switch nothing off.

Two optional layers with degraded modes, and they say which they are in:

What it writes on disk

Everything lives in the server's plugins/PlayPortCore/ folder:

pathwhat it is
config.ymlyour settings. Written once, never rewritten
profile-<name>.ymlthe template profile in effect
imported.ymlwhat /playport import carried in from another plugin's config, so the module that wrote it can say what it took
per-module dataeach module that keeps state writes its own file or folder here - jobs-data.yml, rtp.yml and art-ids.yml are three the plugin's own vocabulary names. The panel's Plugins tab lists the files a module keeps as well
pages/, dialogsthe file halves of the ui and dialogs modules, when they are on

The panel's own settings for this server are a file it writes beside the jar, in plugins/. That file is the top layer of the config load - which is why /playport panel exists: it names every setting the panel supplied, so "why is this value not what my config.yml says" is one command away.

Back up plugins/PlayPortCore/ with the rest of the server. It is small, and it holds the things your players would notice losing (jobs, rtp usage, lists of pages).

Reloading and upgrading

what you changedwhat applies it
a value in config.yml or the profile/playport reload, or one minute through the panel
anything in the panel's Plugins tabapplied within a minute, or /playport panel report to send the state back now
modules.<id>.enabled, commands.<id>, interop.commandNamespacea restart
a module's own content files (pages, dialogs, blueprints)that module's own reload verb, or a restart

Upgrading is the panel's job (chapter 07): it installs a versioned jar and takes the older ones away, and because your config.yml is never rewritten, your edits survive the upgrade. Two things to expect after a version change: a new key appears in the shipped defaults and is not added to your file (set it explicitly if you want to change it - the shipped value is what applies until you do), and a key that no longer exists is reported by /playport check as a setting nothing reads, which is the same answer you would give it: delete it.

The plugin refuses to run on a Java runtime it was not built for, and the panel refuses it earlier with a console line rather than letting it fail at class load. That is deliberate - a plugin failing at class load reads as a broken server, not as a version mismatch. Chapter 03 covers the runtimes the panel installs.

Honest limits worth knowing before you hand this to players

Files behind this chapter

What this section documentsSource
The three rules, the boot banner, /playport and its verbs, /dimensions, the panel linkpkg/src/ppcore/PlayPortCore.java (../pkg/src/ppcore/PlayPortCore.java)
The module contract: id, label, switch, vocabulary, problems(), checkSummary(), namesFirstWord, flush(), offNotice()pkg/src/ppcore/Module.java (../pkg/src/ppcore/Module.java)
Runtime command registration, renames, namespaces, takeover rules, the yield table, forceModules, hookspkg/src/ppcore/Interop.java (../pkg/src/ppcore/Interop.java)
<command> check and /playport check: the two halves, the value checks, the platform-wide scanpkg/src/ppcore/ConfigCheck.java (../pkg/src/ppcore/ConfigCheck.java)
The generated reports and the module registrypkg/src/ppcore/ConfigVocab.java (../pkg/src/ppcore/ConfigVocab.java), pkg/src/ppcore/ModuleIndex.java (../pkg/src/ppcore/ModuleIndex.java)
The descriptor: load: STARTUP, api-version, and why no commands are declaredpkg/plugin.yml (../pkg/plugin.yml)
The shipped settings, comments, precedence and section listpkg/config.yml (../pkg/config.yml)
The template profiles that ship with the pluginpkg/profiles/ (../pkg/profiles/)
Parity with the plugins a module was rebuilt frompkg/PARITY.md (../pkg/PARITY.md), pkg/PROVENANCE.md (../pkg/PROVENANCE.md)
Config-authoring style the shipped file followspkg/notes/GEN-STYLE.md (../pkg/notes/GEN-STYLE.md)

Chapter 09. Networks and exposure

This chapter is about running several Minecraft servers as one address - a Velocity network with a hub, a lobby and as many game servers as you like, sharing one login, one chat and one staff team - and about giving a game server a public address when you cannot open a port on the router. It assumes you can already create a single server (chapter 03) and that you know what the core plugin does (chapter 07, chapter 08).


What a network is in PlayPort

A network is a group of servers behind one Velocity proxy:

PieceWhat it isType in the panel
The proxyVelocity. Players connect here, and it routes them to a backend.velocity
The hubThe server players land on: a lobby, a spawn, a rules room. One or more.paper / vanilla / (any Bukkit family backend)
Game serversSurvival, creative, minigames, whatever the network is for. Any number.paper / vanilla
LimboOptional. NanoLimbo: an empty ~5 MB world that holds players while a backend restarts. It loads no plugins.limbo

The panel owns the whole relationship: which servers are in which network (each instance carries a network name), the shared forwarding secret, every generated config file, and the start order. That is the point of doing it here instead of by hand - a network's config is one record in the panel (networks.json) and every file is rewritten from it, so there is no set of files that can drift apart from each other.

The proxy is the address you hand out. Backends listen on their own ports too, but a player who connects straight to a backend bypasses the proxy and is normally rejected - that is what the shared forwarding secret is for.


Building one: Minecraft Network

Game Servers -> + New server -> the Minecraft Network category. The wizard builds the whole thing in one go:

FieldWhat it doesDefault
Network nameThe name of the group. Also the default prefix for the hub and limbo names.- (required)
Proxy versionWhich Velocity build to download. latest resolves from PaperMC's own API.latest
Minecraft versionThe version the hub and the game servers are created with.latest
Proxy portThe port players connect to.25565
Create a hub / Hub software / Hub name / Hub RAMThe server players land on.on, Paper, Hub, 2048 MB
Game servers (rows: name, software, RAM, and a PlayPort Core picker per row)The backends this network is for. Each row can be left empty and is then skipped.-
Create a limbo lobby / Limbo name / Limbo RAMThe NanoLimbo holding server.on, Limbo, 256 MB
Accept the Minecraft EULARequired, exactly as for a single server.off

Ports are chosen for you unless you type one: the hub starts at 25580, game servers at 25590, 25592, 25594…, limbo at 30066, and the panel scans every existing server and website so it never picks a port something else is already using. A network is refused if the name is already taken (A network named '<name>' already exists.) or the EULA was not accepted.

Each backend's core plugin is decided before anything is created. Every game row and the hub block carry a PlayPort Core picker: automatic (the name picks the template), a named template, or No core plugin. Automatic is the default and reads the server's own name and role, so a game server called Creative comes up on the creative template, Survival on the survival one, and a hub resolves to the hub template whatever the network is called; a template name this build does not ship is refused while the network is still empty, rather than leaving half of it built. A backend that cannot load plugins (Vanilla, or the limbo lobby) is told so in its own console and in Activity instead of being handed a jar it cannot use.

When the network is made, the panel creates the proxy first, then the hub, then limbo, then the game servers, and finally rewrites the proxy config - which is also when the bridge is installed and /hub is pointed at the hub. The proxy's console gets one line: "Network '<name>' created with N backend(s). Start the hub and games first, then the proxy." The toast names the templates it chose, because "Creative is creative" is the thing you just asked for and reading it back is how you know the name was understood.

Java for a proxy is not guessed. The panel reads the requirement out of the proxy's own jar (current Velocity builds ask for Java 25; older ones for 21 or 17) and downloads that runtime - see chapter 03 for the Java story. There is deliberately no javaVer pin on a network: a pin the jar cannot load is cleared with a line in the console rather than honoured, because a JVM rejects the class before any of the proxy's code runs, so such a pin could only produce a network that restarts forever.

What the panel writes

Everything below is generated. The header of each file says so, and the settings that drive them live in one place (the next section, not the files):

FileWhereWhat it holds
velocity.tomlthe proxy's folderbind on the proxy's port, the MOTD, show-max-players, online-mode, player-info-forwarding-mode, the shared secret file name, an empty [forced-hosts], and a [servers] section with one line per member (slug = "127.0.0.1:<port>") plus try = [...] in the order players should be sent to them
forwarding.secretthe proxy's folderthe shared secret, 24 random hex characters, created once and reused
config/paper-global.ymleach Paper/Vanilla backendproxies.velocity enabled, online-mode: true, and the secret - this is modern forwarding
server.propertieseach Paper/Vanilla backendonly the online-mode=false line is rewritten, because the proxy authenticates players and a backend that repeats the Mojang handshake rejects them
config/secretevery backendthe same secret, for anything (limbo included) that reads it from a file
settings/settings.ymlthe limbo lobbythe port it binds, its MOTD, and the infoForwarding block with the secret
plugins/playport-bridge-<version>.jar and plugins/playportbridge/bridge.ymlthe proxythe bridge (next section)
networks.jsonthe data rootthe per-network settings record this all comes from

[servers] is written even when a network has no backends yet (it falls back to a limbo1 entry), because Velocity substitutes its own template defaults for a missing section - lobby.example.com mapped to a server called lobby - and then refuses to start.

The bridge: the proxy half of a network

The core plugin is a Paper plugin. It can see its own world and nothing else, so on a network it cannot know that a hub exists or move anybody to it. That job belongs to the proxy, and the bridge is the small Velocity plugin that does it: playport-bridge.jar, installed into the proxy's plugins/ folder, with its own config at plugins/playportbridge/bridge.yml.

Where it goes matters: on the proxy, not on a backend. The panel installs it as part of configuring a network, and the network card shows its state - bridge <version> on the proxy, bridge out of date, no bridge on the proxy, or no bridge in this panel build when the exe was built without one. The card's Install bridge / Update bridge button installs the build this panel ships and removes older ones of its own making.

The card's × on a member's name removes that server from the network and keeps the server and its world; the delete-network dialog offers Delete servers, keep their files and Delete everything for the same reason - "does the world survive this?" is the question a person actually has.

What the proxy answers itself

CommandAliasesWhoWhat it does
/hub/lobby, /spawn, /leveryone - deliberately no permissionsends the player to the hub. With a player name it sends that player, which is a staff verb
/server/srveveryonebare, lists the network with player counts; with a name, connects you (or, with a second argument, sends somebody - staff)
/send-staff/send <player> <server>. Named separately from /server so a mistyped name can never move somebody who did not ask
/sendall-staffmoves everybody to a server
/find-staffwhere somebody is, and their ping - the most asked question on a network
/glist-staffthe network as one list, grouped by server and by player
/alert/broadcaststaffone line to everybody on the network, console included
/staffchat/scstaff chat permissionthe network's back channel: it fans out by permission, not by server, so staff spread over four backends read one line

Server names are the names from velocity.toml - the panel writes them, and /server with no argument lists them, which is the quickest way to check one. An alias table in bridge.yml lets you add short names of your own.

What the proxy forwards

Some commands cannot be answered on the proxy at all, because the record they touch lives on a backend: /ban owns a player record, /vanish changes a player's state on their server. Those are handed over the playport:bridge plugin channel to the backend the player is standing on, and run there, as that player - so the backend's own permissions, ban records and command logic still decide. The list is configuration, never arbitrary text: the default is ban, kick, mute, warn, vanish, and each one is granted with playport.proxy.forward.<name> or by being staff.

Two consequences worth knowing:

Who counts as staff on the proxy

Three sources, and any one of them is enough:

  1. a name list in bridge.yml (staff.players), for a network whose backends run no PlayPort core plugin at all - Velocity grants a player nothing by itself, so without this list the staff half of a network would be unreachable on exactly the networks that need it most;
  2. a permissions plugin on the proxy (LuckPerms, for instance);
  3. the backends' own groups, reported over the link: the core plugin's perms groups carry the proxy's nodes and its bridge module sends them, so a network whose servers run the core plugin needs nothing installed on the proxy. The report is kept with the server it came from and only honoured while the player is still on that server, and only for node names the proxy itself owns - a backend cannot invent authority, and a player who moves to a server that does not run the core plugin walks away from the grant.

Starting and stopping a network

A network's card has one button: Start network or Stop network. Behind it the panel does the ordering for you:

The card's dots are health, not process state: a backend that is running but does not answer a status ping is drawn as a warning, and its tooltip says running but not answering a status ping. Limbo is exempt from that check on purpose - NanoLimbo has no player list and does not answer server-list pings, so probing it would read as unhealthy. The player number on the card comes from the proxy, which is authoritative: somebody sitting in limbo or between servers counts there and not in any backend.

Starting a network is also a job: a start that failed is retryable with one click from Activity once the underlying problem (usually Java or a port) is fixed.

Adding a server to an existing network

The card's + Add server creates a new backend or attaches one you already have.

Either way the panel creates or enrols the server, rewrites the shared secret, the proxy's [servers] list and that server's own proxy settings - and tells you the one thing it cannot do for you: restart the proxy to apply the new routing. Detaching (× on the member, or Remove from the network) reverses it and keeps the server.

Network settings in one place

The card's Network settings dialog is the single source of truth for the network. It offers:

FieldValues
MOTDthe line players see in their server list; colour codes work
Max playersthe proxy's slot count
Default serverFirst in start order (default) or a named backend - where players land
Player login checksOn - Mojang verifies every player (recommended) / Off - offline mode (private testing only)
How the proxy forwards playersModern - Paper 1.13+ (recommended) / Legacy BungeeCord - needs a plugin on each backend / Off - players show as the proxy's address

The dialog also lists the servers in the network with their roles and ports, and whether the shared forwarding secret is set and identical across all servers. Save & apply to every server rewrites every generated file and then reports what it touched, file by file; Re-push configs only does the same rewrite without changing a setting. Either way: "Restart the proxy for the proxy-side changes to take effect. Backends pick up their settings when they restart."

Two honest notes about this dialog in the current build:

playit.gg: a public address without opening a port

cloudflared fronts HTTP (chapter 05). A Minecraft client does not speak HTTP, so for a game server the tunnel to use is playit.gg, which fronts raw TCP and UDP. PlayPort downloads and runs their agent for you: Settings -> Components, or the health board's Install button (~5 MB, their signed Windows build, because Smart App Control refuses an unsigned binary on some machines).

One thing stays a human's job: the secret key. Generate it with playit's setup wizard - the panel links straight to it - and paste it into the panel once. It is stored beside the agent in the data root, never sent back to the browser (only its last four characters are shown), and deleting the folder deletes both halves. Then create the tunnel on playit's own dashboard and point it at 127.0.0.1:<port> - the panel prints that address on a game server's Overview and on a site's public access card. No router rule is needed.

What the panel does around that, and why:

Ports and addresses an operator must get right

Gotchas

SymptomWhat it usually is
Players connect to the proxy but every backend refuses themthe shared forwarding secret does not match, or online-mode=false is missing in a backend's server.properties. Network settings -> Save & apply to every server rewrites both
/hub says Unknown serverthe hub is not a member (/server lists the names), or the proxy has not been restarted since the backend was added
A backend shows as running but not answering a status pingit is still booting, or it crashed after starting; open its console
New backend was created but nothing routes to itthe proxy was not restarted (Backend 'X' added - restart the proxy to apply the new routing)
The card says bridge out of datethe panel ships a newer bridge than the proxy has: press Update bridge and restart the proxy
Staff verbs (/find, /send, staff chat) are refusedthe player is not staff on the proxy by any of the three routes - a bridge.yml name, a permissions plugin on the proxy, or their backend's own groups
A forwarded verb works for staff but not the consolea forwarded console command needs a player name in it, because the record it touches lives on that player's server
Limbo shows no player countexpected: NanoLimbo does not answer server-list pings, and its members are counted by the proxy
Deleting a network took the worlds with itthe dialog has two buttons; Delete everything means the files too. Detach (×) is the way to keep a server

Files behind this chapter

FeatureSource
Network creation, members, attach/detach, settings, apply, start order, generated filessrc/Instances.cs (CreateNetwork, AddNetworkMember, AttachToNetwork, DetachFromNetwork, NetworkConfig, SetNetworkConfig, ApplyNetworkConfig, RewriteVelocityToml, ApplyBackendProxyConfig, NetworkOrder, StartNetwork, StopNetwork)
The stored per-network settings record (networks.json)src/NetConf.cs
Network routes and what each one requiressrc/Api.cs (the Networks section)
Installing the bridge, its bridge.yml, and reading its versionsrc/ProxyBridge.cs
Which core template a backend getssrc/Core.cs (BackendProfile, ProfileForName, ValidProfile, InstallOnBackend)
The proxy plugin: commands, permissions, forwarding, the perms reportproxy/src/ppbridge/PlayPortBridge.java
The default bridge config an operator can editproxy/bridge.yml
The bridge's own descriptor and versionproxy/velocity-plugin.json, build-proxy.sh
Java requirement fallback for proxies and limbosrc/JavaReq.cs
The network card, wizard, add-server and settings dialogsweb/app.js (refreshNetworks, netDeleteModal, the inet* wizard fields, netSettingsModal)
playit.gg: download, secret key, wanted marker, "no key means stop"src/Playit.cs
Product-level description of networks and the bridgeREADME.md (Minecraft networks, Adding servers to an existing network, Network settings in one place, one-button start/stop, playit.gg)

Chapter 10. Mail, AI and the help assistant

This chapter covers the four things in PlayPort that are services with their own doors rather than pages in the panel: the mail service your websites send through, the local AI the panel runs on itself, the free cloud and linked machines it can borrow instead, and the help assistant that answers questions about the panel and can repair a few known problems. It also covers alerts, which are how all of the above tell you that something broke. None of it is required: a panel with mail off, no model loaded and no alerts configured still runs servers and websites.

Two of the doors in Appendix B belong to this chapter - 8100 (models and agents) and 8101 (the brain). They are separate from the panel's own port on purpose: publishing one must not publish the others.


Email: a real SMTP service for your websites

PlayPort runs its own SMTP service. The point of it is that a PHP site can call mail() the moment it is created, with nothing to configure and nothing to sign up for: each site's php.ini is generated with SMTP = 127.0.0.1, the submission port (1025 by default), and a sender derived from that site. If a mailbox is attached to the site, that address is used; otherwise the panel builds one from the site's domain and name, and falls back to noreply@playport.local when the site's domain has not been hooked up here. The submission port is loopback only and takes mail from any local process without a password, so it is never exposed - which is also why changing it is harmless.

Everything about it is on the Email page (#/mail), which is split into tabs because it answers six different questions: Overview, Delivery, Domains, Mailboxes, DNS records and Messages. ? Setup guide prints the registrar steps, the records and the ports; a plain user sees only Messages, because the rest is service plumbing the API refuses them anyway.

Hooking up a domain comes first

Mail is domain first. PlayPort does not register domains: you hook up one you already own, and the panel hosts the mail for it - locally, and out to the internet once the DNS records it prints are published. A mailbox only exists underneath a hooked domain, because a mailbox on a domain nobody has hooked up is a local address that can never send anywhere. The panel enforces that: Create mailbox on a domain that is not hooked up is refused.

Hooking a domain up gives it its own DKIM key pair and its own two switches:

A live DNS check runs when you hook the domain up and on demand afterwards. It queries the public resolver for the domain's MX, SPF, DKIM and DMARC records and reports a plain verdict - live, partly live or not live yet - with exactly what is still missing or different. It reports honestly rather than optimistically: a published DKIM key that does not match this panel's is called out as a mismatch, not as present.

The records themselves are on the DNS records tab, one line of explanation each, and they are yours to publish at your registrar: an A record for the mail host (mail.<domain>, or the configured hostname when it sits inside the domain), an MX record pointing at it, an SPF record, the DKIM record <selector>._domainkey (the selector ships as playport), and a DMARC record. Nothing in the panel can publish them for you, and sending to strangers' inboxes works badly until they are live.

Three delivery modes, because they fail differently

The Delivery tab is one switch with three settings, and the help text under each one says how it fails rather than how it works:

ModeOn the page asWhat it doesThe honest catch
captureCapture onlyKeeps every message on this machine and delivers nowhereThis is the default. Nothing leaves the PC - which is also why it proves a site's email works without risking a spam complaint
relayRelay through a providerHands messages to your provider or a transactional service over STARTTLS with a loginThe only mode that reliably reaches strangers' inboxes from a home connection, because it borrows the provider's IP reputation
directDirect to the recipientLooks up each recipient's MX record and delivers on port 25Most ISPs block outbound 25, and without a static IP with matching reverse DNS plus SPF/DKIM the big providers will junk it

For relay the submission host, port, user, password and security (starttls, ssl or none) are typed in, with three presets one click away: TransIP, Gmail / Workspace and Outlook / Microsoft 365 (all on port 587 with STARTTLS). The relay password is stored in the panel data folder and is never sent back to the browser. Send test message walks the exact same path a site's mail() takes, so the chain can be proven end to end - the test returns a step list rather than a yes/no, and the summary says what the last failing step was.

A relay test result is remembered (its timestamp and summary are written to mail.json), and it is cleared the moment a relay setting changes, because a stale "it works" badge is worse than no badge.

Mailboxes, catch-all, and mail that arrives for nobody

Mailboxes live under a hooked domain, optionally attached to a website. Per domain you get one catch-all, and a message for an address with no mailbox is kept in the Unmatched folder rather than bounced away - accept-and-store is friendlier than a bounce when the domain is not fully set up yet. A mailbox can carry a password, but there is no IMAP or POP3 login behind it in this build: the password field is reserved for a future one, which is worth knowing before you hand an address to a customer who expects to collect mail from it.

Messages are readable per folder with headers and a plain-text body, and Raw headers shows the message as it arrived. Folders are the mailbox address (with @ written as _at_) plus the internal _sent and _unmatched; each folder keeps the newest 2,000 messages.

Incoming mail is opt-in

Receiving mail for a hosted domain means listening on port 25, which needs administrator rights, a firewall rule and an ISP that does not block the port. So it is a switch (Incoming mail), not a default. Turning it on binds the port, adds the inbound firewall rule the panel needs to be reachable at all (without it Windows silently drops the packets, which looks exactly like a wrong router forward), and lets you choose the port. Turning it off leaves the submission side running for your sites.

Where mail lives, and who may touch it

PathWhat it holds
<data root>/mail.jsonthe settings: service switch, mode, submission and incoming ports, relay credentials, the hooked domains, their DKIM key pairs, the last relay test
<data root>/mail/the stored messages themselves, one .eml per message, in per-mailbox folders plus _sent and _unmatched

A damaged mail.json is not silent: the panel starts with shipped defaults and says so, naming the copy beside the original that still holds your addresses, relay host and passwords. The relay password and every per-domain DKIM private key stay in that file and are never sent to a browser.

Who may change what: a plain user may read the service status and their own messages, but not change the service, hook up a domain or read the internal folders. Admin and moderator may do all of it.

What this is not: it is not mailbox hosting. There is no webmail, no IMAP and no POP3 in this build, so treat it as the thing your websites send through (and, if you opt in, the thing that receives their mail for you to read in the panel). Sending to the internet from a home connection still depends on the reputation of whatever relay you point it at, which is why relay is the mode to use for real mail.


The models: the AI this panel runs on itself

The panel hosts a model of its own and uses it for four things, all of them local and all of them offline by default:

Every one of those obeys the hardware guard first: if the machine is busy or hot, the work is refused with the same sentence the guard would give any other request. A running game server outranks a nice paragraph.

The guard is about the machine, not about trust

This panel also hosts game servers, so a model competes with them for the same GPU, RAM and heat budget. The guard reads the machine and refuses starting work, rather than starting it and cutting it short. Its thresholds are settings with these shipped values:

SettingDefaultMeaning
GPU temperature, soft / hard83 / 88 °CPause new work / stop and unload
VRAM kept free700 MBnever planned for, because the display wants it
System RAM kept free2.5 GBfloor below which nothing loads
CPU considered busy / Busy for this long92 % for 90 s"something else is using this machine"
Disk kept free15 GBrefuse a model download below it
Cooldown after a hard stop120 sbefore anything may load again
Unload after idle10 minfree the VRAM when nothing is asking
This machine shares one memory pool / Of that pool a model may claimoff / 70 %for a mini PC whose GPU and CPU share one pool

An idle unload is not undone on a timer: the next question (or the page's button) loads the model back. A model that reloads itself only to sit idle again is a machine heating itself for nobody.

The engine, the models, and where they come from

The engine card on the Models page shows the runtime the panel drives. By default that is a llama-server from llama.cpp, started with flags computed for this machine: the number of layers on the GPU comes from the fit calculation rather than a fixed guess, the context window comes from the setting, and the model's own chat template is used (which is also what makes tool calling work). One model is loaded at a time - an 8 GB card holds one useful model, and switching stops the old server and starts the new one. The server's own output goes to a log file, and the interesting lines (the ones that say why a load failed) are read back and shown, because "the server exited with 1" is not a diagnosis. A process killed by application-control policy is reported as exactly that, from its exit code.

If you already run a runtime - Ollama, LM Studio - the Vendor runtime URL setting drives it through its own API instead of spawning llama.cpp, with Model to use in the vendor runtime and a hold time in minutes. A runtime like that loads on demand, and the panel treats it as able to answer: it takes the request and pulls the model itself.

Models (the catalogue card) come from Hugging Face. Weight files are data, not programs, so application-control policy does not apply to them. Three things the downloader is careful about, all of them visible in the transfer: it resumes (every file is written as .part and picked up with a Range request, because a 19 GB file over a home connection will be interrupted), it plans every shard of a split quant (half a split file is not a model), and it reports a gated repository as needing a Hugging Face account, which this panel cannot provide. Model files live under <data root>/models unless Where models are kept points somewhere else - a share on another machine is allowed, and the panel keeps its own caches elsewhere precisely because a share can be asleep.

The model this panel runs is chosen for you by default. autoload ships as auto: the panel ranks what this machine can actually run and says why - a model that is already here beats one that has to be fetched, one that fits entirely on the GPU beats one that spills into system RAM, and among equals the larger model wins - then fetches the pick, loads it, and reports each step in the feed. A candidate the guard would refuse is dropped before it can win, so the automatic pick is a model that can load on this machine as it is now; a machine that was busy at startup gets its model later, on the panel's tick, with nobody watching. Setting autoload to a model id pins that model, and leaving it empty means nothing loads by itself - which is what "do not touch my GPU" looks like as a setting. Let go on the page unloads it on purpose; it comes back when you ask.

The rest of the Models page is the state rather than the machinery: This machine (a live strip), Stats, Ask the panel (a chat that uses the model), and What happened (the feed of loads, refusals and answers). The hardware guard says: opens each card with the guard's own verdict, and a limit that has been crossed is stated with the measured value next to the threshold it broke.


Which model answers, and where: the tiers and the free cloud

One panel can answer from three places: a small model here, the big model this machine was measured for here, and a linked machine across the network. There is also a fourth: the free cloud.

Light and deep

Every request carries a weight. Routine work (the overview, the help box, a lookup, a thought) is light and is answered on this machine, always, on the small model: it costs nothing extra and never wakes anything. A hard question (a crash nobody has seen, a repair, an agent turn) is deep, and only a deep question may move:

  1. the model that is already loaded, if it can do it;
  2. the big model here, but only if this machine is idle enough for it - at or above Above this processor load the big model stays unloaded (tier_busy_pct, 60 % by default), or with a guard verdict against starting work, it is not loaded on top of what is already running;
  3. a linked machine that is already awake;
  4. a linked machine that can be woken - the last resort, because waking a whole computer is the most expensive thing the panel can do;
  5. the small model here, saying so, because a shorter answer with a reason beats a failure without one.

Which machine answers (tier_mode) is the policy: auto (this machine first), local (never hand work to somebody else) or peer (prefer the other machine). Prefer a linked machine for hard questions (tier_prefer_peer) says whether a linked machine is tried before this one's big model, and Shortest time between model swaps (tier_min_secs, 45 s) stops a busy machine reloading itself continuously to answer polls. The Models page shows both paths with the reason for each, read from the panel rather than re-derived by the page.

The free cloud

cloud_tier is one switch in three honest positions:

PositionWhat it meansWhat leaves this machine
offthe shipped answerNothing, because a page was opened
fallbackthe cloud is where a question goes when nothing here and no linked machine can take itA question this machine and no linked machine can answer
preferanswer here from the cloudEvery deep question - the right answer for a machine that cannot run a good model, because the strongest thing on it is a 550B reasoning model and it costs nothing per token

It points at any OpenAI-compatible router (cloud_url, default https://router.requesty.ai/v1), with cloud_model naming the model. The key is a secret: it lives in its own file under the panel's state directory (ai/cloud.json), never in settings.json, because a setting is something the panel shows, logs and hands to every page. One key from the router's own key page opens it, and tier_mode=local still wins over all three positions: a panel told to keep its questions local is never overridden by this switch.

Two things the panel is careful about, and both are things you should know before switching it on:


Other machines: two panels using each other's hardware

A second PlayPort running this same software can answer for this one, and this one for it. That is a decision, not a default: nothing is sent anywhere until somebody links a machine, and this one answers other machines only while Serve this machine's models to linked machines (peer_serve) is on.

A machine is linked by the address of its model port (not its panel) and a key minted on its Agents page. Getting that right by hand means three things at once on the correct machine, so the Other machines card pairs instead: on the machine lending its hardware, Pair a machine shows a one-time code (eight characters from an alphabet with no 0/O or 1/I/L in it, because a code read off a screen should not have two possible readings) and a ticket carrying that machine's own address. On the other machine the ticket goes in one field - no address is typed and no key is minted by hand.

The code is the credential for that one exchange and nothing else. It works once, it expires (15 minutes by default, and the page counts down), ten wrong tries throw it away, and what comes back is an ordinary API key named after the machine that paired. It asks for no key on the redeem route because the code is the key for that exchange. A paired machine does not bypass peer_serve: the machine that showed the code keeps its right to refuse, and pairing says so up front instead of letting the first hard question fail.

Waking a linked machine is a standard magic packet on the local network (the peer's MAC is typed in with the link) plus an optional wake URL - a relay on the peer's own network, since a broadcast cannot cross the internet. Everything the panel says about a peer is what the peer itself answered: /health on its model port needs no key and reports the model it has loaded, its guard state and its load, so "asleep", "busy" or "running qwen3:14b" are its own words rather than a guess from the last time this panel looked. A peer that refuses, is busy or is not there is a fallback for the caller, never an error in the middle of somebody's answer. The list lives in <data root>/ai/peers.json, and the keys in it never travel anywhere except to the peer they belong to.


Looking things up, for a model that has no internet

A model running on this machine cannot browse and cannot know anything released after it was trained - which is exactly what a hosting panel gets asked: is this build newer than what that plugin wants, what does this exception mean, which Java does 1.21.4 need. The panel does have an internet, so with Let a model ask the panel to look things up online (ai_web) on, the model may ask: it replies with a search request instead of guessing, the panel runs the search, reads the page, and hands the model what it found.

It is off by default, and that is deliberate: it is the only place in the panel where something leaves the machine on the panel's own initiative, so a switch is what makes that a decision rather than a surprise. The engine (ai_web_engine) is one of duckduckgo (the plain HTML endpoint, no key), wikipedia, or searx - your own instance, by URL, which is the homelab answer because nothing goes anywhere you did not point it. Engine base URL carries the searx address or a mirror, and the ceilings are settings: Lookups one answer may spend (2), Lookups allowed per hour (20, for the whole panel) and Characters of a page the model may see (6000). When the engine asks for a human check - the scripts-in-a-browser kind of block - the panel says so and suggests a searx instance rather than pretending the lookup found nothing.


The agent API: other AI tools using this panel's models

The Agents page (#/agents) is the whole story in one screen: The three ports with what each is for, the Keys, who is Connected now, the Two-way channel (a box to say something to an agent and the feed of what agents said), and Pointing a tool here - a ready-made curl and the OpenAI-compatible base URL (/v1/chat/completions) for the tool you are about to point at it.

The surface is on port 8100 (Settings, Model and agent port), and it is deliberately small: nothing on this listener can reach instances, settings, users or the panel's own state. That is the property that makes it safe to hand a key to a tool somebody else wrote.

RouteWhat it is
POST /v1/chat/completionsOpenAI-compatible completion, streaming included
GET /v1/modelswhat is loaded now, or the catalogue when nothing is
POST /api/agents/helloan agent checks in: name, kind, version, capabilities
POST /api/agents/sayan agent says something into the panel's feed
GET /api/agents/inbox?wait=25long poll for what the panel said back
POST /api/agents/toolone tool call, for an agent running its own loop
POST /api/agents/worka whole turn: the panel runs the loop and reports the steps
GET /api/agents/methis key, its workspace and the guard's state
GET /healthneeds no key: the loaded model, the guard's state and the load

Making a key (Agents, then New key) shows the secret once, with a copy button, and the modal says so in the same breath as showing it. Only a hash is kept, so a lost secret means a new key rather than a recovered one. Auth is Authorization: Bearer ppk_..., X-Api-Key: ppk_..., or ?key=ppk_... for a tool that cannot set headers - the last one exists because the alternative is a tool that cannot connect at all, and it is worth knowing it puts the key in a URL.

What a key may do is one grant per key, editable later (What "<name>" may do): a workspace, whether it may write outside that workspace, run commands, delete files, and whether it may read and write the shared brain. Writing outside the workspace, running and deleting are all off until a person ticks them, per key. Keys can be paused and revoked - revoking stops the key everywhere at once - and each key carries its own per-minute request limit (60 by default).

What a key cannot do, deliberately: touch instances, read files you did not grant, or change settings. It can use models and it can talk. And the guard applies to an agent exactly as it applies to the panel's own AI: an external tool asking for a completion while the machine is hot gets the same 503 with the same sentence. An agent cannot outrank the hardware, and it cannot outrank a running game server.

If you publish this port - to a tool on another machine, or through a tunnel - remember which door you opened: the answer is a key on 8100, not the panel's login page, and a tunnel aimed at 8100 must not also carry 8080. Reachable from the network binds it beyond loopback and the key remains the gate whatever the bind; where Windows refuses that bind, the port still comes up on loopback and the log names the exact command that would fix it.


The brain: what this machine has learned

The brain is a folder of markdown under the data root (brain/entries/*.md, with brain/index.json beside it), so you can read it, grep it, copy it or edit it by hand with no PlayPort running. Each entry has frontmatter (id, type, title, tags, scope, created, updated) and a body, and the types are knowledge (a durable fact), memory (what happened: this crash, this fix), decision (a choice and why) and note (anything else).

Two ways entries get written: the model writes them when it learns something worth keeping, and the panel writes them as memory after work that changed files, so the record of what an agent did survives the agent. The help assistant's Remember this button is the only way a model's answer becomes an entry, and it is a button on purpose: a brain feeds every later prompt, so a guess must not land there on the model's own say-so.

It is served two ways - the panel's own routes (a person, with a session) and port 8101 (a program, with a key). Reading is open to any valid key; writing needs that key's brain permission, which is on by default for keys a person creates here, because a shared brain nobody may write to is just a document.

Route on 8101What it does
GET /health, GET /statsis it up, and how much is in there by type
GET /search?q=&n=&type=scored word search, each hit compressed against the query
GET /context?q=&n=&budget=the block to paste into a prompt, ready to use
GET /get?id=, GET /list?type=&n=one entry whole, or the newest first
POST /add {type,title,body,tags,scope}, POST /delete {id}write an entry, remove one

The notes are compressed, not truncated. A note is written for a person and read by a model, and a prompt pays for every character twice, so a note is compressed against the question: the blocks that answer are kept in their original order and the rest is dropped and counted - the answer says how much it left behind and how much of the budget it spent (the block's ceiling is 1,400 characters), so a reader can ask for the entry by id rather than believing they read the whole thing. A question nothing answers reports the size of the store it searched rather than pretending nothing is known.

With no panel at all, the same store answers from the command line:

PlayPort.exe --data <root> --brain-context "port 8080 already in use" --brain-budget 300

The help assistant, and the hands it is allowed to use

Help in the sidebar (or F1 from anywhere) opens one box that answers questions about this panel, using the model the panel already runs. Buddy is the same assistant as a page (#/buddy), with the conversation, the model's own thinking above each answer, the free models one click away, and the switch that decides which machine answers.

What makes an answer checkable rather than merely fluent is the evidence the box is given before the model sees anything:

When no model is available the box is still not a dead end: it says so, names the note that looks relevant, and points at Models to see what this machine runs and why.

Buddy's hands

An assistant that can only talk is a consultant. Buddy's hands is the middle ground between talking and having a shell, and the whole of it is one idea: a rulebook that says where the lines are. Every failure the panel already recognises by name knows which file is worth opening for it, so a rule carries the symptom it answers to (the same rule ids the crash page uses), the files to open and the pattern that picks the lines out of them with a sentence saying what they are, and - where one exists - the single safe edit that removes the cause. Nothing is invented at the time, by the panel or by the model. The rulebook ships with ten entries and four fixes (a half-made world, a taken port, a view distance a machine cannot afford, and the EULA line), and it is listed on the card with where each rule looks.

Pick a server in the composer and the answer changes. The panel reads that server's console through the same feeder the crash page uses, adds the tail of the server's own logs/latest.log, and opens only the files the rulebook names for the symptoms it recognised - the model is handed those lines with their file and line numbers and told to quote only what it was given. It never reads a whole log: a 40 MB latest.log costs a 256 KB tail read.

A fix is written by the panel, not by the model. A model can only name a fix the panel has already decided applies to that server as it is now, so a fix card is a file, a key, the value it holds and the value it would get. Four rules make that safe, and all four are checked in code before the card exists:

The mode is ai_buddy_hands, and it follows who is doing the thinking by default:

SettingOn the card asWhat the assistant may do
autoFollow the modelfull hands when the free cloud or a linked machine answers; the rulebook when this machine's own small model does
offOffreads the server's console, changes nothing
rulesReads and fixesthe rulebook and its prewritten fixes - no free-form writing
fullEverythingthe rulebook plus the agent tools, boxed in the server's own folder

That auto is the honest answer to "can a 7B model be given hands": it can be given a rulebook and a journal, and it does not have to author the change. Running commands and deleting files are switches (Run commands (panel assistant), Delete files (panel assistant)), off by default, and a manager sets this - it decides what the panel's assistant may change on this machine. A job runs in the server's own folder, and the card says so before you ask for one.


Alerts: being told when something breaks

A hosting panel that only speaks when you are looking at it is not much use at 03:00. Alerts sit on the Settings page and push the handful of events that actually matter to a Webhook URL (Discord or Slack - the panel sends content, text and username, so one URL works for either) or by Email address through the panel's own mail stack, or both.

SwitchFires when
A server crasheda server exited on its own
Auto-restart gave upauto-restart stopped after repeated fast failures
An off-machine backup copy faileda backup copy to another machine did not get there
Warn when the data volume is this % full (0 = off)the data volume crossed the threshold (90 % by default; the volume is checked at most once every ten minutes)

Repeat the same alert after this many minutes (0 = always) is the flood guard, and it is on by default (10 minutes): a crash loop fires the same event every few seconds and a webhook that floods is one people mute. The key is the event plus the server, so two servers crashing at once are both reported. Set it to 0 to be told every single time.

Send a test proves the wiring and reports what each channel actually said - and it decides success by the status code, not by the text, because a webhook that was deleted still returns a body. A test that reaches only some channels says so. Two further properties worth knowing: an alert is never sent more than once per window, and an alert never throws into the caller - a webhook that is down must not be able to break a game server. Email alerts need the mail module running; the panel says so rather than failing quietly.


Files behind this chapter

FeatureWhere
The SMTP service, the three delivery modes, mailboxes, hooked domains, storagesrc/Mail.cs
DKIM signing (rsa-sha256, relaxed/relaxed) and the key pair per domainsrc/Dkim.cs
The Email page: tabs, mode help, relay presets, setup guide, message readingweb/mail.js
Alerts: the four events, webhook and email channels, dedupe, the testsrc/Alerts.cs
The Alerts card in Settingsweb/ops.js
The AI's own use of a model: explain, triage, assist, overviewsrc/Ai.cs
Every AI setting, its default and its label; the tuple of ports and directoriessrc/AiConf.cs
The engine: one model at a time, the fit-computed flags, the load logsrc/LlamaServer.cs
The automatic pick: ranking, fetching, loading, keeping itsrc/AutoModel.cs
The model catalogue and the Hugging Face downloader (resume, shards, gated repos)src/ModelDownload.cs
The free cloud: the router, the measured free list, failover, the key filesrc/Cloud.cs
Linked machines: linking, pairing codes, waking, publishing the model portsrc/Peers.cs
The model and agent API (port 8100) and its routessrc/ModelSrv.cs, src/ApiModels.cs
Agents: keys, hashed secrets, grants, the feed, the two-way channelsrc/Agents.cs, src/ApiAgents.cs, web/agents.js
The brain: markdown entries, search, compression against the questionsrc/Brain.cs, src/BrainSrv.cs
The help assistant: the pages/settings/health/brain evidence, "Remember this"src/Help.cs
Buddy: the page, the model's thinking, the hands card and the mode switchweb/buddy.js
Buddy's hands: the rulebook, the safe edits, the modessrc/Hands.cs
The lookup path for a model with no internet: the three engines and the fetchsrc/Web.cs (the ai_web_* settings themselves are declared in src/AiConf.cs)
The panel's own search box (Ctrl+K) - a different thing, and permission-filteredsrc/Search.cs

Chapter 11 covers the health and diagnose screens these features report into, and the ports/firewall reference. Chapter 09 covers playit.gg, which is a different way of exposing a game server than the Cloudflare tunnels used here.

Chapter 11. Operations, the API and troubleshooting

This chapter is for the person who keeps a PlayPort machine running: what to watch, what to back up, what the health board is telling you, what the panel's API actually serves, and what to do when something breaks. It assumes the panel is installed and has servers or sites on it (chapter 01); it does not repeat how to create them.

Everything here was read out of this tree's sources. Where the code is ambiguous the chapter says so rather than guessing, and the Files behind this chapter table at the end names the file each section describes.


Running a panel day to day

The panel is a normal Windows program with a console window and a data folder. Its own operation comes down to four things: knowing whether the pieces are up, knowing when something failed, not running out of disk, and having a copy of the data folder that is not on the same disk.

A reasonable routine:

  1. Look at the health board once a day (Health in the sidebar, or GET /api/health). It walks every subsystem once and answers per row - see the health board below.
  2. Look at the activity list (Activity, GET /api/tasks). Everything that takes time - an install, a backup, a MariaDB download, a certificate order - is a job with an outcome, and a failed job stays in the list with a Retry button. A job nobody retried is the usual way a feature is quietly dead for a week.
  3. Watch the storage number (Settings, Storage, GET /api/storage). It measures the whole data root and lists the servers and sites by size. Its ?fresh=1 form is the Re-measure button: it throws the measurement cache away first, which is what to use when the number looks stale rather than when it looks large.
  4. Keep a copy of the data folder somewhere else. See backing up and moving the panel's own data.
  5. After an update, look at the health board and the panel's own version line again. The panel reports the build's check line on its Panel row when the build was not verified by this tree's gates (--version prints the same line as its second line): that is not a failure, it is the artifact telling you it was built with the checks skipped.

Two logs carry almost every answer in this chapter:

WhereWhat it is
playport.log in the data root (appendix C)the panel's own log: startup, every API failure with its stack, logins, installs, certificate orders, port moves
<data>/instances/<server>/console.logthe server's own console output, rotated to console.log.old; the same text the Console tab streams (chapter 04)

The health board

Health.Report walks the machine's subsystems once and returns one row per area with a verdict in words an operator can act on. It is staff only: GET /api/health answers 403 for a plain user, because the board reports on the machine, not on one tenant's corner of it.

Four verdicts, and the fourth is the one most panels get wrong:

VerdictMeaning
okworking
warnworking, but something will bite later (a certificate inside its renewal window, a backup that has not run for a week)
failnot working right now
offdeliberately not set up, so nothing is wrong

The board is only useful because the second line of every row is the next step rather than a restatement, and because each row is produced by measuring something. The areas it covers:

The board ends with a summary sentence and a single verdict (fail beats warn beats ok), so a script can watch it without parsing the rows.

The Settings page also carries a separate Server readiness panel. That one is a preflight for a machine about to face the internet - see reachability below.


Diagnose: what the panel says when a server broke

The panel explains a server failure with a rule engine over the console output, not with an AI and not with a service. That is deliberate: sending a customer's console to somebody else's computer is a privacy leak and a paid dependency, and the engine works offline, instantly and for free. Its one honest limit is that it can only say "I don't recognise this" - it never invents a cause.

Every finding carries the same fields, and they answer the three questions a non-technical owner actually has:

FieldWhat it holds
titlewhat broke, in one line
whatwhat that means, in plain words
stepsexactly what to click, in order
rulethe rule id that matched (e.g. java.too-old, port.in-use, mc.eula)
severityinfo, warn, error or fatal
areaserver, java, network, config, disk, mods or auth
snippet, linethe console line that triggered it, and where it was

Where it is reachable:

What the engine does not recognise is kept, not dropped. Every crash no rule explains is filed in the review queue (Activity in the panel) and written to unknown-crashes.json in the data root:

The panel's routes over that queue are /api/unknowns (list, rescan, re-check, clear). This is a filing cabinet, not a diagnosis: nothing in it leaves the machine either.


The ports and firewall reference

Appendix B is the authoritative table of every port the panel listens on and what proves who is talking. This section is the Windows half - what has to be open, and what Windows refuses.

The panel and every HTTP website are served through http.sys, which is why two separate things have to line up for a machine that faces the internet:

  1. A URL ACL - Windows checks the http:// prefix form separately for each spelling. Binding a non-loopback HTTP port without a reservation answers access denied, and the panel prints the exact command to fix it: netsh http add urlacl url=http://+:8080/ user=Everyone add-urlacl.ps1 in this repository does it once, for ports 8080, 8081 and 80, and for every prefix form http.sys checks separately (+, *, 127.0.0.1, localhost, and one hard-coded LAN address - that last one is a script you should read before you run it, because the address in it is not yours). A site that was refused says the same sentence with its own port (Util.PlainPortError), which is the difference between a website that is broken and a website that is not allowed to listen.
  2. An inbound firewall rule per port - the panel's HTTP is http.sys, so a rule scoped to PlayPort.exe never matches. add-firewall.ps1 adds rules named PlayPort web TCP <port> for 80, 8080 and 8081. Every other port - a game server, a site on 8092, a TLS port - needs its own rule, which the panel adds itself while it is running elevated.

"The port is taken" is two different faults. A port already bound by another program is one; a port shadowed by another program's wildcard reservation (http://+:8080/, which some vendor tools and phone-link utilities install) is the other, and it is much nastier: the panel binds successfully, answers its own health check, and every browser gets an HTTP 503 from a queue nobody is serving. PlayPort measures this rather than assuming it - at startup it asks itself on the selected port, and if the answer does not come back it moves to a port that does answer and saves the choice, then records the one admin command that gives the old port back:

netsh http delete urlacl url=http://+:8080/     # or: netsh http show urlacl

The moved-from port is shown in the panel (the settings/installation card, portMovedFrom in /api/install), which is why the address a panel answers on can change without anyone choosing it.

HTTPS is not the same path. A TLS listener is its own socket rather than an http.sys prefix, so a site on a tlsPort does not need a URL ACL - it needs the certificate and the port open (appendix B, chapter 05).

Running elevated is what lets the panel open non-loopback ports and add firewall rules for you. Without it, loopback-only panel plus loopback-only sites still work, and the readiness board says so in as many words.


The security model

Stated plainly, because a hosting panel's security is mostly a list of what is not protected.

What is protected, and how

ThingHow
The panel itselfa login session; the panel binds loopback (127.0.0.1) only by default, so nothing on the LAN reaches it. --bind-all (or Reachable from the network) is a deliberate act, and a non-loopback bind needs the URL ACL above
PasswordsPBKDF2 (Rfc2898DeriveBytes), 60,000 iterations, 32-byte output, a per-user salt. The iteration count is part of the stored format's meaning: raising it invalidates existing passwords and needs a migration
Sign-in guessingper-name rate limiting: 8 failures inside a minute and that name gets 429 Too many attempts - wait a minute
Sessionsa token stored with the accounts in auth.json, valid for 30 days from issue, checked on every request; an expired or unknown token is 401 Not logged in
Per-resource rightsfour capabilities - view, control, edit, files - granted per user per instance and per site. Every request is checked on the server, where the answer is authoritative; the panel also attaches the same answer to each resource it lists (perms) so the interface can hide an action you would only get a 403 for
Rolesadmin manages everything; moderator manages everything; user sees what it owns or was granted. The first account created by first-run setup is the owner/admin
The three program doorsthe model/agent port and the brain port demand an API key whatever they are bound to, so finding the port is not enough to use the hardware
The core plugin's reportthe panel writes a per-server token into that server's plugin folder. It authorises exactly one thing: this server's status. It cannot read anything, start anything or act as a user - which matters because the file lives on a game server that customers may share
Stopping the panelPOST /api/panel/shutdown works only over loopback and only with the per-run secret in the panel's control file, so a build script or a second copy can stop it gracefully without administrator rights, and nothing on the network can

API keys are a different door from the panel. An agent key is minted under Agents, looks like ppk_..., is shown once at creation, and is presented as Authorization: Bearer ppk_..., X-Api-Key: ppk_... or a key= query parameter (for tools that cannot set headers). It is accepted by the model and brain ports, not by the panel's own /api/* surface, which takes the session token. A key can also carry a per-minute limit, a model allow-list and a tool grant - that is chapter 10's subject.

What is not protected - read this part


Backing up and moving the panel's own data

The panel does not have a button that archives its own data root - its backup feature archives servers (chapter 04). The panel's own state is a folder, and the honest procedure is a file copy that respects what is running:

The consistent copy (recommended)

  1. Stop the game servers that matter (their worlds are the part that is painful to lose) and stop the mail/DB-dependent things you care about.
  2. Stop the panel: the console window closed, or PlayPort.exe --stop for a graceful shutdown that lets saves finish.
  3. Copy the whole data root. It is the folder the Settings page names (dataRoot), normally playport-data/ beside the exe.
  4. Start the panel again.

What is in that folder (appendix C, and the rest grows with the features you switch on): settings.json and auth.json, instances/ (every server), sites/, steamcmd/, MariaDB's own data/, databases.json, mail, peers.json, networks.json, unknown-crashes.json and playport.log.

Two cautions that are worth more than the procedure

Restoring is the reverse: stop the panel, put the folder back (whole, or only the parts you restored), start it. --purge-data on an uninstall is what deletes that folder, and data is kept by default when you uninstall (chapter 01).

Moving the installation or the data root has a documented path rather than a drag-and-drop: --install --dir DIR, --data DIR, --move-data / --no-move-data, and --finish-install for the case where a panel is running and could not move its own folder - chapter 01 owns that procedure.


The REST API reference

The panel's API is /api/* on the panel port. It is not the model API: an OpenAI-compatible client pointed at the panel gets an answer that names the model port, the base URL and the kind of key to use, instead of a 404 that reads like the feature was removed. The three doors are appendix B.

Authentication. Everything needs a signed-in session token except the six public endpoints. The token is the value returned by POST /api/login (or from first-run setup); it is sent the way the panel's own page sends it, and POST /api/logout deletes it. Agent keys (ppk_...) are for the model and brain ports and are not accepted here.

The public endpoints - no session, and each one is deliberately small:

Method and pathBody / queryWhat it does
GET /api/needsetup-{ needSetup, authTrouble }. The difference between this panel is new and this panel cannot read its accounts file - both have zero accounts, and only one of them may show the first-run form
POST /api/setup{ name, pass }creates the owner account, once. 403 when the panel is already set up, and 403 with the reason when the accounts file is damaged
POST /api/login{ name, pass }{ token, user }, or 401, or 429 while rate limited
GET /api/version-{ app, version, startedAt, pid } - the identity probe a second copy uses to notice this one
POST /api/panel/shutdown-stop this panel gracefully; loopback only, and it must present the per-run secret from the panel's control file
POST /api/plugin/report-the core plugin reporting from a game server, with its per-server token. It authorises that one server's status and nothing else

The resource surface. The dispatcher reads the third path segment as the resource and then matches sub-routes, so the shapes below are patterns, not a list of every generated path. :id is a server or site id; { } is the request body.

ResourceRoutesNotes
statsGET /api/statsthe dashboard numbers; seg carries the sub-shape
versionsGET /api/versionswhat the machine has and what can be installed
javaGET /api/java?mc=&type=what a version needs and what is installed; the requirement comes from Mojang's metadata, cached in the data root
minecraftGET /api/minecraft/software, GET /api/minecraft/extensions?type=&loader=the software catalog the picker reads, and which extension kind a type loads
startersGET /api/starters?type=starter packs, plus the core plugin's profiles
instancesGET /api/instances, POST /api/instances { }, GET|POST|DELETE /api/instances/:id, then /:id/<action>the big one - see the action table below
networks/api/networks and /api/networks/:slug/...start/stop a whole network (chapter 09)
templates/api/templatesthe game template catalog
usersGET /api/users, POST /api/users { name, pass, role }, DELETE /api/users/:id, POST /api/users/:id/pass, POST /api/users/:id/role, GET|POST /api/users/:id/grantsadmin only. Grants carry kind (instance or site), id, and the four capability booleans; a grant naming a resource that no longer exists is dropped rather than stored, and a row with every box cleared is not stored at all
settingsGET /api/settings, POST /api/settings { }reading is any session; writing is admin only. A partial body does not blank fields the caller did not mention
portsGET /api/portsevery door this panel listens on, with what each is for (appendix B)
destinationsGET|POST /api/destinationsbackup destinations; admin only both ways
sites/api/sites, /api/sites/:id, /:id/<action>see the site action table
tunnel/api/tunnel/..., e.g. /api/tunnel/:id/quickCloudflare tunnels (chapter 05)
playit/api/playitthe playit.gg agent (chapter 09)
dbGET /api/db, POST /api/db { op: install|start|stop }, GET /api/db/all, POST /api/db/issue { kind, id, name }, GET /api/db/owner/:ownerId, DELETE /api/db/:dbinstall, all and the MariaDB lifecycle are admin only; issuing needs the resource (admins/moderators anywhere, a user only on their own), and dropping is the owner's or an admin's (chapter 06)
mail/api/mail and its sub-routesstaff see the relay credential, plain users get it blanked; a mailbox is readable by its owner, and staff can read any (chapter 10)
models, agents, ai, brain/api/models, /api/agents, /api/ai, /api/brainthe AI surface in the panel; the model keys themselves are minted under agents (chapter 10)
guideGET /api/guidethe setup guide. Any signed-in account may read it; staff additionally get the relay username shown
alertsGET|POST /api/alerts, POST /api/alerts/teststaff only
healthGET /api/healthstaff only
readyGET /api/readythe deployment preflight, admin or moderator only
storageGET /api/storage (and ?fresh=1)the data root and per-resource sizes, staff only
searchGET /api/search?q=&limit=one box over everything the caller may see, so a hit is always something you can open
tasksGET /api/tasks, POST /api/tasks/clear, POST /api/tasks/:id/retrythe activity list and its retries
unknowns/api/unknowns, plus rescan / re-check / clearthe unrecognised-crash queue
installGET /api/installread-only by design. It returns the install plan - the folder a button would have chosen, where this copy is running, the data root and its size, whether a job is queued, whether panels are running on it, the port it answers on, and the one line a person can copy: PlayPort.exe --install | --update | --uninstall. Every other method is refused with 405 and that same sentence, so an old cached page gets told where the job moved instead of a 404

Instance sub-actions (/api/instances/:id/<action>), with the capability each one needs. The mapping is enforced per request, and it is the same table whether the caller is an admin or a grant-holder:

ActionWhat it isNeeds
GET / POST / DELETE on /api/instances/:idread the server, change its settings, delete itview / edit
start, stop, restart, killthe lifecyclecontrol
inputsend a console commandcontrol
consolethe console text (the live stream is the WebSocket /ws/console?id=&token=, chapter 04)view
configread/change the server's own settings fileview / edit
filesthe file managerfiles
backups, backups/downloadlist/create/restore, and take the archive awayview / edit, download always files
schedulesthe server's schedulesview / edit
worldread the world folder, and repair itview / files
gamethe game settings (properties) editorview / edit
playerswhitelist, operators, bansview / control
plugins / extensionsthe plugin and mod browser and installer for this serverview / files
bridgeinstall or refresh the network bridge on a Velocity proxyview / edit
sql / databaselink, unlink or test a database for the plugin(the route's own check)
updateinstall/update the server softwareedit
diagnose, metrics, lag, overviewthe plain-English diagnosis, the charts, the lag report, the summary cardview
duplicatecopy the serveredit
snippetssaved console commandsview / control
installjavainstall the Java runtime this server needsedit

Site sub-actions (/api/sites/:id/<action>): toggle (control), files and importzip (files), tls (view for reading, edit for changing), preview and diagnose (view), and GET/POST on the site itself (view/edit).

Two honest caveats about this table. First, the dispatcher is a chain of prefix matches with dynamic segments, so a route added by a newer build will not appear here; what is listed is what this tree's Api.cs names. Second (and this is the reason the manual says it): a control drawn in the panel's page is not proof that a route exists - a sibling chapter found website controls whose backend is not in this tree at all. When in doubt, the code is the authority and this table is a map.

Errors. A request body that is not a JSON object is 400 Request body is not valid JSON.; a missing or expired session is 401 Not logged in; a missing right is 403 with a sentence; an unknown route is 404 unknown endpoint; a handler that throws logs the whole stack to playport.log and answers with a plain sentence rather than the exception's own words. That last rule is this tree's: what a buyer reads is a sentence, and the stack goes to the log.


Troubleshooting by symptom

Each entry names the check and the chapter that owns the detail. Where a command is given, it is the one this tree prints.

The panel will not start

The console window was closed

The panel and the game servers it started are that process tree. Closing the window stops them, and Windows may not let a server finish its world save. Start the panel again and check the servers' consoles for what was cut short (chapter 04); a world that was being created at that moment is the case the repair path exists for (chapter 03).

A server will not start, or keeps restarting

  1. Open its Console tab and read the diagnosis - it is usually one of five things, and the engine names which: Java too old or missing (java.too-old, java.missing), memory (java.heap), a port another server already holds (port.in-use), the EULA (mc.eula), or a bind refused by Windows (port.lan-acl). Each finding carries the steps.
  2. If nothing matches, the crash is filed in the review queue - that is the case where the panel is saying "I don't know" rather than "nothing is wrong".
  3. PlayPort.exe --diagnose <the server's console.log> gives the same answer as JSON without the panel running.

Chapter 03 owns versions, loaders, the start executable, restart policy and the world repair.

A website answers 404, or nothing at all

TLS fails, or a certificate does not renew

Readiness has a Certificates row and a Websites row, and the site's own TLS panel reports the last order. The common causes are structural: the CA cannot reach port 80 on that domain (http-01 challenge), the domain does not resolve here, the site is bound to loopback, or the port is held by another program. Chapter 05 owns the ACME flow, staging vs production and the self-signed and PFX alternatives.

The database will not start

The health board's Database row says which of the states it is in, in its own words, and chapter 06 maps each sentence to a cause - most often port 3306 already taken by another MySQL, a data folder that another MySQL instance owns, or a disk that is full. The panel deliberately does not adopt a foreign 3306 without saying so.

The core plugin is not loading in a server

A model will not load

The guard comes first, and it is about the machine rather than about trust: this panel also runs game servers, so a model competes for the same GPU, RAM and heat budget. A limit that was crossed is stated with the measured value next to the threshold it broke, and a load is refused rather than started and cut short. Chapter 10 owns the model screen and the tiers.

The machine is unreachable from the internet

Run the Server readiness preflight (staff, GET /api/ready). It measures rather than repeats: your public address, whether the DNS records would name a private address (the single most common way a real deployment fails while every field looks plausible), reverse DNS, outbound port 25 and 587, the mail domains, the website bind address, HTTPS coverage, and the panel's own exposure. It uses four verdicts - pass, warn, fail, info - and every row carries the next step.

The three things that must line up for anything to be reachable: the router forwards the port, Windows has an inbound rule for it, and the program is bound to something other than loopback. A tunnel or playit.gg exists so you can skip the first of those (chapters 05 and 09).

A change could not be saved

The health board's Saved settings row names the file, and playport.log carries the full sentence. The two directions are different faults: a file that could not be read (a damaged settings.json, left where it is or kept aside) and a change that could not be written (a read-only file, a full disk, another program holding it). The panel records both rather than replacing a file it could not read with defaults - which is what used to make this silent.


What to send when you ask for help

The people who can actually answer have to reproduce what you saw. In order of value:

  1. The diagnosis, as JSON - PlayPort.exe --diagnose <log> output, or the server's diagnose view with ?full=1. It carries the rule ids, the severities and the console lines that matched.
  2. The server's own log - <data>/instances/<server>/console.log (and console.log.old if the failure rotated away).
  3. The panel's log - playport.log from the data root. This is where an API failure's stack went: the response deliberately carries a plain sentence instead, so the log is the only place with the detail.
  4. The health board - a screenshot or the JSON from GET /api/health, including the summary line.
  5. The version - PlayPort.exe --version, both lines: the build, and whether the build's checks ran or were skipped.

Please do not send auth.json, databases.json or the mail credentials: the first two are the panel's keys to itself, and the third is other people's mail. The diagnosis and the two logs are enough for almost every fault in this chapter.


Files behind this chapter

AreaSource
The API surface, its dispatcher and every route group in the referencesrc/Api.cs (../src/Api.cs), src/ApiAgents.cs (../src/ApiAgents.cs), src/Http.cs (../src/Http.cs)
The health board and its verdictssrc/Health.cs (../src/Health.cs)
The rule engine and the finding shapesrc/Diagnose.cs (../src/Diagnose.cs)
The unrecognised-crash queue and its filesrc/Unknowns.cs (../src/Unknowns.cs)
The readiness preflightsrc/Ready.cs (../src/Ready.cs), src/NetConf.cs (../src/NetConf.cs)
Accounts, sessions, rate limiting, grants, the API keyssrc/Store.cs (../src/Store.cs), src/Agents.cs (../src/Agents.cs), src/Util.cs (../src/Util.cs)
Port shadowing, the startup port move, the elevation and ACL messagessrc/Program.cs (../src/Program.cs), src/Util.cs (../src/Util.cs)
The two Windows scripts (URL ACLs and firewall ports)add-urlacl.ps1 (../add-urlacl.ps1), add-firewall.ps1 (../add-firewall.ps1)
Activities, storage accounting, the install cardsrc/Jobs.cs (../src/Jobs.cs), src/Disk.cs (../src/Disk.cs), src/Install.cs (../src/Install.cs)
The console log the diagnosis readssrc/Instances.cs (../src/Instances.cs), src/LogFeed.cs (../src/LogFeed.cs)
The command-line diagnose and inspect flagssrc/Program.cs (../src/Program.cs)

Chapter 12. The module reference

The core plugin is one jar built out of modules, and chapter 08 explains what that means: each module has an id, a label, its own switch, its own config section, its own commands and its own <command> check. Appendix D in the top-level manual is the registry of all of them - the list /playport status prints, in the order it prints it.

That table answers which modules exist. This chapter answers the question a reader has next, module by module: what does this one do, how do I turn it on, and what can I tell it to do?

Every module has an entry on one of the pages below, and every entry answers those three questions in the same shape. The pages are grouped by what their modules have in common rather than by registry order, so read them as thematically related batches and use the table here (or /playport status) to find a module by id.

How to read an entry

Each entry is headed by the module's id and the label the plugin itself reports:

### `econ` - Economy, /pay, /sell and a salary for time played

The label is not decoration: it is the same string /playport status, the help menu and the panel's Plugins page show, so a heading you can search for in the manual is one you can search for in the product. Under it, three answers:

Some entries add further labels when there is something more to say: Commands. with the usage, Permissions. with the nodes (see chapter 08 for how nodes reach groups), and Panel. where the panel gives the same content an editor of its own (the editors are in chapter 07).

Where a module's settings live. Almost always in a top-level section of pkg/config.yml named after the module - econ:, shop:, dungeons: - and the switch is not in that section but in the modules: block at the top of the same file. The small modules near the end of the registry (the batch that closed the last gaps against the reference plugins) are often a command and a handful of keys in a section of their own, and a few take their arguments on the command line and have no keys at all. Each entry tells you which it is.

Precedence is the one thing that applies to every module. An edit you make in config.yml wins over the profile, and the profile wins over the shipped defaults: config.yml starts as a complete copy of the defaults, so a line you have never touched does not block the profile. The full statement of that rule, with the profile: key, is in chapter 08.

The pages

PageModulesWhat they have in common
01 - Server foundations14: perms, econ, hearts, ess, claims, mines, ranks, staff, chat, ah, ench, server, admin, cosThe modules a server of any kind switches on: groups and permissions, the economy, hearts, essentials (homes, warps, kits, teleport requests), claims, prison mines, ranks, staff tools, chat, the auction house, enchanting, the server commands, admin commands, cosmetics
02 - Economy, factions and shops9: factions, beacons, spawners, shop, perf, items, customench, models, hoppersThe modules that make a survival or factions server worth playing: faction land, beacons and sieges, spawners, the shop, performance tooling, custom items, custom enchantments, custom models and chunk hoppers
03 - Players and games10: rtp, potions, familiars, arena, npc, trade, guard, vaults, koth, teamsThe modules that act on players: random teleport, debuff potions, cosmetic pets, boss arenas, NPCs, player trading, cheat detection, private vaults, king of the hill, teams
04 - Farming, worlds and progression11: harvest, elevator, fishing, skyblock, plots, minigames, bridge, quests, tools, dungeons, uiThe modules a longer game is built from: chain mining and fast farming, elevators, fishing, skyblock, plots, minigames, the network bridge, quests, levelling tools, dungeon blueprints, operator-authored menus
05 - The living world6: worldgen, disasters, seasons, votes, service, analyticsThe modules that change the world around the player and the ones that talk to the outside: generated worlds, world events, seasons, vote sites, the outbound client, the staff snapshot
06 - Commands for items, blocks and rewards13: anvil, attachcommand, bansgui, bounty, button, colors, compass, cooking, crates, donate, doors, drops, editorThe small modules of the compatibility batch - one job each, usually a command or a placed block: anvil costs, a command bound to the held item, the bans menu, bounties, reward buttons, the colour list, a compass that points, cooking stations, crates, giving an item away, doors that move as a set, extra mob drops, the in-game editor
07 - Protections, effects and storage13: effect, enchantlock, flags, flyc, giftpet, killall, kiteditor, limiter, lockip, lootchests, market, merchant, notargetThe rest of the batch: potion effects, blocked enchantments, world flags, timed flight, handing over a pet, clearing mobs, saving a kit, entity limits, IP locking, refilling loot chests, the black market, villager windows, and mobs that ignore a player
08 - Players, mobs and world state13: orders, parkour, patrol, revive, reward, saveall, security, shoot, shulker, silentchest, skin, sound, stackBuy orders, parkour courses, staff patrol, reviving a downed player, the join reward, saving the world on demand, contraband scans, firing a projectile, opening a shulker from the hand, silent containers, remembered skins, sounds, stacked drops
09 - Signs, actions and the engines13: stream, tags, top, voice, warpsign, actions, dialogs, dsign, hooks, icsign, prewards, schematics, jobsStreamer tooling, equipable chat tags, /top, proximity voice, warp signs, and the engine modules the others are built on: the action engine, dialogs, dynamic signs, hooks, interactive signs, playtime rewards, schematics and jobs

Adding a module to the manual

The registry is generated, so half of this reference maintains itself and half does not - and it is worth knowing which is which when you add a module:

Files behind this chapter

What this section documentsSource
The registry every page and the table are held topkg/src/ppcore/ModuleIndex.java (../pkg/src/ppcore/ModuleIndex.java)
The module contract: id, label, switch, vocabulary, problems(), checkSummary()pkg/src/ppcore/Module.java (../pkg/src/ppcore/Module.java)
Runtime command registration, namespaces, the yield table, forceModulespkg/src/ppcore/Interop.java (../pkg/src/ppcore/Interop.java)
The shipped settings every entry quotes a default frompkg/config.yml (../pkg/config.yml)
The template profiles that ship modules switched onpkg/profiles/ (../pkg/profiles/)
The gate that holds the table and these pages to the registryharness/gates/manual-gate.js (../harness/gates/manual-gate.js)

Server foundations - the modules every server switches on

These are the modules a server of almost any kind switches on: who a player is and what they may do (perms), money (econ), the everyday essentials (ess), land (claims), staff tools and chat, and the commands an operator types. Each one is a section of pkg/config.yml plus a switch in the modules: block at the top of it; chapter 12 explains that shape and the precedence rule that applies to all of them (your edit beats the profile, the profile beats the shipped defaults). The five switches to look at first are modules.econ.features.pay, modules.ess.*, server.afk.pause-salary, perms.groups and ranks.ladder - they are the ones a fresh install gets wrong in a way players notice.

perms - Groups and permissions (replaces a permissions plugin)

What it does. This module is the server's permission system: groups with a prefix, an inherits chain and a list of nodes, plus the ladder in ranks that hands a group out. It replaces a permissions plugin - a server running this does not need LuckPerms - and it is where every other module's node reaches a player. ppcore.perm.admin is the staff half (creating groups, editing them, reading somebody's effective nodes), and a node that no group grants is a finding in harness/gates/perm-gate.js, not a surprise in game.

Turning it on. modules.perms.enabled: true, which is the shipped default. It has nothing else to switch on, but it is the module you must configure first, because everything below it in this file is gated by the groups here: a fresh install whose default group omits a node is a feature that answers "you do not have permission" to every player. The staff group in the shipped file is the worked example of the whole surface.

What you can configure. perms.defaultGroup (the group a player is put in when nothing else says otherwise) and perms.groups.<name> with four keys per group:

KeyWhat it is
prefixwhat the group shows in front of a name, e.g. &c[Staff] &r
inheritsthe groups this one inherits from - the shipped ladder is default ← member ← veteran ← elite, with staff inheriting member
permsthe nodes the group grants, one per line. A wildcard works (ppcore.kit.*), and so does a node that only exists as a rank allowance (ppcore.homes.10, ppcore.hoppers.amount.8, ppcore.plots.amount.<n>)
weightthe group's ordering where the platform compares two groups

Commands. /perm set <player> <group> and the rest of the /perm verbs behind ppcore.perm.admin - the shipped member group's own comment names /perm set <player> member as the way a server hands a rank out by hand.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/perms.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help perms prints the same names and the same nodes, line for line, because both read this one registration.

/perm (/permissions, /pperm)

permission: ppcore.perm.admin · always · Perms.java

Groups and permissions, and the whole of it is one command with subcommands. Bare /perm prints the five it answers - list, info, set, luckperms and reload - which is the screen to reach for before guessing at a subcommand. /perm list reads the groups out of perms.groups with what each one inherits and how many nodes it carries, /perm info [player] names the group a player is in and prints every node that group resolves to (their own group when you leave the name out, so a player can ask about themselves), and /perm set <player> <group> moves somebody - the target has to be online, the group has to exist, and the change is applied to them at once and written to the console log, because a group change is the kind of thing that has to be explainable a week later.

/perm reload re-reads the groups from config.yml and re-applies them to everyone online, which is what to run after editing the group ladder. /perm luckperms is the way out of this module: it writes LuckPerms command files for the template in effect (or, with all, for every template) - the groups, their parent chain, their prefixes and every node - so an operator switching to LuckPerms reads the files first and runs /perm luckperms apply when they are ready, and the module stands down by itself while LuckPerms is installed. Everything here needs ppcore.perm.admin; there is no read that a player is allowed to make about another player's nodes.

Permissions. ppcore.perm.admin for the admin verbs; everything else in the tree is a node this module's groups are compared against.

econ - Economy, /pay, /sell and a salary for time played

What it does. The economy, and the shape of it is deliberate: money enters the world in exactly two ways, both gameplay - a salary paid for time online and selling things you gathered - and there is no purchase command anywhere in the plugin. /pay moves money between players (staff can read it in the log), /cheque writes a balance onto paper that pays out when it is deposited, /sell pays the configured price for what is in your hand and /baltop is the leaderboard. Other modules read a balance through this one: the shop, a bounty, an auction, a faction bank and the hearts withdraw price all spend or print money the economy owns.

Turning it on. modules.econ.enabled: true (the default), with two feature switches: modules.econ.features.pay for player-to-player /pay, and modules.econ.features.cheque for /cheque. A server that wants no economy at all switches the module off; a module that spends money (ess.kits.list.<kit>.price, shop, potions, hoppers) then says so in its own check rather than handing out free things. Players need ppcore.econ.pay, ppcore.econ.sell and ppcore.econ.cheque - all three are in the shipped default group.

What you can configure. econ.symbol ($), econ.startBalance (100.0, what a player has on their first join), econ.salaryPerMinute (5.0 - paid to every online player once a minute, and 0 switches the salary off; server.afk.pause-salary is what stops a client left open overnight collecting it), and econ.prices - the /sell table, one MATERIAL: amount per line. Deleting a line makes that item unsellable, and an item that is not listed cannot be sold at all.

econ.numberFormat is the whole of how money prints, and every price in the plugin goes through it: decimalSeparator (.), groupingSeparator (,), minimumIntegerDigits, maximumIntegerDigits, minimumFractionDigits and maximumFractionDigits (all 2), hideFraction (false) and the optional shortScale block (enableShortScaleNumbering: false, so 1000000 stays a number a player can subtract from). /money check names the combinations that read wrong, such as two separators that are the same character.

Commands. /pay, /sell, /balance, /baltop, /cheque, and /money check for the format report. /setworth and /generateworth belong to the shop rather than here.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/econ.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help econ prints the same names and the same nodes, line for line, because both read this one registration.

/money (/balance, /bal, /cash)

permission: none of its own · always · Econ.java

Money, and the admin give/take/set forms. Typed with nothing after it by a player, /money prints that player's own balance. /money <player> prints somebody else's, and reading another player's balance is its own node - ppcore.econ.others - so without it the name is ignored and you are shown your own balance rather than refused. The console has no balance of its own and must name a player.

The three staff forms are /money give <player> <amount>, /money take <player> <amount> and /money set <player> <amount>; they are checked against ppcore.econ.admin inside the handler rather than at the registration, and the config key money is therefore one command with two faces. The target has to be online (there is nobody to pay if they are not), the amount has to be a number and cannot be negative, and a take of more than somebody holds empties the balance instead of refusing. Every one of the three is written to the console log as econ: <who> <verb> <amount> to <player>, which is the audit trail this plugin relies on instead of a purchase command.

/pay (/send)

permission: ppcore.econ.pay · always · Econ.java

Pay another player out of your own balance: /pay <player> <amount>. The money leaves your balance and arrives in theirs, so nothing is created - and both sides are told what happened, and both lines are logged, because /pay is the usual shape of a real-money trade done off-platform and staff should be able to see it. It refuses four things, each in its own words: a name that is not online, your own name (paying yourself is not a transfer), an amount that is not a number, and an amount you do not have. Players only, so the console has to act through /money give. modules.econ.features.pay switches player-to-player payments off without removing the command: with it off, /pay exists and answers that payments are switched off, which is a different thing from a player mistyping the command.

/baltop (/balancetop, /richlist)

permission: none of its own · always · Econ.java

The leaderboard: the ten richest balances on the server, numbered, printed to whoever typed it. It reads what the plugin has already recorded, so it needs no arguments and no permission, and a server that has just been set up is told that no balances are recorded yet rather than being shown an empty list.

/sell (/worth)

permission: ppcore.econ.sell · always · Econ.java

Sell what you are carrying for the price this server sets, from the econ.prices table. /sell hand sells the stack in your hand and /sell on its own means the same thing; /sell all walks your whole inventory and pays for everything in it that has a price. Anything the table does not name is left alone

nothing you have is sellable, the command says so instead of paying zero. A hearts item is refused here even when it is worth something, deliberately: a heart is won by killing another player, and a lifeline that can be sold into the economy and bought back is a pay-to-win loop with extra steps. It answers with how many items went, what they fetched and what your balance is now.

/cheque (/check, /withdraw)

permission: ppcore.econ.cheque · only while modules.econ.features.cheque is on · Econ.java

Paper money, and the one command here that turns a balance into something a player can carry - which is what makes a trade between two players possible without a staff member in the middle. Hold a plain piece of paper, type /cheque <amount>, and the amount leaves your balance and is written onto the paper, named Cheque for $X with the issuer and Right click to deposit in its lore. Whoever right-clicks it gets the money into their own account, so a cheque is a bearer instrument: hand it over and it is theirs, which is the point of it. A right-click on a container or another interactive block is left to the container, so carrying one does not stop you opening chests. The amount may be written with the currency symbol and thousands separators; it must be a positive amount and no more than your balance, and it is charged the moment the cheque is written. The id printed in the config is the one a rename would move, so /withdraw and /check are names for the same command. Writing cheques and depositing them are both logged, and the deposits go through this module's own economy, so econ.symbol and the number format apply to what the paper says.

Permissions. ppcore.econ.pay, ppcore.econ.sell, ppcore.econ.cheque (all default), ppcore.econ.admin (staff - the shipped group has it).

hearts - PvP hearts (lifesteal): won and lost only in combat

What it does. Lifesteal hearts, with the rule that a heart only changes hands when one player kills another: a mob death costs nothing unless you switch that on separately, and there is no way to buy a heart back. Run out and the configured out action happens - ban (the shipped answer), spectator or none. Players spend hearts too: /hearts revive brings one back for unban hearts, and the player-driven revive pays out of the reviver's own hearts. There are tiers, so a heart item can be worth more than one heart (the shipped Double Heart and Heart Bundle), and /hearts withdraw turns a heart into a tradeable item worth withdrawWorth.

Turning it on. modules.hearts.enabled: false as shipped - it is off unless a template turns it on, because a server that has not decided about lifesteal should not be running it. With it on, modules.hearts.features.withdraw decides whether /hearts withdraw exists. Players need the module's own nodes (the shipped home for them is the staff group's ppcore.hearts.admin for the admin verbs); check the class the module reports.

What you can configure. hearts.max (10), hearts.start (10), hearts.unban (5), hearts.out (ban), hearts.banMessage, hearts.pvpOnly (true - non-PvP deaths cost nothing), hearts.changeMaxHealth (true, so the bar shrinks as hearts are lost) with healthPerHeart (2), hearts.item (NETHER_STAR) and itemName (&cHeart) for the item that represents one, hearts.dropWhenFull (false), and the withdraw pair withdrawFloor (1, the level it may never take you below) and withdrawWorth (1). hearts.tiers names each worth more than one heart.

Two anti-exploit rules ship off and are worth reading before switching on: antiexploits .samePlayerCooldown (0 - a killer may not take two hearts from the same victim inside N seconds) and antiexploits.preventIfOnSameIP (false - a sibling on one connection would otherwise refuse every kill). deathReasonsOverride and mobDeathReasonsOverride are the death lines, keyed by cause and by mob; customBans.enabled (false) with customBans.commands runs console commands instead of the ban; playerRevive.requiredHearts (5) and cooldownSeconds (0) price the player-run revive; and combatLog.enabled (false) with combatLog.seconds (15) makes quitting mid-fight cost a heart to the last person who hit you. Non-PvP loss is two separate switches on purpose - mobs.enabled (false, with mobs.perMob) and other.enabled (false, with other.amount).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/hearts.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help hearts prints the same names and the same nodes, line for line, because both read this one registration.

/hearts (/heart, /hps)

permission: none of its own · always · Hearts.java

The lifesteal command, and every part of it is a subcommand of the one name. Typed with nothing after it - or with a player's name - it answers how many hearts somebody has out of the cap (hearts.max, ten by default, and hearts.start for a player who has never been counted). /hearts help prints the same list the plugin answers as a refusal, so a player who mistypes a subcommand is told the shape rather than left guessing.

Players use three of them. /hearts top is the leaderboard - the ten heartiest players, or a line saying nothing has been recorded yet. /hearts withdraw <n> turns hearts into the configured heart item so they can be traded, which is the only way a heart changes hands outside a kill: it keeps at least hearts.withdrawFloor hearts (one by default) and hands over whole items, where one item is worth hearts.withdrawWorth hearts, so asking for three hearts at a worth of two is refused rather than quietly rounded. /hearts revive <player> brings somebody back, and it is two verbs in one: staff with ppcore.hearts.admin do it for free, and a player pays hearts.playerRevive.requiredHearts of their own hearts for it, no more often than hearts.playerRevive.cooldownSeconds. A name nobody has ever seen on this server is refused before any hearts are taken, so a typo cannot cost anybody five hearts, and a revive lifts the ban and returns the player at hearts.unban hearts.

The staff half is /hearts set|add|remove <player> <n>, /hearts max for the current cap and /hearts setmax <n> to cap everyone for this run - the reply says that hearts.max is what makes it permanent. Setting, adding and removing all clamp between zero and the cap, work on an offline player's record as well as an online one, and re-apply the health bar so the hearts on screen match the number.

ess - Homes, warps, kits, teleport requests, spawn and /back

What it does. The everyday verbs, and each family has its own switch: homes (with extra home slots sold as rank allowances rather than bought), warps with a cooldown, kits (a grid, a preview, cooldowns, one-time kits and kits that are bought with in-game money), /tpa requests, the spawn, /back and the death point behind it. It also owns the small movement and sign verbs: /jump to the block you are looking at, /up and /down for the first place you can stand above or below you, and /se / /signedit for the sign you are looking at.

Turning it on. modules.ess.enabled: true (default) and then the feature switches: modules.ess.features.homes, .warps, .kits, .tpa, .tpaall, .spawn, .firstspawn, .back, .dback, .jump, .updown and .signedit - each one removes its commands rather than hiding them. Kits come from ess.kits.list.<name> plus the entries the panel's kits editor writes (ess.kits.panel: true offers that editor). A warp a staff member places is behind ppcore.staff.warp; a kit is unlocked by ppcore.kit.<id> unless the kit's own permission line says otherwise, and none means every player may claim it.

What you can configure. ess.spawnOnJoin (false), ess.backOnDeath (true), ess.homes.max (3) with ess.homes.defaultName (home) and ess.homes.perms - the node-to-slots map that turns ppcore.homes.5 into five homes; ess.warps.cooldownSeconds (0, and staff who place warps never wait); ess.tpa.cooldownSeconds (30) and ess.tpa.timeoutSeconds (60); ess.jump.range (8) and ess.updown.range (24).

The kit settings are worth reading as a set. ess.kits.cooldownSeconds (3600) is the default every kit falls back to, and per kit you can set permission (defaults to ppcore.kit.<id>, none for everybody), cooldownSeconds (0 for none), oneTime: true (claimable once ever, and /kit then says claimed instead of counting down - a limit, not a rate), price (what it costs in money, 0 for claimed), icon (a material this server does not have is drawn as a barrier and reported by check) and items as MATERIAL:amount lines. ess.kits.menu.size (27) and previewSize (27) are the two windows, and ess.kits.messages.* is the whole vocabulary of the menu, the preview, the purchase screen, the chat list and the refusals - the shipped vip and deluxe kits are the two worked examples of a bought kit.

Commands. /home, /set-style home verbs, /warp, /kit (the grid), /kit <name>, /kit list, /kit give <player> <kit> and /kitcdreset (staff), /tpa /tpahere /tpaccept /tpdeny, /tpaall, /spawn, /back, /dback [player], /resetback [player], /jump, /up, /down, /se, /signedit.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/ess.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help ess prints the same names and the same nodes, line for line, because both read this one registration.

/home (/homes)

permission: none of its own · always · Essentials.java

Teleports you to one of your homes. /home with nothing after it does the obvious thing rather than printing a menu: with one home it takes you there, and with several it lists them with how many of your allowance you are using. /home <name> goes to that one, and the default name - what a bare /sethome writes - is ess.homes.defaultName, shipped as home. A player with no homes yet is told to use /sethome rather than being sent nowhere.

/sethome

permission: none of its own · always · Essentials.java

Sets a home where you are standing: /sethome for the default name, /sethome <name> for one of your own. Your allowance is the rank ladder's own node (ppcore.homes.<n>), and reaching it refuses with the list of homes you already have and the word to delete one first - a new name at the limit is refused, while moving an existing home is always allowed because it takes nothing extra. The home is written to the plugin's data store rather than to a file of this module's own, so it is the same record /playport sql data names.

/delhome (/removehome)

permission: none of its own · always · Essentials.java

Deletes one of your homes: /delhome for the default name, /delhome <name> for one of your own. A name you do not have is refused rather than ignored, because "you never had that home" and "the delete worked" look identical from the outside otherwise. The record is removed rather than emptied, so a deleted home stays deleted after a restart and does not reappear in the next /home list.

/spawn

permission: none of its own · always · Essentials.java

Teleports you to this plugin's own spawn. Until staff set one, it says so and points at /setspawn - and the server's own spawn keeps working meanwhile, so a server is never left with no way home. It is the same destination new players arrive at once /setfirstspawn is set, unless you have set the two differently on purpose.

/setspawn

permission: ppcore.staff.spawn · always · Essentials.java

Sets this plugin's spawn to where you are standing. Staff only (ppcore.staff.spawn), and it has to be run in game - the console has no location to read. It writes to the module's own file, so the spawn survives a restart, and it is what /spawn and, by default, a first join use.

/setfirstspawn (/firstspawn)

permission: ppcore.staff.spawn · always · Essentials.java

Where players arrive on their very first join, as opposed to every join after it. Run it where you want them to land - staff only, in game - and /setfirstspawn remove hands that job back to the ordinary spawn. It is the setting that makes an introduction area possible without changing where players respawn for the rest of their time on the server.

/warp (/warps)

permission: none of its own · always · Essentials.java

Teleports to a server warp, or lists them: /warp with nothing after it prints the warp names, and /warp <name> goes there - a name that does not exist is refused by name. Warps have their own cooldown (ess.warps.cooldownSeconds, 0 by default, and it only bites when it is above 0), reported with the seconds left to wait; staff who place warps (ppcore.staff.warp) are not made to wait for their own tooling.

/setwarp (/createwarp)

permission: ppcore.staff.warp · always · Essentials.java

Creates a warp where you are standing, or moves an existing one to it: /setwarp <name>, staff only and in game. The name is lowercased, and setting a name that already exists is how a warp is moved rather than a second warp with the same name. The warp is written to the module's own file immediately, so it is there for the next player who asks.

/delwarp (/removewarp)

permission: ppcore.staff.warp · always · Essentials.java

Deletes a warp by name, staff only: /delwarp <name>. The record is removed rather than emptied, so a deleted warp does not come back on the next restart, and its name is then free for a new one.

/warpgroups (/warpsgroups)

permission: none of its own · always · Essentials.java

The warp list, grouped: for a server whose warps have grown past one line, this prints them by the group each one was given (see /editwarp), so a player can find the one they want rather than reading forty names. It needs no permission beyond the command itself and takes no arguments.

/editwarp

permission: ppcore.staff.warp · always · Essentials.java

Changes a warp that is already placed, without moving it: staff only. /editwarp <warp> <newName> renames it - which is the way to fix a typo or a name that has outgrown its meaning - and /editwarp <warp> group <group> puts it into a named group, which is what /warpgroups prints. Use /setwarp to move a warp to where you are standing; this verb is for the parts of a warp that are not its coordinates.

/se (/signedit, /editsign)

permission: none of its own · always · Essentials.java

The sign editor: reads or changes the sign you are looking at. /se follows the server's own sign behaviour, /se <line> <text> writes that line, and the multi-word text is joined back together, so spaces need no quotes. It is the source plugin's verb under the source plugin's name (signedit, editsign), and its own feature switch turns it off for servers that would rather signs were signs.

/kit (/kits)

permission: none of its own · always · Essentials.java

Claims a server kit, or hands one to somebody else. /kit on its own opens the kit grid - every kit an item, with its state drawn on the icon, which is the form a player who has never read the config can actually use - and /kit list prints the same thing as lines, for the console or anybody who wants to compare two kits without clicking. /kit <name> claims one, refusing by name for a kit that does not exist, telling a player which node a kit they cannot yet have needs, saying how long a cooldown has left, and refusing a one-time kit that has already been claimed. /kit give <player> <kit> (ppcore.kit.give) puts a kit into another player's inventory, dropping what will not fit, and deliberately does not touch their cooldown: giving a kit is not claiming one, and a gift should not cost the receiver a timer. A kit with a price opens the purchase screen instead of claiming, from every way in.

/kitcdreset

permission: ppcore.kit.admin · always · Essentials.java

Clears a player's kit cooldown, so the kit is ready now: /kitcdreset <player> [kit], staff only (ppcore.kit.admin). Naming a kit clears that kit only; without a name it clears the player's records for every kit. This is the verb for the support case - "I lost my items in a crash and the kit is still on cooldown" - rather than for changing what a kit costs.

/kitusagereset

permission: ppcore.kit.admin · always · Essentials.java

Forgets that a player has claimed a one-time kit, so they may claim it again: /kitusagereset <player> [kit], staff only (ppcore.kit.admin). One kit at a time with a name, or every kit without one. Use it when a one-time kit was lost through no fault of the player's; the cooldown half of a kit is /kitcdreset's job.

/tpa (/tpask)

permission: none of its own · always · Essentials.java

Asks another player whether you may teleport to them: /tpa <player>. The request waits for an answer for ess.tpa.timeoutSeconds and then expires on its own, so neither side is left holding a question. Players only, and the reply is the other player's /tpaccept or /tpdeny.

/tpahere (/tpahere)

permission: none of its own · always · Essentials.java

Asks another player to come to you: /tpahere <player>. It is the same request in the other direction, and accepting it moves the person who was asked to where the asker is standing - not the other way round.

/tpaccept (/tpyes)

permission: none of its own · always · Essentials.java

Accepts the teleport request waiting for you. The direction is the one the request was made in: a request to teleport to you moves the asker to you, and a request for you to come to them moves you to them. With nothing pending it says so, and if the player who asked has since left the server, it says that instead of moving anybody.

/tpdeny (/tpno, /tpdecline)

permission: none of its own · always · Essentials.java

Refuses the teleport request waiting for you, and tells the player who asked - so a refusal is an answer rather than a silence they wait out. With nothing pending it says so, and there is no penalty or cooldown for refusing: the request simply ends.

/tpaall

permission: none of its own · always · Essentials.java

Asks everybody who is online at once whether you may teleport to them: /tpaall. Each of them gets the same request and answers it with their own /tpaccept or /tpdeny, so the ones who agree are moved and the ones who do not are left alone. Useful for a staff member arriving at an event, and it is the reason /tpa and /tpahere had to have their directions right.

/back (/return)

permission: none of its own · always · Essentials.java

Returns you to the place you last died. With no death recorded yet, it says so rather than moving you anywhere. It reads the same recorded point the rest of the module's death verbs use, so a staff member sending somebody back to their death and a player typing /back arrive at the same place.

/dback (/deathback)

permission: none of its own · always · Essentials.java

Sends a player back to the place they last died: /dback [player], staff, with the target optional so the console can name one. It is /back with a target on it, reading the same recorded death point, which is the verb staff reach for when somebody needs their items back and does not know the command.

/resetback (/clearback)

permission: ppcore.staff.teleport · always · Essentials.java

Forgets a player's death point: /resetback [player], staff (ppcore.staff.teleport). After it, that player's /back answers that no death location is recorded rather than sending them somewhere they no longer mean to go. Useful before gifting a plot that still carries somebody else's death point.

/jump

permission: none of its own · always · Essentials.java

Jumps you to the block you are looking at, within the server's own reach, which is the quickest way up a cliff or into a build without touching /tpa. Players only, and if there is nothing to land on it does nothing rather than dropping you somewhere worse. It is grouped with /up and /down for a reason: a server that does not want players cutting through its terrain turns all three off together.

/up

permission: none of its own · always · Essentials.java

Climbs you to the first place you can stand above your head: /up, optionally with the number of blocks to look up, which is how a player gets out of a mine shaft or a cave without building their way up. Players only, and it refuses plainly when there is nowhere above to stand.

/down

permission: none of its own · always · Essentials.java

The same thing downwards: /down, optionally with a distance, drops you to the first place you can stand below - the way into a ravine, a mineshaft or a cellar. Players only, and it refuses when there is nothing below rather than letting you fall somewhere you did not ask for.

Permissions. ppcore.homes.<n> for home slots, ppcore.kit.<id> / ppcore.kit.*, ppcore.kit.cooldown.bypass, ppcore.kit.give, ppcore.kit.admin, ppcore.staff.warp, ppcore.staff.spawn.

claims - Land claims with earned claim blocks

What it does. Player land claims built like WorldGuard's flag surface. Claim blocks are earned by playing - blocksPerMinute minutes online at a time, never bought - claims may not overlap, and protection is decided per claim by seven flags with six levels of precedence: a value written for one player beats one written for their group, which beats the claim's own value, and a claim answers to the people it trusts (owner, member, or ppcore.claims.admin) before it falls back to the config and then to the built-in default. That ordering is what stops an owner locking themselves out of their own claim with a flag they just set.

Turning it on. modules.claims.enabled: true (default). Nothing else has to be created first: a player earns blocks from the moment the module is on, and /claim check reports a flag this module has no code for rather than leaving it looking like protection that refuses nothing.

What you can configure. claims.blocksPerMinute (3 - the whole earning rate) and claims.flags.<flag>, one line per action an owner would ask about:

FlagShippedWhat it decides
builddenybreaking and placing blocks, and buckets
usedenychests, doors, buttons, levers, anvils
pvpdenyplayer against player inside the claim - both sides have to allow it
entryallowwalking in at all
mob-spawningallowa mob appearing inside, naturally, from a spawner or from an egg
explosionsdenya blast taking blocks out of the claim
interact-entitydenyright-clicking an animal, a villager, a stand, a frame

The two allow defaults are the two an owner would be surprised to find changed underneath them. entry and mob-spawning are the claim not deciding where a player may walk and not stopping the world spawning a cow; everything else denies, because that is what a claim is for. Older servers' claims.protect.* keys (containers, interactive, entities, pvp, explosions) are still read when the new name has no entry, so an upgrade does not lose the protection it had.

Commands. /claim flag <flag> <value> for the claim's own value or /claim flag <flag> <player|group:name> <value> for one player or a group - the subject comes before the value, and none in place of a value hands the answer back down a level. /claim flags prints every flag with two levels: what the claim says and, when it differs, what it says to the reader. /claim check is the config report.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/claims.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help claims prints the same names and the same nodes, line for line, because both read this one registration.

/claim (/claims)

permission: none of its own · always · Claims.java

Land claims, bought with claim blocks earned by playing rather than with money: claims.blocksPerMinute accrues them while a player is on, and a claim costs its own volume out of the free balance. Most of the command is about the claim you are standing in, which is the situation an owner is always in when the question comes up - /claim on its own describes it (name, owner, volume, how many are trusted, and where to read the flags), and /claim flags prints that claim's rules.

Making one is two corners and a name: /claim pos1 and /claim pos2 mark them wherever you stand, and /claim create <name> checks the three things that can go wrong - corners in different worlds, a volume larger than the blocks you have free, and an overlap with a claim that already exists - each in its own words and before anything is written. Names are yours alone, lower-cased, and /claim list is your own claims with a block tally, /claim blocks the arithmetic on its own: how many you have earned, how many are in use and what the earning rate is. /claim delete <name> (alias /claim remove) hands the land back, and staff with ppcore.claims.admin may delete somebody else's by name.

Trust is per claim and works by standing in it: /claim trust <player> and /claim untrust <player> add and remove a builder, or take the claim as a third argument when you are not there. The flags are WorldGuard's own seven - build, use, pvp, entry, mob-spawning, explosions and interact-entity - so an owner arriving from a region file does not have to learn a second vocabulary, and each one is enforced rather than recorded. The shape is the thing to know: /claim flag build deny sets it for the claim, and /claim flag build Beta deny sets it for one player, with group:name in place of a name setting it for a whole group. One value for one player beats their group, which beats the claim's own value, which beats the file, which beats the built-in default, and none hands a value back down to the level that then answers. A flag written for one player needs that player online, and a wrong value is answered with the shape rather than with "no".

Permissions. ppcore.claims.admin for the staff half; players act on their own claims.

mines - Prison mines that refill themselves

What it does. A mine is a cuboid plus a weighted palette, and it refills on command, when a percentage of it has been mined out, or never (manual) - which one is part of the mine's own record rather than this file. Blocks mined count towards the rank ladder, and payPerBlock turns a mine into paid work. Refills are announced.

Turning it on. modules.mines.enabled: false as shipped - a prison module is a decision, not a default. With it on, modules.mines.features.teleport adds /mine tp <name> for players. The mines themselves are created in game and stored in the plugin's own data file, not in config.yml: the palettes below are what a mine is painted with, and the cuboid, the refill rule and the percentage are the mine's own. Staff need ppcore.mines.admin.

What you can configure. mines.announce (true - a refill says so), mines.blocksPerTick (4000 - the refill rate, and the first number to lower if a refill is visibly lagging a busy server) and mines.payPerBlock (0.0 - off by default; anything above 0 needs the econ module).

mines.palettes.<name> is a weighted list of MATERIAL: weight lines, and the weight is the share of the mine that material takes. Four palettes ship hand-written - stone, deep, nether and the ore-focused examples - and a generated block of 58 more (one per ore, per biome and per theme: diamond, sculk, end, coral, badlands, ancient_city, trial_chamber and the rest) is written into the file when the jar is built, from the server's own material list. A hand-written palette is never touched by a rebuild, and the build refuses to ship a generated block that has drifted.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/mines.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help mines prints the same names and the same nodes, line for line, because both read this one registration.

/mine (/mines)

permission: none of its own · always · Mines.java

The prison-mine command: a mine here is a cuboid of blocks plus a palette, refilled from that palette as it is mined out. Bare /mine means /mine list, which names every mine with its size in blocks, its palette, the percentage it refills at, and whether that refill is automatic or has to be asked for; a mine that is refilling right now says so, and a server with none says that instead of printing an empty list.

Setting one up is a sequence, not one command: stand at a corner and run /mine pos1, stand at the opposite one and run /mine pos2, then /mine create <name> [palette]. Both corners have to be in the same world, the palette has to exist (the default is stone), and creation fills the mine immediately - the reply gives the volume and the palette it is filling with. A name that already exists is reused as the region rather than refused, so moving a corner and re-running create is how a mine is resized. /mine palettes lists what mines.palettes defines, and /mine palettes <name> reads one palette on its own - a listing of sixty palettes is more text than any chat window carries.

The rest is maintenance and visitors. /mine reset <name> refills it now instead of at the threshold, /mine delete <name> forgets the region and leaves the blocks where they are, and /mine info <name> is the status line for one mine: volume, world, how many blocks have been mined since the last refill, the refill threshold and whether a refill is running. /mine tp <name> (alias /mine warp) teleports you to the top of it and goes through modules.mines.features.teleport, so an owner who does not want players dropping into mines can switch the verb off without losing the mine. Everything except list, palettes, info and tp needs ppcore.mines.admin, and the teleport is the only one of those four a player can use without the node.

ranks - Earned rank ladder (drives permission groups)

What it does. The earned ladder: every rung is a name, a permission group and the requirements to reach it, and a rung is checked against things this plugin can see for itself - blocks mined, minutes played and money earned in game. money is a balance to hold, not a fee: ranking up never spends anything, which is what keeps the ladder earned rather than bought. Because a rung's group is a group from perms, "ranks are permission sets" is literally what happens rather than a promise.

Turning it on. modules.ranks.enabled: true (the default). ranks.autoPromote: true promotes a player as soon as they qualify; with it off, promotions are staff's (ppcore.ranks.set). A server that keeps its own permissions plugin instead hands the promotion over: ranks.command is a console command template, shipped empty, e.g. lp user {player} parent set {group} - with it set, the ladder decides when and the other plugin does the granting.

What you can configure. ranks.ladder is an ordered list, and each rung is:

KeyWhat it is
namethe rung's name, as a player sees it (Newcomer, Member, Veteran, Elite)
groupthe perms group it grants (default, member, veteran, elite)
blocksblocks mined (0, 500, 5000, 25000)
minutesminutes played (0, 30, 300, 1200)
moneythe balance to hold, not to spend (0, 250, 5000, 25000)

Every requirement is cumulative and the ladder is read as an order, so a rung a player qualifies for is the rung they get. The way to change the pacing is to change these numbers, not to add a second ladder.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/ranks.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help ranks prints the same names and the same nodes, line for line, because both read this one registration.

/rank (/ranks, /myrank)

permission: none of its own · always · Ranks.java

Where you are on the earned ladder, and what the next rung costs. /rank tells a player their rank by name and by position ("rank 3 of 6") and then prints what the rung above asks for and how far they are from it - blocks mined, minutes played, and money when the econ module is running. It is a per-player command: the console is told to be a player, because there is no place on a ladder for a console to be. A server whose ranks.ladder is empty answers that there is no ladder configured rather than showing an empty one.

/rankup (/promote)

permission: none of its own · always · Ranks.java

Take the next rung. /rankup checks the requirements against the player's own record and, when they are met, moves them and hands their new group over - through ranks.command when that is configured, or to this plugin's own perms module otherwise. When they are not met it refuses with a sentence that says the rank is earned by playing and prints the same progress /rank does, so a player learns what to do rather than that they failed. At the top of the ladder it says so instead. Promotion is also checked on the core's per-minute tick when ranks.autoPromote is on, which is why a player can rank up without typing anything - the command is the manual half of the same move, not a second implementation of it.

/rankinfo (/rinfo)

permission: none of its own · always · Ranks.java

What one rung asks, and where a player stands against it. /rankinfo describes your own rung, /rankinfo -next the one above (and says when there is no next rung), /rankinfo <rank> a rung by name, and /rankinfo <player> [rank|-next] somebody else's position - a player name is tried before a rank name, which is the order an operator means when a word could be either. Each answer names the rung's position in the ladder, whether the reader can rank up now, what it costs, the group it grants, and what comes next. It is a read, so the console may run it: a console names a player when it wants progress shown, and without one it reads the rung alone and says no player was chosen rather than inventing a place.

/ranklist (/rlist)

permission: none of its own · always · Ranks.java

The whole ladder in order, one line per rung, with the reader's place marked - and the next rung's requirements printed against every other line, because plain chat cannot hover the way the source plugin's menu does. /ranklist <player> marks somebody else's place instead, which is the same read a console gets: with no player named there is simply no marker, and the header says so rather than guessing. A name that has never joined this server is refused by name.

/rankset (/rset)

permission: ppcore.ranks.set · always · Ranks.java

The correction, and the only verb here that moves a ladder without the rungs being earned. It is staff work for a reason: it costs nothing, there is no way for a player to run it at themselves, and an operator with ppcore.ranks.set may put somebody at a named rank or at a position number. It refuses by naming what it could not find - the account, or the rank - tells the player when they are online, names the change from the old rank to the new one, and writes rankset: <who> set <player> to <rank> to the console log. When the player is offline the ladder still remembers the move; the reply says so and also says when ranks.command will only hand the group over on their next join.

/rankdown (/rdown)

permission: none of its own · always · Ranks.java

Step down to an earlier rung, which is a loss and therefore takes a confirmation. Bare /rankdown lists the rungs below the player, or says they are at the bottom one; /rankdown <rank> describes what stepping down to that rung gives back - the group - and asks for /rankdown <rank> confirm before doing it. A rank that is not below the current one is refused, and the argument is read before the bottom-of-the-ladder answer, so the two refusals are reachable rather than every miss being told there is nothing below. What the player has earned is kept: this moves the rung and the group, not the record.

staff - Staff tools: bans, mutes, warnings, vanish, freeze, reports

What it does. Punishments with a memory: /ban, /mute, /warn and /kick write the reason, the staff member and the expiry into staff.yml, so /history can still answer "why was I banned" a month later, and warnActions runs automatically at a warning count (3: "mute 1d", 5: "ban 7d"). It also owns vanish and the staff mode that pairs it with flight, the saved inventories behind /invsave and /invload (a record, not an easy keep-inventory - nothing goes back to a player unless staff run the load), the address memory behind /sameip and /checkaccount, staff chat, alerts and reports.

Turning it on. modules.staff.enabled: true (default), and four feature switches: modules.staff.features.alerts, .staffchat, .ips (remember the address each account joins from) and .invsnap (the saved inventories). invsnap is separate on purpose: a server sharing a server with a dedicated logging plugin stands that part down without losing bans. commandName etc. - every punishment verb can be renamed in the modules: block / commands: section; staff.defaultBanReason, defaultMuteReason.

What you can configure. staff.defaultBanReason ("Banned by staff") and defaultMuteReason ("Muted by staff"), staff.broadcastPunishments (true), staff.ips.max-per-name (5 addresses kept per account, newest first - a repeat visit is a date change rather than a new line), and staff.warnActions (shipped empty; keys are warning counts and values are "mute 1d"-style actions). staff.vanish.nightVision (false) and staff.mode.vanish / staff.mode.fly (both true) decide what staff mode turns on. staff.inv is the snapshot block: keep (5 per player, oldest dropped and never the newest), saveOnDeath (true - a record taken before the drops clear, and it is not keep-inventory), deathNode (empty = everybody), ignoreEmpty (true), materials (never written into a snapshot) and staff.inv.restore.items / .health / .hunger / .exp / .effects - which parts /invload puts back, with health clamped to the player's current maximum so a snapshot cannot raise it.

Commands. /ban, /mute, /warn, /kick, /vanish, /freeze, /history, /notes, /report, /sameip, /checkaccount, /inv (open a player's inventory), /invsave, /invload, /invlist, /invcheck, /invremove, /invremoveall, /ipban, /sudo, /patrol, and /staffmsg for staff chat.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/staff.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help staff prints the same names and the same nodes, line for line, because both read this one registration.

/ban

permission: ppcore.staff.ban · always · Staff.java

Ban a player by name: /ban <player> [reason], and the reason is everything after the name, so it can be a sentence. The ban goes into the server's own name ban list, which is what actually refuses a connection, and into this module's record, which is what a staff member can read back later; the player is kicked with the reason shown, and staff are told - or just the sender, when staff.broadcastPunishments is false. With no reason written, staff.defaultBanReason is used, so a ban is never recorded with nothing beside it. A ban the server itself refuses is reported with the server's own words rather than swallowed, and the console is answered as well as the player, because a ban typed at the console used to be the one that printed nothing.

/tempban

permission: ppcore.staff.ban · always · Staff.java

The same ban with an expiry: /tempban <player|address> <1d2h30m> [reason]. The length is read as 12h, 7d, 30m, perm and combinations of them; one that cannot be read, or that lands in the past, is refused with those examples rather than being treated as permanent. Everything else is /ban: the reason defaults to staff.defaultBanReason, the player is kicked with the time shown, staff are notified, and the record keeps the length so /history can explain it. A temporary ban is lifted by the server's own clock at the time it was given for - /unban is for ending one early.

/ipban

permission: ppcore.staff.ipban · always · Staff.java

Ban an address rather than an account: /ipban <player|address> [reason]. A player's address can only be read while they are online (the server only knows an address while somebody is connected to it), so either name a player who is here or name the address itself - which is the workflow this verb belongs to, reading /sameip for a set of accounts and acting on what it found. The ban goes into the server's address ban list and the record keeps both the address as it was spelled and the account it was read from, so /ipbanlist cannot report a ban the server has never heard of. ppcore.staff.ipban covers /ipban and /tempipban; the account ban list is a separate list, so a name ban does not ban an address.

/unban

permission: ppcore.staff.ban · always · Staff.java

Lift a ban: /unban <player>. It pardons the name in the server's own ban list and clears the punishment record, tells staff who did it, and reports when the server refuses - which is the case worth seeing, because a record cleared while the ban list still holds the name looks like a working unban from inside the game. It lifts a name ban: an address ban lives in the server's other list, so /ipbanlist and /checkban <address> are where to see whether a banned address is still being refused.

/banlist

permission: ppcore.staff.ban · always · Staff.java

The server's own list of banned names, printed as one line of names, or a line saying nothing is active. It reads the server's list rather than this module's record on purpose: the list is what refuses a connection, so it is the honest answer to "is this player banned". If the server's list cannot be read at all, the failure is reported instead of an empty list.

/ipbanlist

permission: ppcore.staff.ban · always · Staff.java

Both halves of the address record, because they disagree in a way that matters. It prints every address the server is actually refusing, with the reason and the expiry, and then any address this plugin recorded a ban for that the server is no longer refusing - which is a ban that quietly stopped working. The second half is the point of the verb: an operator reading only the server's list cannot tell a lifted ban from a forgotten one.

/tempipban

permission: ppcore.staff.ipban · always · Staff.java

An address ban with a clock: /tempipban <player|address> <1d2h30m> [reason]. The length reads the same way /tempban reads it (30m, 12h, 7d, perm, and combinations), and a length that cannot be read is refused with those examples. Everything else is /ipban - the player has to be online or the address named outright, the record keeps the address and the account, and /ipbanlist shows it beside the server's own entries.

/checkaccount

permission: ppcore.staff.inspect · always · Staff.java

Everything this server knows about one account, on one screen: whether they are online, when they were first and last seen, minutes played, blocks mined, kills and deaths, their group and ladder rung, where they are standing and in what game mode, their balance, their warnings, whether a mute or a ban is in force, and every address on record. The two numbers that belong to other modules are read from those modules while the player is online and from the shared record when they are not, so this is not a second opinion about somebody's balance. A name that has never joined is refused by name rather than printed as a blank record.

/sameip

permission: ppcore.staff.inspect · always · Staff.java

The accounts behind an address, and the addresses behind a player: /sameip <player|address> prints each address on record and which accounts have joined from it. It reads what the server recorded, so the answer comes with its three honest limits, said out loud rather than left to be discovered - the record can be switched off (modules.staff.features.ips), it only holds what the server saw while it was on, and a shared address is not proof of a shared person, because a school, a cafe and a VPN all look the same from here.

/haspermission

permission: ppcore.staff.inspect · always · Staff.java

Ask a yes/no question about a node: /haspermission <player> <node>. The answer names where it came from - the player's own attachments while they are online, or their group's nodes when they are not - so "no" can be acted on rather than merely believed. A player this server has never seen is refused by name. /checkperm is the same question with the other half attached.

/realname

permission: ppcore.staff.inspect · always · Staff.java

The account behind a nickname: /realname <nickname> resolves a display name through the cosmetics module's own store. It reads the players who are here now, which is exactly what makes it useful during a conversation and exactly why it cannot find somebody who has logged out - the reply says so, and points at /checkaccount for the half that starts from the account instead. Colour codes in the nickname are ignored for the comparison, so what a player sees is what can be typed here.

/kick

permission: ppcore.staff.kick · always · Staff.java

Disconnect a player: /kick <player> [reason], with Kicked by staff as the reason when none is written. The kick is recorded against the player and staff are notified, because a kick is a punishment in this module's record even though nothing is left behind on the account. Kicking somebody who is not online is refused as such rather than answered with success.

/mute

permission: ppcore.staff.mute · always · Staff.java

Stop a player talking: /mute <player> [length] [reason]. A second argument that reads as a duration (30m, 12h, 7d) becomes the expiry and the rest is the reason; a word that is not a duration is read as part of the reason, so /mute Beta being rude mutes indefinitely with the reason intact. The reply to the muted player names the time and the reason, staff are told, and the record is written - which is what /history and /checkaccount read. With no reason at all, staff.defaultMuteReason is used.

/unmute

permission: ppcore.staff.mute · always · Staff.java

Let a muted player talk again: /unmute <player>. It clears the mute in the record and tells both the player and staff, and it is the same verb that ends a shadow mute - the difference is that the reply says when the mute it lifted was a shadow one, so the record reads correctly afterwards.

/warn

permission: ppcore.staff.warn · always · Staff.java

Warn a player: /warn <player> <reason>. The warning is appended to their numbered list with the time and your name, so it is readable and reviewable rather than a private note, the player is told to their face, and staff are notified with the running total. Warnings are also where automatic escalation lives: staff.warnActions maps a count to an action - 3: "mute 1d", 5: "ban 7d" - and the action is taken when the count is reached, with the record naming it as automatic.

/warnings

permission: ppcore.staff.warn · always · Staff.java

A player's warnings, in order, one per line, as /warn wrote them. An empty list says the player has no warnings rather than printing a header and nothing under it. Reading needs ppcore.staff.warn, the same node that writes them, and the list this prints is the one /warnings shows - there is no second copy kept for reporting.

/vanish

permission: ppcore.staff.vanish · always · Staff.java

Hide from other players, and show yourself again - one toggle, and the reply says which way it went. Vanish is live state owned by this module: it is not written to disk, so it does not survive a restart, and other modules ask this one whether a player is hidden (votes treats a vanished voter as offline rather than keeping a second copy of the fact). The items-dropped question is answered by /vanishedit: a vanished player walks over their drops unless you say otherwise.

/vanishlist

permission: ppcore.staff.vanish · always · Staff.java

Who is vanished right now, with the two things that differ between them: whether a vanished player still picks up items, and whether staff are told when their vanish is toggled. It reads the live set rather than a cached copy, so the answer changes the moment the state does, and a server with nobody hidden says that in one line.

/vanishother

permission: ppcore.staff.vanish · always · Staff.java

Vanish or reveal somebody else: /vanishother <player> [on|off]. With a direction written the verb means exactly that and does not guess; with none written it toggles, which is the shape an operator typing it in a hurry wants. The reply names the new state unless you targeted yourself, in which case the visible change is the answer. The player has to be online - vanish is live state and there is nothing to hide for an offline account.

/vanishedit

permission: ppcore.staff.vanish · always · Staff.java

What vanish means for one player: /vanishedit <player> <pickup|announce> <on|off>. pickup decides whether a vanished player may pick items up (off means they walk over drops, which is what makes vanish useful for watching a fight without changing it), and announce decides whether staff are told when that player's vanish is toggled. The player has to be online, because these are properties of the live session and they are not written to the file.

/warnlist

permission: ppcore.staff.warn · always · Staff.java

Every warning on record for every player, sorted, with the count and the newest one beside each name, and the total across the server in the header. It walks the lists /warn appended to rather than a report of its own, so it cannot disagree with /warnings about one player. A clean record says there are no warnings rather than printing an empty list.

/kickhistory

permission: ppcore.staff.history · always · Staff.java

Recent kicks, newest first, from the log /kick writes. The cap is the point: a busy server's kick log is thousands of lines and the question is always about the last few, so it prints twenty and says so - /kickhistory all raises that to two hundred for the case where the question really is about the history. Every line names the player it belongs to.

/reloadcmi

permission: ppcore.staff.reload · always · Staff.java

Re-read config.yml and apply it to this module. The name is the source plugin's, the node is ppcore.staff.reload because reloading the file is the same power as /playport reload, and the reply says out loud that it reloads this plugin's own settings rather than another plugin's thousands of files. Nothing is stopped or restarted by it: the punishments in force, the mutes and the saved inventories are all still where they were.

/freeze

permission: ppcore.staff.freeze · always · Staff.java

Stop a player moving, or let them move again: /freeze <player> toggles, and the reply says which way it landed. A frozen player is told why and cannot walk, no matter what they are holding, which is what makes this the verb for the moment an operator wants to talk to somebody. The state is live and shared with the three other names for it below - there is one set of frozen bodies, not one per verb.

/unfreeze

permission: ppcore.staff.freeze · always · Staff.java

Let a player move again, written as a direction rather than a toggle: /unfreeze <player>. Having both spellings is deliberate, because a console or a script that wants somebody moving again should not have to know what state they were in; /freeze remains the toggle for the case where an operator is looking at the screen.

/freezeall

permission: ppcore.staff.freeze · always · Staff.java

Freeze everyone online at once, for the arena-wide moment - a restart being announced, a glitch being contained - and report how many players it caught, in the singular when that is one. Nobody online is answered as such rather than as a silent success. It is the same freeze as /freeze, so /unfreezeall is what returns everybody to normal.

/unfreezeall

permission: ppcore.staff.freeze · always · Staff.java

Let everyone online move again, with the count in the reply for the same reason /freezeall prints one: an operator firing this into a busy server wants to know it reached somebody. It is the bulk form of /unfreeze, and it leaves nothing frozen behind.

/cuff

permission: ppcore.staff.freeze · always · Staff.java

The source plugin's own name for the freeze, with the direction written as a word: /cuff <player> [true|false]. A bare name cuffs, false (or off, no, uncuff) releases, and any other trailing word leaves the direction at cuff rather than being misread as one - which is how a -s flag or a stray word behaves. It sets the same frozen state /freeze sets, so the two verbs cannot disagree about whether a player is held.

/staffmode

permission: ppcore.staff.mode · always · Staff.java

The one toggle an operator lives in: vanish and flight together, and off again. Which of the two is part of it is config - staff.mode.vanish and staff.mode.fly - so a server can have a staff mode that is only a quiet invisibility, or only the ability to fly around. It is a per-player toggle for the session and the reply says which way it went.

/stafftp

permission: ppcore.staff.teleport · always · Staff.java

Teleport to a player: /stafftp <player>. It moves you to them rather than them to you, which is the direction a staff member wants when they are answering a report, and it does not ask the target's permission - the node is the check. A name that is not online is refused as such.

/staffchat

permission: ppcore.staff.chat · always · Staff.java

Toggle a channel where everything you type goes to staff only: /staffchat on and /staffchat off, with the reply saying which. Players only, and the channel is per player for the session. It is the staff-side answer to a busy public chat rather than a replacement for /msg.

/shadowmute

permission: ppcore.staff.mute · always · Staff.java

Mute a player without telling them: /shadowmute <player> [length] [reason]. The player is deliberately not told - that is the verb - their lines reach nobody, and staff are told, because a punishment nobody can see in a record is one nobody can review. The length reads the way /mute reads it, and /shadowmute <player> off or /unmute <player> ends it; with no reason written, staff.defaultShadowMuteReason is used.

/sudo

permission: ppcore.staff.sudo · always · Staff.java

Make a player run one of their own commands: /sudo <player> <command>, or /sudo <player> c:<text> to make them say a line in public chat. The command runs as that player, so the server checks their permissions on the way in - sudo cannot hand out a power the target does not have, which is the whole safety argument for the verb. A player holding ppcore.staff.sudo.bypass cannot be made to do anything at all, and the refusal names their node. Every use is announced to staff with who made whom do it.

/report

permission: ppcore.report · always · Staff.java

Report a player to staff: /report <player> <reason>. Players only, and the report goes to the staff who are online and into the alert list /alerts prints, with the reporter's name kept on it. The reporter is thanked and nothing else is promised, which is the honest answer: whether anyone is looking is not something this command can know.

/alerts

permission: ppcore.staff.alerts · always · Staff.java

The staff reports, newest last, ten of them, or /alerts clear to empty the list once they have been dealt with. It is the read that belongs with /report: a report nobody can find again is not a report. An empty list says so rather than printing a header.

/notes

permission: ppcore.staff.notes · always · Staff.java

The private notes staff keep about a player: /notes <player> (or <player> list) reads them numbered, /notes <player> add <text> appends one with the time and your name, /notes <player> remove <id> takes one out, and /notes <player> clear empties the list. A note's number is its position in the list, so removing one renumbers what follows; the numbering is the list rather than a second index that could fall out of step with it. Nothing in this store is visible to the player it is about.

/note

permission: ppcore.staff.notes · always · Staff.java

The same store as /notes, under the source plugin's own name, with the four verbs spelled out: /note <player> add <text>, remove <id>, clear and list. It is registered as its own verb rather than as an alias because it carries those verbs, and a second argument that is none of them is read as the note text, so the older spelling - a player's name followed by the note - still works. A note added under one name is read back under the other, because there is one list.

/history

permission: ppcore.staff.history · always · Staff.java

The punishment history of one player, section by section: bans, address bans, mutes, warnings, kicks and inventory restores, each with its own count and its lines in order. It is the record /ban, /mute, /warn and /kick wrote, read back whole, and it is also where edits to the record show up - a warning that staff removed appears here as removed, by whom and when, which is what keeps /warnings and /history from disagreeing. A player with nothing on record says so.

/staffsuitereload (config key staffreload)

permission: ppcore.staff.reload · always · Staff.java

Reload this plugin's settings, under the source plugin's name for it. Its config key is staffreload and its default name is /staffsuitereload, which is the verb the source plugin's own documentation lists; /reloadcmi is the same reload under the other name, and both carry ppcore.staff.reload. Worth knowing before you rely on it: this plugin's handler matches the reload by the command's own id, so on a server that leaves the default name alone it is /reloadcmi that answers the reload - setting commands.staffreload.name: staffreload in config.yml makes this name answer as well. Reloading touches settings only: nothing in force is lifted and no saved inventory is dropped.

/invview

permission: ppcore.staff.inspect · always · Staff.java

Look at what a player is carrying, read-only where it can be: /invview <player> [armor|ender|snapshots [id]|deaths [id]]. The bare form opens the live inventory, armor shows the four armour slots and the off hand on their own, and ender opens the ender chest; those three are live views, so the opening is logged even though nothing can be changed from the view a copy would give. The two snapshot views are genuinely read-only - snapshots lists what was saved and snapshots <id> opens one, deaths the same restricted to death snapshots - because a snapshot is a copy of items with nothing to take them from. Snapshots need the saved-inventory switch on, and they work for an offline player; the live views need the player online.

/inv (/invedit)

permission: ppcore.staff.inv.edit · always · Staff.java

Open a player's live inventory to edit it: /inv <player>, which is the same container /invview opens with the verb that means "change it". Both are logged; the difference is real and it is the node - editing needs ppcore.staff.inv.edit, so an owner can allow a look without allowing a change. The reply says plainly that the opening is what gets written down, not the change, because the server has no read-only view of a live inventory to hand out.

/checkperm

permission: ppcore.staff.inspect · always · Staff.java

Ask what a player can do: /checkperm <player> <node> answers yes or no with where the answer came from, and /checkperm <player> list prints the nodes they have - all sources at once for a player who is online, and the group's nodes for one who is not, with the reply naming which of the two it read. Sixty are printed and the rest are counted, because a list longer than that is a list nobody reads and the question is nearly always about one node. It and /haspermission go through the same reader, so the two verbs cannot come to different conclusions about one player.

/checkban

permission: ppcore.staff.ban · always · Staff.java

Whether one name or one address is banned, with both sides of the answer: the server's own list, which is what refuses the connection, and this plugin's record, which is what can be explained later. A ban in one and not the other is named as exactly that - a record without a live ban is a ban that stopped working, and a live ban with no record is one nobody can justify. For a name it also lists the addresses on record, and it ends by naming the verb that lifts it and the one that shows the rest.

/editwarnings (/edelete)

permission: ppcore.staff.warn · always · Staff.java

The editing half of /warnings: /editwarnings <player> list reads the numbered list, /editwarnings <player> delete <n> removes one, and /editwarnings <player> clear confirmed empties them all - the confirmed word is required because clearing somebody's warnings is the kind of action that should not happen on a mistyped argument. A number outside the list is refused with the range. A removal is written into the same record /history prints, so the fact that a warning was removed stays visible rather than leaving the two reads disagreeing.

/clearhistory (/clearh)

permission: ppcore.staff.history · always · Staff.java

Clear a player's punishment record: /clearhistory <player> [confirmed]. It clears the record, not the punishments - the distinction is the whole verb, because the log and the state live in one file and a verb that wiped the whole entry would lift a ban as a side effect. Before doing anything it says how many lines would go and what is still in force (banned, address-banned, muted), and it says afterwards that nothing was lifted. /unban and /unmute are what lift a punishment.

/punish

permission: ppcore.staff.inspect · always · Staff.java

One entry point for the four punishments: /punish <player> [action] [arguments]. On its own, or with nothing after the name, it is a briefing - whether the player is online, how many warnings they have, whether a mute or a ban is in force, and every action they could take with the usage and the node beside it, marking the ones you do not hold. With an action it hands the work to the verb that owns it, so a rename in commands still works, the action's own permission still decides, and the record written is the same record: kick, ban, tempban, ipban, mute and warn are the actions, and a word that is not one of them is refused with the list.

/invsave (/invsnap)

permission: ppcore.staff.inv.save · only while modules.staff.features.invsnap is on · Staff.java

Take a snapshot of what a player is carrying: /invsave <player> [reason], with the reason kept so the list can explain itself later. The player has to be online - a snapshot is taken from the player the server is holding, not from a file. Snapshots are kept newest-first up to staff.inv.keep (five by default), the oldest dropping off when a new one is taken, and an empty pack can be skipped whole depending on staff.inv.ignoreEmpty; both facts are in the reply, so nobody has to guess whether something was kept. Death snapshots are taken by the same store when staff.inv.saveOnDeath is on.

/invlist (/invhistory)

permission: ppcore.staff.inv.list · only while modules.staff.features.invsnap is on · Staff.java

What the server holds: with no name, every player with saved inventories, how many each has and when their newest was taken; with a name, that player's snapshots newest first, ten to a page, with /invlist <player> <page> for the rest. Each line names the save's id, when it was taken, why, and how many slots it filled, which is what /invload and /invremove are then addressed by. A player with nothing saved, and a server with nothing saved, are told apart.

/invload (/invrestore)

permission: ppcore.staff.inv.load · only while modules.staff.features.invsnap is on · Staff.java

Put a saved inventory back on its player: /invload <player> <id|last> [confirmed], where last means their newest. The player has to be online, and the reply says why when they are not - the file would have to be written behind the server's back and the next join would write over it. If they are carrying anything, the restore asks for confirmed once and says how much would be replaced; a restore into an empty inventory needs no confirmation, because that is the case the verb exists for. What comes back is the parts staff.inv.restore.* allows - items, health, hunger, experience and effects each have their own switch - and the reply lists exactly which parts were put back. The player is told a restore happened, and it is recorded against them.

/invremove (/invdelete)

permission: ppcore.staff.inv.remove · only while modules.staff.features.invsnap is on · Staff.java

Delete one saved inventory, or all of them: /invremove <player> <id|last|all> [confirmed]. A single id or last goes without ceremony, and all asks for confirmed because it is the one form that cannot be undone. Every deletion names the save it removed - when it was taken, why and how full it was - so the console log of it is readable, and staff are notified.

/invremoveall (/invprune)

permission: ppcore.staff.inv.remove · only while modules.staff.features.invsnap is on · Staff.java

Prune the whole server's saved inventories, keeping each player's newest: /invremoveall [keep] confirmed. It is the one verb here that touches every player at once, so it previews first - how many saves it would delete and how many it would keep each - and does nothing until the confirmed word is written. The number kept defaults to staff.inv.keep and can be given as the argument, so "keep the last two each" is one word.

/invcheck

permission: ppcore.staff.inspect · only while modules.staff.features.invsnap is on · Staff.java

The question worth asking before a restore: /invcheck <player> says how many snapshots the server holds for them out of the cap, whether they are online, when the newest and oldest were taken and why, how many of them are deaths, and the ids to use. It ends with the one thing that decides whether the restore can happen at all, said rather than discovered - a restore needs the player online, and the reply states whether they are.

Permissions. ppcore.staff.ban, .mute, .kick, .warn, .vanish, .freeze, .mode, .teleport, .inspect, .chat, .alerts, .notes, .history, .reload, .jail, .ipban, .sudo, .patrol, .silentchest, .lockip, the five .inv.save / .inv.load / .inv.list / .inv.remove / .inv.edit nodes, and ppcore.report - all of them granted to the shipped staff group.

chat - Chat: private messages, ignore, broadcasts, alerts, mail

What it does. This module owns the chat event: a muted player, a muted server, the staff channel, a recipient's ignore list, or ordinary chat with the configured format. It carries the rest of the chat vocabulary too - private messages with a sound, /me, broadcasts, the alert line, titles, mail (with /mailall to everyone the server has seen recently), the transient /actionbarmsg and /bossbarmsg, /ctext named blocks of text that can be edited in game, /clearchat, the /chatcolor palette and /helpop. Colour codes in chat need ppcore.chat.colour, so a plain player cannot forge a staff-looking line.

Turning it on. modules.chat.enabled: true (default) with eleven feature switches: modules.chat.features.msg, .ignore, .servermute, .broadcast, .alert, .title, .mail, .display, .ctext, .clear, .color, .helpop, .mailall and .staffmsg. Each removes its own verbs. A server that replaces chat wholesale switches the module off; chat.useFormat then decides whether the format below is applied at all, so a server that wants this module's moderation and not its formatting says so with one key.

What you can configure. chat.useFormat (false - the module does not touch the format until you say so) and chat.format ({prefix}{tag}&f{player}&7: &f{message}, where {tag} is what the player is wearing from the tags module and is empty for somebody wearing nothing); chat.stripColourWithoutPermission (true); the per-verb formats chat.msg.format, chat.msg.sound/soundName, chat.me.format, chat.broadcast.format, chat.alert.format/sound and the chat.title.* timings (fadeInTicks 10, stayTicks 50, fadeOutTicks 10); chat.mail.limit (25) with deliveredOnJoin (true) and chat.mailall.windowDays (7 - who /mailall reaches, measured by who this server has actually seen join); chat.display.actionbarSeconds (3), bossbarSeconds (3) and bossbarMax (9 - the bars one client can hold, and a second message with the same name updates the bar rather than stacking one); chat.clearLines (100); the chat.color.palette map (black … white, each a colour code, and a colour not named here is refused by name); chat.helpop.cooldownSeconds (60) with its format; chat.ignore.maxList (50); chat.ctext.<name> for the named text blocks; and chat.debugDelivery (false - turn it on while chasing "nobody hears chat": every line is logged with whether the event arrived and how many viewers the delivery loop reached).

Commands. /msg (and /r, /reply), /me, /ignore, /options, /msgtoggle, /pmtoggle, /broadcast, /bbroadcast, /alert, /title, /mail, /mailall, /actionbarmsg, /bossbarmsg, /ctellraw, /toast, /ctext <name>, /editctext <name> set|remove, /clearchat, /chatcolor, /helpop, /mutechat (also reachable as /silence) and /staffmsg.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/chat.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help chat prints the same names and the same nodes, line for line, because both read this one registration.

/msg (/tell, /whisper, /pm)

permission: ppcore.chat.msg · only while modules.chat.features.msg is on · Chat.java

A private message from one player to one player: /msg <player> <message>, drawn in the line chat.msg.format so a server can shape its own private-message look. The name has to be someone who is online - an offline player is told about, not queued for - and the command remembers who last messaged you, which is what /reply answers. The console can send one too, but it has to name the target explicitly: /msg <player> <message> is the shape there, because there is no "whoever typed this" to read. The server's own /msg, /tell and /me are taken over while this module is on, unless chat.takeOverVanilla is switched off.

/reply (/r)

permission: ppcore.chat.msg · only while modules.chat.features.msg is on · Chat.java

Answers the last player who messaged you: /reply <message>, with no target to type. Three refusals, each in its own words, because each has a different fix: nobody has messaged you yet, the player who did is no longer online, and a message with nothing in it. Console senders cannot use it - they are pointed at /msg.

/me (/action)

permission: ppcore.chat.me · only while modules.chat.features.msg is on · Chat.java

An emote: /me <action> broadcasts one line in the third person, drawn in chat.me.format, so /me waves reads as the player doing it rather than saying it. Players only, and a muted player is refused with You are muted. rather than having the line quietly dropped - the mute is the staff module's, so the two modules cannot disagree about who is silenced.

/ignore (/unignore)

permission: ppcore.chat.ignore · only while modules.chat.features.ignore is on · Chat.java

Your own ignore list, one player at a time. /ignore with nothing after it lists who you are ignoring; /ignore <player> adds them, and the same command typed as its unignore alias (or as the name you are already ignoring) takes them back off, saying You can hear <player> again.. The list is per player and written to the module's own file, so it survives a restart, and it has a ceiling - chat.ignore.maxList, 50 - which is reported when it is reached rather than silently refusing. Players only.

/options (/chatoptions)

permission: ppcore.chat.options · only while modules.chat.features.options is on · Chat.java

Your own chat settings, in one place: this is the source plugin's own surface for them, and the private message switch is one of its rows. Type it with no arguments to see where you stand and change what you want, and use /msgtoggle when the switch is the only thing you came for. The settings are yours, so this command needs only its own node and no staff rights.

/msgtoggle

permission: ppcore.chat.options · only while modules.chat.features.options is on · Chat.java

Turns private messages from other players on or off, in one word: type it and it flips, and it tells you which state you are in now. The choice is per player and remembered between sessions. It is the same switch /pmtoggle changes, under the other name the source plugins give it - one flag with two names cannot be on and off at the same time, which two flags would be.

/pmtoggle

permission: ppcore.chat.options · only while modules.chat.features.options is on · Chat.java

The same private-message switch as /msgtoggle, spelled the other way the source plugins spell it. Both names write one setting, so whichever one a player reaches for, the answer is the same and the two cannot drift apart.

/mutechat (/unmutechat, /silence)

permission: ppcore.chat.servermute · only while modules.chat.features.servermute is on · Chat.java

Silences public chat for the whole server, and unsilences it: /mutechat with nothing flips the state, /mutechat on and /mutechat off say which you want, and the unmutechat alias always turns it off. The state is written to the module's own file and announced to everybody, so nobody has to guess why their messages stopped appearing. Staff who need to talk through it are the ones holding this module's own node - the refusal names it - and silence is the same command under the source plugin's other name, not a second flag.

/broadcast (/bc, /announce)

permission: ppcore.chat.broadcast · only while modules.chat.features.broadcast is on · Chat.java

An announcement to everyone: /broadcast <message> (and its bc and announce aliases) draws one line in chat.broadcast.format and sends it to the whole server. The message is everything after the command, so no quotes are needed and no message is too short to send. It is the plain form; /alert is the one with a sound on it.

/alert (/alertall)

permission: ppcore.chat.broadcast · only while modules.chat.features.alert is on · Chat.java

An urgent message: same shape as /broadcast, but in chat.alert.format, and every online player also hears the sound named in chat.alert.sound. The console is told as well, so a line typed by a staff member is not missing from the log they read afterwards. Use it for things that need to interrupt - restarts, an event about to land - rather than for ordinary news.

/titlemsg (/title, /broadcasttitle)

permission: ppcore.chat.title · only while modules.chat.features.title is on · Chat.java

Puts a title and an optional subtitle on one player's screen: /titlemsg <player> <title> [subtitle], with the fade-in, stay and fade-out times coming from chat.title.fadeInTicks, stayTicks and fadeOutTicks. Colour codes work in both lines, and the multi-word title is joined back together, so spaces need no quotes. On a server whose API has dropped the older title call, the two lines arrive as ordinary chat messages instead - the command still does something rather than failing.

/mail (/inbox)

permission: ppcore.chat.mail · only while modules.chat.features.mail is on · Chat.java

A note for a player who is not online: /mail <player> <message>. Each note is stamped with the date and time and who wrote it, the inbox holds up to chat.mail.limit notes (25 by default) and a full one is reported rather than silently dropping the message, and a player who is online is told immediately. Typed by a player with nothing after it, /mail reads your own inbox; /mail read reads it and clears it, so the same notes are not announced twice; /mail clear empties it. Reading and clearing are private, so those two are players only, while sending works from the console - which is the case this command mainly exists for: leaving a banned player the reason, or handing an offline owner a note.

/actionbarmsg (/broadcastaction)

permission: ppcore.chat.display · only while modules.chat.features.display is on · Chat.java

A short line above the hotbar: /actionbarmsg <player|all> [-s:seconds] <message>. all is everybody who is online, a name is one player, and -s: says how many seconds to leave it up - the default is chat.display.actionbarSeconds (3). The switch may sit anywhere in the message, and the remaining words are joined back into the line, so spacing and colour codes behave as typed. Nobody online is reported by name rather than clearing the screen of nobody.

/bossbarmsg

permission: ppcore.chat.display · only while modules.chat.features.display is on · Chat.java

A named boss bar, on one player's screen or on everybody's: /bossbarmsg <player|all> [-n:name] [-t:seconds] [-c:colour] [-p:max/current] <message>. The name is the bar's own label, the seconds come from -t: (or -sec:) and default to chat.display.bossbarSeconds, the colour is one of the bar colours the server knows, and -p: sets the fill as max/current so the bar can show progress rather than sitting full. Every switch may appear in any order among the message words, which are joined back together, and the reply says how many players it reached.

/bbroadcast

permission: ppcore.chat.broadcast · only while modules.chat.features.display is on · Chat.java

The same boss bar as /bossbarmsg, aimed at everyone without naming them: /bbroadcast <message> [seconds]. A bare number as the last word is the time to keep it up rather than part of the message, which is the source plugin's own shape for it, so /bbroadcast Restarting in 5 minutes 30 shows that line for thirty seconds. It reports how many players received it.

/toast

permission: ppcore.chat.display · only while modules.chat.features.display is on · Chat.java

Refused, by name and with the reason. A toast is the little advancement popup the client draws: the API this plugin builds against has no call that makes one, the registry behind it cannot be written at runtime, and this plugin deliberately sends no packets of its own - so the command says exactly that and points at /bossbarmsg and /actionbarmsg instead of drawing some other thing and calling it a toast. A setting that does nothing quietly is the failure this refusal exists to avoid.

/ctellraw

permission: ppcore.chat.ctellraw · only while modules.chat.features.display is on · Chat.java

A raw JSON text component, sent to one player or to everybody: /ctellraw <player|all> <json>. The argument is the component itself, exactly as the server's own text format writes it, so hover and click events and mixed colours are all available; the words are joined back together, so the JSON may contain spaces. A component that does not parse is refused with the parser's own message rather than sent as plain text, and the reply says how many players were reached.

/ctext

permission: ppcore.chat.ctext · only while modules.chat.features.ctext is on · Chat.java

Shows one of the server's named blocks of text, out of chat.ctext: /ctext <name> shows it to yourself, /ctext <name> <player|all> to somebody else, and /ctext with nothing after it lists the names that exist. A name that does not exist is answered with the list of ones that do. Showing text to anyone but yourself needs ppcore.chat.ctext.others, and from the console the audience is not optional: there is no "yourself" to fall back on, so the command asks who should see it.

/clearchat

permission: ppcore.chat.clearchat · only while modules.chat.features.clear is on · Chat.java

Pushes the chat history out of the window, for everyone or for one player. /clearchat clears everybody except the staff members holding ppcore.chat.clearchat.bypass, which needs ppcore.chat.clearchat.others; /clearchat self clears your own window and needs nothing beyond the command's own node. There is no "empty the chat" packet to send, so this is what clearing is: enough blank lines to fill a window (chat.clearLines, 100 of them). The reply says how many players were cleared and, for the whole-server form, that the bypass list was skipped.

/chatcolor

permission: ppcore.chat.color · only while modules.chat.features.color is on · Chat.java

Picks the colour your own chat lines are drawn in, from the palette in chat.color.palette: /chatcolor <colour> sets it, /chatcolor list prints the colours this server offers, and /chatcolor off puts you back to the ordinary colour. Colouring somebody else needs ppcore.chat.color.others, and from the console the player has to be named because there is no "your own" to fall back on. Both argument orders are accepted - /chatcolor <colour> <player> and /chatcolor <player> <colour> - because every other staff verb here takes its target first, and the two orders can only be confused by somebody named after a colour.

/helpop

permission: ppcore.chat.helpop · only while modules.chat.features.helpop is on · Chat.java

Asks staff for help: /helpop <message> reaches every player holding ppcore.chat.helpop.inform, writes the line to the console and to the log, and is drawn in chat.helpop.format. It has a cooldown, chat.helpop.cooldownSeconds (60 seconds), reported with the seconds left rather than swallowed. When nobody is listening, the sender is told that plainly instead of waiting for an answer that is not coming - the same line is in the console log for whoever reads it later.

/mailall

permission: ppcore.chat.mailall · only while modules.chat.features.mailall is on · Chat.java

One note to everybody the server has seen recently: /mailall <message> (an optional leading send is ignored, because that is how the source plugin spells it) leaves the message in the inbox of every player seen in the last chat.mailall.windowDays days, seven by default. Anyone online is told at once. Inboxes already at chat.mail.limit are counted and reported rather than silently skipped, and /mailall clear empties every mailbox this module knows about, reporting how many it cleared.

/staffmsg

permission: ppcore.staff.chat · only while modules.chat.features.staffmsg is on · Chat.java

One line to staff chat without changing whether you are in it: /staffmsg <message>. With no message, or with toggle, it reports whether staff chat is on for you and points at /staffchat, which is the staff module's verb for the switch itself; /staffmsg on and /staffmsg off set it from here as well. The point of the command is the middle case - a staff member who keeps staff chat off for the ordinary conversation but wants to say one thing to the team.

Permissions. ppcore.chat.msg, .ignore, .mail, .me, .options, .colour, .color, .helpop, .ctext, .clearchat and ppcore.chat.broadcast, .title, .servermute, .socialspy, .display, .ctellraw, .mailall, .helpop.inform, .ctext.others, .ctext.edit, .color.others, .clearchat.others, .options.others, .msg.bypass, and ppcore.staff.chat for /staffmsg.

ah - Auction house: players sell to each other with a chest GUI

What it does. Players list items and other players buy them, through a chest GUI, with optional bidding. A listing runs for a configured number of hours and expires; a completed sale can announce itself through the operator's own Discord webhook, and that request goes through the module that owns the outbound client (service) rather than being this module's own hook - so service.enabled, service.features.discord and a host in service.allow are the switches that decide whether anything is sent. Nothing here is a rank advantage: the fee, if any, is paid by everybody equally, so it is a money sink rather than a perk.

Turning it on. modules.ah.enabled: true (default), with modules.ah.features.sell (players may list) and modules.ah.features.bidding (new listings accept bids - off makes it buy-now only). Players need ppcore.ah.use and ppcore.ah.sell, both in the shipped default group; staff need ppcore.ah.admin.

What you can configure. ah.listing.minPrice (1.0), maxPrice (1000000000.0 - a ceiling that stops a typo freezing a number field), maxPerPlayer (10, the same for everyone including donors), hours (48, the listing's life), minBidIncrement (1.0) and feePercent (0.0 - the cut taken in game money; 0 so selling is free); ah.paySellerOnlyWhenOnline (true - false parks the earnings for /ah collect); ah.expireCheckMinutes (5); ah.categories (empty - pin a material to a category, e.g. DIAMOND: ores); and ah.webhook.embeds (true) / tts (false) for the announcement. /ah webhook send proves the wiring.

Commands. /ah (the browser), /ah collect, /ah webhook send (staff, and the one command that says whether the webhook is reachable at all).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/ah.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help ah prints the same names and the same nodes, line for line, because both read this one registration.

/ah (/auction, /auctionhouse, /market)

permission: ppcore.ah.use · always · Ah.java

The auction house, and it takes a form: a chest GUI, so it is a command a player types and a window they browse. /ah with nothing after it opens the board, showing every listing in nine categories (blocks, ores, tools, weapons, armour, wood, farming, magic, misc) and the three buttons under it - collect, my listings, search. A listing is a whole stack at one price, so what you sell is what you are holding.

The rest of the command is the verbs around that window: /ah sell <price> [buy-now] puts the stack in your hand up for sale for 48 hours by default (ah.listing.hours), with an optional buy-now price that must be at least the starting price; /ah my lists what you have up so you can cancel one and take it back; /ah collect hands over anything that came back to you while you were offline - expired items, won auctions, and money that could not be paid into a balance; /ah search <text> finds a listing by name; and /ah cancel <id> ends one early, refunding any bid on it.

Three rules make it fair rather than a shop: the only currency is money earned in game, every limit is a server-wide number rather than a perk (ah.listing.minPrice, maxPrice, maxPerPlayer), and a listing always has an owner - when it expires the item goes back to the seller and a standing bid is refunded, never deleted. Bidding is a feature switch (modules.ah.features.bidding): with it off a listing is buy-only and the bid button is not drawn. A fee may be taken from the seller's payout (ah.listing.feePercent), and ah.paySellerOnlyWhenOnline decides whether a payout waits in the collect box for an offline seller or goes to them the moment they are next seen.

Two verbs are for operators rather than players, and both work from the console: /ah sweep expires everything past its time now instead of at the next ah.expireCheckMinutes tick, and /ah webhook reports whether the sale webhook is configured and /ah webhook send posts one test line through the core's own outbound client. The webhook is off unless service.discord.url is set and the service module's own switch allows it, and the shape of the payload is ah.webhook.embeds and ah.webhook.tts.

Permissions. ppcore.ah.use, ppcore.ah.sell (default group), ppcore.ah.admin (staff).

ench - Enchantments and item editing, with permission locks

What it does. Progression rules for enchantments, plus the item-editing verbs. A locked enchantment needs a permission node, and the lock is enforced in all three of the places one can arrive from - /enchant, the enchanting table, and an anvil combining two books - because a rule enforced in one of three places is not a rule. blocked is the second kind of lock: nobody may apply it, whatever they hold. levelGate is the third and it is earned in game rather than granted: an enchantment can need a spawner level from the spawners module, and it is enforced in both directions - below the level it cannot be applied (table, anvil and command all refuse) and it cannot be used either, so a book bought from another player does not bypass it. The item verbs are /unbreakable, /hideflags, /itemname and /itemlore.

Turning it on. modules.ench.enabled: true (default) with six switches: modules.ench.features .command (/enchant and /unenchant), .unbreakable, .hideflags, .itemname, .itemlore and .locks (enforce ench.locked in the table, the anvil and the command). Players need ppcore.ench.use plus ppcore.ench.unbreakable, .name and .lore; ppcore.ench.bypass skips every lock and ppcore.ench.admin is the admin half.

What you can configure. ench.apply (true - /enchant works at all), ench.allowUnenchant (true), ench.table (true) and ench.anvil (true) - the two places the locks are enforced besides the command; ench.maxLevel (0 = the vanilla maximum per enchantment) with ench.maxLevelPercent (100, a share of that cap - 50 gives half levels); ench.maxNameLength (64, which stops a rename breaking the item tooltip); ench.requireNode (empty = every enchantment is open, otherwise a node they all need); ench.locked (a map of enchantment to node, shipped empty with sharpness and mending written as comments); ench.blocked (a list nobody may apply, shipped empty) and ench.levelGate (a map of enchantment to spawner level, shipped with mending: 25 - Mending is the top of that ladder's reward). Nothing about enchanting is purchasable: levels are vanilla, anvils cost what they always cost, and /unenchant is free.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/ench.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help ench prints the same names and the same nodes, line for line, because both read this one registration.

/enchant (/ench)

permission: ppcore.ench.use · only while modules.ench.features.command is on · Enchants.java

Put a vanilla enchantment on the item in your hand: /enchant <enchantment> [level], as in /enchant sharpness 3. The name is read in every spelling a player might type it in - the modern key, the API name and the older historical name - so a command written from memory or off an old page still resolves, and an unknown one is refused with a pointer to tab completion for the list. The level is clamped to what the enchantment allows unless ench.maxLevel and ench.maxLevelPercent raise the ceiling, and the answer names the enchantment and the level it landed at. Two things can stop it: the switch ench.apply in the config, and a lock - ench.locked, ench.blocked, ench.levelGate and ench.requireNode - which the enchlocks verb below reads back in words. A player holding ppcore.ench.bypass skips the locks.

/unenchant (/disenchant, /clean)

permission: ppcore.ench.use · only while modules.ench.features.command is on · Enchants.java

Take an enchantment off the item in your hand. /unenchant with no argument lists what the item has and how to name one, /unenchant <enchantment> removes that one, and /unenchant all strips the lot - which is a separate node (ppcore.ench.use.all) because removing every enchantment from somebody's gear is a bigger action than removing one. ench.allowUnenchant can switch the whole verb off, and an item with nothing on it says that rather than answering success.

/unbreakable (/unbreak)

permission: ppcore.ench.unbreakable · only while modules.ench.features.unbreakable is on · Enchants.java

Turns the unbreakable flag on the item in your hand on and off - a toggle, so one verb does both and the reply says which way it landed. The display flag is set with it, so an item that will not wear out does not say that it will: the tooltip and the behaviour agree. Items that cannot hold the flag are refused rather than silently left alone.

/hideflags (/hideflag)

permission: ppcore.ench.use · only while modules.ench.features.hideflags is on · Enchants.java

Hides the enchantment, attribute and dye lines on the item in your hand, and shows them again - one toggle, and the reply names what is being hidden rather than only "done". It is the cosmetic half of /enchant: the enchantment is still there and still works, it is the tooltip that stops arguing with the name or the lore.

/itemname (/rename, /name)

permission: ppcore.ench.name · only while modules.ench.features.itemname is on · Enchants.java

Rename the item in your hand: /itemname <text> sets the name, /itemname clear hands the item its own material name back. Colour codes need ppcore.ench.colour; without it the codes are shown literally, and the reply says so rather than appearing to swallow them. The name is cut at ench.maxNameLength characters (64 by default).

/itemlore (/lore)

permission: ppcore.ench.lore · only while modules.ench.features.itemlore is on · Enchants.java

The lore of the item in your hand, in five verbs: no argument or show reads the lines out with their numbers, /itemlore add <text> appends, /itemlore set <line> <text> replaces one, /itemlore insert <line> <text> opens a space, and /itemlore remove <line> takes one out, with /itemlore clear emptying the lot. Line numbers start at 1 and are checked against what the item actually has, so a wrong number is refused with the count rather than ignored. Colour codes work the same way they do in /itemname - ppcore.ench.colour, and the reply says when they will show literally.

/enchlocks (/enchlock)

permission: ppcore.ench.admin · only while modules.ench.features.locks is on · Enchants.java

Why nobody can put Mending on a book, answered in game instead of in the file. It prints the locks and the level gates together: an enchantment locked to a permission or blocked outright is shown with what it is waiting for, and a gate is shown as the spawner level it wants, marked as earned rather than granted. A server with no locks at all says so in one line - which is the answer worth being able to read, because the alternative is an operator hunting a config key that does nothing.

server - time, weather, ping, tps, player lookups and maintenance

What it does. The server's own operations, in five families. Diagnostics - /ping, /tps, /uptime, /version, /seen, /playtime, /whois, /near, /pos, /distance, /list, /worlds and /seed (the seed only to holders of ppcore.server.seed, in /seed and /worlds alike, so the two never disagree about who may know it). Inspection - /blockinfo, /iteminfo and the NBT verbs, all reading the same blockInfoRange. World control - /time, /weather, /gamerule and the personal clocks. /maintenance, which kicks everyone without ppcore.admin.maintenance and survives a restart because a maintenance window that ends when the server does is not a maintenance window. /afk, which exists because one thing in this plugin pays for being connected rather than for doing. The tab list, the named command lists behind /schedule, and two walks of a world: /scan (which reads) and /replaceblock (which writes, hence its own switch and its own node - a grant that lets somebody look for the ore should not also turn it to air).

Turning it on. modules.server.enabled: true (default) with nine feature switches: modules.server.features.lookup, .inspect, .world, .maintenance, .afk, .tablist, .schedule, .scan and .placeholders. Each is separate for a reason worth reading in the file: the tab list of a network that keeps its own, the console power of /schedule, the cost of /scan, and a second opinion about placeholders are all things an owner should be able to refuse without giving up /time. /maintenance's state and /setrt's switch live in the plugin's own server.yml rather than here, because an operator flips them at a moment and a switch only read at boot is a switch that does not come back after the restart that was going to apply it.

What you can configure. server.banner (true - the startup summary; the warnings below it are not part of this and print whatever this says), server.blockInfoRange (8, shared by every "what am I looking at" verb), server.maintenance.defaultReason; server.afk.auto-seconds (300, and 0 flags nobody automatically), check-seconds (5, capped at 60 and announced at load when a larger value is written), move-blocks (0.5 - turning on the spot does not count) and pause-salary (true: an away player is paid nothing, which is the whole reason /afk exists), plus the server.afk .messages.* lines; server.ptime.refreshSeconds (10 - a held clock has to be re-sent or "held at night" quietly becomes morning); server.realtime.checkSeconds (30), tolerance-ticks (200) and worlds (empty = every loaded world, which is what a real day/night server means); server.scan.blocks-per-tick (20000, counted as positions examined rather than matches), results-max (200), progress-seconds (5) and default-radius (128); server.replace .blocks-per-tick (20000), default-radius (64) and progress-seconds (5); server.tablist.enabled (true) with header and footer (the placeholders are %player% %world% %online% %max% %ping% %tps%, and a change reaches players who are already on); and server.schedule.<name> with every-seconds (0 = only when somebody runs /schedule <name>, and anything under 5 is refused by the module's own check) and commands (run in order, as the console - which is why /schedule is staff's).

Commands. /tps, /ping, /uptime, /version, /seen, /playtime, /whois, /near, /pos, /distance, /list, /worlds, /seed, /blockinfo, /iteminfo, /itemnbt, /blocknbt, /entitynbt, /entityinfo, /itemcmdata, /recipe, /time, /weather, /gamerule, /pweather, /ptime, /maintenance, /afk, /afkcheck, /tablistupdate, /schedule <name>, /scan <type> [r:<blocks>|r:g] [w:<world>] [c:<n>] with status|results|tpto|pause|resume|stop|purge, /replaceblock, /setrt on|off, /servertime, /placeholders, /checkcommand, /oplist, /blockcycling, /endgateway, /serverlinks, and <command> check on any of them for the config report.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/server.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help server prints the same names and the same nodes, line for line, because both read this one registration.

/ping

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

Your own round trip, or another player's: /ping with no argument reads your own and adds a word for what the number means (excellent under 60ms, good under 120, playable under 250, poor above), because the number alone does not tell a player whether they can mine or fight. /ping <player> names somebody else, and the console has no ping of its own, so it must name a player.

/tps (/lag)

permission: ppcore.server.tps · only while modules.server.features.lookup is on · ServerOps.java

The server's tick rate, the way an operator asks about lag: three figures - the last minute, five minutes and fifteen minutes - each out of 20, with a verdict (healthy at 19.5 and above, slightly behind, struggling, overloaded) and the number of players online beside them. On software that ships a built-in /tps the server keeps that name and this verb answers /lag; the header and the help line name whichever name really answers, measured after registration rather than assumed.

/uptime

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

How long this server has been running, as a readable span (2h 15m) with the raw seconds beside it. It is the Java process's own uptime, so it is the number that resets when the server restarts and not a count of playtime or of wall-clock since the world was made.

/version (/ver, /about)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

The server's version, the Bukkit version under it, this plugin's build, memory in use against the maximum, players and worlds - and, when the plugins folder holds a second build of this plugin, a line naming it, which is the answer to "do I have to restart after an update". It also warns when memory is 85% used or more, naming the setting to raise. /ver and /about are the same command; the takeover of the built-in /version name is not relied on, because the server puts its own back.

/update (/checkupdate, /updatecheck)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

Reads the manifest this server is configured with (service.update.url) and reports whether a newer build than the one running is listed (this build is current, or a line saying a newer build is available and that the panel installs it). It needs the outbound service client to be on and that host to be allowed; a manifest that answers but holds no version number is reported as that rather than guessed at. It writes nothing and installs nothing - updating is the panel's job.

/seen (/lastseen, /lastonline)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

When a player was last online. An online player is answered "online now" with how long this session has lasted; an offline one is answered with how long ago they were here and the date, plus their total playtime. A name this server has never recorded is refused by name rather than answered with a date, and a record that was never stamped says so.

/playtime (/cplaytime)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

How long a player has played, counted by this server while they were actually online: /playtime alone is your own, /playtime <player> is somebody else's, online or not. A name with no record is refused by name. It is the same number /seen, /whois and the playtime board print, and /editplaytime is the verb that corrects it when a count is wrong.

/playtimetop (/ptimetop, /toptime)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

Who has played the most: the leaders with their totals, /playtimetop <n> asking for up to 25 (10 by default). It reads the stored records rather than who is online, deliberately - the top of a playtime board is the people who are not here - and it says when a request was cut down to 25 rather than quietly answering a smaller question. A server with no records yet is told that rather than shown an empty list.

/whois (/playerinfo, /pinfo, /info)

permission: ppcore.server.whois · only while modules.server.features.lookup is on · ServerOps.java

Everything this plugin knows about one player on one screen: their group and rank rung, balance, hearts, kills and deaths, time played, blocks mined, when they were last seen, and - if they are online - their ping, world and game mode. Online figures come from the live player and offline ones from the stored record, so the balance and hearts it prints are the same ones /money and the hearts module would show. The console may read any name; a player needs the node this command carries.

/ustats (/mystats)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

A player's own statistics, and the argument decides what it costs: /ustats with nothing after it reads the caller's own record and needs no node at all, while /ustats <player> reads somebody else's and asks for the same node /whois uses for that read. The refusal names both facts, because "not for you" and "not for you with a name" are different sentences. The console has no statistics of its own and must name a player.

/near (/nearby, /radar)

permission: ppcore.server.near · only while modules.server.features.lookup is on · ServerOps.java

Who is nearby: the players in your own world within 100 blocks, or /near <radius> for a different distance, sorted nearest first with the distance in blocks beside each name. Different worlds do not count and neither do you, and an empty answer says "nobody within N blocks" rather than printing nothing. Players only - the console is nowhere.

/pos (/whereami, /getpos, /coords, /position)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

Where a player is standing: world, block coordinates, the compass direction they face and the chunk they are in. /pos alone is you; /pos <player> is somebody else, which is what a staff member reads out in a report. It reads exactly the position the player is at, so it agrees with /near and /distance about where "here" is.

/distance (/dist)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

How far away another player is: /distance <player> answers with the rounded distance and the direction from you, and refuses a player who is not online by name. A player in another world is not a distance - the answer says which world they are in instead of a number that would be meaningless.

/list (/who, /online)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

Who is online, sorted, with the count against the advertised slots; a player who is marked away carries a star and the star is explained underneath, and a server in maintenance says so on the same screen. A name marked * is a client that has not touched the keyboard, which is a different question from how many players are connected. Nobody online prints "nobody" rather than an empty list.

/worlds (/worldinfo)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

Every loaded world on one screen: its name, environment (normal, nether, the end), what time of day it is in that world's own clock, whether it is clear, raining or thundering, its difficulty and how many players are in it. The seed is printed only for a reader who has the seed node, so this and /seed cannot disagree about who may know it.

/seed

permission: ppcore.server.seed · only while modules.server.features.lookup is on · ServerOps.java

The seed of the world you are standing in, for the reader who has the node. It has to be run in game, because "your current world" is not a question a console can answer about itself, and the same node gates the seed column of /worlds.

/blockinfo (/binfo)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

What you are looking at: the block's name, its raw block data string, where it is, the light level, the biome and its hardness, and - when the block carries one - the fluid in it, as a source, a running stream with its level, or a waterlogged block named by the block rather than by the water. The ray is 8 blocks by default (server.blockInfoRange) and passes through water and lava rather than reporting what is behind the lake. Look at nothing within range and it says so.

/iteminfo (/iinfo, /whatsthis)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

What you are holding: the material, how many are in the stack, the maximum stack size, the namespaced id, durability used against the maximum (and whether it is unbreakable), its enchantments with levels, its display name and how many lore lines it carries. It is the summary; the raw half is /itemnbt.

/repaircost (/repairprice)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

What an anvil would charge to repair what you are holding, using the game's own arithmetic: one level per material, each restoring a quarter of the maximum durability and never more than four of them, plus the prior-work penalty the item already carries from earlier anvil work. It names the material a repair would take, and for an item that does not repair with a material - a bow, a fishing rod, an elytra without its membrane - it says so and names the prior-work penalty instead of guessing. Items with no durability, unbreakable items and items already at full durability are each told what they are.

/itemnbt (/inbt)

permission: ppcore.server.nbt · only while modules.server.features.inspect is on · ServerOps.java

The held item's own data, raw: the server's serialized form of the item's metadata rather than a description written for the screen, followed by the list of top-level keys and how many there are. It is the view to reach for when something behaves wrongly - a plugin's own data on an item, a custom model data value, an enchantment that should not be there - and an item with no data says so rather than printing an empty record.

/blocknbt (/bnbt)

permission: ppcore.server.nbt · only while modules.server.features.inspect is on · ServerOps.java

The record behind /blockinfo: the block you are looking at, its block data string, and then whatever the block itself carries - a container's true contents (read from the block, not from the copy a client was shown), a sign's lines, a banner's base colour and pattern count, a skull's owner, a spawner's creature and delay. A block that carries none of that says so. The block is resolved by the same range and ray /blockinfo uses, so the two cannot mean different blocks.

/entitynbt (/enbt)

permission: ppcore.server.nbt · only while modules.server.features.inspect is on · ServerOps.java

The creature you are looking at, as its own data: the type and uuid, its custom name, and the pieces that matter per kind - tameable and who tamed it, adult or baby, a charged creeper, a villager's profession, an armour stand's flags - plus any keys this plugin's modules have attached to the entity, which is the only in-game view a staff member has of a boss tag, a familiar's owner or a tool's binding. There is no "the entity I am looking at" in the server API, so the ray is drawn from your eyes the same way the client does it.

/entityinfo (/einfo, /mobinfo)

permission: ppcore.server.nbt · only while modules.server.features.inspect is on · ServerOps.java

What the thing in front of you is, for a staff member or a player who wants the numbers: the entity type with its namespaced key, its custom name, its health out of its maximum and how many ticks it has lived, what it holds in its main hand, where it is, how many passengers it has, its uuid, and its flags (glowing, invulnerable, silent, riding). It is the summary; /entitynbt prints the record behind it.

/itemcmdata (/cmdata, /modeldata)

permission: ppcore.server.cmdata · only while modules.server.features.inspect is on · ServerOps.java

Reads or writes the custom model data on the item in your hand, which is the number a resource pack uses to decide which texture to draw: with no argument it prints the value or says the item carries none, with a number it sets it, and clear removes it. Negative values are refused by name because model data cannot be negative, and an item that cannot carry metadata at all is told what it is.

/recipe (/recipes, /howdoi)

permission: none of its own · only while modules.server.features.inspect is on · ServerOps.java

How an item is made: /recipe <item> by name, or /recipe alone with the item in your hand. Shaped grids are drawn row by row and shapeless ones are listed as the ingredients; up to six recipes are shown and a larger set says how many more there are. One of this core's own items answers with the grid that module registered and its own display name, not every recipe that makes its base material, and when it has none the answer says which of the three reasons applies - it is earned rather than crafted, the grid is in the config but broken, or the crafting layer is switched off.

/time

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

The world's clock: /time or /time query [world] reads it (the part of the day and the tick count), and /time set|add <day|noon|night|midnight|sunrise|sunset|ticks> [world] changes it. Reading is open to anyone and setting is staff, which is how the server's own /time behaves - a player reading the time is not an operator action. Changing needs ppcore.server.time, and an unknown world or an unreadable value is refused by name rather than answered.

/weather

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

The world's weather: /weather reads it (clear, rain or thunder, with how long the current spell has left) and /weather clear|rain|thunder [seconds] [world] sets it, optionally for a fixed number of seconds so a ceremony can be rained on and then stop. Reading is open and setting needs ppcore.server.weather, the same split /time uses and the same one the server's built-in has.

/gamerule (/worldrule)

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

Reads or sets a game rule for one world. With no argument it lists every rule this server has with its value in each world's own section, /gamerule <rule> [world] reads one, and /gamerule <rule> <value> [world] sets it. A second word is read as a world only when no value could take it and a world really has that name, so /gamerule randomTickSpeed 3 sets and /gamerule keepInventory plots reads. The value is read as the rule's own type - a flag takes true or false, a count takes a whole number - and a rule this build cannot write is refused by name instead of being written through a cast that would fail later.

/pweather (/playerweather)

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

Weather only one client can see: /pweather clear|rain|sun|reset [player], with check reading what the player currently sees. Setting your own is free and setting somebody else's needs ppcore.server.pweather, and the first argument is read as a player when it names one, so /pweather <player> rain and /pweather rain <player> both work. reset puts the server's real weather back on that client and is the form that always works.

/ptime (/playertime, /personaltime)

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

The sky one client draws, so a player can have daylight at midnight while everybody else is in the dark: /ptime [player] <freeze|unfreeze|day|night|dusk|morning|realtime|reset|check>, a tick count, a clock time (13:00, 1pm) or a named time from /time. freeze holds the sky where it is, realtime makes it follow this machine's clock, unfreeze lets it run with the world again at the offset it has, and reset gives the server's clock back. check reports both what this module recorded and what the client is really drawing - the two can differ, and that is the question the verb exists for. Setting your own is free; setting somebody else's needs ppcore.server.ptime. A personal clock lasts for the session and is dropped on quit, so nobody's world is rearranged by a relog.

/day

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

/time set day with the word already chosen, and an optional world after it: /day [world]. It is a registration rather than an alias because an alias cannot carry a fixed argument, and it calls the same handler /time does, so the two cannot come to different answers about what "day" means. It needs the node that changes a world's clock.

/night

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

/time set night with the word already chosen, and an optional world: /night [world]. The value it sets reads back as night - the named times and the words /time reports are one table, so a set and a read of the same moment agree. It needs the node that changes a world's clock.

/servertime (/realtime)

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

The clocks, side by side: this machine's own date and time, how long the server has been up, and every loaded world with its time, its name for that moment, and - when the clock follows the real one - how far it is from the real time. It then says whether the world's clock follows this machine at all, the offset in minutes, how often it is checked, how many writes and re-assertions have happened, and the last time the driver yielded, if it ever has. Reading whose clock is driving is a question a player can ask and needs no node.

/setrt (/realtimemode)

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

Makes a world's clock follow this machine's clock: /setrt on [minutes], off, offset <minutes>, reassert <on|off> or status. The offset is in real minutes - 60 is an hour of sky, which is 1000 ticks - and a negative one shifts the other way. reassert on puts the clock back whenever something else moves it and counts the fights; reassert off turns the driver off the moment another writer wins and says what it saw, which is the setting for a server whose seasons module or staff own the sky. The switch and the offset live in this module's own state file, so they survive a restart. Needs ppcore.server.realtime.

/sun

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

/weather clear with the word already chosen, and optional seconds and world: /sun [seconds] [world]. It calls the same handler /weather does, so a sun and a clear cannot mean two different things, and it needs the node that changes a world's weather.

/rain

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

/weather rain with the word already chosen: /rain [seconds] [world]. Same handler, same node, and the message names the world and the duration so the answer is checkable at a glance.

/thunder

permission: none of its own · only while modules.server.features.world is on · ServerOps.java

/weather thunder with the word already chosen: /thunder [seconds] [world]. It sets the storm and the thunder together and, when a duration is given, applies it to both the weather and the thunder, so the noise stops when the message said it would.

/scan (/worldscan)

permission: ppcore.server.scan · only while modules.server.features.scan is on · ServerOps.java

Walks a world looking for blocks and creatures - /scan <type>[;<type>...] or /scan hand, with [r:<blocks>|r:g] for the radius (r:g is every chunk the server has loaded), [w:<world>] for the world and [c:<n>] for how many rows to keep. A start has to name everything it needs: an unknown block, a radius smaller than one chunk and a hand that is empty are each refused with the word that was wrong. While it runs, status says where it is, pause and resume hold and release it, results [page] lists what it found with a clickable row, tpto <n> teleports to one, stop ends the walk and keeps the rows, and purge forgets them. The walk never loads a chunk - a chunk that is not loaded is counted as skipped, and the report names how many - one row is one type in one chunk with how many of that type are in it, and the throttle is server.scan.blocks-per-tick so a busy server turns it down rather than switching it off. A scan is kept in memory only: a restart forgets it, because the world it was walking has moved on. Needs ppcore.server.scan.

/placeholders (/placeholderlist, /phlist)

permission: ppcore.server.placeholders · only while modules.server.features.placeholders is on · ServerOps.java

Every placeholder this server fills, with a live value beside each one: the six the tab list draws (%player%, %world%, %online%, %max%, %ping%, %tps%) and the four the ui module draws in pages, sidebars and HUD lines, each resolved for whoever asked. The values are read through the same code the tab list and the ui module really use, so this list cannot drift from them. A name a page draws that nothing fills is a different verb - /playport placeholders, which walks the config. Needs ppcore.server.placeholders.

/checkcommand (/cmdinfo)

permission: ppcore.server.checkcommand · only while modules.server.features.lookup is on · ServerOps.java

Whether a command exists, and who owns it - the question behind "why does my command do nothing". It answers with the description, the plugin that registered it (naming the module when it is one of ours, aliases included), the usage line and the aliases, and whether the reader has the node it asks for. A name nothing registered is answered as unknown with the closest names the server's command map has, rather than as a refusal a reader would misread. Needs ppcore.server.checkcommand.

/oplist (/ops)

permission: ppcore.server.oplist · only while modules.server.features.lookup is on · ServerOps.java

Who is opped on this server, in pages of twenty with the online ones marked. It is the server's own op list and it says so: a permissions plugin's groups are a different answer to a similar question, and the line names the verb that reads those. /ops is the same command; nobody being opped is answered in words rather than with an empty page.

/chunkinfo (/chunk)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

The chunk a player is standing in, and what it holds: its coordinates in the world, the block range it covers, how many entities and block entities are in it, and the biome. /chunkinfo <player> reads somebody else's chunk, which is how a lag report is checked. It reads the live chunk, so it answers about what is loaded now.

/biomeinfo (/biome)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

The biome where a player is, or at coordinates: /biomeinfo <player> or /biomeinfo <x> <y> <z> [world], with no argument reading your own feet. It prints the namespaced key (minecraft:plains) rather than a prettified name, because that is the spelling a resource pack, a config file and a world-generation setting all use.

/lightlevel (/lightlevelat)

permission: none of its own · only while modules.server.features.lookup is on · ServerOps.java

How light it is at a place, split into the part from blocks and the part from the sky, with a verdict that says what it means: pitch black and mobs can spawn, dim so crops will not grow, alright but mobs still can, or bright enough that nothing spawns there. It takes a player name or coordinates exactly as /biomeinfo does.

/maintenance (/maint)

permission: ppcore.admin.maintenance · only while modules.server.features.maintenance is on · ServerOps.java

Takes the server offline for everyone but staff: /maintenance on [reason] closes it, moving out every player who does not have ppcore.admin.maintenance or ppcore.admin and telling them the reason, and /maintenance off opens it again. With no argument it reports the state and the reason. The state lives in this module's own file and survives a restart, which is what "maintenance" means; while it is on, /list says so and stopping the server on purpose does not accidentally let everyone back in. Needs ppcore.admin.maintenance.

/setmotd (/motdset)

permission: ppcore.server.motd · only while modules.server.features.control is on · ServerOps.java

Sets the message of the day the server list shows: /setmotd <text>, where | starts a second line because a console cannot type a line break, and the server's own & colour codes work. With no argument it reads back what the list currently says and how to change it. It takes effect on the next server list ping. Needs ppcore.server.motd.

/maxplayers (/slots)

permission: ppcore.server.maxplayers · only while modules.server.features.control is on · ServerOps.java

Reads or sets how many slots the server list advertises: /maxplayers reports the number, /maxplayers <n> changes it. A negative number is read as a typo and clamped to zero rather than stored, and the change is logged with who made it. It changes what the list advertises and does not disconnect anybody who is already here.

/playercollision (/collision)

permission: ppcore.server.collision · only while modules.server.features.control is on · ServerOps.java

Whether players push each other: /playercollision on|off [player] sets it, /playercollision or toggle flips it, and the person affected is told in words ("you can push and be pushed now" or "you pass through other players"). The argument order is read both ways, so /playercollision Steve off works. Needs ppcore.server.collision.

/editplaytime (/setplaytime)

permission: ppcore.admin.statsedit · only while modules.server.features.control is on · ServerOps.java

Corrects a player's recorded playtime: /editplaytime <player> <time|+time|-time>, where a bare number is minutes, h and d are units, and a leading + or - makes it a correction rather than a value, so 90, 1h30m and +10m all read. The reply shows the old and new totals, the player is told their playtime was corrected, and the change is logged; a name no account has ever used, or a value that is not a time, is refused by name. It needs the node that owns statistics editing, because it writes a number other modules report.

/ifonline

permission: ppcore.server.ifonline · only while modules.server.features.control is on · ServerOps.java

Runs a command only if a player is online: /ifonline <player> <command>, and the command is run as the console - so this is a scripting tool with the console's own power, which is why it is staff's and nobody else's. A name this server has never seen is refused rather than being treated as offline, which is the mistake that would run the wrong branch of a script. It always answers with the branch it took and whether the server recognised the command at all, since the nested command's own output goes to the console.

/ifoffline

permission: ppcore.server.ifonline · only while modules.server.features.control is on · ServerOps.java

The other half of /ifonline: runs a command only if the player is not online, again as the console. The same refusal applies to a name no account has ever had, and the same one-line answer reports which branch was taken. Needs ppcore.server.ifonline.

/worldspawn (/setworldspawn)

permission: none of its own · only while modules.server.features.control is on · ServerOps.java

Where the world puts a new player: /worldspawn [world] reads it, /worldspawn here moves it to where you stand and /worldspawn <x> <y> <z> [world] moves it to coordinates. Reading is open - it is where you are - and moving it is staff, because it is where everybody will arrive; a move is logged with who made it. here needs a player, and the console is told to give coordinates instead of nothing.

/motdget

permission: none of its own · only while modules.server.features.control is on · ServerOps.java

Reads the message of the day exactly as the server list draws it, line by line, with the colour codes translated. It is the read half of /setmotd and carries no node, because every player can already see this line in their server list.

/pingall

permission: none of its own · only while modules.server.features.control is on · ServerOps.java

Every online player's ping, worst first, with a line saying what the number is (a round trip, so lower is better). It is the whole-server view of /ping and the first screen for "is it us or is it them", and nobody being online is answered in words.

/status (/gc)

permission: ppcore.server.status · only while modules.server.features.control is on · ServerOps.java

The server's own health on one screen: version, Java and OS, memory used against the maximum, the one-minute tick rate, uptime, players against slots, worlds, how many modules are on and how many commands are registered - counted the same way /playport status counts them, so the two cannot disagree. It carries the status node, and /gc is the same command under the name the old plugin used.

/paste (/supportdump, /serverpaste)

permission: ppcore.server.status · only while modules.server.features.control is on · ServerOps.java

Uploads a support dump - the build, the server, Java, the module switches and the command surface, the same facts /status and /version print - to the paste service this server is configured with (service.paste.url), and shows the link it answers with. With no service configured, or with the outbound gate closed, it says which of the two it is rather than doing nothing; the upload is logged. Needs ppcore.server.status.

/tablistupdate (/tabupdate)

permission: ppcore.server.tablist · only while modules.server.features.tablist is on · ServerOps.java

Re-sends the tab list header and footer to everyone from the configured lines, and prints what was sent with the send number. It is what an operator reaches for after editing server.tablist.header in the config, and it refuses by name when the tab list is switched off rather than silently doing nothing. The placeholders in the lines are resolved per player, so everybody's header is their own. Needs ppcore.server.tablist.

/schedule

permission: ppcore.server.schedule · only while modules.server.features.schedule is on · ServerOps.java

The commands this server runs on a clock: with no argument it lists every configured schedule with its interval, how many times it has run and how many commands it holds, and /schedule <name> runs one now. Everything runs as the console, on demand or on its clock, which is why the node on it is the console's own power and nothing smaller; a name that is not configured is refused with the ones that are, and a schedule whose list is empty is refused rather than reported as having run.

/afk (/away)

permission: none of its own · always · ServerOps.java

Says you are away, or comes back: /afk marks you away, /afk <reason> marks you away with a reason that other players see, and typing it again while you are away brings you back. Anything that looks like playing - a real movement, a line of chat, any command except this one - brings you back on the spot, and the module can also flag you automatically once you have been idle for server.afk.auto-seconds. The flag is what the economy's salary asks, so a client left open overnight earns nothing while it is set. No node: it is a thing players do about themselves.

/afkcheck (/afklist, /idle)

permission: ppcore.server.afk · always · ServerOps.java

Whether a player is away, and for how long: /afkcheck <player> reports that they are AFK, how long since they went away and why if a reason was given, or that they are at the keyboard with how long they have been idle. With no argument it reads the player who typed it, and the console must name one. Needs ppcore.server.afk.

Permissions. ppcore.server.seed, .tps, .time, .weather, .ptime, .gamerule, .pweather, .motd, .maxplayers, .collision, .ifonline, .oplist, .checkcommand, .blockcycling, .endgateway, .serverlinks, .whois, .near, .afk, .status, .worldspawn, .tablist, .realtime, .scan, .replaceblock, .placeholders, .schedule, .nbt and .cmdata.

admin - heal, feed, god, fly, give, teleport and jails

What it does. The admin toolkit: player state (/heal, /feed, /god, /fly, /speed, /gamemode, /exp, /kill, /clear, /ender, /dispose), items (/give, /giveall, /more, /hat, /repair), the portable stations, teleport (/tphere, /tppos, /tpall, /tpo reaching a player who is offline off a last-known position, /tpohere, /tpopos, /tpallworld, /launch), world movement (/world, /findbiome), the timed modes (/tfly and /tgod - /fly and /god with a clock, written to admin.yml so a restart resumes the countdown and a clock that ran out while the server was down ends the mode at boot), the body verbs (/air, /burn, /ext, /clearender, /falldistance, /smite), /spawnmob, the bulk verbs that point the same verbs at a set of players, /punch and /spin, the two readouts /durability and /counter, /bottom and /thru, and jails. Two design notes worth knowing: /god stops damage at the event rather than making a player invulnerable, so mobs still behave normally around a staff member in god mode; and a jail is a place plus a record - the location in admin.yml, the reason, the staff member and the end time with it, and the end time is checked on a timer so somebody who logs out for the day is still released on time.

Turning it on. modules.admin.enabled: true (default) with thirteen switches, and the split is the point: modules.admin.features.state, .items, .stations, .teleport, .worlds, .tempmode, .jail, .state-extra (the verbs that harm or strip a player, so an owner may hand out /heal without handing out /burn), .mobs, .bulk (a set is not a person: an owner who gives helpers /feed may still not want /burnall), .shove, .readouts (no node, because your own tool and what is standing around you are things every player may ask) and .movement (/thru is a builder's shortcut to one owner and a way out of a jail or a claim to another). /tp is deliberately absent from the teleport family in the code: registering it takes the name from vanilla, which loses selectors and relative coordinates.

What you can configure. admin.disposeTitle; admin.god.blockDamage (true - false leaves god mode as a marker only); admin.temp.seconds (300, the default lifetime of /tfly and /tgod); admin.launch.velocity (1.5) and max (8.0); admin.findbiome.radius (2000) and max-radius (10000); admin.burn.seconds (5); admin.smite.effect-only (false = it hurts; true only draws the strike, and the reply says which happened); admin.spawnmob.max (20 - the ceiling that stops a slip turning one command into a server that cannot tick); and the four CMI-tail numbers, each of which has a value that loads cleanly and makes its verb do nothing: admin.bulk.radius (32 - how near "near" is), admin.counter.radius (32), admin.thru.max (8) and admin.punch.power (1.0, 0.1-10 and capped in code). admin.jail is radius (8.0 - how far a prisoner may wander), defaultReason and blockCommands, the commands a jailed player cannot use - escape routes first, because a jail without that list is a jail you can /spawn out of.

Commands. /heal, /feed, /god, /fly, /speed, /gamemode, /exp, /kill, /suicide, /clear, /ender, /dispose, /give, /giveall, /more, /hat, /repair, /workbench, /anvil, /grindstone, /tphere, /tppos, /tpall, /tpo, /tpohere, /tpopos, /tpallworld, /launch, /world, /findbiome, /tfly, /tgod, /air, /burn, /ext, /clearender, /falldistance, /smite, /spawnmob, /feedme, /feedall, /feednear, /feedother and the /heal twins, /starve, /extall, /burnall, /speedall, /flyspeedall and the two resets, /punch, /spin, /durability, /counter, /bottom, /thru, /jail, /unjail, /setjail, /jaillist.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/admin.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help admin prints the same names and the same nodes, line for line, because both read this one registration.

/heal

permission: ppcore.admin.heal · only while modules.admin.features.state is on · Admin.java

Brings a body back to full: health to the player's own maximum, any fire put out, and hunger lifted to at least ten if it was below that, so a healed player is not left half-fed as well. /heal [player] with no name heals you. It is the health half of the pair with /feed and shares nothing else with it - an operator clearing up after a fight usually types both.

/starve

permission: ppcore.admin.feed · only while modules.admin.features.state is on · Admin.java

Empties the hunger bar and the saturation behind it: /starve [player]. It always writes, which is what separates it from /hunger, whose bare form only reads - this is "food level zero" said the way an operator says it. The reply carries the number the server holds afterwards, so the result is measurable rather than assumed.

/feed

permission: ppcore.admin.feed · only while modules.admin.features.state is on · Admin.java

Fills the hunger bar to twenty, fills the saturation behind it and clears exhaustion, so the player does not start losing the bar again on their next sprint. /feed [player] with no name feeds you.

/saturation (/sat)

permission: ppcore.admin.feed · only while modules.admin.features.state is on · Admin.java

Reads or sets saturation - the hidden bar hunger drains into first, which is why a player can look full and still be losing food to nothing visible. /saturation [player] [amount], and the name and the number may be typed in either order because both orders are what people type. With no amount it prints the current value against twenty; with one it writes it, capped at twenty, and says when it clamped. /sat is the same verb under the source plugin's short name.

/hunger (/foodlevel)

permission: ppcore.admin.feed · only while modules.admin.features.state is on · Admin.java

Reads or sets the food bar itself: /hunger [player] [amount], either order. With no amount it prints the bar against twenty. Writing it can also cut saturation, because the game silently throws away saturation above the bar - the reply says when that happened rather than reporting a number the client will not draw. /foodlevel is the same verb.

/maxhp (/maxhealth)

permission: ppcore.admin.maxhp · only while modules.admin.features.state is on · Admin.java

Reads or sets a player's maximum health: /maxhp [player] [amount], either order. With no amount it prints the ceiling the player is carrying, so a change made by an effect or by another module is visible in the answer. With an amount it writes it between one and 1024, brings the body down if the new ceiling is below the current health, and reports both numbers. On a server where the hearts module owns this number, a write is refused and the verb that owns it is named - /hearts setmax <player> <amount> - while a read still works, because reading a number is not a second opinion about it.

/statsedit (/stedit)

permission: ppcore.admin.statsedit · only while modules.admin.features.state is on · Admin.java

The one place a recorded number about a player is corrected: /statsedit <player> <field> <value>, for minutes, blocks, kills and deaths. A value beginning + or - is a delta rather than a new total, which is what a correction usually is - "add the twenty minutes the crash lost" rather than "set it to 412" - and every change is logged with the before and after, plus a line to the player if they are online. The other fields this verb knows (money, hearts, rank, group, claimblocks, lastdeath) are refused with the verb that owns them, because two writers of one number is how a server comes to disagree with itself.

/god (/godmode, /tgm)

permission: ppcore.admin.god · only while modules.admin.features.state is on · Admin.java

Toggles damage immunity: /god [player], and with no name the target is you. The state is kept in the module's own file, so a holder who logs out still has it when they come back, and both the player and the sender are told which way it went.

/fly (/flight)

permission: ppcore.admin.fly · only while modules.admin.features.state is on · Admin.java

Toggles the right to fly: /fly [player]. Turning it on also starts the flight, because a player standing on the ground with flight allowed looks broken until they jump; turning it off stops the flight and the permission together. Like /god the state is remembered across a restart, and the same list answers /flylist.

/godlist

permission: ppcore.admin.god · only while modules.admin.features.state is on · Admin.java

Everyone in god mode, named, including holders who are not online - the state is persisted, so a player who left with it appears with (offline) beside their name. An empty list says so in a sentence rather than printing nothing.

/flylist

permission: ppcore.admin.fly · only while modules.admin.features.state is on · Admin.java

The same list for flight: who may fly, including anybody offline who kept it.

/speed (/walkspeed, /flyspeed)

permission: ppcore.admin.speed · only while modules.admin.features.state is on · Admin.java

Changes how fast a player moves: /speed <1-10> [player], or /speed walk|fly <1-10> [player]. Ten is the fastest the client renders smoothly - above it the server accepts the number and the player rubber-bands - so the range is capped and said out loud. /walkspeed and /flyspeed are this verb with the mode in the name, and an explicit walk or fly argument wins over the name, because somebody who typed both meant the argument. With no mode at all the player's own state decides: somebody flying gets a flight speed, somebody walking a walking speed.

/gamemode (/gm, /gma, /gmc, /gms, /gmsp)

permission: ppcore.admin.gamemode · only while modules.admin.features.state is on · Admin.java

Changes a player's game mode: /gamemode <survival|creative|adventure|spectator> [player], and 0 to 3 mean the same four. The source plugin's short names are aliases rather than separate commands - /gmc Beta is creative for Beta, /gms survival, /gma adventure, /gmsp spectator - because the mode is in the name that was typed and there is nothing else to parse; /gm is the plain alias of all four.

/exp (/xp)

permission: ppcore.admin.exp · only while modules.admin.features.state is on · Admin.java

Gives or sets experience: /exp <amount> [player] is a give, /exp give <amount> [player] says so out loud, and /exp set <levels> [player] puts the level and clears the progress into it. Both spellings exist because both are what people type, and the bare number is the shortest thing that can work; amounts below zero are treated as zero rather than refused, so a clamp cannot be mistaken for a typo. /xp is the same verb.

/checkexp (/xplook)

permission: none of its own · only while modules.admin.features.state is on · Admin.java

The read half of the same number: the level, how far into that level the player is, the total experience, and how many points are still needed for the next level. Reading your own costs nothing; reading somebody else's asks for the node /exp already asks for, and the refusal says which of the two it was.

/kill

permission: ppcore.admin.kill · only while modules.admin.features.state is on · Admin.java

Kills one player: /kill [player]. It refuses a target who is in god mode and says why, because the alternative is a command that looks like it did nothing. A vanilla selector (@e, @a, @p) is not ours: it is handed straight to the server's own command, which reaches whatever a selector can reach, so a macro that has always used one keeps working.

/suicide

permission: none of its own · only while modules.admin.features.state is on · Admin.java

Kills you. There is nothing to name and no target to get wrong, and it is a player's verb - the console has no body to remove.

/clear (/invclear, /ci)

permission: ppcore.admin.clear · only while modules.admin.features.state is on · Admin.java

Empties a player's inventory: /clear <player>, armour and the off hand included. It counts what was there before clearing, so "emptied a full inventory" and "emptied nothing" are different answers, and a selector argument goes to the server's own command as /kill's does. /invclear and /ci are the same verb.

/ender (/enderchest, /ec, /echest)

permission: none of its own · only while modules.admin.features.state is on · Admin.java

Opens your own ender chest wherever you are standing: /ender. There is no target, because the contents are already yours - this is the command for a player who is far from a chest they own. /ec, /echest and /enderchest are the same verb.

/dispose

permission: none of its own · only while modules.admin.features.state is on · Admin.java

A bin: /dispose opens a 36-slot inventory whose title says that anything left in it is gone when it closes. Nothing is written to disk and no rule decides what is destroyed - closing the window is the whole of it, and the title is admin.disposeTitle.

/air (/oxygen)

permission: ppcore.admin.air · only while modules.admin.features.state-extra is on · Admin.java

Refills or drains a player's breath bar: /air [player] [ticks], and the name and the number may be typed in either order because both orders are what an operator types. No number means full. A number is capped at twenty minutes of air - not because a lung can hold it, but because a slipped digit should not be a one-way trip - and zero is answered as "cannot breathe" rather than as a quantity. /oxygen is the same verb.

/burn (/ignite)

permission: ppcore.admin.burn · only while modules.admin.features.state-extra is on · Admin.java

Sets a player on fire: /burn [player] [seconds]. With no number the length is admin.burn.seconds, and the range is one second to an hour. Both the target and the sender are told the number, because the fire bar clears the moment it is set and the reply is the only place the clock is visible. /ignite is the same verb.

/ext (/extinguish)

permission: ppcore.admin.burn · only while modules.admin.features.state-extra is on · Admin.java

Puts a player out: /ext <player>. Same target rules and the other direction of /burn - registering one without the other would make the pair a one-way door. /extinguish is the same verb.

/clearender (/emptyender)

permission: ppcore.admin.clearender · only while modules.admin.features.state-extra is on · Admin.java

Empties a player's ender chest: /clearender <player>. It counts what was inside before clearing, so the report can say how many stacks went rather than only that it happened, and the player is told their own chest was emptied too. /emptyender is the same verb.

/falldistance (/setfalldistance)

permission: ppcore.admin.falldistance · only while modules.admin.features.state-extra is on · Admin.java

Sets how far a player has already fallen: /falldistance <player> <blocks>. The target has to be named, and the number is read from the argument after it. Zero is a real value rather than "no answer", which is why the verb reports the distance the server now holds instead of echoing what was typed - this is how a body that keeps taking fall damage on landing from nowhere is cleared without a leap of faith.

/smite (/lightning)

permission: ppcore.admin.smite · only while modules.admin.features.state-extra is on · Admin.java

Strikes lightning at a player: /smite [player]. admin.smite.effect-only decides which of the two things this verb means on your server: a warning, where the sky lights up and nobody is hurt, or a real strike. The reply says which answer it gave, so a warning is never mistaken for a punishment that failed to land.

/shakeitoff (/shake)

permission: ppcore.admin.shakeitoff · only while modules.admin.features.state-extra is on · Admin.java

Strips the harmful effects a fight left on a body: /shakeitoff [player], and with no name it is your own. "Harmful" is the game's own classification rather than a list kept in this module, so an effect a later server version adds is covered the day it exists; the reply names what came off (poison, slowness ii) and says plainly when there was nothing to shake off. From the console a player must be named, because the console has no body of its own. /shake is the same verb.

/feedme

permission: ppcore.admin.feed · only while modules.admin.features.bulk is on · Admin.java

Feeds you and only you. The four feedme, feedother, feedall and feednear names are the source plugin's four ways of pointing one verb at a set - yourself, one player, everyone, the people around you

this one, since the console has no hunger bar to fill.

/healme

permission: ppcore.admin.heal · only while modules.admin.features.bulk is on · Admin.java

Heals you: health to your maximum, fire out, hunger lifted to at least ten. It is /heal with yourself as the target, and it is the same body as the two *other forms and the two *all forms below.

/feedother

permission: ppcore.admin.feed · only while modules.admin.features.bulk is on · Admin.java

Feeds one named player: /feedother <player>. A name is required - not being you is the whole point of the name - and the reply says who was fed.

/healother

permission: ppcore.admin.heal · only while modules.admin.features.bulk is on · Admin.java

Heals one named player: /healother <player>, with the health, the fire and the food floor that /heal gives, exactly as it does when it is handed a name.

/feedall

permission: ppcore.admin.feed · only while modules.admin.features.bulk is on · Admin.java

Feeds everyone online in one pass, and answers with the number of players who were fed - the number an operator checks against the player list when it matters. Nobody offline is touched: there is no live bar to write.

/healall

permission: ppcore.admin.heal · only while modules.admin.features.bulk is on · Admin.java

Heals everyone online, and answers with the count.

/feednear

permission: ppcore.admin.feed · only while modules.admin.features.bulk is on · Admin.java

Feeds the players around you, and not you: /feednear [radius], with admin.bulk.radius as the radius when none is typed. The sender is left out on purpose, because a version that also fed the caster would be /feedme with a longer name; when nobody is within the radius it says so rather than reporting a count of zero.

/healnear

permission: ppcore.admin.heal · only while modules.admin.features.bulk is on · Admin.java

The health half of the same set: the players around you except you, /healnear [radius].

/extall

permission: ppcore.admin.burn · only while modules.admin.features.bulk is on · Admin.java

Puts every online player out, and answers with the count. It is /ext over the whole server, which is a fight-recovery tool rather than a punishment.

/burnall

permission: ppcore.admin.burn · only while modules.admin.features.bulk is on · Admin.java

Sets every online player on fire: /burnall [seconds], with the same admin.burn.seconds default as /burn and a ten-minute ceiling instead of the hour one player may be given - a whole server on fire is a spectacle, and it should not outlive the joke.

/speedall

permission: ppcore.admin.speed · only while modules.admin.features.bulk is on · Admin.java

Sets movement speed for everyone online: /speedall <1-10>. Each player follows their own state exactly as /speed does - somebody flying gets a flight speed, somebody walking a walking speed - and the reply names how many of them were flying when it was applied.

/flyspeedall

permission: ppcore.admin.speed · only while modules.admin.features.bulk is on · Admin.java

Sets the flight speed for everyone online, one number for all of them whether or not they are flying: /flyspeedall <1-10>.

/flyspeedreset

permission: ppcore.admin.speed · only while modules.admin.features.bulk is on · Admin.java

Puts flight speed back to the game's default, for a named player or for yourself: /flyspeedreset [player]. Reset means what a fresh player has rather than what that player had before, which is the answer that cannot surprise anybody.

/walkspeedreset

permission: ppcore.admin.speed · only while modules.admin.features.bulk is on · Admin.java

Walking speed back to the game's default: /walkspeedreset [player].

/punch

permission: ppcore.admin.punch · only while modules.admin.features.shove is on · Admin.java

Knocks a player back: /punch [player]. The direction is away from the sender's own position, which is the direction an operator means; from the console, which has no position to be away from, the punch goes along the target's own facing instead. There is always some lift, because a flat shove that leaves the target standing is not a punch, and the strength is admin.punch.power. /punch is one half of a pair with /spin: both are pranks to a friend and harassment to anybody else, which is why the module registers them behind their own switch.

/spin

permission: ppcore.admin.spin · only while modules.admin.features.shove is on · Admin.java

Spins a player's view whole turns: /spin [player] [turns], one to ten. It is animated over a second rather than jumping the view in a single teleport, because one 360-degree jump is indistinguishable from a client glitch - and a second /spin on the same player replaces the first instead of stacking two turns on one head.

/durability

permission: none of its own · only while modules.admin.features.readouts is on · Admin.java

Reads or sets how much of the held tool is left: /durability [value] [player], and the number is the remaining durability - what the item's own bar shows - rather than the damage taken. The reply always names what the server holds afterwards, so a value beyond the item's maximum (a 500 on a wooden pickaxe) is visible as a clamp rather than as silence. Nothing damageable in hand is answered as its own thing, not as a failure.

/counter

permission: none of its own · only while modules.admin.features.readouts is on · Admin.java

Counts the entities around you: /counter [type] [radius], with admin.counter.radius as the radius when none is typed and two hundred blocks as the ceiling. With a type it answers one number - "how many zombies did that farm just make" - and without one it answers the total plus a short breakdown by type, which is what makes both forms a measurement rather than a report. Players are not counted.

/bottom

permission: ppcore.admin.movement · only while modules.admin.features.movement is on · Admin.java

Puts a player at the lowest place they can stand in their own column: /bottom [player]. That is the lowest block they can actually stand on rather than the world's floor, because an operator standing on a roof two hundred blocks up means the ground; a column with nothing to stand on says so instead of dropping them somewhere arbitrary.

/thru

permission: ppcore.admin.movement · only while modules.admin.features.movement is on · Admin.java

Walks you through the wall you are looking at: /thru follows your eye forward until something solid has been crossed and the first pocket of air behind it has headroom, and puts you there. It stops looking after admin.thru.max blocks and says that there was nothing to go through, which is a different answer from a wall with a sealed room behind it. Only a player can use it - there is no eye to trace from the console or a command block.

/spawnmob (/mob)

permission: ppcore.admin.spawnmob · only while modules.admin.features.mobs is on · Admin.java

Spawns mobs at a player: /spawnmob <type> [amount] [player]. The type is the game's own entity name (a zombie, a wither), and it is checked against what the server says can spawn, so an unknown name is refused with the name in the sentence rather than spawning nothing and reporting success. The amount is capped by admin.spawnmob.max, and a player name anywhere after the type is the destination. /mob is the same verb.

/give

permission: ppcore.admin.give · only while modules.admin.features.items is on · Admin.java

Gives an item to a player: /give <player> <item> [amount]. An amount larger than a stack is handed over in stack-sized batches, and anything that will not fit is dropped at the player's feet rather than being lost, so /give Alpha diamond 100 is a hundred diamonds. The item is a material name, the player gets their own line saying what arrived, and a selector argument is passed to the server's own command as /kill and /clear do.

/giveall

permission: ppcore.admin.giveall · only while modules.admin.features.items is on · Admin.java

Gives an item to everyone online: /giveall <item> [amount], with the same overflow rule - anything that does not fit is dropped beside each player rather than disappearing. The reply names the item and the number of players who got one.

/more (/stack)

permission: ppcore.admin.more · only while modules.admin.features.items is on · Admin.java

Fills the stack in your hand to its maximum, or to a number you name: /more [amount]. It needs something in hand and an item that stacks at all. /stack is the same verb under the source plugin's name.

/hat

permission: none of its own · only while modules.admin.features.items is on · Admin.java

Wears the item in your hand as a helmet: /hat. The helmet that was there goes back into your hand, which makes a second /hat its own undo and means nothing is destroyed by trying it.

/repair (/fix)

permission: ppcore.admin.repair · only while modules.admin.features.items is on · Admin.java

Repairs the item in your hand, or everything you are carrying with /repair all - the inventory and the armour, slot by slot, so the repair is written back to the player rather than made on a copy. An item with no durability to begin with (a block of stone, a written book) is answered as having nothing to repair instead of being counted as fixed. /fix is the same verb.

/workbench (/craft, /wb)

permission: none of its own · only while modules.admin.features.stations is on · Admin.java

Opens a crafting table wherever you stand: /workbench is the same virtual window a real block gives, without needing the block - or the walk back to one. On server software that refuses to open a virtual station the answer says so plainly instead of throwing a stack trace into the console, which is true of every station verb below as well. /craft and /wb are the same verb.

/anvil

permission: none of its own · only while modules.admin.features.stations is on · Admin.java

Opens an anvil wherever you stand, with no anvil block and no walk to one. It is the real anvil interface, so repairs, renames and combination costs behave the way they do at a block.

/grindstone

permission: none of its own · only while modules.admin.features.stations is on · Admin.java

Opens a grindstone wherever you stand. It is the real interface, so disenchanting returns the experience the way a block does.

/cartography (/cartographytable)

permission: none of its own · only while modules.admin.features.stations is on · Admin.java

Opens a cartography table wherever you stand, for copying and zooming maps without hunting for a village. /cartographytable is the same verb.

/smithing (/smithingtable)

permission: none of its own · only while modules.admin.features.stations is on · Admin.java

Opens a smithing table wherever you stand - the real interface, so upgrade templates and netherite costs follow the server's own rules. /smithingtable is the same verb.

/stonecutter

permission: none of its own · only while modules.admin.features.stations is on · Admin.java

Opens a stonecutter wherever you stand, with the full recipe list the block offers.

/loom

permission: none of its own · only while modules.admin.features.stations is on · Admin.java

Opens a loom wherever you stand, for banner patterns without carrying a loom around.

/enchanting (/enchanttable)

permission: none of its own · only while modules.admin.features.stations is on · Admin.java

Opens an enchanting table wherever you stand: the table itself rather than a menu this plugin draws, so the real level costs, lapis and bookshelf rules apply. /enchanttable is the same verb.

/tphere (/s)

permission: ppcore.staff.teleport · only while modules.admin.features.teleport is on · Admin.java

Brings a player to where you are standing: /tphere <player>, and /s is the source plugin's short name for it. Both the moved player and the sender are told what happened. It works on players who are online - for somebody who is not here, the verbs further down this module are the ones that wait for a login.

/tppos

permission: ppcore.staff.teleport · only while modules.admin.features.teleport is on · Admin.java

Teleports you to coordinates: /tppos <x> <y> <z>, in the world you are already standing in. The three must be numbers, and the answer repeats them rounded, which is enough to see a mistyped sign before you look around.

/tpall

permission: ppcore.staff.teleport · only while modules.admin.features.teleport is on · Admin.java

Brings everyone online to you: /tpall, or /tpall <player> to move just one of them. The count in the answer is the number actually moved, so the sender can tell "everybody came" from "everybody was already here".

/tpo (/tpoffline)

permission: ppcore.staff.teleport · only while modules.admin.features.teleport is on · Admin.java

Takes you to where a player who is offline was last seen: /tpo <player>. The module keeps that record for everybody who logs out, which is what makes this the verb for getting somebody out of the nether before they log in tomorrow - the answer names the place, the world and how long ago the record was written, so the age of the answer is visible before you stand in it. A player who is online is refused, because moving to somebody who is here is what the server's own teleport is for, and the two ways of having no destination - nobody ever wrote a record for that name, and the record's world is not loaded at this boot - are different sentences, because they send staff looking in different places. /tpoffline is the same verb.

/tpohere

permission: ppcore.staff.teleport · only while modules.admin.features.teleport is on · Admin.java

Moves a player who is offline to where you stand, the next time they log in: /tpohere <player> [cancel]. The request is queued, logged, and visible again in the answer; cancel (or reset, or off) takes it back before it happens. A player who is online is refused and pointed at /tphere, because a move that waits for a login has nothing to do with somebody who is already here.

/tpopos

permission: ppcore.staff.teleport · only while modules.admin.features.teleport is on · Admin.java

Puts a player who is offline at coordinates when they next log in: /tpopos <player> <x> <y> <z> [world], in your own world when none is named - and in the server's first world when the sender is the console or the panel's command box, so a queued move from a script does not fail for lack of a body. cancel as the second argument clears a queued move instead of setting one, which is the pair /tpohere has as well.

/tpallworld

permission: ppcore.staff.teleport · only while modules.admin.features.teleport is on · Admin.java

Brings everyone in one world to you: /tpallworld [world], your own world when none is named, and /tpallworld -a (or all) for every world at once. Each moved player is told which world they came from, because somebody pulled out of the nether should know where they were.

/launch

permission: ppcore.staff.teleport · only while modules.admin.features.teleport is on · Admin.java

Throws a player into the air: /launch [player] [velocity]. A lone number is the velocity rather than a name, because no player is called 3. The strength is capped by admin.launch.max, and the throw keeps the player's own horizontal speed, so somebody running is launched forward as well as up.

/world

permission: ppcore.admin.world · only while modules.admin.features.worlds is on · Admin.java

Lists the worlds, or sends a player to one. /world on its own lists every world with its player count and spawn point, marking the one you are in; /world <world> [player] sends a player to that world's spawn, yourself when no player is named. A first argument that names an online player is read as the player instead, sending them to the spawn of the world they are already in, which is what an operator means by /world Alpha. Before teleporting it looks for the first place in the column a body can actually stand on, so a spawn point inside a roof does not leave the player in the wall.

/findbiome

permission: ppcore.admin.world · only while modules.admin.features.worlds is on · Admin.java

Finds the nearest instance of a biome and takes you there: /findbiome <biome> [radius], with admin.findbiome.radius as the search radius when none is typed and admin.findbiome.max-radius as its ceiling. Already standing in that biome is an answer of its own, and so is no such biome inside the radius - both are said rather than teleporting you somewhere nearly right. The trip is logged with the biome and the distance, because a search that runs for a second should leave a trace.

/tfly

permission: ppcore.admin.fly · only while modules.admin.features.tempmode is on · Admin.java

Flight that ends on its own: /tfly [player] [seconds]. The length defaults to admin.temp.seconds, and 0 ends the grant now rather than asking for a zero-second one, which is the one thing a clock needs. It writes the same state /fly writes and appears in /flylist, so nothing in the module has to know which verb turned it on; a second grant replaces the first clock rather than stacking two.

/tgod

permission: ppcore.admin.god · only while modules.admin.features.tempmode is on · Admin.java

God mode that ends on its own: /tgod [player] [seconds], the same clock and the same 0 to end it early, over the same state /god and /godlist use.

/jail

permission: ppcore.staff.jail · only while modules.admin.features.jail is on · Admin.java

Puts a player in the cell: /jail <player> [time] [reason]. A time reads 30m, 2h, 1d or a bare number of minutes, and no time at all is a sentence with no end - the plugin does not invent one. It needs a jail to have been set with /setjail first, and it refuses to jail a member of staff unless the sender is an admin. The release is checked on a clock, so somebody who logs out for the day is let out on time rather than on their next move; the broadcast follows staff.broadcastPunishments and the default reason is admin.jail.defaultReason.

/unjail (/release)

permission: ppcore.staff.jail · only while modules.admin.features.jail is on · Admin.java

Lets a player out of jail: /unjail <player>, online or by name - the record itself knows the name, so somebody who is not here can still be released. Releasing returns them to the place the record kept when they were picked up, and a name that is not in jail is told so rather than being quietly ignored. /release is the same verb.

/setjail (/jailedit)

permission: ppcore.staff.jail · only while modules.admin.features.jail is on · Admin.java

Sets the jail to where you are standing, or to coordinates from the console: /setjail [x y z] [world]. It prints the radius around that point that actually holds people, because a radius bigger than the cell is a jail players walk out of - the module has exactly one cell, and this is where it is. /jailedit is the same verb under the source plugin's name.

/jaillist (/jails)

permission: ppcore.staff.jail · only while modules.admin.features.jail is on · Admin.java

Who is in jail, how much of their time is left, why they are there and who put them there: /jaillist reads the same record the release clock reads, so somebody released on time is off the list immediately. /jails is the source plugin's name for the same list.

Permissions. ppcore.admin.maintenance, .world, .heal, .feed, .air, .burn, .clearender, .falldistance, .smite, .shakeitoff, .spawnmob, .god, .fly, .speed, .gamemode, .punch, .spin, .movement, .exp, .kill, .clear, .repair, .maxhp, .statsedit, .more, .give, .giveall, .effect, .flyc, .killall, .lore, .notarget, .saveall, .attach, .shoot, .sound, ppcore.staff.jail, ppcore.staff.freeze, ppcore.admin.

cos - nicks, nameplates, hats, sitting, holograms, portals, armour stands and heads

What it does. The cosmetic and build-extra surface, and the two things it keeps firmly apart are /nick (the name in chat and the tab list) and /nameplate (the name above the head), because tying them together would make it impossible to be "Josh" in chat and "[Owner]" above your head. /sit spawns an invisible marker armour stand and puts the player on it, which is why sitting survives a relog; right-clicking a stair or slab sits you there unless you are sneaking, so chairs never get in the way of building. Holograms are display entities - real multi-line text, no collision box - and they are respawned from holograms.yml on every start, so the file is the truth. A portal is two corners plus a destination, with a cooldown and a leave-before-it-fires rule so a linked pair does not bounce a player back and forth. Hats and balloons are drawn: a hat goes into the head slot of every client that can see you by a packet, so the helmet underneath is never taken from your inventory and never lost when the plugin is removed, and a balloon is an invisible stand that follows its owner and is removed when they leave. /armorstand is the source plugin's editor as subcommands over the stand you are looking at, and /head / /mobhead handle heads. Nothing here can ever be an advantage: a hat is not armour and a balloon does not fight - which is also why ppcore.cos.scale is deliberately left out of the shipped default group, since scale changes the hitbox and reach.

Turning it on. modules.cos.enabled: true (default) with fourteen switches: modules.cos.features .nick, .glow, .scale, .hats, .balloons, .sit, .armoreffect, .particle, .animation, .armorstand, .heads, .holograms and .portals. /scale needs a server with the size attribute. cos.hats.<id>.node gates one cosmetic behind its own permission, and price lets a player buy it with earned money (/cosmetics buy <id>), so a hat can be a rank perk, a purchase, or neither.

What you can configure. cos.nick.maxLength (32) and blockImpersonation (true - refuses a nickname identical to an online player's); cos.wear-ticks (2 - how often worn cosmetics are redrawn, and 0 switches a bob off, which is what a headless test server wants), cos.hat-refresh-ticks (40 - a busy server with many wearers can raise it), cos.balloon-height (2.2) and balloon-bob (0.12); cos.sit.offset (0.35) and chairs (true); cos.holograms.lineSpacing (0.27), startHeight (1.0), billboard (CENTER), seeThrough (false - true draws through walls) and lineWidth (200); cos.portals.cooldownSeconds (3), message and reach (8); cos.armoreffect.requiresFullSet (true - false is /glow with extra steps) and sameFamily (true); cos.particle.allowed (the list is the picker's menu: HAPPY_VILLAGER, CRIT, ENCHANT, FLAME, SNOWFLAKE, HEART) with count (2) and spread (0.4); cos.animation.ticks (20), armSwings (3) and spinDegrees (360); cos.armorstand.reach (8 - past it the command answers with the source plugin's own "Armor stand is too far" instead of pretending there is nothing there); cos.heads.max-amount (64); cos.mobheads.textures for a mob head the game has no item for - a cow or a pig is a texture, and the module refuses by name and lists what it does have rather than inventing one.

cos.hats and cos.balloons ship with hand-written examples plus a generated block that the build writes from the server's own material list (35 hats, 90 balloons), and the hand-written rows are never touched by a rebuild.

Commands. /nick, /nameplate, /glow, /scale, /cosmetics, /cosmetics buy <id>, /sit, /ride, /armoreffect, /particlepicker, /panimation, /totem, /armorstand (visible, arms, name, glow, gravity, plate, small, invulnerable, marker, label, scale, the pose parts, rotation, position, equip, copy, remove), /head, /mobhead, /mobhead list, /hologram, /portal (and /portal pos1). /cos check is the config report.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/cos.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help cos prints the same names and the same nodes, line for line, because both read this one registration.

/nick (/nickname)

permission: ppcore.cos.nick · only while modules.cos.features.nick is on · Cosmetics.java

Sets the name other players see in chat and in the tab list: /nick <nickname|off> [player], where off clears it. A nickname is limited by cos.nick.maxLength, and by default a nickname that is exactly another online player's name is refused, because that is the impersonation case that gets a server in trouble. Colour codes work only for a player who has the colour node, and renaming somebody else needs its own node - the two are separate on purpose, so a server can let players rename themselves without letting them forge a staff-coloured name. A trailing argument that names an online player is read as who it is for rather than as part of the nickname, so /nick Cool Steve sets "Cool" for Steve only when Steve is online.

/nameplate (/plate)

permission: ppcore.cos.nameplate · only while modules.cos.features.nick is on · Cosmetics.java

Sets the name drawn above a player's head: /nameplate <text|off> [player], and it is separate from /nick because the two are drawn in different places - a nickname covers chat and the tab list, a nameplate covers the world above their head. Changing somebody else's needs the module's others node and the colour node is what lets codes through. off, clear and no text at all are the same thing here - this one removes it, and it is the nickname that reads itself back.

/glow (/glowing)

permission: ppcore.cos.glow · only while modules.cos.features.glow is on · Cosmetics.java

Makes you visible through blocks by turning the glowing flag on, which is the setting a staff member wants at night or in a cave: /glow flips it, /glow on and /glow off set it, and the choice is stored, so it is applied again the next time you join. It applies to the player who typed it, and it answers with the state it set rather than leaving the reader to infer it.

/scale (/size)

permission: ppcore.cos.scale · only while modules.cos.features.scale is on · Cosmetics.java

Changes how big a player is: /scale <0.5-4> [player], or /scale reset back to normal, with the bounds from cos.scale.min and cos.scale.max - a size outside them is refused with the range rather than clamped. A server version without the size attribute is told so instead of the command appearing to work. Size is stored, so it comes back after a restart, and the answer names who was resized when it was somebody else.

/cosmetics (/cosmetic, /cosmeticsmenu)

permission: ppcore.cos.wear · only while modules.hats.features.balloons is on · Cosmetics.java

Hats and balloons, in one command because they are one screen of choices: /cosmetics or list shows what you are wearing and every option with its price, ownership and lock state, /cosmetics hat <id|off> and /cosmetics balloon <id|off> wear one or take it off, /cosmetics buy <id> pays the configured price out of your balance and remembers the purchase, and /cosmetics hide on|off hides what you are wearing without taking it off. An option whose node you do not have is marked locked and cannot be worn, one that is free is just worn, and reload (for staff) re-reads the definitions and redraws everybody. Somebody else's cosmetics are never edited from here - what a player wears belongs to that player.

/sit

permission: ppcore.cos.sit · only while modules.cos.features.sit is on · Cosmetics.java

Sits you down: /sit puts you on an invisible seat where you stand and /sit <player> sits you on that player, which is the party trick the source plugin ships. Sneaking stands you up. The seat is an invisible armour stand this module owns, so it is removed when you get up, when you quit and when the server stops, rather than being left in the world for somebody to find.

/ride (/mount)

permission: ppcore.cos.ride · only while modules.cos.features.sit is on · Cosmetics.java

Rides another player: /ride <player> makes you a passenger of that player, and /ride on its own gets you off whatever you are riding. Riding yourself is refused, and so is a player who already has a passenger - the refusal names which of the two it was. Both players are told, so the person being ridden knows who is on them.

/hologram (/holograms, /holo)

permission: ppcore.cos.admin · only while modules.cos.features.holograms is on · Cosmetics.java

Floating text: list shows every hologram with its line count and position, near narrows that to the ones within 20 blocks of you, create <name> <text> makes one where you stand and picks up the height from cos.holograms.startHeight, add appends a line to an existing one, set <name> <line> <text> rewrites one line by number, remove <name> <line> deletes a line (and the whole hologram when it was the last one), move <name> picks it up and puts it where you stand now, and delete <name> removes it for good. A name that already exists, a line number the hologram does not have, and a create from the console are each refused by name, because the position is where you stand. Holograms are entities, so they are respawned from the file at every start rather than being stored in the world. Needs ppcore.cos.admin.

/portal (/portals)

permission: ppcore.cos.admin · only while modules.cos.features.portals is on · Cosmetics.java

Regions that teleport. pos1 <name> and pos2 <name> set the two corners from the block you are looking at, link <name> <otherPortal|here|x,y,z[,world]> says where it goes (a coordinates pair may name a world, and here means where you stand), info <name> reads back the corners and the destination, list shows every portal and warns when one has no destination yet, tp <name> takes you through it by hand and delete <name> removes it. pos1 and pos2 have to be typed in game because they use the block under your crosshair, and a portal cannot be linked to a name that does not exist. Needs ppcore.cos.admin.

/armorstand (/armourstand)

permission: ppcore.cos.armorstand · only while modules.cos.features.armorstand is on · Cosmetics.java

Edits the armour stand you are looking at rather than an item or a player: info reads every flag, its scale, its body rotation, its name and its pose; arms, visible, name, glow, gravity, plate, small, invulnerable and marker each flip one flag; label <text|off> names it (and shows the name, because a label nobody can see is an inert setting); scale <0.1-10> resizes it; rotate <degrees> turns the whole stand, which is not the same as turning the torso; pose <part> <x> <y> <z> sets one part's angles and pose reset puts them back; and position <x> <y> <z> moves it. The reach is cos.armorstand.reach, a stand further away than that is refused with how far it actually is, and a marker stand - invisible to the ray by design - is still a stand you may edit. Needs ppcore.cos.armorstand.

permission: ppcore.cos.head · only while modules.cos.features.heads is on · Cosmetics.java

Gives you a player's head as an item: /head [player] [amount], with no name meaning your own. The amount is a whole number up to cos.heads.max-amount, and a name that has never logged in to this server is refused - a head of somebody who was never here would be a head of nobody. It is the player skin, which is a different thing from a mob head, and it needs a player to give the item to.

/mobhead

permission: ppcore.cos.mobhead · only while modules.cos.features.heads is on · Cosmetics.java

Gives you a mob's head: /mobhead <mob> [amount], or /mobhead list for the ones this server has. The heads the game itself ships are given directly, and any other mob needs a texture - the base64 value and signature - in cos.mobheads.textures.<mob>, which this command reads rather than having a texture invented for it. A name with no head and no texture is refused with the list of known ones, and asking for a player's head here is answered by pointing at /head, since a player's head is their own skin. Needs ppcore.cos.mobhead.

/armoreffect (/armoureffect)

permission: ppcore.cos.armoreffect · only while modules.cos.features.armoreffect is on · Cosmetics.java

Glows you while you are wearing armour: /armoreffect on switches it on, /armoreffect off (or clear) switches it off, and with no argument it reads the state. It stores the choice and nothing else - the glow itself is decided by the same code that decides what a full set is, so it follows the server's cos.armoreffect.requiresFullSet and cos.armoreffect.sameFamily settings and cannot disagree with them about what counts as a set. The answer names what it will take to glow.

/particlepicker (/particle, /particles)

permission: ppcore.cos.particle · only while modules.cos.features.particle is on · Cosmetics.java

Chooses the particle you leave behind you: /particlepicker or list shows what you are using and the names this server offers, <name> picks one, and off clears it. The offered list is cos.particle.allowed, and a name that is not on it is refused even when the server has the particleat all - the list and the menu are one answer, so picking something invisible to list is not possible. A trail has to be redrawn to exist, so it is drawn by this module's own clock, which the particle feature starts; nothing is drawn for a player who has picked none.

/panimation (/playanimation)

permission: ppcore.cos.animation · only while modules.cos.features.animation is on · Cosmetics.java

A short vanity animation on yourself: /panimation wave waves, /panimation spin spins, and /panimation off stops it. It is a burst of frames over cos.animation.ticks ticks of the module's wear clock rather than a one-off effect, which is why it exists only when the animation feature does - there is no version of it where the animation runs and the clock does not. No argument is the same as wave.

/totem (/totemanimation, /totemanim)

permission: ppcore.cos.totem · only while modules.cos.features.animation is on · Cosmetics.java

Draws the totem-of-undying animation on a player: /totem <player> [silent]. It is a client effect for the target's own client, so nobody else is told, and the sound the animation makes is the server's own - silent (or quiet) plays the animation with no sound. It is an admin action rather than a player's own vanity burst, which is why it carries its own node and is not a verb of /panimation; the target is told who drew it, and a name that is not online is refused by name.

Permissions. ppcore.cos.sit, .ride, .nick, .nick.others, .nameplate, .nameplate.others, .glow, .scale, .colour, .wear, .head, .mobhead, .armorstand, .armoreffect, .particle, .animation, .totem, .admin, and the per-hat node cos.hats.<id>.node where a server writes one.

Files behind this page

What this section documentsSource
Groups, prefixes, inheritance, the node listspkg/src/ppcore/Perms.java (../../pkg/src/ppcore/Perms.java)
The economy, /pay, /cheque, the number format and /money checkpkg/src/ppcore/Econ.java (../../pkg/src/ppcore/Econ.java)
Hearts, the death tables, the anti-exploit rules and the player revivepkg/src/ppcore/Hearts.java (../../pkg/src/ppcore/Hearts.java)
Homes, warps, kits, teleports, spawn, /back, /jump, /up, /down, the sign editorpkg/src/ppcore/Essentials.java (../../pkg/src/ppcore/Essentials.java)
Claim blocks and the per-claim flag levelspkg/src/ppcore/Claims.java (../../pkg/src/ppcore/Claims.java)
Mines, palettes and refillspkg/src/ppcore/Mines.java (../../pkg/src/ppcore/Mines.java)
The earned ladder and the promotion command templatepkg/src/ppcore/Ranks.java (../../pkg/src/ppcore/Ranks.java)
Punishments, warnings, vanish, staff mode, saved inventories, address memorypkg/src/ppcore/Staff.java (../../pkg/src/ppcore/Staff.java)
The chat event, messages, mail, broadcasts, the palette, /ctextpkg/src/ppcore/Chat.java (../../pkg/src/ppcore/Chat.java)
Listings, bids, expiry, the webhook announcementpkg/src/ppcore/Ah.java (../../pkg/src/ppcore/Ah.java)
Enchantment locks, the level gate and the item-editing verbspkg/src/ppcore/Enchants.java (../../pkg/src/ppcore/Enchants.java)
Diagnostics, world control, /maintenance, AFK, the tab list, schedules, /scan, /replaceblockpkg/src/ppcore/ServerOps.java (../../pkg/src/ppcore/ServerOps.java)
Player state, items, teleport, the timed modes, the body verbs, jailspkg/src/ppcore/Admin.java (../../pkg/src/ppcore/Admin.java)
Nicknames, nameplates, hats, balloons, sitting, holograms, portals, stands, headspkg/src/ppcore/Cosmetics.java (../../pkg/src/ppcore/Cosmetics.java)
Every default quoted abovepkg/config.yml (../../pkg/config.yml)
The switches, and the shipped groups and ladderpkg/config.yml (../../pkg/config.yml), pkg/profiles/ (../../pkg/profiles/)

Economy, factions and shops

These nine modules are the ones that make a survival or factions server worth playing rather than merely working: land that groups fight over, the bases that stand on it, the farms that feed it, the market that prices it, and the tooling that keeps the whole thing ticking when a hundred players are online. Read chapter 12 first if you have not - it states the shape every entry below follows, where a module's settings live in config.yml, and how the profile layer decides what wins when two of them name the same key.

Most of these ship switched on, because the shipped config.yml is a complete copy of the plugin's defaults. What a template changes is the handful of switches in pkg/profiles/*.yml - the Factions and Raidsteal profiles are the two that matter most on this page, and they differ from each other in ways that decide how a base is defended.

factions - factions: land claims, power, allies and faction chat

What it does. Land that groups own and fight over. /f create <tag> makes a faction, /f invite, /f join, /f kick, /f promote, /f demote and /f transfer manage its members, /f claim takes the chunk you are standing in and /f unclaim gives it back. Power is the currency land is bought with: it starts at 10, is gained by killing an enemy and lost by dying, and regenerates per minute while a member is online. DTR (death to raid) is what gives a raid an ending - while a faction's DTR is above zero its land is closed to enemies; every member death takes it down and freezes the clock, and a faction that logs out entirely does not recover, which is what stops "log out until it is safe" from being an answer. /f tnt is a shared TNT bank, /f upgrade buys a bigger bank, more shared vaults and more member slots out of the faction's own bank (never with real money), /f ally, /f enemy, /f neutral and /f truce set relations, /f sethome and /f home are the base, /f setwarp and /f warp are named ones, /f map draws the local territory, /f who, /f list, /f top and /f power are the read-outs, /f chat is faction-only talk, /f toggle scoreboard is the sidebar, /f stuck searches for wilderness when a player is trapped in someone else's land, and /f zone claim <tag> paints named land that belongs to nobody (a spawn town, a market, an arena) with its own flags (build, container, door, button, pvp, explosions, entry, mobs - and mobs: false blocks natural spawning only, so a world boss still arrives).

gamemode is the one setting that decides what the module is. claim makes a chunk boundary the defence; raidsteal makes land protect nothing and moves the defence onto the beacons module below; both (the default) runs the two side by side, so a base inside claimed land is armoured on top of the claim. The disabled half stands its own commands down with a message naming the enabled one, so a player typing /f claim on a raidsteal server is told why.

Turning it on. modules.factions.enabled: true, which the shipped file and every faction-shaped profile already set. Nothing needs to exist first: /f create makes the faction, and the first /f claim takes land. It yields to a factions plugin when one is installed (interop.yieldToInstalledPlugins is on by default), and interop.forceModules.factions: true runs it anyway. The Factions template switches claims off as it turns this on, because two claim systems means two answers to "can this player build here"; the Raidsteal template is the same file with factions.gamemode: raidsteal and one more line on beacons.

What you can configure. Everything is in the factions: section, and the feature switches are under modules.factions.features (chat, home, territoryNotice, protectContainers, safeZoneOwnLand, allowAllyBuild, friendlyFire, allyFriendlyFire, homeMustBeOwnLand, homeEnemyAllowed, protectFromExplosions, loginNotice, dtr, tntbank, warps, stuck, graceBlocksPvp).

Commands. /f (aliases /faction, /factions, /fac) for players, /fadmin (alias /fa) for staff. /playport help factions prints every verb with its description, and it is built from the registration, so a switched-off module appears there as switched off rather than as a command that mysteriously does not exist.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/factions.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help factions prints the same names and the same nodes, line for line, because both read this one registration.

/faction (/f, /factions, /fac)

permission: none of its own · always · Factions.java

The whole faction, under one command: /faction on its own prints the help page, and every other word is a subcommand of it. Founding and membership first - /f create <tag> [name] (the tag is a few letters and numbers, by default 3 to 6 and not already taken, and factions.createCost can charge for it), /f invite <player>, /f join <tag>, /f leave, /f kick <player>, /f promote and /f demote, /f role <player> <rank>, /f transfer <player>, and /f disband, which only the leader may run and which asks for /f confirm first. Land is /f claim [radius] for the chunk you stand in, /f autoclaim to claim as you walk, /f unclaim, /f unclaimall and /f map to see what surrounds you; /f who [tag] is a faction's sheet, /f top the ranking by power, /f power what land your power buys and /f dtr [tag] how close a faction is to being raidable.

Then the holdings and the rules: /f sethome, /f home, /f warp [name] with /f setwarp and /f delwarp; /f money [deposit|withdraw|send] for the bank and /f tnt [deposit|withdraw|fill|siphon] for the TNT bank, with /f upgrades and /f upgrade <name> to spend either; /f set <property> for the tag, description, open, link, title, boom and warps; /f perm <relation> <permission> <allow|deny> and /f ally <tag>, /f enemy <tag>, /f neutral <tag> for what allies and enemies may do; /f ban <player> and /f unban <player> to keep somebody out; /f toggle <chat|logins|scoreboard|seechunk> for your own switches, /f chat to talk only to your faction, /f zone for the zone you are standing in, /f grace for the grace window, /f stuck to be moved to the nearest wilderness and /f confirm to answer a question a command asked. A verb whose feature is switched off says so, and a word that is not one of these is refused with the help page rather than ignored.

/fadmin (/fa)

permission: ppcore.factions.admin · always · Factions.java

The staff side of the same faction, as its own command: a node on one cannot be handed out by accident with a node on the other. /fadmin force join <player> <tag>, force kick <player>, force disband <tag>, force home <player> and force role <player> <tag> <role> move people and factions without their leaders; /fadmin dtr <tag|resetall> <set|modify> [value] sets or shifts the raidable timer; /fadmin power <set|remove|permanent|boost> <player> <value> [minutes] changes power, including the temporary kind that decays back. Every verb that writes names the faction or the player it touched, so the command is one line in the log rather than a silent edit, and /fadmin alone prints its own help.

Permissions. /f is registered with no node of its own - each verb checks what it needs, so a player gets a refusal rather than "unknown command". /fadmin needs ppcore.factions.admin.

beacons - faction beacons, reinforced obsidian and siege charges

What it does. The base that has to be besieged, which is what the raidsteal gamemode leans on. A member places a beacon and runs /beacon register: everything inside beacons.radius (50 blocks, a square) that is made of a protected material - obsidian, crying obsidian and spawners - cannot be mined with a pickaxe at all, and comes down to siege charges and nothing else. Every hit is announced to the defenders with the coordinates and the attacker's name, so a raid becomes a battle rather than a burglary. Newly placed blocks are hardening, not hardened: a protected block is mineable by an enemy until both it and the beacon have stood protection.delayedSeconds (300s, which the Raidsteal profile lowers to 180), and the timer is per block - the beacon's own clock starts when it is registered (/beacon timer reads it, /reinforce restarts it) and a block's starts when it is placed. Both halves matter: a block-only timer lets an enemy hollow out a wall the moment the beacon goes up, and a beacon-only timer lets them drop a fresh wall in front of a hammering siege. Build denial inside the base, explosion stripping and the siege charge are deliberately not gated by the window - a charge is the designed way in, so it works from the first second.

The other half is quality of life: /obtoggle (aliases /obstoggle, /obsidiantoggle) is the player's own instant-obsidian-mining switch, and /beacon border draws the protected square for the player who asks (border.seconds is the look; /beacon border on keeps it up until turned off).

Turning it on. modules.beacons.enabled: true (on in the shipped file, and named explicitly by both Faction templates) plus first use: /beacon register while standing at a beacon in the faction's own land (allowOutsideOwnLand: false by default), which needs officer rank or above. features.autoRegisterOnPlace (false) makes a placed beacon register itself instead.

What you can configure. The beacons: section: radius (50), maxPerFaction (1), protectedMaterials (OBSIDIAN, CRYING_OBSIDIAN, SPAWNER), allowOutsideOwnLand (false), protectFromExplosions (true), protection.delayedSeconds (300; 0 turns hardening off, so every base is armoured at once), instantBreak.enabled (true) with instantBreak.materials (OBSIDIAN, CRYING_OBSIDIAN), border.seconds (5), siege.hitsRequired (5), siege.chargeCooldownSeconds (3), siege.alertCooldownSeconds (10), siege.splashEnabled (true), siege.splashMaxBlocks (10), siege.chargeItem (FIRE_CHARGE), and beacons.messages.* for every line - including the reinforcement read and the border text. The two feature switches are modules.beacons.features.protectFromExplosions and .autoRegisterOnPlace.

Commands. /beacon (aliases /fbeacon, /fb) with register, timer, border, reinforcement and forceprotect; /siegecharge (alias /charge); /reinforce; /obtoggle. forceprotect is the operator verb that arms a base by hand: it forgets the placement clocks and winds the beacon's clock back, which is what you want when a base should be hardened now.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/beacons.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help beacons prints the same names and the same nodes, line for line, because both read this one registration.

/beacon (/fbeacon, /fb)

permission: none of its own · always · Beacons.java

A faction's base, held by a beacon block. /beacon register takes the beacon you are looking at within eight blocks - or the one you are standing next to, within five, because aiming at a block is fiddly - and turns it into your faction's base. What it needs is checked and each refusal has its own sentence: you must be in a faction, an officer or above (or hold the admin node), the beacon must stand inside your own claimed land unless allowOutsideOwnLand is on, the server must be running the game mode that holds ground with bases rather than claims, and the faction must be under its limit of bases. When it succeeds it says the protected radius and, if the base is still on its hardening clock, that an enemy may break it until the clock runs out.

The rest reads the base back. /beacon info [tag|player] reports every beacon the faction has, where it stands, the radius, how long it has stood and whether it has hardened, then the siege damage: how many blocks have been hit and how many hits they have taken in total, which together say whether a raid is a scratch or about to break through - the argument is there so staff can audit a siege from the console. /beacon border [on|off|status] shows the protected area (kept up with on), /beacon reinforcement says whether the block you are looking at is reinforced and for how long, /beacon timer how long the base has stood and whether it has hardened, /beacon list every base on the server with its faction, /beacon unregister deactivates one (leader only, and the damage counters under it go with it, with the faction told whether it was destroyed or switched off), and /beacon forceprotect [tag] arms a base now instead of at the end of its clock. The beacon is also registered by placing it, and breaking one counts as destroying the base.

/siegecharge (/charge)

permission: ppcore.beacons.admin · always · Beacons.java

The one thing that breaks reinforced obsidian, handed out by staff: /siegecharge <player> [amount] gives that player charges (the amount defaults to one, and is clamped to what a stack can hold). The player is told they received them and what to use them for, and if somebody else ran the command they are told who gave how many. Nothing else in this module - and nothing a player can type - removes a reinforced block, which is why this is a staff verb rather than an item anybody can buy.

/reinforce

permission: ppcore.beacons.admin · always · Beacons.java

Clears the siege damage a base has taken: /reinforce puts every tracked block back to undamaged and says how many blocks it covered. It deliberately does not restart the hardening clock - one staff command that disarmed every base on the server for the length of that window would be a surprise nobody asked for - so a base that wants a fresh clock is unregistered and registered again, and beacons.protection.delayedSeconds: 0 is the switch that turns the hardening window off everywhere at once. /beacon info is where you look to see which bases were affected.

/obtoggle (/obstoggle, /obsidiantoggle)

permission: ppcore.beacons.obtoggle · always · Beacons.java

Your own switch for instant obsidian mining, kept per player because the same person wants it on while building and off while mining: /obtoggle flips it, and /obtoggle on|off says which way to go. Two rules apply, and both of them come from the plugin this module ports: the server decides whether the feature exists at all (beacons.instantBreak.enabled, and its answer is what a player who has never typed the command gets), and your choice is remembered in beacons.yml rather than lost at the next restart. Inside a faction's beacon range it still will not break a reinforced block - the command says so when it goes on, because reading this switch as "I can mine a base now" is the one misunderstanding it could cause.

Permissions. /beacon and /obtoggle have none of their own (registering is gated by faction rank, inside the command). /siegecharge and /reinforce need ppcore.beacons.admin.

spawners - spawner stacking, egg changing, levels, heads

What it does. One spawner that behaves like sixty-four of them. A stack is a count stored on the block, so a stack of 64 costs the server nothing extra and breaking it gives every spawner back (features.returnStackOnBreak). Right-click with a matching spawner to add to a stack, with a spawn egg to change the creature, and with a booster item to boost it. /spawner shop sells creatures for in-game money (and features.claimOnFirstUse makes touching an ordinary world spawner claim it, off by default). /sellheads turns the heads a spawner's mobs drop into money, and /sellsign is the sign half of the same idea.

Levels raise what a kill drops, never how fast mobs spawn - cranking spawn rates is how spawner plugins melt a server, so the ladder gives the same sense of progress for a fixed number of entities. Twenty-five levels, each one either unlocking a spawner tier (a creature that can then be bought or made with an egg) or granting a perk, cumulative: headsChance, stackBonus and shopDiscount. The ladder is also a gate elsewhere in the file - ench.levelGate locks Mending until level 25 - so the top of this table and the gates around it are meant to be the same ceiling.

The rest of the module is the farm machinery an operator actually needs: /spawner axes and /spawner upgrade <axis> buy levels of range, delay, amount, nearby (the entity cap) and xp or drops, each with its own ceiling and price, and axes.entities.<TYPE> changes one axis for one creature. Charges and the lifetime count (/spawner charges, and the item itself) make a bought spawner a purchase rather than a machine. Crowbars (/spawner crowbar) are the tool that breaks a spawner and hands it back, priced in whatever currency you like. Traits change what a spawned mob becomes (/spawner metadata <name>). /spawner locations, /spawner drops, /spawner heads, /spawner info, /spawner bar, /spawner trust, /spawner grant, /spawner give and /spawner remove are the read-outs and the admin verbs, and the spawn shape - vanilla SINGLE, SPREAD (a wider square) or ABOVE (the mob appears above the block, for a farm the player stands on) - is per creature.

Turning it on. modules.spawners.enabled: true, on in the shipped file and in every factions-shaped profile; the feature switches are stacking, eggs, levels, heads, shop, boosters, returnStackOnBreak and claimOnFirstUse. /spawner check is the report to run after an edit, and panel: true offers the spawner editor in the PlayPort panel.

What you can configure. The section is large because the source plugins it answers are large. The keys an owner reaches for first: spawners.stack.max (64), spawners.anyoneMayStack (false), spawners.linkRadius (8), spawners.locationsLimit (20), spawners.shop.<TYPE> prices (delete a line to hide it from the shop), spawners.charges.enabled and destroy-on-empty, spawners.spawnable.enabled, spawners.heads.chance (0.25), spawners.heads.drop-chance.<TYPE>, spawners.heads.price.<TYPE> and spawners.heads.xp.<TYPE>, and spawners.levels.max (25) with levels.xp.<n>, levels.dropMultiplier.<n>, levels.grants.<n>.unlock and .perk, plus levels.mobs-are (which creature /level counts) and levels.hud.{xp-bar,bossbar,hide-when-full}.

Deeper, and there when you need it: the price engine (spawners.prices) writes any price in one of four currencies - a raw number means whatever prices.keys.<key> names, and EXPERIENCE-500, LEVELS-10, ECONOMY-2500, MATERIAL:DIAMOND-5 and FRAGMENT-10 are the spellings; prices.constants defines named items; prices.format.abbreviations is how a big number is printed; and prices.settings.ignore-op-creative (true) lets ops and creative players pay nothing. spawners.tools.crowbars.<name> each carry material, glint, name, lore, usage (0 = unlimited), chance.value/chance.destroy, price, use-type and what they may touch (ignore/allow with owned, natural, trusted, other). spawners.traits.<name> gives applicable creatures and a list of transform/attribute steps (with chance and limit, so a set can be proven not to fire). spawners.spawning.<TYPE> is the shape, spawners.axes is the ladder, spawners.interval.{ticking, checking,validation} are the three clocks (40/20/600 ticks), spawners.general holds disable-item-spawners, reduce-nearby-entities, delay-offset (25% jitter) and per-entity particles, spawners.nearby shapes what "nearby" counts, spawners.requirements.<TYPE> is the one part that ships as syntax and an example rather than a rule nobody asked for (nothing is enforced until an entity is named there), and spawners.redstone-power.<TYPE> lets a lever control a farm where required is a signal threshold and disable-with-power ignores it - setting both is a line that reads as two rules and runs as one, and /spawner check says so.

Commands. /spawner (aliases /spawners, /sl), plus /level (/spawnerlevel, /sllevel), /spawnerlevels, /spawnerrankup (/rankupspawner, /slrankup), /sellheads, /sellsign.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/spawners.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help spawners prints the same names and the same nodes, line for line, because both read this one registration.

/spawner (/spawners, /sl)

permission: none of its own · always · Spawners.java

The spawner, its owner and its axes. On its own it prints the help page, which is the honest index of what is here, and the arguments are: shop [page] to buy a spawner with money, trust <player> and untrust <player> (with trusted to list who is trusted - a trusted player may use your spawners), locations to list yours, drops [type] for what a spawner produces, axes for the six axes of the spawner you are looking at and what the next level of each costs, upgrade <axis> [levels] to buy one of those levels, info to read the block back - what the spawner itself says rather than what the plugin assumes - charges <n> [spawnable] for how many spawns are left or -1 for unlimited, crowbar <name> [player] for a tool that breaks a spawner and hands it back whole, metadata <name> for which trait set the spawner uses, item <constant> [player] [amount] to hand out a defined item, and the booster verbs: booster [all] lists the live boosters and the types this server runs, booster give <player> <type> [amount] hands one out, booster remove takes the booster off the spawner you are looking at. level, rankup, levels and grant mirror the four level commands below so they can also be typed as arguments of this one, and give and reload are the staff verbs. Players are also told the rule the block itself follows: right-click a spawner with a matching one to stack it, or with a spawn egg to change what it spawns.

/spawnerlevel (/sllevel) (config key level)

permission: none of its own · only while modules.spawners.features.levels is on · Spawners.java

Your spawner level and how far the progress towards the next one has come. /spawnerlevel reads back your own; /spawnerlevel <player> reads somebody else's, which is how staff answer "why can this person not upgrade". The progress it shows is the same number /spawner bar puts on screen and the same one /rankupspawner spends, so the three cannot disagree.

/spawnerlevels (/slladder, /spawnerladder)

permission: none of its own · only while modules.spawners.features.levels is on · Spawners.java

The whole ladder: what every level grants, in one listing, so a player can see what the next few levels are worth before spending progress on one. /spawnerlevels [level] narrows it to one rung. It is a command of its own rather than an argument on the level line because the board is what people read before deciding, and a board buried in a subcommand is one nobody finds.

/rankupspawner (/slrankup, /spawnerrank) (config key spawnerrankup)

permission: none of its own · only while modules.spawners.features.levels is on · Spawners.java

Spend your accumulated progress on the next level: /rankupspawner for yourself, and /rankupspawner <player> - which needs the admin node - for somebody else, the same way /spawner grant hands out levels outright. It is a distinct config id from the ranks module's own rankup on purpose, so renaming one does not rename the other; what it grants is a spawner level and nothing to do with a rank.

/sellheads (/sellhead)

permission: none of its own · only while modules.spawners.features.heads is on · Spawners.java

Turn the mob heads in your inventory into money, at whatever this server pays for each one. /sellheads sells your own; /sellheads <player> sells somebody else's, which needs the permission to act on another player. Heads are worth something here because the head half of this module is what drops them, and the same command backs the sign below.

/sellsign (/ssign)

permission: none of its own · only while modules.spawners.features.heads is on · Spawners.java

Make a sign that sells heads for you: /sellsign marks the sign you are looking at within six blocks, and right-clicking that sign afterwards sells the heads of whoever clicked it - the shop counter for a player who does not want to type the command. The sign keeps working until it is broken, and the template and prices are the ones the head half of this module reads from this module's own config.

Permissions. All of them are registered with no node of their own: what a player may do is decided inside the command by their level, their faction rank and whether the spawner is theirs.

shop - buy and sell shop with a GUI

What it does. A chest-GUI shop over a price table you own. /shop opens the categories; a click buys, a shift-click sells all of the item you are holding, a middle click opens the amount picker, and the Sell everything window sells a whole inventory in one confirm - deliberately not gated, because a screen costs clicks, so the slow path is free and the fast path is what has to be earned. /sellall is the earned path: its capability comes from a quest (master_seller in the shipped quest library), bypassable by staff with ppcore.shop.sellall.bypass, and it stands down on its own when the quests module is off. /sellwand is the third way: fast, finite (the uses live in the item, so a part-used wand stays part-used wherever it goes) and buyable at wand.price. /price [item|all] is the market look-up, /worthlist prints the whole table, /setworth and /setenchantworth change prices without leaving the game (kept in shop-data.yml; /setworth <item> reset puts the file's own value back), /generateworth prices everything the recipes make, and /scavenge takes an enchanted item apart for experience bottles and its own ingredients - with the four chances printed before anything is spent, so the gamble is an informed one.

Prices move with supply when dynamic.enabled is on: every unit the shop sells pushes the price up, every unit it buys pushes it down, and elasticity, min-multiplier and max-multiplier are the rails it can never leave. One item opts out with dynamic: false on its row, or starts with a stock.

Turning it on. modules.shop.enabled: true (on in the shipped file, and in the Factions, Raidsteal and Survival profiles), with the feature switches sellAll, price, amount, bulk, wand, worth, enchantWorth, generateWorth and scavenge. The catalogue is shop.categories plus shop.items, and the shipped file carries about five hundred listings; nothing needs to exist first.

What you can configure. shop.title, shop.rows (1-6), shop.priceListMax (30 - /price all is a sorted leaderboard, not a dump), shop.sellMultiplier (1.0, identical for every player), shop.buyMoreAmount (16, what a shift-click buys for a player without ppcore.shop.buymore), the four placeholders that stand where a price would be (buyPriceForUnsellablePlaceholder, and the two hide*PriceFor* switches that make those unreachable), the picker switches (enableBuyGUI, enableSellGUI, enableSellGUISellAll, enableSellAll, allowAllSellAllStackSizes, quickBuySell, returnToShop, closeGuiAfterSellAll, enableAmountSelectionFix, and itemStackSizeCappedAt for the per-item ceiling inside the picker), shop.amount (the window's own size, item-slot, fill and step buttons, including the two bulk shapes), shop.bulk.title, shop.dynamic, shop.scavenge.* (cost, exp-price, the four chances and ingredient-chance), shop.sellall.require-capability and .bypass-permission, shop.wand.* (material, uses, name, lore, price, capability), shop.categories.<id>, and shop.items.<ITEM> rows with category, buy, sell, name, lore, icon, permission, command, dynamic and stock.

Commands. /shop (aliases /shopgui, /gui, /store, /ecoshop), /sellall, /price, /worthlist, /setworth, /setenchantworth, /generateworth, /scavenge, /sellwand.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/shop.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help shop prints the same names and the same nodes, line for line, because both read this one registration.

/shop (/shopgui, /gui, /store, /ecoshop)

permission: none of its own · always · Shops.java

The shop window. /shop opens the shelf of categories and /shop <category> opens one of them directly; a category nobody defined is refused by name, and a server whose shop has no rows left is told that rather than shown an empty window. Where the shop refuses to open at all - a world listed in disableShopsInWorlds, or a game mode in disableShopsInGamemodes - the refusal names what did it, because "the shop is broken in here" is not what happened. Staff have /shop sudo <player> [shelf], which opens the shop for somebody else and is how you answer "what does this player see"; it checks that the player would be shown that shelf (shop.sudo.skip-shop-check turns the check off), and shop.sudo.all-shops builds the window with the locks ignored so a view that cannot trade is still a view.

/sellall (/sellhand, /sella)

permission: ppcore.shop.sell · only while modules.shop.features.sellAll is on · Shops.java

Sell everything sellable in one command: /sellall walks your inventory and pays the shop's price for each thing it buys, through the same walker the bulk window uses, so the command and the window cannot pay different amounts for the same pack. /sellall <player> does it for somebody else and needs ppcore.shop.others. It is the earned fast path: with the quests module running, shop.sellall.require-capability names a capability a quest hands out, and a player who does not have it is offered the shop's own Sell everything window instead, which pays exactly the same. The answer says how many items went and what they fetched, and when part of the total was the enchantments it says so - a total bigger than the price list suggests is otherwise unexplainable.

/price (/prices)

permission: none of its own · only while modules.shop.features.price is on · Shops.java

What the shop pays, without walking to it. /price reads the item in your hand, /price <item> names one, and /price all prints the whole list - the last is behind its own permission because a price list the whole server can read is a market, and an owner may not want one. A custom item is asked about as itself rather than as the plain item it is built from, so the answer about a named sword and the answer about the material it is made of are two different answers.

/worthlist (/pricelist)

permission: ppcore.shop.admin · only while modules.shop.features.worth is on · Shops.java

Every item this shop trades with its live numbers: what it pays, whether it buys or sells it at all, and whether the price is the file's or somebody's - a row changed by /setworth is marked as changed and carries the file's own number beside it. /worthlist <filter> narrows it to items whose material name contains the filter; the listing stops after thirty rows and says how many it did not show. This is the command an operator stands in front of when they ask "what does this server pay for stone?", and the last line of the answer names the way to change one.

/setworth (/setprice)

permission: ppcore.shop.admin · only while modules.shop.features.worth is on · Shops.java

Change what the shop pays: /setworth <item> <sell> [buy] sets the sell price, and the optional buy price, for one item; /setworth <item> reset puts the file's price back. A name that is not an item and an item this shop does not trade are refused differently on purpose - a typo should not read like a missing feature - and the change is remembered in the module's own file, marked in /worthlist, and logged. Nothing else about the shop changes: quantities and the amount window are the shop's own settings, not a price.

/setenchantworth (/setenchprice)

permission: ppcore.shop.admin · only while modules.shop.features.enchantWorth is on · Shops.java

What an enchantment adds to an item's price, which is what makes an enchanted tool worth more to this shop than the plain one. /setenchantworth with no arguments lists what enchantments are priced, with a price per level where one exists, and /setenchantworth <enchant> <price> sets a price for every level of one enchantment. The number the shop adds is this table's plus the per-level rows, so an owner who wants levels to differ sets both, and the effect is visible the next time an enchanted item is sold or quoted by /price.

/generateworth (/genworth)

permission: ppcore.shop.admin · only while modules.shop.features.generateWorth is on · Shops.java

Price what the recipes make: /generateworth derives a price for the items this server's recipes produce, so a server with a large crafting tree does not have to price hundreds of rows by hand. It writes only rows it generated - anything you set yourself is left alone - and /generateworth remove (or clear) throws the generated rows away and puts the file's prices back, saying how many went. The run is logged, because a price list that changed under everybody's feet is worth a line in the console.

/scavenge (/salvage)

permission: ppcore.shop.scavenge · only while modules.shop.features.scavenge is on · Shops.java

Break the enchantments off what you are holding: /scavenge on the item in your main hand, with /scavenge confirm where the server asks for the word. It is the reverse of an anvil - the enchantments come off as what they are worth rather than onto another item - and it refuses an empty hand and an item that has nothing on it, each in its own sentence. What it costs, and what it can fail to do to the item, is the shop's own shop.scavenge settings.

/sellwand (/wand)

permission: none of its own · always · Shops.java

A wand that sells what you use it on, which is the third way to sell beside the window and /sellall: /sellwand buy buys one for yourself, and /sellwand give <player> [uses] hands one to somebody else with a number of uses. The uses are what makes it finite - the menu is free and slow, the command is free and earned, and the wand is paid for and runs out - and a wand with none left is a plain stick until it is thrown away.

Permissions. /shop, /price and /sellwand have none of their own; /sellall needs ppcore.shop.sell, /scavenge needs ppcore.shop.scavenge, and the four price verbs need ppcore.shop.admin.

perf - tps, mspt, hotspots, lag cleanup, restart scheduler

What it does. The performance screen and the verbs a staff member reaches for when the server is struggling. /perf prints the dashboard (tps, mspt, memory, entities, chunks, players); /perf hotspots lists the loaded chunks with the most entities; /perf clean sweeps, /perf freeze and /perf thaw stop and resume the sampler's own reporting, and /perf exempt <chunk> keeps a chunk out of the redstone limiter. /perf restart [minutes] schedules a restart by running restartCommand (the default is Paper's restart), and /perf telemetry is the opt-in half: with a clock set, the same snapshot goes to your endpoint through the service module - there is no third-party collector and no default endpoint. The module also does work behind the scenes: it samples once a second, deep-scans entities and chunks every deepScanSeconds, throttles runaway redstone per chunk, alerts staff when the tick rate drops, and writes perf.json for the panel to read.

The source plugins' own names are registered in their own right, because an owner migrating types the verb they know: /lfix (clear dropped items and the mobs over the per-chunk cap), /groundclean (the drops-only half), /unloadchunks, /fixchunk and /viewdistance.

Turning it on. modules.perf.enabled: true (on in the shipped file), with the feature switches redstoneLimiter, alerts, explain (suggest a cause when the tick rate is low) and panelSnapshot. Nothing needs to exist first; it is also on in the Creative profile, which is where a plot world with a thousand builds turns out to need entity and tile-entity limits.

What you can configure. perf.window (60 samples), perf.deepScanSeconds (10), perf.title, perf.restartCommand (restart), perf.redstone.limitPerChunkPerTick (240) and perf.redstone.exempt, perf.clean.mobsPerChunk (30 - named mobs are always kept, because one of those is somebody's pet), perf.chunks.keep-chunks (8 - the number that decides whether /unloadchunks helps or hurts), perf.view.min/max (2/32), perf.alerts.tpsBelow (15.0), perf.alerts.msptAbove (45.0) and perf.alerts.afterSeconds (10), perf.telemetry.every-minutes (0 = never on a clock), and perf.messages.* for the alert, the throttled-chunk line and the three restart lines.

Commands. /perf (aliases /psuite, /performance, /lagmonitor) with status, hotspots (hotspot/lag), clean (purge), freeze, thaw, exempt, restart, alerts, reload, telemetry; plus /lfix, /groundclean, /unloadchunks, /fixchunk and /viewdistance as commands of their own.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/perf.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help perf prints the same names and the same nodes, line for line, because both read this one registration.

/perf (/psuite, /performance, /lagmonitor)

permission: none of its own · always · Perf.java

The performance dashboard, and the console's own view of the server. /perf opens the screen in game; /perf status prints the same reading as text, which is the form an operator can run from the console or read in a log: tick rate over the last minute, five minutes and fifteen minutes with the worst of the five-minute window, tick time in milliseconds where the server reports it, the entity and chunk counts, the number of players, how many chunks are frozen and how many exempt with the redstone limit in force, and a countdown when a restart is armed. When the minute is below 18 ticks it also says what is slow, in plain words. The rest are subcommands: hotspots for what is costing the time, clean|purge for the sweep lfix runs, freeze and thaw for redstone in a chunk, exempt for chunks that are never frozen, restart <minutes|cancel|now> to arm, cancel or take a restart now, telemetry [send] for the opt-in report to the owner's own endpoint, alerts for a player's own toggle (be told when the server starts to struggle) and reload for the settings. Nothing here is installed, licensed or sent to a third party: telemetry posts the snapshot the panel already reads to a URL the operator set, and is off until it is set.

/lfix (/lagfix)

permission: ppcore.perf.clean · always · Perf.java

The cleanup sweep: dropped items and the mobs over the per-chunk cap, in one command, which is what a server does when the tick rate drops and the floor is covered in loot. It is the same sweep /perf clean runs, as a verb you can type without going into the dashboard.

/groundclean (/cleanupdrops, /cleardrops)

permission: ppcore.perf.clean · always · Perf.java

Clear only the items lying on the ground. It is /lfix minus the mobs, which is the right command when the problem is a farm's overflow rather than a crowd of entities - and the one that does not remove a single living thing, so it can be run while people are fighting or playing.

/unloadchunks (/chunkunload)

permission: ppcore.perf.chunks · always · Perf.java

Unload the chunks nobody is near: /unloadchunks uses perf.chunks.keep-chunks as the radius kept around every player, and /unloadchunks <keep-chunks> names one for this run (clamped to 0-64). The unload is the server's safe mode, so a chunk something still needs - an entity on the move, a redstone tick, a player riding through - is left alone and the count is what actually came free rather than what was attempted. The answer says how many of how many went, and the run is logged.

/fixchunk (/resendchunk)

permission: ppcore.perf.chunks · always · Perf.java

Resend the chunk a player is standing in: /fixchunk for yourself and /fixchunk <player> for somebody else, which needs the node. This is what you do for a client that shows a hole in the world that the server knows is not there, and the answer says whether the server agreed to resend it.

/viewdistance (/viewdist)

permission: ppcore.perf.view · always · Perf.java

How far a player can see, in chunks: /viewdistance reads the number back for you and /viewdistance <chunks> [player] sets it, for you or for somebody named. The number is clamped to the band perf.view.min to perf.view.max (2 to 32), and when the clamp bites the reply says which number was used and why - a client is sent chunks it cannot render at one end of that band and loses the world at the other, so the number is the server's decision rather than a preference. The player is told their own new distance, and staff are told the old one as well.

Permissions. /perf has none of its own; /lfix and /groundclean need ppcore.perf.clean, /unloadchunks and /fixchunk need ppcore.perf.chunks, and /viewdistance needs ppcore.perf.view.

items - custom items and short codes for symbols

What it does. Named, described, enchanted, glinting items defined in config instead of in code: a vote key in a crate, a menu icon, an event prize, a sword a boss drops. Each item carries its id in a persistent data tag, so a crate or a menu recognises it even after a player renames it, and an item may carry custom model data (a pack you already have then renders it correctly), vanilla enchantments, the core's own custom enchantments, a potion effect while it is worn or in hand (never from the backpack, and the effect ends by itself when the item comes off), and its own crafting recipe - shape plus ingredients, where item:<id> means that exact item rather than any item of that material. A token that is both a material name and one of your item ids is refused rather than guessed at.

The other half is the verbs: /condense and /uncondense trade nine loose items for their block and back (convert is the map, and it is always 9:1), /dye, /trim, /itemframe, /book, /openbook and /getbook are the item-in-hand editors, /mirror builds across an axis through your own column, and the glyphs are the short codes that let chat and lore carry symbols an ordinary keyboard cannot type (:star: or the bare code - both are accepted, because people type it either way).

Two operator pieces are worth knowing. /customitem import <folder> reads a Nexo or ItemsAdder catalogue and turns it into this section's own vocabulary, reporting every number it translated and everything it had no answer for; customitems.import.auto with customitems.import.folder does it once, on the first boot that finds no items-imported.yml - and because that file is what loads afterwards, a later change to the source tree cannot silently rewrite your items. And a library of 119 pieces of gear ships in the jar, switched by pack (blades, tools, armour, archery, fishing, starter, event, then nether, elemental, boss and specialists, off by default); only absent ids are written, so an item you define yourself is yours, whole, and a jar upgrade never overwrites it.

update.on-reload (off by default) makes a reload reach copies that have already been handed out - the definition wins, the amount and the wear stay the stack's (an updater that repaired a player's gear would be a duplicator with extra steps), and a copy whose definition is gone is left alone and counted. update.reach.* decides which surfaces a run walks: ender chests, the window a player has open, nested shulker boxes and bundles to eight deep, and containers standing in the world near a player.

Turning it on. modules.items.enabled: true (the shipped file says customitems: for the section name but the module id is items, and the panel and config.yml both call it that), with the feature switches glyphs, verbs, mirror and recipes. Nothing needs to exist first - the shipped file already defines four items (vote_key, crate_token, frostblade, event_medal).

What you can configure. customitems.import.{auto,folder}, customitems.library.enabled and library.packs.*, customitems.model-data.skipped.<n> (reserve a model number before the allocator hands it to something else, with the reason printed back to the next person who asks for it), customitems.update.{on-reload,reach.*} (enderchest, window, nested, placed, placed-radius 64, placed-limit 512), customitems.convert.<ITEM> pairs, customitems.glyphs.<:code:>, customitems.effects.{enabled,interval} (40 ticks between refreshes, each application lasting twice that, so a lagging tick does not drop the buff), and customitems.items.<id> definitions.

Commands. /customitem (aliases /citem, /ccitem) with give, list, reload, check, recipe, update and import; /saveditems (alias /itemlist); and, with the verbs switch on, /item, /condense, /uncondense, /dye, /trim, /itemframe, /book, /openbook, /getbook and /mirror. /customstat (/cstat) and /usermeta (/playermeta, /pmeta) read and write a named number on a player - the store a ui page reads with a {...} - and /contentadmin (/contentconfig, /cadmin) deletes or disables a content definition reversibly, without rewriting config.yml.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/items.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help items prints the same names and the same nodes, line for line, because both read this one registration.

/customitem (/citem, /ccitem)

permission: ppcore.items.admin · always · Items.java

The custom items themselves. On its own, or with list, it names every item this server defines; with give <player> <id> [amount] it hands one out, drop <id> [amount] [x y z] drops one at you or at a named spot, and remove <player> <id> [amount] takes one back. recipe <id> prints the recipe that makes an item. reload re-reads the definitions, the glyphs and the recipes and says how many of each it found, and with customitems.update.on-reload it also walks what has already been handed out - update [player|all] does the same by hand, reaching every online player and reporting three numbers rather than two: how many items changed, how many were already current, and how many belong to a definition that no longer exists, which are deliberately left alone instead of being deleted with it.

/saveditems (/itemlist)

permission: ppcore.items.admin · always · Items.java

Every custom item this server has defined, under the source plugin's own name for the listing. It answers with the same list /customitem does; it is a separate name because an operator arriving from another plugin types it from muscle memory, and the plugin's own vocabulary prints the same answer.

/item (/giveitem)

permission: ppcore.items.give · only while modules.items.features.verbs is on · Items.java

Give a custom item by its id, or a plain one by material name: /item <id|material> [amount] [player]. A named player must be online, the amount defaults to what the definition says for a custom item and to one for a plain one, and an id that is neither a definition nor a material this server has is refused by name so a typo never quietly hands out something else.

/condense (/compress)

permission: ppcore.items.convert · only while modules.items.features.verbs is on · Items.java

Turn what is in your inventory into its block form - the packed version of what you are carrying, so a pack stuffed with loose ingots becomes a stack of blocks. The conversions are the server's own packing table, and anything that has no packed form is left where it is.

/uncondense (/decompress, /uncompress)

permission: ppcore.items.convert · only while modules.items.features.verbs is on · Items.java

The other direction: turn the packed blocks in your inventory back into their loose items, which is how a full block of blocks becomes buildable material again. It is one feature split into two verbs on purpose - packing and unpacking are done at different moments and by different people - and both read the same conversion table.

/dye (/colour)

permission: ppcore.items.dye · only while modules.items.features.verbs is on · Items.java

Dye the leather armour you are holding, by hex colour (#rrggbb), by three numbers (r,g,b) or by the name of a dye's own colour, with off to take the colour back to the plain leather. Add all to dye every leather piece you are wearing rather than the one in your hand, and the confirmation says what the colour was set to. This is a colour, not a rename or a trim: what the item is does not change, only what it looks like.

/trim (/armourtrim, /armortrim)

permission: ppcore.items.trim · only while modules.items.features.verbs is on · Items.java

Apply an armour trim to the piece in your hand (or to every piece with all), or strip one with /trim off. The pattern and the material are spelled the way the game spells them, and a pair this server does not have is refused rather than silently accepted. It is the same decision the smithing table makes, available as a command for a staff member setting up a display or fixing a wrong trim.

/itemframe (/frame)

permission: ppcore.items.frame · only while modules.items.features.verbs is on · Items.java

Edit the item frame you are looking at, without breaking and replacing it: /itemframe rotate [steps] turns what is in the frame, which is how a display is angled exactly. The frame has to be the one your crosshair is on, and the changes are the frame's own - nothing is taken from or added to your inventory.

/book (/writebook)

permission: ppcore.items.book · only while modules.items.features.verbs is on · Items.java

Write a book a page at a time. /book <title> starts one with that title, /book page <text> adds a page, /book save <name> puts the finished book into this server's library so it can be handed out again, and /book list names what the library holds. It writes into your own hand as it goes, so the book you are carrying is the one being built, and a page added to a book that is not the one you started is refused rather than appended to whatever you happen to be holding.

/openbook (/readbook)

permission: none of its own · only while modules.items.features.verbs is on · Items.java

Open the book you are holding, for reading. It asks for no permission of its own, because reading a book you are carrying is nobody's decision but yours, and it is what a quest reward or a dialog points at when it hands somebody a written book and wants it seen immediately.

/getbook (/booklib)

permission: ppcore.items.book · only while modules.items.features.verbs is on · Items.java

Take a book out of the server's library: /getbook <name> [player], where /book list names what is available. Naming somebody else gives it to them instead, which is how a written welcome book or a rules handout reaches a player without staff standing next to them.

/customstat (/cstat)

permission: ppcore.items.customstat · always · Items.java

A named number kept against a player: /customstat <read|write|increment|decrement> <player> <attribute> [value]. It is the storage a quest, a scoreboard or another module reads and writes, and being one command rather than a different verb per stat is what keeps the reading of a stat and the writing of it in one place. An attribute that has never been written reads as absent rather than as zero, so "not measured yet" and "measured as zero" stay different answers.

/usermeta (/playermeta, /pmeta)

permission: ppcore.items.usermeta · always · Items.java

A named piece of text, or a counter, kept against a player: /usermeta <player> <add|remove|clear|list|increment> [key] [value]. It is the free-form half of the same storage - a flag, a tag, a note - and list prints everything a player carries, which is also how staff find the key they meant to write.

/contentadmin (/contentconfig, /cadmin)

permission: ppcore.items.content · always · Items.java

Turn a content definition off, or back on, without deleting the file it lives in: /contentadmin <item|namespace|recipes> <delete|disable|restore|enable> <name> [confirm], with /contentadmin on its own listing what the server currently has disabled. Deleting here is reversible by design - the definition is disabled rather than removed, so a server can take an item out of play and put it back later - and confirm is the word that writes. It asks for its own node, separate from the one that hands content out.

/mirror (/mirrorbuild)

permission: ppcore.items.mirror · only while modules.items.features.mirror is on · Items.java

Mirror what you build across an axis: /mirror <x|y|z|off> picks the axis, off stops it. While it is on, a block you place appears mirrored on the other side of the axis, which is how a symmetric build is done once rather than twice; the axis is per player and is not remembered across a restart, so a mirror nobody switched off does not surprise anyone the next morning.

Permissions. /customitem and /saveditems need ppcore.items.admin; the verbs have their own nodes (ppcore.items.give, .convert, .dye, .trim, .frame, .book, .mirror) because a builder allowed to condense their own inventory is not thereby allowed to hand out custom items; /openbook has none (reading the book you are holding is your own book), /customstat needs .customstat, /usermeta needs .usermeta, and /contentadmin needs ppcore.items.content.

customench - custom enchantments

What it does. Enchantments defined in config and applied by listeners rather than by the vanilla registry, so they need no resource pack and no client mod. Values are per level: damage: 1.5 at level three is 4.5 extra damage. The effect vocabulary is the source plugins' own: damage, damageMultiplier (a share of the hit, capped at 5x), lifesteal, protect, thorns, poison, wither, burn, slow, weak, feed, speed, haste, regen, jump, smelt, autopickup, luck, xp, veinmine (capped by veinmineMax), lightning, tnt (never breaks blocks), particle and sound. Four of them take an argument in the key, because the name is part of what the enchantment does: sound:ENTITY_BAT_TAKEOFF, potion:BLINDNESS, selfpotion:SPEED and wait (hold everything after this row for the value in ticks, max 400). A sound or potion this server does not know is logged and skipped rather than silently doing something else.

/ce list is what a player reads, /ce apply and /ce give are the grants, /ce book hands a book over, /ce check names a definition that would load cleanly and do nothing, and /enchantinfo <id> (EcoEnchants' own verb, with its own chest window) prints everything the core knows about one enchantment - including the four flags that decide where it may appear: enchantable (can it be applied at all), discoverable (is it offered by the table's roll, a mob drop or /ce list), tradeable (may a book carrying it leave the player) and conflicts (the ids it refuses to share an item with). /withdrawsouls turns the souls an item has collected into a soul gem.

Turning it on. modules.customench.enabled: true (on in the shipped file, and in the Factions and Raidsteal profiles), with every effect having its own switch under modules.customench.features - that is what lets a server keep the combat effects and drop the mining ones, or hand the whole job to a dedicated enchantment plugin. The shipped customench.enchantments table already defines venom, lifedrain, guardian, spikes and frostbite, so there is something to find on a fresh server.

What you can configure. customench.panel (offer the editor in the panel), customench.tableChance (0.35 - the chance the enchanting table grants a custom enchant as well), customench.mobDropChance (0.02 - a killed mob dropping a book), customench.veinmineMax (64), customench.lightningDamage (5.0), customench.tntPower (2.0), customench.particle and particleMax (40), customench.loreFormat ({colour}{name} {level}), customench.veinmineBlocks (empty = logs, wood, stems, ores, ancient debris, obsidian), customench.smelt (extra BLOCK=RESULT pairs), customench.debugEffects (logs every applied on-hit effect with the enchantment that caused it), and customench.enchantinfo.{rows,mask,item} - the read-only window /enchantinfo opens. Each enchantment in customench.enchantments.<id> carries name, colour, max, rarity, items, the four flags above and its effects map.

Commands. /ce (aliases /customench, /customenchants), /enchantinfo (/ceinfo), /withdrawsouls (/wsouls).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/customench.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help customench prints the same names and the same nodes, line for line, because both read this one registration.

/ce (/customench, /customenchants)

permission: ppcore.ce.use · always · CusEnch.java

The custom enchantments. /ce lists them fifteen to a page - the shipped library is hundreds of rows, and a single answer with all of them is a wall of chat that a client may stop showing altogether - and /ce list <page> walks the pages, with the name, the id, the maximum level, the rarity and the effects of each. An enchantment a pack marks hidden (discoverable: false) is not in a player's listing but is in staff's, marked as hidden, because somebody has to be able to find out why nothing is dropping. /ce info <id> is one enchantment in full: what it goes on, its rarity and maximum level, and what each of its effects is worth per level. /ce apply applies the enchantment in your off hand to the item in your main hand - the same path the right-click takes, plugin items first and then the book, so the command and the click cannot disagree about what was spent.

/ceinfo (/enchantinfo, /ceinfo)

permission: ppcore.ce.info · always · CusEnch.java

Everything the core knows about one custom enchantment, under the source plugin's own name for the verb. It is the same reading as /ce info <id> as a command of its own, because an operator moving off that plugin types this one, and the module answers under both names rather than asking anybody's fingers to change.

/wsouls (/withdrawsouls, /wsouls)

permission: ppcore.ce.use · always · CusEnch.java

Turn the souls an item has collected into a soul gem - the source plugin's own verb, under its own name. A soul-collecting enchantment writes its count onto the item, and this is what takes that count out as something a player can hold and trade, which is the point of the mechanic rather than a side effect of it.

Permissions. /ce and /withdrawsouls need ppcore.ce.use; /enchantinfo needs ppcore.ce.info.

models - custom models, model data and pack building

What it does. Blockbench models in the world. A definition points at a .bbmodel in the plugin's folder and gets a material, a name, a model id (auto makes the allocator hand one out, skipping every number already taken) and a display - item, block or head. A poseable model is rigged rather than placed: one display per cube, posed by the animation a skill asks for, and the animation names in it are the ones a boss's skills can request. A model with a seat can be sat on (a bench wants about 0.45, a throne more; right-click to sit, sneak to stand). A connectable model draws a different model when a neighbour stands within range, which is how a run of pipe builds itself as it is placed. /model carries the verbs - build, list, give, place, rig, anim, remove, pack - and /disguise (/undisguise) points the same rig machinery at a player.

The pack is the module's other half: it compiles the models into a resource pack, and with pack.serve on it hands the zip to joining clients itself (the plugin's own tiny HTTP source, on the bind and port you name; url is what the client is actually handed, and {port} is filled in). The pack is shipped optional on purpose - a client that declines still gets every server-decided behaviour, and only a model's drawing wants the pack. /pack info prints the URL a client was given and /pack reload asks a client to take the new bytes, which is the only way a running server can make a connected client reload a pack.

Turning it on. modules.models.enabled: true (the shipped file has it on, and the Creative profile keeps it on because a build world is where models are actually shown off), with the feature switch pack. Nothing needs to exist first - /model list is empty until you define one - and pack.serve is off by default, so a server that never asked behaves exactly as it did before the switch existed.

What you can configure. models.namespace (used in the pack's asset paths), models.packName (shown on the client's prompt), models.firstModelId (10000), models.packFormat (0 = work it out from the server version), models.into (merge into an existing pack folder), models.connect.radius (1.5) and .interval-ticks (10), and models.pack.*: serve, bind, port, url, at-join, required, the seasonal overlay (pack.seasons.enabled, .fixed, .tint.<season>, .files.<season>) and the vanilla-language override (pack.lang.enabled, pack.lang.overrides.<locale> - a JSON object, base64: of one, or file:<name>, so a server can rename a vanilla item or menu without a mod). models.items.<id> is where a model is defined, and model-data.skipped in the items module is where you reserve a number for a pack you already have.

Commands. /model (aliases /models, /custommodel, /cmodel, /meg), /pack (alias /playportpack), /disguise (/disguiseself) and /undisguise (/undisguiseself).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/models.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help models prints the same names and the same nodes, line for line, because both read this one registration.

/model (/models, /custommodel, /cmodel, /meg)

permission: ppcore.models.admin · always · Models.java

Custom models, and the pack that draws them. /model build compiles every model named under models.items - a .bbmodel file with its textures embedded, or a flat texture - into playport-models.zip in one go; build verbose names each model and how many cubes came out of it, so a model that rendered as something unexpected can be told apart from one that was never read. list prints every model with the model-data number it was given, whether it spins, what it connects to and its hitbox. give <player> <model> [amount] hands the item to somebody, place [model] puts a rotating 3D copy of it where you stand, remove takes the nearest copies away, and hitbox [radius] names and outlines the box a client actually targets for every placed copy nearby - read from the display itself, not computed from the model, so the diagnostic cannot disagree with the server. pack prints where the zip is with its SHA-1 and the two server.properties lines it goes in, and reload re-reads models.items without a restart. Model-data numbers are handed out once and never reused: a reused number would silently repaint every item already sitting in a player's chest. rig [model] [scale], anim [name] and rigdespawn are the posing half - a .bbmodel with bones and at least one animation can be spawned as a rig on an invisible stand and animated by name, which is also what a boss display is.

/pack (/playportpack)

permission: ppcore.models.admin · always · Models.java

The resource pack this server hands to connected clients, and the only place an operator changes it. info says whether the pack is built, the URL it is served at and the format the server wants; sha1 prints the hash on disk beside the one clients were handed, and the two disagree after a rebuild - which is exactly the line worth reading when players report seeing plain items. reload <player|all> asks one client, or everybody, to take the pack again (a connected client never re-downloads on its own), build rebuilds the zip and restarts the source, and season [spring|summer|fall|winter|auto] shows or forces the season the pack carries and offers the rebuilt pack to everyone online. The pack is built whether or not it is served: models.pack.serve is what turns this plugin into its source, models.pack.bind and models.pack.port decide where it listens, models.pack.url overrides the address a client is given (a proxy, a CDN or a hostname, with {port} filled in), and models.pack.required is whether a client may decline. The seasonal overlay needs models.pack.seasons.enabled on top of serving - on with nothing serving is a pack rebuilt for nobody. models.pack.lang.enabled with the locales under models.pack.lang.overrides replaces the client's own words.

/disguise (/disguiseself)

permission: ppcore.models.disguise · always · Models.java

Dress somebody - by default yourself - as a custom entity: /disguise <model> [player] [scale]. The model has to be poseable (a .bbmodel with bones and at least one animation), and it is mounted on the player's own body, so it moves and turns with them and the player's real body is hidden from every other client. Nothing about the player changes on the server's side: reach, collisions and combat still see the real player. That is what a disguise is - a change to what is drawn, not to who is there - and it is why a disguise is not a way to become somebody else. Disguising yourself is the module's user node; putting a model on another player is the admin one.

/undisguise (/undisguiseself)

permission: ppcore.models.disguise · always · Models.java

Take the disguise off: the model is despawned and the player is shown to every client again, straight away rather than at the next respawn. It also happens on its own - quitting, dying, and a reload or a shutdown all uncover the player - because a module that leaves somebody invisible after a crash is the one failure this pair exists to avoid. It needs a disguised player to have anything to do; with nobody disguised, there is nothing to take back and nothing is said.

Permissions. /model and /pack need ppcore.models.admin; the disguise pair needs ppcore.models.disguise, which is a user grant (the source plugin's own ia.user.disguise), so it belongs to the default group - targeting somebody else needs ppcore.models.admin.

hoppers - bought chunk hoppers that feed nearby chests

What it does. A hopper a player buys with money earned by playing: /chunkhopper opens the buy screen, the placed block picks up dropped items around itself and feeds them into the containers next to it, and sneak-looking at it prints what it has collected. Three things it deliberately will not do, because each turns a convenience into a support ticket: it never loads a chunk (it works where a player already is), it never deletes an item (if every container is full, the stack stays on the ground), and it never fills a chest inside somebody else's claim. One repeating task drives every hopper, scanning a few per cycle on a round-robin cursor, so a hundred hoppers cost a fraction of a hundred timers, and a hopper is protected from pistons because a moved hopper would keep working at its old spot.

/chunkhopper upgrade <links|range|speed> buys one step of a ladder and says the new number, so an upgrade that changes nothing is impossible by construction - a ceiling equal to the base is reported by /chunkhopper check rather than sold. links is containers one pass may fill, range is how far it looks for them, speed is how many stacks it moves per cycle.

The generator block is the producing half, in the same module on purpose because it is the same shape - a block, the containers beside it, and a record that survives a restart. /generator opens its shop; a placed generator fills the containers next to it with a chosen item on a clock, and the upgrade is the rate (tier-cycles is one number per tier: wait this many cycles between batches). A type may produce one item or draw from a weighted drops table, a generator can spoil (off by default, but in the shipped file at 1% per generator per 30 minutes, repairable with /generator repair), a fault count that reaches crash-handling.crash-limit holds a block out of production until /generator unstick names it, and /generator event <factor> [minutes] is the staff-run boost - with a boss bar over the hotbar, because a broadcast is missed by everyone underground. /generator convert <file> [apply] reads another plugin's generator file and writes hoppers-imported.yml (loaded under your config, so your own file still wins), and /generator notifier answers the update notice in the source plugin's words without phoning anybody.

Turning it on. modules.hoppers.enabled: true (on in the shipped file), with the feature switches shop (players may buy) and generators. Nothing needs to exist first: /chunkhopper and /generator are the way in, and the price is money earned by playing.

What you can configure. For the hoppers: hoppers.price (15000.0), upgrade-price, collect-radius (4), link-radius (8), links (1), max-links (6), max-range (12), per-cycle (8), max-per-cycle (24), max-per-player (0 = no limit; ppcore.hoppers.amount.<n> raises it per rank and ppcore.hoppers.admin ignores it), filter-max (16 filter entries), hoppers-per-cycle (8), interval-ticks (10), pickup-delay-ticks (20, so a player can pick their own drop up first), reach (6), protectFromPistons (true), respectClaims (true), upgrades.{links,range,speed}.{step,price}, itemName, itemLore and shopTitle. For the generators: generators.enabled, .block (LODESTONE), .price, .upgrade-price, .max-per-player (4), .min-distance (4), .tier-cycles ([8,4,2,1]), .links, .drop-when-full (true), .shopTitle, .number-formatting.short, .crash-handling.crash-limit (3), .corrupted-generators.* (enabled, chance, check-time-minutes, repair-price, only-if-active), .enable-event-bossbars, .event.{max-factor,default-minutes}, .update-notifier.{enabled,on-join,message}, .converter.messages.*, and .types.<id> - each with name, item, amount, price and an optional weighted drops list. The shipped file carries 390 generated types beside the four hand-written ones.

Commands. /chunkhopper (aliases /ch, /chopper, /chunkhoppers) with upgrade, check and the inspect path; /generator (aliases /gens, /gen) with upgrade, info, list, repair, unstick, event, convert and notifier.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/hoppers.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help hoppers prints the same names and the same nodes, line for line, because both read this one registration.

/chunkhopper (/ch, /chopper, /chunkhoppers)

permission: none of its own · always · Hoppers.java

A chunk hopper: a hopper a player buys with in-game money that picks up dropped items around itself and moves them into the chests next to it. /chunkhopper with nothing after it opens the shop, which is the whole of the player-facing side - the price comes out of the buyer's own balance, and the module's own shop switch can take the counter away. Staff work from the console as well as from the game: give <player> [amount] hands the item over, list prints every placed hopper with its owner and what it has collected, reload re-reads the file, remove takes the block you are looking at out of the world, and info reports the one you are looking at. An owner upgrades what it can do through three ladders - upgrade links for how many containers one pass may fill, upgrade range for how far it looks and upgrade speed for how many items one pass moves - and sets the filter with mode (blacklist, whitelist or off) and filter, which lists, adds and removes entries by name or material. The tier lives on the item as well as on the block, so a hopper that was paid for can be broken and placed again without losing what was paid for. Three limits are deliberate and worth promising a player: it only runs in a chunk that is already loaded, so it can never be the reason a server keeps hundreds of chunks resident; it never deletes an item, so a full chest leaves the stack on the floor rather than eating it; and how many hoppers one account may place is max-per-player, raised per rank with ppcore.hoppers.amount.<n>.

/generator (/gens, /gen)

permission: none of its own · always · Hoppers.java

Generators: the blocks that produce items over time. /generator with no word lists the types, what each one produces and what it costs; list is the same list and is what staff reach for from the console. give <player> <type> [amount] hands one over, reload re-reads the types and the placed blocks, repair starts a stopped generator again - the owner may pay for their own and staff may repair anyone's, which is the difference between this and picking the block up - and info says which types are loaded, how many come from an imported file rather than config.yml, and what this server's own check notifier would say. event <factor> [minutes] scales what every generator produces for a while, for a double-drop weekend, and convert <file> [apply] reads a tiers file in the source plugin's shape and writes this module's imported file underneath config.yml; without apply it only reports what it would make, entry by entry, and nothing is written. Generators can be switched off as a whole - with the switch off this command says so instead of listing anything.

Permissions. Both commands carry no node of their own; the grants that matter are ppcore.hoppers.amount.<n> (how many a rank may own), ppcore.hoppers.admin (ignores the limit) and ppcore.hoppers.generators.admin, plus ppcore.hoppers.update-notify for the join notice the shipped staff group already holds.

Files behind this page

What this page documentsSource
Faction land, power, DTR, the bank, zones, upgrades, the sidebar and /fpkg/src/ppcore/Factions.java (../../pkg/src/ppcore/Factions.java)
Beacon base registration, reinforcement and hardening, siege charges, /obtogglepkg/src/ppcore/Beacons.java (../../pkg/src/ppcore/Beacons.java)
Stacking, levels and gates, axes, crowbars, traits, spawning shapespkg/src/ppcore/Spawners.java (../../pkg/src/ppcore/Spawners.java)
The shop GUI, the picker, dynamic prices, worth, scavenge and the sell wandpkg/src/ppcore/Shops.java (../../pkg/src/ppcore/Shops.java)
The performance sampler, hotspots, cleanup, the redstone limiter and restartspkg/src/ppcore/Perf.java (../../pkg/src/ppcore/Perf.java)
Custom item definitions, the library, recipes, glyphs, the item verbs and the updaterpkg/src/ppcore/Items.java (../../pkg/src/ppcore/Items.java)
Custom enchantment definitions, the effect vocabulary, books and the table rollpkg/src/ppcore/CusEnch.java (../../pkg/src/ppcore/CusEnch.java)
Model definitions, rigs, seats, connecting pieces and the resource packpkg/src/ppcore/Models.java (../../pkg/src/ppcore/Models.java)
Chunk hoppers, their ladders, and the generator block and its eventspkg/src/ppcore/Hoppers.java (../../pkg/src/ppcore/Hoppers.java)
The shipped settings every default above is read frompkg/config.yml (../../pkg/config.yml)
The profiles that switch these modules on, and the two factions templatespkg/profiles/ (../../pkg/profiles/)

Players and games

These ten modules are the ones that act on a player: where they land, what is thrown at them, what follows them, what they fight, who they trade with, what they store, and what a group of them can hold together. Almost all of them are switched on and left alone: the interesting part of each is one or two settings, not a stack of them.

The shared rules - the switch in the modules: block, an edit in config.yml beating the profile, and /playport reload - are in chapter 12; the panel's own screens are in chapter 07.

rtp - random teleport to safe, unclaimed ground

What it does. /rtp (also /wild, /randomtp, /rt) teleports a player to a random spot in one of the listed worlds, and the whole value of the module is where it lands: on solid ground, not in water, lava, fire, cactus, powder snow, cobweb or a portal, not inside somebody's claim, not on top of another player, and - with /rtp <biome> - in the biome they asked for. The search is deliberately fussy, which is why it has two cost controls: a per-world cache of known-good spots built a couple at a time while players are online, so an ordinary /rtp is instant and does no chunk work, and a hard attempt cap for when nothing is cached.

A teleport has a warmup (warmup-seconds, three by default) - moving or being hit cancels it - then a cooldown, then an optional daily limit. /pearlfix is the module's second half and has its own switch: it refuses an ender-pearl landing where the pearl clipped a block or left the player with too few open sides at their feet, prints the rule in force, and counts how many pearls this boot it refused. /rtpadmin (stats, cache, reset) is registered when rtp.admin is true.

Turning it on. modules.rtp.enabled: true. Nothing else is required, but rtp.worlds must name worlds that are actually loaded, or the search never happens in them - and an empty list means every normal-environment world.

What you can configure. rtp.worlds (shipped [world]), the box the search draws in (rtp.min-x/max-x/min-z/max-z at ±3000, min-y: -64, max-y: 200), rtp.attempts: 32, rtp.cooldown-seconds: 60, rtp.daily-limit: 0 (0 is unlimited), rtp.warmup-seconds: 3, rtp.min-player-distance: 64, the cache (rtp.cache: true, cache-size: 16, cache-per-run: 2, cache-check-ticks: 100), rtp.safeArrival: true, rtp.safe-blocks: [] and rtp.avoid-blocks (leave it empty for the built-in list), rtp.messages.searching, and the pearl half (rtp.pearl.fix, rtp.pearl.room - 2 refuses a one-block shaft and a corner pocket, 1 allows a corridor, 0 leaves only the block check - and rtp.pearl.messages.unsafeLocation/veryClose).

Commands. /rtp [biome], /pearlfix [check], /rtpadmin - the last needs ppcore.rtp.admin and rtp.admin: true.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/rtp.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help rtp prints the same names and the same nodes, line for line, because both read this one registration.

/rtp (/randomtp, /wild, /wilderness, /rt)

permission: none of its own · always · Rtp.java

Teleport to a random spot: /rtp on its own, /rtp <biome> to ask for a biome, and -w <world> or -b <biome> when a name and a world have to be named in one line. This is the door that turns a fresh spawn into a world worth walking, and the reason it answers in a second: the module keeps a cache of locations it has already checked, filled slowly around players who are online rather than for an empty world, so /rtp does not make the server generate chunks nobody asked for. A world has to be enabled for it (staff with ppcore.rtp.admin are let through anywhere), the biome permission ppcore.rtp.biome.<name> is asked of a player who names one while that feature is on, and a cooldown, an optional daily cap and an optional warm-up are all answered in words - "Wait 30s before teleporting again", "You have used all 5 random teleports for today", "Teleporting in 3s. Do not move, or the teleport is cancelled" - rather than by silence. ppcore.rtp.bypass skips the cooldown, the cap and the count, which is the node to hand a rank that should wander freely.

/pearlfix

permission: none of its own · always · Rtp.java

Says what this server's pearl check actually enforces, and nothing else: /pearlfix prints the setting, /pearlfix check names what it would refuse. It is registered whether the check is on or off, on purpose - "pearls are not checked here" is the answer a player needs when a pearl does something they did not expect, and a command that vanished when the check was switched off would leave them with silence instead of an answer.

/rtpadmin (/rtpa)

permission: ppcore.rtp.admin · always · Rtp.java

The staff side of random teleport: the counters behind it, the location cache and resetting what a player has used. It is registered only while rtp.admin is on in config.yml, and it is checked against ppcore.rtp.admin at the registration, so it is the one part of this module a rank has to be given rather than being able to type. Use it when a player reports that /rtp will not send them anywhere - the answer is usually their cooldown or the day's cap, and both are readable here.

Permissions. ppcore.rtp.bypass skips the cooldown and the daily limit.

potions - throwable custom debuff potions with a shop

What it does. Thirty throwable debuffs, each one a fight mechanic rather than a stat: armor curse, acid splash, rust plague, slippery oil, gear jam, weapon weaken, shield shatter, lava tag, curse roulette and so on. Every one is a visible, named, temporary state - the player who is hit is told what hit them by name, the thrower is told it landed, and /cp effects lists what is on you. The definitions live in the module's code, not in the config file, so a deleted section cannot silently remove a potion; the config only turns one off or changes its price, duration and strength.

Three limits are enforced rather than documented. The potions that reach into an inventory (gear jam, armor swap glitch, inventory shuffle, loot lock, gapple glitch, elytra curse) are capped by potions.max-disruptive-seconds whatever the per-potion seconds says. Nothing is destroyed: a durability drain stops one point short of breaking, and a shuffle only reorders the hotbar. And no state survives a logout - expiring effects are swept every second and a quit clears the rest.

Turning it on. modules.potions.enabled: true. All thirty ship enabled with a price, so the module works the moment it is on; /potionshop needs the economy (money) and modules.potions.features.shop. potions.debug: true logs every splash and what it did, which is the answer to "my potion does nothing".

What you can configure. potions.shopTitle, potions.debug, potions.max-affected: 4 (entities one splash may hit), potions.max-disruptive-seconds: 5, potions.affectThrower: false, potions.tellVictim/tellThrower: true, and one line per potion under potions.list with enabled, price, seconds and power - the shipped file lists all thirty with their own values.

Commands. /cp give, /cp list, /cp effects; /potionshop (also /pshop), which needs ppcore.potions.use.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/potions.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help potions prints the same names and the same nodes, line for line, because both read this one registration.

/cp (/custompotions, /potions)

permission: none of its own · always · Potions.java

Custom potions - the throwable debuffs an arena fight is allowed to have. /cp list prints every definition the owner wrote with its effect, how long it lasts, its price and whether it is enabled; /cp shop opens the buy menu for whoever may use it; /cp effects lists what is on you right now and how many seconds each one has left, which is the only place a player can read a debuff they are under. The staff half is checked inside the handler: /cp give <player> <potion> [amount] hands the item over (and drops it at their feet if their inventory is full), /cp reload re-reads the definitions and /cp clear wipes every active custom-effect state on the server. A potion's own item carries its name, what it does and what it lasts in its lore - deliberately no potion tint, because an item carrying that component arrived corrupt in a real client - and nothing a custom potion does destroys anything.

/potionshop (/pshop)

permission: ppcore.potions.use · only while modules.potions.features.shop is on · Potions.java

The shop on its own command, for a server that wants the buying door somewhere a player can find it without knowing /cp shop. It opens the same menu and is checked against ppcore.potions.use; every potion on the shelf is bought with the balance the economy owns, at the price its own definition carries - which is the number /cp list prints beside it. The shop feature switch (modules.potions.features.shop) removes this command and nothing else: /cp shop still opens the menu, so switching it off is a decision about naming, not about whether players can buy the potions.

familiars - cosmetic pets, including Blockbench model pets

What it does. A pet that follows its owner, with two things making it more than "summon a wolf". First, a familiar can be a Blockbench model instead of a vanilla mob: set model: to a model the models module already knows and the pet is that model - a floating lantern, a tiny dragon, a chest with legs - with no mod and no resource pack on the player's side. Second, a familiar can never be an obstacle, and that is structural rather than patched: every familiar spawns with collision disabled, a mob familiar has its AI off and is moved by this module's own follow task (so no pathfinding walks it through a wall), it never spawns inside the block its owner stands in, and it deals no damage, takes no damage, cannot be targeted, cannot catch fire, never despawns with distance and cannot be mounted.

The menu is /familiar (/fam, /pet, /pets) with summon, dismiss and list, and an entry that has been unlocked is kept forever. /releasepet (also /unpet, /freepet) is the other kind of pet a player has - the tamed vanilla one - and hands it back to the wild. Priced familiars are bought with money earned by playing; there is no store hook anywhere in the module.

Turning it on. modules.familiars.enabled: true, plus familiars.defs entries to have anything to offer. A model: entry needs the models module on; an entity: name this server does not have and no model to draw is named in the log at boot, and that familiar is simply not in the menu.

What you can configure. follow-distance: 5.0, max-distance: 24.0, step: 0.45, interval-ticks: 4, slots: 1, respawn: true, invulnerable: true, show-names: false, spin: false, title and lore, and one definition per familiar under familiars.defs.<id> with enabled, display-name, entity or model, price, scale, glow, silent and an optional permission.

Permissions. ppcore.familiars.use and ppcore.familiars.admin; ppcore.familiars.extraslot raises one player's slots by one, up to four.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/familiars.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help familiars prints the same names and the same nodes, line for line, because both read this one registration.

/familiar (/fam, /pet, /pets)

permission: ppcore.familiars.use · always · Familiars.java

Your cosmetic companion, and it is cosmetic on purpose: a familiar follows you and can be renamed, and nothing it does fights for you. /familiar and /familiar menu open the picker, /familiar list prints every familiar this server defines - with a tick against the ones you own and a price beside the ones you do not - /familiar summon <familiar> puts one out and /familiar dismiss puts it away. A familiar is tied to your session: it is removed when you log out and anything left in the world by a previous run is cleaned up at boot, so a display can never be orphaned. /familiar name <text> renames the one you have out, and colour in that name is asked for separately (ppcore.familiars.colour) - without it the colour codes are stripped rather than refused, so a decorated name is never a broken one. /familiar rename only reports whether renaming is on here, which is modules.familiars.features.rename.

/familiaradmin (/famadmin)

permission: ppcore.familiars.admin · always · Familiars.java

The staff half: unlocking a familiar for a player, taking one back, listing what somebody has and re-reading the definitions after an edit. It is checked against ppcore.familiars.admin, and it is where you fix the two things a player cannot: a familiar that was never unlocked for them, and a definition that changed under someone who already owns the pet.

/releasepet (/unpet, /freepet)

permission: ppcore.familiars.use · always · Familiars.java

Release one of your own tamed vanilla pets - the wolf, cat or parrot the game itself tamed - back into the wild. Stand next to it and type /releasepet; the nearest pet of yours within eight blocks is the one it takes, deliberately by distance rather than by aim, because a player with three wolves out should not have to point at the right one. Standing next to somebody else's pet does nothing: the vanilla owner record has to be you, which is what stops a friendly-sounding verb from being a way to unclaim another player's animals. This module's own cosmetic familiars are skipped outright, so a paid companion can never be turned loose by a stray use of this command.

arena - boss arenas, custom bosses and damage-based loot

What it does. An arena is a region plus a schedule, and a boss is a mob with skills, a trigger list and a drop table. Setting one up, in the order the shipped file lists them: /arena create crypt, /arena pos1 and /arena pos2 at two corners of the room (or the wand: left-click one corner, right-click the other), /arena setregion crypt to save it and see it outline itself in particles, /arena spawn add crypt zombie_king, /arena interval crypt 60 for a boss every hour, and /arena enable crypt - or /arena now crypt for one right now.

A fight is watched: a bossbar over the boss for players in range, a leash back to where it spawned, a reset when it is left alone out of combat, a despawn if nothing kills it, and a loot roll decided by damage share - arena.loot.mode: lottery rolls each drop once for the whole kill and gives it to one player drawn by damage, while each lets everybody over min-share roll the table for themselves. A kill can also hand the moment to the shared action engine (arena.kill.actions names lists in actions.yml) with the killer as the target. /arena import <folder> reads another server's MythicMobs folder, prints every translation it made and everything it had no answer for, and can write arena-imported.yml - a layer loaded under config.yml, so anything in your file still wins.

Turning it on. modules.arena.enabled: true, then at least one arena. Nothing spawns on its own: an arena has to be created and enabled (or started by hand). arena.panel: true also offers the drop tables as an editable grid in the panel, which writes panel-drops.yml and merges it into the same paths.

What you can configure. arena.panel, arena.wand (shipped GOLDEN_AXE), arena.announce, the region highlight (highlight, highlight-seconds: 8, highlight-particle: FLAME), default-interval-seconds: 3600, max-live-bosses: 32, bossbar/bossbar-range: 48.0, despawn-minutes: 30, reset-seconds: 120, stuck-seconds: 30, leash-radius: 24.0, minions-radius: 10.0, phase-radius: 32.0, combat-timeout-seconds: 15, debugSkills, boss-summon-delay-seconds: 5, egg-permission, the leftover sweep (auto-clear.enabled: false, seconds: 300), loot (mode, min-share: 5.0, broadcast, breakdown, top: 5), kill.actions, arena.import.folder and arena.import.auto, and the content itself: arena.drops (the shared drop tables), arena.skills, arena.random-spawns and the per-boss tables under arena.bosses.

Commands. /arena help|list|gui (also /bossarena, /arenas), /spawnboss <arena>, /worldboss <arena>, and /boss help with the aliases an owner migrating from the source plugin types (cbossesgui, cbossesregionwand, cbossessummonslow, cbossessummonfast, cbossesspawnegg and the rest). All of them need ppcore.arena.use, the same node a boss egg's use needs.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/arena.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help arena prints the same names and the same nodes, line for line, because both read this one registration.

/arena (/bossarena, /arenas)

permission: ppcore.arena.use · always · Arenas.java

Boss arenas, and the whole of the module behind one command. /arena with nothing after it prints the help page; list prints the arenas, info <name> one of them, gui (or import) opens the screens, and check names everything about the setup that would not work - the command to run after editing, because an arena that spawns nothing looks the same as an arena nobody has triggered. Building one is create <name>, pos1, pos2 and setregion <arena>, then spawn add <arena> <boss|hand>, spawn list <arena> and spawn remove <arena> <n> for where its bosses appear, spawner <arena> [<point> interval|amount <value>] for the spawners inside it, and drops and conditions for what it gives and when it may run. Running it is enable/disable <arena>, enableall, interval <arena> <minutes>, now (or spawnnow) to summon one immediately, autoclear, killall and top for the counters. The fight itself is the skills half: skills lists every skill with its steps, skill <id> and targets read one back, live shows the fights that are running right now with each boss's stance, step and bolts in the air, cast <skill> runs one as a player and test <boss> [level] [world x y z] summons a boss for tuning. voucher <arena> <player> [amount] hands out a voucher for a fight, and randomspawn, reload and the rem verbs finish the set.

/spawnboss

permission: ppcore.arena.spawnboss · always · Arenas.java

Spawn an arena's bosses right now, out of the interval that would normally decide it: /spawnboss <arena>. It is the verb an operator reaches for while tuning an arena - the same thing /arena now <arena> does, as its own command because the source plugin has one - and it names the arena rather than requiring you to be standing in it.

/worldboss

permission: ppcore.arena.spawnboss · always · Arenas.java

The same job under the source plugin's own name: /worldboss <arena> summons that arena's bosses immediately. A world boss and an arena boss are one implementation here - an arena is the region a boss belongs to - so this is a second name for /spawnboss rather than a second kind of boss, and the two share the array node.

/boss (/cbossesgui, /cbossesplaceholders, /cbossesregionwand, /cbossessummonslow, /cbossessummonfast, /cbossesuuid, /cbossesclean, /cbosseslang, /cbossesloadholograms, /cbossesspawnegg, /cbossesspawneggperm, /cbosses_reloadconfigs)

permission: ppcore.arena.use · always · Arenas.java

The boss half of the module under the source plugin's names: gui, placeholders, wand, summonslow/summonfast (summon a boss with the slow or fast animation), uuid <boss|arena>, clean, lang, holograms, spawnegg <arena> [player] [amount] with its spawneggperm twin, and reload. Every one of those verbs is also registered as its own alias (/cbossessummonfast lair means /boss summonfast lair), which is what an owner migrating from the source plugin has in their existing signs and menus - the verb arrives as part of the name and the handler reads it back, so a renamed or aliased invocation means exactly the same thing as the spelled-out one.

npc - NPCs: clickable characters that greet and act

What it does. An NPC is a tagged armour stand - a head wearing a player's skin, or a Blockbench model - standing in the world, with no packets involved. It turns to look at players inside look-range, and clicking is one place rather than five: right-click, the vanilla armour-stand equipment screen and damage are all cancelled for these entities, then exactly one action list runs, with a per-player click cooldown so a spam-clicking player cannot fire a command a hundred times. Whatever an NPC runs is a normal command, so a sign that says /spawn is the same as typing it.

Everything is /npc: create <name> where you stand, action <name> add <action>, skin <name> <player|none>, model <name> <modelId|none>, name <name> <text|hide>, look, move/here, rename, enable/disable, collidable/solid, glowing, permission <name> <node|none>, attribute, visibilitydistance <name> <blocks|off>, list, remove and check. There is no showintab, and it refuses the verb rather than storing a setting that would never draw anything: a tab-list entry is a packet, and this module is packet-free.

Turning it on. modules.npc.enabled: true, then create NPCs in game or ship them from a template under npc.defs. An NPC's own attributes and equipment live with the NPC in npcs.yml, not in config.yml - the equipment block below only dresses every NPC at once.

What you can configure. look-range: 8.0, look-interval-ticks: 10, click-cooldown-ms: 600, default-skin: Steve, stand-visible: true (false leaves a floating head for a sign or a board), model-scale: 1.0, model-spin: false, click-sound, visibility-distance: 0 (0 is no limit; above 0 the NPC is only sent to players inside that many blocks), the shared kit under npc.equipment (main, off, chest, legs, feet) and the NPCs a template ships under npc.defs.

Permissions. ppcore.npc.admin for everything, including /npc list and /npc check.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/npc.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help npc prints the same names and the same nodes, line for line, because both read this one registration.

/npc (/npcs)

permission: ppcore.npc.admin · always · Npcs.java

/npc is the whole of this module: one command with a verb per thing an NPC can be. /npc on its own opens the list as a menu and /npc edit <name> opens one NPC's detail screen, which is the fastest way to see everything the verbs below do. /npc create <name> puts a character where you are standing (in game only - an NPC stands somewhere), /npc move brings one to you, /npc rename <name> <new> renames it, /npc remove <name> deletes it, and /npc list and /npc reload print and re-read the file. Its appearance is a chain of its own verbs: skin <name> <playerName|none>, name <name> <text|hide>, model <name> <modelId|none>, glowing, collidable, look, visibilitydistance, turntoplayer <name> <true|false> with turntoplayerdistance, interactioncooldown <name> <ms|default> and attribute <name> <attribute> <value>. copy <name> <newName> duplicates one, and action <name> <add|remove|clear|list> [type:value] is what it does when clicked - a message, a command, and the other action types /npc action <name> add reports. Everything is checked against ppcore.npc.admin because an NPC is staff furniture: it is created, named and given an action by somebody trusted, and then clicked by everybody.

trade - player trading: two windows, both must confirm

What it does. /trade <player> opens a window holding copies of what each side offers. Nothing leaves either inventory until both players have confirmed; a change to either side clears both confirmations; and the swap itself is checked against both inventories before a single item moves. That rule is not configurable, because it is not a preference - so what is left in the config is comfort: how long an unanswered request waits, how long a window with no change stays open, how far apart the two players may wander before the trade stops, and how much an offer may exceed what a player holds. Money can be offered too, when the economy is live.

Turning it on. modules.trade.enabled: true. There is nothing to set up and no permission: trading is either something your server has or does not.

What you can configure. trade.requestTimeout: 60, trade.timeoutSeconds: 180, trade.maxMoney: 0.0 (0 means only what the player holds), trade.maxDistance: 0 (0 is no limit), trade.logSize: 200 (settled trades kept for the log) and trade.logToConsole: true. The money and log halves are switches under modules.trade.features.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/trade.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help trade prints the same names and the same nodes, line for line, because both read this one registration.

/trade (/trades, /trader)

permission: none of its own · always · Trade.java

Trade items and money with another player, in a window both sides see: /trade <player> asks somebody to trade, and the request times out on its own (trade.requestTimeout) rather than waiting forever. Its verbs are the two answers and the four settings a trader needs: accept, deny (or decline), cancel, toggle to opt out of trade requests altogether, money <amount> to put part of your balance into the trade, and logs for the record of what you have traded. The pair of you have to stay near each other - the window closes when either of you walks off (trade.maxDistance) - and there is a ceiling on how much money one trade may carry (trade.maxMoney), which is the one setting on a server that takes its economy seriously. Money in a trade moves through the econ module's own balances, so both sides have to be able to pay; the whole exchange can be written to the console when modules.trade.features.log is on, and modules.trade.features.money is what removes the money half entirely for a server that would rather players traded their hands only.

guard - cheat detection: conservative checks, alerts only

What it does. Three rules explain the whole module. Alerts, not punishments: punish: false with an empty punish-commands, so nothing here kicks or bans anybody, and an owner who wants automation sets both. An exemption is a feature: creative, flying, gliding, riding, water, ladders, damage, knockback, launches, teleports, high ping, low TPS and any movement potion effect each suspend the checks they would otherwise break - that last one is the custom-potion exemption, and a module that fires when a custom potion is used would be worse than no module. A single event is never a violation: each check fills a buffer that decays per second, and only a full buffer raises a violation level.

It reads Bukkit events by default. With the PacketEvents plugin installed it also reads the client's own packet stream, which is what makes timer, aim, badpackets and groundspoof possible; with no packet layer those four appear in /guard checks as not checked (no packet layer) rather than being left for an owner to assume. The module also carries the anti-crash half - payload caps on item NBT, books, signs and spawn bursts, rate limits on chat, tab-complete and block interactions, all lag-scaled, and creative/schematic compatibility so a printer is throttled rather than accused. With Vulcan, Matrix, Spartan, Grim, NoCheatPlus, Themis, Karhu, Intave, AAC, Horizon, Guardian or AntiCheatReloaded installed, the module stands down and says so.

Turning it on. modules.guard.enabled: true. It needs nothing else; PacketEvents is a soft dependency that widens the checks when present. interop.forceModules.guard: true runs it even when a plugin it yields to is installed.

What you can configure. guard.punish: false and guard.punish-commands: [] (the only two keys that can punish anybody, and both together), violation-reset-minutes: 8, log.file: true, alerts (format, console-format, print-to-console, toggle-on-join, min-interval-ms: 700), exempt (join-grace-ticks: 100, teleport-grace-ms: 1500, velocity-grace-ms: 2500, damage-grace-ms: 2500, min-tps: 15.0, max-ping: 400, creative, flying, gliding, riding, effects, bypass-permission), the check groups (combat.reach|killaura|autoclicker|aim, movement.speed|fly|nofall, player.fastplace|inventory, exploit.nbt|book|sign|spawn|window, packet.chat|tabcomplete|interact|badpackets), each with enabled, its threshold, buffer (max, multiple, decay), dont-alert-until and max-violations; and the anti-exploit block guard.anticrash (compat, lag, budget, log).

Commands. /guard (alerts, checks, exemptions, exploit protection, logs; aliases /ac, /playportguard), /jday (the queue of players to punish and the run that does it) and /punishlogs.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/guard.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help guard prints the same names and the same nodes, line for line, because both read this one registration.

/guard (/ac, /playportguard)

permission: ppcore.guard.admin · always · Guard.java

Cheat detection, driven from one command: /guard status is where you start, and it prints what is on, how many violations the checks have seen and whether the server is currently exempt from being judged. The verbs are on|off, checks (every check with its thresholds, from guard.combat.*, guard.movement.* and guard.player.*), top (the players the checks are most suspicious of right now), logs (the recent record), exempt <player> [seconds] [reason] and unexempt for the player who is lagging or on an old machine, clear <player> to wipe one player's violations, reset for all of them, reload after a config edit, alerts to switch the live alert stream on and off, and the two knobs nothing else on the server offers: kb/knockback and rotate. guard.punish is what decides whether a violation is only reported or acted on, and guard.exempt.min-tps, guard.exempt.max-ping and guard.exempt.join-grace-ticks are why a check that fires on a struggling server is a check nobody believes. The packet checks have their own door under packets (aliased anticrash), because an anti-crash guard is about the server falling over rather than about one player cheating.

/jday

permission: ppcore.guard.admin · always · Guard.java

Judgement Day: the queue of players this module believes should be punished, and the run that does it. /jday add <player> [reason], /jday remove <player>, /jday list and /jday execute (or start) are the four verbs - a queue rather than an immediate ban, so an owner can read what the checks found and decide before anything irreversible happens to a player's account. It is the same object as /guard, registered under the name an owner migrating from Vulcan already types, so the two commands are one implementation and the queue is the same queue.

/punishlogs

permission: ppcore.guard.admin · always · Guard.java

Read a player's punishment log: what this module did to them, when, and what it was answering. Type it with a name - /punishlogs <player> - which is the record an appeal is answered from, and the reason the node is ppcore.guard.admin rather than something a player holds. Where /guard logs shows the recent stream across the server, this is one player's history, oldest to newest.

Permissions. ppcore.guard.admin for the commands, ppcore.guard.alerts for alerts on join and ppcore.guard.bypass to exempt a player without turning the module off. None of these is in any shipped group - the bypass node in particular is deliberately unassigned.

vaults - private vaults (/pv)

What it does. A chest per player, opened from anywhere: /pv for the first one, /pv <number> for another, /pv <player> <number> to look at somebody else's (logged when log-views is on), and /pvlist for the list with how full each one is. How many a player gets comes from the highest ppcore.vaults.amount.<n> node they hold, capped by max-count - so a new rank gets its vaults from the rank's node list rather than from an edit here.

The shared chest is the same window for a group: /teamvault [number] (also /tv, /fvault, /gvault, /teamvaults, /sharedvault) opens a faction's or a team's storage, sized by shared-count and gated by shared-access and shared-permission. A blocked item is taken back when the window closes rather than when it is clicked, because an item can arrive by shift-click, from a hopper or from another plugin, and a click-time check misses all three. /pvdel <player> <number> is the admin verb for deleting one.

Turning it on. modules.vaults.enabled: true. Give your ranks the amount nodes for more vaults; the shared chest needs factions or teams running.

What you can configure. vaults.rows: 6 (1-6 rows of 9), default-count: 1, max-count: 6, shared-count: 3, shared-title, shared-access: members (members or managers), shared-permission: ppcore.vaults.shared, log-shared-views: false, title, own-only: true, drop-blocked: true (hand a banned item back rather than deleting it), log-views: true, the open and close sounds, blocked-bypass: ppcore.vaults.bypass, and the banned items under vaults.blocked (shipped: bedrock, barrier, the three command blocks, structure block and void, jigsaw, light, debug stick, end portal frame; a trailing * matches a family).

Permissions. ppcore.vaults.amount.<n> for how many private vaults, ppcore.vaults.shared for the group chest, ppcore.vaults.bypass to store a blocked item, ppcore.vaults.admin for /pvdel.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/vaults.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help vaults prints the same names and the same nodes, line for line, because both read this one registration.

/pv (/vault, /chest, /playervaults, /vc, /axshulkers)

permission: none of its own · always · Vaults.java

Open one of your vaults: /pv for the first, /pv <number> for one of the others, and /pv <player> <number> for a staff look at somebody else's (ppcore.vaults.admin, and the console can type it too - it has no vault of its own). /pv team [number] - and faction, group, shared or guild as the first word, all spellings of the same vault - opens the shared chest instead, so somebody who learned /pv never has to learn a second command for their team's storage. A player cannot drop what they are carrying into a vault if vaults.drop-blocked says so, vaults.rows and vaults.title shape the window, vaults.own-only decides whether anybody may see another player's, and vaults.log-views writes every look at somebody else's vault into the console - which is the line that makes a shared storage auditable rather than private.

/pvdel (/vaultdel)

permission: ppcore.vaults.admin · always · Vaults.java

Delete a vault: /pvdel <player> <number>. It is checked against ppcore.vaults.admin and it is the only command in this module that destroys anything, so it is the one to think about before typing - open it first with /pv <player> <number> if you are not sure, and remember that the contents go with the vault rather than being handed back to the player.

/pvlist (/vaults)

permission: none of its own · always · Vaults.java

List your vaults and how full each one is, so you can see which one you meant before you open it - the honest way to answer "where did my sword go" without opening seven windows one after another. It takes no arguments, and it is the list an operator points a player at when they ask how many vaults they have.

/teamvault (/tv, /fvault, /gvault, /teamvaults, /sharedvault)

permission: none of its own · always · Vaults.java

The shared vault, on its own command for the servers where it is the point of the module: /teamvault opens the first one, /teamvault <number> another, /teamvault list prints them and /teamvault delete <number> removes one for whoever holds the officer's node. It is a vault that belongs to a group rather than to a player - a faction, a team, or a group on a server with neither - and vaults.shared-access, vaults.shared-count and vaults.shared-permission are the three settings that decide how many there are and who may open one.

koth - King of the hill: capture zones, timers and rewards

What it does. A hill is a zone plus a clock. Standing in it, uncontested, for capture-seconds takes it and pays out. All three states are part of the design: empty (progress decays if decay-per-second says so), contested by two teams (the timer freezes) and held (it counts). Everyone in the zone is measured by the same timer, and a capture depends on presence, contest and the clock - never on a purchase.

koth.team decides what a "team" is: auto uses a faction when the factions module is live and treats each player as their own team otherwise, so a survival server and a faction server both get the right answer without a team plugin, and player forces the second. A hill can be created in game with the wand - left-click one corner, right-click the other - or shipped by a template under koth.defs.<id>; a half-hourly hill is interval-minutes: 30, and staff start one by hand otherwise. A hill deleted in game stays deleted, because the id is remembered in koth.yml.

Turning it on. modules.koth.enabled: true, then one or more hills. /koth join works out of the box, since the default group already holds join-permission's node.

What you can configure. koth.team: auto, bossbar: true with bossbar-range: 64.0, outline: true and outline-particle: VILLAGER_HAPPY, wand: GOLDEN_AXE, max-hills: 16, join-permission: ppcore.koth.join, and the defaults a new hill starts with (default-capture-seconds: 120, default-max-seconds: 1800, default-decay-per-second: 0.0). A hill under koth.defs.<id> carries display, world, pos1/pos2, its own capture-seconds, max-seconds and decay-per-second, the money each member of the capturing team is paid, its interval-minutes (0 means staff start it) and a rewards list of actions.

Commands. /koth list, /koth join, /koth info <hill> (also /kingofthehill), and /kothsetjoin sets one of the five global join points to where you stand.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/koth.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help koth prints the same names and the same nodes, line for line, because both read this one registration.

/koth (/kingofthehill, /kotha)

permission: none of its own · always · Koth.java

King of the hill: a marked region a team holds for a stretch of time while everybody else tries to take it. /koth list prints the hills and their state, /koth info <hill> one of them in full, /koth join puts you in the queue for the next run and /koth team <name> is how a hill that has teams turned on knows which side you are on. Every hill can be held for a time (time) or capped (maxtime), can decay while nobody stands in it (decay), can pay money (money), can run on an interval by itself (interval), can carry console commands as its reward (reward <name> <add|remove|list|clear> [command]), and can be pointed at with tp/goto, captures/top, editor and display. Building one is /koth create <name>, then pos1, pos2, setregion (or wand if you would rather click the corners), then start when you want it live - stop and reset are the other two answers. koth.max-hills is the ceiling on how many a server may have, and koth.join-permission is the node that decides who may queue for one.

/kothsetjoin (/kothjoin)

permission: ppcore.koth.admin · always · Koth.java

Set one of the five global /koth join points to where you are standing: the hill a player is put on when they type /koth join and nothing else. It is checked against ppcore.koth.admin, and a server that names its points this way never has to teach players hill names - /koth join puts them wherever the operator last stood.

Permissions. ppcore.koth.join to play, ppcore.koth.admin for the setup verbs. Staff bypass the join node.

teams - teams: shared storage, a home and a chat for a group

What it does. A team is a group players make themselves: a shared chest, a home and a chat. It is the module the templates without factions were missing - survival, lifesteal, skyblock and SMP all want "us" and none of them has a faction plugin behind it - and on those servers it is also the identity the vaults module keys a shared chest by.

It is deliberately not a faction with a different label: no land, no power, no wars, no allies, and a team cannot claim a chunk. A server that wants that runs factions, which is still there and still separate. Everything is /team (/team create <name>, /team invite <player>, /team vault, /team home) plus /teamchat (/tc) to talk to the team alone. Every limit is one server-wide number - a team's chest is the same size as anybody's own - and create-permission is a rank gate an owner can hand to whoever they like rather than a price.

Turning it on. modules.teams.enabled: true. Decide who may start a team via create-permission (give the node to a rank and leave it off the default group to make teams a perk, or blank the key to let anybody create one). The shared chest also needs vaults for its size and access rules.

What you can configure. teams.max-members: 12, teams.max-teams: 200 (a guard on the file), teams.max-name-length: 12, teams.invite-only: true (false lets anybody join without an invite), teams.invite-minutes: 60, teams.home-cooldown-seconds: 60 and teams.create-permission: ppcore.team.create.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/teams.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help teams prints the same names and the same nodes, line for line, because both read this one registration.

/team (/teams)

permission: none of its own · always · Teams.java

A group of players with a name, a home and a shared chest: /team create <name> (or make) starts one, /team invite <player> and /team uninvite <player> are the door, /team join <team> (or accept) the answer to it, and /team list and /team info are how you find one. The running of it is /team kick <player>, /team promote|demote <player>, /team transfer <player> (or setowner) and /team disband; the living in it is /team sethome and /team home, /team chat <message> for a line only your own team hears, and /team vault [number] for the shared storage - the same storage the vaults module's own shared chest is, which is why a player who learned one of the two commands does not have to learn the other. teams.invite-only decides whether a team can be joined by name at all or only by invitation, teams.max-members and teams.max-teams are the ceilings, teams.create-permission is the node that decides who may start one, and teams.home-cooldown-seconds is what stops /team home being a free teleport.

/teamchat (/tc)

permission: none of its own · always · Teams.java

Talk to your team only, on its own command for the servers where that is the habit: /teamchat <message> is the same line as /team chat <message>, heard by your team and nobody else. It is registered with the same node as the rest of the module, and it is deliberately a second name for one behaviour rather than a second implementation - what /team chat does, this does.

Files behind this page

What this section documentsSource
/rtp, the spot search, the cache and the warmup, and /pearlfixpkg/src/ppcore/Rtp.java (../../pkg/src/ppcore/Rtp.java)
The thirty potions, their limits, /cp and the potion shoppkg/src/ppcore/Potions.java (../../pkg/src/ppcore/Potions.java)
Familiars: the follow task, the collision and AI rules, model pets, the menupkg/src/ppcore/Familiars.java (../../pkg/src/ppcore/Familiars.java)
Boss arenas, bosses, skills, loot by damage share and the MythicMobs importpkg/src/ppcore/Arenas.java (../../pkg/src/ppcore/Arenas.java)
NPCs: the tagged stands, the look task, click handling and every /npc verbpkg/src/ppcore/Npcs.java (../../pkg/src/ppcore/Npcs.java)
Trading: the two-window confirmation rule and the swap checkpkg/src/ppcore/Trade.java (../../pkg/src/ppcore/Trade.java)
Guard: the checks, the buffers, the exemptions and the anti-crash halfpkg/src/ppcore/Guard.java (../../pkg/src/ppcore/Guard.java)
Vaults: the per-player chests, the amount nodes and the shared chestpkg/src/ppcore/Vaults.java (../../pkg/src/ppcore/Vaults.java)
King of the hill: zones, the clock, team resolution and rewardspkg/src/ppcore/Koth.java (../../pkg/src/ppcore/Koth.java)
Teams: membership, invites, the home and the team chatpkg/src/ppcore/Teams.java (../../pkg/src/ppcore/Teams.java)
Every key quoted above, with its comments and shipped valuepkg/config.yml (../../pkg/config.yml)

Farming, worlds and progression

These eleven modules are the ones a longer game is built from: the tools that make gathering fast, the world a player owns, the games played in it, and the ladders that give all of it a direction. Three of them - skyblock, plots and dungeons - can create worlds of their own, and each says where and when that world appears, because generating one is a cost somebody pays.

The shared rules - the switch in the modules: block, an edit in config.yml beating the profile, the feature switches under modules.<id>.features, and /playport reload - are in chapter 12; the panel's own screens are in chapter 07.

harvest - chain mining, tree felling and fast farming

What it does. Break one block and the module takes the ones that belong with it: an ore vein, a tree from its bottom log, the ripe crops around the one that was broken. A chain is queued and drained blocks-per-tick at a time (8 by default), so a 64-block vein is eight bounded ticks rather than one 64-block tick - which is the whole reason a chain cap and a per-tick budget exist side by side. Mining needs a pickaxe, logging an axe and farming a hoe (require-tool), one point of durability is charged per extra block and the tool is never broken by it (charge-durability), and claims and factions get the first answer, always (respect-claims). Canvas clearing follows leaves only within leaf-radius of the stump and only up to max-leaves: a canopy pass that walked into the next tree would be a griefing tool, so both bounds are there rather than a switch. Replanting is on, and it costs a seed or sapling the player is already carrying - turning consume-seed off is a seed duplicator, and the comment in the shipped file says so.

The same section holds three things a chain-mining plugin usually does not. The crop lifecycle (harvest.crops): a crop type is a seed, the block it becomes, how many stages it takes, the seconds per stage, whether it needs water and light, what a mature one pays and what an early break returns - grown on a wall clock rather than vanilla's random tick, which is what lets a farm keep growing while its owner is away. The offline half is bounded on purpose: offline.max-stages: 4 is the ceiling on what one pass may pay, so a week-old farm catches up over the following passes instead of arriving ripe as a jackpot, and offline.unloaded-chunks decides whether a crop outside the view distance keeps its record at all. A crop also remembers who planted it, and protect: true means only the planter may break it before it is ready. /harvest crops, /harvest crops info <type> and /harvest crops stats read that back. Quality and consumables (harvest.crops.quality, .consumables): a harvest rolls a tier table (ratio: "17/2/1" over the names Common, Fine, Prime) unless a carried consumable names a better one, and the five consumable types are QUALITY, VARIATION, SOIL_RETAIN, SPEED_GROW and YIELD_INCREASE, each lasting times growth passes. The sprinkler (harvest.crops.sprinklers): a placed thing that waters a pattern (square, rhombus or circle) on its own clock out of a tank a player fills with a bucket or a potion, or forever when infinite: true. What it deliberately is not is a scripting layer - the crop's four moments (plant, stage, ripe, harvest) are bound to named lists in the action engine and everything else is a number in this file.

orestack is the module's fourth half and it is off by default. telepathy puts an ore's drops into the pack rather than on the floor and merges them with the stack already there; auto-smelt hands out what a furnace would, by the table in harvest.orestack.smeltable-items. Both decide what a broken ore yields, and a server that has not decided should get vanilla's answer - which is why the switch ships off rather than on. /offlinegrowth is the away-farm clock's own command, and the four collection-sources beyond block_drops are read, kept and reported by /harvest check as having nothing behind them rather than being quietly inert.

Turning it on. modules.harvest.enabled: true, and it ships on: chain harvesting is a convenience, so the default is everybody having it and each player being able to turn it off for themselves with /harvest off. The finer switches are modules.harvest.features.mining, .logging, .farming, .replant, .crops (the lifecycle and sprinkler half) and .notify. The two settings that decide where a chain may happen are harvest.worlds (empty means every normal-environment world; name worlds to keep instant blocks out of a creative plot world or a minigame arena) and the min-y/max-y band.

What you can configure. harvest.worlds: [], harvest.min-y: -64, harvest.max-y: 320, harvest.mode: classic (sneak chains only while the player is sneaking; a player can change their own with /harvest mode), harvest.max-blocks: 64, harvest.blocks-per-tick: 8, harvest.max-distance: 0 (0 is no limit), harvest.diagonal: false (false is face-adjacent, the shape veins actually are), harvest.require-tool, harvest.charge-durability, harvest.respect-claims, harvest.fell-leaves: true, harvest.max-leaves: 96, harvest.leaf-radius: 5, harvest.farm-radius: 1 (1 is the 3x3 a scythe would get; 0 turns the area pass off), harvest.auto-replant-seeds/.auto-replant-saplings/.consume-seed, harvest.notify: actionbar (none, actionbar, chat or both), harvest.mine-groups (extra families a material name cannot express - ores still chain with their own kind by name, so this adds rather than replaces), harvest.extra-farm-blocks: [], the four sounds under harvest.sound, every sentence under harvest.messages, the two window titles (gui-title, admin-gui-title), and the crop, quality consumable, watering-can and sprinkler tables described above. Each crop type carries seed, block, stages, seconds, water, light, drops, seed-return and an optional events binding.

Commands. /harvest (aliases /sh, /superharvest, /fastbreak, and the two window titles above are its GUI), /harvest mode, /harvest crops [info <type>|stats], /offlinegrowth, and /harvest check for the settings that would do nothing. The source plugin's /orestack name is not this module's to register - the batch of compatibility modules already owns that verb.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/harvest.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help harvest prints the same names and the same nodes, line for line, because both read this one registration.

/harvest (/sh, /superharvest, /fastbreak)

permission: none of its own · always · Harvest.java

Chain mining, tree felling and fast farming - the module that breaks the whole vein when you break one ore - and the command is the player's own preferences plus the staff's readings. /harvest with nothing after it prints your state, info the same in more detail, and on and off switch the module off for you alone, which is the setting that matters to a player who wants to mine one block at a time. mining, logging and farming are the three halves, each with its own feature switch under modules.harvest.features.*; replant decides whether a harvested crop is put back, mode classic|sneak whether the chain runs on every swing or only while sneaking, and status none|actionbar|chat|both what you are told about a block you just took. crops is the farming catalogue and its doors - list, info <type>, stats, give <type> [player] [amount], quality, consumables, sprinklers info <kind> and fed - and orestack and collections are the stack half of the module under its own verbs, since the source plugin's /orestack name belongs to another module. admin stats|reload|player <name>|on|off is the staff door behind ppcore.harvest.admin. The performance settings are the ones to read before turning this on across a big world: harvest.max-blocks, harvest.blocks-per-tick, harvest.max-distance, harvest.cooldown-ms, harvest.require-tool and harvest.respect-claims.

/offlinegrowth

permission: none of its own · always · Harvest.java

What grows while nobody is near it: the offline clock, its counters, and what is waiting for the players who log in next. /offlinegrowth prints the state, and collections is its second reading - the per-player tally of what grew away from them. It lives here because this is the module that owns crops and growth; the command exists under the name the source plugin uses so an owner migrating from it keeps the verb they already know.

elevator - elevators: jump for the floor above, sneak for the one below

What it does. A carriage block is a floor stop, and the ride is the gesture: jump to go up, sneak to go down. blocks: [IRON_BLOCK] is the whole mechanic by default, and every block of that material anywhere in a column becomes a stop on the shaft that column forms. The module scans for the next landing that has headroom blocks of free space, will not cross max-distance floors in one ride (32 by default, and ppcore.elevator.bypass.distance lifts it - 255 or more is the source plugin's "ignore it completely"), and stops at anything named in stop-at, which is how a glass ceiling or a floor you do not want crossed is expressed. A landing may have a torch, a ladder, a rail or a sign in its head room - that is what ignore-list is for - and the ride is refused with a message rather than by being silent about it.

Two blocks in one shaft is the source plugin's combinations syntax, kept here: STEP:7, IRON_BLOCK names the floor you stand on and the floor you land on, a third number is that pair's own distance ceiling, and any on either side means any block (as a landing, any means the same height the floor being left has, so a slab lifts to a slab). Colons work where a comma inside a list entry is awkward. A :N like STEP:7 is a pre-1.13 block data value this server no longer has, and the check says so once rather than pretending. The decoration is all optional and all off by default: a boss bar naming the floor and its rung, a column of particles for where the lift is and where it is going, and the title line as well as the action bar.

Land is asked at both ends of a move - the blocks are leaving somewhere as well as arriving - which is why land: relations is the ordinary answer and a respect-claims: false file with no land gets off, exactly what that line always meant. lazy-check adds one floor past either end of the column where a normal floor is not an elevator (the ground below the bottom carriage, a floor above the top one), with an optional whitelist that invert turns into a blacklist. /elevator create builds the landing above you, and create-replace-blacklist is the list of blocks it will refuse to overwrite rather than punching a hole in.

Turning it on. modules.elevator.enabled: true, and it ships on: it is transport rather than power, and a hub without one is the first thing players notice. modules.elevator.features.up, .down, .sound and .title are the per-feature switches. elevator.worlds: [] means every world; name worlds to keep a free ride out of one where it changes the game.

What you can configure. elevator.blocks: [IRON_BLOCK], elevator.combinations: [], elevator.worlds: [], elevator.override-y: false (false uses the world's own limits; true uses min-y: -64/max-y: 320), elevator.max-distance: 32, elevator.delay-ms: 700 (between rides; ppcore.elevator.bypass.cooldown lifts it), elevator.delay-ticks: 0 (above 0 the lift warns and then moves), elevator.headroom: 2, elevator.stop-at: [], elevator.ignore-list (the full shipped list of torches, signs, buttons, rails and vines), elevator.land: relations and elevator.respect-claims, elevator.notify: actionbar, elevator.title: true with title-fade-in: 5/title-stay: 10/ title-fade-out: 5, the lazy-check block (top, bottom, whitelist, invert), the boss-bar block (enabled: false, color: RED, style: SOLID, seconds: 3, its message with %floor% and %totalFloors%), the arrow block (enabled: false, current-floor, destination-floor, color-up: green, color-down: red, size: 2.0), elevator.create-replace: true with create-replace-blacklist, temporary-invulnerable: false and temporary-invulnerable-ticks: 40, disable-in-combat-seconds: 0 (0 is off; the module's own tag, no combat plugin needed), the two sounds under sound.up/sound.down (each with enabled, sound, pitch and world - world: true plays it to everyone in range rather than only the rider), and every sentence under messages.

Commands. /elevator (also /elevators) rides and reports, and /elevatoradmin (also /sea) is the admin half: reload and settings, behind ppcore.elevator.admin.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/elevator.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help elevator prints the same names and the same nodes, line for line, because both read this one registration.

/elevator (/elevators)

permission: none of its own · always · Elevators.java

Elevators out of ordinary blocks: stand on one and jump to be taken to the floor above, or sneak to be taken to the one below. The command itself is the reading rather than the moving - /elevator and /elevator help explain the mechanics, and /elevator list (or info) prints what the module knows where you are standing. Nothing has to be typed to use an elevator, which is the point of it: a player who has never read anything gets it right first try, and the command exists for the one who wants to know why nothing happened. elevator.max-distance is how far up or down a jump may carry somebody, elevator.headroom is how much clear space has to be there before it does, elevator.delay-ms is the pause that keeps a player from shooting through five floors, elevator.respect-claims stops an elevator leaving somebody else's territory, and the up, down, sound and title feature switches remove one direction or the announcement without removing the elevator.

/elevatoradmin (/sea, /elevatoradmin)

permission: ppcore.elevator.admin · always · Elevators.java

The staff half: re-reading the module's settings after an edit in config.yml (or in the panel), which is what an operator wants after changing a distance or a delay rather than waiting for a restart. It is checked against ppcore.elevator.admin, and it is registered under the source plugin's own names (/sea) as well, so an owner migrating from it does not have to relearn the verb.

fishing - custom catches, a fishing level and the fish market

What it does. Casting a line rolls the module's own weighted table instead of vanilla's, and what you get is a named, levelled fish: /fish opens your level, the catalog and the market. Twelve catches ship across six rarities, from a dace and a muddy boot at level 1 to the ancient coelacanth at weight 1, and the rule that makes the level mean something is the gate - a fish whose level is above yours is never rolled at all, so the legendary at the bottom of the catalog is something a player sees somebody else catch first. Rarity decides how loud the catch is: announceFrom: epic sends a server-wide line at epic and above, and announceLevels does the same when somebody levels up. Levelling is xpBase: 100 for the first level, xpPerLevel: 50 more each time, to max: 100.

The market is the other half and it is the source plugin's own idea: /fish sells what you are holding at worth times sellMultiplier (1.0 as shipped), and pack names a .zip or folder whose textures are used for the catches. replaceVanilla: true is the one behaviour switch in the section rather than a feature switch, and it is deliberately separate from modules.fishing.enabled because "my own catches" and "no vanilla cod" are two different decisions. The module also registers the source plugin's five own doors - /ifcatalog, /ifleaderboard, /fishtopscore, /ifevaluator, /ifspots - so a server migrating from it has the commands its players already type.

Turning it on. modules.fishing.enabled: true, and it ships on and pre-filled with the twelve catches, so a fresh server has a reason to walk to the water without an owner writing a line. The feature switches are modules.fishing.features.announce (the rare-catch line) and .announceLevels. The module stands down by itself when a fishing plugin is installed.

What you can configure. fishing.replaceVanilla: true, fishing.announceFrom: epic (one of common, uncommon, rare, epic, legendary, mythic), fishing.sellMultiplier: 1.0, fishing.pack: "", fishing.level.xpBase: 100, .xpPerLevel: 50, .max: 100, the weighted fishing.loot list ({ id, weight }, in file order), and one block per catch under fishing.fish.<id> with material, name, rarity, level, xp, worth, an optional glow: true, model (auto assigns custom model data) and lore.

Commands. /fish (aliases /fishing, /fishmarket) for the catalog and the market, the source plugin's five doors listed above, and /fishadmin (also /fishingadmin) behind ppcore.fishing.admin for levels, catches and reload.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/fishing.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help fishing prints the same names and the same nodes, line for line, because both read this one registration.

/fish (/fishing, /fishmarket)

permission: none of its own · always · Fishing.java

Fishing with a level ladder, a catalogue of fish and a market that buys your catch. /fish on its own prints your own state - level, experience, how many you have caught, what you have earned and the best thing you have landed - and list (or catalog) prints every fish this server defines with its rarity, the level it needs and what it is worth, which is the page a new player is told to read. sell sells what is in your hand and sell all everything sellable you are carrying, at the price the fish's own definition carries times fishing.sellMultiplier; top is the ranking of anglers by level. The catalogue itself comes from fishing.pack (or the packs the panel has on), fishing.replaceVanilla decides whether the vanilla fishing table is replaced or added to, fishing.level.* sets the curve, and modules.fishing.features.announce and .announceLevels are what tell the server about a good catch.

/fishadmin (/fishingadmin)

permission: ppcore.fishing.admin · always · Fishing.java

Staff: levels, catches and reloading the catalogue. /fishadmin info <player> reads somebody's angler record, give <player> <fish> [amount] hands a fish over, level <player> <number> and xp <player> <number> set where they are, roll <player> [times] draws catches without any fishing - which is how you test a table - event starts and stops a fishing event, and reload re-reads the definitions. It is checked against ppcore.fishing.admin.

/ifcatalog

permission: none of its own · always · Fishing.java

The catch catalogue, under the source plugin's own name: what can be caught and the level each one needs. It is /fish list spelled the way an owner migrating from ItemFish already types it, on its own command so a sign or a commands.<id>.name rename can point at it.

/ifleaderboard

permission: none of its own · always · Fishing.java

The anglers, by level and catches - the same ranking as /fish top, under the source plugin's own name, so a server that has a leaderboard sign written for that plugin keeps working.

/fishtopscore

permission: none of its own · always · Fishing.java

A player's best catch: /fishtopscore <player> prints the top score this module keeps for that angler, which is the one number a fishing competition is scored on. It wants a name - the score is kept per player, and the command says so rather than guessing whose you meant.

/ifevaluator

permission: none of its own · always · Fishing.java

What the market would pay for your catch: it answers the price before you sell, where /fish sell answers it after the fact - which is the difference between knowing what a fish is worth and finding out. It is the source plugin's own name for that reading.

/ifspots

permission: none of its own · always · Fishing.java

The hot spots: marked water where the good things bite, so a player who has just bought a rod knows where to walk to. The spots come from the pack's own settings, and this command is the source plugin's own name for the listing.

skyblock - skyblock: an island world, island levels, challenges and upgrades

What it does. Every player (or every team, up to max-members: 6) gets a square of its own in a generated void world, laid out on a grid of 400 blocks between island centres. A new island is a platform of radius: 8 blocks at level y: 100 with a tree and a chest (an OAK_SAPLING, BONE_MEAL, ICE, LAVA_BUCKET, a melon slice, pumpkin seeds, sugar cane) and a barrel with the farming starter set, plus a pad of stone at the world origin so the first player to arrive does not fall into the void. range: 40 is how far from the centre an island may be built on, max-range: 120 is the ceiling the size upgrade can reach, and the grid is capped at half of itself whatever the range says - a range past half the grid reaches into a neighbour, which is why the module logs the number it actually used when the three disagree. The three numbers are a budget, not three preferences: radius + step * max should land at or below max-range, and the check says so when it does not.

On top of the platform: levels, scored as level = floor(sqrt(points / divisor)) with divisor: 100.0 as the one number that sets how fast a server's players climb - the scan looks height: 48 above the platform, walks up to max-blocks: 400000 blocks, counts a diamond block as 100 points and an emerald block as 150 (anything unlisted is default-value: 1), refreshes one island per pass every recount-seconds: 60 and lists the leading top: 10 at /island top. The feature list is the module's own summary of the rest: world, protection (containers, interactive blocks, explosions and mobs, with PvP off between people who do not share an island), visits, level, challenges, upgrades, bank (members deposit into it), vault (a chest the whole island shares) and oneblock. The last one reverses the start: an island is a single block at the centre that comes back as the next block on a weighted ladder each time it is mined, dropping from plains to forest to hills to caves to the nether and holding still at the last phase rather than wrapping - blocks-per-phase: 100 decides when a phase moves on, and a line that is not a placeable block (a seed, an egg) is skipped, because what gets placed has to be a block.

Turning it on. modules.skyblock.enabled: true, and it ships on because a template that installs it wants islands. Nothing generates at boot: the world is created on the first use - the first /island create, go or visit, or an owner rejoining - so a template that installs the core for something else never gets an island dimension it will not use. prepare-world: false is that trade written down: bringing a world up is a synchronous call on the server thread with no timeout, measured at 1s on a quiet box and 12s while three other servers were booting (past Paper's 60s watchdog on a busy one), so left false the cost lands inside the first /island command and a player sees a frozen server, and set true it lands in the first start and the operator sees a slow one. The skyblock profile sets it true; this file, which is the documented default, has it off. auto-create: true gives a player who joins with no island one and puts them on it - the whole onboarding of a skyblock server - and teleport-on-join is the half that moves them.

What you can configure. skyblock.world: islands, skyblock.grid: 400, the island block (radius: 8, range: 40, max-range: 120, y: 100, floor: GRASS_BLOCK, pad: STONE, a tree of log/leaves/height, the chest and barrel starter lists as MATERIAL:count lines, and clear-on-delete: true), the oneblock block (blocks-per-phase: 100 and the phases ladder of MATERIAL:weight lines), max-members: 6, max-islands: 5000, spiral-limit: 48 (how far the search for a free cell goes), auto-create, teleport-on-join, prepare-world, invite-minutes: 30, members-can-invite: true, pvp: false, the four switches under protect, and the levels block (divisor: 100.0, height: 48, max-blocks: 400000, default-value: 1, top: 10, recount-seconds: 60, values).

Commands. /island (aliases /is, /skyblock, /sb) - create, go, visit, level, challenges, upgrade and the rest, per the module's own help and /island check - and /islandadmin (aliases /isadmin, /sbadmin) behind ppcore.skyblock.admin for islands, levels and ranges.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/skyblock.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help skyblock prints the same names and the same nodes, line for line, because both read this one registration.

/island (/is, /skyblock, /sb)

permission: none of its own · always · Skyblock.java

Your island, and everything about it behind one command. /island create makes one in the grid the config describes (skyblock.grid, skyblock.island.*), /island go (or home/tp) takes you there, /island sethome moves its spawn point, /island coords says where it is, and /island info, /island list, /island near and /island top are the readings - the last of them being the island levels the level feature keeps. Who lives with you is invite (or add), uninvite, accept/join, reject/deny, kick, coop/uncoop and trust/untrust for the two softer kinds of helper, ban/unban for the one that cannot come back, setowner to hand the island over and leave to walk away from it; lock and unlock decide whether anybody may visit at all, and visit <player> is how somebody does. The inside of it is level/phase (where the oneblock is), challenges, upgrade/upgrades, bank with deposit and withdraw (the island's own balance, which is not the player's) and vault for its storage; rename names it and menu/gui opens all of it as a printed page. skyblock.auto-create builds an island on a player's first join rather than waiting to be asked, skyblock.max-members and skyblock.pvp and skyblock.teleport-on-join are the three settings an owner changes first, and each half of the module has its own feature switch (bank, challenges, level, oneblock, protection, upgrades, vault, world) for the servers that want some of it.

/islandadmin (/isadmin, /sbadmin)

permission: ppcore.skyblock.admin · always · Skyblock.java

Staff: the islands themselves and the grid they stand in. /islandadmin list prints every island, info one of them, tp <player> goes there, range and level set how big it is and how it scores, recount rebuilds the level table, setowner moves an island to another player, delete removes one and save and reload write and re-read the file. It is checked against ppcore.skyblock.admin, and it is where an operator goes when an island is in the wrong place or belongs to somebody who has gone - neither of which a player can fix from their own side.

plots - plots: a plot world with a road grid, claiming and sharing

What it does. A world divided into size: 32-block squares with a road: 7 wide between them, laid at y: 64 out of a floor of grass, a wall of stone and a road of gravel, in the PLAINS biome. On top of the grid is the ownership model: a player claims a square, shares it (trust), merges it with a neighbour, and sets per-plot flags from a registry the file owns - fifteen of them ship (pvp, explosion, mob-spawn, tnt, use, hostile, feed, heal, titles, weather, time, fly, gamemode, music), each with an enabled switch that takes a rule off the menu entirely and a default that decides what a plot which has not chosen gets. /plot set is the other half: biome, floor, wall, road, weather, time and fly, laid on the plot rather than the world, with set.max-blocks: 262144 as the ceiling on one command so a wide plot cannot turn one edit into a stall. /plotadmin calibrate re-lays a plot that already exists after a material or size change - a change here affects chunks generated after it, and that verb is what reaches the ones already on disk.

A road belongs to the world rather than to anybody, so protect.roads: true means only staff may build on one; an unclaimed plot is open by default (protect.unclaimed: false), because walking onto one and building is how a player decides they want it. How many plots a player may own is a permission question rather than a number: the highest ppcore.plots.amount.<n> they hold, else default-amount: 2, capped by max-amount: 100. The social half is a small review system - rate, comment, like, inbox and plot chat - with max-comments: 20 kept per plot and inbox: 10 shown. PlotSquared takes this job over when it is installed.

Turning it on. modules.plots.enabled: true, on by default for the same reason as skyblock. The feature switches are modules.plots.features.world, .protection, .merging, .content, .flags, .social, .gamemode (a plot may set the gamemode of the people on it) and .panel. Like skyblock, the world is created on first use rather than at boot: auto-create: true generates the default area's world on the first claim, and prepare-world: false decides whether that cost lands in the first command a player types or in the first server start (the creative profile sets it true). A hand-built plot world already on disk is loaded even with auto-create: false.

What you can configure. plots.default-area: plots, plots.world: plots, plots.size: 32, plots.road: 7, plots.y: 64, plots.biome: PLAINS, the three materials under plots.materials, plots.auto-create, plots.prepare-world, plots.teleport-on-join: false, plots.default-gamemode: SURVIVAL (what a plot's gamemode flag hands back on the way out), plots.default-amount: 2, plots.max-amount: 100, plots.max-areas: 8, plots.max-plots: 200000, plots.spiral-limit: 64, plots.near-range: 256, plots.panel-rows: 400, plots.protect.roads and plots.protect.unclaimed, plots.set.max-blocks: 262144, plots.social.max-comments and .inbox, the flags registry described above, and plots.messages.

Commands. /plot (aliases /p, /plots) - claim, auto, home, trust, flag, set, merge, the social verbs - and /plotadmin (aliases /padmin, /plotsadmin) behind ppcore.plots.admin for areas, worlds and the grid, with calibrate among its verbs.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/plots.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help plots prints the same names and the same nodes, line for line, because both read this one registration.

/plot (/p, /plots)

permission: none of its own · always · Plots.java

Your plot, and the whole of its life behind one command: /plot claim takes the plot you are standing in, /plot auto finds you a free one, /plot home returns to yours, /plot visit <player> and /plot tp travel, /plot list and /plot info print what you have and what is around it (/plot near for the neighbours). Who may build with you is the sharing half - /plot add <player> (or helper) for a builder, /plot trust, /plot deny for somebody you would rather keep out, /plot remove and /plot kick to take it back - and the plot's own settings are /plot set, /plot flag, /plot clear, /plot done (finish what you were doing), /plot merge and /plot unmerge (or unlink) for a bigger build across two plots, /plot setalias to name it, and /plot delete to give it up. /plot rate, /plot like, /plot comment, /plot review, /plot inbox and /plot chat are the social half, which is the only part that talks to other players: plots.social.max-comments is what stops a plot's comment wall becoming a second chat. plots.size, plots.default-amount and plots.max-plots decide how big a plot is and how many one player may own, plots.protect.* and plots.flags.* ship the defaults for what a plot forbids, and /plot area and /plot setup are the staff-side setup that the plotadmin command repeats.

/plotadmin (/padmin, /plotsadmin)

permission: ppcore.plots.admin · always · Plots.java

Staff: the areas, the worlds and the plot grid behind the player-facing command. /plotadmin list prints the areas and who owns what, info one plot, tp and range to look at one, level to set a plot's own level, recount to rebuild the counts, setowner to hand a plot over, delete to remove one and save and reload for the file. It is checked against ppcore.plots.admin, and it is where an operator goes when a player's claim does not look the way they described - the grid, not the player, is usually the thing that is wrong.

minigames - Hide and seek and tap the target: two rounds, one engine

What it does. Three games in one module, and they share an arena, a countdown and a scoreboard rather than an engine. Hide and seek is a hiding phase (30 seconds, seekers blind and held at the pen) followed by a seeking clock (300 seconds) in which a caught hider becomes a seeker, which is what ends a round; default-min-players: 3 and default-start-seekers: 1 decide when a round may start, default-catch-radius: 2.5 is how close a seeker has to be, and default-out-seconds: 8.0 puts a player out who leaves the arena for that long. countdown: 15 is the gather-and-count-down step (lobby in the feature list), celebration-seconds: 6 holds the result up before the arena resets, and the round is locked down: a player in one may only use the command roots under allow-commands, so a hider cannot /spawn out and a seeker cannot /fly in, while /hs itself always answers so /hs quit and /hs info keep working. /hs set edits what a new arena starts with.

Tap the target is the source plugin TappTheButton: a block lights up for default-tap-window: 4 seconds, a tap while it is up scores, it goes dark for default-tap-dark: 3 and comes back until the default-tap-seconds: 60 clock ends, and the most taps wins. Two details are worth knowing. The count is kept in tap-objective (ppminitaps) on the server's main scoreboard rather than on a sidebar, because a number on the main board survives a restart and /tapp score can answer it between rounds - what a player watches during a round is a private sidebar that exists only for that round. And the two states have to differ (tap-block: LIME_CONCRETE, tap-dark-block: GRAY_CONCRETE): a live target that looks like a dark one is a game that never tells a player when to tap, and /minigames check says so by name. default-tap-cooldown-ms: 500 is what an autoclicker hits rather than the game. Chat games (the FunGames half) need no arena: a question goes to the server, the first correct answer in chat wins, and three kinds ship - collect the word (letters shuffled), mathematics (evaluated, so 6 and 6.0 are one answer) and riddle. They run on a clock when auto-seconds is above 0, and on staff command with /chatgames start when it is 0.

The game selector is the menu the module was missing: every other way into a round is a typed command with an arena id in it. It ships off, and that is a decision with a cost on both sides - on, every joining player is handed an item, which is a change to everybody's world; off, a server that wants it sets one key. Everything under the block is dead while it is off, and /hs selector answers with the key to set rather than a menu. permission-spectate: ppcore.minigames.spectate is who may watch a round they are not in, and the default group is deliberately not given it. Behind feature switches there are more doors with their own commands (/easteregg, /leprechaun, /arcade and /casino, /boxmeter, /luckyblock, /slidingpuzzle, /repeatafterme), each registered only when its own game is on.

Turning it on. modules.minigames.enabled: true, on by default because a template that installs it wants a game players can start themselves. The feature switches are modules.minigames.features.hide, .tag, .quit, .lobby, .lockdown, .rewards, .stats, .bossbar and .selector. Arenas are marked in game and there is a ceiling of max-arenas: 16. HideAndSeek and FunGames take the job over when they are installed.

What you can configure. minigames.wand: GOLDEN_AXE, minigames.play-permission: ppcore.minigames.play, countdown: 15, celebration-seconds: 6, max-arenas: 16, the allow-commands list, and the defaults a new arena starts with: default-hide-seconds: 30, default-seek-seconds: 300, default-min-players: 3, default-start-seekers: 1, default-catch-radius: 2.5, default-out-seconds: 8.0, default-tap-seconds: 60, default-tap-window: 4, default-tap-dark: 3, default-tap-cooldown-ms: 500. Then tap-objective, tap-block, tap-dark-block, tap-board: true with tap-board-title and tap-board-lines ({time}, {window}, {taps}, {score}, {leader}), the messages.winner/.loser pair, the whole selector block (enabled: false, item: COMPASS, title, selector_item: PAPER, next: ARROW, rows: 3, permission-spectate), and the chat block (enabled: true, auto-seconds: 0, time-seconds: 60, cooldown-seconds: 300, min-online: 0, worlds: [], sound: true, plus the question lists).

Commands. /hs (aliases /hideandseek, /hns) to play - join, quit, info, top, list, set, target, selector - /tapp (aliases /tap, /tappthebutton) to play the tap game and /tappadmin (also /tappgiveaway) behind ppcore.minigames.admin, /chatgames (aliases /chatgame, /fgchat) for the chat games, and the gated doors listed above.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/minigames.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help minigames prints the same names and the same nodes, line for line, because both read this one registration.

/hs (/hideandseek, /hns)

permission: none of its own · always · Minigames.java

Hide and seek: /hs join puts you in the queue for the next run and /hs quit takes you out, /hs list prints the arenas and what state each is in, /hs info one of them, /hs stats [player] and /hs top the scores, and /hs selector (or menu) opens the arena picker. The staff verbs are the same command: create <name>, delete, wand, pos1/pos2, region <name>, spawn and pen for where the game starts and where the seekers wait, target <arena>, set <name> <key> <value> for the arena's own timings, reward <name> add|clear <command>, enable/disable, start, stop and reload. The defaults those settings override are minigames.countdown, minigames.default-hide-seconds, default-seek-seconds, default-catch-radius, default-min-players, default-start-seekers, default-out-seconds, and minigames.play-permission is the node that decides who may join at all.

/tapp (/tap, /tappthebutton)

permission: none of its own · only while modules.minigames.features.tap is on · Minigames.java

Tap the target: a target appears and the living player who taps it first scores. /tapp join, /tapp quit, /tapp list, /tapp info, /tapp score [player] and /tapp top are the player's half, and the verbs behind them are the same ones /hs uses - a second command here is a second name, never a second game.

/tappadmin (/tappgiveaway)

permission: ppcore.minigames.admin · only while modules.minigames.features.tap is on · Minigames.java

Tap the target for staff: target, kind, start, stop, info and list - which target is up, what kind of round it is, and starting or stopping one now instead of waiting for the timer. It is checked against ppcore.minigames.admin, and it exists as its own name because the source plugin has one.

/chatgames (/chatgame, /fgchat)

permission: none of its own · only while modules.chat.features.minigames.chat.enabled is on · Minigames.java

The chat games: a word, a sum or a riddle in chat and the first player to answer wins. /chatgames start [word|sum|riddle] runs one now, stop (or end) ends it, list, info and top are the readings, and /chatgames with nothing after it prints the help page. modules.minigames.chat.enabled is the switch that decides whether the suite runs at all - a server with a busy chat may well want it off.

/easteregg (/easter, /egg, /ee, /funeasteregg)

permission: none of its own · always · Minigames.java

The easter egg hunt: /easteregg prints the help page, list the spawn points, the eggs currently out and the scores, and check everything about the hunt that is inert, by name - the reading to run after editing, because a hunt with no eggs out looks exactly like a hunt nobody has found yet. trigger starts (or restarts) the hunt now, stop ends it and lifts every egg, and the staff verbs are add [id] (mark the block you are standing on as a spawn point), remove <id>, and additem <1-3> <chance> to add the item in your hand to one place's rewards.

/leprechaun (/lepre)

permission: none of its own · always · Minigames.java

The leprechaun hunt, the same machine as the easter egg hunt with a different occasion: list the spawn points and the hunt's state, check what is inert, and the same add, remove, additem, trigger and stop verbs for staff. It is registered only while its own switch is on, so a server that wants one hunt and not the other says so in the config rather than in what players are told.

/arcade (/machines)

permission: ppcore.minigames.admin · always · Minigames.java

The arcade, for staff: list the machines, info one, install <id> <size> [category] to place a game, remove, payouts and payout for what a machine pays and from which pool, play to start one yourself while building it, and help. It is checked against ppcore.minigames.admin because a machine that pays money is a shop, not a toy - and the per-game verbs players use (/luckyblock give <type>, /casino list, /slidingpuzzle list, /repeatafterme list) are the game's own command rather than this one.

/casino (/fgcasino)

permission: none of its own · always · Minigames.java

The casino: right-click the machine to play, and /casino list to see the machines that are installed. The game itself is played with the block, not with a command, which is why this command carries so little

/boxmeter (/fgboxmeter)

permission: none of its own · always · Minigames.java

The box meter: right-click the machine to start it, right-click again to stop. There is nothing to type once it is installed - /boxmeter list is available for finding the machines, and what it pays is set where every arcade game's payouts are set.

/luckyblock (/fgluckyblock)

permission: none of its own · always · Minigames.java

Lucky blocks: /luckyblock give <type> [amount] [player] hands out a block, /luckyblock list (or types) prints the kinds this server defines with what each one can drop, and the rest of the game is a player breaking the block they were given. The drops and their chances are the machine's own table, which /arcade reads back.

/slidingpuzzle (/funsliding, /spuzzle)

permission: none of its own · always · Minigames.java

Sliding puzzles: /slidingpuzzle list prints the puzzle machines, categories the categories a puzzle can be picked from, and install <id> <size> [category] places one. Playing is by clicking the machine - image puzzles are played in the window, not in chat - and the size and category decide which puzzle a player gets.

/repeatafterme (/ram, /funrepeat)

permission: none of its own · always · Minigames.java

Repeat after me: a sequence is shown and the player repeats it. /repeatafterme list prints the machines and install <id> places one; a round is started and answered on the machine itself, which is what makes it a game rather than a command a player can script.

bridge - network bridge: /hub, /server and network staff commands

What it does. This is the backend half of a network: a proxy (Velocity) in front, this jar on every server behind it, and one plugin channel (playport:bridge) between them. /hub asks the proxy to move the player to the server named in hub: hub, /server <name> lists the network and moves a player to a named server, and the proxy answers its own verbs (/hub, /server, /send, /sendall, /find, /glist, /alert, /staffchat) - when one of those arrives here instead, relay hands it back rather than answering it from the wrong place. That list matters because Velocity runs at most one command per player every 50ms and sends the over-limit ones to the backend, so a player typing /hub and /server in the same instant gets the second one dispatched here; passing it on is what keeps the backend's own /staffchat toggle from answering the network's /staffchat.

The direction that needs care is the other one: the proxy may ask this server to run a command on a player's behalf, and only the commands under forward are accepted - ban, kick, mute, warn, vanish as shipped, because a ban record is this server's. Permission is still checked twice: the proxy needs its own node to forward at all, and the command then runs as the player, so this server's node decides. Keeping that list short is the point: every entry is a verb a misconfigured proxy could send. heartbeat-seconds: 30 keeps the link warm and stale-seconds: 90 is when silence means the proxy is gone - after which /hub and /server say so instead of pretending to be linked. The perms feature is the last piece: the module tells the proxy which playport.proxy.<node> each player holds, read from this server's own groups, so a network can use the core's permissions instead of installing a permissions plugin on the proxy.

Turning it on. modules.bridge.enabled: true, and both ends are needed: the proxy half is the playport-bridge jar the panel installs on the proxy (build-proxy.sh builds it). On a server that is not behind a proxy the module is a no-op - /hub falls back to this server's own spawn and /server says so. The feature switches are modules.bridge.features.link, .hub, .server, .forward, .relay and .perms.

What you can configure. bridge.relay (the proxy's own verb names, cancelled here before dispatch and passed on), bridge.channel: "playport:bridge" (change it on both halves if another plugin has claimed the name), bridge.hub: hub, bridge.forward (the list above), bridge.heartbeat-seconds: 30 and bridge.stale-seconds: 90.

Commands. /hub (aliases /lobby, /l), /server (alias /srv), /serverlist (alias /netservers), /sendall <server> (which needs the proxy's own playport.proxy.staff node rather than a new backend one, because the proxy is what moves the player), and /network (aliases /net, /netinfo) behind ppcore.bridge.status to read the link, the server name and the heartbeat. The shipped hub feature is what makes /hub and /server exist at all.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/bridge.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help bridge prints the same names and the same nodes, line for line, because both read this one registration.

/hub (/lobby, /l)

permission: none of its own · always · Bridge.java

Go to the hub server of this network, which is the one command a lobby needs to be reachable: a portal, a sign or a printed button can all run it, and it takes you to bridge.hub by name and carries no arguments. On a server with no proxy behind it, /hub does not just refuse - it sends you to this server's own spawn if it has a /spawn to send you to, and says so, because "there is no network here" is a dead end for a player standing in a portal.

/server (/srv)

permission: none of its own · always · Bridge.java

List the network's servers, or move to one: /server on its own prints the servers the proxy knows about, /server <name> sends you to that one. The list itself lives on the proxy, so this verb asks it rather than keeping a second copy here - which is also why the names cannot be tab-completed when the link is down, and why nothing here is allowed to guess.

/serverlist (/netservers)

permission: none of its own · only while modules.bridge.features.server is on · Bridge.java

The same listing as its own command, for the servers and signs that want to show the network rather than move people through it: it always answers with the list, whatever is typed after it, where /server will move somebody. It needs the same link, and on a server that is not part of a network it says so instead of printing an empty list.

/sendall

permission: playport.proxy.staff · only while modules.bridge.features.server is on · Bridge.java

Move every player on the network to one server: /sendall <server>, checked against the proxy's own playport.proxy.staff. The proxy is what owns this verb - it is the only side that knows who is connected - so this command asks it to run for the player who typed it, and the proxy's own answer lands in that player's chat. It is the emergency verb (a server going down, a hub that needs emptying) rather than a transport.

/network (/net, /netinfo)

permission: ppcore.bridge.status · always · Bridge.java

The state of the link, for an operator who needs to know whether the network half is working at all: whether this server is connected to a proxy and under what name, where /hub points, which channel the link uses (bridge.channel), and the counters - how many commands the proxy has asked this server to run, how many were refused, and how many were relayed back. Outside the console it prints the readable half of the same thing; the counters are the only way to answer "did that actually run here" from the outside, which is why they are printed with the link state rather than kept in a log.

quests - quests: goals, gates and rewards earned by playing

What it does. A quest is three things written down - a goal (do this much of this), a gate (what you must already have before it starts counting) and a reward (what it pays) - and the module holds no quests of its own, so a new one is written rather than built. The goal vocabulary is the whole template, and a type that is not on the list is refused at load with the list printed, because a misspelled type would count nothing while looking like it works. Ten types are counted from events (break, place, kill, playerkill, craft, smelt, fish, harvest, sell, distance) and six are read from counters the server already keeps (blocks, minutes, rank, spawner-level, money, heads). target names the material or entity, is blank or any for anything, and starts with ! to mean everything except that; blocks is deliberately the same counter the rank ladder uses, so a block goal and a rank cannot disagree. A gate is where a quest reads its neighbours - spawner-level is the spawners module's level and rank/rank-name is a rung on the ranks ladder - and a reward can hand a level back or hand out a capability: a name in a set, deliberately not a permission node, because the point is that this one came from playing (/sellall is the capability the shop asks about).

The library is the other half: hundreds of ready-made quests shipped in the jar and taken by the pack. A pack is a theme's worth of quests that chain into one another, and sixteen ship on - starter for the first hour, ladders for mining, farming, combat, fishing, building, crafting, economy, exploration, dungeons and endgame, plus spawner-ladder (one quest per level), skyblock, factions and raidsteal (this core's own server, selling heads against the level ladder). Two rules make the library safe to take: a pack only fills ids nothing above it defined, so your own quest - or one the panel's editor wrote - always wins and survives a jar upgrade, and take: stops a pack after its first N rungs, which is one entry per spawner level for take: 5 on spawner-ladder. The library is checked against this server's own material and entity lists when the jar is built, so a pack can never name an item the server has not got - and a pack that says it is on while loading nothing is the failure to look for, which is why /quests library and the panel's Quests page print the real loaded count beside the switch.

Turning it on. modules.quests.enabled: true, and it ships on with the library on. The feature switches are modules.quests.features.board (the /quests view) and .panel (the panel's quests editor). announce: true tells the player when a quest completes; check-seconds: 5 is how often the goals that read existing counters are polled; the ten event types need no clock at all.

What you can configure. quests.announce: true, quests.check-seconds: 5, the four sentences under quests.messages (header, done, noQuest, noPerm), quests.library.enabled with a per-pack line under library.packs and a per-pack rung limit under library.take, and the quests themselves under quests.defs.<id> - name, desc, goal (type, target, amount), an optional gate, optional requires (ids that must be finished first) and a reward (money, xp, a spawner-level, a capability, or an action list). Quest ids are what the panel, /quests <id> and requires name, so they are lowercase and never renamed without re-pointing everything that names them.

Commands. /quests (aliases /quest, /q) shows what is done, what is open and what it pays; /quests <id> reads one; /quests library prints what each pack really loaded; /quests check is the module's own config report. The panel's Quests editor writes quests.defs.<id> into panel-quests.yml, which the plugin merges within a minute.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/quests.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help quests prints the same names and the same nodes, line for line, because both read this one registration.

/quests (/quest, /q)

permission: none of its own · only while modules.quests.features.board is on · Quests.java

What you have done, what is open, and what it pays. /quests on its own opens the board - every quest with its goal and its progress - and /quests list (or all) is the same board as a chat listing for a player who would rather read it there. /quests <id> prints one quest in detail, which is also how a quest is explained in a GUI's own click. /quests library [pack] is the part an owner reads first: it names every shipped pack, whether it is switched on, and how many of the pack's quests are actually in this server's config - the honest number, because a pack can be on and still contribute fewer than it holds when an owner wrote their own quest under one of its ids, or when quests.library.take stops it after the first few. /quests admin <list|grant|finish|reset> <player> <value> is the staff door behind ppcore.quest.admin: grant a quest, mark one finished, or wipe a player's progress, which is what an operator needs after a bug took a reward away rather than what a player ever asks for. The board has its own switches (modules.quests.features.board and modules.quests.features.panel) for the servers that would rather read a quest on a printed page than in chat.

tools - tools that level with use: stat points, abilities and skins

What it does. A pickaxe, axe, shovel, hoe, sword or bow earns experience as it is used - xp.block: 1.0 for a block broken with it, ore: 4.0 for an ore (because that is the block worth mining) and mob: 12.0 for a kill with it in hand - and pays out levels on a curve of base: 100 for level 1 to 2 plus step: 50 more each level, to levels.max: 30 for a material with no rule of its own. Each material class has its own ceiling and rate, keyed by the material name up to its first underscore: wooden 6 levels at half xp, stone 10 at 0.75, iron 18 at 1.0, gold 12 at 1.5 (fast and fragile - it levels quickly and caps early), diamond 25 and netherite 30. A level hands over points-per-level: 1 stat point, and the four stats are what it buys: fortune (5 points per extra drop, ceiling 15 = three extra), speed (5 points per Haste level, ceiling 20, capped at Haste III whatever the points say), durability (each point banks 2 breaks that cost none, ceiling 20) and radius (10 points per block of area reach, ceiling 20).

Abilities unlock by level rather than by purchase: area at level 5 (a 3x3 by default, up to 5x5 with the radius stat, for pickaxes and shovels - an axe is a tree feller and harvest already ships that), smelt at level 10 (a broken ore yields what the tools.smelt table says a furnace would, so iron ore comes out as an ingot), and replant at level 3 (a harvested crop goes straight back in, and it costs a seed, because conjuring crops is not farming). Skins are the cosmetic half: a skin has a name (&7Ironclad %tool%), a custom model data number for a resource pack that has one, and the level that unlocks it (ironclad 4, emberforged 12, voidtouched 20, plus the generated block shown in the file as presets). name-format is what a levelled tool is called (&b%tool% &7[&fLv%level%&7]), and a skin's own name replaces it while it is applied. Every level is earned by use and nothing here can be bought: the only money in the module is /tool repair and /tool reroll.

Turning it on. modules.tools.enabled: true, on by default. modules.tools.features.bind is the one worth reading twice: with it on, the first player to use a tool keeps it - it earns for them and for nobody else, and its points are theirs to spend - because without it a level-30 pickaxe is tradeable work. .area, .smelt and .notify gate the two abilities and the level-up line.

What you can configure. tools.levels.max: 30, .base: 100, .step: 50, tools.points-per-level: 1, tools.xp.block/.ore/.mob (spelled as doubles on purpose - the module reads them with getDouble, and the panel types a key from this spelling, so an integer here is a value the form cannot take a fraction of), tools.name-format, one rule per material class under tools.tiers (max-level, xp-multiplier), the four stat blocks under tools.stats (fortune, speed, durability, radius, each with its own point ratio and ceiling), tools.abilities.area (level: 5, max-radius: 2, groups: [PICKAXE, SHOVEL]), .smelt.level: 10, .replant (level: 3, seeds: true), the tools.smelt block-to-ingot table, and one entry per skin under tools.skins.<id> (name, model, level).

Commands. /tool (aliases /tools, /tinkertools) behind ppcore.tools.use - info, upgrade (stat points), skin, repair, reroll, and /tool check for the module's own config report.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/tools.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help tools prints the same names and the same nodes, line for line, because both read this one registration.

/tool (/tools, /tinkertools)

permission: ppcore.tools.use · always · Tools.java

Tools that level with use - a pickaxe that gets faster because you mined with it. With a tool in your hand, /tool info prints its level, its points and what its next level costs, /tool stats (or abilities) prints the ladders a level can be spent on, /tool upgrade <stat> spends points on one of them, /tool skin <id> changes what the tool looks like, /tool repair pays to put its durability back (tools.repair.price-per-durability) and /tool reroll pays to re-roll what it carries (tools.reroll.price). Levels come from playing rather than from paying: tools.xp.block, tools.xp.ore and tools.xp.mob are what each kind of action is worth, tools.levels.* sets the curve and tools.points-per-level how much a level is worth spending. Which abilities a tool may have is the tools.abilities.* block - area mining, smelting and auto-replant are the three the shipped file configures - and tools.respect-claims is what stops an area ability from reaching into somebody else's land. /tool admin is the staff side behind ppcore.tools.admin, and ppcore.tools.use is the node the whole command asks for.

dungeons - blueprints: mark a region with a wand, save it, stamp it

What it does. A blueprint is a region of a world saved as a file: mark two corners with the wand (wand-material: BLAZE_ROD, named &bBlueprint wand; left click is corner 1, right click corner 2), /blueprint copy captures what is between them and /blueprint paste stamps it somewhere else. What copy carries is part of the contract rather than a bonus - blocks, container contents (containers), spawner settings (spawners - the mob, the delay and the ranges, so a pasted spawner actually spawns), the mobs standing in the region (entities) and the doors the blueprint carries (triggers). max-volume: 250000 is the ceiling on one copy or paste, blocks-per-tick: 20000 slices a large paste across ticks rather than freezing the server for one, paste-cooldown-seconds: 2 is the shortest gap between two pastes by one player, and respect-claims: true means a paste asks the same claims question a placed block asks - a blueprint stamped into somebody's base is a break-in. While the wand is in hand, wand-actionbar: true gives one line saying what it is waiting for (which corner is missing, how big the marked region is, what is on the clipboard) and show-edges draws the region in particles, within edge-max-points per player.

/blueprint generate builds a dungeon from a theme rather than a saved file, saves it as an ordinary blueprint and pastes it - and the library behind it ships sixteen packs (sixty-odd themes, about a hundred and twenty dungeons with their waves, loot and voices), tiers 1-3 on and the deeper packs (nether, end, ocean and three tier-5 packs) one word away, because a dungeon that hands out netherite is a decision an owner should make rather than inherit. The library fills only ids nothing above it defined, so your own crypt keeps your own edits across a jar upgrade. Runs are the second half and a feature of their own: a run stamps a dungeon into a world of its own (world-prefix: dg-), puts a party in it and takes the world away when they are done - maximum-simultaneous: 1 per dungeon, past which a run is queued and told so rather than failing, protect: true so only the party may build in a run's world, backups: 1 copies the world before it is deleted (and a copy that fails stops the delete), reclaim-on-boot cleans up a world a restart left behind, and empty-seconds: 300 ends a run nobody is in. An entrance is where an operator stood and typed /dungeon entrance <name> and a run starts when a player walks within entrance-radius: 1.5 of it - it is optional, because /dungeon run <name> works from anywhere, which is what an NPC's action ends up calling. Parties are deliberately the opposite of teams: made by an invite, alive only while the run they asked for is, and written nowhere, because a party that survived a restart would be a party whose members are no longer waiting for anything.

Turning it on. modules.dungeons.enabled: true, and every concern has its own feature switch: wand, copy, save, paste, containers, spawners, entities, generate, triggers, runs, entrances, parties, waves, loot, messages, map and panel. Each says what stops working when it is off - save off means dungeons.folder is inert and the clipboard is memory only; waves off means a run is the walk-through with no mobs in it - and /blueprint check names the inert ones rather than leaving you to notice. Storage is dungeons.folder: blueprints, one YAML file per blueprint under the plugin's data folder. Nothing shipped is a dungeon server, so this is an owner's deliberate choice rather than a template's.

What you can configure. dungeons.folder: blueprints, max-volume: 250000, blocks-per-tick: 20000, paste-cooldown-seconds: 2, respect-claims: true, wand-material, wand-name, wand-actionbar: true, wand-reach: 20.0, show-edges: true, edge-particle: END_ROD, edge-interval-ticks: 20, edge-max-points: 120, trigger-interval-ticks: 5, trigger-max: 200, the parties block (enabled: true, max-size: 4, invite-seconds: 120, ready-required: true, password-required: false, disband-when-run-ends: true), the runs block (maximum-simultaneous: 1, world-prefix: dg-, protect, finish-on-leave, backups: 1, reclaim-on-boot, prepare-world: false, interval-ticks: 20, entrance-radius: 1.5, empty-seconds: 300), and the library block that decides which packs are taken. A dungeon itself is one entry under dungeons.dungeons: theme (generated at the run's seed) or blueprint (a saved file - set one, not both), seed (0 means a new dungeon every run), origin, members, portal, lives, lives-sharing, respawn-at-checkpoint, the door keys, waves and loot.

Commands. /blueprint (aliases /bp, /dungeons, /dungeonmaker, /dungeon) behind ppcore.dungeons.use for the region tool, the copy/paste verbs, generate, theme, the door wiring and import/export for the panel page; /dungeon run <name>, /dungeon entrance <name> and /dungeon end for the run half; and /party (alias /dungeonparty) behind ppcore.dungeons.party - create, invite, join, ready, start, status, disband, select, leader, password. The panel's Dungeons page edits dungeons.dungeons, dungeons.themes, dungeons.loot, dungeons.waves and dungeons.messages into panel-dungeons.yml, and lists the blueprints on disk instead of inventing one - a blueprint is a region of a world, made with the wand in game.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/dungeons.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help dungeons prints the same names and the same nodes, line for line, because both read this one registration.

/blueprint (/bp, /dungeons, /dungeonmaker, /dungeon)

permission: ppcore.dungeons.use · always · Dungeons.java

The whole dungeons module behind one command - a region tool, a themed generator and the runs players actually enter - with /blueprint help as the page to start from. Building one is the selection half: wand (a golden axe by default, dungeons.wand-material), pos1 and pos2 for the corners, sel to see what you have selected, then copy to take it, save to keep it under a name, open to load one back, paste to put it down and diff to compare two. generate (or gen) is the half that makes a dungeon instead of copying one: a theme from dungeons.themes plus a seed produces rooms, and preview/draw shows the result before it is pasted, at the size the theme's own settings describe. theme(s) lists what is configured, edges draws the region's border so an operator can see what they selected, and info, map, hud, ascii and panel are the readings - panel opens the authoring screen a browser can write back to. Playing what you built is the other register: run (or enter), join, leave, runs/queue for who is waiting, state and checkpoint for a run in progress, loot, wave(s), messages, var for its variables, trigger(s) for what fires in it, entrance, dungeon for one dungeon in full and end to finish. delete, clear, import, export and give are the management verbs, and the modules.dungeons.features.* switches (containers, copy, entities, generate, panel, paste, save, spawners, triggers, wand) turn off one function at a time - which is what an owner does instead of switching the module off because one behaviour is not wanted.

/party (/dungeonparty)

permission: ppcore.dungeons.party · always · Dungeons.java

A dungeon party: the group a run is entered with. /party create starts one, invite asks somebody, join accepts, and start (or ready) is the moment everybody has said they are ready - a five-player dungeon is a coordination game, and the party is what makes the coordination part of the plugin rather than of the operator's chat. /party list and /party info show who is in it, kick and leave are the two ways out, and disband ends it. It is a separate command from /blueprint on purpose: that is the builder's surface, this is the player's, and the two carry different nodes (ppcore.dungeons.party against ppcore.dungeons.use).

ui - operator-authored menus: pages, buttons and actions

What it does. A page is a menu written as config: a title, a row count, and a slots.<n> map of buttons, each with an icon and a list of actions. /ui opens one by name, a sign can name one, and an action list elsewhere can too. Four kinds of page ship behind feature switches: a chest window, a layout board, a hud strip on the action bar drawn from elements placed in pixels and redrawn while refresh: is set, and a sidebar - a title and up to fifteen lines beside the player, where a line may carry its own permission node and hide, so one line can be staff-only. The consequence of sidebar is worth knowing before switching it on: showing one takes the player's scoreboard over, which is why Factions' own sidebar yields to it - two sidebars on one player is not two features, it is the second one winning at random. Every page can be drawn from artwork instead of an item: a PNG in ui.art.folder is published into the resource pack this plugin already builds for models, given a codepoint in the private use area, and drawn by sending that codepoint. /ui art list prints every picture, its codepoint and its size; /ui art reload re-reads the folder and rebuilds the pack (a restart does the same, and /model build publishes the models as well). Codepoints are written down in art-ids.yml beside the config and never reassigned, so five pictures installed today and a sixth added next week leave the first five exactly where they were; a picture whose file is deleted keeps its reserved glyph, and a broken file claims nothing rather than squatting on a number.

One page, one file (ui.folder, default pages) is the other half of the design: ui.pages is a set of pages a server edits together - right for the menus that ship with the plugin - while a file is the thing an operator actually asks for, one page handed to somebody or put under version control. A file with the same id as a config page wins, and the three verbs around it are honest about what they carry: /ui export <page> [file] says how many fields it wrote, /ui import <file> [as] names every key it did not carry and every value it had to change to load the page, and /ui diff <a> <b> walks two loaded pages field by field - so "a page round-trips without losing a field" is a measurement rather than a promise. A page file is also read at boot, which is why /ui check names a broken one: otherwise it is simply a page that is not there. debugClicks: true logs every click with the page, the slot and what happened, because a page that does nothing has three identical-looking causes from outside (no button at that slot, a permission refused, an action that ran and showed nothing). The module also loads the source plugin's own dialog documents (Dialogs/*.yml), so a server that already has those screens gets them here rather than retyping them. What nothing in it can do is grant something: a command: action runs as the clicker, so a page cannot give what the clicker's own permissions do not already allow.

Turning it on. modules.ui.enabled: true, on by default. The feature switches are modules.ui.features.sounds, .panel (the menu editor in the panel), .board (layout pages), .art (the artwork folder), .hud (action-bar strips), .sidebar (scoreboard pages) and .files (the page folder and the three file verbs). Each one says what stops working when it is off - with art off the folder is not read and /ui art list names no artwork - and /ui check reports per page.

What you can configure. ui.default-page: main, ui.debugClicks: false, ui.click-sound: UI_BUTTON_CLICK, ui.folder: pages, the art block (enabled: true, folder: art, the import list for pictures drawn at a pack's own codepoint, the adopt list for pictures copied into this plugin's pack and given a number of its own - adopting never writes over a picture already in art/), and the pages themselves under ui.pages.<id> (title, rows, kind, refresh, slots.<n> with a button's icon, permission node and action list).

Commands. /ui (aliases /uis, /menu) with open <page> [player]; /ui list, /ui reload, /ui check, /ui art list|reload, /ui hud <page>, /ui sidebar <page> [player] and /ui sidebar off, /ui export|import|diff for page files, and the dialog verbs for the source plugin's screens. The panel's Menus editor writes ui.pages.<id> into panel-uis.yml.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/ui.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help ui prints the same names and the same nodes, line for line, because both read this one registration.

/ui (/uis, /menu)

permission: none of its own · always · Ui.java

Open a menu page: /ui opens the default page, /ui <page> a named one, and /ui <page> <player> opens it on somebody else's screen (ppcore.ui.open.others or ppcore.ui.admin). open is the verb if you would rather spell it out. With a page in hand the other verbs are how an owner sees and tunes what they wrote: hud <page> draws a strip on screen now (refresh on the page is what keeps it up, and hud off takes it down - a HUD is drawn nowhere but a player's screen, so a command that shows it is the only way to read it back), sidebar the same for a scoreboard strip, art and dialogs for the two other things a page can be made of, list for the pages that exist, reload after an edit, and export, import and diff for moving a page between servers as a file. ui.default-page is what a bare /ui means, which matters because a menu reached from a sign is only useful if the name on the sign is the name that works, and modules.ui.features.panel is the switch that lets pages be written in browser rather than in the config file.

/ppui

permission: none of its own · always · Ui.java

The plumbing, not a command to type: it is the name a click event sends when a player presses a button on a printed board, and every click a page has runs through it. It is registered because a click must name something the client can send, and it is named after the module (rather than after one page) so the name is stable as pages come and go. It asks for no permission of its own on purpose - the page's own rules decide what a click is allowed to do, and a node here would be a second answer to a question the printed page already answers. ui.debugClicks writes what each click did into the console when you are working out why a button did nothing.

Files behind this page

What this section documentsSource
Chain mining, tree felling, the crop clock, quality, sprinklers and orestackpkg/src/ppcore/Harvest.java (../../pkg/src/ppcore/Harvest.java)
Carriages, the landing scan, combinations, land and /elevator createpkg/src/ppcore/Elevators.java (../../pkg/src/ppcore/Elevators.java)
Catches, rarity, the level gate, the market and the source plugin's five doorspkg/src/ppcore/Fishing.java (../../pkg/src/ppcore/Fishing.java)
The island world, levels, challenges, upgrades and the oneblock ladderpkg/src/ppcore/Skyblock.java (../../pkg/src/ppcore/Skyblock.java)
The plot grid, claiming, sharing, flags, /plot set and the areaspkg/src/ppcore/Plots.java (../../pkg/src/ppcore/Plots.java)
Hide and seek, tap the target, the chat games and the selectorpkg/src/ppcore/Minigames.java (../../pkg/src/ppcore/Minigames.java)
The channel, the heartbeat, relay and the forwarded staff verbspkg/src/ppcore/Bridge.java (../../pkg/src/ppcore/Bridge.java)
Goals, gates, rewards, the packs and the loaded-count reportpkg/src/ppcore/Quests.java (../../pkg/src/ppcore/Quests.java), pkg/src/ppcore/QuestLibrary.java (../../pkg/src/ppcore/QuestLibrary.java)
Levels, tiers, the four stats, the three abilities and the skinspkg/src/ppcore/Tools.java (../../pkg/src/ppcore/Tools.java)
Blueprints, the wand, the generator, runs, entrances, parties and the librarypkg/src/ppcore/Dungeons.java (../../pkg/src/ppcore/Dungeons.java), pkg/src/ppcore/DungeonGen.java (../../pkg/src/ppcore/DungeonGen.java), pkg/src/ppcore/DungeonRun.java (../../pkg/src/ppcore/DungeonRun.java), pkg/src/ppcore/DungeonParty.java (../../pkg/src/ppcore/DungeonParty.java), pkg/src/ppcore/DungeonLibrary.java (../../pkg/src/ppcore/DungeonLibrary.java)
Pages, buttons and actions, the four kinds, artwork and the page filespkg/src/ppcore/Ui.java (../../pkg/src/ppcore/Ui.java)
The shipped quest and dungeon librariespkg/library/quests.yml (../../pkg/library/quests.yml), pkg/library/dungeons.yml (../../pkg/library/dungeons.yml)
Every key quoted above, with its comments and shipped valuepkg/config.yml (../../pkg/config.yml)
The profiles that switch these modules on, and set prepare-worldpkg/profiles/ (../../pkg/profiles/)

The living world

These six modules are about everything around the player rather than the player: the worlds the plugin makes and pre-generates, the weather that lands on them, the calendar that turns them over, the site that pays a player for voting, the one gate through which anything is ever sent off the machine, and the snapshot staff ask for when somebody says "who is even on?".

Two of them are unusual and are worth reading even if you never switch them on. votes is the only module that opens a port, so it ships off and refuses to open one without a token. service is the only module that ever sends a request, so it ships off twice — the module and the master gate — and every call that does happen is named in the log with its destination.

The shared rules — the switch in the modules: block, an edit in config.yml beating the profile, and /playport reload — are in chapter 12; the panel's own screens are in chapter 07.

worldgen - World generation: the worlds this plugin owns are made from config

What it does. A world is a preset: a base height and a relief around it, how many blocks wide one landform is, a sea level (0 for no water), a biome for land and one under the water, a surface block with a depth of subsurface under it and a filler below that, optional bedrock for a floor, optional trees (log, leaves, a chance per column of land, a trunk height range) and optional ore veins (a material, tries per chunk, a vein size and the heights it may appear between). /worldgen create makes a world from one and asks Dimensions to own it — the module loads at STARTUP, so it defers that request to the server's own worlds being up rather than guessing.

The other half is pre-generation, and it exists for one reason: a player's first visit should not wait for terrain. A job runs on the tick, nearest-first, in one of nine shapes (square/rectangle, circle, ellipse, diamond, triangle, pentagon, hexagon, star) and one of five orders (region, concentric, spiral, loop, nearest), is capped on both axes and by a total chunk count, and resumes across a restart from the chunk it had reached. /pregen start <world> world covers every chunk inside the border. When a job finishes, the names in worldgen.pregen.actions run once each, in order, from the operator who started it — the replacement for the source plugin's owner-written generation hooks. /tree grows one tree where you are looking; what it writes is terrain, which is why it lives here.

Turning it on. modules.worldgen.enabled: true, and it is on by default because it claims nothing until a world names it: a server that never runs /worldgen create pays for a command registration and one claim on Dimensions. End to end: /worldgen create <name> [preset] (no preset means plains), then optionally /pregen start <name> <radius>.

What you can configure. worldgen.presets.<name> — flat, plains and islands ship, and every key is optional per preset (a preset that only names a biome still produces a world, on the plains shape) — with base, relief, scale, sea-level, biome, water-biome, environment, surface, underwater, subsurface, depth, filler, bedrock, trees.type (none plants nothing) and ores[]. Under worldgen.pregen: per-tick: 4 (chunks one tick may generate), max-radius: 32 (the ceiling a radius request is accepted up to), max-chunks: 200000 (the ceiling for the two areas that are not radii — a typed selection and a world border), shape: square, pattern: concentric, resume: true and actions: []. worldgen.panel: true offers the panel's world view, which writes panel-worlds.yml and is applied on the same minute tick as the other editors.

Commands. /worldgen (alias /wgen) and /pregen (alias /chunky), both under ppcore.worldgen.admin, plus /tree (alias /growtree) under the same node. /dimensions regen — the verb that re-makes land already on disk — is the core's own, in chapter 08.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/worldgen.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help worldgen prints the same names and the same nodes, line for line, because both read this one registration.

/worldgen (/wgen)

permission: ppcore.worldgen.admin · always · Worldgen.java

The worlds this plugin makes and keeps, and the generator behind them. /worldgen with nothing after it, or /worldgen list (/worldgen presets), prints the presets the file defines - each id with its base height, relief, sea level, biome, whether it grows trees, and how many ores it places - followed by the worlds this plugin owns. /worldgen info <world> answers with that world's preset, seed, sea level, spawn and what is on disk, which is the first thing to read when a regeneration did not produce what was expected: the recorded seed is what a regeneration uses, and it is printed rather than inferred. /worldgen height <world> <x> <z> asks the generator what it would put at one column without generating anything - the way to check a preset before making a world out of it.

/worldgen create <world> <preset> [seed] makes the world and loads it, from a preset the file names and a seed you give; with no seed one is chosen and written down, so the world that exists is the world the file describes and a later regeneration builds the same ground. A world is deleted or regenerated with /dimensions regen <world> confirm rather than from here - creating is the part this module owns. /worldgen reload re-reads worldgen.yml and says so; /worldgen help prints the verb list.

/worldgen pregen <world> [area] is the same job as /pregen start under this command's own name, and /worldgen stop stops whatever is running (keeping where it got to, so /pregen start continues it). Both exist on purpose: an operator who has run the source plugin on another server reaches for its own spelling, and a command nobody can guess is a feature nobody has.

/pregen (/chunky)

permission: ppcore.worldgen.admin · always · Worldgen.java

Pre-generation: making the ground before players walk into it, so the first visitor does not pay for the chunks. /pregen on its own, or /pregen status (info, list), prints the rate it runs at, the radius and job-size ceilings, whether a restart resumes a job, then - for every world this plugin owns - whether it has ever been pre-generated, where a job got to, the shape and order it was walking, and how long ago it last wrote. A world it has never touched names the command that starts one.

/pregen start <world> [area] [shape] [order] begins or continues a job. The area is a radius (8), a rectangle (8x12), a selection (@x1,z1,x2,z2), or the word for every chunk inside the world's border; continue and resume are synonyms of start, because continuing a job a restart interrupted is the question this command exists to answer and it should not need a different verb. A job that was in flight when the server stopped is picked up on the next boot; a job you stopped waits for start. /pregen shape and /pregen pattern read, or set, what this world's next job will use - pattern is the same verb spelled as the order the cells are made in. Both are stored on the world's own record rather than in the config file, because they are part of what a resume rebuilds: change the order and the index on file no longer means the same thing, which each of the two verbs says out loud.

/pregen order <world> [area] [shape] [order] answers what a job would generate before any of it is generated: the cell count, the first sixteen cells in the order they will be made, the last one, how many region files they touch, and the files in the order they are touched. A plan past worldgen.pregen.max-chunks is refused with its own number and the key rather than started, because a job that size would read as one that is fine to start. /pregen stop (cancel) stops the job and keeps its place; /pregen help prints the verbs, the shapes and the orders. The rate, the ceilings and whether a restart resumes are all under worldgen.pregen in config.yml.

/tree (/growtree)

permission: ppcore.worldgen.admin · always · Worldgen.java

Grows one tree, where you are looking - a builder's verb rather than a world generator. /tree with no argument lists the names it accepts: the species a person types first (oak, bigoak, spruce, tallspruce, megaspruce, birch, tallbirch, jungle, acacia, darkoak, mangrove, cherry, azalea, crimson, warped, swamp, and the two mushrooms), then every name the server's own tree list has, with no filter - the question "is this a tree" does not exist in the API, and the fungi in that list are things it really does grow. /tree <type> grows the named one in the block you are looking at, or in the block you are standing in when you are looking at nothing; the block has to be free and on something a tree can grow on, and the answer says so when it does not fit. A name it does not know is answered with the list rather than growing something arbitrary, and the reply names the tree by the word you typed rather than by the server's own spelling of it, because those two are not the same word: a plain oak is TREE in the server's list, and a spruce is REDWOOD.

disasters - world events: storms, quakes, meteors and tornadoes

What it does. An event is a definition in config with a type, a radius, a warning countdown, a duration, one damage number, whether it may break blocks and which ones, how often it starts on its own, which worlds it may land in, and a money/xp reward for whoever is inside when it lands and still inside when it stops. The four types are the source plugin's four: storm, quake, meteor and tornado — anything else lands nothing and is named by /playport check disasters. A tornado's centre walks; the other three use the fixed centre they were given.

Damage is only the number in the file: a quake's cracks and a storm's bolts are this module's own, so what an operator writes is what a player feels. Two limits exist because an event is a cost: a radius ceiling (every second, every living thing inside is enumerated — a typo like radius: 2400 is a stall, so numbers over the ceiling are swept at the ceiling and the check says so) and a cap on how many may be in flight at once. Claims, factions and their zones refuse every block an event would break, and a player-started event asks on that player's behalf — so staff cannot level a base they do not own, unless respect-claims is turned off, which is weather on somebody else's build and never by accident. The schedule counts its gap from boot rather than from the first tick, so a restart does not fire one of every kind the moment somebody logs in.

Turning it on. modules.disasters.enabled: true ships on, because it claims nothing until it runs: disasters.schedule.enabled decides whether anything starts by itself, and /disasteradmin start runs one by hand whatever the schedule says. Its four feature switches are events (the schedule and the sweep — off, only a manual start works), blocks (events may break blocks at all, and the claim question is asked per block), warning (the on-screen countdown as well as the chat line) and rewards (pay whoever survives inside the area).

What you can configure. disasters.schedule.enabled: true, schedule.world: world (where an event lands when it names no worlds), schedule.every-seconds: 1800 (0 is by hand only); limits.max-live: 2, limits.min-players: 1, limits.max-radius: 96.0, limits.respect-claims: true; history.max: 40 (events kept for /disaster history); every player-facing line under disasters.messages.*; and one entry per event under disasters.events.<name> with type, title, radius, warning-seconds, duration-seconds, damage, destroy-blocks, break (left out, the default is natural ground — stone, dirt, grass, sand, leaves, logs — and putting a wall material in the list is a decision an operator should make on purpose), every-seconds, worlds and reward.money/reward.xp. The shipped file defines all four with their own numbers.

Commands. /disaster (aliases /disasters, /storm, /quake) lists what is happening and /disaster history says what has happened; /disasteradmin (alias /da) starts, stops and schedules them by hand under ppcore.disasters.admin.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/disasters.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help disasters prints the same names and the same nodes, line for line, because both read this one registration.

/disaster (/disasters, /storm, /quake)

permission: none of its own · always · Disasters.java

World events, from the player's side, and the report an operator reads before starting one. Typed with nothing after it - or as /disaster here, /disaster now - it answers what is in flight: each event's name, whether it is still warning or already sweeping, how many seconds are left, the world and coordinates it is centred on, and, if you are in the same world, how many blocks away it is. That is the whole point of a warning countdown: knowing where not to stand, which is why this verb is open to everybody while starting one is not, and why it says The world is quiet. rather than nothing at all.

/disaster list prints the events the file defines: each id, its type (meteor, quake, storm or tornado), its radius, duration and warning seconds, whether it destroys blocks and how many materials it may break, and whether it is scheduled (every seconds) or by hand only. A material name the server does not know is counted and flagged there rather than silently dropped. /disaster history [n] prints the last events that landed - ten by default, capped by the file - with what each one did.

This name also answers the operator's verbs - start, stop, schedule and reload - and each of them asks for ppcore.disasters.admin in the handler, so a player typing one is refused in words rather than met with "unknown command". /disasteradmin is the same handler registered under that node; it is the name to write in a staff document, and the two differ only in which of them a server advertises.

/disasteradmin (/disasteradmin, /da)

permission: ppcore.disasters.admin · always · Disasters.java

The operator's name for world events, registered under the admin node. Its reads are the same ones /disaster answers - what is in flight, what the file defines, what has happened - and the four verbs that change the world are:

/disasteradmin start <event> [world] [x z] runs one now, in your world unless you name another, centred on you or on the coordinates you name (or on the event's configured centre when the console starts it), and it warns for the configured seconds before anything lands - it refuses with the ceiling when disasters.limits.max-live events are already running, and it says whether protections are being asked per block (disasters.limits.respect-claims). The name it prints in its own usage lines is whichever of the two you typed.

/disasteradmin stop <event|all> ends events early and awards nothing, deliberately: an admin stopping a meteor because it landed on somebody's base must not pay that base's owner for the privilege.

/disasteradmin schedule <event|all> <seconds|off> turns the clock on or off for an event, stored on the event rather than in the file's own every-seconds, and the interval is counted from the moment you set it - never from boot, so a restart is not an event; /disasteradmin start is the verb for one right now. off takes an event out of the rotation without deleting it from the file.

/disasteradmin reload re-reads disasters.yml and reports how many events it found.

seasons - seasons: a calendar, day length, crop growth and temperature

What it does. One fact is measured against: the date. The season is the latest *-start at or before today, the month is where the date falls in the month table, and the length of the day is that month's column — which is why the module takes the world clock over (doDaylightCycle goes off in the worlds it syncs, and it writes the time itself; switching the module or the daylight feature off hands the clock back). Three other things follow from the date:

Turning it on. modules.seasons.enabled: true ships on, and it claims nothing until it is asked: the calendar starts on January 1 and runs in real time, the day is vanilla's own 10+10 until a month table says otherwise, and an empty world's temperature is the season's own range. Its feature switches are calendar, daylight, crops, temperature and block-replacements — that last one ships off, because it rewrites blocks a player can see and an owner should choose that on their own world.

What you can configure. seasons.worlds: [] (empty is every world — the worlds the blocks are replaced in, separate from seasons.daylight.synced-worlds on purpose, so one sky can be shared between worlds while you build in a world that never snows; the nether and the end are never synced, they have no day to be short). seasons.block-replacements: max-blocks-per-chunk: 512 (a chunk is 256 columns, so 512 reaches every one of them in one pass; a lower ceiling is a delay rather than a hole, and what a pass missed is named in the change's own line), sweep-blocks-per-change: 2048, sweep-chunk-radius: 4, sweep-spawn-blocks-per-change: 2048 (0 turns either pass off at the cost the comment describes), and replacements.<season> as FROM: TO pairs. seasons.calendar: start-date: "" (deliberately empty — with no date anywhere the calendar, the daylight, the crops and the temperature all stand by and the console says so at boot), winter-start/spring-start/summer-start/fall-start, and months.<name> with days, day-length-in-min and night-length-in-min. seasons.crops.default-growth.<season> and crops.growth.<MATERIAL>.<season>. seasons.temperature: update-ticks: 40, change-rate: 0.2, season-base.<season>.min-temp/max-temp, armour (leather is the warm one and has its own ceiling; a key is a family, so IRON_HELMET is IRON and GOLDEN_BOOTS is GOLDEN), weather.rain/storm, max-range: 8, blocks as 'LAVA{range=7,modifier=22}' rows, touching-water.<season>, height.per-block: 0.08, other-dimensions.nether/end, effects.freezing-temperature, slowness-temperature, hunger-temperature, comfortable-above (the three thresholds have to be in order or one is only ever reached inside the one below it, which /season check says) and effect-seconds: 6.

Commands. /season prints today's date, the season, the length of a day and how warm you are with the arithmetic shown; /season calendar|crops|temp [who] for the year, the crop table and one player's temperature; /season set <season>, /season date <month> <day>, /season time <hh:mm>, /season length <month> <day-min> <night-min>|reset <month> and /season reload are staff only (ppcore.seasons.admin) — and /season length is kept in the module's own seasons.yml, so tuning December does not mean editing config.yml. /season blocks and /season check report the counters and name what is inert.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/seasons.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help seasons prints the same names and the same nodes, line for line, because both read this one registration.

/season (/seasons)

permission: none of its own · always · Seasons.java

The calendar, in one command, with a report per part of it - and the reports are separate verbs because each answers a question an owner has to be able to check without guessing. Typed on its own (or as /season now, /season info, or /season date with nothing after it) it prints the date, the time of day, this month's minutes of day and night, and, for a player, the temperature they are feeling and the band it falls in. Where the clock is not actually being driven, the report says which switch is off (modules.seasons.features.calendar, daylight, temperature) instead of printing numbers that look like an answer.

/season calendar (also /season year, /season months) prints the whole year: each month with its day count and its day/night lengths, the current month marked, and each season's start date - a season with no valid start date is named as such rather than left off the list. /season blocks (also /season block-replacements) prints the season's own FROM: TO material pairs, the worlds they apply in, and the three counters: how many blocks changed as chunks loaded, how many were swept when the season changed under players who were standing still, and how many arrived as somebody joined a chunk loaded long before. It also prints both ceilings (seasons.block-replacements.sweep-blocks-per-change and sweep-spawn-blocks-per-change) and says, in words, what a ceiling of 0 leaves behind - a chunk that stays loaded keeps the season it was loaded in. /season crops prints the growth multiplier now in force for every material the file names, each marked stopped, slower, faster or as normal, plus how many growths a season refused and how many it boosted.

/season temp [player] is the temperature report, and it is the one verb here that measures rather than reads: it prints the value for you, the band it falls in, and the arithmetic behind it - the trace lines from the blocks around you - so an owner can see why a spot reads cold. Naming another player needs the admin node. Every verb that moves the clock needs it too: /season set <season> jumps to a season's own start, /season date <month> <day> sets the date (rejecting a day the month does not have), /season time <hh:mm> sets the time of day, and /season length <month> [day-min] [night-min] overrides a month's lengths - which is also /season length <month> to read one and /season length reset <month> to hand it back to the file. /season reload re-reads seasons.yml, and says how many months, seasons, crop rules and heat sources it found, plus how many settings would do nothing.

Until a date is set - in the file (seasons.calendar.start-date) or here with /season set or /season date - every verb except those two and reload refuses and tells you so. That is deliberate: a report saying "winter, January 1" on a server that never chose a date looks like an answer, and a season that nothing is driving is exactly the thing this module exists to make visible.

Out of scope, and named rather than half-done. The source plugin this answers also recolours leaves and grass and grows icicles and leaf litter through a resource pack, which needs a client to reload its pack — something a running server cannot do to a player who is already connected. What is here is what the server can decide.

votes - Vote sites: a listener they call, vote points, streaks, rewards and vote parties

What it does. A vote site is an HTTP client: it does not know about a command or a file, it opens a socket and says who voted. So this module runs a small listener of its own (com.sun.net.httpserver, so it costs no dependency) with two routes in, and the difference between them is what each one is worth:

No token, no listener: with an empty token and no site keys the port is not opened at all, because a vote endpoint anybody can POST to is a way to hand out rewards to strangers. A vote is recorded when it arrives and its rewards are a separate delivery: the record holds total (votes heard) and paid (votes whose rewards were handed over), the difference is exactly what a player is owed, and /playport save cannot lose it. A vote that arrives while its player is offline is paid on their next join. On top of that: vote points, streaks (extra points per consecutive day), milestones at an exact vote count, a vote party at a threshold (a count that goes back to zero when it fires, so it fires once), four top-voter boards (day, week, month, all), a shop that spends vote points, and a join reminder each player can switch off for themselves.

Turning it on. modules.votes.enabled: false ships off because it opens a port — nothing else this plugin ships listens on the network, so that should be decided rather than discovered. Turn it on and set votes.listener.token (or a key per site) first: without one the module refuses to open the port at all. /adminvote status says whether it bound, and the listener's own start-up line names the address it took and which of the token and v1 routes came up. The feature switches are listener (the endpoint itself — off, votes are still recorded and paid but only /adminvote can make one, which is what a server with a vote plugin elsewhere wants), votifier, votifier2, streak, milestones, party and remind.

What you can configure. votes.listener: bind: 0.0.0.0, port: 8199, token: \"\", challengeSeconds: 300 (how long a challenge a site asked for stays good; 0 turns the replay rule off and /votes check says so in words) and debug: 0 (1 logs one line per request with its verdict, 2 adds the names and lengths of what arrived — never a token, a key or a signature; this is the switch a site that answers 401 needs, and /adminvote reload picks it up without a restart). The rules: log-to-file: false (one line per counted vote, including a vote let through against a rule), broadcast-blacklist: [] (never announced, still counted and still paid), cooldown.ignore: false (a vote arriving too soon is counted anyway, written to the log with the rule it went against), oncePerDay: true, pointsPerVote: 1, points.max: -1, limit.monthly: 0 (0 is no ceiling, −1 is the source plugin's derived number), treat-vanish-offline: false, streak.bonusPerDay: 1. The delivery: rewards.process: true (off, votes are still counted and still build a streak — the half of a network a hub wants), rewards.commands, rewards.actions (named actions.yml lists run per paid vote), rewards.message, rewards.broadcast, rewards.payOffline, rewards.joinMessage, and one rewards.entries.<name> per reward with command, rewardType (command runs it, broadcast says it to the server, message says it to the voter — one reader, three verbs), message, chance, permission, worlds, blackListedWorlds, weekDay, advancedPriority, forceOffline, expLevels, displayItem, the composed forms (randomCommand, numberCommand, randomReward, advancedRandomReward, randomItem, pickRandom, lucky, onlyOneLucky, onlyOneItemChance, delayed, title) and votes.milestones.<n>. Per site: votes.sites.<name>.* with enabled, name, service, url, delayHours, priority (highest first, the source plugin's rule for the /vote list) and the site's own key. Then the boards (votes.top.default: all, top.size: 10, top.load.<board>, top.ignore-permission, top.awards-ties), the reminder (votes.remind.message, voteReminding.enabled/remindOnLogin/remindOnlyOnce/remindDelay) and the whole message layer under votes.format.* — helpLine, noPerms, invalidCommand.vote/adminVote, broadcastMsg, broadcastWhenOnline, offlineBroadcast, onlyOneOfflineBroadcast, alternateBroadcast.* (a timer that names how many people voted in the last N minutes, and says nothing when nobody did), topVoter.*, timeFormat, rewardDelay, the shop lines and the two page arrows.

Commands. For players: /vote, /votehelp, /votelast, /votetotal, /votes, /votetoday, /votenext, /voteall, /votebest, /votetop, /voterewards, /voteparty, /voteremind, /voteurl, /votegui, /voteshop and /votetogglebroadcast. For staff: /adminvote under ppcore.votes.admin — one root whose aliases include the source plugin's own spellings of every verb (/avstatus, /avreport, and so on, registered as its own rows because command-audit.js reads registered names out of the source and a name built in a loop is a name no check can see) — plus /voteedit <player> [add|set|take|clear] [n], which writes a player's count through the same words rather than reimplementing them, because two writers for one record is how a vote counts twice.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/votes.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help votes prints the same names and the same nodes, line for line, because both read this one registration.

permission: none of its own · always · Votes.java

The site list, and where a player starts: every site configured under votes.sites, each with its link and whether it is ready for you - [ready], or the reason you cannot vote there yet, in bracket. It ends with your points, your streak in days and your total, and it says when nothing at all is ready. The sites do the counting: a player clicks the link, votes there, and the site calls this server's listener, which is why the line says to run /vote again to check. Two details of the shape matter: with votes.gui.main-command switched on, a bare /vote opens the menu instead of printing (only the bare form - /vote <anything> still means the list, so the list stays reachable), and a first word the command does not know is answered with That is not a vote command. Try /votehelp. followed by the list, rather than silence.

/votetop (/vtop, /topvoters)

permission: none of its own · always · Votes.java

The voting leaderboard for one period: /votetop [day|week|month|all] (its vtop and topvoters aliases), defaulting to whichever board votes.top.default names, and sized by votes.top.size. A board this server does not keep is refused by the key that turned it off - votes.top.load.<kind> - rather than printed empty, because "no votes yet" and "the owner closed this board" are different answers. With no votes counted for that period, it says so.

/voterewards (/votestreak, /votepoints, /vstreak, /vpoints)

permission: none of its own · always · Votes.java

Your own vote record, in one screen: points, total votes, your streak in days, and how many votes' worth of rewards are still owed to you - the ones paid on your next join. It adds your milestones, with their thresholds, when the milestones feature is on, and the vote party's progress against votes.party.threshold. Players only: it reads your own record, and the console's view is /adminvote status.

/voteparty (/vparty)

permission: none of its own · always · Votes.java

How many votes the next vote party needs: the threshold from votes.party.threshold (20 by default), how many votes are in the window, and how many more are needed. With the party feature switched off it says that rather than printing a counter nothing will ever move. The line itself is votes.party.progress, so an owner rewording it does not need a new jar.

/voteremind (/votetoggle, /votetogglereminders, /vtoggle, /vtogglereminder)

permission: none of its own · always · Votes.java

Turns the join reminder on or off: /voteremind reports which state you are in, and /voteremind on or /voteremind off sets it. The reminder is the note a player gets when they join and can still vote somewhere, so switching it off is for somebody who votes without being asked. The choice is per player and remembered; votetogglereminders and vtoggle are the same command under the names the source plugin gives it.

/adminvote (/av, /adminvotes, /adminvotestatus, /avstatus, /adminvotesites, /avsites, /adminvotereport, /avreport, /adminvotehelp, /avhelp, /adminvotereload, /avreload, /nvreload, /adminvotereloadall, /avreloadall, /adminvoteversion, /avversion, /adminvotecurrentplugintime, /avcurrentplugintime, /adminvotetest, /avtest, /testvote, /adminvotetestreward, /avtestreward, /adminvotegiveall, /avgiveall, /adminvotegiveallonline, /avgiveallonline, /adminvotegivereward, /avgivereward, /adminvoteresetpoints, /avresetpoints, /adminvotecleartotal, /avcleartotal, /adminvoteclearofflinevotes, /avclearofflinevotes, /adminvoteclearofflinerewards, /avclearofflinerewards, /adminvoteperms, /avperms, /adminvotepermsplayer, /avpermsplayer, /adminvoteplaceholders, /avplaceholders, /adminvotemilestones, /avmilestones, /adminvoteresyncmilestones, /avresyncmilestones, /adminvoteresyncmilestonesalreadygiven, /avresyncmilestonesalreadygiven, /adminvoteresetmilestonecount, /avresetmilestonecount, /adminvoteclearcache, /avclearcache, /adminvotebackgroundupdate, /avbackgroundupdate, /adminvoteuser, /avuser, /adminvoteuuid, /avuuid, /adminvoteuserremove, /avuserremove, /adminvoteuseruuidremove, /avuseruuidremove, /adminvotepurge, /avpurge, /adminvotevote, /avvote, /fakevote, /adminvotevotesite, /avvotesite, /adminvotegui, /avgui, /adminvoterewards, /avrewards, /adminvotevoteparty, /avvoteparty)

permission: ppcore.votes.admin · always · Votes.java

The operator's half of this module, and one command with a lot of words. /adminvote with nothing after it is status; /adminvote help prints the whole word list, which is the authority rather than any list here. Reads: status, sites, report, placeholders, perms, milestones, user, uuid. Reloads and upkeep: reload, reloadall, version, currentplugintime, clearcache, backgroundupdate. Records: add, take, remove, total, points, streak, clearday, pay, resetpoints, cleartotal, clearofflinevotes, purge, userremove, useruuidremove, resetmilestonecount, resyncmilestones, resyncmilestonesalreadygiven. Testing and giving: test, testreward, vote and votesite (which record a vote as though a site had called), giveall, giveallonline and givereward. And the two menus: gui and rewards.

Every word answers in its own words and numbers - "that row costs 10 and they have 4" rather than "invalid argument" - a destructive word needs confirm and prints what it would change first, and the reads work from the console because the console is where an operator asks. giveall reaches every record on file and giveallonline only the players standing on the server; both refuse a non-number and a zero before changing anything. The source plugin registered one command per word under its own names (adminvotereport, avreport, ...); those are aliases here, translated into the words above, so a name cannot drift away from the word it means - which is also why a spelling that looks like a word is answered as that word rather than as status.

/voteedit (/adminvoteedit, /voteset)

permission: ppcore.votes.admin · always · Votes.java

One player's vote count, edited directly: /voteedit <player> [add|set|take|clear] [n], with the source plugin's own argument order and its own info line back. It is translated into the record words of /adminvote rather than writing the record itself, because two writers for one count is how a player's votes end up counted twice. Staff only (ppcore.votes.admin), and voteset and adminvoteedit are its other names.

/votehelp

permission: none of its own · always · Votes.java

What each voting verb does, in the order a player meets them: the site list, the ready-now list, today's state, the next opening, the menu, the shop, the totals, the last vote, a single site's link, the leaderboard, the reminder switch and the announcement switch. It finishes with your own numbers - votes, points, what is still owed and whether your votes are announced - so a player who typed it out of curiosity leaves knowing both the commands and where they stand.

/votelast

permission: none of its own · always · Votes.java

When you voted last, and where: the site, how long ago, and the exact time in votes.format.timeFormat. It also answers the question the first half provokes - that site is either ready again now or closed until a stated time - because "when did I last vote" is nearly always asked to work out whether the next vote will count. /votelast <player> reads somebody else's, which needs ppcore.votes.admin.

/votetotal

permission: none of its own · always · Votes.java

Your votes in full: how many you have, how many have been paid out, how many are still owed and are paid on your next join, your points, your streak in days, and how many shop rows you have bought. Naming another player needs ppcore.votes.admin; without a name it is your own record. The console is told to use /adminvote status instead, because it has no record of its own to read.

/votes (/votecount, /checkvotes)

permission: none of its own · always · Votes.java

The same report as /votetotal, under the name and the shape the source plugin uses for it: a bare /votes is your own count, and /votes <player> is that player's, which needs the admin node. It exists as its own name rather than an alias because that is the word a server's own documentation points at, while behind it the two read one record and print one report.

/votetoday

permission: none of its own · always · Votes.java

Per site, today's state: either ready now or done, opens in .... It is the report a player reads to find out which sites are left before they stop for the day, and it says the rule underneath it out loud - a vote counts once per site per day, and the day is the calendar's, so everything resets at midnight. Players only, and a server with no sites configured is told that rather than shown an empty list.

/votenext

permission: none of its own · always · Votes.java

The one site to vote on next: the soonest site you can vote on right now, with its link, or - if none is ready - the site that opens soonest and how long the wait is. When more than one is ready it says how many others there are and points at /voteall. Players only.

/voteall

permission: none of its own · always · Votes.java

Every site you can vote on right now, one per line, with each link - the list to work through in one sitting rather than /votetoday's state report. If nothing is ready it says so and names the site that opens soonest. A site with no URL set is shown as such, so a missing link is visible rather than presented as a link that does nothing.

/votebest

permission: none of its own · always · Votes.java

The single top voter for a period: /votebest [day|week|month|all], defaulting to the configured board. It names the winner, the number of votes, and points at /votetop for the full board - the answer to "who is winning" without scrolling a list. With no votes counted in that period it says so rather than picking somebody arbitrarily.

/voteurl

permission: none of its own · always · Votes.java

One site's link, and everything about that site on one screen: its display name and key, the link (or that none is set), the service it calls back through, the delay in hours before the same player may vote there again (delay: none when there is none), whether the day rule applies, and - for a player - whether it is ready for you right now. With no argument it prints the usage line with the site keys on it, and an unknown name is refused with No site called '<name>' rather than failing silently.

/votegui

permission: none of its own · always · Votes.java

The same information as a menu you can click: a slot per site showing its state, with a ready site printing its link when clicked, and the bottom row carrying your vote points (and the shop behind them), today's state, how voting works, and the two switches you own - the join reminder and whether your votes are announced. A switch's state is in the item's own name, so what the window says can be read back. A server with more sites than fit on one page gets page arrows rather than a list that stops at the first forty-five.

/voteshop

permission: none of its own · always · Votes.java

Where vote points are spent: /voteshop opens the shop, and /voteshop list prints the rows - each row's id, name, cost in points, its per-player limit if it has one, the permission it needs, and any reason the row cannot be sold - as lines, which is the form the console can use. Naming a row and a player buys it for that player out of their points, which is how an operator or a console hands over a purchase (it needs ppcore.votes.admin), and a row's own limit is counted per player rather than per purchase.

/votetogglebroadcast

permission: none of its own · always · Votes.java

Whether your own votes are announced to the whole server: /votetogglebroadcast [on|off], or with nothing after it to read and set it in one line. Only the announcement changes - your votes still count, still pay their rewards, and the site still gets its call - which the reply says so nobody thinks their rewards have stopped; the server-wide half of that switch is votes.rewards.broadcast.

Two switches, one route each. votifier and votifier2 are independent: a server whose sites speak only v2 can leave v1 off, and a body whose switch is off is refused with that switch named in the log rather than falling through to the other version. votes.listener.debug (1 or 2) logs a line per request with its verdict, which is the switch to reach for when a site answers 401, and /votes check reports a challengeSeconds of 0 in words, because that setting is what turns the v2 replay rule off.

service - the only outbound client, off until switched on

What it does. Every other module here reads what the server already has. A handful of the plugins behind this core need the opposite — a webhook that announces something, a manifest URL that answers "is there a newer version", a paste upload, a Twitch viewer count — and the honest way to give them that is not to scatter new URL(...) through a dozen modules, where the first false alarm about a plugin phoning home is unanswerable, but to put every request behind one gate an operator can read, switch and audit. This is that gate, and it asks four things before a byte leaves the box: the master switch, the named feature's own switch, and the host being on the allowlist — then it logs the call and its destination, counts it, and reports the counters.

What ships off matters. service.enabled ships false, so a server that never touches the section is bit-for-bit the server that shipped before this class existed: every caller asks for a refusal first and gets a sentence. The five features are twitch (a plain GET of a viewer count, used by the stream module), update (read a manifest URL — it reports, it installs nothing; the panel owns installs), discord (POST a line to a webhook), paste (POST text to a URL and hand back the link it answers) and telemetry (POST opt-in numbers to your own endpoint — never a third party's and never bStats' shape). A refusal is a value, not an exception and not a silent no-op: the caller prints the sentence naming the switch that stopped it, which is the difference between a setting that is off and a feature that is broken. The database half is deliberately not rebuilt here: sql: and the panel's MariaDB are a different job, so service carries no sql feature.

Turning it on. Two switches, because the module and the client are different things: modules.service.enabled: true ships on so that /service status is reachable and the counters can be read, and service.enabled: false ships off and is the wall. Then service.features.<name>: true for each feature you want, that feature's URL (service.update.url, service.discord.url, service.paste.url, service.telemetry.url — each empty by default, so a feature switched on with no URL says so in the report rather than failing quietly), and service.allow naming the host or host:port it may talk to. twitch has no endpoint of its own because the stream module passes a URL per call.

What you can configure. service.enabled (the master switch — false means no request leaves this server), service.allow: [] (the endpoint allowlist, matched on host or host:port, so a value pasted into a URL field cannot become a request to somewhere never named), service.log: true (every call and refusal with its destination), service.timeout-ms: 4000 (the ceiling a caller naming no bound inherits), service.features.twitch/update/discord/paste/telemetry and the four URL keys.

Commands. /service — /service status prints each feature in the order the code lists them with its counters (how many calls were asked for, sent, refused and failed, and where the last one went), which is the answer to "does this server talk to anything, and where" as a line rather than a guess. /service features lists the switches. A name in the config that the code does not have, or a switch the code reads that the config does not set, is a problems() line, so the two cannot drift quietly.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/service.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help service prints the same names and the same nodes, line for line, because both read this one registration.

/service

permission: none of its own · always · Service.java

The one place this plugin talks to anything outside itself, and the command that answers "does this server talk to anything, and where". /service on its own is /service status: whether service.enabled is true, how many features are on, how many hosts are listed in service.allow, and the counters - how many calls were sent, how many were refused by the gate and how many failed - plus the last call, named with its feature and destination. The counters are the point: every one of those is a question an operator asks and cannot answer by watching chat.

/service features lists the five named features (twitch, update, discord, paste, telemetry) in order, each with on or off and the URL it would send to, so the shape of what is switched on is one screen rather than a config hunt. /service test <feature> <url> [post] makes one call through the very same gate a module uses, and prints the sentence back: a refusal here is exactly the sentence stream or a webhook would print, which is what makes the command worth typing before switching a feature on. Add post as a fourth argument to send a POST instead of a GET. Anything else typed is answered with the list of verbs.

Three things gate every call, and the refusals name the one that stopped it, so a setting that is off never reads like a feature that is broken: service.enabled is the master switch and ships false, so a server that never touches this section opens no connection at all; service.features.<name> has to be on for that feature; and the destination host has to be listed in service.allow, which matches a host or a host:port and is checked whatever else is switched on - a URL pasted into a config field cannot become a request to a host nobody named. An allow entry written as a whole URL never matches, and /playport check says so. Calls are logged with their destination (service.log, true) unless that is switched off, and the timeout ceiling is service.timeout-ms (4000) so one slow service cannot hold a tick thread open. service.update.url, service.discord.url, service.paste.url and service.telemetry.url are the per-feature destinations; twitch takes its URL per call, from the streamer's own source setting.

analytics - staff snapshot of who is online and how long

What it does. One staff command that answers the question a moderator actually asks: who is on right now, and how long has each of them been here. It prints the online/max line, then one line per player — name, ping in milliseconds, and minutes played taken from the core's own stats — and nothing else: no window, no file, no records of its own. It is the compatibility batch's row for the source plugin's analytics addon, and that plugin's own spelling of the command (staffanalytics) is registered as an alias of this one rather than as a second command, so the two cannot answer differently.

Turning it on. modules.analytics.enabled: true, which is how it ships. There is nothing else to do: the module has no section of its own in config.yml and no prerequisites, and it is safe on a server with one player on it as well as on a full one.

What you can configure. Nothing. This module has no config keys at all beyond its switch in the modules: block — what it prints is the module's own, in code. That is worth knowing before you go looking for a section that does not exist; /playport check analytics is satisfied with the switch.

Commands. /analytics, with /staffstats and /staffanalytics as its aliases, under ppcore.analytics.admin — so it is a staff tool and not something a player can ask for.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/analytics.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help analytics prints the same names and the same nodes, line for line, because both read this one registration.

/analytics (/staffstats, /staffanalytics)

permission: ppcore.analytics.admin · always · Analytics.java

A one-screen snapshot, for staff, of who is online right now and how long they have been here. It answers with Online <n>/<max> - the server's own numbers, so it matches what the player list says - and then one line per player: their name, their ping in milliseconds, and the minutes the plugin has recorded them as having played. There are no arguments and no verbs: type it and read it.

The minutes come from the statistics this plugin already keeps per player, so they are the same totals /playtime reports and there is nothing extra being counted for this command. It is a reading rather than a report to hand out, and the permission is the module's own admin node - a player who can see who is online and for how long can see it, which is why it is not granted by default.

The source addon behind this module registered the same screen under its own name; that name is an alias of this command rather than a second command, so both spellings print exactly the same thing and cannot drift apart.

Files behind this page

What this section documentsSource
Presets, pre-generation (shapes, orders, ceilings, resume), /worldgen, /pregen, /tree, the panel's world viewpkg/src/ppcore/Worldgen.java (../../pkg/src/ppcore/Worldgen.java)
The presets, pre-generation limits and the panel's world formpkg/config.yml (../../pkg/config.yml), the worldgen: section
The four event types, the sweep, the radius and in-flight ceilings, claim refusal, history, messagespkg/src/ppcore/Disasters.java (../../pkg/src/ppcore/Disasters.java)
The schedule, the limits and the per-event definitionspkg/config.yml (../../pkg/config.yml), the disasters: section
The calendar, the daylight clock it takes over, crop growth, temperature and its contributors, block replacement and both sweepspkg/src/ppcore/Seasons.java (../../pkg/src/ppcore/Seasons.java)
The month table, the season ranges, the armour and block temperature tables, the two sweep ceilingspkg/config.yml (../../pkg/config.yml), the seasons: section
The HTTP listener, both routes in, the vote record and what is owed, streaks, milestones, parties, the shop, the top-voter boards, every player and admin verbpkg/src/ppcore/Votes.java (../../pkg/src/ppcore/Votes.java)
The listener, the rules, the reward engine, the sites, the boards and the whole message layerpkg/config.yml (../../pkg/config.yml), the votes: section
The one outbound gate: the refusal as a value, the allowlist, the five features, the counters, /servicepkg/src/ppcore/Service.java (../../pkg/src/ppcore/Service.java)
The master switch, the allowlist, the timeouts and the four endpoint keyspkg/config.yml (../../pkg/config.yml), the service: section
The staff snapshot command and its aliasespkg/src/ppcore/Analytics.java (../../pkg/src/ppcore/Analytics.java)
The module switches and feature switches for all sixpkg/config.yml (../../pkg/config.yml), the modules: block

Commands for items, blocks and rewards

These are the first thirteen modules of the compatibility batch: the small ones, each closing one gap against a reference plugin that was a command, a placed block or a short table of settings. Their switches sit together at the end of the modules: block in pkg/config.yml and they all ship on - a module that ships off is a feature an owner never sees.

Two things are worth knowing before reading an entry. First, half of these modules keep what they need in a file of their own (button.yml, bounty.yml, cooking.yml) rather than in config.yml, because a marked block or a claimed reward is a place or a record rather than a setting, and has to survive a restart. Second, several of them have nothing to configure at all - they are a command an operator runs, and saying so is the honest answer rather than a list of keys that do not exist. The shared rules (the switch, the precedence of your edit over the profile, <command> check) are in chapter 12, and the batch continues on page 07.

anvil - cap anvil repair cost

What it does. Caps what the anvil may charge. Every time the anvil asks the server to prepare a result, the module pulls the repair cost down to the cap - both on the inventory's own cost and on the item the anvil would hand over - so a heavily used tool can never reach the vanilla "Too Expensive" line and become unrepairable. Nothing else about the anvil changes: the same combinations, the same rename, the same renaming cost.

Turning it on. modules.anvil.enabled: true, which is the shipped default. There is nothing to create and no command to run to make it work; it is a listener on the anvil. Its source plugin's aereload is /playport reload under the source plugin's own name, which is the same verb: the reload re-reads config.yml and anvil.max-cost with it.

What you can configure. anvil.max-cost, shipped as 30 - the most one operation may cost in levels. The module tells you at check time when the number cannot do its job: a cap below 1 refuses every anvil result, and a cap above 40 is above vanilla's own too-expensive threshold and may never show. /anvilcost <n> sets the cap and pins it for the session, so a number set in game does not silently go back to the file's number on the next reload.

Commands. /anvilcost [n] (alias anvilrepaircost).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/anvil.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help anvil prints the same names and the same nodes, line for line, because both read this one registration.

/anvilcost (/anvilrepaircost)

permission: ppcore.anvil.admin · always · AnvilPlus.java

The anvil's repair-cost cap, and the whole of this module. /anvilcost with no argument prints the cap in force; /anvilcost <n> sets it, and every anvil result is then capped at that number, which is how a server keeps a repaired tool from becoming "Too Expensive!" before the player has paid for it. The value is pinned rather than written into config.yml: a cap set here survives until the server is restarted, and anvil.max-cost stays the file's own answer for the next boot - set the key as well if the number is meant to be permanent. A cap below 1 refuses every anvil result outright and a cap above 40 is past vanilla's own limit and may never show a result at all, so both are named by the module's own check.

Permissions. ppcore.anvil.admin.

attachcommand - bind a command to the item in hand

What it does. Makes an item run a command. Hold an item, run /attachcommand <command>, and from then on right-clicking with that item - in the air or on a block - runs the command as the player who clicked. The click itself is cancelled before the command runs, so the item is not placed, not used and not eaten. The binding is written into the item's own persistent data, so it travels with the item through a trade, a chest or a crate.

Turning it on. modules.attachcommand.enabled: true. Nothing else: there is no list of bound items to maintain and no file, because the binding lives on the item. The command needs an item in your hand and a command to bind; a bare /attachcommand prints the usage.

What you can configure. Nothing. The module reads no config keys at all - what it needs to know is on the item you are holding.

Commands. /attachcommand <command> (alias attachcmd). A leading / is accepted and stripped, so /attachcommand /spawn and /attachcommand spawn are the same binding.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/attachcommand.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help attachcommand prints the same names and the same nodes, line for line, because both read this one registration.

/attachcommand (/attachcmd)

permission: ppcore.admin.attach · always · AttachCmd.java

Binds a command to the item in your hand: hold the item, type /attachcommand <command>, and right- clicking that item while holding it runs the command for you. The command is stored on the item itself rather than in a list on the server, so the binding travels with the item - which cuts both ways: a bound item handed to a player still runs the command, and the command runs as the player who clicked it, so its own permission checks apply and nobody gains a node by being handed something. A leading slash is allowed and dropped, and the whole argument list is kept, so a bound command with its own arguments (a warp name, a kit) works. There is no unbind verb: binding a different command replaces the old one. The binding is invisible in the item's name and lore, which is the point for a menu item and the trap for a staff item. Console and a bare /attachcommand print what to type; an empty hand is refused.

Permissions. ppcore.admin.attach.

bansgui - a click menu of players and of bans, for staff

What it does. Two menus over the staff module's own record. /bansgui shows the players who are online as heads, and a click hands that player to /ban; /unpunish shows the players who are banned, and a click hands one to /unban. Each head carries the reason, who issued the punishment and when it ends, and the list is paged rather than one long inventory. This module is deliberately not a second opinion about a punishment: it runs the verb that owns the action, so the permission, the wording and the written record are all the staff module's. It also lists a name the record does not have but the server does, so a ban issued with /ban at the console is visible here. It will not lift a mute - /unmute is one word, and a menu that quietly chose which punishment a click meant would be ambiguous in the case that matters.

Turning it on. modules.bansgui.enabled: true, plus the staff module for anything to act on - this is a front door onto /ban and /unban, not a punishment system of its own.

What you can configure. bansgui.page-size (45, the most heads one page shows - a longer ban list says so instead of quietly showing the first page), bansgui.title (&8Players), bansgui.unpunish-title (&8Banned players) and bansgui.messages.*: unpunishment-confirmed, not-banned, empty and more. There is no usage message on purpose: the sentence /unpunish answers with no argument is the command's own description, so a messages.unpunish-usage key would be a line nothing ever prints.

Commands. /bansgui (alias maxbans) and /unpunish <player>.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/bansgui.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help bansgui prints the same names and the same nodes, line for line, because both read this one registration.

/bansgui (/maxbans)

permission: ppcore.staff.ban · always · BanGui.java

A click menu of the players who are online right now, for staff: pick a head and the click runs the staff module's own ban verb on that player. Nothing about the punishment is reimplemented here - the permission, the wording and the written record are the ban command's, which is deliberate, because a menu that made its own decision would be a second answer to "is this player banned". The menu is paged, so a full server is still usable: bansgui.page-size is how many heads a page holds (9 to 45, as a chest can hold no more) and bansgui.title is what the menu calls itself. With the staff module off there are no punishments to show and the menu opens empty, which the module's own check says out loud rather than leaving staff to wonder.

/unpunish

permission: ppcore.staff.ban · always · BanGui.java

The other half of the same idea: a menu of the players who are banned, one head each, carrying the reason, who issued it and when it ends, and a click that lifts the ban through the staff module's own unban verb. /unpunish <player> is the same verb without the menu, for the case where the name is already known. It deliberately does not lift a mute: a click that quietly chose which punishment it meant would be ambiguous in exactly the case it matters. The menu's title is bansgui.unpunish-title, and the player name is the command's first word, so the platform's own config-check verb is reachable here only through its other spelling.

Permissions. ppcore.staff.ban for both.

bounty - put money on a player's head

What it does. Puts a price on somebody. /bounty <player> <amount> takes the money from your own balance and puts it on that player's head; /bounty list prints the board by player name rather than by account id. When the wanted player is killed by another player, the killer takes the whole pot and the server is told who claimed it - and killing somebody who has no bounty on them pays nothing. The board is kept in bounty.yml, so it survives a restart.

Turning it on. modules.bounty.enabled: true, and econ switched on: money comes from the economy and is paid through it. The module says so itself when the pair is wrong - with bounty on and econ off, placing a bounty and paying one out both do nothing.

What you can configure. bounty.min, shipped as 10: the smallest bounty anybody may place. Below it a bounty is refused rather than taken, so one player cannot cover the whole server in one-coin bounties. A negative minimum is named at check time.

Commands. /bounty <player> <amount> and /bounty list (alias bounties).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/bounty.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help bounty prints the same names and the same nodes, line for line, because both read this one registration.

/bounty (/bounties)

permission: none of its own · always · Bounty.java

Money on somebody's head. /bounty <player> <amount> takes that amount out of your own balance and adds it to the pot on the player you name, and the whole board hears about it; the minimum is bounty.min, and the player has to be online. /bounty list prints the board - who is worth what - by name rather than by account id, so an operator reading it can act on it. The pot is paid to whoever kills the named player, and the same broadcast announces it; the money is not created at that moment, it was taken when the bounty was placed, so a bounty can never pay out more than it collected. It leans on the economy module: with econ off the board still prints but placing a bounty answers that it cannot pay, which the module's own check warns about.

Permissions. None - every player may place and read bounties; the money is the limit.

button - a click button, and the one-time reward button

What it does. Two buttons in one module, and they are the two things a server actually asks a block to do. /tapbutton set marks a race button: a tap is a score, every pay-every taps the fastest hands are paid button.pay, and a player cannot tap again inside button.cooldown-ms. /buttonreward set marks a one-time button: the first tap an account ever makes runs button.reward.commands at the console, and every later tap is answered with the sentence for somebody who has already had it. The claim is recorded and saved before the reward runs, so a crash between the two cannot pay twice - and /buttonreward reset <player> is the help for a player who got nothing. Both marks are kept in button.yml.

Turning it on. modules.button.enabled: true, then mark the block you are looking at with /tapbutton set or /buttonreward set. The one-time half has its own switch (button.reward.enabled) and its own permission (button.reward.permission, blank by default, which means every player may claim it).

What you can configure. button.cooldown-ms (1500, per player between taps), button.pay (5.0), button.pay-every (10 taps between payouts, and 1 pays on every tap), and the button.reward block: enabled (true), permission (""), commands (one console command per line, run in order, with {player} filled in), message, already-claimed and no-permission. The shipped reward command is written as minecraft:give deliberately - this core owns /give, so asking the server's own command keeps the reward the same when modules are switched on and off.

Commands. /tapbutton set (alias buttontap) and /buttonreward set (aliases rewardbutton, breward), with /buttonreward reset <player> to clear one account's claim.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/button.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help button prints the same names and the same nodes, line for line, because both read this one registration.

/tapbutton (/buttontap)

permission: ppcore.button.admin · always · Button.java

Marks the block a player taps for a small, repeatable payment: look at the block and type /tapbutton (the registration's own line spells it /tapbutton set, and the extra word is accepted). Each tap then pays button.pay every button.pay-every taps, out of the economy module, and the mark is remembered in the plugin's own file rather than in the world. One tap spot per server - marking another block moves the mark rather than adding a second one - and the payment needs econ on, which the module's own check will say when it is off. button.cooldown-ms is how long one player must wait between taps, so the payout is a faucet with a rate on it rather than a race; button.pay and button.pay-every are the other two numbers, and they are the whole of the tuning.

/buttonreward (/rewardbutton, /breward)

permission: ppcore.button.admin · always · Button.java

The one-time reward button: a block that pays each account once, ever. set marks the block you look at, unset (or clear) unmarks it while keeping the claims on file, info says where the button is and what it pays, and list names the accounts that have claimed. check <player> answers whether one account has claimed and how many later attempts were refused, and reset <player> forgets a claim so that account may claim once more - which is the help a player who tapped it during a crash gets, and the reason the claims are recorded under a name as well as an account id. The claim is written and saved before the reward runs, so a crash between the two can never pay somebody twice; the cost of that rule is the one it is there for: a player who was paid nothing is visible in list and fixable with reset. The whole feature is button.reward.enabled, the optional node a player must hold is button.reward.permission, the words shown are button.reward.message and button.reward.already-claimed, and the commands a claim runs are button.reward.commands with {player} standing for the claimer.

Permissions. ppcore.button.admin for both marking verbs.

colors - print the colour codes

What it does. Prints the list you need when you are writing a MOTD, a kit name or a chat format: the &0-&9, &a-&f codes with their formatting pair, and then a swatch of every colour this server can draw. Nothing is changed and no permission is needed - it is a reference page with the colours actually shown.

Turning it on. modules.colors.enabled: true. Nothing else.

What you can configure. Nothing - the module reads no config keys.

Commands. /colors, with the aliases colorpicker and colorlimits (the latter is the name CMI uses for the same page). /colour is deliberately not an alias: that spelling belongs to the items module's dye verb.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/colors.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help colors prints the same names and the same nodes, line for line, because both read this one registration.

/colors (/colorpicker, /colorlimits)

permission: none of its own · always · Colors.java

The colour codes a config file accepts, printed as a reference: the sixteen &0-&f colours on one line, the format codes (&k magic, &l bold, &m strike, &n underline, &o italic) and &r for a reset, and then a rendered sample of every colour so the reader sees them rather than guesses at them. It takes no argument and asks for no permission: the page is identical for everyone, which is why CMI's own name for it is an alias here rather than a per-rank palette - this plugin has no per-rank colour limit to read, so an argument would only print the same list again. Nothing about it changes what a player may write in chat; chat.color.palette and the chat module's own nodes are where that is decided.

Permissions. None.

compass - point the compass at a player or spawn

What it does. Points your compass somewhere. /compass <player> aims it at a player who is online, and /compass or /compass spawn aims it at the current world's spawn. It sets the vanilla compass target, so the needle follows the target as either of you moves.

Turning it on. modules.compass.enabled: true. Nothing else.

What you can configure. Nothing - the module reads no config keys and keeps no state.

Commands. /compass [player|spawn] (alias point). A name that is not online is refused rather than silently ignored.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/compass.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help compass prints the same names and the same nodes, line for line, because both read this one registration.

/compass (/point)

permission: none of its own · always · Compass.java

Points your compass somewhere and leaves it there: /compass <player> aims it at that player's position, and /compass spawn - or /compass with nothing after it - aims it at the spawn of the world you are standing in. It is a one-shot aim, not a tracker: the target is a place, so a player who moves on does not drag your needle with them, and asking again is how it follows somebody. A name that is not online is refused rather than aimed at where they last stood, and the console cannot use it, because a compass is the player's own item. Its whole setting surface is the module's switch; there is nothing to configure about where the needle points.

Permissions. None.

cooking - cooking stations: a block that performs recipes

What it does. A station is a block you click. The window lists the recipes that station offers; a recipe takes exactly the ingredients it names - counted before anything is removed, so a short pack is refused with the shortfall in words and nothing is touched - simmers for its seconds while you may walk away, and pays its rewards when it is done. An interrupted cook (the server stopping, you quitting, /cook stop) puts the ingredients back in your pack, because a stack lost to a menu is worse than a pot that never started. /cook hand keeps the source plugin's older half: naming the food in your hand, at no cost and consuming nothing. The source plugin's third stage type, a minigame, is deliberately not run: a stage naming one is skipped and named by /cook check, because a cooking module should not decide how a server's minigames work.

Turning it on. modules.cooking.enabled: true. A station is half a setting and half a place: cooking.stations.<id> says what it is (its block, its title, its permission and the recipes it offers) and /cook place <station> marks the block you are looking at, writing the mark into cooking.yml because a place is not a setting and has to survive a restart. A marked block that is no longer the station's own block is named by /cook check, since clicks there simply do nothing.

What you can configure. cooking.hand-rename (true, the /cook hand half). cooking.stations.<id> with block, display, permission and recipes. cooking.recipes.<id> with display, permission, needs (MATERIAL: amount - exactly what goes in, and the whole of it), stages ([INSERT_ITEM, WAIT]; a list without a WAIT stage finishes on the click, and seconds is not read for it), seconds, rewards (item, amount, chance) and message. cooking.menu.title and cooking.menu.progress-title, and cooking.sound.open/start/done. The window itself is cooking.customization: rows is three lines of nine characters (o empty, s a recipe slot, b/n the page before and after, x close or stop) - how many recipes a page holds is the number of s slots and nothing else - icons.<char> is each character's item, and words.* is every line of text the window draws. Every sentence the module says is in cooking.messages (ready, missing, no-permission, busy, stopped, quit, full, placed, unmarked).

Commands. /cook (alias cooking): hand names the food in hand, place <station> marks the block you are looking at (also spelled menu, and it puts the station's own block down), list prints the stations, info <recipe> reads one back, stats counts what you have cooked, stop interrupts your cook, and cook check reports what the section cannot honour.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/cooking.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help cooking prints the same names and the same nodes, line for line, because both read this one registration.

/cook (/cooking)

permission: none of its own · always · Cooking.java

Cooking: stations are blocks an owner marks, and a player at one cooks a recipe through a small progress screen rather than a crafting grid. /cook with nothing after it prints the verb list with how many stations, recipes and marked places this server has loaded, which is the first thing to read when a station "does not work". /cook menu <station> [page] opens a station's recipes from anywhere, so a hub can hand the menu out without a block; list prints the stations and where each one is placed; recipes prints every recipe with its ingredients and what it gives, and info <recipe> is one of them in words. stats [player] counts the dishes each account has finished - reading cooked in the module's own record

hand names the food you are holding, the older verb, for a server whose stations are untidy; stop abandons the dish you are in the middle of, as does walking away or disconnecting, so a half-cooked dish never holds a station. Recipes and stations come from cooking.recipes and cooking.stations, the screen's own words from cooking.messages.* and cooking.customization.*, and a station whose block or permission is missing is named by the module's own check.

Permissions. None on the command itself; a station's and a recipe's own permission are what gate using them ("" is anybody).

crates - crates: keys, placed crates and weighted rewards

What it does. A crate is a named pool of prizes plus the rules around spending one. A key is an item (key-name is what it is called) stamped with the crate's id; two roads lead in and no more - right-clicking the block /crate place <name> marked, or /crate open <name> spending a key already in your pack. Both spend exactly one key and pay exactly one weighted row from table (a weight of 3 in a pool totalling 23 is paid 3 opens in 23), and every refusal happens before the key is spent: a refusal that burned the key it refused is the one failure a player notices and an operator cannot. /crate preview <name> shows the pool without spending anything, and a reveal can announce the prize, play a sound and fire a rocket. A row's first word may be one of the core's own item ids instead of a material - customitems.items resolves it - which is how a crate pays a named item with its lore, enchantments, effects and model data rather than a plain stack of its material.

Turning it on. modules.crates.enabled: true, then place each crate's block with /crate place <name> and hand keys out with /crate give <player> <crate> [n]. Nothing is a working crate by accident: the pool can be read (/crate list, preview) without a block, and both shipped crates refuse nothing until an owner places one and gives somebody a key.

What you can configure. One section per crate under crates:, and the section's name is the crate's name. table is the pool - one row per prize as <material|item id> <weight> [amount]. permission is the node a player needs on top of holding the key ("" means the key is the only thing that matters). limit-per-player and limit-global are how many times one player (or the whole server) may open it inside window-hours; 0 is no limit. key-name is what the key is called, announce is the line the server says when a key is spent ({player}, {crate}, {prize}; empty for silence), sound and firework are the reveal. The shipped common crate is a pool with two prizes and daily is a once-a-day crate, which is what a per-player window is for. A row that cannot be won - a material this server does not have, an item id nothing defines, a weight below 1 - is named by /crate check rather than skipped, and a pool's expected value per open is checked by the build against the cheapest spawner head so a crate cannot quietly become an income source.

Commands. /crate (alias crates): list, preview <crate>, open <crate>, give <player> <crate> [n], place <crate>, remove, stats <crate>, reset <crate> [player], and crate check.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/crates.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help crates prints the same names and the same nodes, line for line, because both read this one registration.

/crate (/crates)

permission: none of its own · always · Crates.java

Crate keys and the prizes behind them. A crate is a section under crates: in config.yml, with its prizes, their weights and its limits - this command is how you see one, place one and spend a key on one, so an owner never has to guess a crate's name. /crate with nothing after it lists them (list is the same verb) with each crate's prize count, total weight and limits. preview <crate> shows the prize table without spending anything, and open <crate> opens one you hold a key for. place <crate> marks the block you are looking at as that crate - its own key item is what opens it, and clicking it opens the crate rather than a container - while remove unmarks the block you look at. give <player> <crate> [n] hands keys out. stats <crate> [player] reports the opens recorded inside the crate's window, server-wide and for one player with their name in it, and reset <crate> [player] clears that window so the crate may be opened again - clearing a player's own window leaves the server-wide one alone, and the reply says so, because the two limits are counted separately. The admin verbs need ppcore.crates.admin; the announce, sound, key name, window and the global and per-player limits are the crate's own keys under crates.*.

Permissions. None on the command; a crate's own permission is what a player needs on top of the key.

What it does. Hands the stack in your hand to another player who is online. The transfer is all or nothing: if their pack cannot take the whole stack, nothing is sent, your hand keeps the item and nothing is dropped on the floor - which is the only version of this command that cannot lose an item when a recipient's inventory is full.

Turning it on. modules.donate.enabled: true. Nothing else - no file, no state, no marked block.

What you can configure. Nothing - the module reads no config keys.

Commands. /donate <player>. Needs an item in your hand and a player who is online; both refusals say which of the two is missing.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/donate.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help donate prints the same names and the same nodes, line for line, because both read this one registration.

permission: none of its own · always · Donate.java

Gives the item in your hand to another player: /donate <player>. It is all or nothing - if their inventory has no room for the whole stack, nothing moves and you are told so, rather than the stack being dropped on the floor or destroyed while it is being handed over - and both sides get a line, so neither has to ask. The item travels as itself, with its enchantments, name, lore and custom data intact, because it is the same stack being handed on rather than a copy being made; that is also why the module's own check treats a full recipient as an ordinary answer and not an error. Players only, and the player named must be online - posting an item to somebody who is not there is what the mail module is for.

Permissions. None.

doors - doors that move as a set: a selection, a direction, a distance

What it does. A door is a selection, a direction and a distance, over ordinary blocks. /door new <name> [family] takes the block you are looking at as the first corner, /door corner <name> takes the second, /door move <name> <dir> [distance] says which way it travels and how far, and /door type <name> <family> picks one of the source plugin's five families, which are presets over the same mover. /door trigger <name> marks the block whose power opens and closes it. A toggle is one move: the blocks are cleared from where they were and placed where they are going in the same tick, from the snapshot the door took on the way out - there is no client-side animation and no resource pack. Every refusal names its reason (too big, too far, would move into itself, a material in the way, a material it will not carry, land it will not cross, a world that is not loaded, a block that is not a trigger, too far from the trigger, or too many doors), and /door pause and /door stop are the staff pair: pause refuses every toggle and says so, and stop closes what is open even while paused. The older /door create plus /door add lists of blocks that toggle where they stand still work exactly as they did.

Turning it on. modules.doors.enabled: true. There is no content to prepare: a door is created by the verbs above, and the module keeps its own record of what each door is.

What you can configure. doors.max-size (512, blocks one door may carry), doors.max-distance (16, how far it may travel whatever /door move is asked for), doors.distance (3, what a new door gets and what a bare /door move uses), doors.redstone (true, a powered trigger opens it and losing power closes it), doors.power-ticks (10, how often triggers are read - a button held for a shorter pulse than this is missed, and a lever, a plate and a redstone block are states rather than pulses), doors.power-distance (4, how far a trigger may sit from its door) and doors.trigger-blocks (the only blocks /door trigger accepts). doors.auto-close (0, seconds until a door shuts itself), doors.max-doors (-1, no ceiling per player), doors.cooldown-ms (0, refuse a second toggle this soon), doors.sound (true), doors.sound-range (15 blocks, and 0 means only the user hears it), doors.announce (true, say so when a door moves with nobody behind it). doors.land (relations: off, build or relations - asked at both ends of a move, because the blocks are leaving somewhere as well as arriving) with doors.respect-claims (true). doors.carry-blacklist (a door carrying one of these refuses to move, naming it), doors.carry-whitelist (when not empty, only these may be carried) and doors.clear-list (what a door may land on top of, so it can never overwrite somebody's build). Every line the module says is under doors.messages - the opened/closed lines, the refusal for each of the reasons above, the pause pair, the rename, the autoclose and notify answers, and the two states /door fill refuses.

Commands. /door (aliases doors, bigdoors): new, corner, move, type, trigger, open, close, toggle, info, list, mine, rename, autoclose, notify, fill, debug, pause, stop, delete. pause and stop are the admin half; debug prints the record's own keys.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/doors.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help doors prints the same names and the same nodes, line for line, because both read this one registration.

/door (/doors, /bigdoors)

permission: none of its own · always · Doors.java

Big doors that move their own blocks: a portcullis that rises, a drawbridge that drops, a sliding wall or a door that opens in place. Everything is done by looking at the world and naming the door, because a door here is a record with a box, a direction and a distance rather than a single block. /door list prints every door with whether it is open, and mine [player] prints the doors an account owns with each one's size, direction and family - the second word of mine is a player, so listing somebody else's doors is the admin node. Building one: new <name> <family> (families are sliding, portcullis, drawbridge and door) starts it at the block you are looking at, corner <name> sets the other corner of its box, move <name> sets how far and which way it travels, type <name> changes the family, and trigger <name> sets what opens it. Then rename <name> <newName>, autoclose <name> <seconds> (0 never closes it, -1 follows doors.auto-close) and notify <name> <on|off> (the server default is doors.announce). From there open, close and toggle work one door by name, fill <name> fills an unopened door's box with stone before its first move so it cannot be walked through halfway, info describes one door, and debug <name> dumps the record as doors.yml holds it - which is the answer to "why will this door not move" an operator can paste somewhere without reading a stack trace first. Two console verbs exist for a lagging server: pause [on|off] stops every door moving and stop closes every open door; stop works while movement is paused on purpose, since that is the moment it is reached for, and a door the land refuses is named rather than left standing open. Doors obey claims and factions through the module's land settings, and doors.max-doors and doors.max-size are what one account may build and how many blocks one door may cover.

Permissions. None - every door verb is available to players, and what stops a door crossing somebody else's land is doors.land and doors.respect-claims rather than a node.

drops - extra mob drops from config

What it does. Adds drops to a mob's death: when a player kills a living entity, the rows under drops.<entity type> (or drops.default when that type has no list of its own) are rolled and the winners are added to what the mob would have dropped anyway. Each row is a material, an amount and a chance, so the same table covers a guaranteed extra ingot and a rare emerald.

Turning it on. modules.drops.enabled: true. The shipped drops.default is an empty list, so nothing extra drops until you write rows - and a type with no list falls back to default, which is what makes that empty list a safe starting point.

What you can configure. drops.default and one list per entity type, e.g. drops.zombie. The reader splits each row on : - MATERIAL:amount:chance, which is the shape /customdrops prints for it - so a row written some other way is named at check time as a material this server does not have rather than silently skipped, and a row may stop early: a bare MATERIAL is one of it, always, and MATERIAL:amount drops that many every time. The amount is one whole number (a 1-2 there is not a range) and the chance is 0..1, which is named too when it falls outside, since that means the row always or never drops. Write the bare material name: a namespace (minecraft:emerald) would split at its own colon and be read as the material minecraft.

Commands. /customdrops (alias drops) lists the tables in effect.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/drops.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help drops prints the same names and the same nodes, line for line, because both read this one registration.

/customdrops (/drops)

permission: ppcore.drops.admin · always · Drops.java

Where extra mob drops are configured, and nothing else: the command prints the shape of the config and takes no argument, because adding a drop is an edit to config.yml rather than a verb a player should be able to run. Rows live under drops.<ENTITY_TYPE> in the form ITEM:amount:chance - for example DIAMOND:1:0.05 for one diamond on one kill in twenty - and anything not named for that mob falls back to drops.default, so a server can add one interesting drop to every mob with a single line. A row of just the material drops one of it on every kill, ITEM:amount drops that many, and ITEM:amount:chance rolls for it; the chance is read as 0 to 1, and a number outside that range drops always or never, which the module's own check reports rather than leaving an owner to work it out from a chest that fills immediately. Drops are rolled for kills: a mob that dies of fire, a fall or another mob's hit adds nothing, so a farm that kills by itself does not pay. A material this server does not have is reported too, rather than passing silently.

Permissions. ppcore.drops.admin for the listing command; the drops themselves need no node.

editor - the in-game editor: settings and kits without a file

What it does. Opens the panel's editors inside a chest window, so a person standing in the world looking at the thing they want to change does not have to open a browser. /editor settings is the settings catalogue the browser's form is drawn from - one row per editable leaf, with its group, its label, its type and its current value - and /editor kits is the kit editor, seeded from the same rows the browser's kit editor uses. The left column is the sections, the middle is a page of twenty-five fields, and the right column is a live preview: the kit's items are shown as items, and what is about to be written is shown before it is written. Nothing is written until Save & apply, so a mis-click is an unsaved draft rather than a change, and the save writes the files and applies them in the same breath. A value that is not a toggle is asked for in chat - the window closes, the change is named, and the next line you type is the value; cancel is always a valid answer, and a value the type cannot hold is refused with the reason.

Turning it on. modules.editor.enabled: true. It is staff-only and there is nothing for a player to do here, so the node (ppcore.editor) is the whole gate. Nothing needs preparing.

What you can configure. Nothing of its own: this module owns no config. What it edits is the panel's own catalogue, written back as pins in panel-settings.txt (the file the panel's settings overlay already reads) and kits in panel-kits.yml. That is the answer to "which editor wins" - neither: a value typed in game is a pin, exactly the kind of fact a value typed in a browser is, and the two appear in each other's editors with no merge step. The record editors that are not a list of scalars (models, skills, quests, spawners, cooking, dungeons, menus) have no screen here yet.

Commands. /editor [settings|modules|kits] (aliases ingameeditor, ige).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/editor.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help editor prints the same names and the same nodes, line for line, because both read this one registration.

/editor (/ingameeditor, /ige)

permission: ppcore.editor · always · Editor.java

The settings editor inside the game: the same things the panel writes, as chest menus, for the operator who is sitting at the server rather than at the browser. /editor with nothing after it opens the home screen. settings [group] is the config, grouped the way the file is - naming a group (for example settings Economy) opens straight onto that group's fields, which is what makes the screen scriptable and what the tab completer is for. modules is one switch and feature list per module, which is the quickest answer to "what is this server actually running". kits [kit] is the kit builder, and spawners, cooking, quests, jobs and dungeons open those tables' records, each with an optional table name after the verb so a screen can be reached in one command. cancel throws away everything typed and not applied, closing the screen and writing nothing - which is why the reply says so out loud. It is players only, since every screen is an inventory, and it needs ppcore.editor; a screen opened from the console is the one thing this verb cannot do, and the panel is what the console uses instead.

Permissions. ppcore.editor.

Files behind this page

What this section documentsSource
The anvil cost cap and its pinpkg/src/ppcore/AnvilPlus.java (../../pkg/src/ppcore/AnvilPlus.java), anvil: in pkg/config.yml (../../pkg/config.yml)
Binding a command to an itempkg/src/ppcore/AttachCmd.java (../../pkg/src/ppcore/AttachCmd.java)
The player and ban menuspkg/src/ppcore/BanGui.java (../../pkg/src/ppcore/BanGui.java), bansgui: in pkg/config.yml (../../pkg/config.yml)
Bounties, their board and the payoutpkg/src/ppcore/Bounty.java (../../pkg/src/ppcore/Bounty.java), bounty: in pkg/config.yml (../../pkg/config.yml)
The race button and the one-time buttonpkg/src/ppcore/Button.java (../../pkg/src/ppcore/Button.java), button: in pkg/config.yml (../../pkg/config.yml)
The colour-code pagepkg/src/ppcore/Colors.java (../../pkg/src/ppcore/Colors.java)
Pointing a compasspkg/src/ppcore/Compass.java (../../pkg/src/ppcore/Compass.java)
Stations, recipes, stages and the customization layerpkg/src/ppcore/Cooking.java (../../pkg/src/ppcore/Cooking.java), cooking: in pkg/config.yml (../../pkg/config.yml)
Crate pools, keys, windows and the revealpkg/src/ppcore/Crates.java (../../pkg/src/ppcore/Crates.java), crates: in pkg/config.yml (../../pkg/config.yml)
Handing an item to another playerpkg/src/ppcore/Donate.java (../../pkg/src/ppcore/Donate.java)
Doors: the selection, the mover and the refusalspkg/src/ppcore/Doors.java (../../pkg/src/ppcore/Doors.java), doors: in pkg/config.yml (../../pkg/config.yml)
Extra mob dropspkg/src/ppcore/Drops.java (../../pkg/src/ppcore/Drops.java), drops: in pkg/config.yml (../../pkg/config.yml)
The in-game editor over the panel's own cataloguepkg/src/ppcore/Editor.java (../../pkg/src/ppcore/Editor.java), pkg/src/ppcore/Panel.java (../../pkg/src/ppcore/Panel.java)
The batch's switches and the precedence rule every entry inheritsmodules: in pkg/config.yml (../../pkg/config.yml), chapter 12

Protections, effects and storage

These thirteen modules are the second half of what config.yml calls the compatibility batch: the source plugins whose whole remaining gap against this core was one command's worth of behaviour, so the module is most of the port. They ship switched on (a module that ships off is a feature an owner never sees) and their switches are together in the tail of the modules: block, so chapter 12 is where the shared rules are: the switch, the precedence of your edit over the profile, and the commands: map that renames or switches off any command on its own.

Most of these are one command plus a small section. Seven of the thirteen have no section in config.yml at all - effect, giftpet, killall, kiteditor, lockip, merchant and notarget - and their entries say so rather than sending you looking for keys that do not exist. The rest keep their settings under a section whose name is usually, but not always, the module id: flyc reads flycharge:, and lootchests is lootchests:.


effect - apply a potion effect

What it does. Puts a potion effect on a player from the console or from staff chat, which is the one job the source plugin existed for. The verb is /effect (alias peffect), and it also carries the vanilla spellings, so /effect give <player> <type> and /effect clear <player> <type> work the way an operator already types them.

Turning it on. modules.effect.enabled: true (it ships on). Nothing else is needed.

What you can configure. Nothing. There is no effect: section in config.yml and no key anywhere that changes what the command does: the player, the effect type, the duration and the amplifier all arrive as arguments. Its command can still be renamed, given aliases or switched off under the commands: map described in chapter 12.

Commands. /effect <player> <type> [seconds] [amp], /effect give|clear <player> <type>.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/effect.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help effect prints the same names and the same nodes, line for line, because both read this one registration.

/effect (/peffect)

permission: ppcore.admin.effect · always · EffectCmd.java

Apply a potion effect, and take one away. /effect <player> <type> [seconds] [amplifier] is this plugin's own form; /effect give <player> <type> [seconds] [amplifier] and /effect clear <player> [type] are the vanilla verb shapes, and both are accepted because this command owns the name /effect and the server's own one never runs. The amplifier counts from 0, the duration defaults to 30 seconds when it is left out, and a trailing true (the vanilla hide-particles flag) is accepted and ignored rather than read as an amplifier. The target may be a selector (@a is the shape a suite or a console script uses) or one exact name, so a typo is refused with Not online. instead of silently matching a prefix. /effect clear takes every effect off the target, or only the type you name.

Permissions. ppcore.admin.effect.

enchantlock - block named enchantments from being applied

What it does. Refuses to apply an enchantment the server has blocked, wherever it would be applied

switch, and it is a list rather than a mood: the command writes the list.

Turning it on. modules.enchantlock.enabled: true (it ships on). Add names with /enchantlock <name> - each entry may be an enchantment name or its key.

What you can configure. enchantlock.blocked - the list itself, shipped []. /enchantlock <name> writes into this list and /enchantlock list reads it back, so what the command changes is a setting you can also edit by hand.

The source plugin also asked the two questions in the integrations: block (a third-party reporter, and registering with a placeholder plugin). That block is in this file for exactly that reason: integrations.report is the switch this core answers with, and off it means the panel has no live card for this server.

Commands. /enchantlock <name>|list (alias disableenchant).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/enchantlock.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help enchantlock prints the same names and the same nodes, line for line, because both read this one registration.

/enchantlock (/disableenchant)

permission: ppcore.ench.lock · always · EnchantLock.java

Stop named enchantments from being applied, at the enchanting table and at an anvil. /enchantlock <name> adds one to enchantlock.blocked, taking either the name a player sees (sharpness) or the code name (minecraft:sharpness), and writes it through as a pin so the next reload does not drop it again. /enchantlock on its own, or /enchantlock list, prints what is blocked. At the table the enchantment is removed from the offer before it is added, so the item simply comes out without it; in an anvil the result is refused outright, which the player sees as no result at all. The list is read where it is used rather than cached, so a change takes effect on the next enchantment rather than at the next restart.

Permissions. ppcore.ench.lock.

flags - world flags: pvp, explode, mob-spawn, workbenches, enderchest

What it does. Per-world answers to the questions a region plugin would answer: is PvP on here, may blocks explode, do mobs spawn, may a workbench be used, may one be put down, and does an ender chest open. The two workbench keys are separate on purpose - an arena world usually wants a bench usable and not buildable - and they are two decisions everywhere they appear, as a region flag is.

Turning it on. modules.flags.enabled: true (it ships on), then /flag <name> <true|false> while standing in the world you mean. The stored answer belongs to that world; the file's keys are the default a world with no stored answer falls back to.

What you can configure. flags.pvp, flags.explode, flags.mob-spawn, flags.workbenches, flags.workbenches-placement and flags.enderchest, all shipped true. flags.enderchest is about the chest block itself rather than the /enderchest command.

Commands. /flag <pvp|explode|mob-spawn|workbenches|workbenches-placement|enderchest> <true|false> (aliases wgflag, wgefp).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/flags.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help flags prints the same names and the same nodes, line for line, because both read this one registration.

/flag (/wgflag, /wgefp)

permission: ppcore.flags.admin · always · Flags.java

The world flags - pvp, explode, mob-spawn, workbenches, workbenches-placement and enderchest - answered per world. /flag <name> <true|false> sets one for the world you are standing in and remembers it in this module's own flags.yml; /flag <name> on its own reports the value and where it came from, this world's own record or the flags.<name> default in config.yml, which is the first thing to look at when a flag behaves differently from the file you edited. A name that is not one of the six is refused rather than stored: the older shape wrote any word it was handed, so a typo looked like a successful setting and changed nothing at all. Setting pvp also sets the world's own PvP rule.

What each flag answers is the question its name asks - whether explosions may break blocks, whether mobs may spawn naturally (spawns the server or a plugin asks for are left alone), whether crafting tables may be used, whether they may be placed, and whether ender chests may be opened. A refusal in game names the flag that turns it back on, because a silent refusal reads as a broken block. The console must give both arguments, since it has no world to stand in, and every flag also has a default that applies to any world that has never been set: flags.<name> in config.yml.

Permissions. ppcore.flags.admin.

flyc - timed flight charges

What it does. Flight that is bought by the second rather than granted: /flyc give <player> <seconds> hands out a block of flight time and the module runs the clock. When the block runs out, the player's own /autorecharge mode decides what happens next - exp, money, off or on - and with a mode chosen the same clock buys another block from the player's own experience or money instead of the flight simply ending. A mode that cannot be paid falls through to the ordinary ending, so nothing is ever spent for nothing.

Turning it on. modules.flyc.enabled: true (it ships on) and /flyc give <player> <seconds>. /autorecharge (alias /flyrecharge) is the player's switch; ppcore.flyc.auto.others lets staff set it for somebody else.

What you can configure. flycharge.autorecharge.seconds: 300 (how much flight one recharge buys), flycharge.autorecharge.exp: 30 and flycharge.autorecharge.money: 100.0 (the cost per recharge, not per second; 0 makes that mode free, which is still a mode rather than a price). The section is flycharge:, not flyc:.

Commands. /flyc give <player> <seconds> (alias flightcharge), /autorecharge <exp|money|off|on> [player].

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/flyc.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help flyc prints the same names and the same nodes, line for line, because both read this one registration.

/flyc (/flightcharge)

permission: ppcore.admin.flyc · always · FlyCharge.java

Timed flight. /flyc give <player> <seconds> gives a player flight for that many seconds: the grant is recorded against their id, flight is switched on immediately, and a check that runs every second ends it

saying the charge has ended. The clock is stored in flyc.yml, so a restart resumes what is left of it instead of handing out flight for ever or cutting it short. The player must be online to be given one, and the seconds are a whole number: anything else is refused with Need seconds. rather than silently read as zero. This module sells nothing - it is the timed grant itself, and a server that wants to charge for flying wants the economy's own verb or an Admin temporary flight instead.

Permissions. ppcore.admin.flyc, and ppcore.flyc.auto.others for setting somebody else's mode.

giftpet - give ownership of a nearby tamed mob

What it does. Hands a nearby tamed mob to another player, so a pet is transferable without a summoning ritual or a second copy of it.

Turning it on. modules.giftpet.enabled: true (it ships on). Stand near the pet and name the player.

What you can configure. Nothing - there is no giftpet: section and no key behind the verb.

Commands. /giftpet <player>.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/giftpet.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help giftpet prints the same names and the same nodes, line for line, because both read this one registration.

/giftpet

permission: none of its own · always · GiftPet.java

Hand a pet over. /giftpet <player> finds a tamed mob within six blocks of you that you own, makes the named player its owner, and tells them who gave it to them; everything else tameable nearby is ignored, so in a crowd of pets the first one that is yours is the one given away. If you own none within reach it says so rather than looking further afield, which keeps "give this pet" a thing you do standing next to it. The named player must be online. Players only, and it asks for no node of its own: a pet you own is yours to give away.

Permissions. None: the command is open to any player.

killall - remove mobs in a world

What it does. Empties a world of mobs - the clean-up verb an operator reaches for after a raid goes wrong or a spawner runs away. Run with no argument it works on the world you are in.

Turning it on. modules.killall.enabled: true (it ships on).

What you can configure. Nothing.

Commands. /killall [world] (aliases butcher, killmobs).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/killall.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help killall prints the same names and the same nodes, line for line, because both read this one registration.

/killall (/butcher, /killmobs)

permission: ppcore.admin.killall · always · KillAll.java

Remove the mobs in a world. /killall clears the world you are standing in and /killall <world> names one; the console has no world of its own and must name one, or it is told there is no world. Every living entity that is not a player is removed, and the answer says how many went, which is the number a lagging server is looking at. Nothing is dropped and nothing is charged, and pets, villagers and named mobs go with the rest - so name the world deliberately, and reach for the cheaper cleanup first when the problem is a floor covered in drops rather than an overpopulation of mobs.

Permissions. ppcore.admin.killall.

kiteditor - save the hotbar as a kit in config

What it does. Saves what you are holding - the whole hotbar - as a kit under a name you choose, so a kit can be built by playing rather than by editing a file. The kit it writes is a kit like any other.

Turning it on. modules.kiteditor.enabled: true (it ships on). Build the hotbar, then /kiteditor save <name>.

What you can configure. Nothing: the verb's two settings are the name and the inventory it reads. The kit it saves is content, and content lives in the plugin's own files rather than in this one.

Commands. /kiteditor save <name> (alias editkit).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/kiteditor.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help kiteditor prints the same names and the same nodes, line for line, because both read this one registration.

/kiteditor (/editkit)

permission: ppcore.kit.admin · always · KitEditor.java

Save what you are carrying as a kit, from in game. /kiteditor save <name> writes your whole inventory into panel-kits.yml - the panel's own kit file, the same one the browser's kit editor writes, in the panel's own material:amount rows - fills a new row with the three fields the editor's form carries (no permission, a one hour cooldown, not one-time), reloads the modules, and tells you that /kit <name> works now. A kit of that name is replaced rather than duplicated, and the rows are seeded from the kits this server has already loaded, so saving one kit does not delete the kits that came from config.yml. Items are written in lower case on purpose: a kit made here and the same kit made in the browser have to be the same text. It needs the panel half of the plugin to be up, and says so when it is not. Hold everything you want in the kit when you run it, and remember that the cooldown of a brand new kit is one hour until somebody edits it.

Permissions. ppcore.kit.admin.

limiter - cap living entities per chunk

What it does. Holds the number of living entities in a chunk down to a ceiling, removing what is above it as the chunks tick, oldest first - so a mob farm, a bad spawner config or an unattended XP grinder cannot take a server down while nobody is watching.

Turning it on. modules.limiter.enabled: true (it ships on). The cap is read from the file.

What you can configure. limiter.per-chunk: 40. This is the ceiling the module enforces and the value /limiter <cap> sets when a staff member changes it in place.

Commands. /limiter [cap] (alias entitylimit).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/limiter.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help limiter prints the same names and the same nodes, line for line, because both read this one registration.

/limiter (/entitylimit)

permission: ppcore.limiter.admin · always · Limiter.java

Cap the living entities in one chunk. /limiter reports the cap and how many loaded chunks are over it; /limiter <number> sets the cap (limiter.per-chunk, at least 1) and writes it through as a pin, so the next reload does not put the old number back. The cap is enforced at the spawn rather than afterwards: a natural spawn in a chunk already at the cap is refused, so nothing that is already alive is ever taken away - which is also why there is no removal pass and nothing to clean up after. Spawns the server or a plugin asks for by name are exempt, which is what keeps a spawner, a pet or an event's own mob working under a cap. The report is a reading rather than a promise: it counts the chunks the server currently has loaded, so a quiet server reports few chunks and a busy one reports many.

Permissions. ppcore.limiter.admin.

lockip - lock an account to the IP it last used

What it does. Remembers the address an account last played from and refuses it from anywhere else until staff lift the lock - the cheap answer to a stolen account for a server that does not want a full login system.

Turning it on. modules.lockip.enabled: true (it ships on). /lockip <player> locks an account; /lockip off <player> lifts it.

What you can configure. Nothing. The address it remembers is state, not a setting.

Commands. /lockip <player>|off <player>.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/lockip.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help lockip prints the same names and the same nodes, line for line, because both read this one registration.

/lockip

permission: ppcore.staff.lockip · always · LockIp.java

Tie an account to the address it is using now, so it cannot be played from anywhere else. /lockip <player> records that player's current address with the lock; from then on a login from a different address is refused with This account is locked to another IP. and never reaches the world. /lockip off <player> takes the lock off and lets them in from anywhere again. The player has to be online, because the address recorded is the one they are connected from - this is a lock you apply while its owner is in front of you, not one set from a list of addresses - and a player whose address the server cannot read is refused rather than locked to nothing. The lock lives in iplock.yml and survives restarts and reloads, so it is not something to apply casually to an account on a dynamic address.

Permissions. ppcore.staff.lockip.

lootchests - placed loot chests that refill from a template

What it does. A chest a staff member marks becomes a loot chest: it refills from a template on a clock, so a dungeon, a camp or a roadside cache is stocked again without an operator visiting it. A template is a name, a description, a slot count and a pool of weighted rows, and a row may be a vanilla material or a custom item of this core by id - @frostblade is the shipped item - so a chest can drop gear with the core's enchantments and worn effects on it rather than a renamed material. The weight is the row's share of the draw and it is honoured (BREAD:20:4 is weight 20, amount 4).

Forty-four templates ship in the jar, in twelve packs you switch on by line; only ids that are absent are written, so a template you define yourself survives a jar upgrade whole.

Turning it on. modules.lootchests.enabled: true (it ships on), then /lootchest set [template] while looking at a chest. /lootpool [pack] lists the templates a pack gives you.

What you can configure. lootchests.slots: 5 (rows per refill for a template naming no count), lootchests.refill-seconds: 300 (0 means hand-refilled only, by /lootchest fill), lootchests.autoloot: false (the default for the per-player /autoloot switch), lootchests.library.enabled and the twelve lootchests.library.packs.* switches (ten on, nether, end and vault off), and lootchests.templates.<name> with name, desc and rows.

Commands. /lootchest set [template] and its verbs (aliases randomloot, lc, lootchestset), /lootpool [pack] (alias loottemplates), /autoloot [on|off] (no permission - it is a player's own setting).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/lootchests.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help lootchests prints the same names and the same nodes, line for line, because both read this one registration.

/lootchest (/randomloot, /lc, /lootchestset)

permission: ppcore.loot.admin · always · LootChests.java

Chests that refill themselves from a loot table. A chest is marked by looking at it, and the mark is kept by world and coordinates, which is also the spelling every report prints. /lootchest with nothing after it lists the marked chests (list does the same) with each one's template, how many times it has filled and when the next fill is due. set <template> marks the chest you are looking at as that template's and unset clears the mark. fill fills one now; from the game it fills the chest you look at, and from the console - which has no eyes - it takes the chest as its last word (<world>,<x>,<y>,<z>), which the refusal tells you when it is missing. info <template> prints one template: its name, its row count, its slot range and every row with its amount and weight, including a custom item that no longer exists, which is the one row an operator needs to see marked rather than missing. library [pack] prints the chest library this core can draw from, or lists one pack's templates, and templates is the way to reach the template catalogue (the same list /lootpool prints). How often a chest refills is lootchests.refill-seconds and how many slots of it a fill may use is lootchests.slots.

/lootpool (/loottemplates)

permission: ppcore.loot.admin · always · LootChests.java

The catalogue of loot templates: one line per template with its id, its name, how many rows it has, the slot range it fills and its total weight - plus whether it comes from your own config.yml or from a shipped pack. Naming a prefix narrows the list, which is how an operator finds a template in a library of hundreds. The weight total is the number worth reading here: a table whose weights sum to zero draws nothing, and a table whose weights look wrong is a table that was meant to be edited.

/autoloot

permission: none of its own · always · LootChests.java

Whether a marked loot chest goes straight into your pack when you open it, instead of asking you to take it out. /autoloot on its own reports your own setting and says so plainly when you have never chosen one and are still on the server's default; on and off set it and remember it for your account across restarts; status is the same report without changing anything. It takes any other word as a mistake and says which three it wanted.

Permissions. ppcore.loot.admin.

market - a rotating black-market listing

What it does. A listing of goods with prices, opened with /blackmarket, that an operator curates by editing rows. The source plugin's shape is kept without its rotating clock, and a row that is bought is re-rolled when it sells out, so the board is a stock that thins rather than a shelf that empties.

Turning it on. modules.market.enabled: true (it ships on). /blackmarket buy <id> buys a row directly.

What you can configure. market.list.<id> with item, amount and cost. Two hand-written rows ship (tnt, pearls) and they are never touched by the generator; between the presets: market.list and presets markers sit 392 generated rows built from the server's own material and entity lists when the jar is built, and the build refuses to ship a regenerated block that has drifted.

Commands. /blackmarket [buy <id>] (aliases bm, darkshop).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/market.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help market prints the same names and the same nodes, line for line, because both read this one registration.

/blackmarket (/bm, /darkshop)

permission: none of its own · always · Market.java

A shop the owner writes by hand. /blackmarket (or /blackmarket list) prints every listing as id - item for cost, and /blackmarket buy <id> takes that cost out of your balance and puts the item in your inventory, amount at a time. The listings are market.list.<id> rows in config.yml with item, amount and cost, and they are the owner's list and nothing else: there are no built-in rows and no fallback, so a server that has configured none gets a list with nothing in it rather than a default nobody priced. A row naming a material this server does not have is refused before the money moves, and <command> check reports the same row at boot so it is found before a player finds it. A purchase needs the economy to be running and your balance to cover the cost, an id that is not on the list is refused by name, and the command is for players.

Permissions. None: the board is open to any player.

merchant - open a simple villager trade window

What it does. Opens a villager trade window for a player from the command line, which is the one thing the source plugin did - useful for a scripted shop, a reward or a scene, where a real villager would be a second entity to place and protect.

Turning it on. modules.merchant.enabled: true (it ships on).

What you can configure. Nothing: the offers are the window's, and this module owns no file.

Commands. /merchant (alias tradeui).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/merchant.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help merchant prints the same names and the same nodes, line for line, because both read this one registration.

/merchant (/tradeui)

permission: none of its own · always · MerchantCmd.java

Open a villager's trade window with no villager in it. /merchant shows a player the one trade this module carries - five emeralds for a diamond, usable sixteen times in that window - and asks for no permission of its own. It is the shape a dialog, an NPC script or another module calls when it wants a trade screen with no entity behind it, so what it offers is what the code offers rather than anything from a config file; a server that wants its own prices and shelves wants the shop module instead, and one that wants two players trading wants /trade.

Permissions. None.

notarget - mobs ignore a player

What it does. Makes mobs stop hunting a player - a per-player switch a staff member or a player can turn on and off, which is what the module exists for. Run with no argument it toggles your own.

Turning it on. modules.notarget.enabled: true (it ships on).

What you can configure. Nothing.

Commands. /notarget [player].

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/notarget.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help notarget prints the same names and the same nodes, line for line, because both read this one registration.

/notarget

permission: ppcore.admin.notarget · always · NoTarget.java

Make the mobs ignore a player. /notarget toggles it for yourself and /notarget <player> for somebody else; switching it on also drops the target of every mob currently chasing them, so the mobs that are already on them stop rather than finishing the fight first. The answer says which way the toggle went, and a name that is not online is refused instead of being remembered for later. The state is held in memory only, so a restart - or a reload that rebuilds this module - puts every player back in the mobs' sights, which is deliberate for a verb like this: it is not an invisibility anybody has to remember to switch off.

Permissions. ppcore.admin.notarget.

Files behind this page

What this section documentsSource
effect - the verb and its permissionpkg/src/ppcore/EffectCmd.java (../../pkg/src/ppcore/EffectCmd.java)
enchantlock - the blocked list and where it is enforcedpkg/src/ppcore/EnchantLock.java (../../pkg/src/ppcore/EnchantLock.java)
flags - the six flags and the per-world answerspkg/src/ppcore/Flags.java (../../pkg/src/ppcore/Flags.java)
flyc - the flight clock and the recharge modespkg/src/ppcore/FlyCharge.java (../../pkg/src/ppcore/FlyCharge.java)
giftpet, killall, kiteditor, lockip, notarget - the command-only modulespkg/src/ppcore/GiftPet.java (../../pkg/src/ppcore/GiftPet.java), pkg/src/ppcore/KillAll.java (../../pkg/src/ppcore/KillAll.java), pkg/src/ppcore/KitEditor.java (../../pkg/src/ppcore/KitEditor.java), pkg/src/ppcore/LockIp.java (../../pkg/src/ppcore/LockIp.java), pkg/src/ppcore/NoTarget.java (../../pkg/src/ppcore/NoTarget.java)
limiter - the per-chunk cappkg/src/ppcore/Limiter.java (../../pkg/src/ppcore/Limiter.java)
lootchests - marked chests, templates and the library packspkg/src/ppcore/LootChests.java (../../pkg/src/ppcore/LootChests.java), pkg/src/ppcore/ChestLibrary.java (../../pkg/src/ppcore/ChestLibrary.java)
market - the listing and the generated preset rowspkg/src/ppcore/Market.java (../../pkg/src/ppcore/Market.java)
merchant - the trade windowpkg/src/ppcore/MerchantCmd.java (../../pkg/src/ppcore/MerchantCmd.java)
Every key and shipped value quoted abovepkg/config.yml (../../pkg/config.yml)

Players, mobs and world state

These are the modules that act on a player rather than on the world or on a menu: an order board, a parkour course, a staff patrol, a downed state, the join gift, a save on demand, a contraband scan, a projectile, a container opened without the lid, a remembered skin, a sound, and dropped items merged on the ground. orders and parkour are module-sized - each has its own command surface, its own file and settings worth reading; the rest are the compatibility batch, where one closed gap is most of the module, and several have nothing to configure at all.

Two rules apply to nearly every entry here and are not repeated in each one. Every command can be renamed or switched off on its own through the commands: block of config.yml (commands.<id>.enabled, .name, .aliases), and every one of them answers <command> check - /order check, /parkour check, /orestack check - when the caller holds ppcore.check. The switch itself, and the precedence of your edit over the profile, are in chapter 12.

orders - player buy orders: money held, items handed over

What it does. An order board. A player posts money for an item they want, and another player who has the item takes the money and hands the item over - so a player who needs 500 cobblestone asks for it instead of shouting in chat, which is the other direction from the auction house. The money is held from the moment the order is posted, which is what makes the payout real rather than a promise, and it returns to the buyer exactly once when the order is cancelled or expires. A partial fill pays a pro-rata share and leaves the exact remainder on the order, so the money paid out over the life of an order adds up to the money held for it. The source plugin behind this module is NOrder.

Orders are read two ways: /order opens the board as a chest window (a listing page, and a confirm window before a fill), and /order list [page] prints them in chat. The board prints a large price with the suffixes from orders.format.suffixes - k, m, b - while the amount held and paid is always exact. Nothing is ever dropped on the floor: a buyer who was offline, or whose inventory was full, has their items kept until /order collect.

Turning it on. modules.orders.enabled: true, which is the shipped default. It needs the econ module for money: with no economy it refuses to hold anything and says so ("this server has no economy, so an order cannot hold money") rather than posting an order that could never pay. Nothing has to be created first - the board is empty and the first order is the first row. Orders live in orders.yml in the plugin's folder and survive a restart, and the expiry sweep runs on its own clock rather than only when the server stops.

What you can configure. orders.max-per-player (5; -1 is no ceiling), orders.min-amount/orders.max-amount (1..2304 - 36 stacks, so an unfillable order cannot fill the board), orders.min-pay/orders.max-pay (1.0..1000000000.0, the ceiling so a typo cannot freeze a number field), orders.expires-hours (48.0 - written as a double on purpose: the panel takes a setting's type from this default, so a whole number would make the row an integer field and refuse a minutes-scale expiry), orders.expire-check-minutes (5), orders.blacklist (materials that may not be ordered at all: bedrock, barrier, the three command blocks, structure block, jigsaw, spawner, bundle, elytra), orders.format.suffixes, and the 24 sentences under orders.messages.* (posted, filled, part, received, cancelled, expired, the refusals, and the two window titles board-title and confirm-title).

Commands. /order (aliases orders, norder, donutorders) with board (the default), list [page], mine (your own orders and holds), collect (items that could not be handed over), info <id>, fill <id> [amount], cancel <id>, create <item> <amount> <pay>, and purge (staff: expire everything already due).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/orders.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help orders prints the same names and the same nodes, line for line, because both read this one registration.

/order (/orders, /norder, /donutorders)

permission: ppcore.orders.use · always · Orders.java

An order board, and the money in it behaves differently from a shop: a player posts money for an item they want, and whoever brings that item takes the money. Posting holds the price out of the buyer's balance immediately - in one step, before the record is written - so the payout exists before anybody fills it, and the hold comes back to the buyer when the order is cancelled or expires, exactly once.

Typed with nothing after it, /order opens the board as a menu: one entry per open order showing what is wanted, the price, who is asking and how long is left, a click to confirm a fill, a shift-click to hand over a single one, and buttons for paging, your own orders, collect and close. Every verb is also typed: /order create <item> <amount> <pay> posts one, /order list [page] prints the board as chat lines (eight to a page, newest hold first, with the per-item price), /order info <id> is one order in full, /order fill <id> [amount] hands over the items and pays you on the spot, /order cancel <id> takes your own order down and returns the hold, /order mine lists what you posted, /order collect hands over what is waiting for you, and /order purge expires everything already due right now - the same path the clock takes, so an operator cannot pay a hold back twice by a second route.

The shape of an order is bounded by config: orders.min-amount (1) and orders.max-amount (2304), orders.min-pay (1) and orders.max-pay (1e9), orders.max-per-player (5, or -1 for no limit), orders.blacklist of items that may not be asked for, and orders.expires-hours (48). A background clock every orders.expire-check-minutes (5) sweeps the board, and expiry returns the hold the way a cancel does. Filling your own order is refused - money would only move from one of your pockets to the other, and the board would become a way to launder a hold back into a balance.

Two rules make the money add up. A partial fill pays a pro-rata share and leaves the exact remainder on the record, so what an order pays out over its life is exactly what it held; and a fill takes the items, then pays, then delivers, so an interruption leaves items with a record that still holds money rather than money with no items. Nothing is dropped on the floor: a buyer who is offline, or whose inventory is full, has the goods kept for /order collect. The board's wording is orders.messages.* keyed by the reason, and orders.format.suffixes shortens large prices on the board only - the amount ordered, held and paid is always the exact number. Seeing the board needs ppcore.orders.use, posting needs ppcore.orders.create, and cancelling somebody else's order or purging needs ppcore.orders.admin. Orders live in orders.yml; when you fill one, or when a page of them is empty, the board also reports what it has refused since boot, because "why does nothing here fill" is a question about a reason rather than about a record.

Permissions. ppcore.orders.use (see and fill), ppcore.orders.create (post - the shipped default group has both), ppcore.orders.admin (cancel somebody else's order, and purge).

parkour - parkour courses: plates, timers and a leaderboard

What it does. Courses, built out of plates in the world, in the order a runner meets them: the start, the checkpoints, the finish. An operator gets a compass, marks the plates and names the course; a runner who steps on the first plate starts the clock. A fall sends them back to their last checkpoint with the clock still running - a course is usually above nothing, and a fall that ended the run would make long courses unplayable - while a death ends the run, because the respawn point is not the course and a time set that way is a time nobody ran. The clock always counts; the reward is paid once per player. Courses, records and completions live in parkour.yml; the runs themselves are in memory.

The module also owns a scoreboard objective of its own (pkrun by default), created on the main board when it starts, so a course reward that runs scoreboard players add {player} pkrun 1 writes into an objective that exists and whose value survives a restart. The sidebar is the runner's: up when a run begins, down on finish, leave, death or disconnect. The source plugin is Parkour 1.0.0 (Spaleforce).

Turning it on. modules.parkour.enabled: true (the shipped default), then /parkour in game: /parkour wand gives the marking compass, right-click the plates in order, /parkour create <name>. Three features are switchable separately under modules.parkour.features.*: wand (off, courses can only be built with /parkour create on the plate you are looking at), hud (the running line on the action bar) and scoreboard (the runner's sidebar). A course needs a plate rule: parkour.require-plate: true refuses to mark a block that is not a pressure plate, because a course you can trigger by walking over an unmarked block is not a course; false is for a server built out of stone slabs. start-on-plate is what makes a course playable by a player who has never read /parkour help. /parkour check names a course that cannot be run (no finish, a reward for a course that does not exist) rather than leaving it to be found by a runner.

What you can configure. parkour.wand-material (COMPASS), parkour.wand-name, parkour.wand-seconds (60 - the compass takes itself back), parkour.selection-cooldown-ms (500, so two clicks inside it are one mark), parkour.require-plate (true), parkour.plate-blocks (the golden and iron weighted plates ship; add any other plate this server has), parkour.start-on-plate (true), parkour.fall-reset (true) with parkour.fall-drop (4 blocks below your last checkpoint is a fall; false still sends you back, with no count), parkour.once-per-course (true - the reward is once per player, while the clock always counts, because a course whose time can only be set once has no leaderboard), parkour.top-size (10), parkour.hud-ticks (20), the parkour.scoreboard block (objective, enabled, title, and the lines list with {course}, {runner}, {time}, {seconds}, {plate}, {total}, {falls}, {best} and {score}), the parkour.rewards.<course> console commands ({player} and {course} are filled in; a command with no {player} is refused when you set it), and the sentences under parkour.messages.* - the source plugin's fourteen plus this module's own, including two different refusals because "you may not use parkour" and "you may not build courses" are not the same sentence.

Commands. /parkour (alias pk): wand, create <name>, add, end, undo, remove, delete, list, info, start, join, leave, back, status, top, records, tp, reward, clear, help.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/parkour.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help parkour prints the same names and the same nodes, line for line, because both read this one registration.

/parkour (/pk)

permission: none of its own · always · Parkour.java

Parkour courses, where a course is a list of pressure plates in the world rather than a region: an operator marks plates with a compass and the course is born, and every number the module keeps is about that list. Plates are marked rather than assumed - parkour.require-plate refuses a block that is not a plate, and turning the rule off marks anything (the block you are looking at), which is what a course built out of stone slabs wants.

The runner's half is /parkour list (the courses and the best time on each), /parkour info <course> (every plate, one by one, and that course's board), /parkour start <course> (start the clock - on a server with parkour.start-on-plate the first plate does it for you), /parkour status (your time, the plates, your falls), /parkour back (put yourself on the last checkpoint you reached), /parkour leave (stop the clock), /parkour top [course] (the leaderboard), /parkour join <course> and /parkour records. The building half is /parkour wand [seconds] - the marking compass, made of parkour.wand-material and lasting parkour.wand-seconds - /parkour create <name>, which builds from your marks (one mark is a start, two are a start and a finish), /parkour add <name> and /parkour end <name> to make the marked plate the next checkpoint or the finish, /parkour undo <name> and /parkour remove <name> <n> to drop plates, /parkour tp <name>, /parkour reward <name> [command] for what finishing pays, /parkour delete <name> confirm to remove a course and its board, and /parkour clear <name> confirm to wipe a board and keep the plates. The console can run the questions and the destructive verbs - what courses exist, what a plate belongs to, deleting one, clearing a board, setting a reward, reading somebody's run - but two verbs are about where you are standing and stay a player's: marking a plate, and running a course.

The rules are what make it a course rather than checkpoint commands: a fall sends the runner back to their last checkpoint and counts a fall, while a death ends the run (the respawn point is not the course, and a time from there would be a time nobody ran); the clock can be run again as often as you like, and the reward is what is paid once per player; and a run is a session of the server's life, so a disconnect and a return during the same boot puts the runner back on their course with the clock still running - a stored run that outlived the server would let somebody hold a course open for a week. Courses, records and completions live in parkour.yml and the runs are in memory on purpose. Things that would quietly do nothing are reported rather than loaded: a course with no finish refuses every runner, a scoreboard objective this server will not accept leaves the reward's command with nothing to write into, and a setting outside its range is reported with the value it is actually read as.

Permissions. ppcore.parkour.use (run a course - the shipped default group has it), ppcore.parkour.admin (mark plates, build and delete courses).

patrol - jump staff to a random online player

What it does. /patrol teleports the caller to a random player who is online and tells them who they landed on. It is the "where is everybody" verb: a moderator answering a report, or checking an arena, does not have to guess a name. The caller is left out of the pool, so it never teleports you to yourself, and with nobody else online it says so instead of failing quietly.

Turning it on. modules.patrol.enabled: true, which is the shipped default. Nothing to create: the pool is whoever is online at that moment.

What you can configure. Nothing. The module reads no config keys at all; its whole surface is the switch above and the node below.

Commands. /patrol.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/patrol.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help patrol prints the same names and the same nodes, line for line, because both read this one registration.

/patrol

permission: ppcore.staff.patrol · always · Patrol.java

Jump to somebody at random: /patrol picks one other player who is online, teleports you to them and answers Patrolling <name>. It takes no arguments and has no filter - the pool is every other player on the server, staff included and in any world, so it is the verb for wandering a server rather than for checking one player, and you cannot predict where you land. With nobody else online it refuses (Nobody else is online.), which is also what a small server sees most of the time.

It is a player's verb: the console has nowhere to jump from and is told so. The teleport is the server's own, with nothing added around it - no vanish handling and no message to the player you land on, unlike the staff teleports in the staff module - so a patrol is as visible as the teleport makes it. It asks for ppcore.staff.patrol.

Permissions. ppcore.staff.patrol.

revive - downed players can be revived instead of dying

What it does. A player at zero health goes down instead of dying: held at one heart with regeneration refused while they are down (a downed state that heals itself is not a state), and another player brings them back up. A revive is a channel, not a click: the reviver stands next to the downed player for revive-seconds, has to stay within reach, and the pulse plays while it runs. If nobody reaches them within downed-seconds they die for real, with a line that says the reason. One reviver per downed player. The source plugin behind this module is ReviveMe.

Turning it on. modules.revive.enabled: true, which is the shipped default - there is nothing to build. Three features switch separately under modules.revive.features.*: sounds (the four moments - the start, the pulse while it runs, the cancel, the completion), list (/revive list, the window of who is down, how far away and how much longer) and broadcast (tell the server when somebody goes down). The reach decides who may start a revive and what cancels it, so reach-min has to be at least 0.5 and smaller than reach-max; /revive check names it when it is not.

What you can configure. revive.reach-min (1.5) and revive.reach-max (5.0, in blocks), revive.revive-seconds (10) and revive.downed-seconds (75), revive.downed-health (1.0) and revive.revived-health (10), revive.pulse-seconds (1.0; 0 is legal and means only the start and the end are heard), revive.kill-on-quit (true - a player who logs out downed cannot be found and cannot be revived; false lifts the state instead, because the downed state is in memory and nothing could hand it back across a disconnect), the four moments under revive.sound.* (each a sound, volume and pitch), and the sentences under revive.messages.* - the downed player's, the reviver's, and one refusal per cause (too-far and too-close are different numbers to change, so they are different sentences).

Commands. /revive (alias reviveme) - the downed player you are looking at, /revive <player>, or /revive list.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/revive.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help revive prints the same names and the same nodes, line for line, because both read this one registration.

/revive (/reviveme)

permission: none of its own · always · Revive.java

Being down instead of dying, and the verb that brings somebody back. When a hit would kill a player they go down instead: they are put in adventure mode, their walk speed drops to a crawl, their health is held at revive.downed-health (1), they are told they are down, and a broadcast says so too if modules.revive.features.broadcast is on. They then have revive.downed-seconds (75) to be reached - the run-out says Nobody reached you in time. and kills them - unless somebody revives them, which takes revive.revive-seconds (10) of standing in reach.

/revive revives the downed player nearest you, /revive <player> picks one by name, /revive list opens the window of who is down (nearest first, anybody in another world last, each row showing how far) where a click runs exactly the same check the command does, and /revive help prints the three forms. Every route to a revive goes through one gate, so the reach cannot be enforced on one of them and forgotten on another: a revive is refused on yourself, on somebody who is not down, on somebody already being revived, from further away than revive.reach-max (5) and from closer than revive.reach-min (1.5), and each refusal has its own line in revive.messages.*. One reviver per downed player is a rule rather than a limit: two people working on one body would make the completion moment two events for one outcome.

Coming back is deliberately not a full heal - a revived player is set to revive.revived-health (10), because waking up on a full bar would make going down a free heal, and this core's own healing comes from /hearts and the admin verbs. revive.kill-on-quit (true) decides what a disconnect mid-down means: with it on, a player who leaves while down dies rather than returning to a body nobody could reach; with it off they stand up. Three feature switches shape the surface - the sounds, one per moment (start, reviving, stop, done, each with its own volume and pitch under revive.sound.*), the list window, and the broadcast - and the downed state itself is in memory on purpose: there is no file to write, which is why saving the plugin leaves a downed player exactly where they were.

Permissions. None of its own: the reach decides who may revive whom, so any player may run it.

reward - one-time join reward

What it does. Hands a new player a welcome gift the first time they join - an item, an amount and some money - and marks the account so it is handed out once and not again. The record is written before the gift is given, so an interruption costs a player their bread rather than handing one player a gift twice on every restart.

Turning it on. modules.reward.enabled: true, which is the shipped default. The money part needs the econ module; with no economy the items are still given and the money is skipped. The record lives in onetime.yml in the plugin's folder, keyed by account uuid - the file to keep when you move a server or reset an account, and the one /onetime reset <player> writes a removal into.

What you can configure. reward.item (BREAD), reward.amount (16) and reward.money (50.0). /onetime check reports a material this server does not have (which would silently give bread instead) and an amount below 1 (which would silently give nothing).

Commands. /onetime reset <player> (alias otreward) - clear the record so the gift is handed out again, which is what a refund or a wiped inventory needs.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/reward.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help reward prints the same names and the same nodes, line for line, because both read this one registration.

/onetime (/otreward)

permission: ppcore.reward.admin · always · Reward.java

The one-time join reward, and the way to hand it out again. On a player's first join the module gives reward.item (BREAD) times reward.amount (16) into their inventory, adds reward.money (50) if an economy is running, records the player's UUID in onetime.yml so the gift can never be claimed twice, and answers with Welcome gift claimed. A material name this server does not have falls back to bread, and an amount below 1 is reported by the module's own setting check rather than being handed out as nothing.

/onetime reset <player> is the whole command surface: it clears that record for a named player, who must be online, so their next join claims the gift again - the case it exists for is a player who lost the items to a crash or a full inventory on their first day. The record is in the plugin's folder, so a reset survives a restart, and nothing else in the plugin reads or writes it. It asks for ppcore.reward.admin, so it is a staff verb rather than a player one.

The id in config.yml is onetime (/otreward is an alias), which is the name a rename would move.

Permissions. ppcore.reward.admin.

saveall - save worlds and plugin data

What it does. Saves everything, in the order that matters: the server's own save-all first, then flushAll() for every module here that keeps a file. That second half is why the module exists - /save-all alone writes the worlds and leaves the modules' files to whatever they were going to do anyway, so a restart right after a save could still lose the last crate, home or order that only existed in memory.

Turning it on. modules.saveall.enabled: true, which is the shipped default. Nothing to create.

What you can configure. Nothing - no config keys of its own. What it saves is each module's own business: a module with nothing in memory writes nothing.

Commands. /saveall.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/saveall.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help saveall prints the same names and the same nodes, line for line, because both read this one registration.

/saveall

permission: ppcore.admin.saveall · always · SaveAll.java

Save everything, now, in one command: the server's worlds first, and then every module's own data through the plugin's flush half - the second half of what /playport save does on its own. It answers Saved worlds and module data. and takes no arguments.

The distinction between the two halves is what makes this safe to run on a busy server: a module's flush is its save-only half, so nothing is torn down by it. No running fight is cancelled, no pet is dismissed, no claimed chunk is released and no permission set is re-read - and saving does not reload anything, so a value you edited in config.yml is still what the next restart or /playport reload reads. The world save is the server's own, run from the console, which is why this is the one command here that writes files this plugin does not own.

ppcore.admin.saveall by default, so it pairs with the rest of the operator surface rather than being something a player can ask for by name.

Permissions. ppcore.admin.saveall.

security - scan inventories for contraband

What it does. Looks in players' inventories for contraband and tells staff. What it finds is always one of two named things: a material from security.banned-materials, or a phrase from security.blocked-tokens appearing in an item's display name or lore. The trigger travels with the alert - join, interval or container - because "somebody has a command block" and "somebody has one inside a shulker box they are carrying" are different events for whoever has to answer it, and the count in the alert is the number of items rather than of slots, so one box holding 27 of them is one alert that says 27. The source plugin behind this module is StaffSuiteSecurityAddon, and its placeholders ({player}, {amount}, {item}, {trigger}, {reason}) are kept in the alert sentence.

Turning it on. modules.security.enabled: true, which is the shipped default, plus something to look for: with both lists empty the scan has nothing to find and every item passes, and <command> check reports exactly that. The join scan runs one tick after the join event on purpose - a joining client has not sent its inventory yet inside the event, so a scan there reads an empty inventory and looks fine while reporting nothing. scan-interval-seconds: 0 turns the clock off, not the scan: the join scan and the two commands still run, and the check says so in words when both ways in are closed. The lists are edited in game rather than in a file, which is why the source plugin's two commands are here.

What you can configure. security.scan-on-join (true), security.scan-interval-seconds (90; 0 = the clock off), security.auto-remove (true - delete what is found; false leaves it and only alerts, which is what a server runs while it is still deciding what belongs on the list), security.staff-bypass-permission (empty by default: an empty node exempts nobody), security.detect-shulker-boxes (true - look inside a carried shulker box as well as at the inventory around it), security.banned-materials (the command blocks, barrier, structure block and structure void by default), security.blocked-tokens (matched case-insensitively as a substring), security.allow (materials that may be held even though the banned list names them) and security.alert-format.

Commands. /securitywhitelist list|add <material>|remove <material> (alias seclist) and /securitytokens list|add <phrase>|remove <phrase> (alias sectokens). There is no bare /security verb: the module's whole command surface is those two. <command> check prints the running tally - how many scans, how many finds, how many removed - because "0 found" and "never ran" look identical in a report.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/security.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help security prints the same names and the same nodes, line for line, because both read this one registration.

/securitywhitelist (/seclist)

permission: ppcore.security.whitelist · always · Security.java

The materials the contraband scan leaves alone. /securitywhitelist with nothing after it - or list - prints the whitelist, add <material> puts one on it and remove <material> takes one off, and the reply prints the list as it now stands. The name is checked against this server's materials first, so a typo is refused with is not a material this server has. rather than being stored as a line that matches nothing, and the entry is stored in the server's own capitalisation.

Every edit is written through the plugin's setting pin into security.allow, so it survives the next config reload instead of being quietly put back to the file's value - which is what an operator who edits the list in game has every reason to expect. It asks for ppcore.security.whitelist. This is a list of materials, matched against an item's type, so it is the answer to a whole item being caught; /securitytokens is the other half of the scan, and the settings that decide when the scan runs and what it does with what it finds (security.scan-on-join, security.scan-interval-seconds, security.auto-remove, security.detect-shulker-boxes, security.staff-bypass-permission) live in the module's own config section.

/securitytokens (/sectokens)

permission: ppcore.security.tokens · always · Security.java

The blocked phrases, matched against an item's display name and its lore case-insensitively as a substring - so coin catches Gold Coin, coins and My coin shop alike. /securitytokens with nothing after it - or list - prints the phrases and says in the same breath how they are matched; add <phrase> appends one and remove <phrase> drops one, both written through the plugin's setting pin into security.blocked-tokens so a reload does not put the old list back. It asks for ppcore.security.tokens.

This is the half that catches contraband people paid real money for: a banned material can be whitelisted away by a server that wants it, but a phrase on the wrong thing is exactly the case the scan exists for. With both this list and security.banned-materials empty, the scan has nothing to look for and every item passes, which the module's own check reports rather than leaving an operator to wonder why nothing is ever found.

Permissions. ppcore.security.whitelist, ppcore.security.tokens, ppcore.security.alert (who receives a finding). ppcore.security.bypass is deliberately not granted by any shipped group: it is the node whose holder a scan skips entirely, and a pack should not hand that out by accident.

shoot - fire a projectile from a player

What it does. Launches a projectile from a player - a snowball unless a fireball is asked for - as if that player threw it. The projectile comes from the target's own position and is theirs for every purpose afterwards, which is what makes it useful for an event or for checking where a hit lands. The player argument is what the command acts on, so it works from the console as well as in game.

Turning it on. modules.shoot.enabled: true, which is the shipped default. Nothing to create.

What you can configure. Nothing - no config keys of its own. The kind and the target are command arguments.

Commands. /shoot [fireball|snow] [player] - the player defaults to the sender, and any first word starting with fire fires a fireball.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/shoot.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help shoot prints the same names and the same nodes, line for line, because both read this one registration.

/shoot

permission: ppcore.admin.shoot · always · Shoot.java

Fire a projectile from somebody's hand: /shoot [fireball|snow] [player]. The first word is the kind and any of it that starts with fire is a fireball, so fireball, fire and firesomething are the same launch; anything else is a snowball, and a bare /shoot sends a snowball from your own hand. The second name is who fires it, not who is hit - /shoot fireball Alex launches from Alex, which is how this is used to hand somebody the momentum rather than to shoot at them.

It is a player's command in effect: with no name you must be a player, since the console has no hand to launch from, and a named player has to be online or it answers Need a player. The projectile is the server's own and flies normally - nothing here sets a speed, a lifetime or a direction - and the reply is Shot. It asks for ppcore.admin.shoot, so it is a staff verb by default rather than a toy.

Permissions. ppcore.admin.shoot.

shulker - open a shulker from the hand without placing it

What it does. Right-clicking with a shulker box in your hand opens it where it is instead of placing it down: the click is cancelled, a window opens with the box's own contents, and whatever you leave in the window is written back into the item when you close it. The box is never placed, so carrying one to a shop is not a detour. A right-click on a block that is itself interactive is left to vanilla - the hand-shulker takes the click only after every module with a block of its own has had it, so /crate place on a stone block still opens the crate.

Turning it on. modules.shulker.enabled: true, which is the shipped default. Nothing to configure: the item in your hand is the whole state.

What you can configure. Nothing - no config keys of its own.

Commands. /shulker (alias axshulker) - the same open, for a player who would rather not right-click.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/shulker.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help shulker prints the same names and the same nodes, line for line, because both read this one registration.

/shulker (/axshulker)

permission: none of its own · always · ShulkerBox.java

Open the shulker box in your hand without placing it: hold one and type /shulker (alias /axshulker), or right-click in the air - or on a block that is not interactive - with one in hand, and its twenty-seven slots open where you stand. What you take out or put in is written back onto the item when the window closes, so the box keeps its contents and the box itself never leaves your hand. /shulker with anything but a shulker box held is refused with Hold a shulker box., and it is players only, since the console has no hand.

The right-click follows the same two rules a cheque deposit does: a click on a container or any other interactive block is left to the block, so carrying a shulker does not stop you opening chests - and the hand-shulker takes the click last, after every module with a block of its own has had it, so a marked crate still answers its own click. No permission is asked for: what this gives you is what placing the box down and opening it would have given anybody, minus the block in the world, and the window's contents are the item's own.

Permissions. None: the command is registered with an empty node, so any player may use it.

silentchest - open a container without the lid animation

What it does. Opens the container you are looking at, within six blocks, by opening its inventory directly: no lid animation, and no change to the block itself. It is for staff work in a busy area - a chest that opens and closes under its own steam is a chest every player nearby notices, and this one looks untouched afterwards.

Turning it on. modules.silentchest.enabled: true, which is the shipped default. Look at a chest, barrel, hopper or any other container and run the command; looking at anything else, or at nothing, gets a refusal rather than an empty window.

What you can configure. Nothing - no config keys of its own.

Commands. /silentchest (alias sc).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/silentchest.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help silentchest prints the same names and the same nodes, line for line, because both read this one registration.

/silentchest (/sc)

permission: ppcore.staff.silentchest · always · SilentChest.java

Open the container you are looking at without the lid: look at a chest - or a barrel, a shulker box, or any other block whose state is a container - within six blocks of your eyes and type /silentchest (alias /sc), and its inventory opens as though you had right-clicked it, with no opening animation and no sound for anyone standing nearby. It takes no arguments and answers nothing on success.

It is the staff-side answer to reading a player's storage without announcing it, which is exactly why it is not a player verb: it asks for ppcore.staff.silentchest. It refuses Look at a chest. when there is no container in your line of sight - either you are looking at a block that is not one, or there is nothing within those six blocks - and it is players only, because the console has nothing to look with. Opening the inventory is the server's own, so this gives no access a clicked chest would not have given; what it changes is who hears it happen.

Permissions. ppcore.staff.silentchest.

skin - remember a requested skin name

What it does. Remembers a skin name a player asks for, in skins.yml, so /skin <name> and /skin off have somewhere to keep the answer. Be clear about what it does and does not do: it remembers the name and cannot apply a live texture - the module's own reply says so ("applying a live texture needs a skin proxy on this jar"), and a server that wants real skins needs a skin plugin or a proxy. What it gives you today is a recorded, inspectable per-player setting that a proxy or a panel-side integration can read, and a reply that tells the truth rather than pretending.

Turning it on. modules.skin.enabled: true, which is the shipped default. /skin with no argument prints what is remembered for you, /skin <name> records a name, and /skin off clears it.

What you can configure. Nothing in config.yml; the record is the module's own skins.yml, and the module is the only thing that reads or writes it.

Commands. /skin <name|off> (alias setskin).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/skin.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help skin prints the same names and the same nodes, line for line, because both read this one registration.

/skin (/setskin)

permission: none of its own · always · Skin.java

Remember a skin name for a player. /skin <name> stores that name against your own UUID and answers that it is remembered, /skin off clears the record, and /skin on its own tells you what is remembered - or none if nothing is. The id in config.yml is skin (/setskin is an alias), so a rename moves the verb rather than the record.

The limit is worth stating plainly, because the replies do not pretend otherwise: applying a live texture needs a skin proxy on this jar, so this module remembers the request and repaints nobody. What it is for is a server that reads the skins from somewhere else - the panel, or a proxy in front of the game - where the record is the input that side needs, and where a player setting it in game beats an operator editing a file by hand. Players only, since the record is keyed by the player asking; the record is skins.yml in the plugin's folder, so it survives a restart, and no other module reads it. No permission is asked for, so it is the player's own setting rather than a staff verb.

Permissions. None: the command is registered with an empty node, so any player may set their own.

sound - play a sound for a player

What it does. Plays one sound, by name, for one player - the player named, or the sender. Nothing is broadcast, no file is written and no state is kept: it is the verb an operator reaches for to check what a sound name sounds like, or to point something out to one person without making a server-wide noise.

Turning it on. modules.sound.enabled: true, which is the shipped default. The name has to be a sound this server has: an unknown name is refused with "Unknown sound" rather than played silently.

What you can configure. Nothing - no config keys of its own, and no volume or pitch to set: the sound plays at 1.0/1.0 at the target's own location.

Commands. /sound <name> [player] (alias playsound).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/sound.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help sound prints the same names and the same nodes, line for line, because both read this one registration.

/sound (/playsound)

permission: ppcore.admin.sound · always · SoundCmd.java

Play a sound for a player: /sound <name> [player]. The name is the server's own sound constant, written in either case - UI_BUTTON_CLICK, ENTITY_PLAYER_LEVELUP, BLOCK_NOTE_BLOCK_PLING - and an unknown one is refused with Unknown sound. rather than being swallowed. With no name at all you get the usage line and nothing plays, so a sound you are unsure of is one command away from being checked. The name is the whole list this server has: the module does not carry a catalogue of its own, so the answer to "which sounds exist" is the server's version rather than this plugin's.

The player named is who hears it; with nobody named it is whoever typed the command, which is why the console has to name somebody (Need a player.). It plays at the target's own location at full volume and pitch, so the sound is heard by anyone else standing there too, and the reply is Played. It asks for ppcore.admin.sound: a sound nobody can switch off is a staff tool here, not a player toy.

Permissions. ppcore.admin.sound.

stack - merge dropped items in a radius

What it does. Merges dropped items on the ground: when an item is spawned, the module looks for similar stacks nearby and moves as much of it into them as will fit, up to the item's own stack size. It is the ore-stacking feature of the source plugin (Orestack) rather than a whole item-merging engine: one listener, and the items already lying on the ground are what it tidies.

Turning it on. modules.stack.enabled: true, which is the shipped default. The command is /orestack, not /stack: /stack is the admin module's alias for /more (fill the stack in your hand), so this module claims the source plugin's own name for the ore feature instead - two different verbs shared one word in the source batch, and /more had it first. /orestack on|off and /orestack radius <n> pin the setting for the session instead of editing the file, so the answer you set in game is still there after a reload, and a pinned setting says so out loud.

What you can configure. stack.merge (true) and stack.radius (4, in blocks per axis). /orestack check reports a radius below 1 (drops never merge) and one above 16 (a cube big enough to hitch a busy farm).

Commands. /orestack on|off|radius <n> (alias mergestack). With no argument it prints the two settings it is running with.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/stack.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help stack prints the same names and the same nodes, line for line, because both read this one registration.

/orestack (/mergestack)

permission: ppcore.stack.admin · always · Stack.java

Merging dropped items, and the command for it. /orestack with nothing after it reports the two settings as they currently read (Stack merge=true radius=4); on and off turn merging on and off for the running server; and radius <n> sets how far a new drop looks for something to merge into, with values below 1 lifted to 1 and an unreadable one falling back to 4. on, off and radius write through the plugin's setting pin, so the change survives a config reload the way an operator expects - the file is not rewritten, and the pinned value wins until the server restarts. Anything else prints the usage line (/orestack on|off|radius <n>).

The module's real work is not in the command: when an item spawns and stack.merge is true, the drop looks for other drops of the same item within stack.radius (4) and merges, filling each existing stack up to its own maximum first and removing the new drop once it fits entirely. It only ever merges items that are identical (isSimilar), so a renamed or enchanted drop is left alone rather than mixed with a plain one. The radius is where this can go wrong in both directions: below 1 nothing ever merges, and above 16 the scan covers a cube big enough to hitch a busy farm, which the module's own setting check reports.

The name comes from the source plugin, where one word covered both this and filling the stack in your hand - /more kept the short name, so this one is registered as orestack (alias mergestack) and the message you get from an unknown verb is its own name, not /stack. It asks for ppcore.stack.admin.

Permissions. ppcore.stack.admin.

Files behind this page

What this section documentsSource
The order board: the held money, partial fills, the expiry sweep, the board and its windowspkg/src/ppcore/Orders.java (../../pkg/src/ppcore/Orders.java)
Parkour courses: plates, the compass, the clock, the sidebar, parkour.ymlpkg/src/ppcore/Parkour.java (../../pkg/src/ppcore/Parkour.java)
/patrol and the random-player poolpkg/src/ppcore/Patrol.java (../../pkg/src/ppcore/Patrol.java)
The downed state and the revive channel: reach, pulse, the four sounds, the list windowpkg/src/ppcore/Revive.java (../../pkg/src/ppcore/Revive.java)
The one-time join reward and the onetime.yml recordpkg/src/ppcore/Reward.java (../../pkg/src/ppcore/Reward.java)
/saveall: the world save and flushAll()pkg/src/ppcore/SaveAll.java (../../pkg/src/ppcore/SaveAll.java)
The contraband scan, the two lists, the summary and the commands that edit thempkg/src/ppcore/Security.java (../../pkg/src/ppcore/Security.java)
The projectile and its kindpkg/src/ppcore/Shoot.java (../../pkg/src/ppcore/Shoot.java)
The hand-shulker: the click it takes and the contents it writes backpkg/src/ppcore/ShulkerBox.java (../../pkg/src/ppcore/ShulkerBox.java)
The silent container openpkg/src/ppcore/SilentChest.java (../../pkg/src/ppcore/SilentChest.java)
The remembered skin name and its skins.yml recordpkg/src/ppcore/Skin.java (../../pkg/src/ppcore/Skin.java)
/soundpkg/src/ppcore/SoundCmd.java (../../pkg/src/ppcore/SoundCmd.java)
Dropped-item merging, the pinned settings and /orestackpkg/src/ppcore/Stack.java (../../pkg/src/ppcore/Stack.java)
The switches, keys and default values every entry quotespkg/config.yml (../../pkg/config.yml)
The per-module vocabulary (keys, files, nodes) the entries were checked againstpkg/src/ppcore/ConfigVocab.java (../../pkg/src/ppcore/ConfigVocab.java)
What an empty permission node on a command meanspkg/src/ppcore/Interop.java (../../pkg/src/ppcore/Interop.java)
The command dispatcher and <command> checkpkg/src/ppcore/PlayPortCore.java (../../pkg/src/ppcore/PlayPortCore.java)

Signs, actions and the engines

This is the last page of the compatibility batch, and it is where the batch stops being a list of small verbs: five of the thirteen are engines the rest of the plugin is built on or talks to (actions, dialogs, dsign, icsign, hooks), and their entries are longer for that reason. The other eight are the same shape as the modules on the two pages before this one - one job each, switched on out of the box, most of them with nothing to configure.

The shared rules are in chapter 12: the modules.<id>.enabled switch, the precedence of your edit over the profile, and the commands: map that renames or switches off any one command. Two things worth knowing before reading on: the interactive sign modules do not hold their own content (icsign and warpsign point at the action engine and at warps), and service is the switch behind anything here that talks outward - the stream viewer count is refused with a sentence naming the switch until service.enabled and service.features.twitch are both on.


stream - streamer list, live tag, watch time and drops

What it does. Keeps a list of streamers, asks one endpoint whether each listed channel is live, tells joining players who is live, pays people for watching, and lets a streamer fire their own event in chat. "Live" comes from one HTTP template - source.url with {channel} filled in - so a listing of ten channels costs ten small requests and no request at all while nobody listed is playing. A body with no number in it, a refused connection and a 500 all mean the same thing (not live), because the other direction pays people for a stream that is not running; /stream info keeps the last answer and the last problem apart, since "nobody is live" and "the source is broken" are the same picture from a player's seat and two different jobs for whoever runs the server. Nothing here holds a Twitch token.

Turning it on. modules.stream.enabled: true (it ships on) plus two switches further down: service.enabled and service.features.twitch (both ship false), with the endpoint in stream.source.url. Any service that answers with a number will do. The viewer count needs no permission; the money-paying half needs econ.

What you can configure. stream.source.url: "" (empty means nobody is ever live, which /stream info says in those words), source.regex: "" (how the count is read out of the body; empty takes the first run of digits), source.every-seconds: 30, source.timeout-ms: 4000, source.live-above: 1; stream.channels.<player>.channel (Minecraft name to Twitch channel - /stream add <player> <channel> writes here too and the file wins, so a command you ran is not fought by a config you edited later); stream.streamerListOnJoin: true and onlineStreamerFormat; stream.placeholder.liveTag and offlineTag; stream.twitchChatFormat; blacklistTwitchChannel: [] and blacklistWorlds: [] (worlds where watching pays nothing and drops do not land); stream.public: true - false makes it a private stream, and then only the streamers and private-viewers are paid.

The paying half: watchTime.enabled: true, every-minutes: 10, pay: 25, min-viewers: 1 (a gate on the audience, not on liveness), per-session: -1 (-1 is no ceiling); dropOptions.dropChance: 0.1, randomDrops, dropsOnlyForFollower (there is no follower lookup anywhere in this core, so this is the permission ppcore.stream.follower - an honest stand-in), allowMultiStreams (true pays once per live stream rather than once), useAlwaysCommands; stream.alwaysCommands (console commands with %player%, %channel%, %viewers%, %uuid%); and stream.events.<name> - the prefix (!), plus chance, once-per-session, requires-live, pay and commands per event. A proxySettings-marked event is refused when no proxy is linked rather than run locally, because a command meant for the network is a different command.

Commands. /stream (aliases streamer, live): list, live, watch, add, remove, event, chat, info.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/stream.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help stream prints the same names and the same nodes, line for line, because both read this one registration.

/stream (/streamer, /live)

permission: none of its own · always · Stream.java

The streamer list and what watching it pays. /stream on its own is the listing: every streamer this server knows, each with the live word (stream.placeholder.liveTag) or the offline one (stream.placeholder.offlineTag) beside it, the Twitch channel its status is read from, the viewer count the source last reported, and a note when the streamer is not on this server. /stream live is the same answer the other way round - only the live ones, one line each, in stream.onlineStreamerFormat. When nobody listed is live it says so, and if the source itself is unreachable or refusing the last problem is printed in the same breath, which is the difference between an empty list and a broken one.

/stream watch is your own record: the minutes counted while somebody listed was live, what watching has paid you, and the payouts in this session against the cap in stream.watchTime.per-session - a leave ends the session, so a rejoin starts the count and the cap again. Players only. /stream info is the operator's version of the same question: how many are listed and how many are live, how many times the source was asked and how many answers came back, the URL and the interval, the last HTTP error, watch-time payouts by money, events fired, drops paid and missed, and every refusal counted by its reason (already paid this session, a private stream, a blacklisted world, no proxy) - which is how a drop that never lands is traced to the rule that stopped it. It also says whether the stream is public and how many viewers stream.private-viewers names, because a private stream changes who gets paid, and that list's size is the number that matters.

/stream event <name> fires one of the events in stream.events as if the source had reported it, which is how you test a reward without going live; the name has to be one the config defines. /stream chat <channel> <user> <message> posts a line into chat in stream.twitchChatFormat, the relay for a bridge or a bot that wants Twitch chat shown in game. /stream add <player> [channel] lists a streamer by their player name, with the channel their live status is read from; a listing with no channel can never be live, and the answer says so rather than leaving you to discover it. /stream remove <player> takes the listing away and reports when the name was not there in the first place. The listing, the live list, your own watch time and the info screen ask for nothing; event, chat, add and remove are the operator's half and check ppcore.stream.admin.

Permissions. ppcore.stream.admin for /stream event and for /stream add|remove <player> [channel]; ppcore.stream.follower for the follower-only drop gate. The player's own verbs (list, live, watch, chat, info) are what the command registers for, and it carries no node of its own.

tags - chat tags a player can equip

What it does. Gives players a tag they can wear in front of their name - [VIP], [ADMIN], or one of the 31 generated colour tags. A tag whose value is a list is animated: the frames are drawn on a clock every wearer shares, so two players wearing it show the same frame in the same second rather than each running a private animation that started when they logged in. The tag is drawn by this module and put on the chat line by chat's own format ({tag}), which is why the two modules are worth reading together.

Turning it on. modules.tags.enabled: true (it ships on) and a {tag} in the chat format for a player to see it. /tag list, /tag set <id>, /tag off are the player's three verbs.

What you can configure. tags.settings.animated-tag-speed: 10 (how long one frame lasts, in ticks; under 1 is refused by /tag check because it would never advance) and tags.list.<id> - a string, or a list of strings for an animated tag. Two hand-written entries ship (vip, rainbow) beside 31 generated colour and role rows between the presets: tags.list markers, regenerated when the jar is built.

Commands. /tag (aliases tags, prefix, mytags): list, set <id>, off.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/tags.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help tags prints the same names and the same nodes, line for line, because both read this one registration.

/tag (/tags, /prefix, /mytags)

permission: none of its own · always · Tags.java

The tag in front of your name, and the list to choose from. /tag on its own, or /tag list, prints every id in tags.list - with (animated) after the ones that move - then the tag you are wearing as it actually renders, and, when that one is animated, the frame rate from tags.settings.animated-tag-speed with the key named, so an owner reading the list knows why it cycles.

One word sets the tag: /tag <id> wears it, and /tag set <id> is the same thing spelled out. An id the list does not have is refused with the id quoted, which is the whole reason the list is printed first. /tag off (or /tag clear) takes the tag off and says so. Players only, and the choice is written to this module's own file straight away, so it survives a restart and /playport reload does not lose it.

A tag's value in tags.list is either one string or a list of frames; a list is an animated tag whose current frame is decided by the clock rather than by whoever is watching, so everybody wearing it sees the same frame at the same moment instead of each starting a private animation at login. A tag that is blank, or whose list of frames is empty, renders as nothing at all - /playport check tags names both, because a player can otherwise wear a name that shows nothing and never find out why.

Permissions. None at all: the module checks no node, so every id in tags.list is wearable by every player and the list itself is the gate - take an id out and nobody can wear it. A tag that should be earned belongs behind a node you grant in your own group, or behind a quest that runs /tag set for the player.

top - teleport to the highest block above you

What it does. Puts a player on the highest block in their own column - the escape from a cave, a mine or somebody else's trap. It is one verb with no state.

Turning it on. modules.top.enabled: true (it ships on).

What you can configure. Nothing.

Commands. /top.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/top.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help top prints the same names and the same nodes, line for line, because both read this one registration.

/top

permission: none of its own · always · TopCmd.java

Put yourself on the surface: /top takes the X and Z you are standing at, finds the highest block at those coordinates in the world you are in, and moves you to the block above it - keeping the direction you are facing - then answers Top.. Nothing else is asked and nothing else changes: no argument, no permission, no cooldown, and the world you are in stays the world you are in, so it is the way out of a mine or a cave rather than a teleport across the map. It is players only, so the console is told to use a player, and where the surface at your feet is a roof rather than the sky, that is where you land - the verb reads the highest block, not the highest air.

Permissions. None.

voice - proximity chat as a stand-in for voice range

What it does. Makes ordinary chat local, per player: while your proximity chat is on, a line you send reaches only players in the same world within radius blocks, and it stops there. The source plugin ships a real voice plugin, and this is the server-side half of what an owner actually asked for - a room where what you type reaches the people in the room. No audio moves through it, and the line itself is still the chat module's line, so formatting is unchanged; only the list of who receives it is cut down.

Turning it on. modules.voice.enabled: true (it ships on), and each player opts in themselves with /voice. A server where nobody has typed it behaves exactly as it did before, which is deliberate - proximity is a choice about your own lines, not a server-wide rule.

What you can configure. voice.radius: 24.0 - how far a local line carries, in blocks. There is no per-world or per-group radius.

Commands. /voice toggles for you, /voice on and /voice off say what to do rather than what state to be in, and /voice status reads the state - every answer carries the radius, because "am I local, and how far does it carry" is the question somebody typing this has (aliases proximity, voicechat).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/voice.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help voice prints the same names and the same nodes, line for line, because both read this one registration.

/voice (/proximity, /voicechat)

permission: none of its own · always · Voice.java

Proximity chat: the server-side half of a voice plugin's range. Toggle it on and the lines you type in public chat are delivered only to players in your world within voice.radius blocks (24 shipped); everybody else simply does not receive the line, rather than getting a mute notice. Toggle it off and your chat is heard as far as the server normally carries it. It is a stand-in for a voice range rather than a replacement for one, and it is players only.

/voice with nothing after it flips the state - the way it has always worked - and /voice on and /voice off say which state to be in rather than flipping. /voice status is the read. Every answer carries the radius, because "am I local, and how far does it carry" is the question somebody typing this has, and voice.radius is read on every call, so a reload reaches the next line of chat without a reconnect.

Permissions. None.

warpsign - signs that warp when clicked

What it does. Binds a sign to a warp: line 1 carries the header, line 2 names the warp, and a right-click sends the player there. The click is cancelled before it completes, so the sign never opens the vanilla edit screen, and the warp itself is performed as a command (warp <name>) - the warps belong to the module that owns warps, so that module's own rules decide who may use one rather than a second copy of the rule living here. Colour codes on line 1 are stripped before the header is compared, so a sign written with a coloured first line still works, and a sign whose line 2 is empty does nothing at all when clicked.

Turning it on. modules.warpsign.enabled: true (it ships on), and warps to point at - the warps are the ess module's, so a sign on a server with ess off has nothing to run. Write the header on line 1 and the warp name on line 2 of a sign: writing the header is itself a staff action, so a player without the node has that edit cancelled and is told why. /warpsign only prints those two lines for reference.

What you can configure. warpsign.header: "[warp]" - the word a sign's first line must carry for the binding to be found. The built-in [warp] works even when you change this key, so an old sign keeps working.

Commands. /warpsign (prints the two lines; it takes no arguments and binds nothing).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/warpsign.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help warpsign prints the same names and the same nodes, line for line, because both read this one registration.

/warpsign

permission: ppcore.staff.warp · always · WarpSign.java

Signs that warp when they are clicked, which is the whole module: the sign's first line reads [warp] (the word is warpsign.header, default [warp]) and its second line is the name of a warp, and right-clicking it runs /warp <name> for whoever clicked. [warp] written in any case is accepted, and the line is recoloured when it is written so that every later reader sees the same sign.

The command itself is the instructions: /warpsign prints the two lines to write, and does nothing else. Writing such a sign is the part that is staff-only - the module refuses the edit rather than leaving a sign that looks right and does nothing - and clicking with a sign whose second line is blank is left alone, so a half-written sign is harmless. The warp that runs is the one the ess module owns, so a warp that does not exist answers exactly as it would if the player had typed the command themselves.

Permissions. ppcore.staff.warp to write the header on a sign. Clicking is not gated by this module: the warp command's own permission decides who may use the warp.

actions - the action engine: typed actions and conditions over config

What it does. The engine the other interactive modules run on, and the reason this plugin has no scripting language: what the reference plugins did with on-hit scripts and JavaScript rewards is done here with named lists of typed steps, so no arbitrary code runs on the server. A named list is a sequence of "<action> [argument]" lines, with if <condition> [argument] opening a gate for the steps under it and endif closing it (gates stack, and the inner ones are ANDed with the outer), and /actions run <name> [player] fires one by hand. A condition is never an action - run asks it and either walks on or stops the steps under that gate - so nothing a condition returns can choose a branch, and nothing an action does can change which condition is asked.

The vocabulary is closed and small enough to print. The seventeen action kinds are message, actionbar, title, sound, particle, command, console, give, take, heal, damage, delay, potion, spawn, teleport, hologram and fake-item; the eleven condition kinds are chance, permission, world, y, health, money, biome, item, team, flag and phase. A name in neither set is refused when the file is read, with the list and the line number, rather than at the moment it would have run - and an argument is checked the same way, so a chance outside 0-100, a sound or a potion effect or a biome this server does not have, a flag no world has, a delay of zero ticks, or a teleport that is neither spawn nor three numbers is a finding rather than a step that loads and quietly does nothing. icsign runs a list when a sign is clicked, and a module with a moment of its own (the arena's boss skills) shares these words by calling the engine rather than keeping a second copy of them.

Turning it on. modules.actions.enabled: true (it ships on). On first start, if actions.yml is missing and example-on-missing is on, the module writes one worked example list, so there is something to read and run.

What you can configure. The engine's limits, and only its limits: actions.max-steps: 64 (the most steps one run may take; a run that continues after a delay carries on from where it was rather than starting the count again), actions.max-delay-ticks: 1200 (a longer delay is clamped, not refused, so a list written for a slower server still runs), actions.hologram-seconds: 3 and actions.fake-item-seconds: 3 (how long a floating text or a shown item stays up - the lifetime is the engine's setting rather than a number on the step, so a display cannot outlive the moment it belonged to) and actions.example-on-missing: true.

The lists are not in config.yml. They are the operator's own data, one named list per block in plugins/PlayPortCore/actions.yml, edited with /actions - because config.yml is the panel's file and a list written into it would be a list the panel's editor never draws.

Commands. /actions <list|run|reload> [name] [player].

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/actions.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help actions prints the same names and the same nodes, line for line, because both read this one registration.

/actions

permission: ppcore.actions · always · Actions.java

The action engine's own front door. The lists are not written here - they live in actions.yml, where each name is a run of typed steps with conditions between them - so this command lists them, runs them and re-reads the file.

/actions on its own, and /actions list, print every list with the number of steps in it, which is how you check that an edit landed. /actions run <list> [player] runs one: with no player named it runs for whoever typed it (and a console sender has no player at all), and a player named as the third word has to be online, because a step that gives an item or teleports needs somebody to do it to. /actions reload re-reads actions.yml without a restart and answers with how many lists it found and how many problems - /playport check actions is where those problems are read in full, which is why a config error does not need a restart to be seen either. Anything else typed is answered with the three words.

Permissions. ppcore.actions.

dialogs - native dialogs on 1.21.7+, a menu window on anything older

What it does. Draws Minecraft's own dialog windows - the server's window, with text, the four kinds of input widget the game has, and buttons whose command the client fills from the answers. A definition is one YAML file per dialog in dialogs.folder, and $(key) in a button's action is where that input's answer goes. The server is asked, not assumed: this jar links against no dialog class, so on a server older than 1.21.7 the API is simply found missing and every dialog opens as a ui page instead. The one thing a chest window cannot host is an input widget, so the fallback lists each field and the value it starts at and puts those starting values into the button's command - which is why fallback: false is a real choice on a modern server and a refusal on an old one.

Turning it on. modules.dialogs.enabled: true (it ships on), then write definitions into dialog-defs/. /dialogs lists them, /dialogs api says which half of the wall this server is on, and /dialogs check names a definition that would load cleanly and do nothing.

What you can configure. dialogs.folder: dialog-defs (one definition file per dialog, under the plugin's data folder), dialogs.fallback: true, dialogs.max-inputs: 8 and dialogs.max-buttons: 8 (extra ones are dropped), and dialogs.messages.* - native, fallback, off, none, reloaded. The folder is deliberately not dialogs: ui.dialogs.folder defaults to Dialogs, which is the same directory on any filesystem that ignores case, and the two formats have nothing in common - so each reader would take files the other wrote. /dialogs check names the collision if you set one of them to the other's name. The off message is quoted in the shipped file on purpose: YAML reads a bare off: as the boolean false, which is how that line once refused every edit and printed its default for ever.

Commands. /dialogs (list), /dialogs api, /dialogs check.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/dialogs.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help dialogs prints the same names and the same nodes, line for line, because both read this one registration.

/dialogs

permission: none of its own · always · Dialogs.java

The dialogs this server has, and the way to open one. A dialog is a file in this module's own folder that names its title, its buttons and its inputs; /dialogs on its own, or /dialogs list, prints how many there are, whether the server can show them natively or falls back to a menu window, and then one line per dialog with its button and input counts. Nothing defined yet is answered with the folder to write one in.

/dialogs <name> opens that dialog for you, and /dialogs show <name> is the same thing spelled out - the name is matched against the files, so it is the list's own word rather than a title. /dialogs open <player> <name> opens one for somebody else and needs the player to be online; both refusals name what could not be found, and the line printed when it works says whether the player is looking at a native dialog or a menu. /dialogs api answers the question behind all of this: which dialog API this server exposes, what the dialogs are built as without it, and the value of dialogs.fallback - an input widget cannot live in a chest window, which is why the fallback is a menu rather than a copy. /dialogs reload re-reads the folder after you write or edit a file.

Permissions. None.

dsign - signs whose lines refresh from server placeholders

What it does. A sign whose lines are stored and re-rendered on a clock, so it can show a live server number without anybody editing it. The lines an operator sets are the template; the placeholders with one answer for the whole server (%online%, %max%, %tps%, %world%) are filled every pass, and the per-player ones (%player%, %ping%) are left as written - one physical sign is one line for everyone, and /dsign info names every placeholder it did not fill.

Turning it on. modules.dsign.enabled: true (it ships on), then /dsign new. The signs themselves live in dsign.yml; this section is only the clock and its reach.

What you can configure. dsign.interval: 5 (how often the stored lines are re-rendered onto the blocks, in seconds), dsign.range: 32 (how near a player has to be for a sign to be updated this pass - 0 updates every sign every pass, which is what a sign showing a live count wants, and a positive number is what a wall of them in a mostly empty world wants; signs out of range keep what they showed) and dsign.reach: 6 (how far /dsign looks for the sign a player is standing at).

Commands. /dsign new|create|add, /dsign set|edit|addline <line> <text>, /dsign clear <line>, /dsign remove|delete|rm, /dsign list, /dsign info|show, /dsign moveup, /dsign movedown. There is deliberately no per-sign interval: /dsign interval and /dsign range refuse and point at the keys above, and /dsign individual refuses too - a per-player line is the thing one physical block cannot be.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/dsign.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help dsign prints the same names and the same nodes, line for line, because both read this one registration.

/dsign

permission: ppcore.dsign.admin · always · Dsign.java

Signs whose four lines are templates that refresh on a clock. Look at a sign (within dsign.reach blocks, 6 shipped) and say /dsign new: the sign's current lines become its template, it is remembered by its world and coordinates, and every dsign.interval seconds (5) its %placeholders% are filled again from the server's own values - which is how a sign shows a live count, a balance or the time without anyone touching it.

/dsign set <1-4> <text> changes one line of the sign you are looking at and applies it immediately, so the operator sees the change they just made; /dsign clear <1-4> blanks one the same way. /dsign remove stops refreshing the sign you are looking at, leaving its lines as last shown, and /dsign info prints the coordinates, the interval, the range it refreshes within and all four lines - naming %player% or %ping% if they are on a line, because those two are per-player while a sign is one block everybody reads, so they are left as written rather than filled. /dsign list prints every dynamic sign and works from the console, which the verbs that need a sign in front of you do not: they say so and point at the list instead.

dsign.interval and dsign.range are server settings rather than per-sign ones, and the command says as much: a range of 0 refreshes always, otherwise only the signs with a player nearby are redrawn, so a wall of a hundred signs in an empty world costs a walk and no block writes. The words the reference plugin uses for its editor (moveup, individual, interval, range) are answered with what this core does instead - lines are addressed by number, and there is no per-player mode here.

Permissions. ppcore.dsign.admin.

hooks - hooks into other plugins, or this core's own rule

What it does. Answers the questions where a real API beats an imitation, and answers them itself when the API is absent. Each row is a delegation, never the feature: with the switch on and the plugin present the answer comes from the plugin, and with the plugin absent (or the switch off) this core answers the same question its own way - and /hooks prints which happened, one row at a time. The jar links against none of these plugins, so every call is made reflectively by name and a server with none of them installed is unchanged.

The four rows are economy (a balance through Vault's economy), permissions (ask Vault's permission service for a node), levels (read a skill level from mcMMO) and bedrock (ask floodgate whether a player is on Bedrock Edition). There is deliberately no combat switch: the three combat-log plugins each publish a different API this jar does not link against, and a combat tag is a rule (hearts.combatLog) rather than a feature - so a switch would switch nothing off, and /hooks combat still lists the row and says whose rule answers it.

This is not interop.yield, which is about a plugin owning a whole job (EssentialsX owns ess, and the module stands down). The yield table is in chapter 08.

Turning it on. modules.hooks.enabled: true (it ships on). The install of the other plugin is what changes an answer, not a file edit.

What you can configure. hooks.economy.enabled: true, hooks.permissions.enabled: true, hooks.levels.enabled: true, hooks.bedrock.enabled: true. Each one off makes this core answer that question its own way even when the plugin is installed.

Commands. /hooks (every row), /hooks <row> (one row in detail), /hooks has <node> (the caller's answer, naming the path it went through).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/hooks.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help hooks prints the same names and the same nodes, line for line, because both read this one registration.

/hooks

permission: none of its own · always · Hooks.java

Where each external question is answered, which is the one thing worth asking after installing this plugin beside others. There are five: what a player's balance is, whether a player has a permission node, a player's level in a named skill, whether a player is on Bedrock Edition, and whether a player is in combat. Four of them are answered through a real plugin's API when that plugin is installed - Vault for the balance and the permission node, mcMMO for levels, floodgate for Bedrock - and by this core's own rule when it is not, which for Bedrock is an honest "cannot tell". The fifth is deliberately never delegated: the combat plugins it names each publish a different API this jar does not link against, and a combat tag is a rule rather than a feature, so the row records a hook that cannot be installed rather than one that is missing - the answer is always this core's own tag, the last player to hit somebody for hearts.combatLog seconds.

/hooks prints all five rows, each saying whether it is delegated or answered here and in whose words. /hooks <id> prints one of them in the same words on its own. /hooks has <node>, /hooks level [skill] (mining unless you name another), /hooks money and /hooks bedrock are live probes: they ask the question about you right now and say what they asked of, because "yes" and "yes, through Vault" are different answers to "is the hook working" - the probe is how you read the delegate rather than assume it. The probes are for players, so the console is told to ask as somebody.

hooks.economy.enabled, hooks.permissions.enabled, hooks.levels.enabled and hooks.bedrock.enabled are the switches for the four rows that can be delegated, so a server that would rather have every answer come from this core turns them off. Every row is resolved once when the module loads, like the interop yield table: a plugin installed mid-session shows up on the next reload, which is the same rule a module that stands down follows.

Permissions. None.

icsign - signs that run a named action list when clicked

What it does. A sign whose first line reads [ic:<name>] runs the action list <name> from the engine in the actions entry above when a player right-clicks it. The module owns the binding, not the vocabulary: what the sign does is the list's business, which is why this section is only the sign's own tag and how far /ic reaches.

Turning it on. modules.icsign.enabled: true (it ships on), the actions module on, and a list with that name in actions.yml. A sign naming a list that does not exist is reported rather than silently doing nothing.

What you can configure. icsign.tag: "[ic:" (the start of a sign's first line that binds it; the name follows and a ] closes it) and icsign.reach: 6 (how far /ic reaches to report the sign a player is looking at, in blocks).

Commands. /ic - with no argument, or list/info/show, it reports the sign you are looking at and whether the list it names exists. The source plugin's other verbs (addblock, addentity, permreq, cancelevent, and the list-editing words) are recognised and answered with where the thing now lives: the sign for a trigger, /actions for the list, and a plain sentence for a per-sign permission, because there is none.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/icsign.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help icsign prints the same names and the same nodes, line for line, because both read this one registration.

/ic

permission: ppcore.staff.icsign · always · IcSign.java

Signs that run an action list when they are clicked, and the read-back an author wants before the sign goes into the world. The first line reads [ic:name] (the opening word is icsign.tag, default [ic:) and right-clicking the sign runs the action list name for the player who clicked; the click is consumed either way, so a sign used this way never opens its own edit screen.

/ic with nothing after it is the report: what to write on the sign, what the sign you are looking at runs (naming the list, or saying it is not interactive, or that the name on it is not in actions.yml), and the lists a sign here could name. /ic list, /ic info and /ic show are the same report spelled out, and /ic help prints the two lines of usage. /ic <name> answers the author's question directly: what a sign naming that list would do - and it refuses rather than guesses, saying the action engine is off (actions is a module of its own and has to be on) or that no list of that name exists.

The lists themselves are written and edited with /actions, not here; a name written on a sign that does not exist yet is accepted and named to the writer at the moment of writing, so the sign can be put up first and the list written after it. The words the reference plugin uses for its own editor (triggers, the list, how the click behaves) are answered with where that lives here - a trigger this core has is a sign, the list is an action list, and the click always runs it.

Permissions. ppcore.staff.icsign for /ic and for writing the tag on a sign. A player clicking a sign needs no node.

prewards - rewards claimed against minutes played

What it does. A table of rewards unlocked by minutes played and taken with /prewards claim: the first hour, the first day, a week, whatever an owner writes. A reward with repeat: true is paid again for every whole interval played since. The minutes are the same record /playtime and the rank ladder read, so a reward and a rank rung cannot disagree about how long somebody has played.

Turning it on. modules.prewards.enabled: true (it ships on) and one or more entries under prewards.rewards. Off, the command is not registered and prewards.yml is left where it is - the module says so at boot rather than leaving a file that looks live.

What you can configure. prewards.rewards.<name>: minutes (the playtime requirement, or the interval when repeat: true), repeat, commands (run at the console with %player% replaced) and message (optional, & colours). <name> is your own word - it is what /prewards prints and what a claim pays. An entry with no minutes or no commands is skipped and named by /playport check prewards.

Commands. /prewards [player] and /prewards claim [player] (aliases playtimerewards, ptrewards).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/prewards.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help prewards prints the same names and the same nodes, line for line, because both read this one registration.

/prewards (/playtimerewards, /ptrewards)

permission: ppcore.prewards.use · always · Prewards.java

Rewards that unlock against time played. Each one in prewards.rewards names a number of minutes, the commands to run when it is claimed, an optional message to say, and whether it repeats: a one-time reward is due once that much time has been played, a repeating one is due again for every further run of that same interval that has not been paid for yet.

/prewards prints your own list - each reward, whether it is due, claimed or how much longer it needs - against the minutes recorded for you. /prewards <player> is somebody else's list and needs ppcore.prewards.others, so a player cannot read another player's ladder with it; the named player has to be online. A repeating reward can be several intervals behind, and the list says how many are waiting rather than just that it is due.

/prewards claim pays what is due to you, and /prewards claim <player> to somebody else. Each claim runs the reward's commands on the console with %player% replaced by the name, sends the reward's own message to that player as well as naming the reward to whoever typed it, and records it - a one-time reward as claimed, a repeating one by counting the intervals just paid - so nothing is taken twice. A claim with nothing due answers that rather than quietly doing nothing. The bare and list forms are the same read, so /prewards list <player> works too.

Because a player's name is the first word this command accepts, check cannot also be its first word: /prewards check would be read as a player called check. The module's own report is therefore reached through /playport check prewards, which is where a reward with no interval, no commands, or no player who can reach it is named.

Permissions. ppcore.prewards.use for the command; ppcore.prewards.others to list or claim for somebody else, so a staff member can pay a player without becoming that player.

schematics - schematic save and paste (WorldEdit's format, with or without WorldEdit)

What it does. The core's own reader and writer for the Sponge .schem format - the files a WorldEdit, a DungeonMaker or any schematic tool already produces - so /schem works with no plugin installed. When a real WorldEdit is present the provider yields to it and uses WorldEdit's own API, so an operator's files stay the ones their other tools read. Every write is paced: a paste or a set is handed out blocks-per-tick blocks a tick and never more than 8 ms of one, so a large operation cannot freeze the server. A block state this server does not have is skipped and counted, never thrown, so a paste from a newer version leaves a sentence rather than a crash.

Turning it on. modules.schematics.enabled: true (it ships on). Files live in schematics.folder under the plugin's data folder.

What you can configure. schematics.folder: schematics, schematics.max-voxels: 8000000 (the ceiling a region may be on save, paste and set - it is also the format layer's own read cap, so a hostile file cannot allocate past it) and schematics.blocks-per-tick: 4096.

Commands. /schem (aliases schematic, schematics): save, paste, set, probe, list, info, delete, status.

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/schematics.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help schematics prints the same names and the same nodes, line for line, because both read this one registration.

/schem (/schematic, /schematics)

permission: ppcore.schematics.use · always · SchemCmd.java

Schematic save and paste in the Sponge .schem format - the one WorldEdit writes - so files move in both directions whether or not WorldEdit is installed on this server. There is one command with a verb in front of it, and every job it starts is paced: blocks are placed over the following ticks at schematics.blocks-per-tick, and only one job runs at a time, so a second one is refused with what is running and how far it has got.

/schem with no verb, and /schem status, print that status line. /schem save <name> <world> <x1> <y1> <z1> <x2> <y2> <z2> reads the box between two corners and writes it to the folder as <name>.schem - the box is refused before anything is read when it is empty or bigger than schematics.max-voxels, which is the setting that keeps a stray command from reading a hundred million blocks. /schem paste <name> <world> <x> <y> <z> puts a file back into the world with x y z as the box's minimum corner, ignoring the offset the file was saved with, because a caller who named a coordinate meant that corner. /schem set <world> <state> <x1> <y1> <z1> <x2> <y2> <z2> fills a box with one block state through the same pacing, which is the shape a one-off repair takes.

/schem probe <world> <x> <y> <z> reads one block and prints its state, the read that tells you which word to write in set. /schem list names the files in the folder, /schem info <name> reports a file's dimensions, voxel count and palette size without pasting it, and /schem delete <name> removes one. The folder is schematics.folder under the plugin's data directory. A name is the file's own name and is held to what a file can safely be called - letters, digits, underscore and hyphen, up to 48 characters - so anything reaching for a path with a slash or a dot-dot is refused rather than resolved. Reads and writes go through the server's own world class: when that class does not answer the block API, the command says so instead of appearing to work.

Permissions. ppcore.schematics.use.

jobs - jobs: professions that pay a wage as you work

What it does. Professions a player joins, paid as they play: the things this server already counts pay money and experience while you do them, a ladder of levels pays milestones on the way up, and gates let one profession require another ladder (/jobs join headfarmer on a player at spawner level 6 says "needs spawner level 12 (you are 6)" - the number it wants and the number they have).

A job is a quest that never ends, which is why the action vocabulary is quests' own list rather than a second one and why the counters behind it are the listeners that already existed: break, place, kill, playerkill, craft, smelt, fish, harvest, sell, distance, blocks (every block ever mined) and heads (a mob head sold to /sellheads). Four Quests types are deliberately not actions - minutes (time passing is not work; econ.salaryPerMinute is that), rank and spawner-level (a job paying for a rung bought elsewhere is a loop) and money (a job paying for a balance is interest) - and a pay: row for one of them is refused at load with the list of what would have worked.

Turning it on. modules.jobs.enabled: true (it ships on) plus the library packs, which bring ready-made professions in the jar (gather, craft, combat, market). A profession you write under jobs.list is the one that counts: the library only fills ids nothing above it defined, so an edit survives a jar upgrade.

What you can configure. jobs.announce: true, jobs.check-seconds: 5 (how often the one polled action, blocks, is read), jobs.max-jobs: 3 (how many professions one player may hold at once; 0 is no limit), jobs.card-rows: 50 (accounts the panel's jobs card lists, busiest first; 0 is every account - a report cap, never a record cap, because /jobsadmin reads any account by name), jobs.messages.*, the jobs.levels ladder (max and an xp.<level> curve with a default for the rest of the run - one curve for every profession, so two ladders cannot disagree about what "level 20" means), jobs.library.enabled and its four packs, and jobs.list.<job>: name, icon, permission (empty means anyone may take it), max-level, gate (for example spawner-level: 12), pay (per action, with any: true for an unnamed target and a targets map for named ones) and levels (milestone money, items and a command).

Module-level switches: modules.jobs.features.board (the /jobs command) and features.admin (/jobsadmin).

Commands. /jobs list|join <job>|leave <job>|info [job]|top [job] and /jobsadmin (alias jadmin).

Every command this module registers while it is on, one entry each, in the order it registers them. The heading carries the name a player types and its aliases; the italic line under it is what the registration itself says - the permission it asks for, whether a feature switch gates it, and the file that registers it. The sentences below that are this manual's own reading of the command, and they live in manual/reference/commands/jobs.md, one section per command, keyed by the command's own id so a rename in config.yml cannot orphan them. A feature switch that is off removes the command rather than changing it - an operator cannot type it at all, and /playport help <module> will not name it. /playport help jobs prints the same names and the same nodes, line for line, because both read this one registration.

/jobs (/jobs, /job)

permission: ppcore.jobs.use · only while modules.jobs.features.board is on · Jobs.java

What you do for a living, and what it pays. Work a profession and it pays as you go: every action the professions in the config pay for is counted by the quest module's listeners and handed over here, which is why the counters exist even on a server that has switched the quest book off.

/jobs with no argument, and /jobs info [job], are your card: your level in a profession, the xp you have, and what the next level pays. /jobs list is every profession on this server with whether you hold it, the reason you cannot when something blocks you, and the best-paying action in it with its number - so the choice is made from a list rather than a wiki. /jobs join <job> takes one, up to jobs.max-jobs at a time, and prints the first landmark it will reach; /jobs leave <job> stops it and keeps the xp you earned, so leaving is not a reset. /jobs top [job] is the leaderboard for one profession, or across all of them when none is named. /jobs help prints those five lines.

Players only, and the same door is open to staff: /jobs admin <verb> runs the admin verbs below for anyone with ppcore.jobs.admin, which is what tab completion and the executor both read, so the two cannot disagree about where a verb starts.

/jobsadmin (/jadmin)

permission: ppcore.jobs.admin · only while modules.jobs.features.admin is on · Jobs.java

Staff's half of the same ladder: reading and correcting another player's professions. /jobsadmin info <player> prints that player's card - deliberately the very same card, the same levels and the same next landmark they see for themselves, because staff and the player must not be told different numbers about one ladder.

/jobsadmin give <player> <job> <xp>, /jobsadmin set <player> <job> <xp> and /jobsadmin take <player> <job> <xp> add to, set, or subtract from somebody's xp and report the change as before -> after xp, with the level that leaves them on. set and take clamp at zero rather than going negative. A correction also gives the player the profession if they did not hold it, and a correction that pushes them up a level pays that level the same way playing would - a number written where nothing can read it is the half-done edit that makes one player's card look broken. Every verb names a player who has to be online, since there is nobody to pay or to show otherwise.

/jobsadmin reload re-reads the professions from the config and reports how many it found. /jobsadmin library [pack] lists the shipped profession packs with each one's name, how many professions it holds, whether it is on, and how many of those are loaded - which is the answer to "why is this pack's job not in /jobs list". Anything else typed is answered with the verb list.

Permissions. ppcore.jobs.use, ppcore.jobs.admin, and whatever permission: a job sets for itself (the shipped operator profession uses ppcore.jobs.operator).

Files behind this page

What this section documentsSource
stream - the listing, the viewer count and the payoutspkg/src/ppcore/Stream.java (../../pkg/src/ppcore/Stream.java)
tags - worn tags and the animation clockpkg/src/ppcore/Tags.java (../../pkg/src/ppcore/Tags.java)
top, voice, warpsign - the small verbs and the sign bindingpkg/src/ppcore/TopCmd.java (../../pkg/src/ppcore/TopCmd.java), pkg/src/ppcore/Voice.java (../../pkg/src/ppcore/Voice.java), pkg/src/ppcore/WarpSign.java (../../pkg/src/ppcore/WarpSign.java)
actions - the engine, its limits and actions.ymlpkg/src/ppcore/Actions.java (../../pkg/src/ppcore/Actions.java)
dialogs - the definitions, the API probe and the fallbackpkg/src/ppcore/Dialogs.java (../../pkg/src/ppcore/Dialogs.java), pkg/src/ppcore/DialogExamples.java (../../pkg/src/ppcore/DialogExamples.java)
dsign - the stored lines and the refresh clockpkg/src/ppcore/Dsign.java (../../pkg/src/ppcore/Dsign.java)
hooks - the four delegated questions and the reflective callspkg/src/ppcore/Hooks.java (../../pkg/src/ppcore/Hooks.java), pkg/src/ppcore/Interop.java (../../pkg/src/ppcore/Interop.java)
icsign - the sign tag and the list it runspkg/src/ppcore/IcSign.java (../../pkg/src/ppcore/IcSign.java)
prewards - the playtime table and the claimpkg/src/ppcore/Prewards.java (../../pkg/src/ppcore/Prewards.java)
schematics - the .schem reader and writerpkg/src/ppcore/SchemCmd.java (../../pkg/src/ppcore/SchemCmd.java), pkg/src/ppcore/Schematics.java (../../pkg/src/ppcore/Schematics.java)
jobs - the professions, the ladder and the gatepkg/src/ppcore/Jobs.java (../../pkg/src/ppcore/Jobs.java), pkg/src/ppcore/JobLibrary.java (../../pkg/src/ppcore/JobLibrary.java)
Every key and shipped value quoted abovepkg/config.yml (../../pkg/config.yml)