World & Server Management
Alongside its player-facing commands, AstralEssentials runs a set of always-on listeners and repeating tasks that shape how a server group behaves: mob and trading controls, entity/spawner caps, spawn protection, custom portals, enchantment caps, beehive regeneration, ender dragon handling, world rules, and command gating. Every key below lives in config.yml; see Configuration for the full file reference and Installation for which of these are set up once at onEnable() versus re-applied by /essentials reload.
Mob & Trading Controls
Key | Type | Default | Description |
|---|---|---|---|
|
|
| Entity types blocked network-wide. |
|
|
| Additional entity types blocked only on the matching server group. |
| boolean |
| Disables villager/wandering-trader/piglin trading entirely. |
| String (permission) |
| Permission node a player must hold for phantoms to spawn for them. |
|
| (not shipped) | Per-group toggle to skip a built wither's invulnerability phase. |
MainConfiguration#shouldNotSpawn(EntityType) is the shared check: an entity type is blocked if it's in the global disabled-mob-types set, or in the current server group's entry in disabled-mob-spawning-groups. MobsListener uses it in four places:
EntitySpawnEvent— cancels the spawn outright.PlayerChunkLoadEvent— removes any already-present entity of a blocked type from a chunk as it's sent to a player.PhantomPreSpawnEvent— cancels the phantom's spawn outright ifPHANTOMis disabled (checked before, and independently of, thedisable-phantoms-untilpermission gate below).The armor-stand placement guard (below) also short-circuits if
ARMOR_STANDitself is blocked.
disable-trading cancels four separate events: PiglinBarterEvent, a piglin picking up a dropped item (EntityPickupItemEvent), PlayerTradeEvent (the villager/wandering-trader trade GUI), and PlayerInteractEntityEvent against a VILLAGER, WANDERING_TRADER, or PIGLIN — the last of these stops the trade GUI from ever opening on right-click.
disable-phantoms-until is checked on PhantomPreSpawnEvent: if the spawn's target is a player, the spawn is cancelled unless that player holds the configured permission — phantoms stay disabled for a player until they reach that permission node. This runs independently of, and in addition to, the PHANTOM type check from shouldNotSpawn.
wither-skip-invulnerability-phase-group is not present in the shipped default config.yml — add it to opt in. It's keyed by server group and checked only for a wither summoned by a player (CreatureSpawnEvent.SpawnReason.BUILD_WITHER): when the active group's entry is true, the new wither has setInvulnerableTicks(0) and setInvulnerable(false) applied immediately, so it's damageable from the instant it spawns instead of after its vanilla invulnerability phase.
Entity Limits
entity-limits caps how many of a given EntityType may exist in a single chunk, per server group:
MainConfiguration#entityLimitsForGroup() resolves entity-limits.<active group>, falling back to an empty map (no limits) if the active group has no entry. A limit of 0 or less is treated as disabled. The resolved map is enforced at four points:
MobsListener#onEntitySpawn(EntitySpawnEvent) — cancels a spawn if the type already has≥ limitentities in that chunk.MobsListener#onArmorStandPlace(PlayerInteractEvent, right-clicking an armor stand item) — a dedicated check that blocks placement once the chunk's armor stand count reaches the limit (and blocks it outright ifARMOR_STANDis a disabled mob type, before the count is even checked).ChunkListener#onChunkLoad(ChunkLoadEvent) — trims any type over its limit back down to the limit the moment a chunk loads.EntityLimitTask— a repeating task (Bukkit.getScheduler().runTaskTimer(this, new EntityLimitTask(this), 1800L, 1800L), a 90s initial delay then every 90s) that walks every loaded chunk in every loaded world and applies the same trim, catching entities that accumulated some other way (e.g. natural breeding, dispensers).
Spawner Limits
Key | Type | Default | Description |
|---|---|---|---|
| boolean |
| Whether the spawner-placement listener is registered at all. |
| int |
| Max spawners per chunk. |
|
|
| Server groups where placing a spawner is blocked outright. |
SpawnerListener is only registered in onEnable() if spawners-limits.enabled is true — when disabled, neither check below runs, spawner placement is unrestricted, and disabled-spawners-groups has no effect.
Per-chunk cap — on
BlockPlaceEventfor aSPAWNERblock, the listener countsCreatureSpawnertile entities already in the chunk (the just-placed one included, since the block is already set when the event fires). If that count exceedsspawners-limits.limit, the placement is cancelled and the player is sent thespawners-limit-reachedmessage.Group ban — a second handler, registered at
EventPriority.LOWEST, cancels any spawner placement outright when the active server group is indisabled-spawners-groups, independent of the per-chunk cap, sending thespawnwers-disabled-in-worldmessage (shipped key spelling).
Spawn Protection
Key | Type | Default | Description |
|---|---|---|---|
| int (blocks) |
| Protected radius around each protected world's spawn location. |
|
|
| Worlds protected on each server group. |
SpawnProtection#isProtected(world) looks up enabled-groups.<active group> and checks whether it contains the given world name. SpawnProtectionListener applies it to four events:
BlockBreakEvent/BlockPlaceEvent— skipped for players withspawnprotection.bypass; otherwise cancelled (with thespawn-protectionmessage) if the block is withinradiusblocks (distance-squared) of the world's spawn location in a protected world.BlockExplodeEvent/EntityExplodeEvent— cancelled (no message, since there's no player to message) if any block in the explosion's block list falls withinradiusof spawn in a protected world. These two checks have no permission bypass — they apply regardless of who or what caused the explosion.
Custom Portals
portals.nether and portals.end each replace vanilla portal travel with a config-driven AstralCore action list, gated by an AstralCore requirement list:
Field | Type | Description |
|---|---|---|
| boolean | Whether entering this portal type does anything. |
|
| Run once the requirements pass and the dwell delay elapses. See Actions. |
|
| Evaluated once per portal stay; its own |
PortalListener handles everything: EntityPortalEvent is always cancelled (non-player entities never travel through these portals) and PlayerPortalEvent is always cancelled (vanilla teleport is fully suppressed). Detection instead runs off PlayerMoveEvent block changes:
The player must be standing in a
NETHER_PORTALorEND_PORTALblock (feet or head). Re-entering after stepping out restarts the sequence; while a stay is already being handled, further moves are ignored (no restarted timer, no repeated deny-action spam).If
portal.enabled()isfalse, nothing happens.requirements.run(...)is evaluated once. On failure it fires its owndeny-actionsand the sequence stops there.A teleport is scheduled after the vanilla dwell delay — for nether portals, read live from the player's world
PLAYERS_NETHER_PORTAL_DEFAULT_DELAYgame rule (vanilla default 80 ticks / 4s), orPLAYERS_NETHER_PORTAL_CREATIVE_DELAY(vanilla default 1 tick) for Creative/Spectator players; end portals teleport instantly (0 ticks).When the delay elapses, requirements are re-checked (in case something changed during the dwell), then
actions.run(...)executes with the player as executor.
The shipped [connect-world] resource world_nether action is ConnectWorldAction, registered globally by AstralHomes (registerActionGlobally("connect-world", ...), not by AstralEssentials itself): its first argument is the target server group (here resource) and its second is the destination world name within that group.
Enchantment Level Caps
max-enchantment-levels is a Map<Enchantment, Integer> of bare enchantment ids to a maximum level. EnchantmentListener hooks AstralItems' custom-anvil AnvilResultEvent (fired by the /anvil GUI, not the vanilla anvil block). If the map is empty the listener no-ops. Otherwise, for every capped enchantment: if either input book's stored level already exceeds the cap, or the level the combine would produce (equal input levels → +1, otherwise the higher of the two) would exceed the cap, the entire anvil result is cancelled — the combine is blocked outright rather than clamped to the cap.
Beehive Regeneration
beehive-regeneration-times is keyed by server group; each entry lists the worlds it applies to and a min/max Duration range (DurationParser shorthand, e.g. 20s, 1m). At onEnable(), one tick after startup, AstralEssentials looks up the active group's entry and starts one BeeHiveTask per already loaded world whose name is in that entry's worlds set (new BeeHiveTask(beehiveConfig, world).runTaskTimer(this, 100L, 5L) — 5s initial delay, then every 5 ticks / 0.25s). Worlds loaded after that point are not picked up until a restart.
Each run of BeeHiveTask processes up to 5 of the world's loaded chunks (round-robin across ticks). For every BEE_NEST tile entity not already at max honey level, it stores a next-regeneration timestamp in the block's PersistentDataContainer (key astralessentials:beehive_regeneration); once that time is reached, the honey level is incremented by one and a new delay is rolled uniformly between min and max (inclusive) for the next increment.
Ender Dragon
Key | Type | Default | Description |
|---|---|---|---|
|
|
| Items granted to the dragon's killer. |
| boolean | (not shipped) | Cancels dragon egg formation when |
EnderDragonListener#onEnderDragonDeath (EntityDeathEvent for ENDER_DRAGON with a non-null killer) adds every configured ItemStackWrapper in ender-dragon-drops directly to the killer's inventory; anything that doesn't fit is added to the event's normal ground drops instead of being lost. The shipped default (a single minecraft:stone) is a placeholder — replace it with real loot.
disable-dragon-egg is not present in the shipped default config.yml; add it explicitly to enable. When true, DragonEggListener cancels Paper's DragonEggFormEvent, preventing the egg from forming after a (re)kill.
Separately and unconditionally (not config-gated), EnderDragonPacketListener is registered against PacketEvents in onEnable() — see PacketEvents integration — and cancels the outgoing Play.Server.EFFECT packet with effect id 1028 (the vanilla ender-dragon-death effect) at MONITOR priority, suppressing the vanilla death animation/sound broadcast.
World Rules
keep-inventory and disable-locator-bar are two independent, top-level config.yml booleans (both default true) that shape the whole server's world behavior; see Configuration for the key table.
disable-locator-baris the only one of the two that is a genuine world game rule.AstralEssentials#onEnable()schedules a one-tick-delayed task (Bukkit.getScheduler().runTask(this, () -> Bukkit.getWorlds().forEach(...))) that, when the flag istrue, callsworld.setGameRule(GameRules.LOCATOR_BAR, false)on every world that is already loaded at that point. Worlds that load afterward, and/essentials reload, do not re-apply it.keep-inventoryis not a game rule at all — it's an event-level override.PlayerListener#onPlayerDeathchecks it on everyPlayerDeathEventand, whentrue, callse.setKeepInventory(true)and clearse.getDrops(). The same handler also always suppresses the death message and schedules an instant respawn back to thespawngroup one tick later.
Command Restrictions
blocked-commands, unregistered-commands, and groups-blocked-commands are documented in full on Configuration; this is how CommandListener enforces them:
isCommandBlocked(cmd)—trueif the command label starts with, or ends with:followed by, anyblocked-commandsentry (case-insensitive). Enforced onPlayerCommandPreprocessEvent(cancels the command, showing vanilla's "unknown command" message),TabCompleteEvent(strips matches from completions), andPlayerCommandSendEvent(strips matches — and any command containing:— from the client's known-command list).isCommandBlockedInGroup(cmd)—trueif the active server group has agroups-blocked-commandsentry and the label matches one of itscommands(same starts-with/ends-with rule). Enforced onPlayerCommandPreprocessEvent, sendingcommand-disabled-in-worldand cancelling the command.Both checks are skipped entirely for players holding
astralessentials.bypass.commandblock.unregistered-commandsis unrelated toCommandListener: at load time (onEnable()and/essentials reload), each name is removed from the server'sCommandMapdirectly, so it isn't a runnable command at all (not just hidden).
Server-Group Scoping
Most of the systems above key off AstralPaperAPI.serverInformation().group() and only take effect on server groups that appear in their config section — installing the plugin does not by itself activate them everywhere:
entity-limits(viaentityLimitsForGroup())disabled-mob-spawning-groupswither-skip-invulnerability-phase-groupdisabled-spawners-groupsspawn-protection.enabled-groups(viaSpawnProtection#isProtected)beehive-regeneration-timesgroups-blocked-commands
disable-phantoms-until is the exception among the group-related keys — it's checked against a permission node, not the active server group. See Installation for how this interacts with plugin startup (some of these, like beehive regeneration, are only resolved once against the worlds loaded at enable time).