Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- HyperSkript — User Guide and Language Reference
- ==============================================
- This document describes how to write HyperSkript (.hs) scripts for the HyperSkript plugin.
- It covers file layout, language syntax, variables, events, commands, state & persistence,
- built-in functions and methods, interpolation, examples, and common troubleshooting tips.
- Quick start
- -----------
- 1. Put .hs files into plugins/HyperSkript/scripts/ (the plugin creates an example.hs on first run).
- 2. Use /hs reload (requires permission hyperskript.admin) to reload scripts at runtime.
- 3. Script structure is indentation-based (like Python). Use 4 spaces per indent level (or tabs; tabs count as 4 spaces).
- 4. Comments start with # and continue to end-of-line.
- 5. Strings use double quotes (") and support escape sequences: \n, \t, \", \\.
- File layout / top-level constructs
- ---------------------------------
- Top-level constructs in a .hs file:
- - persist ... — configure persistence (sqlite)
- - state ... — declare script-level variables (simple or map)
- - on <event>: — event handler block
- - command <name>(...): — command handler block
- Example (full example shipped with plugin):
- persist default sqlite
- state String:welcome_msg default "Welcome to the server!"
- state homes:<Player,Location> persist default []
- on player join:
- event.join_message = ""
- player.send("{welcome_msg}")
- command sethome():
- homes[sender] = sender.location
- Lexical rules, whitespace, comments
- ----------------------------------
- - Comments: start with # and run to end of line.
- - Lines are terminated by newline; blank lines and comment-only lines are ignored for indentation.
- - Indentation: INDENT / DEDENT tokens are generated by counting leading spaces (tab = 4 spaces). Use consistent indentation.
- - Strings: "..." with escapes (\n, \t, \", \\).
- - Numbers: integer "123" or decimal "12.34".
- - Keywords: on, command, if, else, for, stop, cancel, and, or, not, persist, default, sqlite, file, wal, state, True, False, null, etc.
- Top-level persist configuration
- -------------------------------
- Syntax (common forms accepted by parser):
- - persist default sqlite
- - persist sqlite
- - persist sqlite file "path/to/file.db" wal True
- Behavior:
- - sqlite => uses an SQLite DB per script; default path: plugins/HyperSkript/data/<scriptname>.db
- - wal True => enables WAL mode for better concurrent performance
- - When persist is configured, state variables declared with "persist" will be saved/loaded automatically.
- State declarations
- ------------------
- Syntax examples:
- - state String:welcome_msg default "Hello"
- - state Int:count default 0
- - state homes:<Player,Location> persist default []
- - state name_of_map:<Player,String> persist
- Meaning:
- - Simple states: valueType:name (String, Int, Location, etc.)
- - Map states: name:<KeyType,ValueType>
- - "persist" after declaration marks it to be stored in DB (requires persist config).
- - default <expr> supplies the initial value if not present in DB.
- - For empty map default use default [] (parser recognizes empty list literal as map default).
- Accessing states:
- - Simple state (non-map): directly by name (welcome_msg).
- - Map state: homes[key] for get or homes[key] = value for set.
- - State variables are namespaced per script.
- Events
- ------
- Syntax:
- on <event type>:
- <statements>
- Supported event types in the implementation (examples):
- - player join
- - player quit
- - block break
- - block place
- - player chat
- Notes:
- - Join/quit listeners are registered at EventPriority.HIGHEST so event.join_message / event.quit_message changes reliably take effect.
- - To cancel an event (if it’s cancellable), use "cancel event" inside the handler (works for chat and other cancellable events).
- Event handler example:
- on player join:
- event.join_message = "&aWelcome, {player.name}!"
- player.send("{welcome_msg}")
- Commands
- --------
- Syntax:
- command name(Type: param=default, ...) :
- <statements>
- Parameter types supported by parser:
- - Int, String (parser recognizes Int and String type tokens)
- - Parameters may specify a default via =value
- - Example:
- command heal(Int: amount=10):
- if sender.permission("server.heal") == False:
- sender.send("&cYou don't have permission")
- stop
- sender.health = min(20, sender.health + amount)
- Command behavior:
- - Registered dynamically at runtime.
- - Label conflicts: plugin tries to register; if label already exists registration can fail.
- The implementation includes a FORCE_OVERRIDE_CONFLICTING_COMMANDS flag which, if true,
- forces the plugin to override conflicting knownCommands entries so your script commands work after reload.
- - After commands are registered, plugin attempts to call server.syncCommands() via reflection to sync Brigadier
- (on Paper/modern forks). If not available, newly added commands may not be recognized by Brigadier until a full server reload
- unless you access them using plugin namespace (hyperskript:<cmd>).
- Statement types
- ---------------
- - Method call statements: player.send("..."), sender.give(1, diamond)
- - Assignments: x = expr or target.property = expr (e.g., sender.health = 10, event.join_message = "...")
- - Map assignments: homes[sender] = sender.location
- - If/else:
- if condition:
- ...
- else:
- ...
- - For loops:
- for i runs(1, 5):
- ...
- Also supports for i runs(times): where times is an expression and start defaults to 1.
- - cancel event — cancels the current event (if cancellable)
- - stop — stops execution of the current event/command handler
- Expressions
- -----------
- - Literals: numbers (integer or decimal), strings, True/False, null, [] (empty list)
- - Variables:
- - Simple variables (from context or state): amount, welcome_msg
- - Built-in roots: player, sender, server, event, block
- - Property access: player.name, sender.health, block.type, server.players
- - Method calls: sender.permission("node"), player.teleport(location)
- - Function calls: min(a, b), max(a,b), random(a,b), chance(p), distance(a,b), difference(a,b), round(x)
- - Map access: homes[sender]
- - Binary ops: +, -, *, /, <, >, <=, >=, ==, !=
- - Logical ops: and, or, not
- Evaluation & types
- ------------------
- - Numbers: If a literal contains a dot, it's parsed as double; integers are prefered otherwise. Numeric operations may return doubles.
- - Booleans: True / False (parser uses tokens TRUE/FALSE).
- - Strings: support interpolation (see next section).
- - When comparing different types, the executor attempts to compare string representations when necessary.
- Variable interpolation in strings
- ---------------------------------
- - Syntax: {path} — inside a string the plugin will replace {…} with runtime values.
- - Examples:
- "{player.name}" => player's name
- "{welcome_msg}" => state variable welcome_msg
- "{block.type}" => block type (lowercased)
- "{block.location}" => Location#toString
- "{block.location.x}" / "{block.location.y}" / "{block.location.z}" => block integer coordinates (TASK FIX implemented)
- "{server.players}" => number of online players (server.players returns a Collection which is stringified to its size)
- "{message}" => chat message inside player chat event
- - Inside braces whitespace is trimmed: "{ player.name }" works.
- - Replacement is case-insensitive for built-in roots and properties (player.name or Player.Name both work in many cases),
- but state variable names are matched exactly as declared.
- Built-in roots, properties, and methods
- ---------------------------------------
- Common roots and what you can access:
- player (event sender):
- - Properties:
- - player.name -> String
- - player.health -> Number
- - player.location -> Location
- - Methods:
- - player.teleport(location) — supported via BuiltinFunctions.teleportPlayer when used as MethodCall
- - player.give(...) is usually applied to sender or player in the code via giveItem
- sender (generic CommandSender):
- - Properties similar to player when sender is a Player
- - Methods:
- - sender.permission("node") -> Boolean
- server:
- - Properties:
- - server.players -> Collection<Player> (stringifies to number when interpolated)
- - Methods:
- - server.alert("message") -> broadcast to server (BuiltinFunctions.broadcastMessage)
- block (inside block break/place events):
- - Properties:
- - block.type -> String (material name lowercased)
- - block.location -> Location (you can print full Location)
- - block.world -> String (world name)
- - block.location.x/y/z -> integer block coordinates (explicitly supported)
- event (join/quit):
- - Properties:
- - event.join_message = "..." — set join message
- - event.quit_message = "..." — set quit message
- Note: event.join_message and event.quit_message will be color-translated automatically by the executor.
- Built-in functions (callable from expressions)
- ----------------------------------------------
- - min(a, b) — numeric min (overloaded for int/double)
- - max(a, b) — numeric max
- - random(a, b) — inclusive integer random between a and b
- - chance(p) — boolean: returns true with probability p (0.0..1.0)
- - distance(locationA, locationB) — double distance, returns Double.POSITIVE_INFINITY if different worlds or nulls
- - diffrence(a,b) — integer absolute difference (keeps script spelling)
- - difference(a,b) — integer absolute difference
- - round(x) — rounds to nearest long
- Runtime helpers implemented as static methods
- ---------------------------------------------
- The plugin uses BuiltinFunctions for fast, reflection-free behavior. These are the operations available via high-level script constructs:
- - sendMessage(sender, message)
- - giveItem(player, amount, itemName)
- - broadcastMessage(message)
- - min/max (int/double)
- - hasPermission(sender, permission)
- - getPlayerName / getSenderName
- - getPlayerHealth/setPlayerHealth
- - getPlayerLocation / teleportPlayer
- - getServerPlayers() -> collection
- - randomInt(min,max), chance(p), distance(a,b), difference(a,b), round(v)
- State persistence details
- -------------------------
- - Database: SQLite located by default at plugins/HyperSkript/data/<scriptname>.db unless overridden in persist file path.
- - Location serialization format: worldName,x,y,z,yaw,pitch (serializeLocation uses 2 decimal precision)
- - Map states saved in script_map_state with serialized keys; Player keys are serialized by UUID string.
- - The plugin caches prepared statements for performance and uses WAL mode if enabled.
- Example scripts and snippets
- ----------------------------
- 1) Welcome message + give starter loot on join:
- on player join:
- event.join_message = ""
- player.send("{welcome_msg}")
- player.send("Welcome, {player.name}!")
- player.give(1, diamond) # give 1 diamond
- 2) Simple heal command with permission:
- command heal(Int: amount=10):
- if sender.permission("server.heal") == False:
- sender.send("&cYou don't have permission to use this command.")
- stop
- if amount < 1 or amount > 20:
- sender.send("&eAmount must be between 1 and 20.")
- stop
- sender.health = min(20, sender.health + amount)
- sender.send("&aHealed {amount}")
- 3) Set home & teleport home (map state + persistence):
- persist default sqlite
- state homes:<Player,Location> persist default []
- command sethome():
- homes[sender] = sender.location
- sender.send("&aYour home location has been saved!")
- command home():
- if homes[sender] != null:
- sender.teleport(homes[sender])
- sender.send("&aWelcome home!")
- else:
- sender.send("&cYou don't have a home set! Use /sethome first.")
- 4) Cancel chat and broadcast custom message:
- on player chat:
- server.alert("{player.name}: {message}")
- cancel event
- 5) Block break printing location components:
- on block break:
- player.send("You broke {block.type} at {block.location.x}, {block.location.y}, {block.location.z} in {block.world}")
- 6) Random/chance usage:
- command give_random():
- r = random(1, 10)
- sender.send("Random number: {r}")
- if chance(0.5):
- sender.send("You won something!")
- Control flow examples (if, for):
- if sender.permission("admin") == True:
- sender.send("Hello admin!")
- else:
- sender.send("No perms!")
- for i runs(1, 5):
- sender.send("Count: {i}")
- Interpolation examples:
- player.send("You are at {player.location}")
- player.send("Block X: {block.location.x}") # supported
- Reloading & runtime registration
- --------------------------------
- - Use /hs reload (requires permission hyperskript.admin) to reparse and re-register scripts at runtime. The command is implemented in the plugin.
- - When scripts register commands dynamically, modern servers (Paper/Spigot forks) may require a server-side brigadier sync to make commands visible to players immediately. HyperSkript calls Bukkit.getServer().syncCommands() via reflection when available; if your server build does not support this, you might need to restart or use the plugin-prefixed command name hyperskript:<cmd> until the server is reloaded.
- - If a command label conflicts with an existing command, HyperSkript can (optionally) force-override knownCommands to ensure the script command is available after reload. This may affect other plugins.
- Important gotchas & best practices
- ---------------------------------
- - Indentation must be consistent. Use 4 spaces per indent level or tabs but avoid mixing.
- - Use event.join_message / event.quit_message to modify join/quit messages; HyperSkript translates color codes (&) for you before setting the Bukkit message.
- - For colored chat/send messages: include & color codes (e.g., "&aHello") — HyperSkript will translate using Bukkit's ChatColor.
- - block.location.x/y/z: nested property access is explicitly supported for speed (use these to get integer block coordinates).
- - server.players returns a Collection; when interpolated it prints the online player count.
- - When using map states with Player keys, use the player object as key; keys are serialized as UUID strings for persistence.
- - If your script commands don't show up after reload on some server forks, check plugin logs for "Synced commands" or "does not support syncCommands()" warnings; if your server doesn't support runtime sync, server restart may be required.
- - If you get "Unknown or incomplete command ..." after /hs reload but commands are registered according to logs, it usually means Brigadier wasn't synced — restart or use hyperskript:cmd as a temporary workaround.
- Debugging and logging
- ---------------------
- - The plugin logs progress for loading/unloading scripts, registering events and commands, database initialization, and failures.
- - When load fails, the plugin prints stack traces for parser/lexer/runtime exceptions. Check server logs for details.
- - For command registration conflicts the loader logs warnings and will either override or skip based on configuration.
- API-ish reference (what you can call from scripts)
- -------------------------------------------------
- - Root objects and properties:
- - player.name, player.health, player.location
- - sender.name, sender.health, sender.location
- - server.players
- - block.type, block.location, block.world, block.location.x/y/z
- - event.join_message, event.quit_message
- - Methods in statements:
- - player.send("message")
- - sender.send("message")
- - server.alert("message")
- - player.give(amount, itemName)
- - sender.permission("node") -> boolean
- - player.teleport(location) (pass a saved Location from state)
- - Functions:
- - min(a,b), max(a,b), random(a,b), chance(p), distance(a,b), diffrence(a,b), difference(a,b), round(v)
- Advanced: persistence & database
- -------------------------------
- - Persistent states require "persist" to be set for a state declaration and a script-level persist configuration.
- - Location values are serialized to worldName,x,y,z,yaw,pitch.
- - DB files default to plugins/HyperSkript/data/<scriptname>.db but can be changed via persist ... file "path" directive.
- Conventions, safety, and performance
- -----------------------------------
- - HyperSkript is designed to parse once and execute AST directly — event/command hot paths avoid reflection for performance.
- - Reflection is used only at plugin startup/reload for command map resolution and brigadier syncing (not on hot paths).
- - Keep heavy computation out of synchronous event handlers to avoid server lag.
- - Use WAL mode for SQLite to improve concurrent performance on persistent states (persist sqlite wal True).
- Complete example (copyable)
- ---------------------------
- persist default sqlite
- state String:welcome_msg default "Welcome to the server!"
- state homes:<Player,Location> persist default []
- on player join:
- event.join_message = ""
- player.send("{welcome_msg}")
- player.send("&aWelcome, {player.name}!")
- player.give(1, diamond)
- on player chat:
- server.alert("{player.name}: {message}")
- cancel event
- command sethome():
- homes[sender] = sender.location
- sender.send("&aHome saved!")
- command home():
- if homes[sender] != null:
- sender.teleport(homes[sender])
- sender.send("&aWelcome home!")
- else:
- sender.send("&cNo home set. Use /sethome.")
- Where to learn more
- -------------------
- - Inspect plugin logs for details when reloading.
- - Read example.hs created by the plugin for a more comprehensive set of examples.
- - Use the plugin's source (if available) to see exact behaviors and available built-ins.
- If you want, I can:
- - Generate a printable cheat-sheet (short reference) of language constructs and builtins.
- - Validate example scripts and point out parse-time errors.
- - Produce a step-by-step tutorial for a particular script (e.g., homes + teleport + GUI integration).
Advertisement