Compare commits

...
6 Commits
Author SHA1 Message Date
VoidsipandJosh Roy f8724e26ef Honor the configured Discord private-message format (#6644)
### Information

This PR fixes #6641.

### Details

**Proposed fix:**

Read the private-message format from `messages.pms-to-discord`, matching
the shipped config. The settings loader currently reads
`messages.private-chat` instead, so a custom format is ignored and the
default `[SocialSpy]` text is used.

Added settings tests that load real YAML files and check the custom
format, default fallback and reload, including placeholders and
apostrophes. The Discord module now includes the core project on its
test classpath.

**Environments tested:**

OS: Windows
Java version: Temurin 25.0.4.1+1

- [ ] Most recent Paper version
- [ ] CraftBukkit/Spigot/Paper 1.12.2
- [ ] CraftBukkit 1.8.8

The full Gradle build passes, including the new settings tests and
Checkstyle. Live Paper/Discord relay testing was not performed.

**Demonstration:**

The regression loads this configuration:

```yaml
messages:
  pms-to-discord: "[Privado] {sender-displayname} -> {receiver-displayname}: {message}"
```

Before the fix, the actual settings loader produces `[SocialSpy] sender
-> receiver: Hello`. After the fix, it produces `[Privado] Sender ->
Receiver: Hello`. The missing-key control keeps the existing default.

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-11-03 16:22:56 +00:00
minoneer e70bdb817c Fix IndexOutOfBoundsException in AlternativeCommandsHandler.getAlternative (#6609)
<!--

EssentialsX bug fix submission guide
====================================

NOTE: Failure to fill out this template properly may result in your PR
being
      delayed or ignored without warning.

NOTE: Don't type between any arrows in the template, as this text will
be
hidden. This includes this header block and any other explanation text
      blocks.

Want to discuss your PR before submitting it? Join the EssentialsX
Development
server: https://discord.gg/CUN7qVb


EssentialsX is GPL
------------------

By contributing to EssentialsX, you agree to license your code under the
GNU General Public License version 3, which can be found at the link
below:
https://github.com/EssentialsX/Essentials/blob/2.x/LICENSE


Instructions
------------

If you are submitting a bug fix, please follow the following steps:

1.  Fill out the template in full.
This includes providing screenshots and a link to the original bug
report. If there isn't an existing bug report, we recommend opening a
new
detailed bug report BEFORE opening your PR to fix it, else your PR may
be
      delayed or rejected without warning.
      
      You can open a new bug report by following this link:
      https://github.com/EssentialsX/Essentials/issues/new/choose 

2.  When linking logs or config files, do not attach them to the post!
Copy and paste any logs into https://gist.github.com/, then paste a
link to them in the relevant parts of the template. Do not use Hastebin
      or Pastebin, as this can cause issues with future reviews.
      
DO NOT drag logs directly into this text box, as we cannot read these!

3.  If you are fixing a performance issue, please include a link to a
    Timings and/or profiler report, both before and after your PR.

4.  If you are fixing a visual bug, such as in commands, please include
    screenshots so that we can more easily review the proposed fix.
    (You can drag screenshots into the bottom of the editor.)

-->

### Information

<!--
Replace #nnnn with the number of the original issue. If this PR fixes
multiple issues, you should repeat the phrase "fixes #nnnn" for each
issue.
-->

This PR fixes #6608. 

### Details

**Proposed fix:**    
<!-- Type a description of your proposed fix below this line. -->

`getAlternative` prunes collected `WeakReference`s through its own
iterator, which can remove
every entry in a bucket. The following `commands.get(0)` then throws
`IndexOutOfBoundsException`.

This stores the first live alias during the iteration and returns that
instead, or `null` when
nothing live remains, which is what the caller already tests for
(`getAlternative(label) == null` in
`EssentialsPlayerListener.CommandSendFilter`).

Confirmed with an instrumented build that logged the list sizes around
the prune loop:

```
[6608] label=w thread=Paper Async Command Builder Thread Pool - 0
       entry=2 removedByUs=2 after=0 expected=0
```

Both references in the `w` bucket had been collected and the loop
removed both.

Note: this does not address the possible concurrent modification also
raised in #6608
(`altCommands` is a plain `HashMap` of `ArrayList`, modified from the
async command-send path).
That needs a separate synchronisation decision.


**Environments tested:**    

<!-- Type the OS you have used below. -->
OS: Debian 12

<!-- Type the JDK version (from java -version) you have used below. -->
Java version:  25.0.4 2026-07-21 LTS

<!--
Put an "x" inside the boxes for the server software you have tested this
bug fix on. If this feature does not apply to a server, strike through
the server software using ~~strikethrough~~. If you have tested on other
    environments, add a new line with relevant details.
-->
- [x] Most recent Paper version (26.2, git-Paper-112)
- [ ] CraftBukkit/Spigot/Paper 1.12.2
- [ ] CraftBukkit 1.8.8


**Demonstration:**    
<!--
Below this block, include screenshots/log snippets from before and after
as
necessary. If you have created or used a test case plugin, please link
to a
download of the plugin, source code and exact version used where
possible.
-->

Before, on every boot with a player join (5 consecutive boots,
EssentialsX 2.22.0 and
2.22.1-dev+17-382de4f, same server and plugin set):

```
[ERROR]: Could not pass event AsyncPlayerSendCommandsEvent to Essentials
java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
    at java.util.ArrayList.get(ArrayList.java:428)
    at com.earth2me.essentials.AlternativeCommandsHandler.getAlternative(AlternativeCommandsHandler.java:133)
    at com.earth2me.essentials.EssentialsPlayerListener$CommandSendFilter.isEssentialsCommand(EssentialsPlayerListener.java:1279)
    at net.ess3.provider.providers.PaperCommandSendListenerProvider.onAsyncCommandSend(PaperCommandSendListenerProvider.java:28)
...
```

Cause isolated with a temporary instrumented build logging list sizes
around the prune loop:

```
[6608] label=w thread=Paper Async Command Builder Thread Pool - 0
       entry=2 removedByUs=2 after=0 expected=0
```

Both `WeakReference` in the `w` bucket had been collected and the loop
removed both.

After, with this fix (2.22.1-dev+18), same server, player join:

```
IndexOutOfBoundsException:                0
AlternativeCommandsHandler references:    0
Could not pass event AsyncPlayerSendCommandsEvent: 0
```
2026-10-04 21:55:15 +00:00
Lewis GibsonandRaw2d 2dbd63adf8 Use PaperLib's asynchronous teleports and chunk loads on 26.x (#6634)
### Information

This PR fixes #6632.

### Details

**Proposed fix:**
PaperLib 1.0.6 reads the server version from `Bukkit#getVersion()` with
a regex that only accepts a single digit major version, so on 26.x it
reads the version as 0 and selects its synchronous fallbacks. Every
`PaperLib.teleportAsync`, `getChunkAtAsync` and
`getBedSpawnLocationAsync` call in EssentialsX then loads the chunk and
teleports on the calling thread instead of using Paper's asynchronous
API.

When PaperLib has not recognised a Paper server that is newer than 1.13,
EssentialsX now installs a PaperLib environment which selects the
handlers PaperLib would have chosen for 1.15.2 and later. Nothing
changes on servers where PaperLib reads the version correctly.

**Environments tested:**
OS: Ubuntu 24.04 (WSL2)
Java version: Temurin 25.0.4.1
- [x] Most recent Paper version (26.3, git-Paper-140)
- ~~CraftBukkit/Spigot/Paper 1.12.2~~
- ~~CraftBukkit 1.8.8~~

**Demonstration:**
`ModernPaperEnvironmentTest` checks that a 26.x version string leaves
PaperLib's own environment calling `Entity#teleport` and that the new
environment calls `Entity#teleportAsync` and `World#getChunkAtAsync`.
`./gradlew build` passes.

Handlers PaperLib selected on Paper 26.3 (build 140), read from the
bundled PaperLib by a test plugin:

Before (2.x, Paper 26.3 build 140):
```
PROBE paperlib env=PaperEnvironment version=0 asyncChunksHandler=AsyncChunksSync asyncTeleportHandler=AsyncTeleportSync isGeneratedHandler=ChunkIsGeneratedUnknown bedSpawnLocationHandler=BedSpawnLocationSync
```
After (this PR):
```
PROBE paperlib env=ModernPaperEnvironment version=0 asyncChunksHandler=AsyncChunksPaper_15 asyncTeleportHandler=AsyncTeleportPaper_13 isGeneratedHandler=ChunkIsGeneratedApiExists bedSpawnLocationHandler=BedSpawnLocationPaper
```
The same scripted run of EssentialsX commands (teleports across 12000
blocks, `/tpa`, `/home`, `/spawn`, `/back` after death, a passenger, 22
checks) passes before and after.

Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
2026-10-04 21:08:16 +00:00
Josh Roy cfb6f12da3 Fix economy lag message (#6629)
wasn't displaying the formatted time see #6583
2026-09-26 06:04:42 +00:00
Josh Roy 49a2f10071 Update to Minecraft 26.3 (#6624) 2026-09-15 15:43:50 +00:00
Keane Audric 43cb76a888 Fix MiniMessage and color formatting in disposal GUI title (#6616)
### Information

This PR fixes #6606.

### Details

**Proposed fix:**  
Passes the localized `disposal` string through
`AdventureFacet#miniToLegacy` before creating the chest inventory view
in both `Commanddisposal.java` and `SignDisposal.java`. This ensures
both legacy formatting codes (`&a&l`) and Adventure/MiniMessage tags
(`<green><bold>`) parse and render correctly in the inventory title bar.

**Environments tested:**  

OS: Windows 11  
Java version: Java 21  

- [x] Most recent Paper version (26.2)
- [ ] CraftBukkit/Spigot/Paper 1.12.2
- [ ] CraftBukkit 1.8.8

**Demonstration:**  
<img width="554" height="76" alt="image"
src="https://github.com/user-attachments/assets/6bb5b6a2-2a7b-4cf8-8d54-15776e63e2dd"
/>

<img width="388" height="223" alt="image"
src="https://github.com/user-attachments/assets/bc8f5dee-9faa-4a03-937e-37d64a283e4c"
/>
2026-08-29 17:54:49 +00:00
25 changed files with 2449 additions and 33 deletions

No files matched your search

+270
View File
@@ -0,0 +1,270 @@
---
name: mc-update
description: Update EssentialsX for a new Minecraft version. Use when the user asks to update to, bump, or support a new Minecraft or Paper version (for example "update to 26.4", "add 26.3 mobs", "bump supported version"), or asks what must be checked for new mobs, items, wood types, armor, enchantments, potions, or Bukkit API changes.
---
# Minecraft update
This checklist comes from every Minecraft update PR from 1.14 to 26.3, and from the fix PRs that followed them.
Paths are relative to the repository root. `ESS` means `Essentials/src/main/java/com/earth2me/essentials`.
## Procedure
1. Find the old and new `paper-api` jars in `~/.gradle/caches/modules-2/files-2.1/io.papermc.paper/paper-api/`. If the new jar is not there, set `paperVersion` in `build-logic/src/main/kotlin/essentials.base-conventions.gradle.kts` and run `./gradlew :EssentialsX:compileJava` to download it.
2. Diff the constants of these classes between the two jars: `org.bukkit.entity.EntityType`, `org.bukkit.Material`, `org.bukkit.enchantments.Enchantment`, `org.bukkit.potion.PotionEffectType`, `org.bukkit.potion.PotionType`, `org.bukkit.TreeType`, and each `*$Variant` class under `org.bukkit.entity`. Use this command for each class:
```sh
unzip -p <jar> org/bukkit/entity/EntityType.class > /tmp/x.class && javap -p /tmp/x.class | grep 'public static final'
```
3. Sort each added, removed, or renamed constant into a section of part 2 or part 3 below.
4. Do part 1. Then do each section that applies.
5. Apply part 4 to every line you write.
6. Run `./gradlew :EssentialsX:compileJava`. Fix errors.
7. Do not commit unless the user asks.
8. Report per section: what changed, and what you skipped and why.
---
## 1. Every update
### 1.1 Version constants
1. Open `ESS/utils/VersionUtil.java`.
2. Add a constant `vXX_Y_R01 = BukkitVersion.fromString("XX.Y-R0.1-SNAPSHOT")`.
- For a patch release, change the string of the existing minor constant. Example: `v26_1_R01` holds `26.1.2`.
- For a minor release, add a new constant. Do not rename or delete an old constant. Old constants gate features. Deleting `v1_20_4_R01` caused #6460.
3. Add the constant to `supportedVersions`. Keep the `ImmutableSet.of(...)` syntax. `build-logic/src/main/kotlin/VersionDataTask.kt` parses it with a regex.
4. Start a server. Confirm `BukkitVersion.fromString(Bukkit.getBukkitVersion())` parses. Spigot 1.14.3 and Paper 26.1 both changed the string format.
### 1.2 Documentation
1. Update the supported versions sentence in `README.md`.
2. Regenerate `items.json` with ItemDbGenerator.
---
## 2. New content
### 2.1 New mob
1. Add an entry to `ESS/Mob.java`: `NAME("CamelName", Enemies.X, "ENTITY_TYPE")`.
- Use the string constructor. Do not use `EntityType.X`. The constant does not exist on old servers.
- Set the plural suffix if it is not `s`. Fish use `""`. Fox uses `es`.
- The display name must not contain a space.
- Boats and rafts are also `Mob` entries.
2. Add `EntityType` constant to `ESS/MobCompat.java` only if `MobData` needs it.
3. Add the mob to `protect.prevent.spawn` in `Essentials/src/main/resources/config.yml`.
4. Check `entityToDisplayName` in `providers/BaseProviders/.../SpawnerItemProvider.java` if the enum name does not title-case well.
### 2.2 New mob variant or data
1. Add entries to `ESS/MobData.java`. Use the string form `"kind:VARIANT"`. Example: `"wolf:PALE"`.
2. Add a `case "kind":` to the `setData` switch.
3. Add `setKindVariant(Entity, String)` to `MobCompat.java`. Gate it with `VersionUtil.getServerBukkitVersion().isLowerThan(vXX_Y_R01)`.
4. Use `RegistryUtil.valueOf(Variant.class, name)` for registry types. Do not use `Enum.valueOf`.
5. Do not call `NewClass.Variant.values()` in an enum initializer. Axolotl did this. It threw `NoClassDefFoundError` on 1.16 (#4376).
6. Catch `Throwable` around reflective lookups. Cow variant threw NPE on old servers (#6121).
7. For a boolean flag, add a `Data` constant and a branch in `setData`. Example: `GOAT_SCREAMING`.
### 2.3 New rideable or saddle mob
1. Add `SADDLE_<MOB>` to `MobData.java` with a `Data.<MOB>SADDLE` constant.
2. Add `set<Mob>Saddle(Entity, Player)` to `MobCompat.java`. Tame the mob. Set the owner. Set the saddle.
3. Check `ESS/craftbukkit/Inventories.java` if the mob adds a player inventory slot. Body and saddle slots (41, 42) broke `/clearinventory` in 1.21.6 (#6218).
### 2.4 New item or block
1. Check each `MaterialUtil` set that lists names by hand. See table.
2. Check `ESS/utils/LocationUtil.java`:
- Add a block that hurts a standing player to `DAMAGING_TYPES`. Magma, campfire, berry bush and wither rose were missed for 2 years (#3537).
- `HOLLOW_MATERIALS` comes from `Material.isTransparent()`. Add a passable block that is not transparent. Example: `LIGHT` (#4601). Remove a transparent block that is solid. Examples: `BARRIER`, `DIRT_PATH`, `FARMLAND`.
3. Check for a new event that reveals player position. Sculk sensor needed vanish handling in `EssentialsPlayerListener` (#5262).
| `ESS/utils/MaterialUtil.java` set | Add when |
|---|---|
| `HELMETS`, `CHESTPLATES`, `LEGGINGS`, `BOOTS` | New armor tier |
| `LEATHER_ARMOR` | New dyeable armor |
| `BEDS` | New bed. `STRAW_BED` in 26.3. |
| `BANNERS` | New banner colour |
| `SIGN_POSTS`, `WALL_SIGNS`, `HANGING_SIGNS`, `HANGING_WALL_SIGNS` | New wood type |
| `MOB_HEADS` | New head. `PIGLIN_HEAD` in 1.20. |
| `POTIONS` | New potion container |
| `EDITABLE_BOOKS` | New book type |
| `FIREWORKS`, `FIREWORK_CHARGE` | New firework item |
`EnumUtil.getAllMatching` skips a name that does not exist. Adding a name is safe on old servers.
### 2.5 Renamed item, block or entity
1. Grep `ESS` for `Material.OLD_NAME` and `EntityType.OLD_NAME`. Replace with `EnumUtil.getMaterial("NEW", "OLD")` or a `MobCompat` constant. `EntityType.PIG_ZOMBIE` in a static map broke 1.16 (#3412). `EntityType.FIREWORK` broke `/firework` in 1.20.6.
2. For a renamed `EntityType`, add `getEntityType("NEW", "OLD")` to `MobCompat.java`. Update `Mob.java`, `MobData.java` and `SpawnMob.java`.
### 2.6 New wood type
1. Add `<WOOD>_SIGN`, `<WOOD>_WALL_SIGN`, `<WOOD>_HANGING_SIGN`, `<WOOD>_WALL_HANGING_SIGN` to the four sign sets in `MaterialUtil.java`. Crimson and warped were missed in 1.16 (#3487).
2. Add `<WOOD>_BOAT` to `Mob.java`. Boats are separate entity types since 1.21.3.
3. Add `<WOOD>` to `MobCompat.BoatVariant` for servers before 1.21.3.
4. Add `<WOOD>_BOAT` to `MobData.java`.
5. Add `<WOOD>_CHEST_BOAT` to `Mob.java`.
### 2.7 New tree type
1. `Commandtree` reads `TreeType.values()`. It needs no code change.
2. Add the name to `treeCommandUsage` and `treeCommandUsage1` in `messages.properties`.
3. `Commandbigtree` has four hard-coded types. Add a case if the tree has a big variant.
4. Bukkit adds `TreeType` late. 26.3-pre-2 has no `POPLAR`.
### 2.8 New armor tier
1. Add the four pieces to `HELMETS`, `CHESTPLATES`, `LEGGINGS`, `BOOTS` in `MaterialUtil.java`.
2. Add a branch to the armor keyword chain in `ESS/SpawnMob.java`. The chain has `netherite`, `copper`, `diamond`, `gold`, `leather`, `iron`.
3. For horse armor, add `<TIER>_HORSE_ARMOR` to `MobData.java`. Example: `NETHERITE_HORSE_ARMOR`.
### 2.9 New enchantment
1. Add a `try { // XX.Y }` block to `ESS/Enchantments.java`.
2. Look up by name: `Enchantment.getByName("NAME")`. Check for null. Do not use `Enchantment.NAME`.
3. Add `ENCHANTMENTS.put("name", e)` and `ALIASENCHANTMENTS.put("alias", e)`.
4. Swift sneak (1.19) and lunge (1.21.11) were missed. Contributors added them months later.
5. Display and permission names come from `Enchantments.getRealName()`. It uses the key on 1.13+.
### 2.10 New potion effect or potion type
1. Add a `try { // XX.Y }` block to `ESS/Potions.java`. Catch `Throwable`.
2. Add `POTIONS.put("name", PotionEffectType.NAME)` and `ALIASPOTIONS.put(...)`.
3. `ModernPotionMetaProvider` maps `LONG_` and `STRONG_` prefixes. Check a new potion follows that pattern.
### 2.11 New banner pattern
No code change. `PatternTypeProvider` reads the registry.
### 2.12 New colour family
Check `MaterialUtil.getColorOf` for a colour name that is not a `DyeColor`.
### 2.13 New spawn egg
No code change. `FlatSpawnEggProvider` derives the egg from `EntityType.name() + "_SPAWN_EGG"`.
### 2.14 New command for new content
1. Gate the command with `VersionUtil`. Return `tl("unsupportedFeature")` on old servers. Examples: `/beezooka`, `/ice`.
2. Add the command to `plugin.yml` and `messages.properties`. Check message parameters. `iceOther` shipped without `{0}`.
---
## 3. API changes
### 3.1 Enum becomes a registry
Enchantment, PotionEffectType, Sound, PatternType, Villager.Profession, Cat.Type, Biome and Boat.Type all changed.
1. Replace `EnumUtil.valueOf` with `RegistryUtil.valueOf(Class, names...)`. Replace `.values()` with `RegistryUtil.values(Class)`.
2. If a static method moved from an enum to an interface, code compiled against the new API throws `IncompatibleClassChangeError` on old servers. `catch (Exception)` does not catch it. `PatternType.getByIdentifier` did this (#6553).
3. For such a case, add a provider pair:
- Interface in `providers/BaseProviders/src/main/java/net/ess3/provider/`.
- `Modern*Provider` in BaseProviders with `@ProviderData(weight = 1)` and a `@ProviderTest`.
- `Legacy*Provider` in `providers/1_12Provider` or `providers/1_8Provider`. These compile against the old API.
- Register with `providerFactory.registerProvider(Legacy.class, Modern.class)` in `Essentials.java`.
4. In a `@ProviderTest`, use `getMethod`, not `getDeclaredMethod`. 1.21 moved spawner methods to a super-interface (80bdc5269).
### 3.2 Removed API class
`PotionData`, `Potion`, `BannerMeta.setBaseColor` and `Achievement` were removed.
1. Move code that uses the removed class to a provider in `1_12Provider` or `1_8Provider`.
2. Add a modern provider in BaseProviders.
3. Remove the method from `OfflinePlayer`, `FakeWorld` and `FakeServer` if it was an override.
### 3.3 Changed method signature
1. Read the Bukkit diff for changed return types on methods EssentialsX calls.
2. A changed return type changes the call descriptor. The call throws `NoSuchMethodError` on old servers. `PlayerPickupArrowEvent#getArrow` did this (#3175). `InventoryView` becoming an interface did this (#5851).
3. Use a provider or reflection for the changed call.
### 3.4 New API methods on Player, World, Server
1. Add stubs to `providers/1_8Provider/.../OfflinePlayer.java` and `providers/1_12Provider/.../FakeWorld.java` if they fail to compile.
2. Tests use MockBukkit since 1.21.6. `FakeServer` no longer needs updates.
### 3.5 New event
1. Put the handler in a separate `*Listener_XX_Y` class.
2. Register it behind a `VersionUtil` check or `Class.forName`.
3. Do not put a new event in a listener that loads on old servers. `EntityTransformEvent` failed on 1.13.1 (#2942).
### 3.6 Spigot NMS obfuscation
1. Add `VXX_Y_RN` to `providers/NMSReflectionProvider/.../ReflUtil.java` if Spigot bumps the NMS revision.
2. Update the obfuscated `MinecraftServer#isRunning` name table in `ReflServerStateProvider.java`. This broke on 1.18, 1.19, 1.19.3, 1.20.6 and 1.21.10.
3. Paper uses `PaperServerStateProvider`. Test on Spigot to see this fail.
### 3.7 Paper internal classes
Paper config classes move. `ReflOnlineModeProvider` broke on 1.19 when config moved to `io.papermc.paper.configuration.GlobalConfiguration`. Check every reflective Paper access.
### 3.8 World height
1. Do not hard-code `0`, `-1`, `128` or `256` as a Y bound.
2. Use `WorldInfoProvider.getMinHeight`, `getMaxHeight` and `getLogicalHeight`.
3. Grep `LocationUtil`, `RandomTeleport`, `Commandtop`, `Commandsethome`, `Teleport`, `AsyncTeleport` and `SpawnMob`. 1.18 needed three fix PRs (#4657, #4667, #4715).
### 3.9 Item data components
1. Check `MetaItemStack.parseStringMeta`. The `[...]` component branch calls `UnsafeValues#modifyItemStack`. Its argument shape changed in 1.20.5 and again in 26.1 (#6570).
2. Check `Commandbook`. Writable and written books have separate meta since 1.20.6 (#6064).
3. Check `Commandskull`. Skull owner needs a resolved profile since 1.20.4 (#6188).
4. Check `MetaItemStack.canSpawn()`.
### 3.10 Vanilla messages
Check the vanilla changelog for a new client message that duplicates an EssentialsX message. 1.15 added "Respawn point set". `EssentialsPlayerListener` now gates `bedSet` (#2943).
### 3.11 Version string format
1. `BukkitVersion.VERSION_PATTERN` in `VersionUtil.java` must parse the new string.
2. Supported suffixes: `-snapshot-N`, `-pre-N`, `-rc-N`, `.build.N-channel`, `.local`, `-R0.1-SNAPSHOT`.
3. `compareDevSpecifier` orders `snapshot < pre < rc < release`.
4. `isSupportedVersion` compares major, minor and patch. A patch release needs a constant change.
5. 26.1 needed four fix PRs for this (#6480, #6483, #6500, #6501).
### 3.12 Adventure and Paper native paths
Each Paper API shift produced a gate:
- `Essentials.initAdventureFacet` gates the Paper facet at `v1_18_2_R01` (#6562).
- `EssentialsSpawnPlayerListener` gates `getRespawnLocation` at `v1_20_4_R01` (#6460).
- `EssentialsPlayerListener` gates configuration events at `v1_21_8_R01`.
Check each gate against the new version.
---
## 4. Compatibility rules
EssentialsX runs on 1.8.8 through the current version. Every update PR must keep that.
1. Do not reference a new `Material`, `EntityType`, `Enchantment` or `PotionEffectType` constant in a static field or enum initializer. Use `EnumUtil.getMaterial`, `EnumUtil.getEntityType` or a name lookup.
2. Catch `Throwable`, not `Exception`, around reflective lookups and provider selection. A swallowed `NoSuchFieldError` selected the wrong provider (7d6195375).
3. Use only Guava and Gson methods that exist in 1.8.8. `Ints.constrainToRange` and `JsonArray#add(String)` do not (#4708, 576866540).
4. Compile legacy providers in `1_8Provider` or `1_12Provider`. Code compiled against the new API can carry the wrong bytecode.
5. Gate with `isLowerThan(<next major>)`, not `isLowerThanOrEqualTo(<patch>)`. Forks use nonstandard revision numbers (43eff69a2).
6. Do not delete a `VersionUtil` constant.
7. Test on Paper latest, Spigot latest, 1.12.2 and 1.8.8. Most follow-up fixes in this history came from old servers.
---
## 5. Test
1. Run `./gradlew test`.
2. Run `./gradlew runServer`.
3. Run `/ess version`. Confirm the support status is FULL.
4. Run `/spawnmob <newmob>`, `/spawnmob <mob>:<variant>`.
5. Run `/give @s <newitem>`, `/give @s minecraft:<newitem>`.
6. Run `/enchant <newenchant>`, `/potion <neweffect>`.
7. Place an EssentialsX sign on each new sign type.
8. Run `/tree <newtree>` if Bukkit has the `TreeType`.
9. Run `/kit` with the new armor. Confirm auto-equip.
10. Teleport onto each new damaging block. Confirm `/tp` refuses.
11. Repeat on Spigot for `ReflServerStateProvider`.
+2 -2
View File
@@ -11,8 +11,8 @@
</option>
<option name="locations">
<list>
<ConfigurationLocation id="bundled-sun-checks" type="BUNDLED" scope="All" description="Sun Checks">(bundled)</ConfigurationLocation>
<ConfigurationLocation id="bundled-google-checks" type="BUNDLED" scope="All" description="Google Checks">(bundled)</ConfigurationLocation>
<ConfigurationLocation id="bundled-sun-checks" type="BUNDLED" scope="All" description="Sun Checks">bundled-sun-checks</ConfigurationLocation>
<ConfigurationLocation id="bundled-google-checks" type="BUNDLED" scope="All" description="Google Checks">bundled-google-checks</ConfigurationLocation>
<ConfigurationLocation id="321e5753-4201-4d2c-bf31-6232011122fd" type="PROJECT_RELATIVE" scope="All" description="EssentialsX">
$PROJECT_DIR$/.checkstyle/checkstyle.xml
<option name="properties">
@@ -116,6 +116,8 @@ public class AlternativeCommandsHandler {
}
// return the first command that is not an alias
Command firstAlias = null;
final Iterator<WeakReference<Command>> iterator = commands.iterator();
while (iterator.hasNext()) {
final Command cmd = iterator.next().get();
@@ -127,10 +129,14 @@ public class AlternativeCommandsHandler {
if (cmd.getName().equalsIgnoreCase(label)) {
return cmd;
}
if (firstAlias == null) {
firstAlias = cmd;
}
}
// return the first alias
return commands.get(0).get();
// return the first alias, or null if every reference in this bucket has been collected
return firstAlias;
}
public void executed(final String label, final Command pc) {
@@ -313,7 +313,7 @@ public final class Enchantments {
ENCHANTMENTS.put("density", density);
}
final Enchantment windBurst = Enchantment.getByName("WIND_BURST");
if (breach != null) {
if (windBurst != null) {
ENCHANTMENTS.put("windburst", windBurst);
ALIASENCHANTMENTS.put("wind", windBurst);
ALIASENCHANTMENTS.put("burst", windBurst);
@@ -46,6 +46,7 @@ import com.earth2me.essentials.textreader.SimpleTextInput;
import com.earth2me.essentials.updatecheck.UpdateChecker;
import com.earth2me.essentials.userstorage.ModernUserMap;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.ModernPaperEnvironment;
import com.earth2me.essentials.utils.PasteUtil;
import com.earth2me.essentials.utils.VersionUtil;
import io.papermc.lib.PaperLib;
@@ -261,6 +262,10 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
getLogger().severe(getAdventureFacet().miniToLegacy(tlLiteral("serverSnapshot")));
}
if (PaperLib.isPaper() && !PaperLib.isVersion(13) && VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_13_0_R01)) {
PaperLib.setCustomEnvironment(new ModernPaperEnvironment());
}
final PluginManager pm = getServer().getPluginManager();
for (final Plugin plugin : pm.getPlugins()) {
if (plugin.getDescription().getName().startsWith("Essentials") && !plugin.getDescription().getVersion().equals(this.getDescription().getVersion()) && !plugin.getDescription().getName().equals("EssentialsAntiCheat")) {
@@ -60,6 +60,7 @@ public enum Mob {
CHERRY_BOAT("CherryBoat", Enemies.NEUTRAL, "CHERRY_BOAT"),
BAMBOO_RAFT("BambooRaft", Enemies.NEUTRAL, "BAMBOO_RAFT"),
PALE_OAK_BOAT("PaleOakBoat", Enemies.NEUTRAL, "PALE_OAK_BOAT"),
POPLAR_BOAT("PoplarBoat", Enemies.NEUTRAL, "POPLAR_BOAT"),
MINECART("Minecart", Enemies.NEUTRAL, EntityType.MINECART),
MINECART_CHEST("ChestMinecart", Enemies.NEUTRAL, MobCompat.CHEST_MINECART),
MINECART_FURNACE("FurnaceMinecart", Enemies.NEUTRAL, MobCompat.FURNACE_MINECART),
@@ -118,7 +119,17 @@ public enum Mob {
FROG("Frog", Enemies.FRIENDLY, "FROG"),
TADPOLE("Tadpole", Enemies.FRIENDLY, "TADPOLE"),
WARDEN("Warden", Enemies.ENEMY, "WARDEN"),
CHEST_BOAT("ChestBoat", Enemies.NEUTRAL, "CHEST_BOAT"),
CHEST_BOAT("ChestBoat", Enemies.NEUTRAL, MobCompat.OAK_CHEST_BOAT),
ACACIA_CHEST_BOAT("AcaciaChestBoat", Enemies.NEUTRAL, "ACACIA_CHEST_BOAT"),
DARK_OAK_CHEST_BOAT("DarkOakChestBoat", Enemies.NEUTRAL, "DARK_OAK_CHEST_BOAT"),
BIRCH_CHEST_BOAT("BirchChestBoat", Enemies.NEUTRAL, "BIRCH_CHEST_BOAT"),
JUNGLE_CHEST_BOAT("JungleChestBoat", Enemies.NEUTRAL, "JUNGLE_CHEST_BOAT"),
SPRUCE_CHEST_BOAT("SpruceChestBoat", Enemies.NEUTRAL, "SPRUCE_CHEST_BOAT"),
MANGROVE_CHEST_BOAT("MangroveChestBoat", Enemies.NEUTRAL, "MANGROVE_CHEST_BOAT"),
CHERRY_CHEST_BOAT("CherryChestBoat", Enemies.NEUTRAL, "CHERRY_CHEST_BOAT"),
BAMBOO_CHEST_RAFT("BambooChestRaft", Enemies.NEUTRAL, "BAMBOO_CHEST_RAFT"),
PALE_OAK_CHEST_BOAT("PaleOakChestBoat", Enemies.NEUTRAL, "PALE_OAK_CHEST_BOAT"),
POPLAR_CHEST_BOAT("PoplarChestBoat", Enemies.NEUTRAL, "POPLAR_CHEST_BOAT"),
CAMEL("Camel", Enemies.FRIENDLY, "CAMEL"),
SNIFFER("Sniffer", Enemies.FRIENDLY, "SNIFFER"),
ARMADILLO("Armadillo", Enemies.FRIENDLY, "ARMADILLO"),
@@ -132,6 +143,7 @@ public enum Mob {
ZOMBIE_NAUTILUS("ZombieNautilus", Enemies.NEUTRAL, "ZOMBIE_NAUTILUS"),
PARCHED("Parched", Enemies.ENEMY, "PARCHED"),
SULFUR_CUBE("SulfurCube", Enemies.FRIENDLY, "SULFUR_CUBE"),
CUSHION("Cushion", Enemies.FRIENDLY, "CUSHION")
;
private static final Map<String, Mob> hashMap = new HashMap<>();
@@ -88,6 +88,7 @@ public final class MobCompat {
public static final EntityType END_CRYSTAL = getEntityType("END_CRYSTAL", "ENDER_CRYSTAL");
public static final EntityType FIREWORK_ROCKET = getEntityType("FIREWORK_ROCKET", "FIREWORK");
public static final EntityType OAK_BOAT = getEntityType("BOAT", "OAK_BOAT");
public static final EntityType OAK_CHEST_BOAT = getEntityType("CHEST_BOAT", "OAK_CHEST_BOAT");
private MobCompat() {
}
@@ -409,6 +410,7 @@ public final class MobCompat {
OAK("GENERIC", "OAK"),
SPRUCE("REDWOOD", "SPRUCE"),
MANGROVE("GENERIC", "MANGROVE"),
POPLAR("GENERIC", "POPLAR"),
;
private final String treeSpecies;
@@ -55,8 +55,8 @@ public enum MobData {
BABY_VILLAGER("child", EntityType.VILLAGER, Data.BABY, false),
TAMED_TAMEABLE("tamed", Tameable.class, Data.TAMED, true),
TAME_TAMEABLE("tame", Tameable.class, Data.TAMED, false),
RANDOM_SHEEP("random", EntityType.SHEEP, Data.COLORABLE, true),
COLORABLE_SHEEP("", Arrays.stream(DyeColor.values()).map(color -> color.name().toLowerCase(Locale.ENGLISH)).collect(Collectors.toList()), EntityType.SHEEP, Data.COLORABLE, true),
RANDOM_COLORABLE("random", Colorable.class, Data.COLORABLE, true),
COLORABLE("", Arrays.stream(DyeColor.values()).map(color -> color.name().toLowerCase(Locale.ENGLISH)).collect(Collectors.toList()), Colorable.class, Data.COLORABLE, true),
POLKA_HORSE("polka", EntityType.HORSE, Horse.Style.BLACK_DOTS, true),
SOOTY_HORSE("sooty", EntityType.HORSE, Horse.Style.BLACK_DOTS, false),
BLAZE_HORSE("blaze", EntityType.HORSE, Horse.Style.WHITE, true),
@@ -181,8 +181,6 @@ public enum MobData {
WHITE_TRADER_LLAMA("white", MobCompat.TRADER_LLAMA, "llama:WHITE", true),
BROWN_TRADER_LLAMA("brown", MobCompat.TRADER_LLAMA, "llama:BROWN", true),
GRAY_TRADER_LLAMA("gray", MobCompat.TRADER_LLAMA, "llama:GRAY", true),
RANDOM_SHULKER("random", MobCompat.SHULKER, Data.COLORABLE, true),
COLORABLE_SHULKER("", Arrays.stream(DyeColor.values()).map(color -> color.name().toLowerCase(Locale.ENGLISH)).collect(Collectors.toList()), MobCompat.SHULKER, Data.COLORABLE, true),
RED_FOX("red", MobCompat.FOX, "fox:RED", true),
SNOW_FOX("snow", MobCompat.FOX, "fox:SNOW", true),
SIZE_PHANTOM("", Collections.singletonList("<1-100>"), MobCompat.PHANTOM, Data.SIZE, true),
@@ -208,12 +206,13 @@ public enum MobData {
JUNGLE_BOAT("jungle", Boat.class, MobCompat.BoatVariant.JUNGLE, true),
REDWOOD_BOAT("redwood", Boat.class, MobCompat.BoatVariant.REDWOOD, true),
MANGROVE_BOAT("mangrove", Boat.class, MobCompat.BoatVariant.MANGROVE, true),
POPLAR_BOAT("poplar", Boat.class, MobCompat.BoatVariant.POPLAR, true),
OAK_BOAT("oak", Boat.class, MobCompat.BoatVariant.OAK, true),
SPRUCE_BOAT("spruce", Boat.class, MobCompat.BoatVariant.SPRUCE, true),
SADDLE_CAMEL("saddle", MobCompat.CAMEL, Data.CAMELSADDLE, true),
PALE_WOLF("pale", EntityType.WOLF, "wolf:PALE", true),
SPOTTED_WOLF("spotted", EntityType.WOLF, "wolf:PALE", true),
SNOWY_WOLF("snowy", EntityType.WOLF, "wolf:PALE", true),
SPOTTED_WOLF("spotted", EntityType.WOLF, "wolf:SPOTTED", true),
SNOWY_WOLF("snowy", EntityType.WOLF, "wolf:SNOWY", true),
BLACK_WOLF("black", EntityType.WOLF, "wolf:BLACK", true),
ASHEN_WOLF("ashen", EntityType.WOLF, "wolf:ASHEN", true),
RUSTY_WOLF("rusty", EntityType.WOLF, "wolf:RUSTY", true),
@@ -32,6 +32,10 @@ public final class SpawnMob {
private static final Material GOLDEN_HELMET = EnumUtil.getMaterial("GOLDEN_HELMET", "GOLD_HELMET");
private static final Material GOLDEN_CHESTPLATE = EnumUtil.getMaterial("GOLDEN_CHESTPLATE", "GOLD_CHESTPLATE");
private static final Material COPPER_HELMET = EnumUtil.getMaterial("COPPER_HELMET");
private static final Material COPPER_CHESTPLATE = EnumUtil.getMaterial("COPPER_CHESTPLATE");
private static final Material COPPER_LEGGINGS = EnumUtil.getMaterial("COPPER_LEGGINGS");
private static final Material COPPER_BOOTS = EnumUtil.getMaterial("COPPER_BOOTS");
private static final Material GOLDEN_LEGGINGS = EnumUtil.getMaterial("GOLDEN_LEGGINGS", "GOLD_LEGGINGS");
private static final Material GOLDEN_BOOTS = EnumUtil.getMaterial("GOLDEN_BOOTS", "GOLD_BOOTS");
private static final Material GOLDEN_SWORD = EnumUtil.getMaterial("GOLDEN_SWORD", "GOLD_SWORD");
@@ -210,6 +214,11 @@ public final class SpawnMob {
invent.setLeggings(new ItemStack(Material.NETHERITE_LEGGINGS, 1));
invent.setChestplate(new ItemStack(Material.NETHERITE_CHESTPLATE, 1));
invent.setHelmet(new ItemStack(Material.NETHERITE_HELMET, 1));
} else if (inputData.contains("copper") && COPPER_HELMET != null) {
invent.setBoots(new ItemStack(COPPER_BOOTS, 1));
invent.setLeggings(new ItemStack(COPPER_LEGGINGS, 1));
invent.setChestplate(new ItemStack(COPPER_CHESTPLATE, 1));
invent.setHelmet(new ItemStack(COPPER_HELMET, 1));
} else if (inputData.contains("diamond")) {
invent.setBoots(new ItemStack(Material.DIAMOND_BOOTS, 1));
invent.setLeggings(new ItemStack(Material.DIAMOND_LEGGINGS, 1));
@@ -617,7 +617,8 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
final BigDecimal value = _getMoney();
final long elapsed = System.nanoTime() - start;
if (elapsed > ess.getSettings().getEconomyLagWarning()) {
ess.getLogger().log(Level.INFO, "Lag Notice - Slow Economy Response - Request took over {0}ms!", elapsed / 1000000.0);
ess.getLogger().log(Level.INFO, String.format(Locale.ROOT,
"Lag Notice - Slow Economy Response - Request took over %.3fms!", elapsed / 1_000_000.0));
}
return value;
}
@@ -12,7 +12,8 @@ public class Commanddisposal extends EssentialsCommand {
@Override
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
user.sendTl("openingDisposal");
user.getBase().openInventory(ess.getServer().createInventory(user.getBase(), 36, user.playerTl("disposal")));
final String title = ess.getAdventureFacet().miniToLegacy(user.playerTl("disposal"));
user.getBase().openInventory(ess.getServer().createInventory(user.getBase(), 36, title));
}
}
@@ -21,10 +21,8 @@ public class SignDisposal extends EssentialsSign {
@Override
protected boolean onSignInteract(final ISign sign, final User player, final String username, final IEssentials ess) {
String title = (sign.getLine(1) + " " + sign.getLine(2) + " " + sign.getLine(3)).trim();
if (title.isEmpty()) {
title = player.playerTl("disposal");
}
final String customTitle = (sign.getLine(1) + " " + sign.getLine(2) + " " + sign.getLine(3)).trim();
final String title = customTitle.isEmpty() ? ess.getAdventureFacet().miniToLegacy(player.playerTl("disposal")) : customTitle;
player.getBase().openInventory(ess.getServer().createInventory(player.getBase(), 36, title));
return true;
}
@@ -52,7 +52,7 @@ public final class MaterialUtil {
BEDS = EnumUtil.getAllMatching(Material.class, "BED", "BED_BLOCK", "WHITE_BED", "ORANGE_BED",
"MAGENTA_BED", "LIGHT_BLUE_BED", "YELLOW_BED", "LIME_BED", "PINK_BED", "GRAY_BED",
"LIGHT_GRAY_BED", "CYAN_BED", "PURPLE_BED", "BLUE_BED", "BROWN_BED", "GREEN_BED",
"RED_BED", "BLACK_BED");
"RED_BED", "BLACK_BED", "STRAW_BED");
BANNERS = EnumUtil.getAllMatching(Material.class, "BANNER", "WHITE_BANNER",
"ORANGE_BANNER", "MAGENTA_BANNER", "LIGHT_BLUE_BANNER", "YELLOW_BANNER", "LIME_BANNER",
@@ -86,7 +86,8 @@ public final class MaterialUtil {
"OAK_SIGN", "SPRUCE_SIGN",
"CRIMSON_SIGN", "WARPED_SIGN",
"MANGROVE_SIGN", "CHERRY_SIGN",
"BAMBOO_SIGN", "PALE_OAK_SIGN");
"BAMBOO_SIGN", "PALE_OAK_SIGN",
"POPLAR_SIGN");
WALL_SIGNS = EnumUtil.getAllMatching(Material.class, "WALL_SIGN",
"ACACIA_WALL_SIGN", "BIRCH_WALL_SIGN",
@@ -94,21 +95,24 @@ public final class MaterialUtil {
"OAK_WALL_SIGN", "SPRUCE_WALL_SIGN",
"CRIMSON_WALL_SIGN", "WARPED_WALL_SIGN",
"MANGROVE_WALL_SIGN", "CHERRY_WALL_SIGN",
"BAMBOO_WALL_SIGN", "PALE_OAK_WALL_SIGN");
"BAMBOO_WALL_SIGN", "PALE_OAK_WALL_SIGN",
"POPLAR_WALL_SIGN");
HANGING_SIGNS = EnumUtil.getAllMatching(Material.class, "ACACIA_HANGING_SIGN", "BIRCH_HANGING_SIGN",
"DARK_OAK_HANGING_SIGN", "JUNGLE_HANGING_SIGN",
"OAK_HANGING_SIGN", "SPRUCE_HANGING_SIGN",
"CRIMSON_HANGING_SIGN", "WARPED_HANGING_SIGN",
"MANGROVE_HANGING_SIGN", "CHERRY_HANGING_SIGN",
"BAMBOO_HANGING_SIGN", "PALE_OAK_HANGING_SIGN");
"BAMBOO_HANGING_SIGN", "PALE_OAK_HANGING_SIGN",
"POPLAR_HANGING_SIGN");
HANGING_WALL_SIGNS = EnumUtil.getAllMatching(Material.class, "ACACIA_WALL_HANGING_SIGN", "BIRCH_WALL_HANGING_SIGN",
"DARK_OAK_WALL_HANGING_SIGN", "JUNGLE_WALL_HANGING_SIGN",
"OAK_WALL_HANGING_SIGN", "SPRUCE_WALL_HANGING_SIGN",
"CRIMSON_WALL_HANGING_SIGN", "WARPED_WALL_HANGING_SIGN",
"MANGROVE_WALL_HANGING_SIGN", "CHERRY_WALL_HANGING_SIGN",
"BAMBOO_WALL_HANGING_SIGN", "PALE_OAK_WALL_HANGING_SIGN");
"BAMBOO_WALL_HANGING_SIGN", "PALE_OAK_WALL_HANGING_SIGN",
"POPLAR_WALL_HANGING_SIGN");
}
private MaterialUtil() {
@@ -0,0 +1,23 @@
package com.earth2me.essentials.utils;
import io.papermc.lib.environments.PaperEnvironment;
import io.papermc.lib.features.asyncchunks.AsyncChunksPaper_15;
import io.papermc.lib.features.asyncteleport.AsyncTeleportPaper_13;
import io.papermc.lib.features.bedspawnlocation.BedSpawnLocationPaper;
import io.papermc.lib.features.chunkisgenerated.ChunkIsGeneratedApiExists;
/**
* A PaperLib environment for Paper servers whose version PaperLib cannot read.
* <p>
* PaperLib 1.0.6 expects a single digit major version in {@code Bukkit#getVersion()}, so it reads 26.x as version 0 and
* selects its synchronous fallbacks for chunk loading, teleporting and bed lookups. All of those exist on every Paper
* version that reports a year based version.
*/
public class ModernPaperEnvironment extends PaperEnvironment {
public ModernPaperEnvironment() {
asyncChunksHandler = new AsyncChunksPaper_15();
asyncTeleportHandler = new AsyncTeleportPaper_13();
isGeneratedHandler = new ChunkIsGeneratedApiExists();
bedSpawnLocationHandler = new BedSpawnLocationPaper();
}
}
@@ -49,8 +49,9 @@ public final class VersionUtil {
public static final BukkitVersion v1_21_11_R01 = BukkitVersion.fromString("1.21.11-R0.1-SNAPSHOT");
public static final BukkitVersion v26_1_R01 = BukkitVersion.fromString("26.1.2-R0.1-SNAPSHOT");
public static final BukkitVersion v26_2_R01 = BukkitVersion.fromString("26.2-R0.1-SNAPSHOT");
public static final BukkitVersion v26_3_R01 = BukkitVersion.fromString("26.3-R0.1-SNAPSHOT");
private static final Set<BukkitVersion> supportedVersions = ImmutableSet.of(v1_8_8_R01, v1_9_4_R01, v1_10_2_R01, v1_11_2_R01, v1_12_2_R01, v1_13_2_R01, v1_14_4_R01, v1_15_2_R01, v1_16_5_R01, v1_17_1_R01, v1_18_2_R01, v1_19_4_R01, v1_20_6_R01, v1_21_11_R01, v26_1_R01, v26_2_R01);
private static final Set<BukkitVersion> supportedVersions = ImmutableSet.of(v1_8_8_R01, v1_9_4_R01, v1_10_2_R01, v1_11_2_R01, v1_12_2_R01, v1_13_2_R01, v1_14_4_R01, v1_15_2_R01, v1_16_5_R01, v1_17_1_R01, v1_18_2_R01, v1_19_4_R01, v1_20_6_R01, v1_21_11_R01, v26_1_R01, v26_2_R01, v26_3_R01);
public static final boolean PRE_FLATTENING = VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_13_0_R01);
+64
View File
@@ -1149,6 +1149,70 @@ protect:
witch: false
horse: false
phantom: false
zombified_piglin: false
mooshroom: false
snow_golem: false
rabbit: false
guardian: false
elder_guardian: false
endermite: false
shulker: false
polar_bear: false
stray: false
husk: false
wither_skeleton: false
zombie_villager: false
skeleton_horse: false
zombie_horse: false
donkey: false
mule: false
llama: false
evoker: false
vex: false
vindicator: false
illusioner: false
parrot: false
turtle: false
cod: false
salmon: false
pufferfish: false
tropical_fish: false
dolphin: false
drowned: false
cat: false
fox: false
panda: false
pillager: false
ravager: false
trader_llama: false
wandering_trader: false
bee: false
hoglin: false
piglin: false
piglin_brute: false
strider: false
zoglin: false
axolotl: false
glow_squid: false
goat: false
allay: false
frog: false
tadpole: false
warden: false
camel: false
sniffer: false
armadillo: false
bogged: false
breeze: false
creaking: false
happy_ghast: false
copper_golem: false
camel_husk: false
nautilus: false
zombie_nautilus: false
parched: false
sulfur_cube: false
cushion: false
# The maximum height a creeper can explode. -1 allows them to explode everywhere.
# Set 'creeper-explosion' above to true if you want to disable creeper explosions completely.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,62 @@
package com.earth2me.essentials.utils;
import io.papermc.lib.environments.PaperEnvironment;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
public class ModernPaperEnvironmentTest {
private static final String YEAR_VERSION = "26.3-140-main@a1b2c3d (2026-09-30T10:00:00Z) (MC: 26.3)";
@Test
public void testPaperLibFallsBackToSynchronousTeleportOnYearVersions() {
// Documents why ModernPaperEnvironment exists: PaperLib itself picks the synchronous handlers here.
withVersion(YEAR_VERSION, PaperEnvironment::new, env -> {
final Entity entity = mock(Entity.class);
final Location location = new Location(mock(World.class), 0, 64, 0);
env.teleport(entity, location, TeleportCause.PLUGIN);
verify(entity).teleport(location, TeleportCause.PLUGIN);
verify(entity, never()).teleportAsync(any(Location.class), any(TeleportCause.class));
});
}
@Test
public void testTeleportsAsynchronouslyOnYearVersions() {
withVersion(YEAR_VERSION, ModernPaperEnvironment::new, env -> {
final Entity entity = mock(Entity.class);
final Location location = new Location(mock(World.class), 0, 64, 0);
env.teleport(entity, location, TeleportCause.PLUGIN);
verify(entity).teleportAsync(location, TeleportCause.PLUGIN);
verify(entity, never()).teleport(any(Location.class), any(TeleportCause.class));
});
}
@Test
public void testLoadsChunksAsynchronouslyOnYearVersions() {
withVersion(YEAR_VERSION, ModernPaperEnvironment::new, env -> {
final World world = mock(World.class);
env.getChunkAtAsync(world, 1, 2, true);
verify(world).getChunkAtAsync(1, 2, true, false);
});
}
private static <E extends PaperEnvironment> void withVersion(final String version, final Supplier<E> create, final Consumer<E> test) {
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
bukkit.when(Bukkit::getVersion).thenReturn(version);
test.accept(create.get());
}
}
}
@@ -600,7 +600,7 @@ public class DiscordSettings implements IConf {
"username", "displayname", "controllername", "controllerdisplayname", "reason");
kickFormat = generateMessageFormat(getFormatString("kick"), "{displayname} was kicked with reason: {reason}", false,
"username", "displayname", "reason");
pmToDiscordFormat = generateMessageFormat(getFormatString("private-chat"), "[SocialSpy] {sender-username} -> {receiver-username}: {message}", false,
pmToDiscordFormat = generateMessageFormat(getFormatString("pms-to-discord"), "[SocialSpy] {sender-username} -> {receiver-username}: {message}", false,
"sender-username", "sender-displayname", "receiver-username", "receiver-displayname", "message");
plugin.onReload();
+1 -1
View File
@@ -26,7 +26,7 @@ however, have some new requirements:
* **EssentialsX requires CraftBukkit, Spigot or Paper to run.** Other server software may work, but these are not tested
by the team and we may not be able to help with any issues that occur.
* **EssentialsX currently supports Minecraft versions 1.8.8, 1.9.4, 1.10.2, 1.11.2, 1.12.2, 1.13.2, 1.14.4, 1.15.2,
1.16.5, 1.17.1, 1.18.2, 1.19.4, 1.20.6, 1.21.11, 26.1.2, and 26.2.**
1.16.5, 1.17.1, 1.18.2, 1.19.4, 1.20.6, 1.21.11, 26.1.2, 26.2, and 26.3.**
* **EssentialsX currently requires Java 8 or higher.** We recommend using the latest Java version supported by your
server software.
* **EssentialsX requires [Vault](http://dev.bukkit.org/bukkit-plugins/vault/) to enable using chat prefix/suffixes and
@@ -10,7 +10,7 @@ plugins {
val baseExtension = extensions.create<EssentialsBaseExtension>("essentials", project)
val checkstyleVersion = "8.36.2"
val paperVersion = "26.2.build.92-stable"
val paperVersion = "26.3-pre-2.build.0-alpha"
val paperTestVersion = "1.21.8-R0.1-SNAPSHOT"
val junit5Version = "5.12.2"
val junitPlatformVersion = "1.12.2"
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
Vendored
+2 -2
View File
@@ -20,7 +20,7 @@
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
Vendored
+2 -2
View File
@@ -19,7 +19,7 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2
@rem Execute Gradle
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel