Skip to content
ShadowMC

Configuration

ShadowCommand

The commands your server needs and Minecraft does not have.

Free with Premium v1.0.4

Where the files are

plugins/ShadowCommand/

The plugin creates them on first start and updates them by itself on new versions, keeping your changes.

Applying changes

/scmd reload [file]

Reloads the files without restarting the server.

What’s inside

11 files · 172 settings

Start with: config.yml

Settings

The files that decide how the plugin behaves. Start here.

config.yml

General settings
77 settings
plugins/ShadowCommand/config.yml
File notes

Every value in here already carries the right number for a normal server: you can drop the jar in and never open this file. When you do need to change something, the line above each key says what it does and the line below says what happens if you get it wrong.

Durations are written in plain text: 500ms, 20t (ticks), 30s, 5m, 2h30m, 7d. A bare number means seconds.

Colours are written with &: &a &c &l &r, and &#FF8800 for an exact colour. Gradients are the one exception: <gradient:#a:#b>text</gradient>.

Keys, sections and comments are English everywhere in ShadowCommand. Anything a player reads is a message, and messages live in lang/: write them in whatever language your server speaks.

config-version3

Licence

ShadowCommand is a single jar with two editions inside. With no key it runs the FREE edition: the whole engine, with a ceiling on how many commands, GUIs, triggers and schedules you can have. With a signed key it runs Premium, with no ceilings.

Keep reading

The plugin never shuts itself down over a licence problem. The worst case is going back to free, and saying in console why.

You normally touch nothing here: a key is activated in game with

/scmd license claim <purchase-code>    (the one you got when buying)
/scmd license activate <key>           (if you already hold a key)

and it is written to license.yml, which sits outside this file on purpose: a config gets sent to support, a licence does not.

license4 keys

THE KEY LIVES IN license.yml (same folder as this file), or in the central plugins/ShadowControl/licenses.yml for the whole suite. The 'key' below is only a fallback for deploy systems that generate config.yml: leave it empty.

The key does NOT live here: it lives in license.yml, in this folder, written at first start (or in plugins/ShadowControl/licenses.yml for the whole suite). Only if you would rather keep it here than in license.yml. The environment variable SHADOWCOMMAND_LICENSE and the system property -Dshadowcommand.license=... work too.

grace-days7

Days of grace before falling back to free when a subscription expires or when the server address does not match the licence. A renewal has almost always already gone through, and an address that does not add up is far more often a NAT than a thief.

server-ip""

The address this server is known by, if you know it. Behind a proxy the machine never sees its own domain, and this line is the only way to tell it without calling anyone.

resolve-public-iptrue

One call, at startup, to find out this server's public address: behind NAT it is the only way to recognise it. The answer comes from the activation service below (its /ip address), nobody else is asked. Turn it off and, if the licence is bound to a domain, the grace period still applies. Only when a licence is installed: a server without a key asks nobody. false = never ask.

activation-url'https://shadow-licences.riccardobotins.workers.dev'

Activation service. This is the BASE address: the plugin appends /claim, /check and /ip by itself.

Empty = fully offline verification, and the key is pasted by hand.
Set   = /scmd license claim <code> does everything on its own, and

every three hours the server checks the licence has not been revoked.

messages3 keys
default-locale"en_US"

Language used when the player's own is not available, and by the console. Ships with en_US and it_IT. Set "it_IT" for an Italian server.

follow-client-localetrue

Every player sees their client's language, if the file exists.

auto-updatetrue

On update, your files (this one, lang/ and the other files the plugin created) are moved to the new version automatically: new settings and corrected messages arrive, and everything you had changed or added is carried over. The old files are kept in backup/<date>/; if something needs your attention you will find PORTING-<version>.txt in the plugin folder, saying what to fix. Set false to have nothing touched: new settings and messages are then read from the plugin itself.

storage6 keys
table-prefix"sc_"

Table prefix. Change it only if you share a database with something else.

fallback-to-localtrue

If MySQL does not answer, start on the local file instead of blocking startup.

flush-interval"30s"

How often dirty variables and cooldowns are written to disk.

storage.mysql7 keys
enabledfalse

Set true and fill in the fields below: the plugin creates the tables itself.

host"127.0.0.1"
port3306
database"shadowcommand"
username"root"
password""
parameters"useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC&characterEncoding=utf8"
storage.pool5 keys
maximum-pool-size10
minimum-idle2
connection-timeout-ms10000
idle-timeout-ms600000
max-lifetime-ms1800000
storage.h21 key
auto-serverfalse

Lets another program open the local file while the server is running. You only need it to look inside with an external tool.

engine5 keys
case-insensitive-comparetrue

"YES" and "yes" are the same in comparisons. Set false to tell them apart.

strict-by-defaultfalse

true = the first error always stops the script. Out of the box a failing action is reported and the script carries on.

default-cooldown0

Cooldown applied to commands that do not declare one. 0 = none.

deny-sound"block.note_block.bass"

Sound played when a requirement denies. Empty for silence.

engine.limits6 keys
max-steps10000

Maximum instructions per run: stops a loop that never ends.

max-depth16

Maximum call depth: stops two functions that call each other.

max-sessions-per-player8

Concurrent scripts per player.

max-wait"10m"

Longest a wait: may last. A longer wait is cut down to this.

max-command-chain8

A command that runs a command that runs a command...

action-warn-millis25

Past this threshold a single action is logged as slow, and past four times it a stretch of script that held the thread without ever letting go is logged too. Waiting does not count: a script that says "wait: 1s" stands still for a second holding nothing, which is the whole reason wait exists.

commands5 keys
auto-permissiontrue

Generate shadowcommand.command.<name> for commands with no permission written.

auto-permission-defaulttrue

Who may use a command that declares no permission: true | op | false

register-permissionstrue

Register generated permissions with the server, so LuckPerms completes them.

unknown-argument-shows-usagetrue

A wrong argument shows the full usage line.

tab-complete-hiddenfalse

Tab-complete hidden commands as well.

security7 keys
security.op-action3 keys

The op: action runs a command from the console, with the player's placeholders already filled in: write the player into the command when it needs one (op: gamemode creative {player}). It never makes anybody an operator. Until 1.0.3 it did, for one line: files that use op: are listed in console at startup. It also stops when console-action.allowed below is false.

enabledtrue
allowed-commands[ ]

Empty = any command. Otherwise only the ones listed.

logtrue

Every use is logged in console with who, what and from which command.

security.bypass-action1 key

The bypass: action runs a command granting a permission for an instant.

enabledtrue
security.console-action1 key

The console: action (and op:, which runs from the console too).

allowedtrue
security
forbidden-commands[stop, op, deop, reload]

Commands no script may run, by any means. This is the emergency brake: a badly written file must not be able to shut the server down.

security.block-actions2 keys

The actions that change blocks: fill, cylinder, move, paste...

Out of the box they do NOT ask for a permission, and that is not an oversight: the normal use is a command of yours that opens a gate for any player at all, and demanding that player hold a builder permission would break every command of that kind. Turn it on when your staff may write commands but must not be able to rewrite the map.

require-permissionfalse
permission"shadowcommand.blocks"
security
max-broadcast-per-second10

Protection against an announcement repeated by mistake every tick.

escape-player-inputtrue

Anything a player writes - the name of the item in hand, the text on a sign, the arguments of a command - is defused before it goes into a message: a "<click:run_command:...>" typed into a nickname stays written, it does not become a button. Leave this on.

Block editing

A floor is not one block, it is forty thousand. The fill, cylinder, sphere, move and paste actions do not place them all at once: they do a few thousand per tick until they are done, so a town square goes up in two seconds without the server missing a beat.

Keep reading

Whatever fits in one chunk and inside a tick's budget happens RIGHT AWAY: a gate opens in the same instant, not on the next tick.

blocks5 keys
max-blocks500000

The parachute against one zero too many. A fill written wrong rewrites half a world, and this line is the difference between a mistake and a disaster.

per-tick4000

How many blocks per tick at most. Higher = faster and heavier.

undo-history5

How many operations can be taken back with the undo action, per player. 0 turns undo off and keeps nothing in memory.

undo-max-blocks50000

An operation bigger than this is not recorded for undo: holding half a million blocks in memory for a "maybe" is not worth it.

physicsfalse

Whether the world should react while it fills (sand falling, torches popping off). Off is much faster and is almost always what you want: for a single gate or door use physics:true in the action itself.

triggers3 keys
enabledtrue

Turns every trigger off at once, without deleting the files.

move-check-interval4

Ticks between two player-move-block checks.

region-check-interval10

Ticks between two region checks, for region-enter and region-exit.

menus4 keys
default-update-interval0

Live refresh out of the box, in ticks. 0 = only when needed.

click-delay-millis100

Double-click guard, in milliseconds.

max-depth8

How many menus may be nested before it stops.

return-items-on-quittrue

Gives back items left inside a GUI that accepts them.

variables3 keys
cache-offline-minutes10

How long the variables of someone who just left stay in memory.

purge-after-days0

Delete the variables of anyone who has not joined in this many days. 0 = never.

expire-check-interval"1h"

How often expired ones are cleaned up.

logging4 keys
logging.audit2 keys
enabledfalse

Record every run in the database: who, what, with what outcome.

keep-days30

How many days to keep the rows.

logging
debugfalse

Very verbose log, useful only while hunting a problem.

log-slow-actionstrue

Report actions slower than the threshold in engine.limits.

log-sqlfalse

Write every query to the log. Only to understand a database problem.

compatibility3 keys
dollar-placeholderstrue

Accept $player, $arg1, $world like MyCommand.

section-color-codestrue

Accept the section character as well as &.

mycommand-typestrue

Accept type: RUN_COMMAND, runcmd:, permission-required and the like.

integrations4 keys
suitetrue

Hook into ShadowEssentials and the other plugins in the suite.

vaulttrue

Economy and permissions through Vault.

placeholderapitrue

Reading and providing %...% placeholders.

regionstrue

ShadowRegions or WorldGuard, for the triggers and the region: requirement.

bStats - anonymous usage statistics (https://bstats.org)

What is sent, every 30 minutes: that this server runs ShadowCommand, the plugin, Minecraft/Paper and Java versions, the operating system, the number of CPU cores, how many players are online (a number, nothing else), a random serverUuid that identifies nothing, and the edition in use (free, premium or creator). No player data leaves the server: no names, no UUIDs, no addresses, and nothing from your commands, menus or scripts.

metrics1 key
enabledtrue

false switches it off for this plugin only. enabled: false in plugins/bStats/config.yml switches it off for every plugin on this server at once.

overrides.yml

Command overrides
4 settings
plugins/ShadowCommand/overrides.yml
File notes

This is where you decide what to do with other plugins' commands: block them, replace them with a script of your own, or give them a shorter name.

The check happens on what the player types, not on the command tree, so it works with plugins that register after us and it catches the aliases too (/pl as well as /plugins).

The permission shadowcommand.bypass.block skips every block.

config-version2

Blocked commands

block

Nothing is blocked out of the box: these are examples, and they work the moment you remove the comment marks.

- command: "plugins"
  aliases-too: true              # blocks /pl and /? as well
  bypass-permission: "server.seeplugins"
  message: "&7This command is &cnot available&7."
- command: "me"
  message: "&7This command is &cturned off&7 on this server."
- command: "minecraft:tell"
  aliases-too: false

Commands replaced by a script of ours

intercept
- command: "spawn"
  cancel-original: true          # false = runs the original as well
  run:
    - "message: &7Taking you to spawn..."
    - "wait: 2s"
    - "teleport: spawn"
    - "sound: entity.enderman.teleport"

Aliases: type one thing, run another

alias

The name on the left becomes what is on the right, arguments included. gm0: "gamemode survival" gm1: "gamemode creative" w: "msg"

panel.yml

The panel
8 settings
plugins/ShadowCommand/panel.yml
File notes

How the panel that /scmd opens looks.

Everything in here already has a fallback in the code: deleting a key breaks nothing, it just goes back to the built-in value.

Slots are counted from 0. The panel is five rows tall (0-44).

Keys are English. THE WORDS: a title or a name written "lang:<key>" comes from the language files in lang/, under admin.gui.panel, so every admin reads it in their own language. Write a text instead - for example name: "&e&lMy commands" - and that text is shown as it is, to everybody: what you write here wins over the language file. The same goes for a lore you add yourself.

config-version2
panel15 keys
title"lang:admin.gui.panel.title"
12 more entries shaped like title: rows, filler, menus, triggers, schedules, variables, stats, errors, vocabulary, close, import, info

the eight doors

panel.commands3 keys
slot10
materialCOMMAND_BLOCK
name"lang:admin.gui.panel.commands"
lore: [ ... ]   # write one and it replaces the automatic lore

the bottom row

panel.reload3 keys

Close sits in the middle of the last row, where it sits in every other window of the plugin - the example menu and the variable list both put it there. It used to be in the corner, and the version book in the middle: two windows side by side asked for two different clicks.

slot38
materialLIME_CONCRETE
name"lang:admin.gui.panel.reload"

schedules.yml

Scheduled commands
15 settings
plugins/ShadowCommand/schedules.yml
File notes

The things that happen by themselves. Three ways of saying when:

every: "10m"        every so often
at: "04:00"         at one time of day, every day
cron: "0 * * * *"   minute hour day month day-of-week

Useful keys:

enabled          turns one off without deleting it
on-start         also runs once when the server starts
for-each-player  repeats for every online player, with {player} filled
async            runs off the main thread (safe actions only)
conditions       the same requirements commands use

Out of the box there is one announcement, turned off. Turn it on to see how it behaves.

What a player reads ships in English: it is yours to rewrite. An Italian version of this file is in esempi/it_IT/schedules.yml. It is not loaded: copy it over this one to use it.

config-version2
schedules4 keys
schedules.announcement3 keys

An announcement every ten minutes. Off: turn it on when you want it.

enabledfalse
every"10m"
run["broadcast: &8[&fInfo&8] &7Type &f/rules&7 to…]
schedules.wage5 keys

An hourly wage for whoever holds the permission. Off out of the box: it touches the economy, and nobody should find money in their pocket without having decided so.

enabledfalse
cron"0 * * * *"
for-each-playertrue
conditions["permission: server.wage"]
run
["money: give 100", "actionbar: &7+&a100 coins&7 &8(hourly wage)"]+1
schedules.cleanup3 keys

Ground item cleanup, with two minutes of warning.

enabledfalse
every"30m"
run
["broadcast: &8[&fCleanup&8] &7Items on the ground will disappear in &f2 minutes&7.", "wait: 1m", "broadcast: &8[&fCleanup&8] &f1 minute&7 left.", "wait: 50s", "broadcast: &8[&fCleanup&8] &7In &c10 seconds&7.", "wait: 1 …+7
schedules.nightly-backup3 keys

A nightly backup. The command depends on your host: change it.

enabledfalse
at"04:00"
run["console: save-all", "log: nightly save done"]

commands/examples.yml

Custom commands
7 settings
plugins/ShadowCommand/commands/examples.yml
File notes

A catalogue: one example of every kind of command you can build.

THREE ARE LIVE - /rules, /discord and /examplemenu work the moment the server starts. Everything else is commented out, so a fresh install stays clean and nothing you did not ask for appears in the command list.

TO TURN ONE ON, remove the two characters "# " from the front of every line of the block and run /scmd reload. The indentation is already right underneath.

Create as many files as you like in this folder, sub-folders included. The file name means nothing to the plugin: it is only there so you can find things again.

Everything you type - command names, keys, options, values - is English. Everything a player reads is a message, and messages are yours to write in any language: the ones below ship in English. An Italian version of this file is in esempi/it_IT/commands/examples.yml. It is not loaded: copy it over this one to use it.

MESSAGE STYLE, the same one lang/ uses:

&7 the body of the sentence      &f the words that matter
&c what is wrong or missing      &a what has just gone right
&8 the quiet part                no &l: a message is read, not shouted

Placeholders with {}: {player} {arg1} {var:points} {math:1+2} The full list is in /scmd placeholders and in docs/09-VARIABILI.md.

config-version2

01 Text only live

The shortest command there is: a list of lines, no script. Good for rules, info, staff lists, anything that only has to be printed.

rules2 keys
description"The server rules"
text
["&8&m ", "&fServer rules", "&8 1 &7Respect the others.", "&8 2 &7No cheats or third-party programs.", "&8 3 &7No advertising other servers.", "&8 4 &7Have fun.", "&8&m …+6

03 Opens a GUI live

One line, and the command opens menus/example.yml. The same window can be opened from a script with menu: example.

examplemenu2 keys
description"Opens the example menu"
menu"example"

04 Aliases, usage line and visibility

Everything a command can say about itself. hidden: true keeps it out of the client's tab list without turning it off.

website:
  description: "The server website"
  aliases: [ "site", "web" ]
  usage: "/website"
  hidden: false
  run:
    - "message: &7Our website: &fwww.example.com"

05 Typed arguments

The client completes them and the plugin checks them before your script runs. Types: player, offline-player, world, material, choice, int, decimal, word, text, boolean. {arg1} is the first, {args:2-} everything from the second on.

kit:
  description: "Claim a kit"
  permission: "server.kit.{arg1}"
  arguments:
    - name: "kind"
      type: choice
      values: [ "wood", "iron", "diamond" ]
      required: true
      error: "&7Pick one of &fwood&7, &firon&7 and &fdiamond&7."
    - name: "amount"
      type: int
      min: 1
      max: 5
      default: 1
      error: "&7The amount goes from &f1&7 to &f5&7."
  run:
    - "message: &7Here is the &f{arg1}&7 kit &8(x{arg2})"
    - "repeat: {arg2}"
    - "  if: {arg1} == diamond"
    - "    give: DIAMOND_SWORD 1 name:'&fKit sword' enchant:SHARPNESS:3 unbreakable"
    - "  elseif: {arg1} == iron"
    - "    give: IRON_SWORD 1"
    - "  else:"
    - "    give: WOODEN_SWORD 1"
    - "  end"
    - "end"
    - "sound: entity.player.levelup"

06 Sub-commands

A command with children, each with its own permission, its own arguments and its own script. Nest as deep as you like.

staff:
  description: "Staff tools"
  permission: "server.staff"
  permission-default: op
  run:
    - "message: &7Use &f/staff list&7 or &f/staff warn&7."
  subcommands:
list:
  description: "Who is online"
  run:
    - "message: &7Online: &f{server_online}&7/&f{server_max}"
    - "message: &7Names: &f{server_online_names}"
warn:
  description: "Send a player a warning"
  arguments:
    - { name: "target", type: player, required: true }
    - { name: "reason", type: text, required: true }
  run:
    - "tell: {arg1} &cWarning &8- &7{args:2-}"
    - "message: &7Warning sent to &f{arg1}&7."
    - "console: say &8[staff] &7{player} warned {arg1}"

07 Permissions

A command with no permission: gets shadowcommand.command.<name> and everyone has it. Write one, or a list, and say what happens when the player does not hold it.

vipkit:
  description: "The VIP kit"
  permissions: [ "server.vip", "server.bundle.kit" ]
  permissions-mode: any            # all (default) needs them all, any needs one
  permission-message: "&7This kit is for &fVIPs&7. Have a look at &f/shop&7, {player}."
  run:
    - "give: DIAMOND 8"

08 Requirements

Checked before anything runs: nothing is taken and nothing happens when one denies. 48 of them, listed by /scmd requirements. requirements-mode: all (default) | any | none

home:
  description: "Go back to your home"
  player-only: true
  requirements:
    - "world: !world_the_end"
    - type: condition
      value: "{var:home_x|none} != none"
      deny: "&7You have not set a home yet. Use &f/sethome&7."
    - type: condition
      value: "{health} > 4"
      deny: "&7You are &ctoo hurt&7 to teleport."
      sound: "block.note_block.bass"
  requirements-mode: all
  run:
    - "teleport: {var:home_x} {var:home_y} {var:home_z} {var:home_world}"
    - "message: &7Welcome back &ahome&7."

09 Cost

Money, experience, levels, hunger, health, items. All or nothing: if one piece cannot be paid, nothing at all is taken.

repairhand:
  description: "Repair what you are holding"
  permission: "server.repair"
  requirements:
    - "item-in-hand: !AIR"
  cost:
    money: 250
    items:
      - "IRON_INGOT 4"
    message: "&7You paid &f250 coins&7 and &f4 ingots&7."
  run:
    - "console: repair {player}"
    - "message: &7Repaired. &aDone&7."
    - "sound: block.anvil_use"

10 Cooldown

Per player, or one for the whole server. It survives a restart, so a 24 hour kit really is 24 hours.

daily:
  description: "The daily reward"
  cooldown: "24h"
  cooldown-persistent: true
  cooldown-bypass: "server.nocooldown"
  cooldown-message: "&7You already claimed the reward. Come back in &f{cooldown_left}&7."
  run:
    - "money: give 500"
    - "give: DIAMOND 1"
    - "message: &7Reward claimed. &aCome back tomorrow&7."

11 Warm-up

The wait before it starts, with a boss bar, cancelled if the player moves or takes damage.

spawn:
  description: "Go back to spawn"
  player-only: true
  warmup: "5s"
  warmup-bossbar: true
  warmup-cancel-on-move: true
  warmup-cancel-on-damage: true
  run:
    - "teleport: 0 100 0 world"
    - "message: &7You are at &fspawn&7."
    - "sound: entity.enderman.teleport"

12 Confirmation

The question arrives in chat with two clickable buttons and a one-shot code: nobody can answer in your place. Never make a player retype a command to confirm it.

clearinv:
  description: "Empty your inventory"
  player-only: true
  confirm: true
  confirm-text: "&7You are about to empty &call&7 of your inventory."
  run:
    - "clear:"
    - "message: &7Inventory emptied."

13 Who may run it

player-only keeps the console out, console-only keeps the players out. Useful for anything a scheduler or a panel calls.

announce:
  description: "Announcement from the console"
  console-only: true
  arguments:
    - { name: "text", type: text, required: true }
  run:
    - "broadcast: &8[&fInfo&8] &7{args}"
    - "sound-all: block.note_block.pling 0.5 1.5"

14 Variables

var: this player, gvar: everyone with their own copy, svar: one for the whole server. They live in the database, so they survive a restart and can be shared between servers over MySQL.

stats:
  description: "Your statistics"
  run:
    - "var: add opened 1"
    - "message: &8&m                                        "
    - "message: &7Statistics opened &f{var:opened}&7 times."
    - "message: &7Health: &f{health}&7/&f{maxhealth}   &7Level: &f{level}"
    - "message: &7World: &f{world}   &7Ping: &f{ping}ms"
    - "message: &7Play time: &f{playtime}"
    - "message: &8&m                                        "

15 Branching

if / elseif / else / end, with comparisons on anything: numbers, text, placeholders, free expressions.

health:
  description: "How are you doing"
  player-only: true
  run:
    - "if: {health} >= 18"
    - "  message: &7You are &avery well&7. &8({health}/{maxhealth})"
    - "elseif: {health} >= 8"
    - "  message: &7You are a bit &fbattered&7. &8({health}/{maxhealth})"
    - "else:"
    - "  message: &7You are in &cbad shape&7! &8({health}/{maxhealth})"
    - "  effect: REGENERATION 10 1"
    - "end"

16 Loops and waiting

repeat, while, for, break, and wait: - which suspends the script without holding the server for a single tick.

countdown:
  description: "A countdown"
  permission: "server.staff"
  run:
    - "for: n in 5,4,3,2,1"
    - "  broadcast: &7Starting in &f{n}&7..."
    - "  sound-all: block.note_block.hat"
    - "  wait: 1s"
    - "end"
    - "broadcast: &aGo!"
    - "sound-all: block.note_block.pling 1 2"

17 Chance and random

chance: N opens a block that runs N times out of a hundred, and {rand:...} picks a number or an entry.

crate:
  description: "Open a prize crate"
  cooldown: "1h"
  cost:
    items:
      - "TRIPWIRE_HOOK 1"
  run:
    - "repeat: 5"
    - "  actionbar: &7Drawing &f{rand:iron,gold,diamond,emerald}"
    - "  sound: block.note_block.hat"
    - "  wait: {math:{loop}*4}t"
    - "end"
    - "chance: 2"
    - "  give: NETHER_STAR 1"
    - "  broadcast: &f{player} &7drew a &fNether star&7!"
    - "  stop"
    - "end"
    - "chance: 20"
    - "  give: DIAMOND 8"
    - "  message: &7You won &f8 diamonds&7."
    - "  stop"
    - "end"
    - "money: give {rand:50-250}"
    - "message: &7Consolation prize: &f{result} coins&7."

18 Functions

A block of script other commands can call, with arguments and a return value. They can live here under functions:, or in their own file: see functions/examples.yml.

vote:
  description: "Vote for the server"
  run:
    - "call: separator"
    - "message: &7Vote here: &fwww.example.com/vote"
    - "call: separator"
functions:
  separator:
    run:
      - "message: &8&m                                        "

19 Building from a command

fill, hollow, walls, outline, replace, cylinder, sphere, move, copy, paste, undo and blockdata. ~ is where the player stands. A gate is one blockdata line, and it opens in the same instant.

gate:
  description: "Opens and closes the gate"
  permission: "server.gate"
  cooldown: "2s"
  run:
    - "blockdata: 102 71 102 toggle=open"
    - "blockdata: 103 71 102 toggle=open"
    - "sound: block.fence_gate.open"
    - "message: &7The gate moves."
floor:
  description: "A floor under your feet"
  permission: "server.builder"
  player-only: true
  run:
    - "fill: ~-3 ~-1 ~-3 ~3 ~-1 ~3 SMOOTH_STONE"
    - "message: &7Done. If you do not like it: &f/revert"
revert:
  description: "Undo the last block changes"
  permission: "server.builder"
  run:
    - "undo: 1"
    - "message: &7Put back &aas it was&7."

20 Running something the player could not

console: runs from the console. op: runs from the console too, with the player's placeholders filled in (op: gamemode creative {player}), and every op: use is logged with who and what: it never makes anyone an operator. bypass: grants one permission for one line only, runas: runs the line as somebody else.

giverank:
  description: "Give a player a rank"
  permission: "server.staff.rank"
  arguments:
    - { name: "target", type: player, required: true }
    - { name: "rank", type: choice, values: [ "vip", "vip+", "mvp" ], required: true }
  confirm: true
  confirm-text: "&7You are about to give &f{arg2}&7 to &f{arg1}&7."
  run:
    - "bypass: luckperms.user.parent.add | lp user {arg1} parent add {arg2}"
    - "tell: {arg1} &7You received the rank &a{arg2}&7."
    - "broadcast: &f{arg1} &7is now &f{arg2}&7."
    - "log: [ranks] {player} gave {arg2} to {arg1}"

21 Alternative endings

What to do when something says no. Without these the player just gets the standard message from lang/.

shop:
  description: "Opens the shop"
  menu: "shop"
  cooldown: "3s"
  requirements:
    - "world: !world_the_end"
  on-deny:
    - "sound: block.note_block.bass"
  on-cooldown:
    - "message: &7Another &f{cooldown_left}&7 before you can open it again."
  on-error:
    - "message: &7Something went wrong, try again."

22 Placeholders from other plugins

%...% is PlaceholderAPI: anything any other plugin publishes can be read here. Without PlaceholderAPI installed they stay written as is.

profile:
  description: "Your profile"
  run:
    - "message: &7Rank: &f%vault_group%"
    - "message: &7Balance: &f{money_formatted}"
    - "message: &7Position: &f{x}, {y}, {z} &8({world})"

functions/examples.yml

Functions
6 settings
plugins/ShadowCommand/functions/examples.yml
File notes

A function is a block of script other scripts can call:

- "call: reward 3 'first join'"
- "message: &7The function returned {result}."

Arguments become local variables, so they never touch the player's own variables. Recursion is allowed up to the limit in config.yml (engine.limits.max-depth).

Functions can also live inside a command file, under a "functions:" key: they are together here only because it reads better.

What a player reads ships in English: it is yours to rewrite. An Italian version of this file is in esempi/it_IT/functions/examples.yml. It is not loaded: copy it over this one to use it.

config-version2
functions3 keys
functions.reward2 keys

Gives diamonds, says so, and returns how many it gave.

args[amount, reason]
run
["give: DIAMOND {amount}", "message: &7+&f{amount}&7 diamonds &8({reason})", "sound: entity.experience_orb.pickup", "return: {amount}"]+3
functions.separator1 key

A separator line, because writing it out every time gets old.

run["message: &8&m…]
functions.announce2 keys

An announcement in the server's own format, written once: change it here and it changes everywhere.

args[text]
run
["broadcast: &8[&fInfo&8] &7{text}", "sound-all: block.note_block.pling 0.5 1.5"]+1

Languages

The texts the plugin shows to players, one file per language.

lang/

Texts and messages
2 files
plugins/ShadowCommand/lang/
File notes

This file holds what the players read, and it is the final fallback: a key missing from every other language lands here. The keys are English like everywhere else in the plugin; only the values are text for a player, so only the values get translated.

HOUSE STYLE - keep it when you edit these lines:

&7 is the body of every message. Nothing else.
&f marks the words that matter: names, numbers, files, commands.
&c marks what is missing, blocked or wrong.
&a marks what has just gone right.
&8 is for the quiet part: ids, hints, separators.
No &l in chat. A message is read, not shouted.

Colours are written with &: &a &c &r, and &#FF8800 for an exact one. The only exception are gradients, which no & code can express:

<gradient:#7E57C2:#4FC3F7>text</gradient>

System message placeholders are written <like_this>. The ones you write in your own commands and menus are {like_this}.

A message can be a string or a small map:

key:
  text: "&cText"
  channel: ACTION_BAR      # CHAT | ACTION_BAR | TITLE | SUBTITLE | NONE
  sound: block.note_block.bass
  volume: 0.7
  pitch: 0.8
  cooldown: "2s"           # anti spam, per player

channel: NONE silences a message without deleting the key.

Every message the plugin shows to players, one file per language. The keys are not listed one by one: here are the files, how many texts they hold and the placeholders they use.

FileLanguageTexts
lang/en_US.yml reference English 893
lang/it_IT.yml Italiano 893

Most used placeholders

The plugin replaces them with the right value: leave them written as they are.

  • <prefix>
  • <count>
  • <value>
  • <command>
  • <max>
  • <player>
  • <page>
  • <pages>
  • <name>
  • <error>
  • <status>
  • <where>
  • <id>
  • <file>

Message groups

  • prefix 1
  • common 20
  • command 31
  • cost 10
  • requirement 19
  • engine 4
  • problem 45
  • note 60
  • input 5
  • admin 364
  • license 51
  • blocks 2
  • catalog 281

Other

Ready-made content, examples, imports: the files you copy and edit as a model.

triggers/

Automatic triggers
2 files
plugins/ShadowCommand/triggers/

2 files with the same structure. Below, 2 of them in full, one for each different shape; the others are listed by name.

  • examples.yml
  • suite.yml

Example triggers/examples.yml

File notes

A script that runs by itself when something happens.

TWO ARE LIVE - the welcome on a first join and the line on every join. Everything else has enabled: false and is there to be read and turned on: one example per kind of trigger.

Listeners are only registered when somebody uses them: turn off every trigger of one kind and that listener does not even exist, so it costs nothing. /scmd triggers shows which ones are live and how many times they have fired.

MESSAGE STYLE, the same one lang/ uses:

&7 the body of the sentence      &f the words that matter
&c what is wrong or missing      &a what has just gone right
&8 the quiet part                no &l in chat

Every event name is in docs/08-TRIGGER.md and in /scmd triggers.

What a player reads ships in English: it is yours to rewrite. An Italian version of this file is in esempi/it_IT/triggers/examples.yml. It is not loaded: copy it over this one to use it.

config-version2
triggers9 keys
A player's very first join. Fires once per player, ever
triggers.welcome2 keys
eventplayer-first-join
run
["broadcast: &8[&a+&8] &f{player} &7joined for the first time.", "title: &fWelcome", "subtitle: &7Read the rules with &f/rules", "give: BREAD 16", "sound: entity.player.levelup"]+4
Every join: one line on the action bar, so the chat stays clean
triggers.greeting2 keys
eventplayer-join
run
["wait: 1s", "actionbar: &7Welcome back, &f{player}&7."]+1
triggers.diamonds6 keys

A filter on the event itself: diamonds only, one world only, with a cooldown so a vein does not spam the chat.

eventblock-break
enabledfalse
block"DIAMOND_ORE,DEEPSLATE_DIAMOND_ORE"
world"world"
cooldown"3s"
run["broadcast: &f{player} &7found diamonds at &f…]
A sign that opens a GUI

The first line is coloured automatically when somebody writes it, and only who holds the permission may.

triggers.menu-sign5 keys
eventsign-click
enabledfalse
line1"[Menu]"
create-permission"server.signs"
run["menu: example"]
On death: a line to whoever died, with the cause and the killer
triggers.death3 keys
eventplayer-death
enabledfalse
run
["message: &7You died&8: &c{death_cause}", "if: {killer} != ''", " message: &7You were killed by &c{killer}&7.", end]+3
Cancelling an event: here, flower pots may not be broken

cancel: true always cancels; the "cancel" action decides case by case.

triggers.protect-flowers6 keys
eventblock-break
enabledfalse
block"FLOWER_POT"
conditions["permission: !server.staff"]
canceltrue
run["message: &7Hands off &cthe flower pots&7."]
Walking into a region, and out of it again. Needs ShadowRegions or

WorldGuard: without one of the two this trigger is not registered.

triggers.arena-enter4 keys
eventregion-enter
enabledfalse
region"arena"
run
["title: &fArena", "subtitle: &7This is where we fight.", "sound: entity.ender_dragon.growl 0.4 1.4"]+2
Right-clicking with a chosen item in hand

The action names are the server's own: RIGHT_CLICK_AIR when there is no block in front, RIGHT_CLICK_BLOCK when there is, so both are listed.

triggers.compass-menu6 keys
eventitem-click
enabledfalse
item"COMPASS"
action"RIGHT_CLICK_AIR,RIGHT_CLICK_BLOCK"
cooldown"500ms"
run["menu: example"]
Chat: reading what was written, and cancelling it when needed

{message} is what the player typed.

triggers.no-caps5 keys
eventplayer-chat
enabledfalse
conditions
["permission: !server.staff", "condition: {len:{message}} > 8", "condition: {upper:{message}} == {message}"]+2
canceltrue
run["message: &7Write in &clower case&7, please."]

Example triggers/suite.yml

File notes

Reacting to OTHER plugins' events, without depending on them.

event: bukkit-event
class: "full.package.of.TheEventClass"

The class is looked for in every enabled plugin. If the plugin that defines it is not installed, the trigger stays off and shows up in /scmd errors with the name you wrote: a wrong package is otherwise the hardest thing to notice.

Every readable property of the event becomes a placeholder:

getNewLevel()  ->  {new_level}
isPremium()    ->  {premium}
getPlayer()    ->  {player}

/scmd debug lists them while it happens.

Everything in here is off out of the box: turn on what you need.

What a player reads ships in English: it is yours to rewrite. An Italian version of this file is in esempi/it_IT/triggers/suite.yml. It is not loaded: copy it over this one to use it.

config-version2
triggers7 keys
ShadowBattlePass: somebody goes up a tier
triggers.pass-tier-up4 keys
enabledfalse
eventbukkit-event
class"net.shadowmc.battlepass.api.event.BattlePassTierUpEvent"
run
["broadcast: &8[&fPass&8] &f{player} &7reached tier &f{new_tier}&7.", "sound-all: entity.player.levelup 0.5 1.2"]+1
5 more entries shaped like pass-tier-up: pass-quest-done, pass-season-start, match-end, crate-open, variable-changed
triggers.match-join4 keys

ShadowGames: joining a match. The pass experience is handed out here, so the two plugins talk without knowing about each other.

enabledfalse
eventbukkit-event
class"net.shadowmc.games.api.event.GamePlayerJoinEvent"
run
["actionbar: &7You joined &f{game}&7.", "battlepass: event games-played 1"]+1

All the documentation