Compare commits

...
492 Commits
Author SHA1 Message Date
MD 100f575293 Release 2.22.0 (#6550) 2026-05-31 14:16:07 +00:00
c5a25bb79c New Crowdin updates (#6251)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2026-05-31 12:28:07 +00:00
Jason 5baf2391b3 Remove unused essentials.jail perm (#6539)
The `/jail` command is just an alias for `/togglejail`. As such, the
`essentials.jail` permission is never used.
2026-05-12 22:31:46 +00:00
Josh Roy c38c9256e9 Fix deposits rejected when account has negative balance (#6515)
Economy.add() delegated to setMoney() which checks minMoney and loan
permission against the resulting balance. Both checks reject negative
results even when the balance is improving from a deposit. Since
deposits can only move the balance upward, route them directly to
user.setMoney() bypassing the floor/loan checks. Withdrawals still
go through setMoney() with full validation.

Fixes #5606
2026-05-04 08:26:42 +00:00
Josh Roy 79d67e98c3 Fix essentials.home.bed permission being ignored (#6516)
Two paths bypassed the permission check:

1. Explicit /home bed: the permission check at line 44 was combined
   with the bed-spawn logic in a single condition. When denied, it
   fell through to goHome("bed") which found the stored bed home
   by name and teleported anyway. Now rejects immediately.

2. Fallback respawn: when a player has no homes and isSpawnIfNoHome
   is true, respawn() was called unconditionally which tries the bed.
   Now checks the permission and falls back to world spawn when denied.

Fixes #5963
2026-05-04 08:24:28 +00:00
Josh Roy 5db483b548 Fix Discord Link RoleSyncManager thread leak (#6512)
Two issues causing unbounded thread growth:

1. The repeating sync task was never cancelled on plugin disable,
   leaking the timer itself.

2. Each sync() call spawns an async task that calls
   acquireUninterruptibly() on a 5-permit semaphore. The timer fires
   up to 50 sync calls per cycle, so 45 threads block indefinitely
   waiting for permits. Before they drain, the next cycle spawns 50
   more. Threads accumulate until OOM.

Fix: cancel the task on disable, and replace acquireUninterruptibly()
with tryAcquire(5s timeout) in both sync() and unSync() so threads
don't block indefinitely — skipped syncs retry on the next cycle.

Fixes https://github.com/EssentialsX/Essentials/issues/6381
2026-05-04 08:23:08 +00:00
HotWaterFlaskandJosh Roy 39bc0fe224 Send geoip join messages to console (#6523)
Not only output to the players who have the "essentials.geoip.show"
permission, but also output to the console.

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-05-04 08:16:42 +00:00
Josh Roy 1a9cba913b Add lunge to /enchant (#6534)
Fixes #6521
2026-05-04 08:15:52 +00:00
Hudson AndrésandJosh Roy 24b7285e42 fix: reset nickname when player changes Minecraft account name (#6470)
Closes #6469

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-05-03 05:43:46 +00:00
Adrián Oliva Del RíoandJosh Roy 5c45ee2169 Add /jailedplayers command to list jailed players (#6517)
<!--

EssentialsX feature 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 new feature, please follow the following steps:

1.  Fill out the template in full.
This includes providing screenshots and a link to the original feature
      request. If there isn't an existing feature request, we strongly
      recommend opening a new feature request BEFORE opening your PR to
implement it, as this allows us to review whether we're likely to accept
      your feature in advance, and also allows us to discuss possible
      implementations for the feature. If there is no associated feature
      request, your PR may be delayed or rejected without warning.
      
      You can open a new feature request by following this link:
      https://github.com/EssentialsX/Essentials/issues/new/choose

2. If you are fixing a performance issue, please use the "Bug fix" PR
template
instead. The "bug fix" template is better suited to performance issues.

3.  Include a demonstration.
If you are adding commands, please provide screenshots and/or a video
demonstration of the feature. Similarly, if you are adding a new API,
      please include a link to example code that takes advantage of your
proposed API. This will aid us in reviewing PRs and speed up the process
      significantly.

-->

### Information

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

This PR closes #5838. 

### Details

**Proposed feature:**  
<!-- Type a description of your proposed feature below this line. -->
Create a command to query the list of imprisoned players

**Environments tested:**    

<!-- Type the OS you have used below. -->
OS:  Ubuntu 24.04.4 LTS x64 bits

<!-- Type the JDK version (from java -version) you have used below. -->
Java version:  JDK21

<!--
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 (1.XX.Y, git-Paper-BUILD)
- [ ] 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.
-->
**Jailed players execution with 2 players jailed who are offline:**
<img width="657" height="96" alt="image"
src="https://github.com/user-attachments/assets/45663c9f-5d87-43f7-94a2-cefca62d6c07"
/>

**Jailed players command execution with no players jailed:**
<img width="657" height="96" alt="image"
src="https://github.com/user-attachments/assets/10813887-b7ef-4291-ab12-8927d50b6a05"
/>

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-05-03 03:32:52 +00:00
Josh Roy 87bde72678 Fix consoleName translation key not working (#6506)
Fixes #6343
2026-04-12 21:15:37 +00:00
Josh Roy 97fd5609b4 Fix {USERNAME} underscore escaping in kit items (#6514)
The underscore-to-backslash escaping in KeywordReplacer is needed for
item names/lores where underscores represent spaces, but Minecraft
usernames never contain spaces and their underscores must stay literal.
Skip the escaping for USERNAME keywords so player:{USERNAME} resolves
correctly for names like Player_Name.

Fixes #6296
2026-04-12 21:10:46 +00:00
Josh Roy 936a2c0c62 Fix SocialSpy local chat color bleeding from player display name (#6507)
The display name converted via legacyToMini() produces open MiniMessage
formatting tags that bleed into the subsequent message text. Append
<reset> after the display name to close any inherited formatting.

Fixes #6504
2026-04-12 21:10:40 +00:00
Josh Roy 3d03cf35df Fix command descriptions not using player's locale (#6508)
Usage descriptions were resolved once at command construction time via
tlLiteral() (server default locale) and cached. Now store the
translation key instead and resolve per-player at display time using
playerTl()/tl().

Fixes #6353
2026-04-12 21:10:37 +00:00
Josh Roy 57a8c596f8 Fix /time overriding player's /ptime setting (#6509)
Fixes #6359
2026-04-12 21:10:34 +00:00
Josh Roy b594358545 Fix VanishStatusChangeEvent NPE and document parameter inversion (#6510)
The affected/controller parameters are swapped in Commandvanish (#2604)
but cannot be fixed without breaking the public API.

Fix BukkitListener to use getController() (the actual vanished player)
instead of getAffected() (the sender, null for console), and add
inversion warning javadoc matching the NickChangeEvent pattern.

Fixes #6388
2026-04-12 21:10:30 +00:00
Josh Roy e9f3677b95 Fix /eco ignoring locale-specific decimal separators (#6511)
Commandeco used raw BigDecimal constructor with hardcoded period as
decimal separator, breaking input like '1.000' for European locales. Use
NumberUtil.parseStringToBDecimal() like /pay already does.

Fixes #6475
2026-04-12 21:10:26 +00:00
Josh Roy 678bacfee5 Suppress advancement broadcast for vanished players on Paper (#6513)
Use Paper's event.message(null) API to hide the advancement chat message
when the player is vanished (checked via Bukkit metadata). The player
still receives the advancement, only the broadcast is suppressed.

Fixes #6350
2026-04-12 21:10:23 +00:00
Josh Roy 7f37b5e0f3 Fix minimessage parsing in playerlist keyword replacement (#6505)
Fixes #6502
2026-04-12 20:25:07 +00:00
Brokkonaut f39bc91ef5 Fix version parsing for local paper builds (#6501)
Fixes #6498
2026-04-12 17:42:15 +00:00
Jason 63e7c4d259 Bump supported versions to 26.1.2 (#6500) 2026-04-11 19:06:18 +00:00
Josh Roy 1038b3a597 Fix incorrect teleport safety message (#6486)
old message seems needlessly technical anyway 

Fixes #5088
2026-04-05 20:55:41 +00:00
Josh Roy fb669aa9fd Remove extractUrls from default legacy serializer (#6492)
extractUrls on the default LegacyComponentSerializer caused
legacyToMini to auto-wrap URLs in <click:open_url> tags. When
translation templates like discordCommandLink already wrapped {0}
in their own click tag, this produced nested click events that
Paper 1.21.11+ rejected.

The default serializer no longer extracts URLs. A new
legacyToMiniWithUrls method is available for cases that explicitly
need clickable URL extraction (chat, broadcast).

Fixes #6463
2026-04-05 20:55:38 +00:00
Josh Roy add5eb7cbf Add permission cache system + permission for vanish item pickup (#6488)
Fixes #6270
2026-04-05 20:55:36 +00:00
Josh Roy ddaadf2099 Add socialSpyCmdFormat message key for command social spy (#6489)
Fixes #6049
2026-04-05 17:41:54 +00:00
Josh Roy 4db09f2a2e Change default max-nick-length to 16 (#6484)
Fixes #6028
2026-04-05 17:41:51 +00:00
Josh Roy 00b4a5b3bd Make /flyspeed and /walkspeed force the correct speed type (#6485)
Fixes #4883
2026-04-05 17:41:47 +00:00
Josh Roy 73aa65733e Reject Windows reserved filenames in /setwarp (#6487)
Fixes #5430

stop gap for before we do #5267
2026-04-05 17:41:34 +00:00
Josh Roy 86621cd90e Don't enable fly mode on login when player is in water (#6490)
Fixes #6198
2026-04-05 17:41:33 +00:00
Josh Roy 236ce8837e Add /unmute command and broadcast on unmute (#6491)
Fixes #6419
2026-04-05 17:41:29 +00:00
TouchieandJRoy f5fc4305c9 fix(fly): persist fly mode and restore on join (#6403)
Persist /fly state in userdata and re-enable allowFlight on login so
flight isn’t lost after relogging on ground (upstream #6344).

<!--

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 #6344 

### Details

**Proposed fix:**    
<!-- Type a description of your proposed fix below this line. -->
Persist the /fly state to userdata and restore allowFlight on login so
flight isn’t lost after relogging while on the ground.

**Environments tested:**    

<!-- Type the OS you have used below. -->
OS: Ubuntu 24.04

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

<!--
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 (1.XX.Y, git-Paper-BUILD)
- [x] Paper 1.21.8
- [ ] CraftBukkit/Spigot/Paper 1.12.2
- [ ] CraftBukkit 1.8.8

---------

Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2026-04-05 04:49:23 +00:00
Golfing7andJosh Roy 08b7fe3bb8 Add PreTransactionEvent (#6370)
<!--

EssentialsX feature 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 new feature, please follow the following steps:

1.  Fill out the template in full.
This includes providing screenshots and a link to the original feature
      request. If there isn't an existing feature request, we strongly
      recommend opening a new feature request BEFORE opening your PR to
implement it, as this allows us to review whether we're likely to accept
      your feature in advance, and also allows us to discuss possible
      implementations for the feature. If there is no associated feature
      request, your PR may be delayed or rejected without warning.
      
      You can open a new feature request by following this link:
      https://github.com/EssentialsX/Essentials/issues/new/choose

2. If you are fixing a performance issue, please use the "Bug fix" PR
template
instead. The "bug fix" template is better suited to performance issues.

3.  Include a demonstration.
If you are adding commands, please provide screenshots and/or a video
demonstration of the feature. Similarly, if you are adding a new API,
      please include a link to example code that takes advantage of your
proposed API. This will aid us in reviewing PRs and speed up the process
      significantly.

-->

### Information

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

I added a PreTransactionEvent for cancelling transactions between users.

### Details

**Proposed feature:**  
Adding a PreTransactionEvent.


**Environments tested:**    

<!-- Type the OS you have used below. -->
OS: Linux Mint

<!-- Type the JDK version (from java -version) you have used below. -->
Java version:  23

<!--
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 (1.XX.Y, git-Paper-BUILD)
- [ ] 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.
-->
```java
    @EventHandler
    public void onPay(PreTransactionEvent event) {
        // Prevent money from being sent outside their own faction.
        IUser userSource = event.getRequester().getUser();
        if (userSource == null)
            return;

        FPlayer fplayerSource = FPlayers.getInstance().getByPlayer(userSource.getBase());
        if (fplayerSource == null || fplayerSource.isAdminBypassing())
            return;

        Faction faction = fplayerSource.getFaction();
        if (faction == null || !isFactionVoluntaryExcluded(faction.getId()))
            return;

        // Who are they sending it to?
        FPlayer target = FPlayers.getInstance().getById(event.getTarget().getUUID().toString());
        Faction factionTarget = target.getFaction();
        if (faction != factionTarget) {
            event.setCancelled(true);
            msg(userSource.getBase(), cantPayOutsideExcludedFactionMessage);
        }
    }
```
This code prevents transactions between two users when two factions
can't interact.

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-04-05 03:46:06 +00:00
CrystalD5andJosh Roy 0845ea6f08 Fixed NoSuchMethodException Player.getRespawnLocation() (#6460)
Fixes #6459.

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-04-05 02:57:29 +00:00
vbojovicandJosh Roy 5a78df5494 Add option to allow fishing when afk (#6465)
Fixes #6443
Fixes #6323

- Prevent AFK timer reset when fishing rod bobber is idle
- Fix incorrect teleportOfflineUnknown message showing sender instead of
target

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-04-05 02:36:13 +00:00
Josh Roy 716a61eb16 Mitigate Discord 429s (#6461) 2026-04-04 19:17:36 +00:00
Josh Roy 637ac8f4ee Fix version comparison logic for pre-release/snapshot/rc variants (#6483)
Pre-release, snapshot, and RC variants should be considered lower than
the base release version. Updated test assertions and comparison logic
to reflect that release versions are higher than their dev variants
(snapshot < pre < rc < release).
2026-04-04 18:58:55 +00:00
Jason 218e004108 Bump supported versions to 26.1.1 (#6482)
Yay!
2026-04-04 05:28:07 +00:00
Jason c26909e2b8 Add alias "ver" for the "version" subcommand (#6481)
Fixin what u broke :)
2026-04-04 04:48:36 +00:00
5d535dcbe4 Support new PaperMC versioning scheme in VersionUtil (#6480)
ThioJoe is waiting!
Re #6479

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2026-04-04 04:11:15 +00:00
Josh Roy 8e3b24fdba Minecraft 26.1 (#6473) 2026-03-23 18:07:48 +00:00
CopilotandJRoy d7452bfcc7 Fix stale shadow include group ID for emoji-java in EssentialsDiscord (#6467)
Commit 297d0e4 migrated `emoji-java` from Jitpack
(`com.github.MinnDevelopment`) to EssentialsX's own Maven repo
(`net.essentialsx.deps`), but the `include(dependency(...))` in the
`shadowJar` block was not updated to match, leaving the shadow JAR
unable to locate and bundle the dependency.

### Details

**Proposed fix:**
- Updated `EssentialsDiscord/build.gradle` shadowJar include to use the
correct group ID:
  ```groovy
  // Before
  include(dependency('com.github.MinnDevelopment:emoji-java'))

  // After
  include(dependency('net.essentialsx.deps:emoji-java'))
  ```

**Environments tested:**    

OS: 

Java version:  

- [ ] Most recent Paper version (1.XX.Y, git-Paper-BUILD)
- [ ] CraftBukkit/Spigot/Paper 1.12.2
- [ ] CraftBukkit 1.8.8


**Demonstration:**

<!-- START COPILOT CODING AGENT SUFFIX -->



<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> never updated the shadow include group id here pls fix
> 
> Reference:
https://github.com/EssentialsX/Essentials/commit/297d0e460605efec9db2ca824ad20945838a1ff3


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

📱 Kick off Copilot coding agent tasks wherever you are with [GitHub
Mobile](https://gh.io/cca-mobile-docs), available on iOS and Android.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2026-03-15 15:49:56 +00:00
482175e6c2 Prevent running recipe command while sleeping (#6455)
<!--

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 #6450 . 

### Details

**Proposed fix:**    
I added a check to make sure that items are only placed into the
inventory when it successfully opens.


**Environments tested:**    

<!-- Type the OS you have used below. -->
OS: Linux Mint

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

<!--
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 (1.XX.Y, git-Paper-BUILD)
- [ ] 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.
-->
In order to replicate the issue, I used the following code to test:
```java
    @EventHandler
    public void onRecipeStop(InventoryOpenEvent event) {
        if (event.getPlayer().isSleeping()) {
            event.setCancelled(true);
        }
    }
```

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
Co-authored-by: Benedikt Johannes <benedikt.johannes.hofer@gmail.com>
2026-03-14 15:23:54 +00:00
CopilotandJRoy 302653a77c Fix workflow artifact upload paths missing "X" in EssentialsX addon jar names (#6462)
### Information

Workflow artifact upload steps referenced incorrect jar filenames — all
addon jars are built as `EssentialsX{Addon}-*.jar` (e.g.
`EssentialsXChat`, `EssentialsXProtect`) because project names in
`settings.gradle.kts` are all prefixed with `EssentialsX`, and
`archivesName` is set to `project.name`. The upload globs were missing
the `X`, causing every per-module direct upload to fail to find its
target file.

### Details

**Proposed fix:**  
Update all per-module `archive: false` upload step paths in both
`build-pr.yml` and `build-master.yml` to use the correct
`EssentialsX`-prefixed names:

| Before | After |
|---|---|
| `jars/EssentialsAntiBuild-*.jar` | `jars/EssentialsXAntiBuild-*.jar` |
| `jars/EssentialsChat-*.jar` | `jars/EssentialsXChat-*.jar` |
| `jars/EssentialsDiscord-*.jar` | `jars/EssentialsXDiscord-*.jar` |
| `jars/EssentialsDiscordLink-*.jar` |
`jars/EssentialsXDiscordLink-*.jar` |
| `jars/EssentialsGeoIP-*.jar` | `jars/EssentialsXGeoIP-*.jar` |
| `jars/EssentialsProtect-*.jar` | `jars/EssentialsXProtect-*.jar` |
| `jars/EssentialsSpawn-*.jar` | `jars/EssentialsXSpawn-*.jar` |
| `jars/EssentialsXMPP-*.jar` | `jars/EssentialsXXMPP-*.jar` |

The Javadoc `cp` paths in `build-master.yml` reference filesystem
directory names (which don't include `X`) and remain correct as-is.

**Environments tested:**

OS: N/A (CI workflow change only)

Java version: N/A

- ~~Most recent Paper version (1.XX.Y, git-Paper-BUILD)~~
- ~~CraftBukkit/Spigot/Paper 1.12.2~~
- ~~CraftBukkit 1.8.8~~

**Demonstration:**

N/A — the incorrect globs would silently produce no artifact (or an
error) for each addon module on every workflow run using `archive:
false`.

<!-- START COPILOT CODING AGENT SUFFIX -->



<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

>
https://github.blog/changelog/2026-02-26-github-actions-now-supports-uploading-and-downloading-non-zipped-artifacts/
> 
> update our workflows to use this rather than zipping (still provide
the zip but also individually upload


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for
you](https://github.com/EssentialsX/Essentials/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2026-03-10 00:33:35 +00:00
Théo Dufrene 9d70fdb263 Add MiniMessage support for thunder enabled / disabled (#6454)
<!--

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 missing MiniMessage support for the enabled / disabled
thunder message

### Details

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

**Environments tested:**    

<!-- Type the OS you have used below. -->
OS: Linux
<!-- Type the JDK version (from java -version) you have used below. -->
Java version:  21

<!--
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] 1.21.8 Paper version 
- [ ] 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.
-->
2026-03-09 15:03:43 +00:00
MD 297d0e4606 Migrate Vault and emoji-java dependencies away from Jitpack (#6458)
Jitpack is down. Move dependencies to our own repo to remove the
build-time dependency on Jitpack.
2026-03-08 21:44:39 +00:00
UDPSendToFailed 9c833336fb Fix integer overflow with /burn + /more & fix % in chat formats (#6452)
Before:

- Using `/burn <player> 107374183` extinguished the player.
- `/burn` accepted negative values.
- Using `/more 2147483647` deleted the held item(s) entirely.

After:

- Using `/burn <player> 107374183` sets the player on fire for 107374183
seconds correctly.
- `/burn` doesn't accept negative values.
- Using `/more 2147483647` correctly increases the held item stack to
64.
2026-03-07 20:09:13 +00:00
UDPSendToFailedandMD 5abe8b61ef Fix server freezing with /give (#6449)
<!--

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 a bug where anyone with access to the /give command could
freeze the server.

### Details

Currently, if a user has permission to spawn items with metadata, they
can freeze or completely crash the server by supplying a massive integer
for a book's page number (e.g., `/give <player> written_book 1
page2147483647:payload`).

Because there is no upper bound check in `MetaItemStack.java`, the
internal `for` loop tries to allocate an `ArrayList` with billions of
empty strings to reach that requested page index. This instantly
exhausts the JVM heap and triggers an OutOfMemoryError, hard-crashing
the server thread, or at least freezing it entirely for seconds.

This PR fixes the issue by introducing a hard limit of 100 pages for
book metadata parsing. If the requested page exceeds this limit, the
loop doesn't execute and it instead throws a newly added
`pageLimitExceeded` translatable exception.


**Environments tested:**    

<!-- Type the OS you have used below. -->
OS:  Windows 11 25H2 26200.7922

<!-- Type the JDK version (from java -version) you have used below. -->
Java version: Java 21 (OpenJDK 64-Bit Server VM 21.0.10+7-LTS; Eclipse
Adoptium Temurin-21.0.10+7)

<!--
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.
-->
Purpur version 1.21.11-2545


**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:

<img width="1401" height="269" alt="image"
src="https://github.com/user-attachments/assets/9453a449-af43-4e77-bdfe-26e63e47b634"
/>

After:

<img width="1104" height="189" alt="image"
src="https://github.com/user-attachments/assets/f1783011-92db-4518-9a4d-a831bb847261"
/>

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2026-02-28 23:03:48 +00:00
Gavin HeatheringtonandJosh Roy b8acd9c3ab Allow discord console relay to be filtered by logger name (#6448)
Rearranged logger addition to message entry for filtering by logger
name.

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-02-28 21:22:50 +00:00
BestZige f080f815fa fix(commands): propagate Essentials and module to tree nodes (#6445)
### What does this PR do?

Fixes an issue where `EssentialsTreeNode` instances did not receive
updated
`Essentials` and `IEssentialsModule` references after registration.

This could lead to null references during command execution,
especially in test environments.

### Changes made

- Override `setEssentials` in `EssentialsTreeCommand`
- Override `setEssentialsModule` in `EssentialsTreeCommand`
- Propagate updated references to all registered nodes

### Why is this needed?

Nodes only received references during registration.
If the parent command updated its references later,
the nodes would retain outdated (null) values.

### Does this PR break compatibility?

No.
2026-02-23 17:14:49 +00:00
Josh Roy 18549a718a Fix /ess reload (#6444) 2026-02-22 21:36:39 +00:00
Josh Roy e9b8343751 Move away from adventure-platform for Paper (#6220)
Makes us no longer dependent on adventure-platform for Paper servers as
we will use their native adventure apis.

Few hacks needed to be done to not remap things in places but this is
the best we can do

The PaperAdventureFacet has to be in PaperProvider to avoid the kyori
remapping that happens on the entire EssentialsX module (we already did
this for PaperChatListenerProvider)

Fixes #6215
2026-02-22 19:53:38 +00:00
RVSkeLe 99f7a49f28 Improve gc command performance (#6439)
Updates /gc to use the new provider methods, allowing Paper to use
getTileEntities(false) and avoid unnecessary snapshots while maintaining
spigot compatibility.

"Benchmarks:"
Before, Paper: https://spark.lucko.me/kf2MVRQ5td - 76mspt
After, Paper: https://spark.lucko.me/NhZQXtLqvh - 1mspt

Spigot: https://spark.lucko.me/toXyvx0yq0
2026-02-22 03:40:39 +00:00
Josh Roy bc6acfe1dc Command Trees v2 (#6230)
My take on command trees which fit the vibe of our codebase better than
#5384 imo.

Closes #5384
2026-02-21 21:13:48 +00:00
Vishwas Kumar d16aec1525 Fixed unreachable nickNoMore Message in /nick command. Not a huge change. (#6442)
The display name was comparing getDisplayName() to itself, making it
always true which made the nickNoMore message unreachable. Now it uses
FormatUtil.stripFormat() to strip color codes before comparing against
getName(), also has a null-safety guard.

I have built it locally and tested it - it works well. Verified: "Nick
set normally", "Nick equal to Username", "Colored nick equal to
Username". Had no Errors.
2026-02-21 08:45:17 +00:00
Nassim JahnkeandJRoy 787f539921 Prevent NPE in getPlayerLocale for offline players (#6435)
I hope this message finds you well. This pull requests prevents an NPE
in getPlayerLocale for offline players.

Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2026-02-09 19:38:47 +00:00
TiagoFar78andJosh Roy 6ab56d2fe8 Implement kits placing items in specific slots (#5794)
Closes #5535

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-02-07 05:50:39 +00:00
Ville Blixt e5c9074b7c Fix preventing joins when server isn't actually full (#6426)
Fixes #6424

Caused by https://github.com/PaperMC/Paper/pull/13601
2026-02-07 05:14:10 +00:00
ivall 77756620cf Show expiration on temporary IP ban kick message (#6407)
Show tempban IP expiration on player join, matching standard tempban
behavior.
2026-01-17 19:54:28 +00:00
Josh RoyandMD 0bd4c24c8b Update dump url (#6283)
new site doesn't require .html extension, we can avoid the redirect.

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2026-01-17 19:49:41 +00:00
Phinner 18233d3f04 Fix NPE in PermissionsHandler#getGroups(OfflinePlayer) (#6398) 2026-01-11 00:59:40 +00:00
Phinner 5ed1d1e04a Fix UtilTest tests failing if locale is not English (#6397) 2026-01-11 00:58:18 +00:00
The_Dalek 9ad754b6d9 Use Discord name instead of tag for /msg sender (#6383)
Fixes #5520
2026-01-03 16:40:51 +00:00
benniofficialdevandJosh Roy 68c304e6b6 Fix /balance behavior for vanished players (#6386)
Fixes #6085

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-01-03 16:34:45 +00:00
benniofficialdevandJosh Roy ac666186f5 Fix error when running /helpop from console (#6385)
Fixes #6342

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2026-01-03 15:58:43 +00:00
Stackulate b34951757b Fix Spigot 1.21.10+ ReflServerStateProvider (#6377) 2026-01-03 15:48:20 +00:00
Josh RoyandMD 000e6c551d Mirror dumps on gist (#6193)
This will allow us to backup dumps while issues are still open since
bytebins expire after a bit.

We will need to set `GIST_TOKEN` to a PAT for @Flask-Bot.

obviously will require new website and shouldn't be merged till.

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2025-12-20 20:28:08 +00:00
Josh Roy b0ef56802a Update to Minecraft 1.21.11 (#6364)
Aliases TBD
2025-12-09 17:22:25 +00:00
ThiagoROX faa02b5c49 Fix event thread assignment in TeleportWarmupCancelledEvent (#6371)
Adds a call to super with the correct async flag based on Bukkit's
primary thread status, ensuring proper event handling for
TeleportWarmupCancelledEvent.

fix this error:

```
[18:40:31 WARN]: [Essentials] Plugin Essentials v2.22.0-dev+278-984a0c1 generated an exception while executing task 156164
java.lang.IllegalStateException: TeleportWarmupCancelledEvent may only be triggered synchronously.
        at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:42) ~[paper-1.21.10.jar:1.21.10-117-df4b668]
        at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:131) ~[paper-1.21.10.jar:1.21.10-117-df4b668]
        at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:628) ~[paper-api-1.21.10-R0.1-SNAPSHOT.jar:?]
        at EssentialsX-2.22.0-278.jar/com.earth2me.essentials.AsyncTimedTeleport.cancelTimer(AsyncTimedTeleport.java:160) ~[EssentialsX-2.22.0-278.jar:?]
        at EssentialsX-2.22.0-278.jar/com.earth2me.essentials.AsyncTimedTeleport.run(AsyncTimedTeleport.java:96) ~[EssentialsX-2.22.0-278.jar:?]
        at org.bukkit.craftbukkit.scheduler.CraftTask.run(CraftTask.java:78) ~[paper-1.21.10.jar:1.21.10-117-df4b668]
        at org.bukkit.craftbukkit.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:57) ~[paper-1.21.10.jar:1.21.10-117-df4b668]
        at com.destroystokyo.paper.ServerSchedulerReportingWrapper.run(ServerSchedulerReportingWrapper.java:22) ~[paper-1.21.10.jar:?]
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[?:?]
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[?:?]
        at java.base/java.lang.Thread.run(Thread.java:1583) ~[?:?]
```
2025-12-02 21:50:32 +00:00
Golfing7andJosh Roy 0fa6f70b13 Add ability to register custom item transformers. (#4871)
.

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-11-29 17:41:22 +00:00
Pierre-Olivier GOINandJosh Roy af44749256 Add WarmUpCancelled Event & Expose TPType (#6351)
<!--

EssentialsX feature 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 new feature, please follow the following steps:

1.  Fill out the template in full.
This includes providing screenshots and a link to the original feature
      request. If there isn't an existing feature request, we strongly
      recommend opening a new feature request BEFORE opening your PR to
implement it, as this allows us to review whether we're likely to accept
      your feature in advance, and also allows us to discuss possible
      implementations for the feature. If there is no associated feature
      request, your PR may be delayed or rejected without warning.
      
      You can open a new feature request by following this link:
      https://github.com/EssentialsX/Essentials/issues/new/choose

2. If you are fixing a performance issue, please use the "Bug fix" PR
template
instead. The "bug fix" template is better suited to performance issues.

3.  Include a demonstration.
If you are adding commands, please provide screenshots and/or a video
demonstration of the feature. Similarly, if you are adding a new API,
      please include a link to example code that takes advantage of your
proposed API. This will aid us in reviewing PRs and speed up the process
      significantly.

-->

### Information

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

### Details

**Proposed feature:**  
This PR add a new event when a warmpup teleport is cancelled, and it
also expose the tpType


**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: OpenJDK Runtime Environment Corretto-8.422.05.1 (build
1.8.0_422-b05)

<!--
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 (1.21.8, git-Paper-60-main@29c8822)
- [ ] CraftBukkit/Spigot/Paper 1.12.2
- [x] Paper 1.8.8

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-11-27 15:14:53 +00:00
Josh Roy 87dfe3793a Update to adventure 4.25.0 (#6331) 2025-11-21 21:12:57 +00:00
EesaandJosh Roy 9f57b3e476 Add config option for logging console/RCON commands (#6337)
<!--

EssentialsX feature 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 new feature, please follow the following steps:

1.  Fill out the template in full.
This includes providing screenshots and a link to the original feature
      request. If there isn't an existing feature request, we strongly
      recommend opening a new feature request BEFORE opening your PR to
implement it, as this allows us to review whether we're likely to accept
      your feature in advance, and also allows us to discuss possible
      implementations for the feature. If there is no associated feature
      request, your PR may be delayed or rejected without warning.
      
      You can open a new feature request by following this link:
      https://github.com/EssentialsX/Essentials/issues/new/choose

2. If you are fixing a performance issue, please use the "Bug fix" PR
template
instead. The "bug fix" template is better suited to performance issues.

3.  Include a demonstration.
If you are adding commands, please provide screenshots and/or a video
demonstration of the feature. Similarly, if you are adding a new API,
      please include a link to example code that takes advantage of your
proposed API. This will aid us in reviewing PRs and speed up the process
      significantly.

-->

### Information

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

Closes #6336. 

### Details

<!-- Type a description of your proposed feature below this line. -->
This adds a config option that allows disabling the logging of console
and RCON commands.

**Environments tested:**    

<!-- Type the OS you have used below. -->
OS: Arch Linux

<!-- Type the JDK version (from java -version) you have used below. -->
Java version:  openjdk 21.0.8 2025-07-15

<!--
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 (1.XX.Y, git-Paper-BUILD)
- [x] CraftBukkit/Spigot/Paper 1.12.2
- [x] 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.
-->
Without these changes, with the config option not set:
<img width="680" height="69" alt="image"
src="https://github.com/user-attachments/assets/92647fc0-c0c5-4e7b-9f61-953fcadd7f93"
/>

With these changes, with the config option set to false:
<img width="635" height="68" alt="image"
src="https://github.com/user-attachments/assets/fd11572c-dfe2-4b79-89ab-69e2275d2c9f"
/>

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-11-09 23:52:34 +00:00
1wairesdandJosh Roy 9985dbdd3a Fix double messages when running /heal on self (#6347)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-10-26 23:50:50 +00:00
Warrior bb47c45a60 Buffer reads/writes in uuid cache loading/saving (#6338)
Buffers read and writes to uuid cache
2025-10-23 11:29:19 +00:00
Josh Roy 150dabbe24 Bump to JDA 6 (#6320) 2025-10-17 19:04:22 +00:00
Josh Roy adf77dc4ba Fix exception during first time joins (#6327)
Fixes an issue during first join with the player configuration events on
Paper. The usermap will refuse to implicitly store new users into the
usermap unless it can get a Bukkit player object for it, which is only
created during the PlayerJoinEvent. So for new users, we must defer to
then. The code that was being run (activity/mute checks) was probably
incorrect to be placed inside of configure event anyway.

Also force loads the locale bundle into memory to ensure it's ready for
servers with slow i/o.

Fixes #6315
2025-10-10 15:22:31 +00:00
Josh Roy 321c911222 Update to Minecraft 1.21.10 (#6324) 2025-10-07 19:16:17 +00:00
Josh Roy 264b0ed660 Improve memory resilience with Configurate (#6266)
Hopefully should cut down on memory usage with configurate.

Closes #6118
2025-10-03 14:32:08 +00:00
Josh Roy 4dabeb57db Fix GeoIP dependencies (#6314)
During the gradle/shadow upgrades during the 1.21.9 update, shadow
removed implicit wildcards. Explicitly declare all shadow depends for
jackson in geoip.

Also get rid of mavenCentral since paper mirrors it, was causing
resolution issues for mockbukkit

Fixes #6313
2025-10-03 14:31:44 +00:00
Josh Roy 8982aaec1d Fix updating join message when not needed (#6312)
Prevents calling setJoinMessage when the message is unchanged causing
Bukkit to no longer send the translation key to the client (nuking
vanilla translation)

Fixes #6307
2025-10-03 11:16:41 +00:00
CopilotandJRoy 22b497558a Fixes stale cached locations after using /settpr (#6305)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2025-10-03 11:05:45 +00:00
Josh Roy 65120e9d2c Update to Minecraft 1.21.9 (#6299) 2025-09-30 15:42:02 +00:00
Jason 893bae02e6 Change baltop entry limit wording (#6306)
Ensures consistency with the rest of the config
2025-09-29 03:56:42 +00:00
maxcom1andJosh Roy ca7f3b5d5d Add option to limit baltop entries (#6300)
```yml
# Allows to limit cached balance top (/baltop) entries. This is especially recommended if
# you have a large number of players, then only a certain number of entries will be stored
# in the server's memory. Set to -1 to disable the limit
baltop-entry-limit: -1
```

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-09-29 02:35:59 +00:00
Josh Roy 973e5f8b27 Add support for transmute recipes in /recipe (#6302)
Fixes #6297
2025-09-27 16:18:16 +00:00
Josh Roy 37e10546ea Switch to configuration based events on Paper (#6248)
Moves away from PlayerLoginEvent on Paper and makes use of the new
configuration state to load user/locale data during configuration phase.
Not only does this ensure all I/O is done async before the player even
joins but also finally allows us to actually set the join message in the
PlayerJoinEvent.

This also fixes #5796 and closes #5848
2025-09-19 14:46:49 +00:00
SpottedleafandJosh Roy 0aa25c80b9 Fix issue with "Fix issue with offline player data" (#6286)
Fixes #6277

Fixes the bad fix in e9da11624a

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-09-08 01:56:58 +00:00
Josh Roy ca07b531e4 Add message when discord bot is missing permissions (#6228)
Closes #5450
2025-09-07 15:35:12 +00:00
0ff1andJosh Roy e08606432c Adds support for armor trims inside kits (#6155)
ref #5799

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-09-06 18:49:37 +00:00
Josh Roy 364c87986f Add @s and @p selector support (#6110)
Adds support for @s and @p player selectors (which both select the
source player).

Closes #5925
Partially Addresses #6006
2025-09-06 14:16:30 +00:00
Josh Roy 427f60eb57 Fix matching players with similar names (#6263)
Fixes an issue where matchPlayer didn't always match exact matches
first.

Also adds a test for this method to ensure stuff like this doesn't
happen in the future.

Fixes #6246
2025-09-06 14:13:46 +00:00
Josh Roy efbcfb3bd6 AntiBuild: Properly handle shear interactions (#6262)
Fixes #6257
2025-09-06 14:13:37 +00:00
Josh Roy 19f7e99d95 Add support for LuckPerms group names (#6229)
Closes #5463
2025-09-06 14:13:28 +00:00
SpottedleafandJRoy e9da11624a Fix issue with offline player data (#6284)
Fixes #6277

Totally overlooked the invalidate call here

Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2025-09-04 15:17:05 +00:00
Josh Roy 81ac6f7283 Fix potion command not supporting infinite durations (#6236)
Minecraft understands infinite durations to be `-1` and only `-1`. Our
second -> tick conversion was changing this to `-20` causing a potion
command with a `-1` duration to end up as `-20` and only causing the
potion to occur for 1 tick.

Fixes #5558
2025-09-01 13:09:38 +00:00
Gufeng ShenandJosh Roy 7bc91f1c65 Discord: Add setting to configure an HTTP Proxy for JDA (#6275)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-08-18 15:21:12 +00:00
Josh RoyandMD 7c89592bbc Add setting to preserve fly on gamemode change (#6238)
Adds new option to preserve flight on gamemode change.

```yml
# Should Essentials preserve a player's flight status when they change game modes?
# When enabled, if a player is flying when switching game modes, their flight will be maintained.
# This will only work if the player has the 'essentials.fly' permission.
gamemode-change-preserve-flying: false
```

---------

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2025-08-16 22:15:41 +00:00
Josh Roy 302dd3fe35 Maintain strong reference for online users (#6247)
Possibly fixes #6241
2025-08-16 21:39:03 +00:00
Josh Roy 69fd7bc8ff Fix missing powertoollist permission (#6250) 2025-08-16 21:22:39 +00:00
Josh Roy 5ed43f339b Do not lowercase afk timeout commands (#6255)
Fixes #6254
2025-08-14 21:07:57 +00:00
Josh RoyandMD 8ba7a44a7d Add support for snapshots in VersionUtil (#6265)
We will always assume that a snapshot version is the most recent version

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2025-08-11 05:35:47 +00:00
hello09xandJRoy e0e8d4432a Prevent dragging items outside of invsee (#5902)
Fixes #3065

---------

Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2025-08-10 19:11:19 +00:00
Adrián Oliva Del Río 2ccfad189b Add first login date to /seen (#6258) 2025-08-09 22:49:24 +00:00
Adrián Oliva Del RíoandJRoy 58726fbc90 Add option to use UUIDs over usernames in trade.log (#6252)
<!--

EssentialsX feature 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 new feature, please follow the following steps:

1.  Fill out the template in full.
This includes providing screenshots and a link to the original feature
      request. If there isn't an existing feature request, we strongly
      recommend opening a new feature request BEFORE opening your PR to
implement it, as this allows us to review whether we're likely to accept
      your feature in advance, and also allows us to discuss possible
      implementations for the feature. If there is no associated feature
      request, your PR may be delayed or rejected without warning.
      
      You can open a new feature request by following this link:
      https://github.com/EssentialsX/Essentials/issues/new/choose

2. If you are fixing a performance issue, please use the "Bug fix" PR
template
instead. The "bug fix" template is better suited to performance issues.

3.  Include a demonstration.
If you are adding commands, please provide screenshots and/or a video
demonstration of the feature. Similarly, if you are adding a new API,
      please include a link to example code that takes advantage of your
proposed API. This will aid us in reviewing PRs and speed up the process
      significantly.

-->

### Information

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

This PR closes #6073. 

### Details

**Proposed feature:**  
<!-- Type a description of your proposed feature below this line. -->
Add a setting to replace usernames with UUIDs in the trade.log file


**Environments tested:**    

<!-- Type the OS you have used below. -->
OS: Windows 11 Pro

<!-- Type the JDK version (from java -version) you have used below. -->
Java version:  JDK 17

<!--
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 (1.XX.Y, git-Paper-BUILD)
- [ ] 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:**
<img width="1299" height="33" alt="image"
src="https://github.com/user-attachments/assets/6f34baf2-b5f5-46f6-adab-0ca0c0bf71c5"
/>

**After:**
<img width="1346" height="18" alt="image"
src="https://github.com/user-attachments/assets/672619ef-1215-4833-8320-e24bbb1c7ec0"
/>

**Command execution:**
<img width="491" height="107" alt="image"
src="https://github.com/user-attachments/assets/688efa80-3239-4bfe-8509-c998d3981637"
/>

---------

Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2025-08-06 15:02:38 +00:00
Josh Roy 025ca4895f Discord: Respect game rule for advancement message type (#6195)
Fixes #4865
2025-08-05 18:34:59 +00:00
Josh Roy 8bcf03d61b Discord: Add PAPI support to avatar-url (#6189)
Closes #6160
2025-08-05 18:33:29 +00:00
Mickey42302andJRoy 689c35fb23 Add bypass permission node for the whitelist. (#6232)
I would like to suggest adding an "essentials.whitelist.bypass"
permission node. This permission node allows users to bypass the
whitelist requirement on servers that have the whitelist enabled,
similar to how the "essentials.joinfullserver" permission node allows
users to bypass the player limit.

---------

Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2025-08-04 02:42:21 +00:00
JustinandJosh Roy 03d89380cc feat: Add /powertoollist (#6096)
<!--

EssentialsX feature 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 new feature, please follow the following steps:

1.  Fill out the template in full.
This includes providing screenshots and a link to the original feature
      request. If there isn't an existing feature request, we strongly
      recommend opening a new feature request BEFORE opening your PR to
implement it, as this allows us to review whether we're likely to accept
      your feature in advance, and also allows us to discuss possible
      implementations for the feature. If there is no associated feature
      request, your PR may be delayed or rejected without warning.
      
      You can open a new feature request by following this link:
      https://github.com/EssentialsX/Essentials/issues/new/choose

2. If you are fixing a performance issue, please use the "Bug fix" PR
template
instead. The "bug fix" template is better suited to performance issues.

3.  Include a demonstration.
If you are adding commands, please provide screenshots and/or a video
demonstration of the feature. Similarly, if you are adding a new API,
      please include a link to example code that takes advantage of your
proposed API. This will aid us in reviewing PRs and speed up the process
      significantly.

-->

### Information

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

This PR closes #6095. 

### Details

**Proposed feature:**  
<!-- Type a description of your proposed feature below this line. -->
This PR implements /powertoollist, which lists out all of a user's set
powertools.

Since all of a user's powertools reside within `UserData`, I utilized
this Map to query through all of a user's powertools.

**Environments tested:**    

<!-- Type the OS you have used below. -->
OS: MacOS Apple chip

<!-- Type the JDK version (from java -version) you have used below. -->
Java version: 22.0.2

<!--
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.
-->
- [ ] Most recent Paper version (1.XX.Y, git-Paper-BUILD)
- [ ] CraftBukkit/Spigot/Paper 1.12.2
- [ ] CraftBukkit 1.8.8
```
[20:36:06] [Server thread/INFO]: This server is running CraftBukkit version 4457-Spigot-73860e0-f0c8dd3 (MC: 1.21.4) (Implementing API version 1.21.4-R0.1-SNAPSHOT)
[20:36:06] [Server thread/INFO]: Checking version, please wait...
[20:36:06] [Thread-9/INFO]: You are running the latest version
```

**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.
-->
https://youtu.be/tgOr-fbHRAg

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-08-03 21:48:54 +00:00
Jsinco 790644b1ab Ignore cancelled PlayerDeathEvents (#6179)
<!--

EssentialsX feature 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 new feature, please follow the following steps:

1.  Fill out the template in full.
This includes providing screenshots and a link to the original feature
      request. If there isn't an existing feature request, we strongly
      recommend opening a new feature request BEFORE opening your PR to
implement it, as this allows us to review whether we're likely to accept
      your feature in advance, and also allows us to discuss possible
      implementations for the feature. If there is no associated feature
      request, your PR may be delayed or rejected without warning.
      
      You can open a new feature request by following this link:
      https://github.com/EssentialsX/Essentials/issues/new/choose

2. If you are fixing a performance issue, please use the "Bug fix" PR
template
instead. The "bug fix" template is better suited to performance issues.

3.  Include a demonstration.
If you are adding commands, please provide screenshots and/or a video
demonstration of the feature. Similarly, if you are adding a new API,
      please include a link to example code that takes advantage of your
proposed API. This will aid us in reviewing PRs and speed up the process
      significantly.

-->

### Information

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

This PR closes #6178 . 

### Details

**Proposed feature:**  
<!-- Type a description of your proposed feature below this line. -->
Essentials should ignore cancelled PlayerDeathEvents.


**Environments tested:**    

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

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

<!--
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 (1.21.6, @aa6cd74 build: 42)
- [ ] 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.
-->
2025-08-03 21:44:47 +00:00
334c5a4884 New config.yml option: afk-timeout-command: (#6169)
Implements this https://github.com/EssentialsX/Essentials/issues/2864

**Specifically, this functionality:**

`afk-timeout-command: "server afk {USERNAME}"`
Sends the player to the AFK server on the server-network.

`afk-timeout-command: "litebans:kick {USERNAME} You were kicked after
{KICK_TIME} minutes of inactivity."`
Kicks the player using litebans so that it shows up in their litebans
history.

`afk-timeout-command: "none"`
Default behaviour of essentials kicking the player on afk-timeout.

---------

Co-authored-by: Jason <51760372+JasonHorkles@users.noreply.github.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-08-03 21:41:50 +00:00
Josh Roy d402f17318 Reset oxygen with /heal (#6201)
Closes #6197
2025-08-03 21:41:08 +00:00
Josh Roy 636bb097fe Add tipped arrow support to /potion (#6191)
Closes #5576
2025-08-03 21:40:58 +00:00
Josh Roy 74337fff0f Add wind charge explosion protect config (#6183)
Adds new protect config to cancel the knockback effect from windcharges

```yml
protect:

  # General physics/behavior modifications. Set these to true to disable behaviors.
  prevent:
    windcharge-explosion: false
```

Closes #6181
2025-08-03 21:40:51 +00:00
Josh Roy b36d43380c Prepare for 2.22.0 Dev Builds (#6209) 2025-08-03 18:33:12 +00:00
JRoy eb781a192b Release 2.21.2 2025-08-03 19:21:20 +01:00
Flask BotandMD 16f643a41f New Crowdin updates (#6225)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2025-08-03 18:01:24 +00:00
Josh Roy c01ba4eec4 Bump adventure dependencies (#6243)
Fixes #6215
2025-07-30 19:17:53 +00:00
Josh Roy f4cd1c92e2 Discord: Fix custom bot name requiring show-avatar (#6239)
Fixes #5878. Caused by a regression from
384f63bf92, we were checking for for the
legacy options when we should really be checking if
`mc-to-discord-name-format` is anything other than the bot name.
2025-07-28 05:38:13 +00:00
Josh Roy d002c34fe8 Add version data extraction (#6235)
Will allow me to dynamically fetch the version for the site making it so
we don't have to update it every mc release
2025-07-27 07:10:47 +00:00
Josh Roy ff7c952715 Fix per-player locale for messages with arguments (#6227)
Fixes #6029
2025-07-20 19:01:58 +00:00
Josh Roy 0417624bda Fix local chat spy formatting (#6226)
Fixes #5828
2025-07-20 08:56:46 +00:00
Josh Roy 9ef4fc34c3 Update to Minecraft 1.21.8 (#6224) 2025-07-17 21:55:01 +00:00
Flask BotandMD 73da278b02 New Crowdin updates (#6143)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2025-07-13 14:19:48 +00:00
Josh Roy e04f4888cd Fix inventory behavior with new body/saddle slot (#6218)
There are new slots that are persistent in the player inventory, the
saddle slot + the body slot, ignore them from our inventory
calculations.

Fixes #6216
2025-07-12 14:10:16 +00:00
Josh RoyandMD dfd83cfb0b Fix /tpr on legacy versions (#6212)
Commodore breaks this on legacy likely, use a provider to ensure we use
the old ABI...................i love legacy

Fixes #6211

---------

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2025-07-12 14:09:07 +00:00
Josh Roy cc25a798fc Fix player commands option not working (#6214)
Fixes regression from 8d44d1ae7e
2025-07-10 06:24:24 +00:00
Josh Roy e39b61447b Fix numerous antibuild bypasses with signs (#6186)
Fix numerous antibuild bypasses when it came to breaking blocks that
signs were attached to

BOY DO WE NEED BALLOON TO CLEAN ALL THIS STUFF UP

Fixes #5784
Fixes #6173
2025-07-09 19:44:44 +00:00
b996a9f143 Fix formatCommand to handle empty args correctly (#6213)
<!--

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 #nnnn. 

### Details

**Proposed fix:**    
<!-- Type a description of your proposed fix below this line. -->
Allows creating a clickable confirmation button for the clear command
message.
The space prevents the command from being executed properly, as this
tries to run `'/clear '` instead of `'/clear'`.

**Environments tested:**    

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

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

<!--
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 (1.XX.Y, git-Paper-BUILD)
- [ ] 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.
-->

---------

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-09 18:11:18 +00:00
Josh Roy 3563b41556 Fix command data task (#6207) 2025-07-05 21:54:18 +00:00
Josh Roy ed3c9b83b5 Command + Permission Data Extraction (#6083)
for the new site, we're gonna have our own command and permissions page
and to make this less of a pain, this PR adds a gradle task which will
export that data. the resulting files end up in the root directory under
the generated folder.


gotta figure out how we automatically fetch this for the site, maybe we
make the generated json files part of the jenkins build process?
2025-07-05 21:43:29 +00:00
Josh Roy 6ad737d87a Fix various TPR issues (#6205)
Fixes various issues from the TPR improvements PR

Fixes #6035
2025-07-05 07:17:53 +00:00
Josh Roy 8d44d1ae7e Fix bstats error with unsupported permission plugins (#6190)
Fixes #5225
2025-07-04 15:35:00 +00:00
Josh Roy dae9e3d091 Trim chat messages with shout/question prefix (#6192)
Fixes #5333
2025-07-04 15:33:11 +00:00
Josh Roy 573aae01ee Fix /skull issues on 1.20.4+ (#6188)
Fixes #6124
2025-07-04 15:33:09 +00:00
Josh Roy c43ad269a8 Fix chat url parsing for tlds >3 chars (#6184)
Mojang only looks for urls with 2,3 chars but if we don't block urls
with >3 their regex will still parse the url trimming the extra chars
off the tld *sigh*

Fixes #6156
2025-07-04 15:33:06 +00:00
Josh Roy e3b4e7723f Fix PlaceholderAPI maven repo url (#6204) 2025-07-04 15:33:04 +00:00
Josh Roy 6100cedc58 Fix max nick length with hex color codes (#6185)
Hex color codes convert to the obnoxious md5 format which massively
inflates the character count than what the user entered. Unformat the
nickname before getting the length to remain true to what the user
entered as the command argument.

Fixes #6174
2025-07-04 15:21:57 +00:00
Josh Roy e6b8493749 Fix misleading whois command description (#6187)
Fixes #5616
2025-07-04 15:21:53 +00:00
MD fa4c6b66d5 Add merge queue trigger to PR workflow (#6203) 2025-07-04 15:17:39 +00:00
JRoy 715589c071 Add 1.21.7 Aliases 2025-06-30 08:45:26 -07:00
JRoy 4a06fd1138 Update to Minecraft 1.21.7 2025-06-30 08:45:26 -07:00
Josh Roy 25f2f76302 Update paste link inside of issue template (#6182)
paste.gg is kill
2025-06-28 07:03:27 +01:00
Josh Roy ff1b8b8615 Add 1.21.6 Aliases 2025-06-21 13:55:02 -07:00
Josh Roy 31ea9036cf Move tests to MockBukkit 2025-06-21 13:55:02 -07:00
Josh Roy 2796399268 Add 1.21.6 Mobs 2025-06-21 13:55:02 -07:00
Josh Roy 4057326b4f Update to Minecraft 1.21.6 2025-06-21 13:55:02 -07:00
Josh Roy b44a9b2256 Initial Move to Paper 1.21.6 2025-06-21 13:55:02 -07:00
Ahmed Waleed 4ab3bbca95 [ci skip] Update setup-gradle version (#6165) 2025-06-07 09:19:56 -07:00
Josh Roy f5c06b9676 Fix javadoc publish (#6164)
Netlify now requires a modern node version
2025-06-07 12:10:13 +01:00
Ahmed WaleedandJosh Roy 997f600c15 Update adventure dependencies (#6163)
Fixes issues with clickable chat components on 1.21.5

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-06-05 22:17:44 -07:00
ThiagoROX d42347fb09 Fix sending message during /editsign paste if SignChangeEvent is cancelled (#6136) 2025-05-25 13:27:48 -07:00
Josh Roy e836747244 Prepare for 2.21.2 dev builds (#6128) 2025-05-25 20:35:54 +01:00
JRoy 3d28d82709 Release 2.21.1 2025-05-25 20:24:18 +01:00
MD 3a5c7c44b8 Revert changes to /home and /sethome that break bed interactions (#6151)
This reverts PR #5991 (commit 3fe1495).
2025-05-25 18:05:54 +00:00
Josh Roy aebe851bff Fix double messages when deleting a non-existent home (#6150)
Fixes #6144
2025-05-25 08:12:02 +01:00
Hunh0wandJosh Roy 3fe1495fdb Fix unexpected behavior with essentials.home.bed & essentials.sethome.bed permissions (#5991)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-05-24 20:01:58 -07:00
Flask BotandMD 3b0c229e78 New Crowdin updates (#6131)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2025-05-18 08:42:47 +00:00
JasonandMD d137031a0a Improve config comments & ordering (#6108)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

This PR significantly overhauls the comments of the EssentialsX configuration files, ensuring consistency, clarity, and overall improved grammar (validated by Grammarly and LLMs). There are still a few things that must be completed before this is ready to be merged, but no further significant changes are expected. A couple of options have been moved entirely to make more logical sense, but this will not affect any compatibility.
2025-05-18 08:36:04 +00:00
MD a58db237a4 Update English source strings with fixes from Crowdin (#6140)
Migrate the few typo fixes from Crowdin's English US translation into the source messages.properties file (which is also English US).
2025-05-18 09:28:16 +01:00
_luftwaffleandMD fe430174a1 Fix "heer" typo in messages_en.properties (#6135)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2025-04-29 20:11:11 +00:00
Nassim Jahnke 3f5b12294f Use legacy text for TranslatableException#getMessage (#6134)
Fixes an issue for API consumers getting Adventure in our exception messages.
2025-04-27 19:13:35 +00:00
Flask BotandMD be3e641c62 New Crowdin updates (#6089)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2025-04-19 18:31:22 +00:00
Josh Roy 84552120c1 Fix /spawnmob on < 1.21.5 (#6121)
Fixes #6120
2025-04-05 07:59:29 +01:00
Josh Roy 2bb44386c7 Fix further trade sign validation mismatches (#6116)
can never win with this

fixes #6107
2025-03-31 17:43:27 +00:00
Josh Roy 06c886ca69 Fix /baltop on 1.15.2 and below (#6115)
Fixes #6093
2025-03-30 22:23:45 -04:00
Josh Roy f3a1b2b495 Fix hex colors not working in chat (#6114)
Need to explicitly enable them in our LegacyComponentSerializer.
2025-03-30 20:02:34 -04:00
MD c7ff994d18 Fire GlobalChatEvent even when local chat is disabled (#6113)
This allows EssentialsX Discord to send EssentialsX Chat messages to
Discord when `use-essentials-events: true` is set in the Discord config
while local chat is disabled.

It also allows plugins to hook and modify EssentialsX Chat message
contents, formatting and recipients when local chat is disabled.
2025-03-30 18:04:56 -04:00
Josh Roy f09541cfa6 Update to Minecraft 1.21.5 (#6109) 2025-03-29 22:49:13 +00:00
Josh Roy 0cb387fb05 Fix NPE when unlinking offline players (#6111)
Fixes #6105
2025-03-29 15:43:28 -04:00
Rasmus dabe68701d Add SignTransactionEvent for buy and sell signs (#6071) 2025-03-24 19:51:11 -04:00
William B. 50b696bc07 Adds social spy message type for in EssentialsDiscord (#5620) 2025-03-24 13:54:21 -04:00
Angelillo15 cb6187bbf2 Add option to customize nickname regex filter (#5200) 2025-03-24 16:43:53 +00:00
maxcom1 40540ebb3d Expose IUser#getLastActivityTime to API (#6101) 2025-03-24 16:29:06 +00:00
Josh Roy 46ba679c02 Prepare for 2.21.1 dev builds 2025-03-22 21:15:51 +00:00
Josh Roy 2719a189af Release 2.21.0 2025-03-22 20:52:40 +00:00
Olivia b4a97ab2a9 chore: add issue types to new issues (#6094)
Adds a new [issue
type](https://docs.github.com/en/issues/tracking-your-work-with-issues/configuring-issues/managing-issue-types-in-an-organization)
to new issues. Basically built in labels.
2025-03-19 07:43:07 +00:00
Flask BotandJosh Roy eab220183e New Crowdin updates (#6068)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-03-13 14:26:17 +00:00
Josh Roy 43d1e37b8f Fix flight ability preservation between worlds (#6079)
Fixes #6041
2025-03-09 16:13:50 +00:00
Josh Roy 80bdc5269f Fix wrong SpawnerBlockProvider on 1.21+
1.21 Adds a new org.bukkit.spawner.Spawner class as a superclass of CreatureSpawner which now contains the setMaxSpawnDelay method. Change our test to look for the method on any superclasses too.
2025-03-09 10:14:00 +00:00
Josh Roy 90d766df2b Add non-reflection provider for syncCommands
On 1.21.4+ Player#updateCommands has the same logic as CraftServer#updateCommands, so we can just use that.
2025-03-09 10:11:35 +00:00
Josh Roy 5bf158c1d4 Fix negative playtime stat breaking /baltop (#6074)
Unclear how this happens, possibly a division error?

Fixes #6043
2025-03-01 13:59:46 -05:00
Josh Roy 1531cf4146 Fix exception sending a chat with discord module (#6075)
We do not want or need to set the renderer, EssXChat or the server will do that.
2025-03-01 09:16:56 -05:00
Josh Roy b5748eb32f Update various dependencies (#6078)
Mainly Configurate, JDA, and Adventure
2025-03-01 07:21:48 -05:00
JRoy e49021fffe Fix item overflow issue with kits & trade signs 2025-02-27 22:01:43 -05:00
JRoy ba5257c8e6 Fix translation error in EssentialsSign#getItemStack 2025-02-27 22:01:43 -05:00
Flask BotandJosh Roy fe27182ba8 New Crowdin updates (#6037)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-02-24 01:58:14 +00:00
Josh Roy 5458241fcd Fix expired mails showing as unread on join (#6063) 2025-02-23 22:10:05 +00:00
Josh Roy bad79b79ac Fix /book command on 1.20+ (#6064) 2025-02-23 22:06:26 +00:00
Josh Roy 1ff24cb634 Use an identity map for Paper chat listener (#6067) 2025-02-23 17:02:31 -05:00
JRoy f5efa26300 Fix per-world flight permissions
With the implementation of #6012, there was no permission check done when we re-add flight to user's changing worlds. This broke servers with per-world flight permissions.
2025-02-23 08:42:10 +00:00
Théo DufreneandJosh Roy 263dab54d0 Fix spawn command sending message before teleportation (#6025)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-02-21 22:21:28 -05:00
Hernán Indíbil de la Cruz CalvoandJRoy e3e247dd61 Add discord module support for Paper's chat events (#5908)
Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2025-02-21 21:00:24 -05:00
Josh Roy 5a839c47bc Fix modifying messages with Paper chat events
Previously, our wrapper for Paper chat events did not account for other plugins modifying the message component during the event lifecycle, this will translate calls between (get/set)Message to and from legacy strings. This further add a barrier hover/click events for now but those never worked with EssXChat installed anyway and will require further refactoring at a later date in order to add support.
2025-02-20 16:41:16 -05:00
Josh Roy 3025ba5c05 Fix NPE when chat messages are canceled 2025-02-20 16:41:16 -05:00
Josh Roy 8313d522dc Add config for use of Paper chat events (#6050)
adds a config option to disable the use of paper chat events while other
plugins learn how to join the 21st century.

re #6048
re #6051
2025-02-19 21:00:46 +00:00
Josh Roy 196a2a19c4 Fix startup issue with potion providers (#6055)
The LegacyPotionMetaProvider was initializing a map with enums that
don't exist on modern versions. Since this was a static variable, this
map got initialized when the test method was called, leading to a
NoSuchFieldError.

Also improved resilience of these provider-type issues again by catching
a Throwable rather than an Exception in the ProviderFactory.

Fixes #6054
2025-02-19 15:58:00 -05:00
MD 69ed07a536 Fix the wrong potion meta provider being used on 1.8.8 (#6047) 2025-02-15 11:30:13 -05:00
Josh Roy cb00783ede Add support for Paper's chat events (#6033)
Fixes #4970
Fixes #5887
2025-02-14 15:14:44 -05:00
Josh Roy 4e6478224c Fix loot tables getting removed with essentials.keepinv (#6036) 2025-02-09 10:24:13 -05:00
Flask BotandJosh Roy fb6edc04c6 New Crowdin updates (#6024)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-02-06 14:50:21 +00:00
Josh Roy d232ce4037 Parse MiniMessage in default command usage & descriptions (#6018) 2025-02-05 08:14:09 -05:00
Josh RoyandMD 1a4d75ced7 [AntiBuild] Fix ender crystal breaking on 1.8 (#6009)
Ugly fix but it works for now...

Fixes #4418

---------

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2025-02-02 14:14:21 +00:00
Josh Roy 42121aa4be Remove deprecated teleport APIs (#6017)
They have been no-op deprecations for a while now
2025-02-02 13:45:07 +00:00
Josh RoyandShane Freeder ea3ea202a7 Fix flight state between world teleports (#6012)
Thanks to @electronicboy for the general fix.

Fixes #4325 for Paper only :)

Co-authored-by: Shane Freeder <theboyetronic@gmail.com>
2025-01-28 14:26:39 -05:00
Flask Bot 62fb6a16d4 New Crowdin updates (#6019)
Fixes #6022.
2025-01-28 19:12:50 +00:00
jquery-packageandJosh Roy e2295c1e1f Add permission node for giving skulls to other players (#6008)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-01-24 10:40:55 -05:00
Flask BotandJosh Roy eb6e192d8d New Crowdin updates (#5640)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2025-01-24 10:35:42 -05:00
JRoy b293f2328a Fix adventure error when using color in local chat
We were feeding legacy text into miniToLegacy which logs an annoying message in console
2025-01-24 01:55:00 -05:00
JRoy 22de3d8bab Revert 4219450705
Hotfix is no longer needed as it has been fixed in adventure.
2025-01-24 01:55:00 -05:00
JRoy fe4e388c42 Update adventure versions 2025-01-24 01:55:00 -05:00
Josh Roy 803771e74a Switch to pastes.dev for /ess dump (#6011)
Requires merge of https://github.com/EssentialsX/Website/pull/106
2025-01-17 07:38:54 -05:00
Henning Storck 667b0f7523 Skip upgrading random teleport config when tpr.yml isn't initialized yet (#6003)
If there's no tpr.yml, upgrading the random teleport config will fail
and the plugin cannot be loaded.
2025-01-01 17:03:46 +00:00
Matt Baxter 8e53214b50 Fix paper maven repository URL (#6002) 2024-12-31 23:39:08 -05:00
Josh Roy 5515f9f3f6 Rework the provider system (#5087) 2024-12-31 19:29:23 -05:00
Charles DeLancey f2af9528b0 Add new "world" argument to settpr messages (#5990) 2024-12-09 19:41:53 +00:00
Justin M.andJosh Roy e1091d887d Added sell multipliers (#4770)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-12-08 20:08:23 -05:00
JRoy 424816ef51 Add 1.21.4 Items 2024-12-04 02:49:55 -05:00
JRoy baa8258936 Add 1.21.4 Tree Types 2024-12-04 02:49:55 -05:00
JRoy c37064ff74 Add 1.21.4 Mobs 2024-12-04 02:49:55 -05:00
JRoy d58db0dce9 Update to 1.21.4 2024-12-04 02:49:55 -05:00
Josh Roy bc8aa76690 Revert removal of InvalidWorldException (#5984)
Fixes API breakage for people who catch the exception.
2024-12-02 22:14:03 -05:00
Zarkrey d31ff5553e Remove extraneous code in command preprocess event handling 2024-11-30 23:56:54 +00:00
JRoy b560bbdec4 Fix actions permissions on PRs for junit reports 2024-11-27 17:12:47 -05:00
Flowsqy 2a41ea050a Add config options for specific chat type formats 2024-11-27 17:12:47 -05:00
Gibson PilconisandJosh Roy c7cc1b4f93 Fix respawn-at-anchor setting. (#5825)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-11-25 03:15:25 +00:00
Ayush DeotareandJosh Roy 57c9edcc0c Add optional requirements for balance top listing (#5394)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-11-25 03:06:08 +00:00
KyriakumandJRoy 3f0a412390 Add locale based comma support for /pay (#5962)
Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2024-11-25 02:51:52 +00:00
Charles DeLanceyandJRoy 9590d4cd75 Add translation key for /alts output (#5595)
Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2024-11-25 02:28:49 +00:00
Clement RaynaudandJosh Roy f02eeecee8 Add method to get all linked players in DiscordLinkService (#5896)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-11-25 02:20:54 +00:00
AjMacandJosh Roy 3203e97a5e Parse /pay modifiers more strictly (#5638)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-11-25 02:16:28 +00:00
Charles DeLanceyandJosh Roy bea43e8798 Fix mini message parsing in NumberUtil#displayCurrency (#5921)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-11-25 02:11:41 +00:00
int4_tandJosh Roy 1af1565059 Discord: Add start time placeholder for start message (#5686)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-11-25 01:45:40 +00:00
YurakaiiandJosh Roy 38e42f9f90 Add argument to /skull to give skull to other player (#5822)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-11-24 20:42:04 -05:00
2418a6fb78 Improvements to Random Teleport (#4271)
Co-authored-by: triagonal <10545540+triagonal@users.noreply.github.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-11-24 18:07:18 -05:00
Ahmed WaleedandJosh Roy 1778bf5ada Respect per player locale for command descriptions (#5972)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-11-24 22:24:03 +00:00
Adrián Oliva Del Río a87760d3ce Add wildcard support for /delhome (#5919) 2024-11-24 01:47:36 +00:00
Josh Roy 2ac37d8dd4 Fix repair sign translation error (#5967)
Fixes #5964
2024-11-10 22:41:29 -05:00
JRoy 0b49421bad Fix typo in build logic 2024-11-09 08:00:08 -05:00
JRoy aa2eca17ef Fix compile errors from Sound no longer being an enum 2024-11-09 08:00:08 -05:00
JRoy fe6b04ed84 Support boat variants on older versions
New versions will need to use the specific entity, as we cannot swap the entity type after it has been spawned
2024-11-09 08:00:08 -05:00
JRoy 2b209a4a2f 1.12.3 Item Changes 2024-11-09 08:00:08 -05:00
JRoy a4a09299e7 1.12.3 Mob Changes 2024-11-09 08:00:08 -05:00
JRoy 4a68758975 Update to 1.12.3 2024-11-09 08:00:08 -05:00
JRoy cb15b467ab Update toolchain 2024-11-09 08:00:08 -05:00
MD f7a8f86850 Update shadow config for Discord module (#5960)
trove4j's artifact name changed and so it was no longer being shaded in
the Discord module.

Fixes #5959.
2024-10-21 13:55:22 -04:00
Josh Roy cd06e737e1 Fix error message during warp sign creation (#5956)
Fixes #5954
2024-10-21 10:36:35 +00:00
Josh Roy a8452b4615 Update JDA (#5958) 2024-10-20 22:18:21 +00:00
Josh Roy 1bede4ae3f Fix confirmClear & confirmPayment messages (#5957) 2024-10-20 18:15:57 -04:00
Josh Roy c80bef9841 Fix recipe message not being translated correctly (#5952)
I truly don't understand how this slipped through the cracks.

Fixes #5943
2024-10-15 11:13:16 +00:00
Josh Roy 9c3e0eb312 Add wind charge to /fireball (#5951)
Closes #5949
2024-10-15 06:56:11 -04:00
MD 6157668a63 Bump PlaceholderAPI dependency to version 2.11.6 (#5936)
Versions older than 2.11.5 are currently missing from the PlaceholderAPI
Maven repo, so we need to bump this for EssentialsX to compile.
2024-09-15 09:11:26 +01:00
JRoy 04518dbbb3 Use a click component for discord link command 2024-09-14 16:35:45 -04:00
JRoy 3e29e04722 Fix kittycannon on old versions 2024-09-14 16:35:45 -04:00
JRoy 9dfa8cf3cb Fix similarWarpExist message not being translated correctly 2024-09-14 16:35:45 -04:00
Slimified b54c8c1774 Parse colors from broadcast world command (#5913)
Make the broadcast world command mimic the behavior of the broadcast
command which correctly parses colors from the broadcast message.

Broadcast World Command Output Before: https://i.imgur.com/aFR6Zzb.png

Broadcast World Command Output After: https://i.imgur.com/qrvb8XD.png
2024-08-25 21:25:14 +00:00
Charles DeLancey f1a5caf98e Fixes wording on CannotRemoveNegativeItems (#5901) 2024-08-11 16:32:41 -04:00
TriassicandJosh Roy 7ae01a03ac Update to Minecraft 1.21.1 (#5900)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-08-11 14:48:53 +00:00
Charles DeLanceyandJRoy b763fd3868 Fix removing negative items in /clearinventory (#5899)
Co-authored-by: JRoy <10731363+JRoy@users.noreply.github.com>
2024-08-11 10:43:00 -04:00
Jason Penilla 7b02d22ac7 Fix incompatibility with AlternativeCommandsHandler & Paper API (#5881) 2024-08-01 19:59:37 -04:00
pop4959 8b08a8f2c8 Update placeholder colors for eco give (#5738) 2024-07-23 20:20:12 +00:00
Josh Roy 80c3150e44 Fix trade signs not updating signs (Fixes #5814)
i guess these things are transactional now whatever
2024-07-21 17:48:16 -04:00
JRoy 33e086ff05 Fix notEnoughMoney message not being translated correctly
Fixes #5759
2024-07-13 19:25:09 -04:00
JRoy 8971798a90 Fix build on new spigot versions
Villager.Profession was moved to a registry, only breaks at compile time.
2024-07-13 19:25:09 -04:00
Josh Roy fcf6e64732 Fix legacy compatibility (Fixes #5851) 2024-07-01 23:01:48 -04:00
JRoy 9e57dd6aef Fix NPE when using avatars in discord module (Fixes #5748) 2024-07-01 23:01:48 -04:00
JRoy b392f03ad1 Use enchantment key names for 1.13+
Enchantment#getName is deprecated and gives the wrong names for enchantments starting in 1.21
2024-06-29 11:40:40 +01:00
JRoy 64531aa862 Update items.json for 1.21 2024-06-29 11:40:40 +01:00
JRoy 1ea080effa Add support for 1.21 enchantments 2024-06-29 11:40:40 +01:00
JRoy 6624e6688c Add support for 1.21 potions 2024-06-29 11:40:40 +01:00
JRoy 022b0049b4 Add support for 1.21 Mobs 2024-06-29 11:40:40 +01:00
JRoy cecc00a3b6 Update to Minecraft 1.21 2024-06-29 11:40:40 +01:00
JRoy 29a122ea5e Update toolchain 2024-06-29 11:40:40 +01:00
Josh Roy 3a6fdd9bf3 Fix exception on 1.20.6 with Spigot (#5820)
How could I forget to update the md_5 magic letter

Fixes #5818
2024-06-02 19:34:47 -04:00
Pantera (Mad_Daniel) 434e7a0e70 Update adventure (#5819) 2024-06-02 11:42:27 -04:00
Josh Roy e7a4167111 Add support for data components 2024-05-27 15:53:50 -04:00
Josh Roy aec5859c2a Fix console error during /give command
There has been a method for this since 1.13.
Why have we been using this for so long?

Since item NBT is now dead, this method for checking
if an item is spawn-able is defunct and produces a console
error every time the give command is ran.
2024-05-27 15:53:50 -04:00
Josh Roy 5392169f72 Update items.json for 1.20.6 2024-05-27 15:53:50 -04:00
Josh Roy d4a3f5a9e6 Add support for 1.20.6 Mobs 2024-05-27 15:53:50 -04:00
Josh Roy bef1e57712 Reflect Enchantment API Changes 2024-05-27 15:53:50 -04:00
Josh Roy 4c5a97bf00 Reflect Potion API Changes 2024-05-27 15:53:50 -04:00
Josh Roy b9b3e4430e Reflect Banner API Changes 2024-05-27 15:53:50 -04:00
Josh Roy 84d3138b21 Fix tests
We really need to improve our tests, this will work for now
2024-05-27 15:53:50 -04:00
Jason Penilla 1c8060341f Opt-out of remapping on Paper 1.20.5+ (#5782) 2024-05-27 15:53:50 -04:00
Josh Roy c58aae78e7 Update to Minecraft 1.20.6 2024-05-27 15:53:50 -04:00
Charles DeLancey cde7184da3 Fix duplicated messages with /helpop (#5776) 2024-04-29 21:26:49 -04:00
DarkLaw 549283a1e5 Translate Gamemode placeholder in cantGamemode message (#5771) 2024-04-28 01:44:21 -07:00
Charles DeLancey 6a2527ccdb Fix sender not receiving helpop message (#5769) 2024-04-28 01:39:26 -07:00
Emilia Kond c60ed56190 Fix stack trace when buying from sign when inventory full (#5761) 2024-04-13 21:50:45 +00:00
pop4959andJosh Roy c85e179718 Make ChargeException translatable (#5736)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-04-08 01:22:31 +00:00
DarkLaw 71ca7ffbf6 Fix heal signs not healing up to the player's max health (#5752) 2024-03-31 22:53:53 +00:00
pop4959andJosh Roy 76a513a05c Don't run keyword replacer by default in translated broadcasts (#5739)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-03-25 19:11:18 +00:00
pop4959 8cfe680abc Fix empty translations being sent to Console (#5737) 2024-03-25 19:09:13 +00:00
pop4959 2cb0af0ca4 Strict parsing for legacy converted placeholders (#5740)
This fixes issues where arguments converted implicitly from MiniMessage
are prone to bleeding into the rest of the output.

The fix mostly works since `MINI_MESSAGE_NO_TAGS` is only used for
legacy conversion, while `miniMessageInstance` is used in all other
cases normally. If that were not the case, we would not want strict
parsing everywhere since we don't enforce this in translations anyway.

Fixes #5729
Fixes #5730
Fixes #5732
Fixes #5735

Fixes #5720
Closes #5728
2024-03-11 04:14:58 -04:00
Charles DeLancey 570eca2e21 Add text box to bug report template (#5715) 2024-03-10 21:31:40 -07:00
Charles DeLancey 3817a11d9d Convert color codes in tempban and tempbanip (#5726) 2024-03-05 14:58:57 -05:00
Catarina FreireandJosh Roy c19b20ea3b Fix banner patterns not working in kits (#5723)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-03-04 13:49:30 -05:00
Charles DeLancey a4ac720057 Fix format parsing in ban messages (#5722) 2024-03-04 13:45:06 -05:00
Charles DeLancey 993d7ede56 Fix shout/local chat not sending with certain formats (#5719) 2024-02-29 23:32:51 +00:00
622c8147a9 Add setting to use usernames over display names for social spy (#5613)
Co-authored-by: Bobcat00 <Bobcat00@users.noreply.github.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-02-26 15:25:30 -05:00
pop4959 6b521e78a6 Fix jail-online-time with offline players (#5701) 2024-02-26 14:32:44 -05:00
pop4959 4219450705 Hotfix legacy serializer to include capitalized formatting code chars (#5711) 2024-02-26 05:09:02 -08:00
pop4959 bd8c792fa4 Support custom biomes in random teleport excluded biome list (#5703) 2024-02-26 03:47:00 -08:00
MD 52a638f18f Add actions step to log JUnit test reports (#5684) 2024-02-26 01:51:31 +00:00
pop4959 1b1b511b61 Fix conversion issues between Legacy and MiniMessage (#5702)
Fixes #5652
2024-02-26 01:39:18 +00:00
pop4959 d4b72c8af6 Fix Discord /list command showing tags in output (#5698) 2024-02-25 00:06:52 -08:00
pop4959 185b4e266b Fix /broadcastworld sending in all but the intended world (#5699)
Fixes #5694

This issue was caused by the passed predicate being excludes rather than
includes.

Also addresses a separate issue where no help output was given when no
arguments were specified and the command was sent by a player.
2024-02-24 22:40:22 +00:00
pop4959 7a9a0e6f51 Fix /me from console (#5700)
Fixes #5689
2024-02-24 20:42:30 +00:00
pop4959andMD 9be27ad7ac Fix custom message colors config not working (#5705)
No linked issue; discussed on Discord with @Evidentsinger14

Also @JRoy, I refuse to believe you ever tested this feature, you're
trolling

---------

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2024-02-24 20:39:40 +00:00
pop4959 09fa6463a7 Update GitHub Actions (#5704)
Bump actions steps to remove deprecation warnings on GitHub

https://github.com/EssentialsX/Essentials/assets/17698576/0d032078-0c36-4362-b680-5a9b30d30345
2024-02-24 20:28:07 +00:00
Charles DeLancey 1929d4110a Fix format parsing in /heal (Fixes #5690) 2024-02-20 16:57:57 -05:00
Josh Roy 4386713807 Fix messages not sending after /ess reload (Fixes #5663) 2024-02-19 19:37:03 -05:00
Josh Roy 139db29782 Fix format parsing in various discord commands 2024-02-19 17:01:44 -05:00
Josh Roy d307279b3b Fix format parsing in unlinked kick message (Fixes #5667) 2024-02-19 17:01:44 -05:00
Josh Roy 80e6f96c71 Fix format parsing in /msg from Discord (Fixes #5671) 2024-02-19 17:01:44 -05:00
Josh Roy 77d505bb4a Fix format parsing using in-game commands in console (Fixes #5670) 2024-02-19 17:01:44 -05:00
Josh Roy 14125d9c4c Fix format parsing in /spawner (Fixes #5680) 2024-02-19 17:01:44 -05:00
Josh Roy dfc5c49f56 Log individual JUnit test results 2024-02-19 02:07:58 -05:00
Josh Roy 221a6622f3 Fix format parsing in /time (Fixes #5661) 2024-02-19 02:07:58 -05:00
Josh Roy 82606a7ced Fix format parsing in local spy chat format (Fixes #5665) 2024-02-19 02:07:58 -05:00
Josh Roy 1dccfbfcee Fix format parsing in /suicide (Fixes #5662) 2024-02-19 02:07:58 -05:00
Josh Roy ac0ba8890f Fix format parsing in /unlimited 2024-02-19 02:07:58 -05:00
Josh Roy 0cd47639c2 Fix /kill and /suicide on latest Spigot/Paper 2024-02-19 02:07:58 -05:00
MD 63234d6b1d Update Gradle and plugins; don't enforce running tests on Java 8 (#5685)
Fixes CI failures when trying to run `testJava8`, now that Spigot API is
compiled for Java 17.
2024-02-18 16:14:56 +00:00
Josh Roy 8a57856c96 Remove CommonPlaceholders#displayName methods
I18n argument mutation already converts legacy color codes
to parsed MiniMessage. This was redundant and was creating
issues with other parts of the codebase.
2024-02-09 23:31:06 -05:00
Josh Roy 3d9d3a3254 Fix format parsing in /home (Fixes #5653) 2024-02-09 23:31:06 -05:00
Josh Roy 544f69aec8 Fix format parsing in /kit (Fixes #5651) 2024-02-09 23:31:06 -05:00
Josh Roy 596ba6e2f5 Fix format parsing in /ptime (Fixes #5655) 2024-02-09 23:31:06 -05:00
Josh Roy 1e040fa7b5 Fix format parsing in /me (Fixes #5654) 2024-02-09 23:31:06 -05:00
Josh Roy 51075adec6 Fix format parsing in /near (Fixes #5641) 2024-02-09 23:31:06 -05:00
Josh Roy 4134b5fb99 Fix format parsing for command usage strings 2024-02-09 23:31:06 -05:00
Josh Roy d4ecfd2263 Fix component appending in social spy (#5650)
fixes #5649

this time it wasn't me!
2024-02-05 18:15:13 +00:00
MDandJosh Roy 0d1462a021 Fix message display issues from Adventure refactor (#5648)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-02-04 20:31:58 -05:00
Josh Roy 16e297269d Fix several regressions from adventure PR (#5637)
* Fixes /list
* Fixes /baltop
* Fixes Social Spy
* Fixes Essentials Signs
2024-02-04 03:35:37 +00:00
Pantera (Mad_Daniel) 3b61b3ed31 Update adventure-platform-bukkit (Fix 1.20.4) (#5636) 2024-02-04 02:59:49 +00:00
Josh Roy 23093b68d7 Fix placeholders not parsing inside log messages (#5635) 2024-02-03 21:13:18 -05:00
MDandJosh Roy 746627c4b5 Use sendTl to ensure message format is parsed (#5632)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2024-02-04 01:47:20 +00:00
TiagoFar78 04c01655f6 Fix give command for tipped arrow item (#5627) 2024-02-03 17:12:35 -08:00
388d571da9 We're going on an Adventure! (#4717)
https://user-images.githubusercontent.com/10731363/147530817-f9adc58e-18a5-49ed-84c6-106e51d6948f.mp4
Add support for chat components throughout EssentialsX using the Adventure library.  
Translations have been converted to the MiniMessage format, and custom message files will be migrated on startup.  
This also introduces new options to allow players to see messages in their own language and for server owners to change the main message colours without editing message files.

Closes #2029
Closes #2391

---------

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
Co-authored-by: pop4959 <pop4959@gmail.com>
2024-02-03 20:38:14 +00:00
Flask Bot 8234dedb22 New Crowdin updates (#5629) 2024-02-03 16:12:09 +00:00
Flask Bot 81a8731330 New Crowdin updates (#5511) 2024-02-03 15:10:46 +00:00
Charles DeLancey fbfd7e9871 Fix msgToggleCommandUsage1Description (#5614) 2024-01-13 17:16:21 -08:00
Charles DeLanceyandMD 0af4436c2f Update supported versions (#5600)
Remove 1.19.1
Update 1.19.2 -> 1.19.4

closes: #5599

---------

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-12-16 17:35:57 +00:00
Josh Roy 84e9051f46 Update to Minecraft 1.20.4 (#5592) 2023-12-16 15:14:37 +00:00
Josh Royandoop778 bb7b334ace Use WeakReference to store alternative commands (#5572)
Co-authored-by: oop778 <oskardhavel@gmail.com>
2023-11-27 21:29:20 +00:00
Josh Roy c68b277782 Fix trade signs not updating with full inventories (#5574)
Starting with 1.19, sign block states are no longer persistent causing them to become dead after any call to Sign#update. 
To avoid this, ensure we either always get the latest block state or update the block state after any call to Sign#update.
2023-11-10 21:23:10 -05:00
Josh RoyandMD 50c4d04b02 Prevent possible NPE during legacy file conversion (#5551)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-11-10 19:27:06 -05:00
Marcel KwiatkowskiandMD 3eceec87d3 Fix incorrect online count in custom quit message (#5541)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-10-28 22:29:11 +00:00
Josh Roy fdf1875e7f Fix incorrect permission node in /potion (#5552)
Fixes #5549.
2023-10-24 21:24:06 +00:00
Josh Roy 79449ef663 Update to Minecraft 1.20.2 (#5522) 2023-09-22 18:38:57 -04:00
Flask Bot b900444ff0 New Crowdin updates (#5458) 2023-09-07 16:50:34 -04:00
YanisBftandJosh Roy a3a71afcef Add HelpopMessageSentEvent (#5490)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-08-28 18:41:40 -04:00
ThiagoROX 72ba87c509 Prevent /editsign on waxed signs (#5492) 2023-08-28 17:04:15 -04:00
Josh RoyandMD ee1111f6aa Prevent players from ignoring themselves (#5488)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-08-24 22:04:37 +00:00
Josh Roy 42071f4ad4 Lower non bukkit type creation warns amount 2023-08-24 18:02:00 -04:00
Josh Roy cf43e643ed Fix usermap debug message not being debug 2023-08-24 18:02:00 -04:00
Dennis GyftakisandJosh Roy bb2df0b0a4 Add whitelist indicator for /seen and /whois (#5486)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-08-23 15:20:09 -04:00
diademiemiandJosh Roy e3e52db10e Add discord roles blacklist and aliases for chat format (#5157)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-08-08 21:39:12 -04:00
AlexandJosh Roy ad5f17481c Add abbreviation support to /pay command (#5457)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-08-08 21:05:30 +00:00
0a4cf27342 Add ability to use texture url in /skull (#5120)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-08-08 16:10:26 -04:00
SavageAvocadoandJosh Roy dfa22969c9 Add default book title and authors (#4920)
Prevents written books from being invalid if no author/title is provided.

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-08-08 15:46:07 -04:00
JustinandJosh Roy eb76cf0fbb Added option to limit amount of lore lines (#4773)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-08-08 18:46:31 +00:00
Daniel JensenandJosh Roy 409af5d2aa Add /mail clear <player> and /mail clearall commands (#4878)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-08-08 18:03:22 +00:00
Daniel Fiala 6fb500d9fb Fix null check for I18n error logging 2023-08-07 23:40:36 +00:00
YanisBft 2b993d58cf Add custom model data support for kits (#5433) 2023-08-05 19:38:43 -04:00
Josh Roy 7eed853294 Prepare for 2.21.0 dev builds (#5467)
time for an adventure
2023-08-05 19:16:34 +01:00
Josh Roy 6d2a8ffba4 Release 2.20.1 2023-08-05 18:59:32 +01:00
Josh Roy b781c1e924 Add /ess usermap cache sub-command
Allows to view the number of known UUIDs as
well as name to UUID pairs.
2023-08-05 16:48:23 +01:00
Josh Roy 19d6db0b4c Don't cache previously known offline names
This can lead to the potential that a UUID can
be mapped to the improper name. This logic should
be handled the join logic.
2023-08-05 16:48:23 +01:00
Josh Roy 02ced188c8 Always populate last known name into cache
In theory this should never be an issue but
somehow is?
2023-08-05 16:48:23 +01:00
Josh Roy 7f3b62a180 Remove extraneous manual user cache loading
The usermap will already do this on its own,
and we should really avoid doing this outside
the usermap to avoid unforeseen behavior changes.
2023-08-05 16:48:23 +01:00
Josh Roy 908b06570f Remove extraneous offline name update code
This exact code exists in ModernUserMap#getUser(String)
2023-08-05 16:48:23 +01:00
Josh Roy d7e5c10b51 Fix remaining offline visibility checks (#5466)
Purpur added a new self-referencing method that
we don't implement with our stub class. This will
prevent Player#canSee from being called for
offline players (it would be false anyway).
2023-08-05 11:44:40 -04:00
Josh Roy b2ae28b450 Fix discord module console relay not filtering all color codes (#5454) 2023-07-26 14:22:30 -04:00
Josh Roy e558e7365a Fix trade signs not working with currency suffix (#5441) 2023-07-25 21:16:50 +00:00
Josh Roy c32a0f975e Revert duel hand behavior of /book command 2023-07-25 17:10:20 -04:00
Josh Roy ae1f1583c2 Fix enchant not working with offhand 2023-07-25 17:10:20 -04:00
Flask Bot 58eea1724f New Crowdin updates (#5424) 2023-07-23 20:55:40 +00:00
Josh Roy 52c9ca8197 Fix inaccurate online player count for Discord leave message (#5440)
fixes #5256
2023-07-23 15:10:46 +01:00
Josh Roy f26e1b2e29 Fix invalid unlimited items preventing userdata to load
Invalid material names would previously return a null value
when deserializing. This raises an exception from within
Configurate because they use an EnumSet internally during
deserialization which doesn't support null elements.
2023-07-22 22:53:26 +01:00
Josh Roy f84a311c2b Ensure user data deserialization exceptions include file name
More than just a SerializationException can happen I guess
2023-07-22 22:53:26 +01:00
MDandJosh Roy 60129594a6 Rename OfflinePlayer to OfflinePlayerStub (#5417)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-07-04 09:45:58 -04:00
Flask BotandJosh Roy 563d3fab4e New Crowdin updates (#5370)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-07-04 09:36:32 -04:00
1022f7a3ab Add KitPreExpandItemsEvent (#5407)
Co-authored-by: YanisBft <yanis.briffaut@gmail.com>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-07-01 17:51:58 -04:00
Josh Roy d57094c0bc Add link module to /ess dump 2023-07-01 17:45:07 -04:00
Josh Roy 863b9cffc5 Add backoff and skip for Discord console relay 2023-07-01 17:45:07 -04:00
Josh Roy 1921abd310 Shorten logger names for Discord module console relay 2023-07-01 17:45:07 -04:00
Josh Roy 803d800bdd Fix Discord module webhook closing incorrectly 2023-07-01 17:45:07 -04:00
Josh Roy 5276204cf2 Increase Discord module console relay message limit 2023-07-01 17:45:07 -04:00
Josh Roy 697128bcf2 Update Discord Module to JDA 5 2023-07-01 17:45:07 -04:00
Josh RoyandMD a1fa1e38f8 Don't do visibility checks for OfflinePlayers (#5375)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-07-01 16:41:56 -04:00
Flask Bot ed41790712 Update items.json for 1.20 2023-07-01 20:37:21 +01:00
Josh Roy 834ef4ea57 Update to Minecraft 1.20 2023-07-01 20:37:21 +01:00
Josh Roy 03ed9a5b41 Fix roles/groups not being removed instantly upon unlink 2023-06-18 21:57:27 +01:00
Josh Roy d67dd46cdc Clean up link policy code
Also error when new link policy types are added
so we remember to write out the behavior here
2023-06-18 21:57:27 +01:00
Josh Roy 694ed6e66f Use removeAccount return value for AccountLinkManager#unlinkAccount 2023-06-18 21:57:27 +01:00
Josh Roy 8a0526b1e5 Fix discord link data being rewritten when no data was changed 2023-06-18 21:57:27 +01:00
MD a116e8630d Update Gradle, build plugins and GitHub Actions (#5383) 2023-06-14 19:27:01 -04:00
Josh Roy 8b2c7d7ad1 Fix regression in #5378 (#5379) 2023-06-03 23:13:09 +01:00
Josh Roy 8d07c4bb0f Fix regression w/empty strings with KeywordReplacer (#5378) 2023-06-02 22:54:30 +00:00
Josh Roy fc3dabf8f5 Fix keywords in kit commands not working (#5377)
Don't process b64 item through keyword replacer
and don't replace spaces on command strings
2023-06-01 21:14:41 -04:00
Josh Roy cdd277da92 Fix config BigDecimal parsing returning null on error (#5373)
Also add some more exception logging for parse issues.
2023-05-31 19:46:19 -04:00
Charles DeLancey 0d322e2c6b Fix typo in Discord docs (#5371) 2023-05-30 07:46:09 -04:00
646b60a90b Add ability to get recipe of item in hand (#5346)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
Co-authored-by: pop4959 <pop4959@gmail.com>
2023-05-28 18:27:18 +00:00
Flask Bot 357cd6b246 New Crowdin updates (#5357) 2023-05-27 02:00:11 +00:00
Josh Roy a4fb02f3e6 Don't run AntiBuild checks on NPCs (#5365) 2023-05-26 21:54:46 -04:00
Josh Roy 2828901927 Fix variables in kits having underscores replaced with spaces (#5366) 2023-05-27 01:51:11 +00:00
Josh Roy 1e0f7cb984 Fix question and shout prefixes with toggle shout (#5367) 2023-05-26 21:48:10 -04:00
Josh Roy 52a4dd2c60 Fix NPE during first join for LuckPerms contexts (#5364) 2023-05-26 19:29:37 -04:00
Josh Roy 8323b47e40 Prepare for 2.20.1 dev builds (#5355) 2023-05-23 11:32:12 -04:00
Josh Roy 14cdde009c Release 2.20.0 (#5336) 2023-05-23 11:18:21 -04:00
Flask Bot 1883e6adbc New Crowdin updates (#5348) 2023-05-22 00:49:43 +00:00
Josh Roy 2f9659e7fc Fix NPE during reload when deleted warps directory (#5353) 2023-05-20 10:03:24 -07:00
Josh Roy 1a4f63915f Fix SignChangeEvent never actually being called (#5341)
Fixes #5340. Caused by a regression in #5304 which reverted the behaviour introduced in #5304.
2023-05-09 00:26:28 +01:00
Flask Bot dd3f4c2921 New Crowdin updates (#5334) 2023-05-07 04:57:44 +00:00
Josh Roy 7ebb6359e5 Fix offline player consumer returning null users (#5339) 2023-05-06 19:13:44 -04:00
Josh Roy 069ebfcd5b Fix NPE while syncing offline users for discord line module (#5338)
Only would happen for users who haven't joined
(or been loaded by different parts of the plugin)
since the last restart. This change first of all switches to a method to fetch users which will update the User base to the UUIDPlayer dummy base. Secondly, this change will not update the base of a User to a UUIDPlayer dummy unless the base is currently null (which would be the case in the condition described above).
2023-05-06 18:31:23 -04:00
pop4959 991bc61b0c Expire UserMap cache more aggressively and add cache debugging (#5331) 2023-05-05 18:44:22 -04:00
Flask Bot 8194d1f747 New Crowdin updates (#5303) 2023-05-05 06:40:58 +00:00
Josh RoyandMD ee7ea2ae39 Fire SignChangeEvent in /editsign (#5304)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-05-02 16:13:50 -04:00
Josh Roy 3d23916ad5 Add HomeModifyEvent for home create/delete/rename/set (#5216)
Implements #5213.
2023-05-02 20:48:28 +01:00
Sasha SorokinandJosh Roy e5b0c4c855 Add support for local and global chats in Discord (#4684)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-04-30 21:30:11 -04:00
Josh RoyandMD 84fd45bd09 Bump JDA 4 version (#5328)
Bumping this for 2.20 release as  JDA 5 still not out of beta

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-04-29 18:14:36 +00:00
Josh Roy 01883a6b18 Truncate command and argument descriptions (#5314)
Fixes #5315.
2023-04-29 19:00:40 +01:00
Josh Roy fd49b70a69 Fix top command not sending messages (#5302)
my three year old refactors still haunt me
2023-04-13 06:22:47 +00:00
Josh Roy 3a737480ec Remove unneeded sanitization in webhook names (#5311)
Regression from 384f63bf92
2023-04-13 07:15:48 +01:00
diademiemiandJosh Roy 384f63bf92 Allow configuring Discord webhook name (#5159)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-04-10 00:27:36 +00:00
Joel OteroandJosh Roy 77dc87bb8e Add /bottom command (#5292)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2023-04-09 21:05:53 +00:00
MD c716fc8236 Bump runServer task to Paper 1.19.4 (#5269) 2023-04-06 00:15:15 +00:00
Flask Bot c52c1fea9d New Crowdin updates (#5244) 2023-04-04 21:49:43 -07:00
Josh Roy e7c56b32a4 Fix sulk sensors detecting vanished players (#5262) 2023-04-02 22:29:29 +00:00
Josh Roy 11e6d3ca7b Fix /mail sendall not working (#5263) 2023-04-02 22:12:04 +00:00
Josh Roy 75481925f7 Update display names before formatting chat messages (#5282) 2023-04-02 22:08:38 +00:00
Josh Roy ecb1850451 Add message for unknown last location for /tpo (#5290) 2023-04-02 22:04:36 +00:00
Josh Roy 0f4af9e735 Use strict comparison for potions in trade signs (#5260) 2023-04-02 21:45:46 +00:00
Josh RoyandMD bf14b88600 Bump to 1.19.4 (#5273)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-03-17 18:54:33 +00:00
Josh Roy 43d84de2b2 Move feature requests back to issues (#5251)
* Create request-a-feature.yml

* Update config.yml
2023-02-17 12:11:21 +00:00
25ad8ac9bd Add per-command command cooldown bypass permissions (#4759)
Adds the permission `essentials.commandcooldowns.bypass.<command name>` to bypass the cooldown for a specific command.

Closes #4747.

Co-authored-by: pop4959 <pop4959@gmail.com>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-02-13 23:19:52 +00:00
17051eab73 Add separate events for local and global chat (#4683)
This commit adds two new events: GlobalChatEvent and LocalChatEvent,
which allow other plugin developers to know whether the message sent is
a global or local one and act accordingly. If either of those events is
cancelled, then the source event is cancelled too.

Since all chat-related events share the same structure, a new abstract
class ChatEvent is created with change made for LocalChatSpyEvent to use
it without breaking its API.

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2023-02-13 23:15:15 +00:00
Flask Bot 4dc994df3c New Crowdin updates (#5051) 2023-02-05 04:58:49 -08:00
Warrior 996285c0e3 Fix doubles not working in console for /tppos (#5231)
Closes https://github.com/EssentialsX/Essentials/issues/5230
2023-01-22 16:32:47 -05:00
Josh Roy 312d1699a8 Use metadata for tnt from /nuke (#5222)
Fixes https://github.com/EssentialsX/Essentials/issues/5219
2023-01-12 10:51:13 -05:00
Josh Roy b3238605cc Fix inventory size for 1.8.8 servers (#5212) 2023-01-05 19:13:16 +00:00
Josh Roy b7a4bea52e Revert dual hand behavior from /hat (#5205)
Fixes https://github.com/EssentialsX/Essentials/issues/5202
2022-12-30 18:30:52 -05:00
Josh Roy ca71d93963 Fix discord permission issues with link module (#5197) 2022-12-27 21:07:47 +00:00
Josh Roy 0936fe80bd Discord Link Module 2022-12-26 01:24:17 +00:00
Josh Roy 520e8f991f Add DiscordService#getInviteUrl 2022-12-26 01:24:17 +00:00
Josh Roy 3131cadf65 Add UserMailEvent 2022-12-26 01:24:17 +00:00
Josh Roy bfae6c5c69 Add IPermissionsHandler#addToGroup & IPermissionsHandler#removeFromGroup 2022-12-26 01:24:17 +00:00
Josh Roy 52c500ee6d Add InteractionRole#getAsMention + InteractionRole#canInteract 2022-12-26 01:24:17 +00:00
Josh Roy ba5a8becfc Add support for role arguments in slash commands 2022-12-26 01:24:17 +00:00
Josh Roy 2a1957229f Add InteractionMember#hasRole convince methods 2022-12-26 01:24:17 +00:00
Josh Roy e6af246170 Add IPermissionsHandler#getGroups to fetch all groups 2022-12-26 01:24:17 +00:00
Josh Roy 939e5c5a55 Add InteractionRole#isPublicRole 2022-12-26 01:24:17 +00:00
Josh Roy 80bbf8c55b Add DiscordService#getRole & DiscordService#modifyMemberRoles 2022-12-26 01:24:17 +00:00
Josh Roy 376abc4aab Add DiscordService#getMemberById 2022-12-26 01:24:17 +00:00
Josh Roy a7f4d6b16b Add InteractionMember#getAsMention 2022-12-26 01:24:17 +00:00
Josh Roy e0d6040662 Add IUser#isFreeze & IUser#setFreeze 2022-12-26 01:24:17 +00:00
Josh Roy 6ec4c97ddd Rewrite Inventory Handling (#5021)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

Complete refactor of inventory handling across the plugin. This simplifies various commands, and aims to resolve issues aon various different MC versions and inconsistencies between parts of the plugin.

Fixes https://github.com/EssentialsX/Essentials/issues/3810
Fixes https://github.com/EssentialsX/Essentials/issues/4248
2022-12-24 17:58:51 +00:00
diademiemiandJosh Roy db10678095 Add first join message type for Discord (#5160)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>

Add a new message type for when a player has joined for the first time.
2022-12-24 14:59:00 +00:00
Josh Roy 582a357752 Bump to 1.19.3 (#5179) 2022-12-07 21:47:13 -05:00
Bobcat00andBobcat00 8a1e7019a7 Fix incorect play time in /whois (#5177)
Co-authored-by: Bobcat00 <Bobcat00@users.noreply.github.com>
2022-12-06 09:02:20 -05:00
Josh Roy 4414eea513 Close discord webhook clients on shutdown (#5165) 2022-11-27 19:29:36 +00:00
Josh Roy 6e931b2b3b Fix ConsoleInjector not getting reinjecting on reload (#5149) 2022-11-11 15:28:55 -08:00
Warrior 22a0d53cf2 Improve uuid cache load times (#5143) 2022-11-03 20:14:18 -04:00
20011b9996 Added permission node for Nickname Prefix bypass (#4995)
Allow a permission to determine whether the nickname prefix gets applied to a group or user. It also removes a statement that is not true in the essentials config, the nickname prefix is NOT included in the nickname max length.

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2022-10-25 07:44:05 +01:00
879e70fb6b Ignore NPCs in JailListener (#5130)
Co-authored-by: pop4959 <pop4959@gmail.com>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

Citizens NPCs cannot be jailed. This change prevents unnecessary lookups of NPC users.
2022-10-20 14:19:22 -04:00
Josh Royandpop4959 f91dfed5b4 Improve PlayerTarget code (#5131)
Co-authored-by: pop4959 <pop4959@gmail.com>
2022-10-15 00:41:21 +00:00
Josh Roy 767185ec32 Auto complete /mail clear indices (#5132) 2022-10-15 00:32:47 +00:00
Noah van der AaandJosh Roy 4aa93a43db Add mutable "viewers" list to DiscordRelayEvent (#5080)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2022-10-14 22:26:01 +00:00
Lax 57529c4b7d Update Bukkit wiki links in config (#5094) 2022-10-14 22:15:35 +00:00
MDandJosh Roy dc7fb91939 Make usermap non-player warnings configurable (#5125)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2022-10-11 01:06:54 +00:00
Josh Roy e91ce0c44c Fix Essentials#getUser not caching newly created NPC users (#5129) 2022-10-11 00:36:33 +00:00
Nassim Jahnke f7cbc7b0d3 Add renamehome command (#5121) 2022-10-04 08:40:18 -04:00
MD 28d7abe642 Close any open inventories before showing shapeless recipes (#5122) 2022-10-02 18:43:13 -04:00
0ca58ce4ba Rewrite User storage and UUID cache (#4581)
Co-authored-by: triagonal <10545540+triagonal@users.noreply.github.com>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2022-09-09 15:49:43 -04:00
Josh Roy d75d3cd001 Bump to 2.20 dev
Fourth time's a charm
2022-09-09 15:49:40 -04:00
MD 1eb5d8a459 Use Netlify CLI directly to deploy Javadocs 2022-09-05 18:46:09 +01:00
Josh Roy b808511efe Update discord bot authorization tutorial (#5098) 2022-09-02 09:43:09 -04:00
MD 0897ef71f2 Bump to 2.19.8 dev (#5085) 2022-08-22 10:17:33 -04:00
Josh Roy f176893940 Release 2.19.7 2022-08-21 16:48:14 -04:00
Josh Roy af18afad7a Fix discord url link message (#5079) 2022-08-18 20:42:57 -07:00
Josh Roy bdc5f4c830 Fix baltop not working in command blocks (#5078) 2022-08-18 18:37:15 +00:00
Josh Roy 0be056b9c2 Improve error messages in Backup 2022-08-18 14:14:29 -04:00
Josh Roy 5f5d65c463 Fix null log messages causing an NPE 2022-08-18 14:14:29 -04:00
Josh Roy ca4077b204 Fix rgb color codes getting stripped in some cases (#5076) 2022-08-18 17:29:12 +00:00
Josh Roy 4ab3e20a0c Fix remaining uses of Bukkit's logger (#5075)
Fixes #5056
2022-08-18 17:35:53 +01:00
Josh Roy 576866540e Fix gson incompatibility on 1.8.8 (#5067) 2022-08-17 06:52:38 +00:00
Josh Roy dfcb9a1c52 Update Checkstyle IDEA config (#5047) 2022-08-16 08:18:12 +00:00
Josh Roy a7a9cbc034 Bump down to 2.19.7 (#5064)
Never ending cycle
2022-08-15 14:26:52 +01:00
MDandJosh Roy 2f4f555923 Fix issues with chat preview refactor (#5062)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2022-08-14 18:49:02 -04:00
MD 1c22edbb1b Prevent players opening their own inventories in /invsee (#5061) 2022-08-14 16:31:23 -04:00
Josh Roy b71fb520aa Prepare for 2.20.0 dev builds 2022-08-12 22:30:18 +01:00
579 changed files with 59560 additions and 37365 deletions

No files matched your search

+1 -4
View File
@@ -7,14 +7,11 @@ contact_links:
url: https://github.com/EssentialsX/Essentials/discussions/categories/q-a
about: Visit the Q&A forum to search for previous help requests.
- name: Check past feature suggestions
url: https://github.com/EssentialsX/Essentials/discussions/categories/ideas-and-feature-suggestions
url: https://github.com/EssentialsX/Essentials/issues?q=is%3Aissue+label%3A%22type%3A+enhancement%22
about: Visit the Feature Suggestions forum to view and discuss existing feature suggestions.
- name: Create help request
url: https://github.com/EssentialsX/Essentials/discussions/new?category=q-a
about: Create a support ticket to get help from developers.
- name: Suggest a feature
url: https://github.com/EssentialsX/Essentials/discussions/new?category=ideas-and-feature-suggestions
about: Suggest new features for EssentialsX!
- name: Get help from the community on Discord
url: https://discord.gg/casfFyh
about: Join the MOSS Discord for community-powered EssentialsX support!
+10 -6
View File
@@ -1,6 +1,7 @@
name: Report bug
description: Report a bug in EssentialsX.
labels: 'bug: unconfirmed'
type: Bug
body:
- type: markdown
attributes:
@@ -31,8 +32,8 @@ body:
- type: input
attributes:
label: Error log (if applicable)
description: If you are reporting a console error, upload any relevant log excerpts to either https://paste.gg or https://gist.github.com, save and the paste the link in this box. If you included those files in the same paste as your startup log, paste the same link here.
placeholder: "Example: https://paste.gg/p/anonymous/109dd6a10a734a3aa430d5a351ea5210"
description: If you are reporting a console error, upload any relevant log excerpts to either https://pastes.dev or https://gist.github.com, save and the paste the link in this box. If you included those files in the same paste as your startup log, paste the same link here.
placeholder: "Example: https://pastes.dev/UYL3ASLsee"
- type: textarea
attributes:
@@ -70,10 +71,13 @@ body:
validations:
required: true
- type: markdown
- type: textarea
attributes:
value: |
In the text box below, you can attach any relevant screenshots, files and links to Timings/spark profiler reports.
label: Additional Information
description: |
In this box, you can attach any relevant screenshots, files and links to Timings/spark profiler reports.
You can also include a link to a heapdump if necessary, but please make sure you don't include any private player data in the heapdump.
If you suspect this issue is related to a prior issue/PR/commit, please mention it here.
validations:
required: false
@@ -0,0 +1,41 @@
name: Request a feature
description: Suggest a feature you want to see in EssentialsX!
labels: 'type: enhancement'
type: Feature
body:
- type: markdown
attributes:
value: |
If you have a feature suggestion for EssentialsX, read the following tips:
1. Fill out the template.
This will help us understand what you're requesting and why you want us
to add it.
2. Keep it simple.
Make sure it's easy to understand what you're requesting. A good way is
to keep it to one request per GitHub issue, as we can then easily track
feature requests.
3. Check whether it has already been asked or added.
You can search the issue tracker to see if your feature has already been
requested at https://github.com/EssentialsX/Essentials/issues. You can
also check the changelogs to see if the feature was recently added:
https://github.com/EssentialsX/Essentials/releases
4. Ask yourself: "Does this belong in EssentialsX?"
There are lots of features that we reject because most servers won't
need or use them. If your feature is very specific or already exists in
another plugin, it might not be a good fit for EssentialsX.
- type: textarea
attributes:
label: Feature description
description: Explain what you should expect to happen.
placeholder: |
What feature are you suggesting?
validations:
required: true
- type: textarea
attributes:
label: How the feature is useful
description: Explain what actually happens.
placeholder: |
How is the feature useful to players, server owners and/or developers?
validations:
required: true
+97 -21
View File
@@ -5,6 +5,9 @@ on:
branches:
- 2.x
- dev/*
release:
types:
- published
jobs:
build:
@@ -13,38 +16,90 @@ jobs:
steps:
- name: Checkout Git repo
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Restore Gradle cache
uses: actions/cache@v2
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
distribution: 'temurin'
java-version: 17
- name: Set up JDK 16
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 16
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew build --stacktrace
- name: Publish JUnit report
uses: mikepenz/action-junit-report@v5
if: success() || failure() # Run even if the previous step fails
with:
report_paths: '**/build/test-results/test*/TEST-*.xml'
- name: Archive plugin jars on GitHub
uses: actions/upload-artifact@master
uses: actions/upload-artifact@v7
with:
name: EssentialsX plugin jars
path: jars/
- name: Upload EssentialsX jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsX-*.jar
archive: false
- name: Upload EssentialsXAntiBuild jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXAntiBuild-*.jar
archive: false
- name: Upload EssentialsXChat jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXChat-*.jar
archive: false
- name: Upload EssentialsXDiscord jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXDiscord-*.jar
archive: false
- name: Upload EssentialsXDiscordLink jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXDiscordLink-*.jar
archive: false
- name: Upload EssentialsXGeoIP jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXGeoIP-*.jar
archive: false
- name: Upload EssentialsXProtect jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXProtect-*.jar
archive: false
- name: Upload EssentialsXSpawn jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXSpawn-*.jar
archive: false
- name: Upload EssentialsXXMPP jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXXMPP-*.jar
archive: false
- name: Deploy to Maven repo
if: ${{ success() && github.event_name == 'push' && github.repository == 'EssentialsX/Essentials' && github.ref == 'refs/heads/2.x' }}
env:
@@ -54,22 +109,43 @@ jobs:
./gradlew publish
- name: Prepare Javadocs
if: ${{ success() && github.event_name == 'push' && github.repository == 'EssentialsX/Essentials' && github.ref == 'refs/heads/2.x' }}
run: |
mv Essentials/build/docs/javadoc/ javadocs/
cp -r EssentialsAntiBuild/build/docs/javadoc/ javadocs/EssentialsAntiBuild/
cp -r EssentialsChat/build/docs/javadoc/ javadocs/EssentialsChat/
cp -r EssentialsDiscord/build/docs/javadoc/ javadocs/EssentialsDiscord/
cp -r EssentialsDiscordLink/build/docs/javadoc/ javadocs/EssentialsDiscordLink/
cp -r EssentialsGeoIP/build/docs/javadoc/ javadocs/EssentialsGeoIP/
cp -r EssentialsProtect/build/docs/javadoc/ javadocs/EssentialsProtect/
cp -r EssentialsSpawn/build/docs/javadoc/ javadocs/EssentialsSpawn/
cp -r EssentialsXMPP/build/docs/javadoc/ javadocs/EssentialsXMPP/
- name: Deploy Javadocs
if: ${{ success() && github.event_name == 'push' && github.repository == 'EssentialsX/Essentials' && github.ref == 'refs/heads/2.x' }}
uses: netlify/actions/cli@master
- name: Archive Javadocs
uses: actions/upload-artifact@v7
with:
args: deploy --dir=javadocs/ --prod --message="GitHubActionsDeploy"
name: javadocs
path: javadocs/
publish-jd:
name: Publish Javadocs
needs: build
if: ${{ github.event_name == 'push' && github.repository == 'EssentialsX/Essentials' && github.ref == 'refs/heads/2.x' }}
runs-on: ubuntu-latest
steps:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Download Javadocs
uses: actions/download-artifact@v8
with:
name: javadocs
path: javadocs/
- name: Deploy Javadocs
run: npx netlify-cli deploy --dir=javadocs/ --prod --message="GitHubActionsDeploy"
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_JD_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_JD_2X_SITE_ID }}
+73 -16
View File
@@ -10,6 +10,11 @@ on:
- 2.x
- mc/*
- dev/*
merge_group:
types: [checks_requested]
permissions:
checks: write
jobs:
build:
@@ -18,34 +23,86 @@ jobs:
steps:
- name: Checkout Git repo
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Restore Gradle cache
uses: actions/cache@v2
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
distribution: 'temurin'
java-version: 17
- name: Set up JDK 16
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 16
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew build --stacktrace
- name: Publish JUnit report
uses: mikepenz/action-junit-report@v4
if: success() || failure() # Run even if the previous step fails
with:
report_paths: '**/build/test-results/test*/TEST-*.xml'
- name: Archive plugin jars on GitHub
uses: actions/upload-artifact@master
uses: actions/upload-artifact@v7
with:
name: EssentialsX plugin jars
path: jars/
- name: Upload EssentialsX jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsX-*.jar
archive: false
- name: Upload EssentialsXAntiBuild jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXAntiBuild-*.jar
archive: false
- name: Upload EssentialsXChat jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXChat-*.jar
archive: false
- name: Upload EssentialsXDiscord jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXDiscord-*.jar
archive: false
- name: Upload EssentialsXDiscordLink jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXDiscordLink-*.jar
archive: false
- name: Upload EssentialsXGeoIP jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXGeoIP-*.jar
archive: false
- name: Upload EssentialsXProtect jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXProtect-*.jar
archive: false
- name: Upload EssentialsXSpawn jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXSpawn-*.jar
archive: false
- name: Upload EssentialsXXMPP jar
uses: actions/upload-artifact@v7
with:
path: jars/EssentialsXXMPP-*.jar
archive: false
+105
View File
@@ -0,0 +1,105 @@
name: Mirror EssentialsX dumps to Gist
on:
issues:
types: [opened, edited, closed]
issue_comment:
types: [created]
permissions:
issues: write
contents: read
env:
DUMP_REGEX: 'https?://essentialsx\.net/dump\?bytebin=([A-Za-z0-9_-]+)'
GIST_LINK_FMT: 'https://essentialsx.net/dump?gist='
jobs:
handle-dump:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- name: Mirror dump to a private Gist
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GIST_TOKEN }}
script: |
const issue = context.payload.issue ?? context.payload.comment.issue;
const bodySrc = context.payload.comment ? context.payload.comment.body : issue.body || '';
const regex = new RegExp(process.env.DUMP_REGEX);
const m = bodySrc.match(regex);
if (!m) {
core.info('No EssentialsX dump link found – abort.');
return;
}
const key = m[1];
const dump = await fetch(`https://api.pastes.dev/${key}`);
if (!dump.ok) throw new Error(`pastes.dev fetch failed (${dump.status})`);
const text = await dump.text();
const { data: gist } = await github.rest.gists.create({
files: { [`${key}.txt`]: { content: text } },
description: `EssentialsX dump for issue #${issue.number}`,
public: false
});
const link = `${process.env.GIST_LINK_FMT}${gist.id}`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `📋 Essentials Dump Backup → ${link}`
});
cleanup-gist:
if: github.event_name == 'issues' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Delete gist and hide bot comment
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GIST_TOKEN }}
script: |
const issue = context.payload.issue;
const comments = await github.paginate(
github.rest.issues.listComments,
{ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, per_page: 100 }
);
const re = /https?:\/\/essentialsx\.net\/dump\?gist=([A-Za-z0-9]+)/;
const hit = comments.reverse().find(c => re.test(c.body || ''));
if (!hit) {
core.info('No gist link comment found – nothing to clean up.');
return;
}
const gistId = (hit.body.match(re))[1];
const commentNodeId = hit.node_id;
try {
await github.request('DELETE /gists/{gist_id}', { gist_id: gistId });
core.info(`Deleted Gist ${gistId}`);
} catch (err) {
core.warning(`Could not delete Gist ${gistId}: ${err.message}`);
}
try {
await github.graphql(
`mutation($id:ID!){
minimizeComment(input:{subjectId:$id, classifier:OUTDATED}) {
minimizedComment { id }
}
}`,
{ id: commentNodeId }
);
core.info(`Minimised comment ${hit.id} as OUTDATED`);
} catch (err) {
core.warning(`Could not minimise comment: ${err.message}`);
}
+43
View File
@@ -0,0 +1,43 @@
name: Command and Permissions Data
on:
push:
branches:
- 2.x
jobs:
data:
name: Generate and Upload
runs-on: ubuntu-latest
steps:
- name: Checkout Git Repo
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Generate Data
run: |
chmod +x gradlew
./gradlew commandData
./gradlew versionData
- name: Upload Data
uses: ryand56/r2-upload-action@v1
with:
r2-account-id: ${{ secrets.R2_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
r2-bucket: ${{ secrets.R2_BUCKET }}
source-dir: generated
destination-dir: ./
+7
View File
@@ -15,14 +15,21 @@
/Essentials/kits.yml
/Essentials/userdata/testplayer1.yml
/Essentials/usermap.csv
/Essentials/usermap.bin
/Essentials/uuids.bin
# Build files
.gradle/
/jars/
generated/
out/
build/
target/
*.class
build-logic/.kotlin/
build-logic/bin/
bin/
test-tmp/
# Run directory
/run/
+22 -14
View File
@@ -1,19 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CheckStyle-IDEA">
<option name="configuration">
<map>
<entry key="active-configuration" value="PROJECT_RELATIVE:$PROJECT_DIR$/.checkstyle/checkstyle.xml:EssentialsX" />
<entry key="checkstyle-version" value="8.36.2" />
<entry key="copy-libs" value="true" />
<entry key="location-0" value="BUNDLED:(bundled):Sun Checks" />
<entry key="location-1" value="BUNDLED:(bundled):Google Checks" />
<entry key="location-2" value="PROJECT_RELATIVE:$PROJECT_DIR$/.checkstyle/checkstyle.xml:EssentialsX" />
<entry key="property-2.configDirectory" value=".checkstyle/" />
<entry key="scan-before-checkin" value="true" />
<entry key="scanscope" value="JavaOnly" />
<entry key="suppress-errors" value="false" />
</map>
<component name="CheckStyle-IDEA" serialisationVersion="2">
<checkstyleVersion>8.36.2</checkstyleVersion>
<scanScope>JavaOnly</scanScope>
<copyLibs>true</copyLibs>
<scanBeforeCheckin>true</scanBeforeCheckin>
<option name="thirdPartyClasspath" />
<option name="activeLocationIds">
<option value="321e5753-4201-4d2c-bf31-6232011122fd" />
</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="321e5753-4201-4d2c-bf31-6232011122fd" type="PROJECT_RELATIVE" scope="All" description="EssentialsX">
$PROJECT_DIR$/.checkstyle/checkstyle.xml
<option name="properties">
<map>
<entry key="configDirectory" value=".checkstyle/" />
</map>
</option>
</ConfigurationLocation>
</list>
</option>
</component>
</project>
+59 -11
View File
@@ -3,32 +3,47 @@ plugins {
}
dependencies {
compileOnly('com.github.milkbowl:VaultAPI:1.7') {
compileOnly('net.essentialsx.deps:VaultAPI:1.7') {
exclude group: "org.bukkit", module: "bukkit"
}
compileOnly 'net.luckperms:api:5.0'
compileOnly 'net.luckperms:api:5.3'
api 'io.papermc:paperlib:1.0.6'
api 'org.bstats:bstats-bukkit:2.2.1'
implementation 'org.spongepowered:configurate-yaml:4.2.0'
implementation 'org.checkerframework:checker-qual:3.49.0'
implementation 'nu.studer:java-ordered-properties:1.0.4'
implementation 'org.spongepowered:configurate-yaml:4.1.2'
implementation 'org.checkerframework:checker-qual:3.14.0'
implementation 'net.kyori:adventure-api:4.25.0'
implementation 'net.kyori:adventure-text-minimessage:4.25.0'
implementation 'net.kyori:adventure-platform-bukkit:4.4.1'
// Providers
api project(':providers:BaseProviders')
api project(':providers:PaperProvider')
api(project(':providers:BaseProviders')) {
exclude(module: 'spigot-api')
}
api(project(path: ':providers:PaperProvider', configuration: 'shadow')){
exclude(module: 'paper-api')
}
api(project(':providers:NMSReflectionProvider')) {
exclude group: "org.bukkit", module: "bukkit"
exclude(module: 'bukkit')
}
api(project(':providers:1_8Provider')) {
exclude group: "org.spigotmc", module: "spigot"
exclude(module: 'spigot-api')
}
api(project(':providers:1_12Provider')) {
exclude group: "org.bukkit", module: "bukkit"
exclude(module: 'bukkit')
}
}
test {
testLogging.showStandardStreams = true
}
tasks.register('versionData', VersionDataTask) {
destination.set(rootProject.layout.projectDirectory.dir("generated").file("version-data.json"))
}
shadowJar {
dependencies {
include (dependency('io.papermc:paperlib'))
@@ -39,11 +54,39 @@ shadowJar {
include (dependency('org.yaml:snakeyaml'))
include (dependency('io.leangen.geantyref:geantyref'))
include (dependency('org.checkerframework:checker-qual'))
include (dependency('nu.studer:java-ordered-properties'))
include (dependency('net.kyori:adventure-api'))
include (dependency('net.kyori:adventure-key'))
include (dependency('net.kyori:examination-api'))
include (dependency('net.kyori:examination-string'))
include (dependency('net.kyori:option'))
include (dependency('net.kyori:adventure-platform-bukkit'))
include (dependency('net.kyori:adventure-platform-api'))
include (dependency('net.kyori:adventure-platform-facet'))
include (dependency('net.kyori:adventure-nbt'))
include (dependency('net.kyori:adventure-text-serializer-bungeecord'))
include (dependency('net.kyori:adventure-text-serializer-gson'))
include (dependency('net.kyori:adventure-text-serializer-gson-legacy-impl'))
include (dependency('net.kyori:adventure-text-serializer-json'))
include (dependency('net.kyori:adventure-text-serializer-json-legacy-impl'))
include (dependency('net.kyori:adventure-text-serializer-legacy'))
include (dependency('net.kyori:adventure-text-minimessage'))
include (project(':providers:BaseProviders'))
include (project(':providers:PaperProvider'))
include (project(path: ':providers:PaperProvider', configuration: 'shadow'))
include (project(':providers:NMSReflectionProvider'))
include (project(':providers:1_8Provider'))
include (project(':providers:1_12Provider'))
// Paper in-dev adventure versions (for snapshot/pre-releases)
include (dependency('io.papermc.adventure:adventure-api'))
include (dependency('io.papermc.adventure:adventure-key'))
include (dependency('io.papermc.adventure:adventure-nbt'))
include (dependency('io.papermc.adventure:adventure-text-serializer-gson'))
include (dependency('io.papermc.adventure:adventure-text-serializer-gson-legacy-impl'))
include (dependency('io.papermc.adventure:adventure-text-serializer-json'))
include (dependency('io.papermc.adventure:adventure-text-serializer-json-legacy-impl'))
include (dependency('io.papermc.adventure:adventure-text-serializer-legacy'))
include (dependency('io.papermc.adventure:adventure-text-minimessage'))
}
relocate 'io.papermc.lib', 'com.earth2me.essentials.paperlib'
relocate 'org.bstats', 'com.earth2me.essentials.libs.bstats'
@@ -51,8 +94,13 @@ shadowJar {
relocate 'org.yaml.snakeyaml', 'com.earth2me.essentials.libs.snakeyaml'
relocate 'io.leangen.geantyref', 'com.earth2me.essentials.libs.geantyref'
relocate 'org.checkerframework', 'com.earth2me.essentials.libs.checkerframework'
relocate 'net.kyori', 'com.earth2me.essentials.libs.kyori'
relocate 'net.essentialsx.temp.adventure', 'net.kyori.adventure' // Undo temporary relocation for Paper Provider
minimize {
include(dependency('org.checkerframework:checker-qual'))
include(dependency('net.kyori:adventure-api'))
include(dependency('net.kyori:adventure-platform-bukkit'))
include(dependency('net.kyori:adventure-text-minimessage'))
}
}
@@ -1,9 +1,12 @@
package com.earth2me.essentials;
import java.util.stream.Collectors;
import net.ess3.provider.KnownCommandsProvider;
import org.bukkit.command.Command;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.plugin.Plugin;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -13,7 +16,7 @@ import java.util.Map;
import java.util.logging.Level;
public class AlternativeCommandsHandler {
private final transient Map<String, List<Command>> altcommands = new HashMap<>();
private final transient Map<String, List<WeakReference<Command>>> altCommands = new HashMap<>();
private final transient Map<String, String> disabledList = new HashMap<>();
private final transient IEssentials ess;
@@ -30,41 +33,72 @@ public class AlternativeCommandsHandler {
if (plugin.getDescription().getMain().contains("com.earth2me.essentials") || plugin.getDescription().getMain().contains("net.essentialsx")) {
return;
}
for (final Map.Entry<String, Command> entry : getPluginCommands(plugin).entrySet()) {
for (final Map.Entry<String, Command> entry : getPluginCommands(plugin)) {
final String[] commandSplit = entry.getKey().split(":", 2);
final String commandName = commandSplit.length > 1 ? commandSplit[1] : entry.getKey();
final Command command = entry.getValue();
final List<Command> pluginCommands = altcommands.computeIfAbsent(commandName.toLowerCase(Locale.ENGLISH), k -> new ArrayList<>());
final List<WeakReference<Command>> pluginCommands = altCommands.computeIfAbsent(commandName.toLowerCase(Locale.ENGLISH), k -> new ArrayList<>());
boolean found = false;
for (final Command pc2 : pluginCommands) {
final Iterator<WeakReference<Command>> pluginCmdIterator = pluginCommands.iterator();
while (pluginCmdIterator.hasNext()) {
final Command cmd = pluginCmdIterator.next().get();
if (cmd == null) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "Essentials: Alternative command for " + commandName + " removed due to garbage collection");
}
pluginCmdIterator.remove();
continue;
}
// Safe cast, everything that's added comes from getPluginCommands which already performs the cast check.
if (((PluginIdentifiableCommand) pc2).getPlugin().equals(plugin)) {
if (((PluginIdentifiableCommand) cmd).getPlugin().equals(plugin)) {
found = true;
break;
}
}
if (!found) {
pluginCommands.add(command);
pluginCommands.add(new WeakReference<>(command));
}
}
}
private Map<String, Command> getPluginCommands(Plugin plugin) {
private List<Map.Entry<String, Command>> getPluginCommands(Plugin plugin) {
final Map<String, Command> commands = new HashMap<>();
for (final Map.Entry<String, Command> entry : ess.getKnownCommandsProvider().getKnownCommands().entrySet()) {
for (final Map.Entry<String, Command> entry : ess.provider(KnownCommandsProvider.class).getKnownCommands().entrySet()) {
if (entry.getValue() instanceof PluginIdentifiableCommand && ((PluginIdentifiableCommand) entry.getValue()).getPlugin().equals(plugin)) {
commands.put(entry.getKey(), entry.getValue());
}
}
return commands;
// Try to use non-namespaced commands first if we can, some Commands may not like being registered under a
// different label than their getName() returns, so avoid doing that when we can
return commands.entrySet().stream().sorted((o1, o2) -> {
if (o1.getKey().contains(":") && !o2.getKey().contains(":")) {
return 1;
} else if (!o1.getKey().contains(":") && o2.getKey().contains(":")) {
return -1;
}
return 0;
}).collect(Collectors.toList());
}
public void removePlugin(final Plugin plugin) {
final Iterator<Map.Entry<String, List<Command>>> iterator = altcommands.entrySet().iterator();
final Iterator<Map.Entry<String, List<WeakReference<Command>>>> iterator = altCommands.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<String, List<Command>> entry = iterator.next();
entry.getValue().removeIf(pc -> !(pc instanceof PluginIdentifiableCommand) || ((PluginIdentifiableCommand) pc).getPlugin().equals(plugin));
final Map.Entry<String, List<WeakReference<Command>>> entry = iterator.next();
final Iterator<WeakReference<Command>> commands = entry.getValue().iterator();
while (commands.hasNext()) {
final Command pc = commands.next().get();
if (pc instanceof PluginIdentifiableCommand && !((PluginIdentifiableCommand) pc).getPlugin().equals(plugin)) {
continue;
}
commands.remove();
}
if (entry.getValue().isEmpty()) {
iterator.remove();
}
@@ -72,21 +106,31 @@ public class AlternativeCommandsHandler {
}
public Command getAlternative(final String label) {
final List<Command> commands = altcommands.get(label);
final List<WeakReference<Command>> commands = altCommands.get(label);
if (commands == null || commands.isEmpty()) {
return null;
}
if (commands.size() == 1) {
return commands.get(0);
return commands.get(0).get();
}
// return the first command that is not an alias
for (final Command command : commands) {
if (command.getName().equalsIgnoreCase(label)) {
return command;
final Iterator<WeakReference<Command>> iterator = commands.iterator();
while (iterator.hasNext()) {
final Command cmd = iterator.next().get();
if (cmd == null) {
iterator.remove();
continue;
}
if (cmd.getName().equalsIgnoreCase(label)) {
return cmd;
}
}
// return the first alias
return commands.get(0);
return commands.get(0).get();
}
public void executed(final String label, final Command pc) {
@@ -7,7 +7,7 @@ import com.earth2me.essentials.utils.LocationUtil;
import io.papermc.lib.PaperLib;
import net.ess3.api.IEssentials;
import net.ess3.api.IUser;
import net.ess3.api.InvalidWorldException;
import net.ess3.api.TranslatableException;
import net.ess3.api.events.UserWarpEvent;
import net.ess3.api.events.teleport.PreTeleportEvent;
import net.ess3.api.events.teleport.TeleportWarmupEvent;
@@ -23,8 +23,6 @@ import java.util.GregorianCalendar;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import static com.earth2me.essentials.I18n.tl;
public class AsyncTeleport implements IAsyncTeleport {
private final IUser teleportOwner;
private final IEssentials ess;
@@ -73,7 +71,7 @@ public class AsyncTeleport implements IAsyncTeleport {
time.setTimeInMillis(lastTime);
time.add(Calendar.SECOND, (int) cooldown);
time.add(Calendar.MILLISECOND, (int) ((cooldown * 1000.0) % 1000.0));
future.completeExceptionally(new Exception(tl("timeBeforeTeleport", DateUtil.formatDateDiff(time.getTimeInMillis()))));
future.completeExceptionally(new TranslatableException("timeBeforeTeleport", DateUtil.formatDateDiff(time.getTimeInMillis())));
return true;
}
}
@@ -107,7 +105,7 @@ public class AsyncTeleport implements IAsyncTeleport {
final Calendar c = new GregorianCalendar();
c.add(Calendar.SECOND, (int) delay);
c.add(Calendar.MILLISECOND, (int) ((delay * 1000.0) % 1000.0));
user.sendMessage(tl("dontMoveMessage", DateUtil.formatDateDiff(c.getTimeInMillis())));
user.sendTl("dontMoveMessage", DateUtil.formatDateDiff(c.getTimeInMillis()));
}
@Override
@@ -129,7 +127,7 @@ public class AsyncTeleport implements IAsyncTeleport {
nowAsync(teleportOwner, target, cause, future);
future.thenAccept(success -> {
if (success) {
teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ()));
teleportOwner.sendTl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ());
}
});
}
@@ -166,7 +164,7 @@ public class AsyncTeleport implements IAsyncTeleport {
if (!ess.getSettings().isForcePassengerTeleport() && !teleportee.getBase().isEmpty()) {
if (!ess.getSettings().isTeleportPassengerDismount()) {
future.completeExceptionally(new Exception(tl("passengerTeleportFail")));
future.completeExceptionally(new TranslatableException("passengerTeleportFail"));
return;
}
@@ -204,7 +202,7 @@ public class AsyncTeleport implements IAsyncTeleport {
}
}
} else {
future.completeExceptionally(new Exception(tl("unsafeTeleportDestination", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())));
future.completeExceptionally(new TranslatableException("unsafeTeleportDestination", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
return;
}
} else {
@@ -233,7 +231,7 @@ public class AsyncTeleport implements IAsyncTeleport {
@Override
public void teleport(final Player entity, final Trade chargeFor, final TeleportCause cause, final CompletableFuture<Boolean> future) {
teleportOwner.sendMessage(tl("teleportToPlayer", entity.getDisplayName()));
teleportOwner.sendTl("teleportToPlayer", entity.getDisplayName());
teleport(teleportOwner, new PlayerTarget(entity), chargeFor, cause, future);
}
@@ -248,8 +246,8 @@ public class AsyncTeleport implements IAsyncTeleport {
teleport(otherUser, target, chargeFor, cause, future);
future.thenAccept(success -> {
if (success) {
otherUser.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ()));
teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ()));
otherUser.sendTl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ());
teleportOwner.sendTl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ());
}
});
}
@@ -425,16 +423,16 @@ public class AsyncTeleport implements IAsyncTeleport {
final Location loc;
try {
loc = ess.getWarps().getWarp(warp);
} catch (final WarpNotFoundException | InvalidWorldException e) {
} catch (final WarpNotFoundException e) {
future.completeExceptionally(e);
return;
}
final String finalWarp = warp;
future.thenAccept(success -> {
if (success) {
otherUser.sendMessage(tl("warpingTo", finalWarp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
otherUser.sendTl("warpingTo", finalWarp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
if (!otherUser.equals(teleportOwner)) {
teleportOwner.sendMessage(tl("warpingTo", finalWarp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
teleportOwner.sendTl("warpingTo", finalWarp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
}
}
});
@@ -450,7 +448,7 @@ public class AsyncTeleport implements IAsyncTeleport {
public void back(final IUser teleporter, final Trade chargeFor, final CompletableFuture<Boolean> future) {
tpType = TeleportType.BACK;
final Location loc = teleportOwner.getLastLocation();
teleportOwner.sendMessage(tl("backUsageMsg", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
teleportOwner.sendTl("backUsageMsg", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
teleportOther(teleporter, teleportOwner, new LocationTarget(loc), chargeFor, TeleportCause.COMMAND, future);
}
@@ -459,6 +457,10 @@ public class AsyncTeleport implements IAsyncTeleport {
nowAsync(teleportOwner, new LocationTarget(teleportOwner.getLastLocation()), TeleportCause.COMMAND, future);
}
public TeleportType getTpType() {
return this.tpType;
}
public void setTpType(final TeleportType tpType) {
this.tpType = tpType;
}
@@ -1,14 +1,15 @@
package com.earth2me.essentials;
import net.ess3.api.IEssentials;
import net.ess3.api.IUser;
import org.bukkit.Location;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import static com.earth2me.essentials.I18n.tl;
import org.bukkit.Location;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import net.ess3.api.IEssentials;
import net.ess3.api.IUser;
import net.essentialsx.api.v2.events.TeleportWarmupCancelledEvent;
import net.essentialsx.api.v2.events.TeleportWarmupCancelledEvent.CancelReason;
public class AsyncTimedTeleport implements Runnable {
private static final double MOVE_CONSTANT = 0.3;
@@ -106,14 +107,14 @@ public class AsyncTimedTeleport implements Runnable {
try {
teleport.cooldown(false);
} catch (final Throwable ex) {
teleportOwner.sendMessage(tl("cooldownWithMessage", ex.getMessage()));
teleportOwner.sendTl("cooldownWithMessage", ex.getMessage());
if (teleportOwner != teleportUser) {
teleportUser.sendMessage(tl("cooldownWithMessage", ex.getMessage()));
teleportUser.sendTl("cooldownWithMessage", ex.getMessage());
}
}
try {
cancelTimer(false);
teleportUser.sendMessage(tl("teleportationCommencing"));
teleportUser.sendTl("teleportationCommencing");
if (timer_chargeFor != null) {
timer_chargeFor.isAffordableFor(teleportOwner);
@@ -151,10 +152,18 @@ public class AsyncTimedTeleport implements Runnable {
}
try {
ess.getServer().getScheduler().cancelTask(timer_task);
final IUser teleportUser = ess.getUser(this.timer_teleportee);
if (teleportUser != null && teleportUser.getBase() != null) {
final TeleportWarmupCancelledEvent.CancelReason cancelReason = teleportUser.getBase().isOnline() ? CancelReason.MOVE : CancelReason.LEAVE;
final TeleportWarmupCancelledEvent event = new TeleportWarmupCancelledEvent(teleportUser.getBase(), this.teleport.getTpType(), cancelReason, notifyUser);
ess.getServer().getPluginManager().callEvent(event);
}
if (notifyUser) {
teleportOwner.sendMessage(tl("pendingTeleportCancelled"));
teleportOwner.sendTl("pendingTeleportCancelled");
if (timer_teleportee != null && !timer_teleportee.equals(teleportOwner.getBase().getUniqueId())) {
ess.getUser(timer_teleportee).sendMessage(tl("pendingTeleportCancelled"));
ess.getUser(timer_teleportee).sendTl("pendingTeleportCancelled");
}
}
} finally {
@@ -11,7 +11,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class Backup implements Runnable {
private transient final Server server;
@@ -79,7 +79,7 @@ public class Backup implements Runnable {
taskLock.complete(new Object());
return;
}
ess.getLogger().log(Level.INFO, tl("backupStarted"));
ess.getLogger().log(Level.INFO, ess.getAdventureFacet().miniToLegacy(tlLiteral("backupStarted")));
final CommandSender cs = server.getConsoleSender();
server.dispatchCommand(cs, "save-all");
server.dispatchCommand(cs, "save-off");
@@ -102,12 +102,12 @@ public class Backup implements Runnable {
} while (line != null);
}
} catch (final IOException ex) {
ess.getLogger().log(Level.SEVERE, null, ex);
ess.getLogger().log(Level.SEVERE, "An error occurred while reading backup child process", ex);
}
});
child.waitFor();
} catch (final InterruptedException | IOException ex) {
ess.getLogger().log(Level.SEVERE, null, ex);
ess.getLogger().log(Level.SEVERE, "An error occurred while building the backup child process", ex);
} finally {
class BackupEnableSaveTask implements Runnable {
@Override
@@ -118,7 +118,7 @@ public class Backup implements Runnable {
}
active = false;
taskLock.complete(new Object());
ess.getLogger().log(Level.INFO, tl("backupFinished"));
ess.getLogger().log(Level.INFO, ess.getAdventureFacet().miniToLegacy(tlLiteral("backupFinished")));
}
}
@@ -28,8 +28,8 @@ public class BalanceTopImpl implements BalanceTop {
private void calculateBalanceTopMap() {
final List<Entry> entries = new LinkedList<>();
BigDecimal newTotal = BigDecimal.ZERO;
for (UUID u : ess.getUserMap().getAllUniqueUsers()) {
final User user = ess.getUserMap().getUser(u);
for (UUID u : ess.getUsers().getAllUserUUIDs()) {
final User user = ess.getUsers().loadUncachedUser(u);
if (user != null) {
if (!ess.getSettings().isNpcsInBalanceRanking() && user.isNPC()) {
// Don't list NPCs in output
@@ -40,7 +40,7 @@ public class BalanceTopImpl implements BalanceTop {
user.updateMoneyCache(userMoney);
newTotal = newTotal.add(userMoney);
final String name;
if (user.getBase() instanceof OfflinePlayer) {
if (user.getBase() instanceof OfflinePlayerStub) {
name = user.getLastAccountName();
} else if (user.isHidden()) {
name = user.getName();
@@ -53,7 +53,10 @@ public class BalanceTopImpl implements BalanceTop {
}
final LinkedHashMap<UUID, Entry> sortedMap = new LinkedHashMap<>();
entries.sort((entry1, entry2) -> entry2.getBalance().compareTo(entry1.getBalance()));
for (Entry entry : entries) {
final int entryLimit = ess.getSettings().getBaltopEntryLimit();
final int limit = entryLimit == -1 ? entries.size() : Math.min(entryLimit, entries.size());
for (int i = 0; i < limit; i++) {
final Entry entry = entries.get(i);
sortedMap.put(entry.getUuid(), entry);
}
topCache = sortedMap;
@@ -1,11 +1,9 @@
package com.earth2me.essentials;
public class ChargeException extends Exception {
public ChargeException(final String message) {
super(message);
}
import net.ess3.api.TranslatableException;
public ChargeException(final String message, final Throwable throwable) {
super(message, throwable);
public class ChargeException extends TranslatableException {
public ChargeException(String tlKey, Object... args) {
super(tlKey, args);
}
}
@@ -1,12 +1,17 @@
package com.earth2me.essentials;
import com.earth2me.essentials.adventure.ComponentHolder;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import static com.earth2me.essentials.I18n.tlLiteral;
public class CommandSource {
protected Essentials ess;
protected CommandSender sender;
public CommandSource(final CommandSender base) {
public CommandSource(final Essentials ess, final CommandSender base) {
this.ess = ess;
this.sender = base;
}
@@ -21,7 +26,41 @@ public class CommandSource {
return null;
}
public final net.ess3.api.IUser getUser(final IEssentials ess) {
public void sendTl(final String tlKey, final Object... args) {
if (isPlayer()) {
//noinspection ConstantConditions
getUser().sendTl(tlKey, args);
return;
}
final String translation = tlLiteral(tlKey, args);
if (!translation.isEmpty()) {
sendComponent(ess.getAdventureFacet().deserializeMiniMessage(translation));
}
}
public String tl(final String tlKey, final Object... args) {
if (isPlayer()) {
//noinspection ConstantConditions
return getUser().playerTl(tlKey, args);
}
return tlLiteral(tlKey, args);
}
public ComponentHolder tlComponent(final String tlKey, final Object... args) {
if (isPlayer()) {
//noinspection ConstantConditions
return getUser().tlComponent(tlKey, args);
}
final String translation = tlLiteral(tlKey, args);
return ess.getAdventureFacet().deserializeMiniMessage(translation);
}
public void sendComponent(final ComponentHolder component) {
ess.getAdventureFacet().send(sender, component);
}
public final net.ess3.api.IUser getUser() {
if (sender instanceof Player) {
return ess.getUser((Player) sender);
}
@@ -42,15 +81,18 @@ public class CommandSource {
}
}
public boolean isAuthorized(final String permission, final IEssentials ess) {
return !(sender instanceof Player) || getUser(ess).isAuthorized(permission);
public boolean isAuthorized(final String permission) {
//noinspection ConstantConditions
return !(sender instanceof Player) || getUser().isAuthorized(permission);
}
public String getSelfSelector() {
//noinspection ConstantConditions
return sender instanceof Player ? getPlayer().getName() : "*";
}
public String getDisplayName() {
//noinspection ConstantConditions
return sender instanceof Player ? getPlayer().getDisplayName() : getSender().getName();
}
}
@@ -1,5 +1,6 @@
package com.earth2me.essentials;
import com.earth2me.essentials.adventure.AdventureFacet;
import com.earth2me.essentials.messaging.IMessageRecipient;
import com.earth2me.essentials.messaging.SimpleMessageRecipient;
import org.bukkit.Server;
@@ -8,11 +9,15 @@ import org.bukkit.entity.Player;
import java.util.UUID;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public final class Console implements IMessageRecipient {
public static final String NAME = "Console";
public static final String DISPLAY_NAME = tl("consoleName");
public static String displayName() {
return tlLiteral("consoleName");
}
private static Console instance; // Set in essentials
private final IEssentials ess;
@@ -55,7 +60,7 @@ public final class Console implements IMessageRecipient {
@Override
public String getDisplayName() {
return Console.DISPLAY_NAME;
return displayName();
}
@Override
@@ -63,6 +68,22 @@ public final class Console implements IMessageRecipient {
getCommandSender().sendMessage(message);
}
@Override
public void sendTl(String tlKey, Object... args) {
final String translation = tlLiteral(tlKey, args);
if (translation.isEmpty()) {
return;
}
final AdventureFacet adventureFacet = ess.getAdventureFacet();
adventureFacet.send(getCommandSender(), adventureFacet.deserializeMiniMessage(translation));
}
@Override
public String tlSender(String tlKey, Object... args) {
return tlLiteral(tlKey, args);
}
@Override
public boolean isReachable() {
return true;
@@ -1,5 +1,6 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.RegistryUtil;
import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment;
@@ -15,33 +16,43 @@ public final class Enchantments {
private static boolean isFlat;
static {
ENCHANTMENTS.put("alldamage", Enchantment.DAMAGE_ALL);
ALIASENCHANTMENTS.put("alldmg", Enchantment.DAMAGE_ALL);
ENCHANTMENTS.put("sharpness", Enchantment.DAMAGE_ALL);
ALIASENCHANTMENTS.put("sharp", Enchantment.DAMAGE_ALL);
ALIASENCHANTMENTS.put("dal", Enchantment.DAMAGE_ALL);
final Enchantment SHARPNESS = RegistryUtil.valueOf(Enchantment.class, "DAMAGE_ALL", "SHARPNESS");
ENCHANTMENTS.put("ardmg", Enchantment.DAMAGE_ARTHROPODS);
ENCHANTMENTS.put("baneofarthropods", Enchantment.DAMAGE_ARTHROPODS);
ALIASENCHANTMENTS.put("baneofarthropod", Enchantment.DAMAGE_ARTHROPODS);
ALIASENCHANTMENTS.put("arthropod", Enchantment.DAMAGE_ARTHROPODS);
ALIASENCHANTMENTS.put("dar", Enchantment.DAMAGE_ARTHROPODS);
ENCHANTMENTS.put("alldamage", SHARPNESS);
ALIASENCHANTMENTS.put("alldmg", SHARPNESS);
ENCHANTMENTS.put("sharpness", SHARPNESS);
ALIASENCHANTMENTS.put("sharp", SHARPNESS);
ALIASENCHANTMENTS.put("dal", SHARPNESS);
ENCHANTMENTS.put("undeaddamage", Enchantment.DAMAGE_UNDEAD);
ENCHANTMENTS.put("smite", Enchantment.DAMAGE_UNDEAD);
ALIASENCHANTMENTS.put("du", Enchantment.DAMAGE_UNDEAD);
final Enchantment BANE_OF_ARTHROPODS = RegistryUtil.valueOf(Enchantment.class, "DAMAGE_ARTHROPODS", "BANE_OF_ARTHROPODS");
ENCHANTMENTS.put("digspeed", Enchantment.DIG_SPEED);
ENCHANTMENTS.put("efficiency", Enchantment.DIG_SPEED);
ALIASENCHANTMENTS.put("minespeed", Enchantment.DIG_SPEED);
ALIASENCHANTMENTS.put("cutspeed", Enchantment.DIG_SPEED);
ALIASENCHANTMENTS.put("ds", Enchantment.DIG_SPEED);
ALIASENCHANTMENTS.put("eff", Enchantment.DIG_SPEED);
ENCHANTMENTS.put("ardmg", BANE_OF_ARTHROPODS);
ENCHANTMENTS.put("baneofarthropods", BANE_OF_ARTHROPODS);
ALIASENCHANTMENTS.put("baneofarthropod", BANE_OF_ARTHROPODS);
ALIASENCHANTMENTS.put("arthropod", BANE_OF_ARTHROPODS);
ALIASENCHANTMENTS.put("dar", BANE_OF_ARTHROPODS);
ENCHANTMENTS.put("durability", Enchantment.DURABILITY);
ALIASENCHANTMENTS.put("dura", Enchantment.DURABILITY);
ENCHANTMENTS.put("unbreaking", Enchantment.DURABILITY);
ALIASENCHANTMENTS.put("d", Enchantment.DURABILITY);
final Enchantment SMITE = RegistryUtil.valueOf(Enchantment.class, "DAMAGE_UNDEAD", "SMITE");
ENCHANTMENTS.put("undeaddamage", SMITE);
ENCHANTMENTS.put("smite", SMITE);
ALIASENCHANTMENTS.put("du", SMITE);
final Enchantment EFFICIENCY = RegistryUtil.valueOf(Enchantment.class, "DIG_SPEED", "EFFICIENCY");
ENCHANTMENTS.put("digspeed", EFFICIENCY);
ENCHANTMENTS.put("efficiency", EFFICIENCY);
ALIASENCHANTMENTS.put("minespeed", EFFICIENCY);
ALIASENCHANTMENTS.put("cutspeed", EFFICIENCY);
ALIASENCHANTMENTS.put("ds", EFFICIENCY);
ALIASENCHANTMENTS.put("eff", EFFICIENCY);
final Enchantment UNBREAKING = RegistryUtil.valueOf(Enchantment.class, "DURABILITY", "UNBREAKING");
ENCHANTMENTS.put("durability", UNBREAKING);
ALIASENCHANTMENTS.put("dura", UNBREAKING);
ENCHANTMENTS.put("unbreaking", UNBREAKING);
ALIASENCHANTMENTS.put("d", UNBREAKING);
ENCHANTMENTS.put("thorns", Enchantment.THORNS);
ENCHANTMENTS.put("highcrit", Enchantment.THORNS);
@@ -60,105 +71,126 @@ public final class Enchantments {
ALIASENCHANTMENTS.put("kb", Enchantment.KNOCKBACK);
ALIASENCHANTMENTS.put("k", Enchantment.KNOCKBACK);
ALIASENCHANTMENTS.put("blockslootbonus", Enchantment.LOOT_BONUS_BLOCKS);
ENCHANTMENTS.put("fortune", Enchantment.LOOT_BONUS_BLOCKS);
ALIASENCHANTMENTS.put("fort", Enchantment.LOOT_BONUS_BLOCKS);
ALIASENCHANTMENTS.put("lbb", Enchantment.LOOT_BONUS_BLOCKS);
final Enchantment FORTUNE = RegistryUtil.valueOf(Enchantment.class, "LOOT_BONUS_BLOCKS", "FORTUNE");
ALIASENCHANTMENTS.put("mobslootbonus", Enchantment.LOOT_BONUS_MOBS);
ENCHANTMENTS.put("mobloot", Enchantment.LOOT_BONUS_MOBS);
ENCHANTMENTS.put("looting", Enchantment.LOOT_BONUS_MOBS);
ALIASENCHANTMENTS.put("lbm", Enchantment.LOOT_BONUS_MOBS);
ALIASENCHANTMENTS.put("blockslootbonus", FORTUNE);
ENCHANTMENTS.put("fortune", FORTUNE);
ALIASENCHANTMENTS.put("fort", FORTUNE);
ALIASENCHANTMENTS.put("lbb", FORTUNE);
ALIASENCHANTMENTS.put("oxygen", Enchantment.OXYGEN);
ENCHANTMENTS.put("respiration", Enchantment.OXYGEN);
ALIASENCHANTMENTS.put("breathing", Enchantment.OXYGEN);
ENCHANTMENTS.put("breath", Enchantment.OXYGEN);
ALIASENCHANTMENTS.put("o", Enchantment.OXYGEN);
final Enchantment LOOTING = RegistryUtil.valueOf(Enchantment.class, "LOOT_BONUS_MOBS", "LOOTING");
ENCHANTMENTS.put("protection", Enchantment.PROTECTION_ENVIRONMENTAL);
ALIASENCHANTMENTS.put("prot", Enchantment.PROTECTION_ENVIRONMENTAL);
ENCHANTMENTS.put("protect", Enchantment.PROTECTION_ENVIRONMENTAL);
ALIASENCHANTMENTS.put("p", Enchantment.PROTECTION_ENVIRONMENTAL);
ALIASENCHANTMENTS.put("mobslootbonus", LOOTING);
ENCHANTMENTS.put("mobloot", LOOTING);
ENCHANTMENTS.put("looting", LOOTING);
ALIASENCHANTMENTS.put("lbm", LOOTING);
ALIASENCHANTMENTS.put("explosionsprotection", Enchantment.PROTECTION_EXPLOSIONS);
ALIASENCHANTMENTS.put("explosionprotection", Enchantment.PROTECTION_EXPLOSIONS);
ALIASENCHANTMENTS.put("expprot", Enchantment.PROTECTION_EXPLOSIONS);
ALIASENCHANTMENTS.put("blastprotection", Enchantment.PROTECTION_EXPLOSIONS);
ALIASENCHANTMENTS.put("bprotection", Enchantment.PROTECTION_EXPLOSIONS);
ALIASENCHANTMENTS.put("bprotect", Enchantment.PROTECTION_EXPLOSIONS);
ENCHANTMENTS.put("blastprotect", Enchantment.PROTECTION_EXPLOSIONS);
ALIASENCHANTMENTS.put("pe", Enchantment.PROTECTION_EXPLOSIONS);
final Enchantment RESPIRATION = RegistryUtil.valueOf(Enchantment.class, "OXYGEN", "RESPIRATION");
ALIASENCHANTMENTS.put("fallprotection", Enchantment.PROTECTION_FALL);
ENCHANTMENTS.put("fallprot", Enchantment.PROTECTION_FALL);
ENCHANTMENTS.put("featherfall", Enchantment.PROTECTION_FALL);
ALIASENCHANTMENTS.put("featherfalling", Enchantment.PROTECTION_FALL);
ALIASENCHANTMENTS.put("pfa", Enchantment.PROTECTION_FALL);
ALIASENCHANTMENTS.put("oxygen", RESPIRATION);
ENCHANTMENTS.put("respiration", RESPIRATION);
ALIASENCHANTMENTS.put("breathing", RESPIRATION);
ENCHANTMENTS.put("breath", RESPIRATION);
ALIASENCHANTMENTS.put("o", RESPIRATION);
ALIASENCHANTMENTS.put("fireprotection", Enchantment.PROTECTION_FIRE);
ALIASENCHANTMENTS.put("flameprotection", Enchantment.PROTECTION_FIRE);
ENCHANTMENTS.put("fireprotect", Enchantment.PROTECTION_FIRE);
ALIASENCHANTMENTS.put("flameprotect", Enchantment.PROTECTION_FIRE);
ENCHANTMENTS.put("fireprot", Enchantment.PROTECTION_FIRE);
ALIASENCHANTMENTS.put("flameprot", Enchantment.PROTECTION_FIRE);
ALIASENCHANTMENTS.put("pf", Enchantment.PROTECTION_FIRE);
final Enchantment PROTECTION = RegistryUtil.valueOf(Enchantment.class, "PROTECTION_ENVIRONMENTAL", "PROTECTION");
ENCHANTMENTS.put("projectileprotection", Enchantment.PROTECTION_PROJECTILE);
ENCHANTMENTS.put("projprot", Enchantment.PROTECTION_PROJECTILE);
ALIASENCHANTMENTS.put("pp", Enchantment.PROTECTION_PROJECTILE);
ENCHANTMENTS.put("protection", PROTECTION);
ALIASENCHANTMENTS.put("prot", PROTECTION);
ENCHANTMENTS.put("protect", PROTECTION);
ALIASENCHANTMENTS.put("p", PROTECTION);
final Enchantment BLAST_PROTECTION = RegistryUtil.valueOf(Enchantment.class, "PROTECTION_EXPLOSIONS", "BLAST_PROTECTION");
ALIASENCHANTMENTS.put("explosionsprotection", BLAST_PROTECTION);
ALIASENCHANTMENTS.put("explosionprotection", BLAST_PROTECTION);
ALIASENCHANTMENTS.put("expprot", BLAST_PROTECTION);
ALIASENCHANTMENTS.put("blastprotection", BLAST_PROTECTION);
ALIASENCHANTMENTS.put("bprotection", BLAST_PROTECTION);
ALIASENCHANTMENTS.put("bprotect", BLAST_PROTECTION);
ENCHANTMENTS.put("blastprotect", BLAST_PROTECTION);
ALIASENCHANTMENTS.put("pe", BLAST_PROTECTION);
final Enchantment FEATHER_FALLING = RegistryUtil.valueOf(Enchantment.class, "PROTECTION_FALL", "FEATHER_FALLING");
ALIASENCHANTMENTS.put("fallprotection", FEATHER_FALLING);
ENCHANTMENTS.put("fallprot", FEATHER_FALLING);
ENCHANTMENTS.put("featherfall", FEATHER_FALLING);
ALIASENCHANTMENTS.put("featherfalling", FEATHER_FALLING);
ALIASENCHANTMENTS.put("pfa", FEATHER_FALLING);
final Enchantment FIRE_PROTECTION = RegistryUtil.valueOf(Enchantment.class, "PROTECTION_FIRE", "FIRE_PROTECTION");
ALIASENCHANTMENTS.put("fireprotection", FIRE_PROTECTION);
ALIASENCHANTMENTS.put("flameprotection", FIRE_PROTECTION);
ENCHANTMENTS.put("fireprotect", FIRE_PROTECTION);
ALIASENCHANTMENTS.put("flameprotect", FIRE_PROTECTION);
ENCHANTMENTS.put("fireprot", FIRE_PROTECTION);
ALIASENCHANTMENTS.put("flameprot", FIRE_PROTECTION);
ALIASENCHANTMENTS.put("pf", FIRE_PROTECTION);
final Enchantment PROJECTILE_PROTECTION = RegistryUtil.valueOf(Enchantment.class, "PROTECTION_PROJECTILE", "PROJECTILE_PROTECTION");
ENCHANTMENTS.put("projectileprotection", PROJECTILE_PROTECTION);
ENCHANTMENTS.put("projprot", PROJECTILE_PROTECTION);
ALIASENCHANTMENTS.put("pp", PROJECTILE_PROTECTION);
ENCHANTMENTS.put("silktouch", Enchantment.SILK_TOUCH);
ALIASENCHANTMENTS.put("softtouch", Enchantment.SILK_TOUCH);
ALIASENCHANTMENTS.put("st", Enchantment.SILK_TOUCH);
ENCHANTMENTS.put("waterworker", Enchantment.WATER_WORKER);
ENCHANTMENTS.put("aquaaffinity", Enchantment.WATER_WORKER);
ALIASENCHANTMENTS.put("watermine", Enchantment.WATER_WORKER);
ALIASENCHANTMENTS.put("ww", Enchantment.WATER_WORKER);
final Enchantment AQUA_AFFINITY = RegistryUtil.valueOf(Enchantment.class, "WATER_WORKER", "AQUA_AFFINITY");
ALIASENCHANTMENTS.put("firearrow", Enchantment.ARROW_FIRE);
ENCHANTMENTS.put("flame", Enchantment.ARROW_FIRE);
ENCHANTMENTS.put("flamearrow", Enchantment.ARROW_FIRE);
ALIASENCHANTMENTS.put("af", Enchantment.ARROW_FIRE);
ENCHANTMENTS.put("waterworker", AQUA_AFFINITY);
ENCHANTMENTS.put("aquaaffinity", AQUA_AFFINITY);
ALIASENCHANTMENTS.put("watermine", AQUA_AFFINITY);
ALIASENCHANTMENTS.put("ww", AQUA_AFFINITY);
ENCHANTMENTS.put("arrowdamage", Enchantment.ARROW_DAMAGE);
ENCHANTMENTS.put("power", Enchantment.ARROW_DAMAGE);
ALIASENCHANTMENTS.put("arrowpower", Enchantment.ARROW_DAMAGE);
ALIASENCHANTMENTS.put("ad", Enchantment.ARROW_DAMAGE);
final Enchantment FLAME = RegistryUtil.valueOf(Enchantment.class, "ARROW_FIRE", "FLAME");
ENCHANTMENTS.put("arrowknockback", Enchantment.ARROW_KNOCKBACK);
ALIASENCHANTMENTS.put("arrowkb", Enchantment.ARROW_KNOCKBACK);
ENCHANTMENTS.put("punch", Enchantment.ARROW_KNOCKBACK);
ALIASENCHANTMENTS.put("arrowpunch", Enchantment.ARROW_KNOCKBACK);
ALIASENCHANTMENTS.put("ak", Enchantment.ARROW_KNOCKBACK);
ALIASENCHANTMENTS.put("firearrow", FLAME);
ENCHANTMENTS.put("flame", FLAME);
ENCHANTMENTS.put("flamearrow", FLAME);
ALIASENCHANTMENTS.put("af", FLAME);
ALIASENCHANTMENTS.put("infinitearrows", Enchantment.ARROW_INFINITE);
ENCHANTMENTS.put("infarrows", Enchantment.ARROW_INFINITE);
ENCHANTMENTS.put("infinity", Enchantment.ARROW_INFINITE);
ALIASENCHANTMENTS.put("infinite", Enchantment.ARROW_INFINITE);
ALIASENCHANTMENTS.put("unlimited", Enchantment.ARROW_INFINITE);
ALIASENCHANTMENTS.put("unlimitedarrows", Enchantment.ARROW_INFINITE);
ALIASENCHANTMENTS.put("ai", Enchantment.ARROW_INFINITE);
final Enchantment POWER = RegistryUtil.valueOf(Enchantment.class, "ARROW_DAMAGE", "POWER");
ENCHANTMENTS.put("luck", Enchantment.LUCK);
ALIASENCHANTMENTS.put("luckofsea", Enchantment.LUCK);
ALIASENCHANTMENTS.put("luckofseas", Enchantment.LUCK);
ALIASENCHANTMENTS.put("rodluck", Enchantment.LUCK);
ENCHANTMENTS.put("arrowdamage", POWER);
ENCHANTMENTS.put("power", POWER);
ALIASENCHANTMENTS.put("arrowpower", POWER);
ALIASENCHANTMENTS.put("ad", POWER);
final Enchantment PUNCH = RegistryUtil.valueOf(Enchantment.class, "ARROW_KNOCKBACK", "PUNCH");
ENCHANTMENTS.put("arrowknockback", PUNCH);
ALIASENCHANTMENTS.put("arrowkb", PUNCH);
ENCHANTMENTS.put("punch", PUNCH);
ALIASENCHANTMENTS.put("arrowpunch", PUNCH);
ALIASENCHANTMENTS.put("ak", PUNCH);
final Enchantment INFINITY = RegistryUtil.valueOf(Enchantment.class, "ARROW_INFINITE", "INFINITY");
ALIASENCHANTMENTS.put("infinitearrows", INFINITY);
ENCHANTMENTS.put("infarrows", INFINITY);
ENCHANTMENTS.put("infinity", INFINITY);
ALIASENCHANTMENTS.put("infinite", INFINITY);
ALIASENCHANTMENTS.put("unlimited", INFINITY);
ALIASENCHANTMENTS.put("unlimitedarrows", INFINITY);
ALIASENCHANTMENTS.put("ai", INFINITY);
final Enchantment LUCK_OF_THE_SEA = RegistryUtil.valueOf(Enchantment.class, "LUCK", "LUCK_OF_THE_SEA");
ENCHANTMENTS.put("luck", LUCK_OF_THE_SEA);
ALIASENCHANTMENTS.put("luckofsea", LUCK_OF_THE_SEA);
ALIASENCHANTMENTS.put("luckofseas", LUCK_OF_THE_SEA);
ALIASENCHANTMENTS.put("rodluck", LUCK_OF_THE_SEA);
ENCHANTMENTS.put("lure", Enchantment.LURE);
ALIASENCHANTMENTS.put("rodlure", Enchantment.LURE);
// 1.8
try {
final Enchantment depthStrider = Enchantment.getByName("DEPTH_STRIDER");
if (depthStrider != null) {
ENCHANTMENTS.put("depthstrider", depthStrider);
ALIASENCHANTMENTS.put("depth", depthStrider);
ALIASENCHANTMENTS.put("strider", depthStrider);
}
} catch (final IllegalArgumentException ignored) {
}
ENCHANTMENTS.put("depthstrider", Enchantment.DEPTH_STRIDER);
ALIASENCHANTMENTS.put("depth", Enchantment.DEPTH_STRIDER);
ALIASENCHANTMENTS.put("strider", Enchantment.DEPTH_STRIDER);
// 1.9
try {
@@ -271,6 +303,32 @@ public final class Enchantments {
} catch (final IllegalArgumentException ignored) {
}
try { // 1.21
final Enchantment breach = Enchantment.getByName("BREACH");
if (breach != null) {
ENCHANTMENTS.put("breach", breach);
}
final Enchantment density = Enchantment.getByName("DENSITY");
if (density != null) {
ENCHANTMENTS.put("density", density);
}
final Enchantment windBurst = Enchantment.getByName("WIND_BURST");
if (breach != null) {
ENCHANTMENTS.put("windburst", windBurst);
ALIASENCHANTMENTS.put("wind", windBurst);
ALIASENCHANTMENTS.put("burst", windBurst);
}
} catch (final IllegalArgumentException ignored) {
}
try { // 1.21.11
final Enchantment lunge = Enchantment.getByName("LUNGE");
if (lunge != null) {
ENCHANTMENTS.put("lunge", lunge);
}
} catch (final IllegalArgumentException ignored) {
}
try {
final Class<?> namespacedKeyClass = Class.forName("org.bukkit.NamespacedKey");
final Class<?> enchantmentClass = Class.forName("org.bukkit.enchantments.Enchantment");
@@ -284,6 +342,17 @@ public final class Enchantments {
private Enchantments() {
}
public static String getRealName(final Enchantment enchantment) {
if (enchantment == null) {
return null;
}
if (isFlat) { // 1.13+ only
return enchantment.getKey().getKey();
}
return enchantment.getName().toLowerCase(Locale.ENGLISH);
}
public static Enchantment getByName(final String name) {
if (name == null || name.isEmpty()) {
return null;
File diff suppressed because it is too large. Load diff
@@ -1,7 +1,10 @@
package com.earth2me.essentials;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.utils.MaterialUtil;
import net.ess3.api.IEssentials;
import net.ess3.provider.PersistentDataProvider;
import net.ess3.provider.SpawnerItemProvider;
import org.bukkit.GameMode;
import org.bukkit.block.BlockState;
import org.bukkit.block.CreatureSpawner;
@@ -25,11 +28,11 @@ public class EssentialsBlockListener implements Listener {
public void onBlockPlace(final BlockPlaceEvent event) {
final ItemStack is = event.getItemInHand();
if (is.getType() == MaterialUtil.SPAWNER && ess.getPersistentDataProvider().getString(is, "convert") != null) {
if (is.getType() == MaterialUtil.SPAWNER && ess.provider(PersistentDataProvider.class).getString(is, "convert") != null) {
final BlockState blockState = event.getBlockPlaced().getState();
if (blockState instanceof CreatureSpawner) {
final CreatureSpawner spawner = (CreatureSpawner) blockState;
final EntityType type = ess.getSpawnerItemProvider().getEntityType(event.getItemInHand());
final EntityType type = ess.provider(SpawnerItemProvider.class).getEntityType(event.getItemInHand());
if (type != null && Mob.fromBukkitType(type) != null) {
if (ess.getUser(event.getPlayer()).isAuthorized("essentials.spawnerconvert." + Mob.fromBukkitType(type).name().toLowerCase(Locale.ENGLISH))) {
spawner.setSpawnedType(type);
@@ -45,7 +48,7 @@ public class EssentialsBlockListener implements Listener {
if (is != null && is.getType() != null && !MaterialUtil.isAir(is.getType())) {
final ItemStack cloneIs = is.clone();
cloneIs.setAmount(1);
user.getBase().getInventory().addItem(cloneIs);
Inventories.addItem(user.getBase(), cloneIs);
user.getBase().updateInventory();
}
});
@@ -1,6 +1,6 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.MaterialUtil;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.api.IEssentials;
import org.bukkit.Location;
@@ -32,8 +32,6 @@ import java.util.List;
import java.util.logging.Level;
import java.util.regex.Pattern;
import static com.earth2me.essentials.I18n.tl;
public class EssentialsEntityListener implements Listener {
private static final transient Pattern powertoolPlayer = Pattern.compile("\\{player\\}");
private final IEssentials ess;
@@ -52,12 +50,12 @@ public class EssentialsEntityListener implements Listener {
if (eDefend instanceof Player) {
onPlayerVsPlayerDamage(event, (Player) eDefend, attacker);
} else if (eDefend instanceof Ageable) {
final ItemStack hand = attacker.getBase().getItemInHand();
final ItemStack hand = Inventories.getItemInMainHand(attacker.getBase());
if (ess.getSettings().isMilkBucketEasterEggEnabled()
&& hand != null && hand.getType() == Material.MILK_BUCKET) {
((Ageable) eDefend).setBaby();
hand.setType(Material.BUCKET);
attacker.getBase().setItemInHand(hand);
Inventories.setItemInMainHand(attacker.getBase(), hand);
attacker.getBase().updateInventory();
event.setCancelled(true);
}
@@ -98,7 +96,7 @@ public class EssentialsEntityListener implements Listener {
}
private void onPlayerVsPlayerPowertool(final EntityDamageByEntityEvent event, final Player defender, final User attacker) {
final List<String> commandList = attacker.getPowertool(attacker.getBase().getItemInHand());
final List<String> commandList = attacker.getPowertool(Inventories.getItemInHand(attacker.getBase()));
if (commandList != null && !commandList.isEmpty()) {
for (final String tempCommand : commandList) {
final String command = powertoolPlayer.matcher(tempCommand).replaceAll(defender.getName());
@@ -158,7 +156,7 @@ public class EssentialsEntityListener implements Listener {
}
}
@EventHandler(priority = EventPriority.LOWEST)
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onPlayerDeathEvent(final PlayerDeathEvent event) {
final Entity entity = event.getEntity();
if (entity.hasMetadata("NPC")) {
@@ -167,11 +165,11 @@ public class EssentialsEntityListener implements Listener {
final User user = ess.getUser(event.getEntity());
if (ess.getSettings().infoAfterDeath()) {
final Location loc = user.getLocation();
user.sendMessage(tl("infoAfterDeath", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
user.sendTl("infoAfterDeath", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
}
if (user.isAuthorized("essentials.back.ondeath") && !ess.getSettings().isCommandDisabled("back")) {
user.setLastLocation();
user.sendMessage(tl("backAfterDeath"));
user.sendTl("backAfterDeath");
}
if (!ess.getSettings().areDeathMessagesEnabled()) {
event.setDeathMessage("");
@@ -192,77 +190,35 @@ public class EssentialsEntityListener implements Listener {
final User user = ess.getUser(event.getEntity());
if (user.isAuthorized("essentials.keepinv")) {
event.setKeepInventory(true);
event.getDrops().clear();
// We don't do getDrops().clear() here because it would remove any loot tables that were added by other plugins/datapacks.
// Instead, we remove the items from the drops that are in the player's inventory. This way, the loot tables can still drop items.
// This is the same behavior as the vanilla /gamerule keepInventory.
final ItemStack[] inventory = Inventories.getInventory(event.getEntity(), true);
for (final ItemStack item : inventory) {
if (item != null) {
event.getDrops().remove(item);
}
}
final ISettings.KeepInvPolicy vanish = ess.getSettings().getVanishingItemsPolicy();
final ISettings.KeepInvPolicy bind = ess.getSettings().getBindingItemsPolicy();
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_11_2_R01) && (vanish != ISettings.KeepInvPolicy.KEEP || bind != ISettings.KeepInvPolicy.KEEP)) {
for (final ItemStack stack : event.getEntity().getInventory()) {
if (stack != null && !MaterialUtil.isAir(stack.getType())) {
if (stack.getEnchantments().containsKey(Enchantment.VANISHING_CURSE)) {
if (vanish == ISettings.KeepInvPolicy.DELETE) {
event.getEntity().getInventory().remove(stack);
} else if (vanish == ISettings.KeepInvPolicy.DROP) {
event.getDrops().add(stack);
event.getEntity().getInventory().remove(stack);
}
}
if (stack.getEnchantments().containsKey(Enchantment.BINDING_CURSE)) {
if (bind == ISettings.KeepInvPolicy.DELETE) {
event.getEntity().getInventory().remove(stack);
} else if (bind == ISettings.KeepInvPolicy.DROP) {
event.getEntity().getInventory().remove(stack);
event.getDrops().add(stack);
}
Inventories.removeItems(user.getBase(), stack -> {
if (vanish != ISettings.KeepInvPolicy.KEEP && stack.getEnchantments().containsKey(Enchantment.VANISHING_CURSE)) {
if (vanish == ISettings.KeepInvPolicy.DROP) {
event.getDrops().add(stack.clone());
}
return true;
}
}
// Now check armor
final ItemStack[] armor = event.getEntity().getInventory().getArmorContents();
for (int i = 0; i < armor.length; i++) {
final ItemStack stack = armor[i];
if (stack != null && !MaterialUtil.isAir(stack.getType())) {
if (stack.getEnchantments().containsKey(Enchantment.VANISHING_CURSE)) {
if (vanish == ISettings.KeepInvPolicy.DELETE) {
armor[i] = null;
} else if (vanish == ISettings.KeepInvPolicy.DROP) {
if (!event.getDrops().contains(stack)) {
event.getDrops().add(stack);
}
armor[i] = null;
}
}
if (stack.getEnchantments().containsKey(Enchantment.BINDING_CURSE)) {
if (bind == ISettings.KeepInvPolicy.DELETE) {
armor[i] = null;
} else if (bind == ISettings.KeepInvPolicy.DROP) {
if (!event.getDrops().contains(stack)) {
event.getDrops().add(stack);
}
armor[i] = null;
}
if (bind != ISettings.KeepInvPolicy.KEEP && stack.getEnchantments().containsKey(Enchantment.BINDING_CURSE)) {
if (bind == ISettings.KeepInvPolicy.DROP) {
event.getDrops().add(stack.clone());
}
return true;
}
}
event.getEntity().getInventory().setArmorContents(armor);
// Now check offhand
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_9_R01)) {
final ItemStack stack = event.getEntity().getInventory().getItemInOffHand();
//noinspection ConstantConditions
if (stack != null && !MaterialUtil.isAir(stack.getType())) {
final boolean isVanish = stack.getEnchantments().containsKey(Enchantment.VANISHING_CURSE);
final boolean isBind = stack.getEnchantments().containsKey(Enchantment.BINDING_CURSE);
if (isVanish || isBind) {
event.getEntity().getInventory().setItemInOffHand(null);
if ((isVanish && vanish == ISettings.KeepInvPolicy.DROP) || (isBind && bind == ISettings.KeepInvPolicy.DROP)) {
if (!event.getDrops().contains(stack)) {
event.getDrops().add(stack);
}
}
}
}
}
return false;
}, true);
}
}
}
File diff suppressed because it is too large. Load diff
@@ -3,14 +3,16 @@ package com.earth2me.essentials;
import com.earth2me.essentials.config.ConfigurateUtil;
import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.config.EssentialsUserConfiguration;
import com.earth2me.essentials.config.entities.LazyLocation;
import com.earth2me.essentials.craftbukkit.BanLookup;
import com.earth2me.essentials.userstorage.ModernUUIDCache;
import com.earth2me.essentials.utils.StringUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.Maps;
import com.google.common.io.Files;
import com.google.gson.reflect.TypeToken;
import net.ess3.api.IEssentials;
import net.essentialsx.api.v2.services.mail.MailMessage;
import nu.studer.java.util.OrderedProperties;
import org.bukkit.BanList;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@@ -31,10 +33,10 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -47,13 +49,12 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class EssentialsUpgrade {
private static final FileFilter YML_FILTER = pathname -> pathname.isFile() && pathname.getName().endsWith(".yml");
public static final FileFilter YML_FILTER = pathname -> pathname.isFile() && pathname.getName().endsWith(".yml");
private static final String PATTERN_CONFIG_UUID_REGEX = "(?mi)^uuid:\\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\s*$";
private static final Pattern PATTERN_CONFIG_UUID = Pattern.compile(PATTERN_CONFIG_UUID_REGEX);
private static final String PATTERN_CONFIG_NAME_REGEX = "(?mi)^lastAccountName:\\s*[\"\']?(\\w+)[\"\']?\\s*$";
@@ -70,7 +71,7 @@ public class EssentialsUpgrade {
doneFile.load();
}
public static void uuidFileConvert(final IEssentials ess, final Boolean ignoreUFCache) {
private static void uuidFileConvert(final IEssentials ess, final Boolean ignoreUFCache) {
ess.getLogger().info("Starting Essentials UUID userdata conversion");
final File userdir = new File(ess.getDataFolder(), "userdata");
@@ -93,7 +94,6 @@ public class EssentialsUpgrade {
final int showProgress = countFiles % 250;
if (showProgress == 0) {
ess.getUserMap().getUUIDMap().forceWriteUUIDMap();
ess.getLogger().info("Converted " + countFiles + "/" + userdir.list().length);
}
@@ -103,7 +103,8 @@ public class EssentialsUpgrade {
final EssentialsUserConfiguration config;
UUID uuid = null;
try {
uuid = UUID.fromString(name);
//noinspection ResultOfMethodCallIgnored
UUID.fromString(name);
} catch (final IllegalArgumentException ex) {
final File file = new File(userdir, string);
final EssentialsConfiguration conf = new EssentialsConfiguration(file);
@@ -115,6 +116,7 @@ public class EssentialsUpgrade {
final String uuidString = conf.getString(uuidConf, null);
//noinspection ConstantConditions
for (int i = 0; i < 4; i++) {
try {
uuid = UUID.fromString(uuidString);
@@ -122,7 +124,7 @@ public class EssentialsUpgrade {
break;
} catch (final Exception ex2) {
if (conf.getBoolean("npc", false)) {
uuid = UUID.nameUUIDFromBytes(("NPC:" + name).getBytes(Charsets.UTF_8));
uuid = UUID.nameUUIDFromBytes(("NPC:" + (ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name)).getBytes(Charsets.UTF_8));
break;
}
@@ -130,30 +132,66 @@ public class EssentialsUpgrade {
uuid = player.getUniqueId();
}
//noinspection ConstantConditions
if (uuid != null) {
countBukkit++;
break;
}
}
//noinspection ConstantConditions
if (uuid != null) {
conf.blockingSave();
config = new EssentialsUserConfiguration(name, uuid, new File(userdir, uuid + ".yml"));
config.convertLegacyFile();
ess.getUserMap().trackUUID(uuid, name, false);
ess.getUsers().loadUncachedUser(uuid);
continue;
}
countFails++;
}
}
ess.getUserMap().getUUIDMap().forceWriteUUIDMap();
ess.getLogger().info("Converted " + countFiles + "/" + countFiles + ". Conversion complete.");
ess.getLogger().info("Converted via cache: " + countEssCache + " :: Converted via lookup: " + countBukkit + " :: Failed to convert: " + countFails);
ess.getLogger().info("To rerun the conversion type /essentials uuidconvert");
}
public void convertMailList() {
private void updateRandomTeleport() {
if (doneFile.getBoolean("updateRandomTeleport", false)) {
return;
}
final EssentialsConfiguration config = ess.getRandomTeleport().getConfig();
if (config.getRootNode() != null) {
final LazyLocation center = config.getLocation("center");
final Location centerLoc = center != null ? center.location() : null;
if (center != null && centerLoc != null) {
final double minRange = config.getDouble("min-range", Double.MIN_VALUE);
final double maxRange = config.getDouble("max-range", Double.MIN_VALUE);
for (final World world : ess.getServer().getWorlds()) {
final String propPrefix = "locations." + world.getName() + ".";
config.setProperty(propPrefix + "center", centerLoc);
if (minRange != Double.MIN_VALUE) {
config.setProperty(propPrefix + "min-range", minRange);
}
if (maxRange != Double.MIN_VALUE) {
config.setProperty(propPrefix + "max-range", maxRange);
}
}
}
config.removeProperty("center");
config.blockingSave();
}
doneFile.setProperty("updateRandomTeleport", true);
doneFile.save();
ess.getLogger().info("Done converting random teleport config.");
}
private void convertMailList() {
if (doneFile.getBoolean("updateUsersMailList", false)) {
return;
}
@@ -193,7 +231,7 @@ public class EssentialsUpgrade {
ess.getLogger().info("Done converting mail list.");
}
public void convertStupidCamelCaseUserdataKeys() {
private void convertStupidCamelCaseUserdataKeys() {
if (doneFile.getBoolean("updateUsersStupidLegacyPathNames", false)) {
return;
}
@@ -273,7 +311,7 @@ public class EssentialsUpgrade {
* horrible economy code, as any operation which loads all user data into memory will load all these NPC accounts
* and spam the console with warnings."
*/
public void purgeBrokenNpcAccounts() {
private void purgeBrokenNpcAccounts() {
if (doneFile.getBoolean("updatePurgeBrokenNpcAccounts", false)) {
return;
}
@@ -384,7 +422,7 @@ public class EssentialsUpgrade {
ess.getLogger().info("#===========================================================================#");
}
public void convertIgnoreList() {
private void convertIgnoreList() {
final Pattern pattern = Pattern.compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
if (doneFile.getBoolean("updateUsersIgnoreListUUID", false)) {
return;
@@ -435,6 +473,10 @@ public class EssentialsUpgrade {
}
public void convertKits() {
if (Essentials.TESTING) {
return;
}
final Kits kits = ess.getKits();
final EssentialsConfiguration config = kits.getRootConfig();
if (doneFile.getBoolean("kitsyml", false)) {
@@ -492,7 +534,7 @@ public class EssentialsUpgrade {
doneFile.setProperty("move" + name + "ToFile", true);
doneFile.save();
} catch (final IOException e) {
ess.getLogger().log(Level.SEVERE, tl("upgradingFilesError"), e);
ess.getLogger().log(Level.SEVERE, ess.getAdventureFacet().miniToLegacy(tlLiteral("upgradingFilesError")), e);
}
}
@@ -527,10 +569,10 @@ public class EssentialsUpgrade {
bWriter.close();
if (needUpdate) {
if (!file.renameTo(new File(file.getParentFile(), file.getName().concat("." + System.currentTimeMillis() + ".upgradebackup")))) {
throw new Exception(tl("configFileMoveError"));
throw new Exception(tlLiteral("configFileMoveError"));
}
if (!tempFile.renameTo(file)) {
throw new Exception(tl("configFileRenameError"));
throw new Exception(tlLiteral("configFileRenameError"));
}
} else {
tempFile.delete();
@@ -655,15 +697,15 @@ public class EssentialsUpgrade {
final File tmpFile = new File(listOfFile.getParentFile(), sanitizedFilename + ".tmp");
final File newFile = new File(listOfFile.getParentFile(), sanitizedFilename);
if (!listOfFile.renameTo(tmpFile)) {
ess.getLogger().log(Level.WARNING, tl("userdataMoveError", filename, sanitizedFilename));
ess.getLogger().log(Level.WARNING, ess.getAdventureFacet().miniToLegacy(tlLiteral("userdataMoveError", filename, sanitizedFilename)));
continue;
}
if (newFile.exists()) {
ess.getLogger().log(Level.WARNING, tl("duplicatedUserdata", filename, sanitizedFilename));
ess.getLogger().log(Level.WARNING, ess.getAdventureFacet().miniToLegacy(tlLiteral("duplicatedUserdata", filename, sanitizedFilename)));
continue;
}
if (!tmpFile.renameTo(newFile)) {
ess.getLogger().log(Level.WARNING, tl("userdataMoveBackError", sanitizedFilename, sanitizedFilename));
ess.getLogger().log(Level.WARNING, ess.getAdventureFacet().miniToLegacy(tlLiteral("userdataMoveBackError", sanitizedFilename, sanitizedFilename)));
}
}
doneFile.setProperty("sanitizeAllUserFilenames", true);
@@ -679,7 +721,7 @@ public class EssentialsUpgrade {
return null;
}
public Location getFakeLocation(final CommentedConfigurationNode config, final String path) {
private Location getFakeLocation(final CommentedConfigurationNode config, final String path) {
final String worldName = config.getString((path != null ? path + "." : "") + "world");
if (worldName == null || worldName.isEmpty()) {
return null;
@@ -741,7 +783,7 @@ public class EssentialsUpgrade {
config.setProperty(entry.getKey(), loc);
}
if (!configFile.renameTo(new File(ess.getDataFolder(), "spawn.yml.old"))) {
throw new Exception(tl("fileRenameError", "spawn.yml"));
throw new Exception(tlLiteral("fileRenameError", "spawn.yml"));
}
config.blockingSave();
}
@@ -769,7 +811,7 @@ public class EssentialsUpgrade {
config.setProperty(entry.getKey(), loc);
}
if (!configFile.renameTo(new File(ess.getDataFolder(), "jail.yml.old"))) {
throw new Exception(tl("fileRenameError", "jail.yml"));
throw new Exception(tlLiteral("fileRenameError", "jail.yml"));
}
config.blockingSave();
}
@@ -843,7 +885,7 @@ public class EssentialsUpgrade {
doneFile.save();
}
public void banFormatChange() {
private void banFormatChange() {
if (doneFile.getBoolean("banFormatChange", false)) {
return;
}
@@ -913,77 +955,137 @@ public class EssentialsUpgrade {
Bukkit.getBanList(BanList.Type.NAME).addBan(playerName, banReason, banTimeout == 0 ? null : new Date(banTimeout), Console.NAME);
}
private void repairUserMap() {
if (doneFile.getBoolean("userMapRepaired", false)) {
private void generateUidCache() {
if (doneFile.getBoolean("newUidCacheBuilt", false)) {
return;
}
ess.getLogger().info("Starting usermap repair");
final File usermapFile = new File(ess.getDataFolder(), "usermap.bin");
final File uidsFile = new File(ess.getDataFolder(), "uuids.bin");
final File userdataFolder = new File(ess.getDataFolder(), "userdata");
if (!userdataFolder.isDirectory()) {
if (!userdataFolder.isDirectory() || usermapFile.exists() || uidsFile.exists()) {
ess.getLogger().warning("Missing userdata folder, aborting");
doneFile.setProperty("newUidCacheBuilt", true);
doneFile.save();
return;
}
final File[] files = userdataFolder.listFiles(YML_FILTER);
final DecimalFormat format = new DecimalFormat("#0.00");
final Map<String, UUID> names = Maps.newHashMap();
for (int index = 0; index < files.length; index++) {
final File file = files[index];
try {
UUID uuid = null;
final String filename = file.getName();
final String configData = new String(java.nio.file.Files.readAllBytes(file.toPath()), Charsets.UTF_8);
if (filename.length() > 36) {
try {
// ".yml" ending has 4 chars...
uuid = UUID.fromString(filename.substring(0, filename.length() - 4));
} catch (final IllegalArgumentException ignored) {
}
}
final Matcher uuidMatcher = PATTERN_CONFIG_UUID.matcher(configData);
if (uuidMatcher.find()) {
try {
uuid = UUID.fromString(uuidMatcher.group(1));
} catch (final IllegalArgumentException ignored) {
}
}
if (uuid == null) {
// Don't import
continue;
}
final Matcher nameMatcher = PATTERN_CONFIG_NAME.matcher(configData);
if (nameMatcher.find()) {
final String username = nameMatcher.group(1);
if (username != null && username.length() > 0) {
names.put(StringUtil.safeString(username), uuid);
}
}
if (index % 1000 == 0) {
ess.getLogger().info("Reading: " + format.format((100d * (double) index) / files.length)
+ "%");
}
} catch (final IOException e) {
ess.getLogger().log(Level.SEVERE, "Error while reading file: ", e);
try {
if (!usermapFile.createNewFile() || !uidsFile.createNewFile()) {
ess.getLogger().warning("Couldn't create usermap.bin or uuids.bin, aborting");
return;
}
final Map<UUID, Long> uuids = new HashMap<>();
final Map<String, UUID> nameToUuidMap = new HashMap<>();
final File[] files = userdataFolder.listFiles(YML_FILTER);
if (files != null) {
for (final File file : files) {
try {
final String fileName = file.getName();
final UUID uuid = UUID.fromString(fileName.substring(0, fileName.length() - 4));
final EssentialsConfiguration config = new EssentialsConfiguration(file);
config.load();
String name = config.getString("last-account-name", null);
name = ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name;
final long time = config.getLong("timestamps.logout", 0L);
if (name != null) {
if (nameToUuidMap.containsKey(name)) {
final UUID oldUuid = nameToUuidMap.get(name);
if (oldUuid.version() < uuid.version() || oldUuid.version() == uuid.version() && uuids.get(oldUuid) < time) {
ess.getLogger().warning("New UUID found for " + name + ": " + uuid + " (old: " + oldUuid + "). Replacing.");
uuids.remove(oldUuid);
} else {
ess.getLogger().warning("Found UUID for " + name + ": " + uuid + " (old: " + oldUuid + "). Skipping.");
continue;
}
}
uuids.put(uuid, time);
nameToUuidMap.put(name, uuid);
}
} catch (IllegalArgumentException | IndexOutOfBoundsException ignored) {
}
}
}
if (!nameToUuidMap.isEmpty()) {
ModernUUIDCache.writeNameUuidMap(usermapFile, nameToUuidMap);
}
if (!uuids.isEmpty()) {
ModernUUIDCache.writeUuidCache(uidsFile, uuids.keySet());
}
doneFile.setProperty("newUidCacheBuilt", true);
doneFile.save();
} catch (final IOException e) {
ess.getLogger().log(Level.SEVERE, "Error while generating initial uuids/names cache", e);
}
}
public void upgradeLang() {
if (doneFile.getBoolean("updateLegacyToAdventure", false)) {
return;
}
ess.getUserMap().getNames().putAll(names);
ess.getUserMap().reloadConfig();
ess.getLogger().log(Level.WARNING, "Beginning Adventure locale file conversion.");
doneFile.setProperty("userMapRepaired", true);
doneFile.save();
ess.getLogger().info("Completed usermap repair.");
try {
final File dataFolder = ess.getDataFolder();
if (!dataFolder.exists() || !dataFolder.isDirectory()) {
return;
}
final File backDir = new File(dataFolder, "msg-backups-" + System.currentTimeMillis());
if (backDir.exists() || !backDir.mkdir()) {
ess.getLogger().log(Level.SEVERE, "Unable to make msg-backups dir?!");
return;
}
final File messagesDir = new File(dataFolder, "messages");
//noinspection ResultOfMethodCallIgnored
messagesDir.mkdir();
final File[] files = dataFolder.listFiles();
boolean isThereAtLeastOneBackup = false;
if (files != null) {
for (final File file : files) {
if (file.getName().endsWith(".properties")) {
final File newFile = new File(messagesDir, file.getName());
final File backup = new File(backDir, file.getName());
Files.move(file, backup);
isThereAtLeastOneBackup = true;
final OrderedProperties properties = new OrderedProperties();
properties.load(Files.newReader(backup, Charsets.UTF_8));
for (final String key : properties.stringPropertyNames()) {
final String value = properties.getProperty(key);
properties.setProperty(key, ess.getAdventureFacet().legacyToMini(ess.getAdventureFacet().escapeTags(value), true));
}
properties.store(Files.newWriter(newFile, Charsets.UTF_8), null);
}
}
}
if (!isThereAtLeastOneBackup) {
backDir.delete();
}
doneFile.setProperty("updateLegacyToAdventure", true);
doneFile.save();
} catch (final Throwable e) {
ess.getLogger().log(Level.SEVERE, "Error while upgrading custom locales", e);
}
}
public void beforeSettings() {
if (Essentials.TESTING) {
return;
}
if (!ess.getDataFolder().exists()) {
ess.getDataFolder().mkdirs();
}
@@ -991,7 +1093,18 @@ public class EssentialsUpgrade {
moveMotdRulesToFile("rules");
}
public void preModules() {
if (Essentials.TESTING) {
return;
}
generateUidCache();
}
public void afterSettings() {
if (Essentials.TESTING) {
return;
}
sanitizeAllUserFilenames();
updateUsersPowerToolsFormat();
updateUsersHomesFormat();
@@ -1001,10 +1114,10 @@ public class EssentialsUpgrade {
uuidFileChange();
banFormatChange();
warnMetrics();
repairUserMap();
convertIgnoreList();
convertStupidCamelCaseUserdataKeys();
convertMailList();
purgeBrokenNpcAccounts();
updateRandomTeleport();
}
}
@@ -1,6 +1,8 @@
package com.earth2me.essentials;
import com.earth2me.essentials.adventure.AdventureUtil;
import net.ess3.api.IEssentials;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileInputStream;
@@ -13,25 +15,34 @@ import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.regex.Pattern;
public class I18n implements net.ess3.api.II18n {
private static final String MESSAGES = "messages";
private static final Pattern NODOUBLEMARK = Pattern.compile("''");
private static final ExecutorService BUNDLE_LOADER_EXECUTOR = Executors.newFixedThreadPool(2);
private static final ResourceBundle NULL_BUNDLE = new ResourceBundle() {
@SuppressWarnings("NullableProblems")
public Enumeration<String> getKeys() {
return null;
}
protected Object handleGetObject(final String key) {
protected Object handleGetObject(final @NotNull String key) {
return null;
}
};
@@ -40,30 +51,53 @@ public class I18n implements net.ess3.api.II18n {
private final transient ResourceBundle defaultBundle;
private final transient IEssentials ess;
private transient Locale currentLocale = defaultLocale;
private transient ResourceBundle customBundle;
private final transient Map<Locale, ResourceBundle> loadedBundles = new ConcurrentHashMap<>();
private final transient List<Locale> loadingBundles = new ArrayList<>();
private transient ResourceBundle localeBundle;
private transient Map<String, MessageFormat> messageFormatCache = new HashMap<>();
private final transient Map<Locale, Map<String, MessageFormat>> messageFormatCache = new HashMap<>();
public I18n(final IEssentials ess) {
this.ess = ess;
defaultBundle = ResourceBundle.getBundle(MESSAGES, Locale.ENGLISH, new UTF8PropertiesControl());
localeBundle = defaultBundle;
customBundle = NULL_BUNDLE;
}
public static String tl(final String string, final Object... objects) {
/**
* Translates a message using the server's configured locale.
* @param tlKey The translation key.
* @param objects Translation parameters, if applicable. Note: by default, these will not be parsed for MiniMessage.
* @return The translated message.
* @see AdventureUtil#parsed(String)
*/
public static String tlLiteral(final String tlKey, final Object... objects) {
if (instance == null) {
return "";
}
return tlLocale(instance.currentLocale, tlKey, objects);
}
/**
* Translates a message using the provided locale.
* @param locale The locale to translate the key to.
* @param tlKey The translation key.
* @param objects Translation parameters, if applicable. Note: by default, these will not be parsed for MiniMessage.
* @return The translated message.
* @see AdventureUtil#parsed(String)
*/
public static String tlLocale(final Locale locale, final String tlKey, final Object... objects) {
if (instance == null) {
return "";
}
if (objects.length == 0) {
return NODOUBLEMARK.matcher(instance.translate(string)).replaceAll("'");
return NODOUBLEMARK.matcher(instance.translate(locale, tlKey)).replaceAll("'");
} else {
return instance.format(string, objects);
return instance.format(locale, tlKey, objects);
}
}
public static String capitalCase(final String input) {
return input == null || input.length() == 0 ? input : input.toUpperCase(Locale.ENGLISH).charAt(0) + input.toLowerCase(Locale.ENGLISH).substring(1);
return input == null || input.isEmpty() ? input : input.toUpperCase(Locale.ENGLISH).charAt(0) + input.toLowerCase(Locale.ENGLISH).substring(1);
}
public void onEnable() {
@@ -79,52 +113,108 @@ public class I18n implements net.ess3.api.II18n {
return currentLocale;
}
private String translate(final String string) {
/**
* Returns the {@link ResourceBundle} for the given {@link Locale}, if loaded. If a bundle is requested which is
* not loaded, it will be loaded asynchronously and the default bundle will be returned in the meantime.
*/
private ResourceBundle getBundle(final Locale locale) {
if (loadedBundles.containsKey(locale)) {
return loadedBundles.get(locale);
} else {
synchronized (loadingBundles) {
if (!loadingBundles.contains(locale)) {
loadingBundles.add(locale);
BUNDLE_LOADER_EXECUTOR.submit(() -> {
blockingLoadBundle(locale);
synchronized (loadingBundles) {
loadingBundles.remove(locale);
}
});
}
}
return defaultBundle;
}
}
public void blockingLoadBundle(final Locale locale) {
if (!loadedBundles.containsKey(locale)) {
ResourceBundle bundle;
try {
bundle = ResourceBundle.getBundle(MESSAGES, locale, new FileResClassLoader(I18n.class.getClassLoader(), ess), new UTF8PropertiesControl());
} catch (MissingResourceException ex) {
try {
bundle = ResourceBundle.getBundle(MESSAGES, locale, new UTF8PropertiesControl());
} catch (MissingResourceException ex2) {
bundle = NULL_BUNDLE;
}
}
loadedBundles.put(locale, bundle);
}
}
private String translate(final Locale locale, final String string) {
try {
try {
return customBundle.getString(string);
return getBundle(locale).getString(string);
} catch (final MissingResourceException ex) {
return localeBundle.getString(string);
}
} catch (final MissingResourceException ex) {
if (ess == null || ess.getSettings().isDebug()) {
if (ess != null && ess.getSettings().isDebug()) {
ess.getLogger().log(Level.WARNING, String.format("Missing translation key \"%s\" in translation file %s", ex.getKey(), localeBundle.getLocale().toString()), ex);
}
return defaultBundle.getString(string);
}
}
public String format(final String string, final Object... objects) {
String format = translate(string);
MessageFormat messageFormat = messageFormatCache.get(format);
private String format(final Locale locale, final String string, final Object... objects) {
String format = translate(locale, string);
MessageFormat messageFormat = messageFormatCache.computeIfAbsent(locale, l -> new HashMap<>()).get(format);
if (messageFormat == null) {
try {
messageFormat = new MessageFormat(format);
} catch (final IllegalArgumentException e) {
ess.getLogger().log(Level.SEVERE, "Invalid Translation key for '" + string + "': " + e.getMessage());
format = format.replaceAll("\\{(\\D*?)\\}", "\\[$1\\]");
format = format.replaceAll("\\{(\\D*?)}", "\\[$1\\]");
messageFormat = new MessageFormat(format);
}
messageFormatCache.put(format, messageFormat);
messageFormatCache.get(locale).put(format, messageFormat);
}
return messageFormat.format(objects).replace(' ', ' '); // replace nbsp with a space
final Object[] processedArgs = mutateArgs(objects, arg -> {
if (arg instanceof AdventureUtil.ParsedPlaceholder) {
return arg.toString();
}
return ess.getAdventureFacet().legacyToMini(ess.getAdventureFacet().escapeTags(arg.toString()));
});
return messageFormat.format(processedArgs).replace(' ', ' '); // replace nbsp with a space
}
public static Object[] mutateArgs(final Object[] objects, final Function<Object, String> mutator) {
final Object[] args = new Object[objects.length];
for (int i = 0; i < objects.length; i++) {
final Object object = objects[i];
// MessageFormat will format these itself, troll face.
if (object instanceof Number || object instanceof Date || object == null) {
args[i] = object;
continue;
}
args[i] = mutator.apply(object);
}
return args;
}
public void updateLocale(final String loc) {
if (loc != null && !loc.isEmpty()) {
final String[] parts = loc.split("[_\\.]");
if (parts.length == 1) {
currentLocale = new Locale(parts[0]);
}
if (parts.length == 2) {
currentLocale = new Locale(parts[0], parts[1]);
}
if (parts.length == 3) {
currentLocale = new Locale(parts[0], parts[1], parts[2]);
}
currentLocale = getLocale(loc);
}
ResourceBundle.clearCache();
messageFormatCache = new HashMap<>();
loadedBundles.clear();
messageFormatCache.clear();
ess.getLogger().log(Level.INFO, String.format("Using locale %s", currentLocale.toString()));
try {
@@ -132,28 +222,41 @@ public class I18n implements net.ess3.api.II18n {
} catch (final MissingResourceException ex) {
localeBundle = NULL_BUNDLE;
}
}
try {
customBundle = ResourceBundle.getBundle(MESSAGES, currentLocale, new FileResClassLoader(I18n.class.getClassLoader(), ess), new UTF8PropertiesControl());
} catch (final MissingResourceException ex) {
customBundle = NULL_BUNDLE;
public static Locale getLocale(final String loc) {
if (loc == null || loc.isEmpty()) {
return instance.currentLocale;
}
final String[] parts = loc.split("[_.]");
if (parts.length == 1) {
return new Locale(parts[0]);
}
if (parts.length == 2) {
return new Locale(parts[0], parts[1]);
}
if (parts.length == 3) {
return new Locale(parts[0], parts[1], parts[2]);
}
return instance.currentLocale;
}
/**
* Attempts to load properties files from the plugin directory before falling back to the jar.
*/
private static class FileResClassLoader extends ClassLoader {
private final transient File dataFolder;
private final transient File messagesFolder;
FileResClassLoader(final ClassLoader classLoader, final IEssentials ess) {
super(classLoader);
this.dataFolder = ess.getDataFolder();
this.messagesFolder = new File(ess.getDataFolder(), "messages");
//noinspection ResultOfMethodCallIgnored
this.messagesFolder.mkdirs();
}
@Override
public URL getResource(final String string) {
final File file = new File(dataFolder, string);
final File file = new File(messagesFolder, string);
if (file.exists()) {
try {
return file.toURI().toURL();
@@ -165,7 +268,7 @@ public class I18n implements net.ess3.api.II18n {
@Override
public InputStream getResourceAsStream(final String string) {
final File file = new File(dataFolder, string);
final File file = new File(messagesFolder, string);
if (file.exists()) {
try {
return new FileInputStream(file);
@@ -180,7 +283,7 @@ public class I18n implements net.ess3.api.II18n {
* Reads .properties files as UTF-8 instead of ISO-8859-1, which is the default on Java 8/below.
* Java 9 fixes this by defaulting to UTF-8 for .properties files.
*/
private static class UTF8PropertiesControl extends ResourceBundle.Control {
private static final class UTF8PropertiesControl extends ResourceBundle.Control {
public ResourceBundle newBundle(final String baseName, final Locale locale, final String format, final ClassLoader loader, final boolean reload) throws IOException {
final String resourceName = toResourceName(toBundleName(baseName, locale), "properties");
ResourceBundle bundle = null;
@@ -207,5 +310,13 @@ public class I18n implements net.ess3.api.II18n {
}
return bundle;
}
@Override
public Locale getFallbackLocale(String baseName, Locale locale) {
if (baseName == null || locale == null) {
throw new NullPointerException();
}
return null;
}
}
}
@@ -1,5 +1,6 @@
package com.earth2me.essentials;
import com.earth2me.essentials.adventure.AdventureFacet;
import com.earth2me.essentials.api.IItemDb;
import com.earth2me.essentials.api.IJails;
import com.earth2me.essentials.api.IWarps;
@@ -7,20 +8,8 @@ import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.commands.PlayerNotFoundException;
import com.earth2me.essentials.perm.PermissionsHandler;
import com.earth2me.essentials.updatecheck.UpdateChecker;
import net.ess3.nms.refl.providers.ReflOnlineModeProvider;
import net.ess3.provider.ContainerProvider;
import net.ess3.provider.FormattedCommandAliasProvider;
import net.ess3.provider.ItemUnbreakableProvider;
import net.ess3.provider.KnownCommandsProvider;
import net.ess3.provider.MaterialTagProvider;
import net.ess3.provider.PersistentDataProvider;
import net.ess3.provider.SerializationProvider;
import net.ess3.provider.ServerStateProvider;
import net.ess3.provider.SignDataProvider;
import net.ess3.provider.SpawnerBlockProvider;
import net.ess3.provider.SpawnerItemProvider;
import net.ess3.provider.SyncCommandsProvider;
import net.ess3.provider.WorldInfoProvider;
import com.earth2me.essentials.userstorage.IUserMap;
import net.ess3.provider.Provider;
import net.essentialsx.api.v2.services.BalanceTop;
import net.essentialsx.api.v2.services.mail.MailService;
import org.bukkit.Server;
@@ -79,10 +68,22 @@ public interface IEssentials extends Plugin {
int broadcastMessage(String permission, String message);
void broadcastTl(String tlKey, Object... args);
void broadcastTl(IUser sender, String tlKey, Object... args);
void broadcastTl(IUser sender, String permission, String tlKey, Object... args);
void broadcastTl(IUser sender, Predicate<IUser> shouldExclude, String tlKey, Object... args);
void broadcastTl(IUser sender, Predicate<IUser> shouldExclude, boolean parseKeywords, String tlKey, Object... args);
ISettings getSettings();
BukkitScheduler getScheduler();
List<Player> getJailedPlayers();
IJails getJails();
IWarps getWarps();
@@ -109,8 +110,6 @@ public interface IEssentials extends Plugin {
int scheduleSyncRepeatingTask(Runnable run, long delay, long period);
TNTExplodeListener getTNTListener();
PermissionsHandler getPermissionsHandler();
AlternativeCommandsHandler getAlternativeCommandsHandler();
@@ -119,6 +118,9 @@ public interface IEssentials extends Plugin {
IItemDb getItemDb();
IUserMap getUsers();
@Deprecated
UserMap getUserMap();
BalanceTop getBalanceTop();
@@ -140,33 +142,13 @@ public interface IEssentials extends Plugin {
Iterable<User> getOnlineUsers();
SpawnerItemProvider getSpawnerItemProvider();
SpawnerBlockProvider getSpawnerBlockProvider();
ServerStateProvider getServerStateProvider();
MaterialTagProvider getMaterialTagProvider();
ContainerProvider getContainerProvider();
KnownCommandsProvider getKnownCommandsProvider();
SerializationProvider getSerializationProvider();
FormattedCommandAliasProvider getFormattedCommandAliasProvider();
SyncCommandsProvider getSyncCommandsProvider();
PersistentDataProvider getPersistentDataProvider();
ReflOnlineModeProvider getOnlineModeProvider();
ItemUnbreakableProvider getItemUnbreakableProvider();
WorldInfoProvider getWorldInfoProvider();
SignDataProvider getSignDataProvider();
PluginCommand getPluginCommand(String cmd);
ProviderFactory getProviders();
default <P extends Provider> P provider(final Class<P> providerClass) {
return getProviders().get(providerClass);
}
AdventureFacet getAdventureFacet();
}
@@ -3,6 +3,7 @@ package com.earth2me.essentials;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.signs.EssentialsSign;
import com.earth2me.essentials.textreader.IText;
import net.essentialsx.api.v2.ChatType;
import org.bukkit.Material;
import org.bukkit.event.EventPriority;
import org.spongepowered.configurate.CommentedConfigurationNode;
@@ -36,6 +37,8 @@ public interface ISettings extends IConf {
String getChatFormat(String group);
String getChatFormat(String group, ChatType chatType);
String getWorldAlias(String world);
int getChatRadius();
@@ -52,6 +55,8 @@ public interface ISettings extends IConf {
boolean isChatQuestionEnabled();
boolean isUsePaperChatEvent();
BigDecimal getCommandCost(IEssentialsCommand cmd);
BigDecimal getCommandCost(String label);
@@ -72,6 +77,8 @@ public interface ISettings extends IConf {
boolean isSocialSpyMessages();
boolean isSocialSpyDisplayNames();
Set<String> getMuteCommands();
@Deprecated
@@ -81,10 +88,14 @@ public interface ISettings extends IConf {
String getLocale();
boolean isPerPlayerLocale();
String getNewbieSpawn();
String getNicknamePrefix();
boolean isResetNickOnNameChange();
String getOperatorColor() throws Exception;
boolean getPerWarpPermission();
@@ -95,12 +106,18 @@ public interface ISettings extends IConf {
List<Material> getProtectList(final String configName);
List<String> getProtectListRaw(final String configName);
boolean getProtectPreventSpawn(final String creatureName);
String getProtectString(final String configName);
boolean getRespawnAtHome();
String getRandomSpawnLocation();
String getRandomRespawnLocation();
boolean isRespawnAtAnchor();
Set getMultipleHomes();
@@ -168,6 +185,8 @@ public interface ISettings extends IConf {
boolean isEcoLogEnabled();
boolean isEcoLogUUIDEnabled();
boolean isEcoLogUpdateEnabled();
boolean realNamesOnList();
@@ -192,7 +211,9 @@ public interface ISettings extends IConf {
long getAutoAfk();
long getAutoAfkKick();
long getAutoAfkTimeout();
List<String> getAfkTimeoutCommands();
boolean getFreezeAfkPlayers();
@@ -202,6 +223,8 @@ public interface ISettings extends IConf {
boolean cancelAfkOnChat();
boolean cancelAfkOnFish();
boolean sleepIgnoresAfkPlayers();
boolean sleepIgnoresVanishedPlayers();
@@ -282,6 +305,8 @@ public interface ISettings extends IConf {
int getMaxUserCacheCount();
long getMaxUserCacheValueExpiry();
boolean allowSilentJoinQuit();
boolean isCustomJoinMessage();
@@ -298,6 +323,8 @@ public interface ISettings extends IConf {
boolean isCustomServerFullMessage();
boolean isCustomWhitelistMessage();
boolean isNotifyNoNewMail();
boolean isDropItemsIfFull();
@@ -330,6 +357,10 @@ public interface ISettings extends IConf {
boolean isWorldChangeFlyResetEnabled();
boolean isWorldChangePreserveFlying();
boolean isGamemodeChangePreserveFlying();
boolean isWorldChangeSpeedResetEnabled();
long getCommandCooldownMs(String label);
@@ -386,6 +417,8 @@ public interface ISettings extends IConf {
boolean logCommandBlockCommands();
boolean logConsoleCommands();
Set<Predicate<String>> getNickBlacklist();
double getMaxProjectileSpeed();
@@ -404,6 +437,22 @@ public interface ISettings extends IConf {
boolean showZeroBaltop();
String getNickRegex();
BigDecimal getMultiplier(final User user);
int getMaxItemLore();
String getPrimaryColor();
String getSecondaryColor();
BigDecimal getBaltopMinBalance();
long getBaltopMinPlaytime();
int getBaltopEntryLimit();
enum KeepInvPolicy {
KEEP,
DELETE,
@@ -1,9 +1,9 @@
package com.earth2me.essentials;
import com.earth2me.essentials.adventure.ComponentHolder;
import com.earth2me.essentials.api.IAsyncTeleport;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.config.entities.CommandCooldown;
import net.ess3.api.ITeleport;
import net.ess3.api.MaxMoneyException;
import net.ess3.api.events.AfkStatusChangeEvent;
import net.essentialsx.api.v2.services.mail.MailMessage;
@@ -15,7 +15,6 @@ import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -33,6 +32,8 @@ import java.util.regex.Pattern;
public interface IUser {
boolean isAuthorized(String node);
boolean isAuthorizedCached(String node);
boolean isAuthorized(IEssentialsCommand cmd);
boolean isAuthorized(IEssentialsCommand cmd, String permissionPrefix);
@@ -69,13 +70,9 @@ public interface IUser {
* @return whether there is a teleport request
*/
@Deprecated
boolean hasOutstandingTeleportRequest();
/**
* @deprecated This API is not asynchronous. Use {@link com.earth2me.essentials.api.IAsyncTeleport IAsyncTeleport} with {@link IUser#getAsyncTeleport()}
*/
@Deprecated
ITeleport getTeleport();
default boolean hasOutstandingTeleportRequest() {
return getNextTpaRequest(false, false, false) != null;
}
IAsyncTeleport getAsyncTeleport();
@@ -142,6 +139,14 @@ public interface IUser {
void sendMessage(String message);
void sendComponent(ComponentHolder component);
ComponentHolder tlComponent(String tlKey, Object... args);
String playerTl(String tlKey, Object... args);
void sendTl(String tlKey, Object... args);
/*
* UserData
*/
@@ -155,6 +160,8 @@ public interface IUser {
void delHome(String name) throws Exception;
void renameHome(String name, String newName) throws Exception;
boolean hasHome();
Location getLastLocation();
@@ -171,6 +178,17 @@ public interface IUser {
String getFormattedJailTime();
/**
* Returns last activity time.
* <p>
* It is used internally to determine if user's afk status should be set to
* true because of ACTIVITY {@link AfkStatusChangeEvent.Cause}, or the player
* should be kicked for being afk too long.
*
* @return Last activity time (Epoch Milliseconds)
*/
long getLastActivityTime();
@Deprecated
List<String> getMails();
@@ -330,4 +348,12 @@ public interface IUser {
this.time = time;
}
}
List<String> getPastUsernames();
void addPastUsername(String username);
boolean isFreeze();
void setFreeze(boolean freeze);
}
@@ -5,6 +5,7 @@ import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.config.entities.LazyLocation;
import net.ess3.api.IEssentials;
import net.ess3.api.IUser;
import net.ess3.api.TranslatableException;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
@@ -35,10 +36,10 @@ import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class Jails implements net.ess3.api.IJails {
private static transient boolean enabled = false;
private static boolean enabled = false;
private final IEssentials ess;
private final EssentialsConfiguration config;
private final Map<String, LazyLocation> jails = new HashMap<>();
@@ -102,17 +103,17 @@ public class Jails implements net.ess3.api.IJails {
@Override
public Location getJail(String jailName) throws Exception {
if (jailName == null) {
throw new Exception(tl("jailNotExist"));
throw new TranslatableException("jailNotExist");
}
jailName = jailName.toLowerCase(Locale.ENGLISH);
synchronized (jails) {
if (!jails.containsKey(jailName)) {
throw new Exception(tl("jailNotExist"));
throw new TranslatableException("jailNotExist");
}
final Location location = jails.get(jailName).location();
if (location == null) {
throw new Exception(tl("jailWorldNotExist"));
throw new TranslatableException("jailWorldNotExist");
}
return location;
}
@@ -140,28 +141,6 @@ public class Jails implements net.ess3.api.IJails {
}
}
/**
* @deprecated This method does not use asynchronous teleportation. Use {@link Jails#sendToJail(IUser, String, CompletableFuture)}
*/
@SuppressWarnings("deprecation")
@Override
@Deprecated
public void sendToJail(final IUser user, String jail) throws Exception {
if (jail == null || jail.isEmpty()) {
return;
}
jail = jail.toLowerCase(Locale.ENGLISH);
synchronized (jails) {
if (jails.containsKey(jail)) {
if (user.getBase().isOnline()) {
user.getTeleport().now(getJail(jail), false, TeleportCause.COMMAND);
}
user.setJail(jail);
}
}
}
@Override
public void sendToJail(final IUser user, final String jailName, final CompletableFuture<Boolean> future) throws Exception {
if (jailName == null || jailName.isEmpty()) {
@@ -203,6 +182,10 @@ public class Jails implements net.ess3.api.IJails {
private class JailListener implements Listener {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onJailBlockBreak(final BlockBreakEvent event) {
if (shouldIgnore(event.getPlayer())) {
return;
}
final User user = ess.getUser(event.getPlayer());
if (user.isJailed() && !user.isAuthorized("essentials.jail.allow-break")) {
event.setCancelled(true);
@@ -211,6 +194,10 @@ public class Jails implements net.ess3.api.IJails {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onJailBlockPlace(final BlockPlaceEvent event) {
if (shouldIgnore(event.getPlayer())) {
return;
}
final User user = ess.getUser(event.getPlayer());
if (user.isJailed() && !user.isAuthorized("essentials.jail.allow-place")) {
event.setCancelled(true);
@@ -219,6 +206,10 @@ public class Jails implements net.ess3.api.IJails {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onJailBlockDamage(final BlockDamageEvent event) {
if (shouldIgnore(event.getPlayer())) {
return;
}
final User user = ess.getUser(event.getPlayer());
if (user.isJailed() && !user.isAuthorized("essentials.jail.allow-block-damage")) {
event.setCancelled(true);
@@ -232,6 +223,9 @@ public class Jails implements net.ess3.api.IJails {
}
final Entity damager = event.getDamager();
if (damager.getType() == EntityType.PLAYER) {
if (shouldIgnore((Player) damager)) {
return;
}
final User user = ess.getUser((Player) damager);
if (user != null && user.isJailed()) {
event.setCancelled(true);
@@ -241,6 +235,10 @@ public class Jails implements net.ess3.api.IJails {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onJailPlayerInteract(final PlayerInteractEvent event) {
if (shouldIgnore(event.getPlayer())) {
return;
}
final User user = ess.getUser(event.getPlayer());
if (user.isJailed() && !user.isAuthorized("essentials.jail.allow-interact")) {
event.setCancelled(true);
@@ -249,6 +247,10 @@ public class Jails implements net.ess3.api.IJails {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onJailPlayerGameModeChange(final PlayerGameModeChangeEvent event) {
if (shouldIgnore(event.getPlayer())) {
return;
}
final User user = ess.getUser(event.getPlayer());
if (user.isJailed()) {
event.setCancelled(true);
@@ -257,6 +259,10 @@ public class Jails implements net.ess3.api.IJails {
@EventHandler(priority = EventPriority.HIGHEST)
public void onJailPlayerRespawn(final PlayerRespawnEvent event) {
if (shouldIgnore(event.getPlayer())) {
return;
}
final User user = ess.getUser(event.getPlayer());
if (!user.isJailed() || user.getJail() == null || user.getJail().isEmpty()) {
return;
@@ -266,15 +272,19 @@ public class Jails implements net.ess3.api.IJails {
event.setRespawnLocation(getJail(user.getJail()));
} catch (final Exception ex) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, tl("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage()), ex);
ess.getLogger().log(Level.INFO, ess.getAdventureFacet().miniToLegacy(tlLiteral("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage())), ex);
} else {
ess.getLogger().log(Level.INFO, tl("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage()));
ess.getLogger().log(Level.INFO, ess.getAdventureFacet().miniToLegacy(tlLiteral("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage())));
}
}
}
@EventHandler(priority = EventPriority.HIGH)
public void onJailPlayerTeleport(final PlayerTeleportEvent event) {
if (shouldIgnore(event.getPlayer())) {
return;
}
final User user = ess.getUser(event.getPlayer());
if (!user.isJailed() || user.getJail() == null || user.getJail().isEmpty()) {
return;
@@ -284,16 +294,20 @@ public class Jails implements net.ess3.api.IJails {
event.setTo(getJail(user.getJail()));
} catch (final Exception ex) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, tl("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage()), ex);
ess.getLogger().log(Level.INFO, ess.getAdventureFacet().miniToLegacy(tlLiteral("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage())), ex);
} else {
ess.getLogger().log(Level.INFO, tl("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage()));
ess.getLogger().log(Level.INFO, ess.getAdventureFacet().miniToLegacy(tlLiteral("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage())));
}
}
user.sendMessage(tl("jailMessage"));
user.sendTl("jailMessage");
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onJailPlayerJoin(final PlayerJoinEvent event) {
if (shouldIgnore(event.getPlayer())) {
return;
}
final User user = ess.getUser(event.getPlayer());
final long currentTime = System.currentTimeMillis();
user.checkJailTimeout(currentTime);
@@ -304,13 +318,13 @@ public class Jails implements net.ess3.api.IJails {
final CompletableFuture<Boolean> future = new CompletableFuture<>();
future.exceptionally(ex -> {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, tl("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage()), ex);
ess.getLogger().log(Level.INFO, ess.getAdventureFacet().miniToLegacy(tlLiteral("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage())), ex);
} else {
ess.getLogger().log(Level.INFO, tl("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage()));
ess.getLogger().log(Level.INFO, ess.getAdventureFacet().miniToLegacy(tlLiteral("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage())));
}
return false;
});
future.thenAccept(success -> user.sendMessage(tl("jailMessage")));
future.thenAccept(success -> user.sendTl("jailMessage"));
try {
sendToJail(user, user.getJail(), future);
@@ -318,5 +332,10 @@ public class Jails implements net.ess3.api.IJails {
future.completeExceptionally(ex);
}
}
private boolean shouldIgnore(final Player base) {
// Ignore Citizens NPCs
return base.getUniqueId().version() == 2 || base.hasMetadata("NPC");
}
}
}
@@ -2,30 +2,33 @@ package com.earth2me.essentials;
import com.earth2me.essentials.Trade.OverflowType;
import com.earth2me.essentials.commands.NoChargeException;
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.textreader.IText;
import com.earth2me.essentials.textreader.KeywordReplacer;
import com.earth2me.essentials.textreader.SimpleTextInput;
import com.earth2me.essentials.utils.DateUtil;
import com.earth2me.essentials.utils.MaterialUtil;
import com.earth2me.essentials.utils.NumberUtil;
import net.ess3.api.IEssentials;
import net.ess3.api.TranslatableException;
import net.ess3.api.events.KitClaimEvent;
import net.ess3.provider.SerializationProvider;
import net.essentialsx.api.v2.events.KitPreExpandItemsEvent;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class Kit {
final IEssentials ess;
@@ -40,7 +43,7 @@ public class Kit {
this.charge = new Trade("kit-" + kitName, new Trade("kit-kit", ess), ess);
if (kit == null) {
throw new Exception(tl("kitNotFound"));
throw new TranslatableException("kitNotFound");
}
}
@@ -50,7 +53,7 @@ public class Kit {
public void checkPerms(final User user) throws Exception {
if (!user.isAuthorized("essentials.kits." + kitName)) {
throw new Exception(tl("noKitPermission", "essentials.kits." + kitName));
throw new TranslatableException("noKitPermission", "essentials.kits." + kitName);
}
}
@@ -59,10 +62,10 @@ public class Kit {
if (nextUse == 0L) {
} else if (nextUse < 0L) {
user.sendMessage(tl("kitOnce"));
user.sendTl("kitOnce");
throw new NoChargeException();
} else {
user.sendMessage(tl("kitTimed", DateUtil.formatDateDiff(nextUse)));
user.sendTl("kitTimed", DateUtil.formatDateDiff(nextUse));
throw new NoChargeException();
}
}
@@ -96,7 +99,7 @@ public class Kit {
// Make sure delay is valid
delay = kit.containsKey("delay") ? ((Number) kit.get("delay")).doubleValue() : 0.0d;
} catch (final Exception e) {
throw new Exception(tl("kitError2"));
throw new TranslatableException("kitError2");
}
// When was the last kit used?
@@ -130,7 +133,7 @@ public class Kit {
public List<String> getItems() throws Exception {
if (kit == null) {
throw new Exception(tl("kitNotFound"));
throw new TranslatableException("kitNotFound");
}
try {
final List<String> itemList = new ArrayList<>();
@@ -148,7 +151,7 @@ public class Kit {
throw new Exception("Invalid item list");
} catch (final Exception e) {
ess.getLogger().log(Level.WARNING, "Error parsing kit " + kitName + ": " + e.getMessage());
throw new Exception(tl("kitError2"), e);
throw new TranslatableException(e,"kitError2");
}
}
@@ -171,10 +174,11 @@ public class Kit {
final boolean allowUnsafe = ess.getSettings().allowUnsafeEnchantments();
final boolean autoEquip = ess.getSettings().isKitAutoEquip();
final List<ItemStack> itemList = new ArrayList<>();
final Map<Integer, ItemStack> itemsWithSlot = new HashMap<>();
final List<String> commandQueue = new ArrayList<>();
final List<String> moneyQueue = new ArrayList<>();
final String currencySymbol = ess.getSettings().getCurrencySymbol().isEmpty() ? "$" : ess.getSettings().getCurrencySymbol();
for (final String kitItem : output.getLines()) {
for (String kitItem : output.getLines()) {
if (kitItem.startsWith("$") || kitItem.startsWith(currencySymbol)) {
moneyQueue.add(NumberUtil.sanitizeCurrencyString(kitItem, ess));
continue;
@@ -188,14 +192,25 @@ public class Kit {
continue;
}
int itemSlot = -1;
if (kitItem.startsWith("slot:")) {
final int spaceIndex = kitItem.indexOf(" ");
if (spaceIndex != -1) {
final String slotStr = kitItem.substring("slot:".length(), spaceIndex);
itemSlot = NumberUtil.isInt(slotStr) ? Integer.parseInt(slotStr) : -1;
kitItem = kitItem.substring(spaceIndex + 1);
}
}
final ItemStack stack;
final SerializationProvider serializationProvider = ess.provider(SerializationProvider.class);
if (kitItem.startsWith("@")) {
if (ess.getSerializationProvider() == null) {
ess.getLogger().log(Level.WARNING, tl("kitError3", kitName, user.getName()));
if (serializationProvider == null) {
ess.getLogger().log(Level.WARNING, ess.getAdventureFacet().miniToLegacy(tlLiteral("kitError3", kitName, user.getName())));
continue;
}
stack = ess.getSerializationProvider().deserializeItem(Base64Coder.decodeLines(kitItem.substring(1)));
stack = serializationProvider.deserializeItem(Base64Coder.decodeLines(kitItem.substring(1)));
} else {
final String[] parts = kitItem.split(" +");
final ItemStack parseStack = ess.getItemDb().get(parts[0], parts.length > 1 ? Integer.parseInt(parts[1]) : 1);
@@ -214,57 +229,51 @@ public class Kit {
stack = metaStack.getItemStack();
}
if (autoEquip) {
final Material material = stack.getType();
final PlayerInventory inventory = user.getBase().getInventory();
if (MaterialUtil.isHelmet(material) && isEmptyStack(inventory.getHelmet())) {
inventory.setHelmet(stack);
continue;
} else if (MaterialUtil.isChestplate(material) && isEmptyStack(inventory.getChestplate())) {
inventory.setChestplate(stack);
continue;
} else if (MaterialUtil.isLeggings(material) && isEmptyStack(inventory.getLeggings())) {
inventory.setLeggings(stack);
continue;
} else if (MaterialUtil.isBoots(material) && isEmptyStack(inventory.getBoots())) {
inventory.setBoots(stack);
continue;
}
if (itemSlot == -1 || itemsWithSlot.containsKey(itemSlot)) {
itemList.add(stack);
} else {
itemsWithSlot.put(itemSlot, stack);
}
itemList.add(stack);
}
final Map<Integer, ItemStack> overfilled;
final boolean allowOversizedStacks = user.isAuthorized("essentials.oversizedstacks");
final int maxStackSize = user.isAuthorized("essentials.oversizedstacks") ? ess.getSettings().getOversizedStackSize() : 0;
final boolean isDropItemsIfFull = ess.getSettings().isDropItemsIfFull();
if (isDropItemsIfFull) {
if (allowOversizedStacks) {
overfilled = InventoryWorkaround.addOversizedItems(user.getBase().getInventory(), ess.getSettings().getOversizedStackSize(), itemList.toArray(new ItemStack[0]));
} else {
overfilled = InventoryWorkaround.addItems(user.getBase().getInventory(), itemList.toArray(new ItemStack[0]));
final List<ItemStack> totalItems = new ArrayList<>(itemList);
totalItems.addAll(itemsWithSlot.values());
final KitPreExpandItemsEvent itemsEvent = new KitPreExpandItemsEvent(user, kitName, totalItems);
Bukkit.getPluginManager().callEvent(itemsEvent);
final ItemStack[] totalItemsArray = totalItems.toArray(new ItemStack[0]);
if (!isDropItemsIfFull && !Inventories.hasSpace(user.getBase(), maxStackSize, autoEquip, totalItemsArray)) {
user.sendTl("kitInvFullNoDrop");
return false;
}
for (Map.Entry<Integer, ItemStack> itemWithSlot : itemsWithSlot.entrySet()) {
final ItemStack leftover = Inventories.addItem(user.getBase(), maxStackSize, itemWithSlot.getValue(), itemWithSlot.getKey());
if (leftover != null) {
itemList.add(leftover);
}
for (final ItemStack itemStack : overfilled.values()) {
int spillAmount = itemStack.getAmount();
if (!allowOversizedStacks) {
}
final ItemStack[] itemArray = itemList.toArray(new ItemStack[0]);
final Map<Integer, ItemStack> leftover = Inventories.addItem(user.getBase(), maxStackSize, autoEquip, itemArray);
if (!isDropItemsIfFull && !leftover.isEmpty()) {
// Inventories#hasSpace should prevent this state from EVER being reached; If it does, something has gone terribly wrong, and we should just give up and hope people report it :(
throw new IllegalStateException("Something has gone terribly wrong while adding items to the user's inventory. Please report this to the EssentialsX developers. Items left over: " + leftover + ". Original items: " + Arrays.toString(itemArray));
}
for (final ItemStack itemStack : leftover.values()) {
int spillAmount = itemStack.getAmount();
while (spillAmount > 0) {
if (maxStackSize != 0) {
itemStack.setAmount(Math.min(spillAmount, itemStack.getMaxStackSize()));
}
while (spillAmount > 0) {
user.getWorld().dropItemNaturally(user.getLocation(), itemStack);
spillAmount -= itemStack.getAmount();
}
spew = true;
}
} else {
if (allowOversizedStacks) {
overfilled = InventoryWorkaround.addAllOversizedItems(user.getBase().getInventory(), ess.getSettings().getOversizedStackSize(), itemList.toArray(new ItemStack[0]));
} else {
overfilled = InventoryWorkaround.addAllItems(user.getBase().getInventory(), itemList.toArray(new ItemStack[0]));
}
if (overfilled != null) {
user.sendMessage(tl("kitInvFullNoDrop"));
return false;
user.getWorld().dropItemNaturally(user.getLocation(), itemStack);
spillAmount -= itemStack.getAmount();
}
spew = true;
}
user.getBase().updateInventory();
@@ -282,17 +291,13 @@ public class Kit {
}
if (spew) {
user.sendMessage(tl("kitInvFull"));
user.sendTl("kitInvFull");
}
} catch (final Exception e) {
user.getBase().updateInventory();
ess.getLogger().log(Level.WARNING, e.getMessage());
throw new Exception(tl("kitError2"), e);
throw new TranslatableException(e, "kitError2");
}
return true;
}
private boolean isEmptyStack(ItemStack stack) {
return stack == null || MaterialUtil.isAir(stack.getType());
}
}
@@ -3,6 +3,7 @@ package com.earth2me.essentials;
import com.earth2me.essentials.config.ConfigurateUtil;
import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.utils.NumberUtil;
import net.ess3.api.TranslatableException;
import org.spongepowered.configurate.CommentedConfigurationNode;
import java.io.File;
@@ -14,7 +15,6 @@ import java.util.Map;
import java.util.Set;
import static com.earth2me.essentials.I18n.capitalCase;
import static com.earth2me.essentials.I18n.tl;
public class Kits implements IConf {
private final IEssentials ess;
@@ -145,7 +145,7 @@ public class Kits implements IConf {
String name = capitalCase(kitItem);
final BigDecimal costPrice = new Trade("kit-" + kitItem.toLowerCase(Locale.ENGLISH), ess).getCommandCost(user);
if (costPrice.signum() > 0) {
cost = tl("kitCost", NumberUtil.displayCurrency(costPrice, ess));
cost = user.playerTl("kitCost", NumberUtil.displayCurrency(costPrice, ess));
}
final Kit kit = new Kit(kitItem, ess);
@@ -153,7 +153,7 @@ public class Kits implements IConf {
if (nextUse == -1 && ess.getSettings().isSkippingUsedOneTimeKitsFromKitList()) {
continue;
} else if (nextUse != 0) {
name = tl("kitDelay", name);
name = user.playerTl("kitDelay", name);
}
list.append(" ").append(name).append(cost);
@@ -161,7 +161,7 @@ public class Kits implements IConf {
}
return list.toString().trim();
} catch (final Exception ex) {
throw new Exception(tl("kitError"), ex);
throw new TranslatableException(ex, "kitError");
}
}
@@ -1,16 +1,18 @@
package com.earth2me.essentials;
import net.ess3.api.IUser;
import net.essentialsx.api.v2.services.mail.MailService;
import net.essentialsx.api.v2.events.UserMailEvent;
import net.essentialsx.api.v2.services.mail.MailMessage;
import net.essentialsx.api.v2.services.mail.MailSender;
import net.essentialsx.api.v2.services.mail.MailService;
import org.bukkit.Bukkit;
import org.bukkit.plugin.ServicePriority;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class MailServiceImpl implements MailService {
private final transient ThreadLocal<SimpleDateFormat> df = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy/MM/dd HH:mm"));
@@ -35,6 +37,12 @@ public class MailServiceImpl implements MailService {
}
private void sendMail(IUser recipient, MailMessage message) {
final UserMailEvent event = new UserMailEvent(recipient, message);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
final ArrayList<MailMessage> messages = recipient.getMailMessages();
messages.add(0, message);
recipient.setMailList(messages);
@@ -44,10 +52,28 @@ public class MailServiceImpl implements MailService {
public String getMailLine(MailMessage mail) {
final String message = mail.getMessage();
if (mail.isLegacy()) {
return tl("mailMessage", message);
return tlLiteral("mailMessage", message);
}
final String expire = mail.getTimeExpire() != 0 ? "Timed" : "";
return tl((mail.isRead() ? "mailFormatNewRead" : "mailFormatNew") + expire, df.get().format(new Date(mail.getTimeSent())), mail.getSenderUsername(), message);
return tlLiteral((mail.isRead() ? "mailFormatNewRead" : "mailFormatNew") + expire, df.get().format(new Date(mail.getTimeSent())), mail.getSenderUsername(), message);
}
@Override
public String getMailTlKey(MailMessage message) {
if (message.isLegacy()) {
return "mailMessage";
}
final String expire = message.getTimeExpire() != 0 ? "Timed" : "";
return (message.isRead() ? "mailFormatNewRead" : "mailFormatNew") + expire;
}
@Override
public Object[] getMailTlArgs(MailMessage message) {
if (message.isLegacy()) {
return new Object[] {message.getMessage()};
}
return new Object[] {df.get().format(new Date(message.getTimeSent())), message.getSenderUsername(), message.getMessage()};
}
}
@@ -22,7 +22,7 @@ import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class ManagedFile {
private static final int BUFFERSIZE = 1024 * 8;
@@ -45,7 +45,7 @@ public class ManagedFile {
try {
copyResourceAscii("/" + filename, file);
} catch (final IOException ex) {
Essentials.getWrappedLogger().log(Level.SEVERE, tl("itemsCsvNotLoaded", filename), ex);
Essentials.getWrappedLogger().log(Level.SEVERE, ess.getAdventureFacet().miniToLegacy(tlLiteral("itemsCsvNotLoaded", filename)), ex);
}
}
}
@@ -1,15 +1,19 @@
package com.earth2me.essentials;
import com.earth2me.essentials.items.transform.PluginItemTransformer;
import com.earth2me.essentials.textreader.BookInput;
import com.earth2me.essentials.textreader.BookPager;
import com.earth2me.essentials.textreader.IText;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.MaterialUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.VersionUtil;
import com.google.common.base.Joiner;
import net.ess3.api.IEssentials;
import net.ess3.api.TranslatableException;
import net.ess3.provider.BannerDataProvider;
import net.ess3.provider.ItemUnbreakableProvider;
import net.ess3.provider.PotionMetaProvider;
import org.bukkit.Color;
import org.bukkit.DyeColor;
import org.bukkit.FireworkEffect;
@@ -17,6 +21,8 @@ import org.bukkit.Material;
import org.bukkit.block.Banner;
import org.bukkit.block.banner.PatternType;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.Registry;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BannerMeta;
@@ -29,7 +35,10 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.potion.Potion;
import org.bukkit.inventory.meta.ArmorMeta;
import org.bukkit.inventory.meta.trim.ArmorTrim;
import org.bukkit.inventory.meta.trim.TrimMaterial;
import org.bukkit.inventory.meta.trim.TrimPattern;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
@@ -42,11 +51,10 @@ import java.util.Map;
import java.util.logging.Level;
import java.util.regex.Pattern;
import static com.earth2me.essentials.I18n.tl;
public class MetaItemStack {
private static final Map<String, DyeColor> colorMap = new HashMap<>();
private static final Map<String, FireworkEffect.Type> fireworkShape = new HashMap<>();
private static final transient Map<String, PluginItemTransformer> itemTransformers = new HashMap<>();
private static boolean useNewSkullMethod = true;
static {
@@ -78,6 +86,32 @@ public class MetaItemStack {
this.stack = stack.clone();
}
/**
* Registers an item transformer, belonging to a plugin, that can manipulate certain item metadata.
* @param key the key for the transformer.
* @param itemTransformer the actual transformer.
*/
public static void registerItemTransformer(final String key, final PluginItemTransformer itemTransformer) {
//Warn people if they're trying to register over top of someone else.
if (itemTransformers.containsKey(key)) {
Essentials.getWrappedLogger().log(Level.WARNING, String.format("Plugin transformer registered to \"%s\" attempted to register already existing item transformer \"%s\" belonging to \"%s\"!",
itemTransformer.getPlugin().getName(),
key,
itemTransformers.get(key).getPlugin().getName()));
return;
}
itemTransformers.put(key, itemTransformer);
}
/**
* Unregisters a certain item transformer under key "key".
* @param key the transformer key.
*/
public static void unregisterItemTransformer(final String key) {
itemTransformers.remove(key);
}
private static void setSkullOwner(final IEssentials ess, final ItemStack stack, final String owner) {
if (!(stack.getItemMeta() instanceof SkullMeta)) return;
@@ -131,21 +165,30 @@ public class MetaItemStack {
}
public boolean canSpawn(final IEssentials ess) {
try {
ess.getServer().getUnsafe().modifyItemStack(stack.clone(), "{}");
return true;
} catch (final NoSuchMethodError nsme) {
return true;
} catch (final Throwable npe) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "Itemstack is invalid", npe);
if (VersionUtil.PRE_FLATTENING) {
try {
ess.getServer().getUnsafe().modifyItemStack(stack.clone(), "{}");
return true;
} catch (final NoSuchMethodError nsme) {
return true;
} catch (final Throwable npe) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "Itemstack is invalid", npe);
}
return false;
}
return false;
}
return stack.getType().isItem();
}
public void parseStringMeta(final CommandSource sender, final boolean allowUnsafe, final String[] string, final int fromArg, final IEssentials ess) throws Exception {
final boolean nbtIsKill = VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_20_6_R01);
if (string[fromArg].startsWith("{") && hasMetaPermission(sender, "vanilla", false, true, ess)) {
if (nbtIsKill) {
throw new TranslatableException("noMetaNbtKill");
}
try {
stack = ess.getServer().getUnsafe().modifyItemStack(stack, Joiner.on(' ').join(Arrays.asList(string).subList(fromArg, string.length)));
} catch (final NullPointerException npe) {
@@ -153,7 +196,23 @@ public class MetaItemStack {
ess.getLogger().log(Level.INFO, "Itemstack is invalid", npe);
}
} catch (final NoSuchMethodError nsme) {
throw new Exception(tl("noMetaJson"), nsme);
throw new TranslatableException(nsme, "noMetaJson");
} catch (final Throwable throwable) {
throw new Exception(throwable.getMessage(), throwable);
}
} else if (string[fromArg].startsWith("[") && hasMetaPermission(sender, "vanilla", false, true, ess)) {
if (!nbtIsKill) {
throw new TranslatableException("noMetaComponents");
}
try {
final String components = Joiner.on(' ').join(Arrays.asList(string).subList(fromArg, string.length));
// modifyItemStack requires that the item namespaced key is prepended to the components for some reason
stack = ess.getServer().getUnsafe().modifyItemStack(stack, stack.getType().getKey() + components);
} catch (final NullPointerException npe) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "Itemstack is invalid", npe);
}
} catch (final Throwable throwable) {
throw new Exception(throwable.getMessage(), throwable);
}
@@ -163,19 +222,19 @@ public class MetaItemStack {
}
if (validFirework) {
if (!hasMetaPermission(sender, "firework", true, true, ess)) {
throw new Exception(tl("noMetaFirework"));
throw new TranslatableException("noMetaFirework");
}
final FireworkEffect effect = builder.build();
final FireworkMeta fmeta = (FireworkMeta) stack.getItemMeta();
fmeta.addEffect(effect);
if (fmeta.getEffects().size() > 1 && !hasMetaPermission(sender, "firework-multiple", true, true, ess)) {
throw new Exception(tl("multipleCharges"));
throw new TranslatableException("multipleCharges");
}
stack.setItemMeta(fmeta);
}
if (validFireworkCharge) {
if (!hasMetaPermission(sender, "firework", true, true, ess)) {
throw new Exception(tl("noMetaFirework"));
throw new TranslatableException("noMetaFirework");
}
final FireworkEffect effect = builder.build();
final FireworkEffectMeta meta = (FireworkEffectMeta) stack.getItemMeta();
@@ -191,21 +250,26 @@ public class MetaItemStack {
return;
}
final Material WRITTEN_BOOK = EnumUtil.getMaterial("WRITTEN_BOOK");
if (split.length > 1 && split[0].equalsIgnoreCase("name") && hasMetaPermission(sender, "name", false, true, ess)) {
final String displayName = FormatUtil.replaceFormat(split[1].replace('_', ' '));
final String displayName = FormatUtil.replaceFormat(split[1].replaceAll("(?<!\\\\)_", " ").replace("\\_", "_"));
final ItemMeta meta = stack.getItemMeta();
meta.setDisplayName(displayName);
stack.setItemMeta(meta);
} else if (split.length > 1 && (split[0].equalsIgnoreCase("lore") || split[0].equalsIgnoreCase("desc")) && hasMetaPermission(sender, "lore", false, true, ess)) {
final List<String> lore = new ArrayList<>();
for (final String line : split[1].split("(?<!\\\\)\\|")) {
lore.add(FormatUtil.replaceFormat(line.replace('_', ' ').replace("\\|", "|")));
lore.add(FormatUtil.replaceFormat(line.replaceAll("(?<!\\\\)_", " ").replace("\\_", "_").replace("\\|", "|")));
}
final ItemMeta meta = stack.getItemMeta();
meta.setLore(lore);
stack.setItemMeta(meta);
} else if ((split[0].equalsIgnoreCase("custom-model-data") || split[0].equalsIgnoreCase("cmd")) && hasMetaPermission(sender, "custom-model-data", false, true, ess)) {
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_14_R01)) {
final int value = split.length <= 1 ? 0 : Integer.parseInt(split[1]);
final ItemMeta meta = stack.getItemMeta();
meta.setCustomModelData(value);
stack.setItemMeta(meta);
}
} else if (split[0].equalsIgnoreCase("unbreakable") && hasMetaPermission(sender, "unbreakable", false, true, ess)) {
final boolean value = split.length <= 1 || Boolean.parseBoolean(split[1]);
setUnbreakable(ess, stack, value);
@@ -214,33 +278,49 @@ public class MetaItemStack {
final String owner = split[1];
setSkullOwner(ess, stack, owner);
} else {
throw new Exception(tl("onlyPlayerSkulls"));
throw new TranslatableException("onlyPlayerSkulls");
}
} else if (split.length > 1 && split[0].equalsIgnoreCase("book") && MaterialUtil.isEditableBook(stack.getType()) && (hasMetaPermission(sender, "book",true, true, ess) || hasMetaPermission(sender, "chapter-" + split[1].toLowerCase(Locale.ENGLISH), true, true, ess))) {
} else if (split.length > 1 && split[0].equalsIgnoreCase("book") && MaterialUtil.isEditableBook(stack.getType()) && (hasMetaPermission(sender, "book", true, true, ess) || hasMetaPermission(sender, "chapter-" + split[1].toLowerCase(Locale.ENGLISH), true, true, ess))) {
final BookMeta meta = (BookMeta) stack.getItemMeta();
final IText input = new BookInput("book", true, ess);
final BookPager pager = new BookPager(input);
// This fix only applies to written books - which require an author and a title. https://bugs.mojang.com/browse/MC-59153
if (stack.getType() == Material.WRITTEN_BOOK) {
if (!meta.hasAuthor()) {
// The sender can be null when this method is called from {@link com.earth2me.essentials.signs.EssentialsSign#getItemMeta(ItemStack, String, IEssentials)}
meta.setAuthor(sender == null ? Console.getInstance().getDisplayName() : sender.getPlayer().getName());
}
if (!meta.hasTitle()) {
final String title = FormatUtil.replaceFormat(split[1].replace('_', ' '));
meta.setTitle(title.length() > 32 ? title.substring(0, 32) : title);
}
}
final List<String> pages = pager.getPages(split[1]);
meta.setPages(pages);
stack.setItemMeta(meta);
} else if (split.length > 1 && split[0].equalsIgnoreCase("author") && stack.getType() == WRITTEN_BOOK && hasMetaPermission(sender, "author", false, true, ess)) {
} else if (split.length > 1 && split[0].equalsIgnoreCase("author") && stack.getType() == Material.WRITTEN_BOOK && hasMetaPermission(sender, "author", false, true, ess)) {
final String author = FormatUtil.replaceFormat(split[1]);
final BookMeta meta = (BookMeta) stack.getItemMeta();
meta.setAuthor(author);
stack.setItemMeta(meta);
} else if (split.length > 1 && split[0].equalsIgnoreCase("title") && stack.getType() == WRITTEN_BOOK && hasMetaPermission(sender, "title", false, true, ess)) {
final String title = FormatUtil.replaceFormat(split[1].replace('_', ' '));
} else if (split.length > 1 && split[0].equalsIgnoreCase("title") && stack.getType() == Material.WRITTEN_BOOK && hasMetaPermission(sender, "title", false, true, ess)) {
final String title = FormatUtil.replaceFormat(split[1].replaceAll("(?<!\\\\)_", " ").replace("\\_", "_"));
final BookMeta meta = (BookMeta) stack.getItemMeta();
meta.setTitle(title);
stack.setItemMeta(meta);
} else if (split.length > 1 && split[0].startsWith("page") && split[0].length() > 4 && MaterialUtil.isEditableBook(stack.getType()) && hasMetaPermission(sender, "page", false, true, ess)) {
final int page = NumberUtil.isInt(split[0].substring(4)) ? (Integer.parseInt(split[0].substring(4)) - 1) : 0;
} else if (split.length > 1 && split[0].startsWith("page") && split[0].length() > 4
&& MaterialUtil.isEditableBook(stack.getType())
&& hasMetaPermission(sender, "page", false, true, ess)) {
final int page = NumberUtil.isInt(split[0].substring(4)) ? (Integer.parseInt(split[0].substring(4)) - 1)
: 0;
if (page > 100) {
throw new TranslatableException("pageLimitExceeded");
}
final BookMeta meta = (BookMeta) stack.getItemMeta();
final List<String> pages = meta.hasPages() ? new ArrayList<>(meta.getPages()) : new ArrayList<>();
final List<String> lines = new ArrayList<>();
for (final String line : split[1].split("(?<!\\\\)\\|")) {
lines.add(FormatUtil.replaceFormat(line.replace('_', ' ').replace("\\|", "|")));
lines.add(FormatUtil.replaceFormat(line.replaceAll("(?<!\\\\)_", " ").replace("\\_", "_").replace("\\|", "|")));
}
final String content = String.join("\n", lines);
if (page >= pages.size()) {
@@ -301,17 +381,43 @@ public class MetaItemStack {
meta.setColor(Color.fromRGB(red, green, blue));
stack.setItemMeta(meta);
} else {
throw new Exception(tl("leatherSyntax"));
throw new TranslatableException("leatherSyntax");
}
} else if (MaterialUtil.isArmor(stack.getType()) && split.length > 1 && split[0].equalsIgnoreCase("trim")) {
final ArmorMeta armorMeta = (ArmorMeta) stack.getItemMeta();
final String[] trimData = split[1].split("\\|");
final TrimPattern pattern = Registry.TRIM_PATTERN.getOrThrow(NamespacedKey.minecraft(trimData[0].toLowerCase()));
final TrimMaterial material = Registry.TRIM_MATERIAL.getOrThrow(NamespacedKey.minecraft(trimData[1].toLowerCase()));
armorMeta.setTrim(new ArmorTrim(material, pattern));
stack.setItemMeta(armorMeta);
} else if (split.length > 1 && itemTransformers.containsKey(split[0])) {
transformItem(split[0], split[1]);
} else {
parseEnchantmentStrings(sender, allowUnsafe, split, ess);
}
}
private void transformItem(final String key, final String data){
final PluginItemTransformer transformer = itemTransformers.get(key);
//Ignore, the plugin is disabled.
if (!transformer.getPlugin().isEnabled()) {
return;
}
try {
stack = transformer.apply(data, stack);
} catch(final Throwable thr) {
Essentials.getWrappedLogger().log(Level.SEVERE, String.format("Error applying data \"%s\" to itemstack! Plugin: %s, Key: %s", data, transformer.getPlugin().getName(), key), thr);
}
}
public void addItemFlags(final String string) throws Exception {
final String[] separate = splitPattern.split(string, 2);
if (separate.length != 2) {
throw new Exception(tl("invalidItemFlagMeta", string));
throw new TranslatableException("invalidItemFlagMeta", string);
}
final String[] split = separate[1].split(",");
@@ -326,7 +432,7 @@ public class MetaItemStack {
}
if (meta.getItemFlags().isEmpty()) {
throw new Exception(tl("invalidItemFlagMeta", string));
throw new TranslatableException("invalidItemFlagMeta", string);
}
stack.setItemMeta(meta);
@@ -349,7 +455,7 @@ public class MetaItemStack {
validFireworkCharge = true;
primaryColors.add(Color.fromRGB(Integer.decode(color)));
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
throw new TranslatableException("invalidFireworkFormat", split[1], split[0]);
}
}
builder.withColor(primaryColors);
@@ -359,7 +465,7 @@ public class MetaItemStack {
if (fireworkShape.containsKey(split[1].toUpperCase())) {
finalEffect = fireworkShape.get(split[1].toUpperCase());
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
throw new TranslatableException("invalidFireworkFormat", split[1], split[0]);
}
if (finalEffect != null) {
builder.with(finalEffect);
@@ -373,7 +479,7 @@ public class MetaItemStack {
} else if (hexPattern.matcher(color).matches()) {
fadeColors.add(Color.fromRGB(Integer.decode(color)));
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
throw new TranslatableException("invalidFireworkFormat", split[1], split[0]);
}
}
if (!fadeColors.isEmpty()) {
@@ -387,7 +493,7 @@ public class MetaItemStack {
} else if (effect.equalsIgnoreCase("trail")) {
builder.trail(true);
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
throw new TranslatableException("invalidFireworkFormat", split[1], split[0]);
}
}
}
@@ -403,13 +509,13 @@ public class MetaItemStack {
if (split[0].equalsIgnoreCase("color") || split[0].equalsIgnoreCase("colour") || (allowShortName && split[0].equalsIgnoreCase("c"))) {
if (validFirework) {
if (!hasMetaPermission(sender, "firework", true, true, ess)) {
throw new Exception(tl("noMetaFirework"));
throw new TranslatableException("noMetaFirework");
}
final FireworkEffect effect = builder.build();
final FireworkMeta fmeta = (FireworkMeta) stack.getItemMeta();
fmeta.addEffect(effect);
if (fmeta.getEffects().size() > 1 && !hasMetaPermission(sender, "firework-multiple", true, true, ess)) {
throw new Exception(tl("multipleCharges"));
throw new TranslatableException("multipleCharges");
}
stack.setItemMeta(fmeta);
builder = FireworkEffect.builder();
@@ -425,7 +531,7 @@ public class MetaItemStack {
validFirework = true;
primaryColors.add(Color.fromRGB(Integer.decode(color)));
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
throw new TranslatableException("invalidFireworkFormat", split[1], split[0]);
}
}
builder.withColor(primaryColors);
@@ -435,7 +541,7 @@ public class MetaItemStack {
if (fireworkShape.containsKey(split[1].toUpperCase())) {
finalEffect = fireworkShape.get(split[1].toUpperCase());
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
throw new TranslatableException("invalidFireworkFormat", split[1], split[0]);
}
if (finalEffect != null) {
builder.with(finalEffect);
@@ -449,7 +555,7 @@ public class MetaItemStack {
} else if (hexPattern.matcher(color).matches()) {
fadeColors.add(Color.fromRGB(Integer.decode(color)));
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
throw new TranslatableException("invalidFireworkFormat", split[1], split[0]);
}
}
if (!fadeColors.isEmpty()) {
@@ -463,7 +569,7 @@ public class MetaItemStack {
} else if (effect.equalsIgnoreCase("trail")) {
builder.trail(true);
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
throw new TranslatableException("invalidFireworkFormat", split[1], split[0]);
}
}
}
@@ -484,10 +590,10 @@ public class MetaItemStack {
if (hasMetaPermission(sender, "potions." + pEffectType.getName().toLowerCase(Locale.ENGLISH), true, false, ess)) {
validPotionEffect = true;
} else {
throw new Exception(tl("noPotionEffectPerm", pEffectType.getName().toLowerCase(Locale.ENGLISH)));
throw new TranslatableException("noPotionEffectPerm", pEffectType.getName().toLowerCase(Locale.ENGLISH));
}
} else {
throw new Exception(tl("invalidPotionMeta", split[1]));
throw new TranslatableException("invalidPotionMeta", split[1]);
}
} else if (split[0].equalsIgnoreCase("power") || (allowShortName && split[0].equalsIgnoreCase("p"))) {
if (NumberUtil.isInt(split[1])) {
@@ -497,21 +603,22 @@ public class MetaItemStack {
power -= 1;
}
} else {
throw new Exception(tl("invalidPotionMeta", split[1]));
throw new TranslatableException("invalidPotionMeta", split[1]);
}
} else if (split[0].equalsIgnoreCase("amplifier") || (allowShortName && split[0].equalsIgnoreCase("a"))) {
if (NumberUtil.isInt(split[1])) {
validPotionPower = true;
power = Integer.parseInt(split[1]);
} else {
throw new Exception(tl("invalidPotionMeta", split[1]));
throw new TranslatableException("invalidPotionMeta", split[1]);
}
} else if (split[0].equalsIgnoreCase("duration") || (allowShortName && split[0].equalsIgnoreCase("d"))) {
if (NumberUtil.isInt(split[1])) {
validPotionDuration = true;
duration = Integer.parseInt(split[1]) * 20; //Duration is in ticks by default, converted to seconds
final int parsed = Integer.parseInt(split[1]);
duration = parsed == -1 ? -1 : parsed * 20; //Duration is in ticks by default, converted to seconds
} else {
throw new Exception(tl("invalidPotionMeta", split[1]));
throw new TranslatableException("invalidPotionMeta", split[1]);
}
} else if (split[0].equalsIgnoreCase("splash") || (allowShortName && split[0].equalsIgnoreCase("s"))) {
isSplashPotion = Boolean.parseBoolean(split[1]);
@@ -521,21 +628,11 @@ public class MetaItemStack {
final PotionMeta pmeta = (PotionMeta) stack.getItemMeta();
pEffect = pEffectType.createEffect(duration, power);
if (pmeta.getCustomEffects().size() > 1 && !hasMetaPermission(sender, "potions.multiple", true, false, ess)) {
throw new Exception(tl("multiplePotionEffects"));
throw new TranslatableException("multiplePotionEffects");
}
pmeta.addCustomEffect(pEffect, true);
stack.setItemMeta(pmeta);
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_9_R01)) {
if (isSplashPotion && stack.getType() != Material.SPLASH_POTION) {
stack.setType(Material.SPLASH_POTION);
} else if (!isSplashPotion && stack.getType() != Material.POTION) {
stack.setType(Material.POTION);
}
} else {
final Potion potion = Potion.fromDamage(stack.getDurability());
potion.setSplash(isSplashPotion);
potion.apply(stack);
}
ess.provider(PotionMetaProvider.class).setSplashPotion(stack, isSplashPotion);
resetPotionMeta();
}
}
@@ -546,7 +643,7 @@ public class MetaItemStack {
if (enchantment == null) {
return false;
}
if (hasMetaPermission(sender, "enchantments." + enchantment.getName().toLowerCase(Locale.ENGLISH), false, false, ess)) {
if (hasMetaPermission(sender, "enchantments." + Enchantments.getRealName(enchantment), false, false, ess)) {
int level = -1;
if (split.length > 1) {
try {
@@ -566,7 +663,7 @@ public class MetaItemStack {
public void addEnchantment(final CommandSource sender, final boolean allowUnsafe, final Enchantment enchantment, final int level) throws Exception {
if (enchantment == null) {
throw new Exception(tl("enchantmentNotFound"));
throw new TranslatableException("enchantmentNotFound");
}
try {
if (stack.getType().equals(Material.ENCHANTED_BOOK)) {
@@ -589,7 +686,7 @@ public class MetaItemStack {
}
}
} catch (final Exception ex) {
throw new Exception("Enchantment " + enchantment.getName() + ": " + ex.getMessage(), ex);
throw new Exception("Enchantment " + Enchantments.getRealName(enchantment) + ": " + ex.getMessage(), ex);
}
}
@@ -599,10 +696,10 @@ public class MetaItemStack {
return null;
}
final String enchantmentName = enchantment.getName().toLowerCase(Locale.ENGLISH);
final String enchantmentName = Enchantments.getRealName(enchantment);
if (!hasMetaPermission(user, "enchantments." + enchantmentName, true, false)) {
throw new Exception(tl("enchantmentPerm", enchantmentName));
throw new TranslatableException("enchantmentPerm", enchantmentName);
}
return enchantment;
}
@@ -612,21 +709,23 @@ public class MetaItemStack {
final String[] split = splitPattern.split(string, 2);
if (split.length < 2) {
throw new Exception(tl("invalidBanner", split[1]));
throw new TranslatableException("invalidBanner", split[1]);
}
PatternType patternType = null;
try {
patternType = PatternType.valueOf(split[0]);
//noinspection removal
patternType = PatternType.getByIdentifier(split[0]);
} catch (final Exception ignored) {
}
final BannerMeta meta = (BannerMeta) stack.getItemMeta();
if (split[0].equalsIgnoreCase("basecolor")) {
final Color color = Color.fromRGB(Integer.parseInt(split[1]));
meta.setBaseColor(DyeColor.getByColor(color));
ess.provider(BannerDataProvider.class).setBaseColor(stack, DyeColor.getByColor(color));
} else if (patternType != null) {
final PatternType type = PatternType.valueOf(split[0]);
//noinspection removal
final PatternType type = PatternType.getByIdentifier(split[0]);
final DyeColor color = DyeColor.getByColor(Color.fromRGB(Integer.parseInt(split[1])));
final org.bukkit.block.banner.Pattern pattern = new org.bukkit.block.banner.Pattern(color, type);
meta.addPattern(pattern);
@@ -637,12 +736,13 @@ public class MetaItemStack {
final String[] split = splitPattern.split(string, 2);
if (split.length < 2) {
throw new Exception(tl("invalidBanner", split[1]));
throw new TranslatableException("invalidBanner", split[1]);
}
PatternType patternType = null;
try {
patternType = PatternType.valueOf(split[0]);
//noinspection removal
patternType = PatternType.getByIdentifier(split[0]);
} catch (final Exception ignored) {
}
@@ -653,7 +753,8 @@ public class MetaItemStack {
final Color color = Color.fromRGB(Integer.parseInt(split[1]));
banner.setBaseColor(DyeColor.getByColor(color));
} else if (patternType != null) {
final PatternType type = PatternType.valueOf(split[0]);
//noinspection removal
final PatternType type = PatternType.getByIdentifier(split[0]);
final DyeColor color = DyeColor.getByColor(Color.fromRGB(Integer.parseInt(split[1])));
final org.bukkit.block.banner.Pattern pattern = new org.bukkit.block.banner.Pattern(color, type);
banner.addPattern(pattern);
@@ -678,13 +779,13 @@ public class MetaItemStack {
if (graceful) {
return false;
} else {
throw new Exception(tl("noMetaPerm", metaPerm));
throw new TranslatableException("noMetaPerm", metaPerm);
}
}
private void setUnbreakable(final IEssentials ess, final ItemStack is, final boolean unbreakable) {
final ItemMeta meta = is.getItemMeta();
ess.getItemUnbreakableProvider().setUnbreakable(meta, unbreakable);
ess.provider(ItemUnbreakableProvider.class).setUnbreakable(meta, unbreakable);
is.setItemMeta(meta);
}
}
@@ -1,5 +1,6 @@
package com.earth2me.essentials;
import com.earth2me.essentials.adventure.AdventureUtil;
import com.earth2me.essentials.utils.EnumUtil;
import org.bukkit.Location;
import org.bukkit.Server;
@@ -14,7 +15,7 @@ import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
// Suffixes can be appended on the end of a mob name to make it plural
// Entities without a suffix, will default to 's'
@@ -41,22 +42,31 @@ public enum Mob {
ENDERDRAGON("EnderDragon", Enemies.ENEMY, EntityType.ENDER_DRAGON),
VILLAGER("Villager", Enemies.FRIENDLY, EntityType.VILLAGER),
BLAZE("Blaze", Enemies.ENEMY, EntityType.BLAZE),
MUSHROOMCOW("MushroomCow", Enemies.FRIENDLY, EntityType.MUSHROOM_COW),
MUSHROOMCOW("MushroomCow", Enemies.FRIENDLY, MobCompat.MOOSHROOM),
MAGMACUBE("MagmaCube", Enemies.ENEMY, EntityType.MAGMA_CUBE),
SNOWMAN("Snowman", Enemies.FRIENDLY, "", EntityType.SNOWMAN),
SNOWMAN("Snowman", Enemies.FRIENDLY, "", MobCompat.SNOW_GOLEM),
OCELOT("Ocelot", Enemies.NEUTRAL, EntityType.OCELOT),
IRONGOLEM("IronGolem", Enemies.NEUTRAL, EntityType.IRON_GOLEM),
WITHER("Wither", Enemies.ENEMY, EntityType.WITHER),
BAT("Bat", Enemies.FRIENDLY, EntityType.BAT),
WITCH("Witch", Enemies.ENEMY, EntityType.WITCH),
BOAT("Boat", Enemies.NEUTRAL, EntityType.BOAT),
BOAT("Boat", Enemies.NEUTRAL, MobCompat.OAK_BOAT),
ACACIA_BOAT("AcaciaBoat", Enemies.NEUTRAL, "ACACIA_BOAT"),
DARK_OAK_BOAT("DarkOakBoat", Enemies.NEUTRAL, "DARK_OAK_BOAT"),
BIRCH_BOAT("BirchBoat", Enemies.NEUTRAL, "BIRCH_BOAT"),
JUNGLE_BOAT("JungleBoat", Enemies.NEUTRAL, "JUNGLE_BOAT"),
SPRUCE_BOAT("SpruceBoat", Enemies.NEUTRAL, "SPRUCE_BOAT"),
MANGROVE_BOAT("MangroveBoat", Enemies.NEUTRAL, "MANGROVE_BOAT"),
CHERRY_BOAT("CherryBoat", Enemies.NEUTRAL, "CHERRY_BOAT"),
BAMBOO_RAFT("BambooRaft", Enemies.NEUTRAL, "BAMBOO_RAFT"),
PALE_OAK_BOAT("PaleOakBoat", Enemies.NEUTRAL, "PALE_OAK_BOAT"),
MINECART("Minecart", Enemies.NEUTRAL, EntityType.MINECART),
MINECART_CHEST("ChestMinecart", Enemies.NEUTRAL, EntityType.MINECART_CHEST),
MINECART_FURNACE("FurnaceMinecart", Enemies.NEUTRAL, EntityType.MINECART_FURNACE),
MINECART_TNT("TNTMinecart", Enemies.NEUTRAL, EntityType.MINECART_TNT),
MINECART_HOPPER("HopperMinecart", Enemies.NEUTRAL, EntityType.MINECART_HOPPER),
MINECART_MOB_SPAWNER("SpawnerMinecart", Enemies.NEUTRAL, EntityType.MINECART_MOB_SPAWNER),
ENDERCRYSTAL("EnderCrystal", Enemies.NEUTRAL, EntityType.ENDER_CRYSTAL),
MINECART_CHEST("ChestMinecart", Enemies.NEUTRAL, MobCompat.CHEST_MINECART),
MINECART_FURNACE("FurnaceMinecart", Enemies.NEUTRAL, MobCompat.FURNACE_MINECART),
MINECART_TNT("TNTMinecart", Enemies.NEUTRAL, MobCompat.TNT_MINECART),
MINECART_HOPPER("HopperMinecart", Enemies.NEUTRAL, MobCompat.HOPPER_MINECART),
MINECART_MOB_SPAWNER("SpawnerMinecart", Enemies.NEUTRAL, MobCompat.SPAWNER_MINECART),
ENDERCRYSTAL("EnderCrystal", Enemies.NEUTRAL, MobCompat.END_CRYSTAL),
EXPERIENCEORB("ExperienceOrb", Enemies.NEUTRAL, "EXPERIENCE_ORB"),
ARMOR_STAND("ArmorStand", Enemies.NEUTRAL, "ARMOR_STAND"),
ENDERMITE("Endermite", Enemies.ENEMY, "ENDERMITE"),
@@ -109,6 +119,18 @@ public enum Mob {
TADPOLE("Tadpole", Enemies.FRIENDLY, "TADPOLE"),
WARDEN("Warden", Enemies.ENEMY, "WARDEN"),
CHEST_BOAT("ChestBoat", Enemies.NEUTRAL, "CHEST_BOAT"),
CAMEL("Camel", Enemies.FRIENDLY, "CAMEL"),
SNIFFER("Sniffer", Enemies.FRIENDLY, "SNIFFER"),
ARMADILLO("Armadillo", Enemies.FRIENDLY, "ARMADILLO"),
BREEZE("Breeze", Enemies.ENEMY, "BREEZE"),
BOGGED("Bogged", Enemies.ENEMY, "BOGGED"),
CREAKING("Creaking", Enemies.ENEMY, "CREAKING"),
HAPPY_GHAST("HappyGhast", Enemies.FRIENDLY, "HAPPY_GHAST"),
COPPER_GOLEM("CopperGolem", Enemies.FRIENDLY, "COPPER_GOLEM"),
CAMEL_HUSK("CamelHusk", Enemies.NEUTRAL, "CAMEL_HUSK"),
NAUTILUS("Nautilus", Enemies.NEUTRAL, "NAUTILUS"),
ZOMBIE_NAUTILUS("ZombieNautilus", Enemies.NEUTRAL, "ZOMBIE_NAUTILUS"),
PARCHED("Parched", Enemies.ENEMY, "PARCHED"),
;
private static final Map<String, Mob> hashMap = new HashMap<>();
@@ -169,7 +191,7 @@ public enum Mob {
public Entity spawn(final World world, final Server server, final Location loc) throws MobException {
final Entity entity = world.spawn(loc, this.bukkitType.getEntityClass());
if (entity == null) {
Essentials.getWrappedLogger().log(Level.WARNING, tl("unableToSpawnMob"));
Essentials.getWrappedLogger().log(Level.WARNING, AdventureUtil.getAdventureFacet().miniToLegacy(tlLiteral("unableToSpawnMob")));
throw new MobException();
}
return entity;
@@ -1,11 +1,17 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.RegistryUtil;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.nms.refl.ReflUtil;
import org.bukkit.Material;
import org.bukkit.TreeSpecies;
import org.bukkit.entity.AbstractNautilus;
import org.bukkit.entity.Axolotl;
import org.bukkit.entity.Boat;
import org.bukkit.entity.Camel;
import org.bukkit.entity.Chicken;
import org.bukkit.entity.Cow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Fox;
@@ -15,9 +21,18 @@ import org.bukkit.entity.MushroomCow;
import org.bukkit.entity.Ocelot;
import org.bukkit.entity.Panda;
import org.bukkit.entity.Parrot;
import org.bukkit.entity.Pig;
import org.bukkit.entity.Player;
import org.bukkit.entity.Salmon;
import org.bukkit.entity.TropicalFish;
import org.bukkit.entity.Villager;
import org.bukkit.entity.Wolf;
import org.bukkit.entity.ZombieNautilus;
import org.bukkit.inventory.ItemStack;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import static com.earth2me.essentials.utils.EnumUtil.getEntityType;
@@ -25,8 +40,21 @@ import static com.earth2me.essentials.utils.EnumUtil.getEntityType;
public final class MobCompat {
// Constants for mob interfaces added in later versions
@SuppressWarnings("rawtypes")
public static final Class RAIDER = ReflUtil.getClassCached("org.bukkit.entity.Raider");
public static final Class<?> RAIDER = ReflUtil.getClassCached("org.bukkit.entity.Raider");
// Stupid hacks to avoid Commodore rewrites.
private static final Class<?> COW = ReflUtil.getClassCached("org.bukkit.entity.Cow");
private static final Class<?> COW_VARIANT = ReflUtil.getClassCached("org.bukkit.entity.Cow$Variant");
private static final MethodHandle COW_VARIANT_HANDLE;
static {
MethodHandle handle = null;
try {
handle = MethodHandles.lookup().findVirtual(COW, "setVariant", MethodType.methodType(void.class, COW_VARIANT));
} catch (final Throwable ignored) {
}
COW_VARIANT_HANDLE = handle;
}
// Constants for mobs added in later versions
public static final EntityType LLAMA = getEntityType("LLAMA");
@@ -41,10 +69,25 @@ public final class MobCompat {
public static final EntityType AXOLOTL = getEntityType("AXOLOTL");
public static final EntityType GOAT = getEntityType("GOAT");
public static final EntityType FROG = getEntityType("FROG");
public static final EntityType CAMEL = getEntityType("CAMEL");
public static final EntityType SALMON = getEntityType("SALMON");
public static final EntityType CAMEL_HUSK = getEntityType("CAMEL_HUSK");
public static final EntityType NAUTILUS = getEntityType("NAUTILUS");
public static final EntityType ZOMBIE_NAUTILUS = getEntityType("ZOMBIE_NAUTILUS");
// Constants for mobs that have changed since earlier versions
public static final EntityType CAT = getEntityType("CAT", "OCELOT");
public static final EntityType ZOMBIFIED_PIGLIN = getEntityType("ZOMBIFIED_PIGLIN", "PIG_ZOMBIE");
public static final EntityType MOOSHROOM = getEntityType("MOOSHROOM", "MUSHROOM_COW");
public static final EntityType SNOW_GOLEM = getEntityType("SNOW_GOLEM", "SNOWMAN");
public static final EntityType CHEST_MINECART = getEntityType("CHEST_MINECART", "MINECART_CHEST");
public static final EntityType FURNACE_MINECART = getEntityType("FURNACE_MINECART", "MINECART_FURNACE");
public static final EntityType TNT_MINECART = getEntityType("TNT_MINECART", "MINECART_TNT");
public static final EntityType HOPPER_MINECART = getEntityType("HOPPER_MINECART", "MINECART_HOPPER");
public static final EntityType SPAWNER_MINECART = getEntityType("SPAWNER_MINECART", "MINECART_MOB_SPAWNER");
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");
private MobCompat() {
}
@@ -178,7 +221,7 @@ public final class MobCompat {
}
public static void setBoatVariant(final Entity entity, final BoatVariant variant) {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_9_R01)) {
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_21_3_R01) || VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_9_R01)) {
return;
}
final Boat boat;
@@ -191,10 +234,105 @@ public final class MobCompat {
//noinspection deprecation
boat.setWoodType(TreeSpecies.valueOf(variant.getTreeSpecies()));
} else {
//noinspection deprecation
boat.setBoatType(Boat.Type.valueOf(variant.getBoatType()));
}
}
public static void setCamelSaddle(final Entity entity, final Player target) {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_20_1_R01)) {
return;
}
if (entity instanceof Camel) {
final Camel camel = (Camel) entity;
camel.setTamed(true);
camel.setOwner(target);
camel.getInventory().setSaddle(new ItemStack(Material.SADDLE, 1));
}
}
public static void setWolfVariant(final Entity entity, final String variant) {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_20_6_R01)) {
return;
}
if (entity instanceof Wolf) {
final Wolf wolf = (Wolf) entity;
//noinspection DataFlowIssue
wolf.setVariant(RegistryUtil.valueOf(Wolf.Variant.class, variant));
}
}
public static void setSalmonSize(Entity spawned, String s) {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_21_3_R01)) {
return;
}
if (spawned instanceof org.bukkit.entity.Salmon) {
((Salmon) spawned).setVariant(Salmon.Variant.valueOf(s));
}
}
public static void setCowVariant(final Entity spawned, final String variant) {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_21_5_R01) || COW_VARIANT_HANDLE == null) {
return;
}
if (spawned instanceof Cow) {
try {
COW_VARIANT_HANDLE.invoke(spawned, RegistryUtil.valueOf(COW_VARIANT, variant));
} catch (Throwable ignored) {
}
}
}
public static void setChickenVariant(final Entity spawned, final String variant) {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_21_5_R01)) {
return;
}
if (spawned instanceof Chicken) {
//noinspection DataFlowIssue
((Chicken) spawned).setVariant(RegistryUtil.valueOf(Chicken.Variant.class, variant));
}
}
public static void setPigVariant(final Entity spawned, final String variant) {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_21_5_R01)) {
return;
}
if (spawned instanceof Pig) {
//noinspection DataFlowIssue
((Pig) spawned).setVariant(RegistryUtil.valueOf(Pig.Variant.class, variant));
}
}
public static void setZombieNautilusVariant(final Entity spawned, final String variant) {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_21_11_R01)) {
return;
}
if (spawned instanceof ZombieNautilus) {
//noinspection DataFlowIssue
((ZombieNautilus) spawned).setVariant(RegistryUtil.valueOf(ZombieNautilus.Variant.class, variant));
}
}
public static void setNautilusSaddle(final Entity entity, final Player target) {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_21_11_R01)) {
return;
}
if (entity instanceof AbstractNautilus) {
final AbstractNautilus nautilus = (AbstractNautilus) entity;
nautilus.setTamed(true);
nautilus.setOwner(target);
nautilus.getInventory().setSaddle(new ItemStack(Material.SADDLE, 1));
}
}
public enum CatType {
// These are (loosely) Mojang names for the cats
SIAMESE("SIAMESE", "SIAMESE_CAT"),
@@ -255,7 +393,7 @@ public final class MobCompat {
}
private Villager.Profession asEnum() {
return EnumUtil.valueOf(Villager.Profession.class, newProfession, oldProfession);
return RegistryUtil.valueOf(Villager.Profession.class, newProfession, oldProfession);
}
}
@@ -1,14 +1,16 @@
package com.earth2me.essentials;
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.StringUtil;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.api.TranslatableException;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.Boat;
import org.bukkit.entity.ChestedHorse;
import org.bukkit.entity.Chicken;
import org.bukkit.entity.Creeper;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
@@ -38,8 +40,6 @@ import java.util.Locale;
import java.util.Random;
import java.util.stream.Collectors;
import static com.earth2me.essentials.I18n.tl;
public enum MobData {
BABY_AGEABLE("baby", Ageable.class, Data.BABY, true),
@@ -82,6 +82,7 @@ public enum MobData {
SADDLE_HORSE("saddle", EntityType.HORSE, Data.HORSESADDLE, true),
GOLD_ARMOR_HORSE("goldarmor", EntityType.HORSE, EnumUtil.getMaterial("GOLDEN_HORSE_ARMOR", "GOLD_BARDING"), true),
DIAMOND_ARMOR_HORSE("diamondarmor", EntityType.HORSE, EnumUtil.getMaterial("DIAMOND_HORSE_ARMOR", "DIAMOND_BARDING"), true),
NETHERITE_HORSE_ARMOR("netheritearmor", EntityType.HORSE, EnumUtil.getMaterial("NETHERITE_HORSE_ARMOR"), true),
ARMOR_HORSE("armor", EntityType.HORSE, EnumUtil.getMaterial("IRON_HORSE_ARMOR", "IRON_BARDING"), true),
SIAMESE_CAT("siamese", MobCompat.CAT, MobCompat.CatType.SIAMESE, true),
WHITE_CAT("white", MobCompat.CAT, MobCompat.CatType.WHITE, false),
@@ -158,8 +159,8 @@ public enum MobData {
BLOCKFISH_TROPICAL_FISH("blockfish", MobCompat.TROPICAL_FISH, "tropicalfish:BLOCKFISH", true),
BETTY_TROPICAL_FISH("betty", MobCompat.TROPICAL_FISH, "tropicalfish:BETTY", true),
CLAYFISH_TROPICAL_FISH("clayfish", MobCompat.TROPICAL_FISH, "tropicalfish:CLAYFISH", true),
BROWN_MUSHROOM_COW("brown", EntityType.MUSHROOM_COW, "mooshroom:BROWN", true),
RED_MUSHROOM_COW("red", EntityType.MUSHROOM_COW, "mooshroom:RED", true),
BROWN_MUSHROOM_COW("brown", MobCompat.MOOSHROOM, "mooshroom:BROWN", true),
RED_MUSHROOM_COW("red", MobCompat.MOOSHROOM, "mooshroom:RED", true),
AGGRESSIVE_PANDA_MAIN("aggressive", MobCompat.PANDA, "pandamain:AGGRESSIVE", true),
LAZY_PANDA_MAIN("lazy", MobCompat.PANDA, "pandamain:LAZY", true),
WORRIED_PANDA_MAIN("worried", MobCompat.PANDA, "pandamain:WORRIED", true),
@@ -209,6 +210,33 @@ public enum MobData {
MANGROVE_BOAT("mangrove", Boat.class, MobCompat.BoatVariant.MANGROVE, 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),
BLACK_WOLF("black", EntityType.WOLF, "wolf:BLACK", true),
ASHEN_WOLF("ashen", EntityType.WOLF, "wolf:ASHEN", true),
RUSTY_WOLF("rusty", EntityType.WOLF, "wolf:RUSTY", true),
WOODS_WOLF("woods", EntityType.WOLF, "wolf:WOODS", true),
CHESTNUT_WOLF("chestnut", EntityType.WOLF, "wolf:CHESTNUT", true),
STRIPED_WOLF("striped", EntityType.WOLF, "wolf:STRIPED", true),
SMALL_SALMON("small", MobCompat.SALMON, "salmon:SMALL", true),
MEDIUM_SALMON("medium", MobCompat.SALMON, "salmon:MEDIUM", true),
LARGE_SALMON("large", MobCompat.SALMON, "salmon:LARGE", true),
TEMPERATE_COW("temperate", EntityType.COW.getEntityClass(), "cow:TEMPERATE", true),
WARM_COW("warm", EntityType.COW.getEntityClass(), "cow:WARM", true),
COLD_COW("cold", EntityType.COW.getEntityClass(), "cow:COLD", true),
TEMPERATE_CHICKEN("temperate", Chicken.class, "chicken:TEMPERATE", true),
WARM_CHICKEN("warm", Chicken.class, "chicken:WARM", true),
COLD_CHICKEN("cold", Chicken.class, "chicken:COLD", true),
TEMPERATE_PIG("temperate", Pig.class, "pig:TEMPERATE", true),
WARM_PIG("warm", Pig.class, "pig:WARM", true),
COLD_PIG("cold", Pig.class, "pig:COLD", true),
SADDLE_CAMEL_HUSK("saddle", MobCompat.CAMEL_HUSK, Data.CAMELHUSKSADDLE, true),
TEMPERATE_ZOMBIE_NAUTILUS("temperate", MobCompat.ZOMBIE_NAUTILUS, "zombienautilus:TEMPERATE", true),
WARM_ZOMBIE_NAUTILUS("warm", MobCompat.ZOMBIE_NAUTILUS, "zombienautilus:WARM", true),
SADDLE_NAUTILUS("saddle", MobCompat.NAUTILUS, Data.NAUTILUSSADDLE, true),
SADDLE_ZOMBIE_NAUTILUS("saddle", MobCompat.ZOMBIE_NAUTILUS, Data.NAUTILUSSADDLE, true)
;
final private String nickname;
@@ -327,14 +355,14 @@ public enum MobData {
}
this.matched = rawData;
} catch (final Exception e) {
throw new Exception(tl("sheepMalformedColor"), e);
throw new TranslatableException(e, "sheepMalformedColor");
}
} else if (this.value.equals(Data.EXP)) {
try {
((ExperienceOrb) spawned).setExperience(Integer.parseInt(rawData));
this.matched = rawData;
} catch (final NumberFormatException e) {
throw new Exception(tl("invalidNumber"), e);
throw new TranslatableException(e, "invalidNumber");
}
} else if (this.value.equals(Data.SIZE)) {
try {
@@ -346,7 +374,7 @@ public enum MobData {
}
this.matched = rawData;
} catch (final NumberFormatException e) {
throw new Exception(tl("slimeMalformedSize"), e);
throw new TranslatableException(e, "slimeMalformedSize");
}
} else if (this.value instanceof Horse.Color) {
((Horse) spawned).setColor((Horse.Color) this.value);
@@ -362,8 +390,8 @@ public enum MobData {
((Horse) spawned).getInventory().setArmor(new ItemStack((Material) this.value, 1));
} else if (this.type.equals(EntityType.ZOMBIE.getEntityClass()) || this.type.equals(EntityType.SKELETON)) {
final EntityEquipment invent = ((LivingEntity) spawned).getEquipment();
InventoryWorkaround.setItemInMainHand(invent, new ItemStack((Material) this.value, 1));
InventoryWorkaround.setItemInMainHandDropChance(invent, 0.1f);
Inventories.setItemInMainHand(invent, new ItemStack((Material) this.value, 1));
Inventories.setItemInMainHandDropChance(invent, 0.1f);
}
} else if (this.value.equals(Data.RAID_LEADER)) {
((Raider) spawned).setPatrolLeader(true);
@@ -387,6 +415,14 @@ public enum MobData {
}
} else if (this.value.equals(Data.GOAT_SCREAMING)) {
((Goat) spawned).setScreaming(true);
} else if (this.value.equals(Data.CAMELSADDLE)) {
MobCompat.setCamelSaddle(spawned, target);
} else if (this.value.equals(Data.CAMELHUSKSADDLE)) {
MobCompat.setCamelSaddle(spawned, target);
} else if (this.value.equals(Data.NAUTILUSSADDLE)) {
MobCompat.setNautilusSaddle(spawned, target);
} else if (this.value.equals(Data.ZOMBIENAUTILUSSADDLE)) {
MobCompat.setNautilusSaddle(spawned, target);
} else if (this.value instanceof MobCompat.BoatVariant) {
MobCompat.setBoatVariant(spawned, (MobCompat.BoatVariant) this.value);
} else if (this.value instanceof String) {
@@ -422,6 +458,24 @@ public enum MobData {
case "frog":
MobCompat.setFrogVariant(spawned, split[1]);
break;
case "wolf":
MobCompat.setWolfVariant(spawned, split[1]);
break;
case "salmon":
MobCompat.setSalmonSize(spawned, split[1]);
break;
case "cow":
MobCompat.setCowVariant(spawned, split[1]);
break;
case "chicken":
MobCompat.setChickenVariant(spawned, split[1]);
break;
case "pig":
MobCompat.setPigVariant(spawned, split[1]);
break;
case "zombienautilus":
MobCompat.setZombieNautilusVariant(spawned, split[1]);
break;
}
} else {
Essentials.getWrappedLogger().warning("Unknown mob data type: " + this.toString());
@@ -446,5 +500,9 @@ public enum MobData {
FISH_BODY_COLOR,
FISH_PATTERN_COLOR,
GOAT_SCREAMING,
CAMELSADDLE,
CAMELHUSKSADDLE,
NAUTILUSSADDLE,
ZOMBIENAUTILUSSADDLE,
}
}
@@ -1,6 +1,7 @@
package com.earth2me.essentials;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
@@ -31,4 +32,11 @@ public class PlayerExtension {
public Location getLocation() {
return base.getLocation();
}
public OfflinePlayer getOffline() {
if (base instanceof OfflinePlayerStub) {
return ((OfflinePlayerStub) base).base;
}
return base;
}
}
@@ -2,7 +2,7 @@ package com.earth2me.essentials;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.NumberUtil;
import org.bukkit.ChatColor;
import net.ess3.api.TranslatableException;
import org.bukkit.Server;
import java.util.ArrayList;
@@ -14,7 +14,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public final class PlayerList {
@@ -32,19 +32,19 @@ public final class PlayerList {
}
needComma = true;
if (user.isAfk()) {
groupString.append(tl("listAfkTag"));
groupString.append(tlLiteral("listAfkTag"));
}
if (user.isHidden()) {
groupString.append(tl("listHiddenTag"));
groupString.append(tlLiteral("listHiddenTag"));
}
user.setDisplayNick();
groupString.append(user.getDisplayName());
groupString.append(ess.getAdventureFacet().legacyToMini(user.getDisplayName()));
final String strippedNick = FormatUtil.stripFormat(user.getNickname());
if (ess.getSettings().realNamesOnList() && strippedNick != null && !strippedNick.equals(user.getName())) {
groupString.append(" ").append(tl("listRealName",user.getName()));
groupString.append(" ").append(tlLiteral("listRealName",user.getName()));
}
groupString.append(ChatColor.WHITE.toString());
groupString.append("<white>");
}
return groupString.toString();
}
@@ -55,27 +55,31 @@ public final class PlayerList {
int playerHidden = 0;
int hiddenCount = 0;
for (final User onlinePlayer : ess.getOnlineUsers()) {
if (onlinePlayer.isHidden() || (user != null && !user.getBase().canSee(onlinePlayer.getBase()))) {
if (onlinePlayer.isHidden() || (user != null && onlinePlayer.isHiddenFrom(user.getBase()))) {
playerHidden++;
if (showHidden || user != null && user.getBase().canSee(onlinePlayer.getBase())) {
if (showHidden || user != null && !onlinePlayer.isHiddenFrom(user.getBase())) {
hiddenCount++;
}
}
}
final String online;
final String tlKey;
final Object[] objects;
if (hiddenCount > 0) {
online = tl("listAmountHidden", ess.getOnlinePlayers().size() - playerHidden, hiddenCount, server.getMaxPlayers());
tlKey = "listAmountHidden";
objects = new Object[]{ess.getOnlinePlayers().size() - playerHidden, hiddenCount, server.getMaxPlayers()};
} else {
online = tl("listAmount", ess.getOnlinePlayers().size() - playerHidden, server.getMaxPlayers());
tlKey = "listAmount";
objects = new Object[]{ess.getOnlinePlayers().size() - playerHidden, server.getMaxPlayers()};
}
return online;
return user == null ? tlLiteral(tlKey, objects) : user.playerTl(tlKey, objects);
}
// Build the basic player list, divided by groups.
public static Map<String, List<User>> getPlayerLists(final IEssentials ess, final IUser sender, final boolean showHidden) {
final Map<String, List<User>> playerList = new HashMap<>();
for (final User onlineUser : ess.getOnlineUsers()) {
if ((sender == null && !showHidden && onlineUser.isHidden()) || (sender != null && !showHidden && !sender.getBase().canSee(onlineUser.getBase()))) {
if ((sender == null && !showHidden && onlineUser.isHidden()) || (sender != null && !showHidden && onlineUser.isHiddenFrom(sender.getBase()))) {
continue;
}
final String group = FormatUtil.stripFormat(FormatUtil.stripEssentialsFormat(onlineUser.getGroup().toLowerCase()));
@@ -117,7 +121,7 @@ public final class PlayerList {
users.addAll(groupUsers);
}
if (users.isEmpty()) {
throw new Exception(tl("groupDoesNotExist"));
throw new TranslatableException("groupDoesNotExist");
}
final String displayGroupName = Character.toTitleCase(groupName.charAt(0)) +
groupName.substring(1);
@@ -126,11 +130,11 @@ public final class PlayerList {
// Build the output string
public static String outputFormat(final String group, final String message) {
return tl("listGroupTag", FormatUtil.replaceFormat(group)) +
return tlLiteral("listGroupTag", FormatUtil.replaceFormat(group)) +
message;
}
public static List<String> prepareGroupedList(final IEssentials ess, final String commandLabel, final Map<String, List<User>> playerList) {
public static List<String> prepareGroupedList(final IEssentials ess, final CommandSource source, final String commandLabel, final Map<String, List<User>> playerList) {
final List<String> output = new ArrayList<>();
final Set<String> configGroups = ess.getSettings().getListGroupConfig().keySet();
@@ -163,7 +167,9 @@ public final class PlayerList {
outputUserList = new ArrayList<>(matchedList);
final int limit = Integer.parseInt(groupValue);
if (matchedList.size() > limit) {
output.add(outputFormat(oConfigGroup, tl("groupNumber", matchedList.size(), commandLabel, FormatUtil.stripFormat(configGroup))));
final String tlKey = "groupNumber";
final Object[] objects = {matchedList.size(), commandLabel, FormatUtil.stripFormat(configGroup)};
output.add(outputFormat(oConfigGroup, source == null ? tlLiteral(tlKey, objects) : source.tl(tlKey, objects)));
} else {
output.add(outputFormat(oConfigGroup, listUsers(ess, outputUserList, ", ")));
}
@@ -203,7 +209,7 @@ public final class PlayerList {
String groupName = asterisk.isEmpty() ? users.get(0).getGroup() : onlineGroup;
if (ess.getPermissionsHandler().getName().equals("ConfigPermissions")) {
groupName = tl("connectedPlayers");
groupName = source == null ? tlLiteral("connectedPlayers") : source.tl("connectedPlayers");
}
if (users == null || users.isEmpty()) {
continue;
@@ -4,15 +4,17 @@ import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import java.util.UUID;
public class PlayerTarget implements ITarget {
private final String name;
private final UUID uuid;
public PlayerTarget(final Player entity) {
this.name = entity.getName();
this.uuid = entity.getUniqueId();
}
@Override
public Location getLocation() {
return Bukkit.getServer().getPlayerExact(name).getLocation();
return Bukkit.getPlayer(uuid).getLocation();
}
}
@@ -1,6 +1,7 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.RegistryUtil;
import org.bukkit.potion.PotionEffectType;
import java.util.HashMap;
@@ -21,50 +22,68 @@ public final class Potions {
ALIASPOTIONS.put("sprint", PotionEffectType.SPEED);
ALIASPOTIONS.put("swift", PotionEffectType.SPEED);
POTIONS.put("slowness", PotionEffectType.SLOW);
ALIASPOTIONS.put("slow", PotionEffectType.SLOW);
ALIASPOTIONS.put("sluggish", PotionEffectType.SLOW);
final PotionEffectType SLOWNESS = RegistryUtil.valueOf(PotionEffectType.class, "SLOW", "SLOWNESS");
POTIONS.put("haste", PotionEffectType.FAST_DIGGING);
ALIASPOTIONS.put("superpick", PotionEffectType.FAST_DIGGING);
ALIASPOTIONS.put("quickmine", PotionEffectType.FAST_DIGGING);
ALIASPOTIONS.put("digspeed", PotionEffectType.FAST_DIGGING);
ALIASPOTIONS.put("digfast", PotionEffectType.FAST_DIGGING);
ALIASPOTIONS.put("sharp", PotionEffectType.FAST_DIGGING);
POTIONS.put("slowness", SLOWNESS);
ALIASPOTIONS.put("slow", SLOWNESS);
ALIASPOTIONS.put("sluggish", SLOWNESS);
POTIONS.put("fatigue", PotionEffectType.SLOW_DIGGING);
ALIASPOTIONS.put("slow", PotionEffectType.SLOW_DIGGING);
ALIASPOTIONS.put("dull", PotionEffectType.SLOW_DIGGING);
final PotionEffectType HASTE = RegistryUtil.valueOf(PotionEffectType.class, "FAST_DIGGING", "HASTE");
POTIONS.put("strength", PotionEffectType.INCREASE_DAMAGE);
ALIASPOTIONS.put("strong", PotionEffectType.INCREASE_DAMAGE);
ALIASPOTIONS.put("bull", PotionEffectType.INCREASE_DAMAGE);
ALIASPOTIONS.put("attack", PotionEffectType.INCREASE_DAMAGE);
POTIONS.put("haste", HASTE);
ALIASPOTIONS.put("superpick", HASTE);
ALIASPOTIONS.put("quickmine", HASTE);
ALIASPOTIONS.put("digspeed", HASTE);
ALIASPOTIONS.put("digfast", HASTE);
ALIASPOTIONS.put("sharp", HASTE);
POTIONS.put("heal", PotionEffectType.HEAL);
ALIASPOTIONS.put("healthy", PotionEffectType.HEAL);
ALIASPOTIONS.put("instaheal", PotionEffectType.HEAL);
final PotionEffectType MINING_FATIGUE = RegistryUtil.valueOf(PotionEffectType.class, "SLOW_DIGGING", "MINING_FATIGUE");
POTIONS.put("harm", PotionEffectType.HARM);
ALIASPOTIONS.put("harming", PotionEffectType.HARM);
ALIASPOTIONS.put("injure", PotionEffectType.HARM);
ALIASPOTIONS.put("damage", PotionEffectType.HARM);
ALIASPOTIONS.put("inflict", PotionEffectType.HARM);
POTIONS.put("fatigue", MINING_FATIGUE);
ALIASPOTIONS.put("slow", MINING_FATIGUE);
ALIASPOTIONS.put("dull", MINING_FATIGUE);
POTIONS.put("jump", PotionEffectType.JUMP);
ALIASPOTIONS.put("leap", PotionEffectType.JUMP);
final PotionEffectType STRENGTH = RegistryUtil.valueOf(PotionEffectType.class, "INCREASE_DAMAGE", "STRENGTH");
POTIONS.put("nausea", PotionEffectType.CONFUSION);
ALIASPOTIONS.put("sick", PotionEffectType.CONFUSION);
ALIASPOTIONS.put("sickness", PotionEffectType.CONFUSION);
ALIASPOTIONS.put("confusion", PotionEffectType.CONFUSION);
POTIONS.put("strength", STRENGTH);
ALIASPOTIONS.put("strong", STRENGTH);
ALIASPOTIONS.put("bull", STRENGTH);
ALIASPOTIONS.put("attack", STRENGTH);
final PotionEffectType INSTANT_HEALTH = RegistryUtil.valueOf(PotionEffectType.class, "HEAL", "INSTANT_HEALTH");
POTIONS.put("heal", INSTANT_HEALTH);
ALIASPOTIONS.put("healthy", INSTANT_HEALTH);
ALIASPOTIONS.put("instaheal", INSTANT_HEALTH);
final PotionEffectType INSTANT_DAMAGE = RegistryUtil.valueOf(PotionEffectType.class, "HARM", "INSTANT_DAMAGE");
POTIONS.put("harm", INSTANT_DAMAGE);
ALIASPOTIONS.put("harming", INSTANT_DAMAGE);
ALIASPOTIONS.put("injure", INSTANT_DAMAGE);
ALIASPOTIONS.put("damage", INSTANT_DAMAGE);
ALIASPOTIONS.put("inflict", INSTANT_DAMAGE);
final PotionEffectType JUMP_BOOST = RegistryUtil.valueOf(PotionEffectType.class, "JUMP", "JUMP_BOOST");
POTIONS.put("jump", JUMP_BOOST);
ALIASPOTIONS.put("leap", JUMP_BOOST);
final PotionEffectType NAUSEA = RegistryUtil.valueOf(PotionEffectType.class, "CONFUSION", "NAUSEA");
POTIONS.put("nausea", NAUSEA);
ALIASPOTIONS.put("sick", NAUSEA);
ALIASPOTIONS.put("sickness", NAUSEA);
ALIASPOTIONS.put("confusion", NAUSEA);
POTIONS.put("regeneration", PotionEffectType.REGENERATION);
ALIASPOTIONS.put("regen", PotionEffectType.REGENERATION);
POTIONS.put("resistance", PotionEffectType.DAMAGE_RESISTANCE);
ALIASPOTIONS.put("dmgresist", PotionEffectType.DAMAGE_RESISTANCE);
ALIASPOTIONS.put("armor", PotionEffectType.DAMAGE_RESISTANCE);
final PotionEffectType RESISTANCE = RegistryUtil.valueOf(PotionEffectType.class, "DAMAGE_RESISTANCE", "RESISTANCE");
POTIONS.put("resistance", RESISTANCE);
ALIASPOTIONS.put("dmgresist", RESISTANCE);
ALIASPOTIONS.put("armor", RESISTANCE);
POTIONS.put("fireresist", PotionEffectType.FIRE_RESISTANCE);
ALIASPOTIONS.put("fireresistance", PotionEffectType.FIRE_RESISTANCE);
@@ -125,6 +144,31 @@ public final class Potions {
POTIONS.put("unluck", PotionEffectType.UNLUCK);
} catch (final Throwable ignored) {
}
// 1.21
try {
POTIONS.put("infested", PotionEffectType.INFESTED);
ALIASPOTIONS.put("silverfish", PotionEffectType.INFESTED);
POTIONS.put("oozing", PotionEffectType.OOZING);
ALIASPOTIONS.put("ooze", PotionEffectType.OOZING);
POTIONS.put("weaving", PotionEffectType.WEAVING);
ALIASPOTIONS.put("weave", PotionEffectType.WEAVING);
POTIONS.put("windcharged", PotionEffectType.WIND_CHARGED);
ALIASPOTIONS.put("windcharge", PotionEffectType.WIND_CHARGED);
ALIASPOTIONS.put("wind", PotionEffectType.WIND_CHARGED);
} catch (final Throwable ignored) {
}
// 1.21.11
try {
POTIONS.put("breathofthenautilus", PotionEffectType.BREATH_OF_THE_NAUTILUS);
ALIASPOTIONS.put("nautilusbreath", PotionEffectType.BREATH_OF_THE_NAUTILUS);
ALIASPOTIONS.put("nautilus", PotionEffectType.BREATH_OF_THE_NAUTILUS);
} catch (final Throwable ignored) {
}
}
private Potions() {
@@ -0,0 +1,137 @@
package com.earth2me.essentials;
import io.papermc.lib.PaperLib;
import net.ess3.provider.Provider;
import net.essentialsx.providers.NullableProvider;
import net.essentialsx.providers.ProviderData;
import net.essentialsx.providers.ProviderTest;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
public class ProviderFactory {
private final Map<Class<? extends Provider>, Provider> providers = new HashMap<>();
private final Map<Class<? extends Provider>, List<Class<? extends Provider>>> registeredProviders = new HashMap<>();
private final Essentials essentials;
public ProviderFactory(final Essentials essentials) {
this.essentials = essentials;
}
/**
* Gets the provider which has been selected for the given type.
* @param providerClass The provider type.
* @return the provider or null if no provider could be selected for that type.
*/
public <P extends Provider> P get(final Class<P> providerClass) {
final Provider provider = providers.get(providerClass);
if (provider == null) {
return null;
}
//noinspection unchecked
return (P) provider;
}
@SafeVarargs
public final void registerProvider(final Class<? extends Provider>... toRegister) {
for (final Class<? extends Provider> provider : toRegister) {
final Class<?> superclass = provider.getInterfaces().length > 0 ? provider.getInterfaces()[0] : provider.getSuperclass();
if (Provider.class.isAssignableFrom(superclass)) {
//noinspection unchecked
registeredProviders.computeIfAbsent((Class<? extends Provider>) superclass, k -> new ArrayList<>()).add(provider);
if (essentials.getSettings().isDebug()) {
essentials.getLogger().info("Registered provider " + provider.getSimpleName() + " for " + superclass.getSimpleName());
}
}
}
}
public void finalizeRegistration() {
for (final Map.Entry<Class<? extends Provider>, List<Class<? extends Provider>>> entry : registeredProviders.entrySet()) {
final Class<? extends Provider> providerClass = entry.getKey();
final boolean nullable = providerClass.isAnnotationPresent(NullableProvider.class);
Class<? extends Provider> highestProvider = null;
ProviderData highestProviderData = null;
int highestWeight = -1;
for (final Class<? extends Provider> provider : entry.getValue()) {
try {
final ProviderData providerData = provider.getAnnotation(ProviderData.class);
if (providerData.weight() > highestWeight && testProvider(provider)) {
highestWeight = providerData.weight();
highestProvider = provider;
highestProviderData = providerData;
}
} catch (final Throwable e) {
essentials.getLogger().log(Level.SEVERE, "Failed to initialize provider " + provider.getName(), e);
}
}
if (highestProvider != null) {
essentials.getLogger().info("Selected " + highestProviderData.description() + " as the provider for " + providerClass.getSimpleName());
providers.put(providerClass, getProviderInstance(highestProvider));
} else if (!nullable) {
throw new IllegalStateException("No provider found for " + providerClass.getName());
} else {
essentials.getLogger().info("No provider found for " + providerClass.getSimpleName() + ", but it is nullable");
}
}
registeredProviders.clear();
}
private boolean testProvider(final Class<?> providerClass) throws InvocationTargetException, IllegalAccessException {
try {
for (final Method method : providerClass.getMethods()) {
if (method.isAnnotationPresent(ProviderTest.class)) {
return (Boolean) method.invoke(null);
}
}
return true;
} catch (final NoClassDefFoundError ignored) {
return false;
}
}
private <P extends Provider> P getProviderInstance(final Class<P> provider) {
try {
final Constructor<?> constructor = provider.getConstructors()[0];
if (constructor.getParameterTypes().length == 0) {
//noinspection unchecked
return (P) constructor.newInstance();
}
final Object[] args = new Object[constructor.getParameterTypes().length];
/*
Providers can have constructors with any of the following types, and this code will automatically supply them;
- Plugin - The Essentials instance will be passed
- boolean - True will be passed if this server is running Paper, otherwise false.
*/
for (int i = 0; i < args.length; i++) {
final Class<?> paramType = constructor.getParameterTypes()[i];
if (paramType.isAssignableFrom(Plugin.class)) {
args[i] = essentials;
} else if (paramType.isAssignableFrom(boolean.class)) {
args[i] = PaperLib.isPaper();
} else {
throw new IllegalArgumentException("Unsupported parameter type " + paramType.getName());
}
}
//noinspection unchecked
return (P) constructor.newInstance(args);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
try {
return provider.getConstructor().newInstance();
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException ex) {
e.printStackTrace();
throw new RuntimeException(ex);
}
}
}
}
@@ -1,18 +1,24 @@
package com.earth2me.essentials;
import com.earth2me.essentials.config.ConfigurateUtil;
import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.config.entities.LazyLocation;
import com.earth2me.essentials.utils.LocationUtil;
import com.earth2me.essentials.utils.VersionUtil;
import io.papermc.lib.PaperLib;
import net.ess3.api.InvalidWorldException;
import net.ess3.provider.BiomeKeyProvider;
import net.ess3.provider.BiomeNameProvider;
import net.ess3.provider.WorldInfoProvider;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Biome;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
@@ -24,66 +30,83 @@ public class RandomTeleport implements IConf {
private static final int HIGHEST_BLOCK_Y_OFFSET = VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_15_R01) ? 1 : 0;
private final IEssentials ess;
private final EssentialsConfiguration config;
private final ConcurrentLinkedQueue<Location> cachedLocations = new ConcurrentLinkedQueue<>();
private final Map<String, ConcurrentLinkedQueue<Location>> cachedLocations = new HashMap<>();
private WorldInfoProvider worldInfoProvider;
public RandomTeleport(final IEssentials essentials) {
this.ess = essentials;
config = new EssentialsConfiguration(new File(essentials.getDataFolder(), "tpr.yml"), "/tpr.yml",
"Configuration for the random teleport command.\nSome settings may be defaulted, and can be changed via the /settpr command in-game.");
reloadConfig();
"Configuration for the random teleport command.\nUse the /settpr command in-game to set random teleport locations.");
}
public EssentialsConfiguration getConfig() {
return config;
}
@Override
public void reloadConfig() {
worldInfoProvider = ess.provider(WorldInfoProvider.class);
config.load();
cachedLocations.clear();
}
public Location getCenter() {
try {
final LazyLocation center = config.getLocation("center");
if (center != null && center.location() != null) {
return center.location();
}
} catch (final InvalidWorldException ignored) {
public boolean hasLocation(final String name) {
return config.hasProperty("locations." + name);
}
public Location getCenter(final String name) {
final LazyLocation center = config.getLocation(locationKey(name, "center"));
if (center != null && center.location() != null) {
return center.location();
}
final Location center = ess.getServer().getWorlds().get(0).getWorldBorder().getCenter();
center.setY(center.getWorld().getHighestBlockYAt(center) + HIGHEST_BLOCK_Y_OFFSET);
setCenter(center);
return center;
final Location worldCenter = ess.getServer().getWorlds().get(0).getWorldBorder().getCenter();
worldCenter.setY(worldCenter.getWorld().getHighestBlockYAt(worldCenter) + HIGHEST_BLOCK_Y_OFFSET);
setCenter(name, worldCenter);
return worldCenter;
}
public void setCenter(final Location center) {
config.setProperty("center", center);
public void setCenter(final String name, final Location center) {
config.setProperty(locationKey(name, "center"), center);
config.save();
// Clear cached locations since center changed
this.getCachedLocations(name).clear();
}
public double getMinRange() {
return config.getDouble("min-range", 0d);
public double getMinRange(final String name) {
return config.getDouble(locationKey(name, "min-range"), 0d);
}
public void setMinRange(final double minRange) {
config.setProperty("min-range", minRange);
public void setMinRange(final String name, final double minRange) {
config.setProperty(locationKey(name, "min-range"), minRange);
config.save();
// Clear cached locations since min range changed
this.getCachedLocations(name).clear();
}
public double getMaxRange() {
return config.getDouble("max-range", getCenter().getWorld().getWorldBorder().getSize() / 2);
public double getMaxRange(final String name) {
return config.getDouble(locationKey(name, "max-range"), getCenter(name).getWorld().getWorldBorder().getSize() / 2);
}
public void setMaxRange(final double maxRange) {
config.setProperty("max-range", maxRange);
public void setMaxRange(final String name, final double maxRange) {
config.setProperty(locationKey(name, "max-range"), maxRange);
config.save();
// Clear cached locations since max range changed
this.getCachedLocations(name).clear();
}
public Set<Biome> getExcludedBiomes() {
final List<String> biomeNames = config.getList("excluded-biomes", String.class);
final Set<Biome> excludedBiomes = new HashSet<>();
for (final String biomeName : biomeNames) {
try {
excludedBiomes.add(Biome.valueOf(biomeName.toUpperCase()));
} catch (final IllegalArgumentException ignored) {
}
public String getDefaultLocation() {
return config.getString("default-location", "{world}");
}
public boolean isPerLocationPermission() {
return config.getBoolean("per-location-permission", false);
}
public Set<String> getExcludedBiomes() {
final Set<String> excludedBiomes = new HashSet<>();
for (final String key : config.getList("excluded-biomes", String.class)) {
excludedBiomes.add(key.toLowerCase());
}
return excludedBiomes;
}
@@ -96,39 +119,48 @@ public class RandomTeleport implements IConf {
return config.getInt("cache-threshold", 10);
}
public boolean getPreCache() {
return config.getBoolean("pre-cache", false);
public List<String> listLocations() {
return new ArrayList<>(ConfigurateUtil.getKeys(config.getRootNode().node("locations")));
}
public Queue<Location> getCachedLocations() {
return cachedLocations;
public Queue<Location> getCachedLocations(final String name) {
this.cachedLocations.computeIfAbsent(name, x -> new ConcurrentLinkedQueue<>());
return cachedLocations.get(name);
}
// Get a random location; cached if possible. Otherwise on demand.
public CompletableFuture<Location> getRandomLocation(final Location center, final double minRange, final double maxRange) {
final int findAttempts = this.getFindAttempts();
final Queue<Location> cachedLocations = this.getCachedLocations();
// Get a named random teleport location; cached if possible, otherwise on demand.
public CompletableFuture<Location> getRandomLocation(final String name) {
final Queue<Location> cached = this.getCachedLocations(name);
// Try to build up the cache if it is below the threshold
if (cachedLocations.size() < this.getCacheThreshold()) {
cacheRandomLocations(center, minRange, maxRange);
if (cached.size() < this.getCacheThreshold()) {
cacheRandomLocations(name);
}
final CompletableFuture<Location> future = new CompletableFuture<>();
// Return a random location immediately if one is available, otherwise try to find one now
if (cachedLocations.isEmpty()) {
if (cached.isEmpty()) {
final int findAttempts = this.getFindAttempts();
final Location center = this.getCenter(name);
final double minRange = this.getMinRange(name);
final double maxRange = this.getMaxRange(name);
attemptRandomLocation(findAttempts, center, minRange, maxRange).thenAccept(future::complete);
} else {
future.complete(cachedLocations.poll());
future.complete(cached.poll());
}
return future;
}
// Prompts caching random valid locations, up to a maximum number of attempts
public void cacheRandomLocations(final Location center, final double minRange, final double maxRange) {
// Get a random location with specific parameters (note: not cached).
public CompletableFuture<Location> getRandomLocation(final Location center, final double minRange, final double maxRange) {
return attemptRandomLocation(this.getFindAttempts(), center, minRange, maxRange);
}
// Prompts caching random valid locations, up to a maximum number of attempts.
public void cacheRandomLocations(final String name) {
ess.getServer().getScheduler().scheduleSyncDelayedTask(ess, () -> {
for (int i = 0; i < this.getFindAttempts(); ++i) {
calculateRandomLocation(center, minRange, maxRange).thenAccept(location -> {
calculateRandomLocation(getCenter(name), getMinRange(name), getMaxRange(name)).thenAccept(location -> {
if (isValidRandomLocation(location)) {
this.getCachedLocations().add(location);
this.getCachedLocations(name).add(location);
}
});
}
@@ -177,7 +209,7 @@ public class RandomTeleport implements IConf {
final Location location = new Location(
center.getWorld(),
center.getX() + offsetX,
ess.getWorldInfoProvider().getMaxHeight(center.getWorld()),
worldInfoProvider.getMaxHeight(center.getWorld()),
center.getZ() + offsetZ,
360 * RANDOM.nextFloat() - 180,
0
@@ -193,18 +225,51 @@ public class RandomTeleport implements IConf {
return future;
}
// Returns an appropriate elevation for a given location in the nether, or -1 if none is found
// Returns an appropriate elevation for a given location in the nether, or MIN_VALUE if none is found
private double getNetherYAt(final Location location) {
for (int y = 32; y < ess.getWorldInfoProvider().getMaxHeight(location.getWorld()); ++y) {
if (!LocationUtil.isBlockUnsafe(ess, location.getWorld(), location.getBlockX(), y, location.getBlockZ())) {
final World world = location.getWorld();
for (int y = 32; y < worldInfoProvider.getMaxHeight(world); ++y) {
if (Material.BEDROCK.equals(world.getBlockAt(location.getBlockX(), y, location.getBlockZ()).getType())) {
break;
}
if (!LocationUtil.isBlockUnsafe(ess, world, location.getBlockX(), y, location.getBlockZ())) {
return y;
}
}
return -1;
return Double.MIN_VALUE;
}
private boolean isValidRandomLocation(final Location location) {
return location.getBlockY() > ess.getWorldInfoProvider().getMinHeight(location.getWorld()) && !this.getExcludedBiomes().contains(location.getBlock().getBiome());
return location.getBlockY() > worldInfoProvider.getMinHeight(location.getWorld()) && !isExcludedBiome(location);
}
// Exclude biome if enum or namespaced key matches
private boolean isExcludedBiome(final Location location) {
final BiomeNameProvider biomeNameProvider = ess.provider(BiomeNameProvider.class);
final Set<String> excluded = getExcludedBiomes();
final String enumKey = biomeNameProvider.getBiomeName(location.getBlock());
// Try with good old bukkit enum
if (excluded.contains(enumKey)) {
return true;
}
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_14_4_R01)) {
// No way to get the biome key on versions below this
return false;
}
final String biomeKey;
final BiomeKeyProvider biomeKeyProvider = ess.provider(BiomeKeyProvider.class);
if (biomeKeyProvider != null) {
// Works with custom biome keys
biomeKey = biomeKeyProvider.getBiomeKey(location.getBlock()).toString();
} else {
// Custom biome keys resolve as "minecraft:custom" which is unfortunate
biomeKey = location.getBlock().getBiome().getKey().toString();
}
return excluded.contains(biomeKey);
}
private String locationKey(final String name, final String key) {
return "locations." + name + "." + key;
}
public File getFile() {
@@ -1,5 +1,6 @@
package com.earth2me.essentials;
import com.earth2me.essentials.adventure.AdventureUtil;
import com.earth2me.essentials.api.IItemDb;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.config.ConfigurateUtil;
@@ -13,11 +14,17 @@ import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.LocationUtil;
import com.earth2me.essentials.utils.NumberUtil;
import net.ess3.api.IEssentials;
import net.ess3.provider.KnownCommandsProvider;
import net.ess3.provider.SyncCommandsProvider;
import net.essentialsx.api.v2.ChatType;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.ChatColor;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.event.EventPriority;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.spongepowered.configurate.CommentedConfigurationNode;
import java.io.File;
@@ -29,6 +36,7 @@ import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -36,22 +44,26 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class Settings implements net.ess3.api.ISettings {
private static final BigDecimal DEFAULT_MAX_MONEY = new BigDecimal("10000000000000");
private static final BigDecimal DEFAULT_MIN_MONEY = new BigDecimal("-10000000000000");
private static final String DEFAULT_PRIMARY_COLOR = NamedTextColor.GOLD.toString();
private static final String DEFAULT_SECONDARY_COLOR = NamedTextColor.RED.toString();
private final transient EssentialsConfiguration config;
private final transient IEssentials ess;
private final transient AtomicInteger reloadCount = new AtomicInteger(0);
private final Map<String, String> chatFormats = Collections.synchronizedMap(new HashMap<>());
private final ChatFormats chatFormats = new ChatFormats();
private int chatRadius = 0;
// #easteregg
private char chatShout = '!';
@@ -79,6 +91,7 @@ public class Settings implements net.ess3.api.ISettings {
private BigDecimal maxMoney = DEFAULT_MAX_MONEY;
private BigDecimal minMoney = DEFAULT_MIN_MONEY;
private boolean economyLog = false;
private boolean economyLogUUID = false;
// #easteregg
private boolean economyLogUpdate = false;
private boolean changeDisplayName = true;
@@ -132,10 +145,17 @@ public class Settings implements net.ess3.api.ISettings {
private boolean isWaterSafe;
private boolean isSafeUsermap;
private boolean logCommandBlockCommands;
private boolean logConsoleCommands;
private Set<Predicate<String>> nickBlacklist;
private boolean resetNickOnNameChange;
private double maxProjectileSpeed;
private boolean removeEffectsOnHeal;
private Map<String, String> worldAliases;
private String primaryColor = DEFAULT_PRIMARY_COLOR;
private String secondaryColor = DEFAULT_SECONDARY_COLOR;
private Set<String> multiplierPerms;
private BigDecimal defaultMultiplier;
private List<String> afkTimeoutCommands = Collections.emptyList();
public Settings(final IEssentials ess) {
this.ess = ess;
@@ -153,6 +173,16 @@ public class Settings implements net.ess3.api.ISettings {
return config.getBoolean("respawn-at-home", false);
}
@Override
public String getRandomSpawnLocation() {
return config.getString("random-spawn-location", "none");
}
@Override
public String getRandomRespawnLocation() {
return config.getString("random-respawn-location", "none");
}
@Override
public boolean isRespawnAtAnchor() {
return config.getBoolean("respawn-at-anchor", false);
@@ -179,7 +209,7 @@ public class Settings implements net.ess3.api.ISettings {
final Set<String> homeList = getMultipleHomes();
if (homeList != null) {
for (final String set : homeList) {
if (user.isAuthorized("essentials.sethome.multiple." + set) && (limit < getHomeLimit(set))) {
if (user.isAuthorized("essentials.sethome.multiple." + set) && limit < getHomeLimit(set)) {
limit = getHomeLimit(set);
}
}
@@ -239,6 +269,11 @@ public class Settings implements net.ess3.api.ISettings {
return config.getBoolean("chat.question-enabled", true);
}
@Override
public boolean isUsePaperChatEvent() {
return config.getBoolean("chat.paper-chat-events", true);
}
public boolean _isTeleportSafetyEnabled() {
return config.getBoolean("teleport-safety", true);
}
@@ -313,9 +348,11 @@ public class Settings implements net.ess3.api.ISettings {
}
private void _addAlternativeCommand(final String label, final Command current) {
final KnownCommandsProvider knownCommandsProvider = ess.provider(KnownCommandsProvider.class);
Command cmd = ess.getAlternativeCommandsHandler().getAlternative(label);
if (cmd == null) {
for (final Map.Entry<String, Command> entry : ess.getKnownCommandsProvider().getKnownCommands().entrySet()) {
for (final Map.Entry<String, Command> entry : knownCommandsProvider.getKnownCommands().entrySet()) {
final String[] split = entry.getKey().split(":");
if (entry.getValue() != current && split[split.length - 1].equals(label)) {
cmd = entry.getValue();
@@ -325,7 +362,7 @@ public class Settings implements net.ess3.api.ISettings {
}
if (cmd != null) {
ess.getKnownCommandsProvider().getKnownCommands().put(label, cmd);
knownCommandsProvider.getKnownCommands().put(label, cmd);
}
}
@@ -447,6 +484,11 @@ public class Settings implements net.ess3.api.ISettings {
return config.getBoolean("socialspy-messages", true);
}
@Override
public boolean isSocialSpyDisplayNames() {
return config.getBoolean("socialspy-uses-displaynames", true);
}
private Set<String> _getMuteCommands() {
final Set<String> muteCommands = new HashSet<>();
if (config.isList("mute-commands")) {
@@ -467,11 +509,20 @@ public class Settings implements net.ess3.api.ISettings {
return config.getString("nickname-prefix", "~");
}
private boolean _resetNickOnNameChange() {
return config.getBoolean("reset-nick-on-name-change", false);
}
@Override
public String getNicknamePrefix() {
return nicknamePrefix;
}
@Override
public boolean isResetNickOnNameChange() {
return resetNickOnNameChange;
}
@Override
public double getTeleportCooldown() {
return config.getDouble("teleport-cooldown", 0);
@@ -565,32 +616,132 @@ public class Settings implements net.ess3.api.ISettings {
@Override
public String getChatFormat(final String group) {
String mFormat = chatFormats.get(group);
if (mFormat == null) {
mFormat = config.getString("chat.group-formats." + (group == null ? "Default" : group), config.getString("chat.format", "&7[{GROUP}]&r {DISPLAYNAME}&7:&r {MESSAGE}"));
mFormat = FormatUtil.replaceFormat(mFormat);
mFormat = mFormat.replace("{DISPLAYNAME}", "%1$s");
mFormat = mFormat.replace("{MESSAGE}", "%2$s");
mFormat = mFormat.replace("{GROUP}", "{0}");
mFormat = mFormat.replace("{WORLD}", "{1}");
mFormat = mFormat.replace("{WORLDNAME}", "{1}");
mFormat = mFormat.replace("{SHORTWORLDNAME}", "{2}");
mFormat = mFormat.replace("{TEAMPREFIX}", "{3}");
mFormat = mFormat.replace("{TEAMSUFFIX}", "{4}");
mFormat = mFormat.replace("{TEAMNAME}", "{5}");
mFormat = mFormat.replace("{PREFIX}", "{6}");
mFormat = mFormat.replace("{SUFFIX}", "{7}");
mFormat = mFormat.replace("{USERNAME}", "{8}");
mFormat = mFormat.replace("{NICKNAME}", "{9}");
mFormat = "§r".concat(mFormat);
chatFormats.put(group, mFormat);
}
return getChatFormat(group, null);
}
@Override
public String getChatFormat(final String group, final ChatType chatType) {
final String mFormat = chatFormats.getFormat(group, chatType, new ChatFormatConfigSupplier(group, chatType));
if (isDebug()) {
ess.getLogger().info(String.format("Found format '%s' for group '%s'", mFormat, group));
}
return mFormat;
}
private class ChatFormatConfigSupplier implements Supplier<String> {
private final String group;
private final ChatType chatType;
ChatFormatConfigSupplier(String group, ChatType chatType) {
this.group = group;
this.chatType = chatType;
}
@Override
public String get() {
final String chatKey = chatType.key();
final String groupPath = "chat.group-formats." + (group == null ? "Default" : group);
String configFormat = config.getString(groupPath + "." + chatKey, null);
if (configFormat == null) {
configFormat = config.getString(groupPath, null);
}
final String formatPath = "chat.format";
if (configFormat == null) {
configFormat = config.getString(formatPath + "." + chatKey, null);
}
if (configFormat == null) {
configFormat = config.getString(formatPath, null);
}
if (configFormat == null) {
configFormat = "&7[{GROUP}]&r {DISPLAYNAME}&7:&r {MESSAGE}";
}
configFormat = FormatUtil.replaceFormat(configFormat);
configFormat = configFormat.replace("{DISPLAYNAME}", "%1$s");
configFormat = configFormat.replace("{MESSAGE}", "%2$s");
configFormat = configFormat.replace("{GROUP}", "{0}");
configFormat = configFormat.replace("{WORLD}", "{1}");
configFormat = configFormat.replace("{WORLDNAME}", "{1}");
configFormat = configFormat.replace("{SHORTWORLDNAME}", "{2}");
configFormat = configFormat.replace("{TEAMPREFIX}", "{3}");
configFormat = configFormat.replace("{TEAMSUFFIX}", "{4}");
configFormat = configFormat.replace("{TEAMNAME}", "{5}");
configFormat = configFormat.replace("{PREFIX}", "{6}");
configFormat = configFormat.replace("{SUFFIX}", "{7}");
configFormat = configFormat.replace("{USERNAME}", "{8}");
configFormat = configFormat.replace("{NICKNAME}", "{9}");
configFormat = "§r".concat(configFormat);
return configFormat;
}
}
private static class ChatFormats {
private final Map<String, TypedChatFormat> groupFormats;
private TypedChatFormat defaultFormat;
ChatFormats() {
defaultFormat = null;
groupFormats = new HashMap<>();
}
public String getFormat(String group, ChatType type, Supplier<String> configSupplier) {
// With such a large synchronize block, we synchronize a potential config deserialization
// It does not matter as it needs to be done. It's even better as we ensure to do it once
// TypedChatFormat is also synchronized
synchronized (this) {
final TypedChatFormat typedChatFormat;
if (group == null) {
if (defaultFormat == null) {
defaultFormat = new TypedChatFormat();
}
typedChatFormat = defaultFormat;
} else {
typedChatFormat = groupFormats.computeIfAbsent(group, s -> new TypedChatFormat());
}
return typedChatFormat.getFormat(type, configSupplier);
}
}
public void clear() {
synchronized (this) {
defaultFormat = null;
groupFormats.clear();
}
}
}
private static class TypedChatFormat {
private final Map<ChatType, String> typedFormats;
private String defaultFormat;
TypedChatFormat() {
defaultFormat = null;
typedFormats = new EnumMap<>(ChatType.class);
}
public String getFormat(ChatType type, Supplier<String> configSupplier) {
final String format;
if (type == null) {
if (defaultFormat == null) {
defaultFormat = configSupplier.get();
}
format = defaultFormat;
} else {
format = typedFormats.computeIfAbsent(type, c -> configSupplier.get());
}
return format;
}
}
@Override
public String getWorldAlias(String world) {
return worldAliases.getOrDefault(world.toLowerCase(), world);
@@ -679,18 +830,32 @@ public class Settings implements net.ess3.api.ISettings {
overriddenCommands = _getOverriddenCommands();
playerCommands = _getPlayerCommands();
final KnownCommandsProvider knownCommandsProvider = ess.provider(KnownCommandsProvider.class);
// This will be late loaded
if (ess.getKnownCommandsProvider() != null) {
if (knownCommandsProvider != null) {
boolean mapModified = false;
if (!disabledBukkitCommands.isEmpty()) {
if (isDebug()) {
ess.getLogger().log(Level.INFO, "Re-adding " + disabledBukkitCommands.size() + " disabled commands!");
}
ess.getKnownCommandsProvider().getKnownCommands().putAll(disabledBukkitCommands);
knownCommandsProvider.getKnownCommands().putAll(disabledBukkitCommands);
disabledBukkitCommands.clear();
mapModified = true;
}
if (reloadCount.get() < 2) {
// on startup: add plugins again in case they registered commands with the new API
// we need to schedule this task before any of the below tasks using _addAlternativeCommand.
ess.scheduleSyncDelayedTask(() -> {
for (final Plugin plugin : ess.getServer().getPluginManager().getPlugins()) {
if (plugin.isEnabled()) {
ess.getAlternativeCommandsHandler().addPlugin(plugin);
}
}
});
}
for (final String command : disabledCommands) {
final String effectiveAlias = command.toLowerCase(Locale.ENGLISH);
final Command toDisable = ess.getPluginCommand(effectiveAlias);
@@ -698,7 +863,7 @@ public class Settings implements net.ess3.api.ISettings {
if (isDebug()) {
ess.getLogger().log(Level.INFO, "Attempting removal of " + effectiveAlias);
}
final Command removed = ess.getKnownCommandsProvider().getKnownCommands().remove(effectiveAlias);
final Command removed = knownCommandsProvider.getKnownCommands().remove(effectiveAlias);
if (removed != null) {
if (isDebug()) {
ess.getLogger().log(Level.INFO, "Adding command " + effectiveAlias + " to disabled map!");
@@ -716,19 +881,22 @@ public class Settings implements net.ess3.api.ISettings {
}
}
final SyncCommandsProvider syncCommandsProvider = ess.provider(SyncCommandsProvider.class);
if (mapModified) {
if (isDebug()) {
ess.getLogger().log(Level.INFO, "Syncing commands");
}
if (reloadCount.get() < 2) {
ess.scheduleSyncDelayedTask(() -> ess.getSyncCommandsProvider().syncCommands());
ess.scheduleSyncDelayedTask(syncCommandsProvider::syncCommands);
} else {
ess.getSyncCommandsProvider().syncCommands();
syncCommandsProvider.syncCommands();
}
}
}
nicknamePrefix = _getNicknamePrefix();
resetNickOnNameChange = _resetNickOnNameChange();
operatorColor = _getOperatorColor();
changePlayerListName = _changePlayerListName();
configDebug = _isDebug();
@@ -748,6 +916,7 @@ public class Settings implements net.ess3.api.ISettings {
permissionsLagWarning = _getPermissionsLagWarning();
economyLagWarning = _getEconomyLagWarning();
economyLog = _isEcoLogEnabled();
economyLogUUID = _isEcoLogUUIDEnabled();
economyLogUpdate = _isEcoLogUpdateEnabled();
economyDisabled = _isEcoDisabled();
allowSilentJoin = _allowSilentJoinQuit();
@@ -772,6 +941,7 @@ public class Settings implements net.ess3.api.ISettings {
isWaterSafe = _isWaterSafe();
isSafeUsermap = _isSafeUsermap();
logCommandBlockCommands = _logCommandBlockCommands();
logConsoleCommands = _logConsoleCommands();
nickBlacklist = _getNickBlacklist();
maxProjectileSpeed = _getMaxProjectileSpeed();
removeEffectsOnHeal = _isRemovingEffectsOnHeal();
@@ -779,6 +949,11 @@ public class Settings implements net.ess3.api.ISettings {
bindingItemPolicy = _getBindingItemsPolicy();
currencySymbol = _getCurrencySymbol();
worldAliases = _getWorldAliases();
primaryColor = _getPrimaryColor();
secondaryColor = _getSecondaryColor();
multiplierPerms = _getMultiplierPerms();
defaultMultiplier = _getDefaultMultiplier();
afkTimeoutCommands = _getAfkTimeoutCommands();
reloadCount.incrementAndGet();
}
@@ -809,7 +984,7 @@ public class Settings implements net.ess3.api.ISettings {
final ItemStack iStack = itemDb.get(itemName);
epItemSpwn.add(iStack.getType());
} catch (final Exception ex) {
ess.getLogger().log(Level.SEVERE, tl("unknownItemInList", itemName, "item-spawn-blacklist"), ex);
ess.getLogger().log(Level.SEVERE, ess.getAdventureFacet().miniToLegacy(tlLiteral("unknownItemInList", itemName, "item-spawn-blacklist")), ex);
}
}
return epItemSpwn;
@@ -837,7 +1012,7 @@ public class Settings implements net.ess3.api.ISettings {
try {
newSigns.add(Signs.valueOf(signName).getSign());
} catch (final Exception ex) {
ess.getLogger().log(Level.SEVERE, tl("unknownItemInList", signName, "enabledSigns"));
ess.getLogger().log(Level.SEVERE, ess.getAdventureFacet().miniToLegacy(tlLiteral("unknownItemInList", signName, "enabledSigns")));
continue;
}
signsEnabled = true;
@@ -883,6 +1058,11 @@ public class Settings implements net.ess3.api.ISettings {
return config.getString("locale", "");
}
@Override
public boolean isPerPlayerLocale() {
return config.getBoolean("per-player-locale", false);
}
private String currencySymbol = "$";
// A valid currency symbol value must be one non-integer character.
@@ -932,14 +1112,23 @@ public class Settings implements net.ess3.api.ISettings {
}
@Override
public List<Material> getProtectList(final String configName) {
final List<Material> list = new ArrayList<>();
public List<String> getProtectListRaw(String configName) {
final List<String> list = new ArrayList<>();
for (String itemName : config.getString(configName, "").split(",")) {
itemName = itemName.trim();
if (itemName.isEmpty()) {
continue;
}
list.add(itemName);
}
return list;
}
@Override
public List<Material> getProtectList(final String configName) {
final List<Material> list = new ArrayList<>();
for (String itemName : getProtectListRaw(configName)) {
Material mat = EnumUtil.getMaterial(itemName.toUpperCase());
if (mat == null) {
@@ -951,7 +1140,7 @@ public class Settings implements net.ess3.api.ISettings {
}
if (mat == null) {
ess.getLogger().log(Level.SEVERE, tl("unknownItemInList", itemName, configName));
ess.getLogger().log(Level.SEVERE, ess.getAdventureFacet().miniToLegacy(tlLiteral("unknownItemInList", itemName, configName)));
} else {
list.add(mat);
}
@@ -1000,6 +1189,14 @@ public class Settings implements net.ess3.api.ISettings {
return config.getBoolean("economy-log-enabled", false);
}
public boolean _isEcoLogUUIDEnabled() {
return config.getBoolean("economy-log-uuids", false);
}
public boolean isEcoLogUUIDEnabled() {
return economyLogUUID;
}
@Override
public boolean isEcoLogUpdateEnabled() {
return economyLogUpdate;
@@ -1089,8 +1286,17 @@ public class Settings implements net.ess3.api.ISettings {
}
@Override
public long getAutoAfkKick() {
return config.getLong("auto-afk-kick", -1);
public long getAutoAfkTimeout() {
return config.getLong("auto-afk-timeout", config.getLong("auto-afk-kick", -1));
}
private List<String> _getAfkTimeoutCommands() {
return new ArrayList<>(config.getList("afk-timeout-commands", String.class));
}
@Override
public List<String> getAfkTimeoutCommands() {
return afkTimeoutCommands;
}
@Override
@@ -1125,6 +1331,11 @@ public class Settings implements net.ess3.api.ISettings {
return config.getBoolean("cancel-afk-on-chat", true);
}
@Override
public boolean cancelAfkOnFish() {
return config.getBoolean("cancel-afk-on-fish", true);
}
@Override
public boolean sleepIgnoresAfkPlayers() {
return sleepIgnoresAfkPlayers;
@@ -1446,6 +1657,11 @@ public class Settings implements net.ess3.api.ISettings {
return config.getBoolean("use-custom-server-full-message", true);
}
@Override
public boolean isCustomWhitelistMessage() {
return config.getBoolean("use-custom-whitelist-message", true);
}
@Override
public int getJoinQuitMessagePlayerCount() {
return config.getInt("hide-join-quit-messages-above", -1);
@@ -1469,10 +1685,16 @@ public class Settings implements net.ess3.api.ISettings {
// #easteregg
@Override
public int getMaxUserCacheCount() {
final long count = Runtime.getRuntime().maxMemory() / 1024 / 96;
final long count = Runtime.getRuntime().maxMemory() / 1024 / 1024;
return config.getInt("max-user-cache-count", (int) count);
}
// #easteregg
@Override
public long getMaxUserCacheValueExpiry() {
return config.getLong("max-user-cache-value-expiry", 600);
}
@Override
public boolean isLastMessageReplyRecipient() {
return config.getBoolean("last-message-reply-recipient", false);
@@ -1693,7 +1915,7 @@ public class Settings implements net.ess3.api.ISettings {
try {
newSigns.add(Signs.valueOf(signName).getSign());
} catch (final Exception ex) {
ess.getLogger().log(Level.SEVERE, tl("unknownItemInList", signName, "unprotected-sign-names"));
ess.getLogger().log(Level.SEVERE, ess.getAdventureFacet().miniToLegacy(tlLiteral("unknownItemInList", signName, "unprotected-sign-names")));
}
}
return newSigns;
@@ -1754,6 +1976,16 @@ public class Settings implements net.ess3.api.ISettings {
return config.getBoolean("world-change-fly-reset", true);
}
@Override
public boolean isWorldChangePreserveFlying() {
return config.getBoolean("world-change-preserve-flying", true);
}
@Override
public boolean isGamemodeChangePreserveFlying() {
return config.getBoolean("gamemode-change-preserve-flying", false);
}
@Override
public boolean isWorldChangeSpeedResetEnabled() {
return config.getBoolean("world-change-speed-reset", true);
@@ -1761,9 +1993,7 @@ public class Settings implements net.ess3.api.ISettings {
private List<String> _getDefaultEnabledConfirmCommands() {
final List<String> commands = config.getList("default-enabled-confirm-commands", String.class);
for (int i = 0; i < commands.size(); i++) {
commands.set(i, commands.get(i).toLowerCase());
}
commands.replaceAll(String::toLowerCase);
return commands;
}
@@ -1871,6 +2101,15 @@ public class Settings implements net.ess3.api.ISettings {
return logCommandBlockCommands;
}
private boolean _logConsoleCommands() {
return config.getBoolean("log-console-commands", true);
}
@Override
public boolean logConsoleCommands() {
return logConsoleCommands;
}
private Set<Predicate<String>> _getNickBlacklist() {
final Set<Predicate<String>> blacklist = new HashSet<>();
@@ -1937,4 +2176,95 @@ public class Settings implements net.ess3.api.ISettings {
public boolean showZeroBaltop() {
return config.getBoolean("show-zero-baltop", true);
}
@Override
public String getNickRegex() {
return config.getString("allowed-nicks-regex", "^[a-zA-Z_0-9§]+$");
}
@Override
public BigDecimal getMultiplier(final User user) {
BigDecimal multiplier = defaultMultiplier;
if (multiplierPerms == null) {
return defaultMultiplier;
}
for (final String multiplierPerm : multiplierPerms) {
if (user.isAuthorized("essentials.sell.multiplier." + multiplierPerm)) {
final BigDecimal value = config.getBigDecimal("sell-multipliers." + multiplierPerm, BigDecimal.ZERO);
if (value.compareTo(multiplier) > 0) {
multiplier = value;
}
}
}
return multiplier;
}
private BigDecimal _getDefaultMultiplier() {
return config.getBigDecimal("sell-multipliers.default", BigDecimal.ONE);
}
private Set<String> _getMultiplierPerms() {
final CommentedConfigurationNode section = config.getSection("sell-multipliers");
return section == null ? null : ConfigurateUtil.getKeys(section);
}
public int getMaxItemLore() {
return config.getInt("max-itemlore-lines", 10);
}
@Override
public String getPrimaryColor() {
return primaryColor;
}
private String _getPrimaryColor() {
final String color = config.getString("message-colors.primary", "#ffaa00");
final String textColor = _getTagColor(color);
return textColor != null ? textColor : DEFAULT_PRIMARY_COLOR;
}
@Override
public String getSecondaryColor() {
return secondaryColor;
}
private String _getSecondaryColor() {
final String color = config.getString("message-colors.secondary", "#ff5555");
final String textColor = _getTagColor(color);
return textColor != null ? textColor : DEFAULT_SECONDARY_COLOR;
}
private String _getTagColor(final String color) {
try {
if (color.startsWith("#") && color.length() == 7 && NumberUtil.isHexadecimal(color.substring(1))) {
Color.fromRGB(Integer.decode(color));
return color;
}
if (color.length() == 1) {
return Objects.requireNonNull(AdventureUtil.fromChar(color.charAt(0))).toString();
}
return Objects.requireNonNull(NamedTextColor.NAMES.value(color.toLowerCase(Locale.ENGLISH))).toString();
} catch (NullPointerException | IllegalArgumentException ignored) {
}
return null;
}
@Override
public BigDecimal getBaltopMinBalance() {
return config.getBigDecimal("baltop-requirements.minimum-balance", BigDecimal.ZERO);
}
@Override
public int getBaltopEntryLimit() {
return config.getInt("baltop-entry-limit", -1);
}
@Override
public long getBaltopMinPlaytime() {
return config.getLong("baltop-requirements.minimum-playtime", 0);
}
}
@@ -1,12 +1,13 @@
package com.earth2me.essentials;
import com.earth2me.essentials.Mob.MobException;
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.LocationUtil;
import com.earth2me.essentials.utils.StringUtil;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.api.IEssentials;
import net.ess3.api.TranslatableException;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Server;
@@ -27,8 +28,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import static com.earth2me.essentials.I18n.tl;
public final class SpawnMob {
private static final Material GOLDEN_HELMET = EnumUtil.getMaterial("GOLDEN_HELMET", "GOLD_HELMET");
@@ -49,7 +48,7 @@ public final class SpawnMob {
}
}
if (availableList.isEmpty()) {
availableList.add(tl("none"));
availableList.add(user.playerTl("none"));
}
return StringUtil.joinList(availableList);
}
@@ -91,7 +90,7 @@ public final class SpawnMob {
public static void spawnmob(final IEssentials ess, final Server server, final User user, final List<String> parts, final List<String> data, final int mobCount) throws Exception {
final Block block = LocationUtil.getTarget(user.getBase()).getBlock();
if (block == null) {
throw new Exception(tl("unableToSpawnMob"));
throw new TranslatableException("unableToSpawnMob");
}
spawnmob(ess, server, user.getSource(), user, block.getLocation(), parts, data, mobCount);
}
@@ -122,7 +121,7 @@ public final class SpawnMob {
if (mobCount > effectiveLimit) {
mobCount = effectiveLimit;
sender.sendMessage(tl("mobSpawnLimit"));
sender.sendTl("mobSpawnLimit");
}
final Mob mob = Mob.fromName(parts.get(0)); // Get the first mob
@@ -130,13 +129,13 @@ public final class SpawnMob {
for (int i = 0; i < mobCount; i++) {
spawnMob(ess, server, sender, target, sloc, parts, data);
}
sender.sendMessage(mobCount * parts.size() + " " + mob.name.toLowerCase(Locale.ENGLISH) + mob.suffix + " " + tl("spawned"));
sender.sendMessage(mobCount * parts.size() + " " + mob.name.toLowerCase(Locale.ENGLISH) + mob.suffix + " " + sender.tl("spawned"));
} catch (final MobException e1) {
throw new Exception(tl("unableToSpawnMob"), e1);
throw new TranslatableException(e1, "unableToSpawnMob");
} catch (final NumberFormatException e2) {
throw new Exception(tl("numberRequired"), e2);
throw new TranslatableException(e2, "numberRequired");
} catch (final NullPointerException np) {
throw new Exception(tl("soloMob"), np);
throw new TranslatableException(np, "soloMob");
}
}
@@ -176,15 +175,15 @@ public final class SpawnMob {
private static void checkSpawnable(final IEssentials ess, final CommandSource sender, final Mob mob) throws Exception {
if (mob == null || mob.getType() == null) {
throw new Exception(tl("invalidMob"));
throw new TranslatableException("invalidMob");
}
if (ess.getSettings().getProtectPreventSpawn(mob.getType().toString().toLowerCase(Locale.ENGLISH))) {
throw new Exception(tl("disabledToSpawnMob"));
throw new TranslatableException("disabledToSpawnMob");
}
if (sender.isPlayer() && !ess.getUser(sender.getPlayer()).isAuthorized("essentials.spawnmob." + mob.name.toLowerCase(Locale.ENGLISH))) {
throw new Exception(tl("noPermToSpawnMob"));
throw new TranslatableException("noPermToSpawnMob");
}
}
@@ -192,7 +191,7 @@ public final class SpawnMob {
String data = inputData;
if (data.isEmpty()) {
sender.sendMessage(tl("mobDataList", StringUtil.joinList(MobData.getValidHelp(spawned))));
sender.sendTl("mobDataList", StringUtil.joinList(MobData.getValidHelp(spawned)));
}
if (spawned instanceof Zombie) {
@@ -251,8 +250,8 @@ public final class SpawnMob {
private static void defaultMobData(final EntityType type, final Entity spawned) {
if (type == EntityType.SKELETON) {
final EntityEquipment invent = ((LivingEntity) spawned).getEquipment();
InventoryWorkaround.setItemInMainHand(invent, new ItemStack(Material.BOW, 1));
InventoryWorkaround.setItemInMainHandDropChance(invent, 0.1f);
Inventories.setItemInMainHand(invent, new ItemStack(Material.BOW, 1));
Inventories.setItemInMainHandDropChance(invent, 0.1f);
}
if (type == MobCompat.ZOMBIFIED_PIGLIN) {
@@ -260,8 +259,8 @@ public final class SpawnMob {
setVillager(zombie, false);
final EntityEquipment invent = zombie.getEquipment();
InventoryWorkaround.setItemInMainHand(invent, new ItemStack(GOLDEN_SWORD, 1));
InventoryWorkaround.setItemInMainHandDropChance(invent, 0.1f);
Inventories.setItemInMainHand(invent, new ItemStack(GOLDEN_SWORD, 1));
Inventories.setItemInMainHandDropChance(invent, 0.1f);
}
if (type == EntityType.ZOMBIE) {
@@ -1,51 +1,22 @@
package com.earth2me.essentials;
import net.ess3.api.IEssentials;
import org.bukkit.entity.LivingEntity;
import com.earth2me.essentials.commands.Commandnuke;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityExplodeEvent;
public class TNTExplodeListener implements Listener, Runnable {
private final transient IEssentials ess;
private transient boolean enabled = false;
private transient int timer = -1;
public TNTExplodeListener(final IEssentials ess) {
super();
this.ess = ess;
}
public void enable() {
if (!enabled) {
enabled = true;
timer = ess.scheduleSyncDelayedTask(this, 200);
return;
}
if (timer != -1) {
ess.getScheduler().cancelTask(timer);
timer = ess.scheduleSyncDelayedTask(this, 200);
}
}
public class TNTExplodeListener implements Listener {
@EventHandler(priority = EventPriority.LOW)
public void onEntityExplode(final EntityExplodeEvent event) {
if (!enabled) {
if (!(event.getEntity() instanceof TNTPrimed)) {
return;
}
if (event.getEntity() instanceof LivingEntity) {
return;
}
if (event.blockList().size() < 1) {
if (!event.getEntity().hasMetadata(Commandnuke.NUKE_META_KEY)) {
return;
}
event.setCancelled(true);
event.getLocation().getWorld().createExplosion(event.getLocation(), 0F);
}
@Override
public void run() {
enabled = false;
}
}
@@ -1,403 +0,0 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.DateUtil;
import com.earth2me.essentials.utils.LocationUtil;
import io.papermc.lib.PaperLib;
import net.ess3.api.IEssentials;
import net.ess3.api.ITeleport;
import net.ess3.api.IUser;
import net.ess3.api.events.UserWarpEvent;
import net.ess3.api.events.teleport.PreTeleportEvent;
import net.ess3.api.events.teleport.TeleportWarmupEvent;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.GregorianCalendar;
import static com.earth2me.essentials.I18n.tl;
/**
* @deprecated This API is not asynchronous. Use {@link com.earth2me.essentials.AsyncTeleport AsyncTeleport}
*/
@Deprecated
public class Teleport implements ITeleport {
private final IUser teleportOwner;
private final IEssentials ess;
private TimedTeleport timedTeleport;
private TeleportType tpType;
@Deprecated
public Teleport(final IUser user, final IEssentials ess) {
this.teleportOwner = user;
this.ess = ess;
tpType = TeleportType.NORMAL;
}
@Deprecated
public void cooldown(final boolean check) throws Exception {
final Calendar time = new GregorianCalendar();
if (teleportOwner.getLastTeleportTimestamp() > 0) {
// Take the current time, and remove the delay from it.
final double cooldown = ess.getSettings().getTeleportCooldown();
final Calendar earliestTime = new GregorianCalendar();
earliestTime.add(Calendar.SECOND, -(int) cooldown);
earliestTime.add(Calendar.MILLISECOND, -(int) ((cooldown * 1000.0) % 1000.0));
// This value contains the most recent time a teleportPlayer could have been used that would allow another use.
final long earliestLong = earliestTime.getTimeInMillis();
// When was the last teleportPlayer used?
final long lastTime = teleportOwner.getLastTeleportTimestamp();
if (lastTime > time.getTimeInMillis()) {
// This is to make sure time didn't get messed up on last teleportPlayer use.
// If this happens, let's give the user the benifit of the doubt.
teleportOwner.setLastTeleportTimestamp(time.getTimeInMillis());
return;
} else if (lastTime > earliestLong
&& cooldownApplies()) {
time.setTimeInMillis(lastTime);
time.add(Calendar.SECOND, (int) cooldown);
time.add(Calendar.MILLISECOND, (int) ((cooldown * 1000.0) % 1000.0));
throw new Exception(tl("timeBeforeTeleport", DateUtil.formatDateDiff(time.getTimeInMillis())));
}
}
// if justCheck is set, don't update lastTeleport; we're just checking
if (!check) {
teleportOwner.setLastTeleportTimestamp(time.getTimeInMillis());
}
}
@Deprecated
private boolean cooldownApplies() {
boolean applies = true;
final String globalBypassPerm = "essentials.teleport.cooldown.bypass";
switch (tpType) {
case NORMAL:
applies = !teleportOwner.isAuthorized(globalBypassPerm);
break;
case BACK:
applies = !(teleportOwner.isAuthorized(globalBypassPerm) &&
teleportOwner.isAuthorized("essentials.teleport.cooldown.bypass.back"));
break;
case TPA:
applies = !(teleportOwner.isAuthorized(globalBypassPerm) &&
teleportOwner.isAuthorized("essentials.teleport.cooldown.bypass.tpa"));
break;
}
return applies;
}
@Deprecated
private void warnUser(final IUser user, final double delay) {
final Calendar c = new GregorianCalendar();
c.add(Calendar.SECOND, (int) delay);
c.add(Calendar.MILLISECOND, (int) ((delay * 1000.0) % 1000.0));
user.sendMessage(tl("dontMoveMessage", DateUtil.formatDateDiff(c.getTimeInMillis())));
}
//The now function is used when you want to skip tp delay when teleporting someone to a location or player.
@Override
@Deprecated
public void now(final Location loc, final boolean cooldown, final TeleportCause cause) throws Exception {
if (cooldown) {
cooldown(false);
}
final ITarget target = new LocationTarget(loc);
now(teleportOwner, target, cause);
}
@Override
@Deprecated
public void now(final Player entity, final boolean cooldown, final TeleportCause cause) throws Exception {
if (cooldown) {
cooldown(false);
}
final ITarget target = new PlayerTarget(entity);
now(teleportOwner, target, cause);
teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ()));
}
@Deprecated
protected void now(final IUser teleportee, final ITarget target, final TeleportCause cause) throws Exception {
cancel(false);
Location loc = target.getLocation();
final PreTeleportEvent event = new PreTeleportEvent(teleportee, cause, target);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
if (teleportee.isAuthorized("essentials.back.onteleport")) {
teleportee.setLastLocation();
}
if (!teleportee.getBase().isEmpty()) {
if (!ess.getSettings().isTeleportPassengerDismount()) {
throw new Exception(tl("passengerTeleportFail"));
}
teleportee.getBase().eject();
}
if (LocationUtil.isBlockUnsafeForUser(ess, teleportee, loc.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())) {
if (ess.getSettings().isTeleportSafetyEnabled()) {
if (ess.getSettings().isForceDisableTeleportSafety()) {
PaperLib.teleportAsync(teleportee.getBase(), loc, cause);
} else {
PaperLib.teleportAsync(teleportee.getBase(), LocationUtil.getSafeDestination(ess, teleportee, loc), cause);
}
} else {
throw new Exception(tl("unsafeTeleportDestination", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
}
} else {
if (ess.getSettings().isForceDisableTeleportSafety()) {
PaperLib.teleportAsync(teleportee.getBase(), loc, cause);
} else {
if (ess.getSettings().isTeleportToCenterLocation()) {
loc = LocationUtil.getRoundedDestination(loc);
}
PaperLib.teleportAsync(teleportee.getBase(), loc, cause);
}
}
}
//The teleportPlayer function is used when you want to normally teleportPlayer someone to a location or player.
//This method is nolonger used internally and will be removed.
@Deprecated
@Override
public void teleport(final Location loc, final Trade chargeFor) throws Exception {
teleport(loc, chargeFor, TeleportCause.PLUGIN);
}
@Override
@Deprecated
public void teleport(final Location loc, final Trade chargeFor, final TeleportCause cause) throws Exception {
teleport(teleportOwner, new LocationTarget(loc), chargeFor, cause);
}
//This is used when teleporting to a player
@Override
@Deprecated
public void teleport(final Player entity, final Trade chargeFor, final TeleportCause cause) throws Exception {
final ITarget target = new PlayerTarget(entity);
teleportOwner.sendMessage(tl("teleportToPlayer", entity.getDisplayName()));
teleport(teleportOwner, target, chargeFor, cause);
}
//This is used when teleporting to stored location
@Override
@Deprecated
public void teleportPlayer(final IUser teleportee, final Location loc, final Trade chargeFor, final TeleportCause cause) throws Exception {
teleport(teleportee, new LocationTarget(loc), chargeFor, cause);
}
//This is used on /tphere
@Override
@Deprecated
public void teleportPlayer(final IUser teleportee, final Player entity, final Trade chargeFor, final TeleportCause cause) throws Exception {
final ITarget target = new PlayerTarget(entity);
teleport(teleportee, target, chargeFor, cause);
teleportee.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ()));
teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ()));
}
@Deprecated
private void teleport(final IUser teleportee, final ITarget target, final Trade chargeFor, final TeleportCause cause) throws Exception {
double delay = ess.getSettings().getTeleportDelay();
final TeleportWarmupEvent event = new TeleportWarmupEvent(teleportee, cause, target, delay);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
delay = event.getDelay();
Trade cashCharge = chargeFor;
if (chargeFor != null) {
chargeFor.isAffordableFor(teleportOwner);
//This code is to make sure that commandcosts are checked in the initial world, and not in the resulting world.
if (!chargeFor.getCommandCost(teleportOwner).equals(BigDecimal.ZERO)) {
//By converting a command cost to a regular cost, the command cost permission isn't checked when executing the charge after teleport.
cashCharge = new Trade(chargeFor.getCommandCost(teleportOwner), ess);
}
}
cooldown(true);
if (delay <= 0 || teleportOwner.isAuthorized("essentials.teleport.timer.bypass") || teleportee.isAuthorized("essentials.teleport.timer.bypass")) {
cooldown(false);
now(teleportee, target, cause);
if (cashCharge != null) {
cashCharge.charge(teleportOwner);
}
return;
}
cancel(false);
warnUser(teleportee, delay);
initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false);
}
@Deprecated
private void teleportOther(final IUser teleporter, final IUser teleportee, final ITarget target, final Trade chargeFor, final TeleportCause cause) throws Exception {
double delay = ess.getSettings().getTeleportDelay();
final TeleportWarmupEvent event = new TeleportWarmupEvent(teleporter, teleportee, cause, target, delay);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
delay = event.getDelay();
Trade cashCharge = chargeFor;
if (teleporter != null && chargeFor != null) {
chargeFor.isAffordableFor(teleporter);
//This code is to make sure that commandcosts are checked in the initial world, and not in the resulting world.
if (!chargeFor.getCommandCost(teleporter).equals(BigDecimal.ZERO)) {
//By converting a command cost to a regular cost, the command cost permission isn't checked when executing the charge after teleport.
cashCharge = new Trade(chargeFor.getCommandCost(teleporter), ess);
}
}
cooldown(true);
if (delay <= 0 || teleporter == null
|| teleporter.isAuthorized("essentials.teleport.timer.bypass")
|| teleportOwner.isAuthorized("essentials.teleport.timer.bypass")
|| teleportee.isAuthorized("essentials.teleport.timer.bypass")) {
cooldown(false);
now(teleportee, target, cause);
if (teleporter != null && cashCharge != null) {
cashCharge.charge(teleporter);
}
return;
}
cancel(false);
warnUser(teleportee, delay);
initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false);
}
//The respawn function is a wrapper used to handle tp fallback, on /jail and /home
@Override
@Deprecated
public void respawn(final Trade chargeFor, final TeleportCause cause) throws Exception {
double delay = ess.getSettings().getTeleportDelay();
final TeleportWarmupEvent event = new TeleportWarmupEvent(teleportOwner, cause, null, delay);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
delay = event.getDelay();
if (chargeFor != null) {
chargeFor.isAffordableFor(teleportOwner);
}
cooldown(true);
if (delay <= 0 || teleportOwner.isAuthorized("essentials.teleport.timer.bypass")) {
cooldown(false);
respawnNow(teleportOwner, cause);
if (chargeFor != null) {
chargeFor.charge(teleportOwner);
}
return;
}
cancel(false);
warnUser(teleportOwner, delay);
initTimer((long) (delay * 1000.0), teleportOwner, null, chargeFor, cause, true);
}
@Deprecated
void respawnNow(final IUser teleportee, final TeleportCause cause) throws Exception {
final Player player = teleportee.getBase();
final Location bed = player.getBedSpawnLocation();
if (bed != null) {
now(teleportee, new LocationTarget(bed), cause);
} else {
if (ess.getSettings().isDebug()) {
ess.getLogger().info("Could not find bed spawn, forcing respawn event.");
}
final PlayerRespawnEvent pre = new PlayerRespawnEvent(player, player.getWorld().getSpawnLocation(), false);
ess.getServer().getPluginManager().callEvent(pre);
now(teleportee, new LocationTarget(pre.getRespawnLocation()), cause);
}
}
//The warp function is a wrapper used to teleportPlayer a player to a /warp
@Override
@Deprecated
public void warp(final IUser teleportee, String warp, final Trade chargeFor, final TeleportCause cause) throws Exception {
final UserWarpEvent event = new UserWarpEvent(teleportee, warp, chargeFor);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
warp = event.getWarp();
final Location loc = ess.getWarps().getWarp(warp);
teleportee.sendMessage(tl("warpingTo", warp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
if (!teleportee.equals(teleportOwner)) {
teleportOwner.sendMessage(tl("warpingTo", warp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
}
teleport(teleportee, new LocationTarget(loc), chargeFor, cause);
}
//The back function is a wrapper used to teleportPlayer a player /back to their previous location.
@Override
@Deprecated
public void back(final Trade chargeFor) throws Exception {
back(teleportOwner, chargeFor);
}
//This function is a wrapper over the other back function for cases where another player performs back for them
@Override
@Deprecated
public void back(final IUser teleporter, final Trade chargeFor) throws Exception {
tpType = TeleportType.BACK;
final Location loc = teleportOwner.getLastLocation();
teleportOwner.sendMessage(tl("backUsageMsg", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
teleportOther(teleporter, teleportOwner, new LocationTarget(loc), chargeFor, TeleportCause.COMMAND);
}
//This function is used to throw a user back after a jail sentence
@Override
@Deprecated
public void back() throws Exception {
now(teleportOwner, new LocationTarget(teleportOwner.getLastLocation()), TeleportCause.COMMAND);
}
@Deprecated
public void setTpType(final TeleportType tpType) {
this.tpType = tpType;
}
//If we need to cancelTimer a pending teleportPlayer call this method
@Deprecated
private void cancel(final boolean notifyUser) {
if (timedTeleport != null) {
timedTeleport.cancelTimer(notifyUser);
timedTeleport = null;
}
}
@Deprecated
private void initTimer(final long delay, final IUser teleportUser, final ITarget target, final Trade chargeFor, final TeleportCause cause, final boolean respawn) {
timedTeleport = new TimedTeleport(teleportOwner, ess, this, delay, teleportUser, target, chargeFor, cause, respawn);
}
public enum TeleportType {
TPA,
BACK,
NORMAL
}
}
@@ -1,140 +0,0 @@
package com.earth2me.essentials;
import net.ess3.api.IEssentials;
import net.ess3.api.IUser;
import org.bukkit.Location;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import java.util.UUID;
import static com.earth2me.essentials.I18n.tl;
@Deprecated
public class TimedTeleport implements Runnable {
private static final double MOVE_CONSTANT = 0.3;
private final IUser teleportOwner;
private final IEssentials ess;
private final Teleport teleport;
private final UUID timer_teleportee;
private final long timer_started; // time this task was initiated
private final long timer_delay; // how long to delay the teleportPlayer
// note that I initially stored a clone of the location for reference, but...
// when comparing locations, I got incorrect mismatches (rounding errors, looked like)
// so, the X/Y/Z values are stored instead and rounded off
private final long timer_initX;
private final long timer_initY;
private final long timer_initZ;
private final ITarget timer_teleportTarget;
private final boolean timer_respawn;
private final boolean timer_canMove;
private final Trade timer_chargeFor;
private final TeleportCause timer_cause;
private int timer_task;
private double timer_health;
TimedTeleport(final IUser user, final IEssentials ess, final Teleport teleport, final long delay, final IUser teleportUser, final ITarget target, final Trade chargeFor, final TeleportCause cause, final boolean respawn) {
this.teleportOwner = user;
this.ess = ess;
this.teleport = teleport;
this.timer_started = System.currentTimeMillis();
this.timer_delay = delay;
this.timer_health = teleportUser.getBase().getHealth();
this.timer_initX = Math.round(teleportUser.getBase().getLocation().getX() * MOVE_CONSTANT);
this.timer_initY = Math.round(teleportUser.getBase().getLocation().getY() * MOVE_CONSTANT);
this.timer_initZ = Math.round(teleportUser.getBase().getLocation().getZ() * MOVE_CONSTANT);
this.timer_teleportee = teleportUser.getBase().getUniqueId();
this.timer_teleportTarget = target;
this.timer_chargeFor = chargeFor;
this.timer_cause = cause;
this.timer_respawn = respawn;
this.timer_canMove = user.isAuthorized("essentials.teleport.timer.move");
timer_task = ess.runTaskTimerAsynchronously(this, 20, 20).getTaskId();
}
@Override
public void run() {
if (teleportOwner == null || !teleportOwner.getBase().isOnline() || teleportOwner.getBase().getLocation() == null) {
cancelTimer(false);
return;
}
final IUser teleportUser = ess.getUser(this.timer_teleportee);
if (teleportUser == null || !teleportUser.getBase().isOnline()) {
cancelTimer(false);
return;
}
final Location currLocation = teleportUser.getBase().getLocation();
if (currLocation == null) {
cancelTimer(false);
return;
}
if (!timer_canMove && (Math.round(currLocation.getX() * MOVE_CONSTANT) != timer_initX || Math.round(currLocation.getY() * MOVE_CONSTANT) != timer_initY || Math.round(currLocation.getZ() * MOVE_CONSTANT) != timer_initZ || teleportUser.getBase().getHealth() < timer_health)) {
// user moved, cancelTimer teleportPlayer
cancelTimer(true);
return;
}
class DelayedTeleportTask implements Runnable {
@Override
public void run() {
timer_health = teleportUser.getBase().getHealth(); // in case user healed, then later gets injured
final long now = System.currentTimeMillis();
if (now > timer_started + timer_delay) {
try {
teleport.cooldown(false);
} catch (final Exception ex) {
teleportOwner.sendMessage(tl("cooldownWithMessage", ex.getMessage()));
if (teleportOwner != teleportUser) {
teleportUser.sendMessage(tl("cooldownWithMessage", ex.getMessage()));
}
}
try {
cancelTimer(false);
teleportUser.sendMessage(tl("teleportationCommencing"));
if (timer_chargeFor != null) {
timer_chargeFor.isAffordableFor(teleportOwner);
}
if (timer_respawn) {
teleport.respawnNow(teleportUser, timer_cause);
} else {
teleport.now(teleportUser, timer_teleportTarget, timer_cause);
}
if (timer_chargeFor != null) {
timer_chargeFor.charge(teleportOwner);
}
} catch (final Exception ex) {
ess.showError(teleportOwner.getSource(), ex, "\\ teleport");
}
}
}
}
ess.scheduleSyncDelayedTask(new DelayedTeleportTask());
}
//If we need to cancelTimer a pending teleportPlayer call this method
void cancelTimer(final boolean notifyUser) {
if (timer_task == -1) {
return;
}
try {
ess.getServer().getScheduler().cancelTask(timer_task);
if (notifyUser) {
teleportOwner.sendMessage(tl("pendingTeleportCancelled"));
if (timer_teleportee != null && !timer_teleportee.equals(teleportOwner.getBase().getUniqueId())) {
ess.getUser(timer_teleportee).sendMessage(tl("pendingTeleportCancelled"));
}
}
} finally {
timer_task = -1;
}
}
}
@@ -1,14 +1,14 @@
package com.earth2me.essentials;
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.craftbukkit.SetExpFix;
import com.earth2me.essentials.adventure.AdventureUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.api.IEssentials;
import net.ess3.api.IUser;
import net.ess3.api.MaxMoneyException;
import org.bukkit.Location;
import org.bukkit.entity.Item;
import org.bukkit.inventory.ItemStack;
import java.io.File;
@@ -16,15 +16,15 @@ import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
public class Trade {
private static FileWriter fw = null;
private final transient String command;
@@ -87,7 +87,14 @@ public class Trade {
sb.append(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date()));
sb.append("\",\"");
if (sender != null) {
sb.append(sender);
String senderIdentifier = sender;
if (ess.getSettings().isEcoLogUUIDEnabled()) {
final UUID uuid = ess.getUsers().getNameCache().get(sender);
if (uuid != null) {
senderIdentifier = uuid.toString();
}
}
sb.append(senderIdentifier);
}
sb.append("\",");
if (charge == null) {
@@ -113,7 +120,14 @@ public class Trade {
}
sb.append(",\"");
if (receiver != null) {
sb.append(receiver);
String receiverIdentifier = receiver;
if (ess.getSettings().isEcoLogUUIDEnabled()) {
final UUID uuid = ess.getUsers().getNameCache().get(receiver);
if (uuid != null) {
receiverIdentifier = uuid.toString();
}
}
sb.append(receiverIdentifier);
}
sb.append("\",");
if (pay == null) {
@@ -146,7 +160,7 @@ public class Trade {
sb.append(loc.getBlockY()).append(",");
sb.append(loc.getBlockZ()).append(",");
}
if (endBalance == null) {
sb.append(",");
} else {
@@ -193,23 +207,23 @@ public class Trade {
}
if (getMoney() != null && getMoney().signum() > 0 && !user.canAfford(getMoney())) {
future.completeExceptionally(new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(getMoney(), ess))));
future.completeExceptionally(new ChargeException("notEnoughMoney", AdventureUtil.parsed(NumberUtil.displayCurrency(getMoney(), ess))));
return;
}
if (getItemStack() != null && !user.getBase().getInventory().containsAtLeast(itemStack, itemStack.getAmount())) {
future.completeExceptionally(new ChargeException(tl("missingItems", getItemStack().getAmount(), ess.getItemDb().name(getItemStack()))));
if (getItemStack() != null && !Inventories.containsAtLeast(user.getBase(), itemStack, itemStack.getAmount())) {
future.completeExceptionally(new ChargeException("missingItems", getItemStack().getAmount(), ess.getItemDb().name(getItemStack())));
return;
}
final BigDecimal money;
if (command != null && !command.isEmpty() && (money = getCommandCost(user)).signum() > 0 && !user.canAfford(money)) {
future.completeExceptionally(new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(money, ess))));
future.completeExceptionally(new ChargeException("notEnoughMoney", AdventureUtil.parsed(NumberUtil.displayCurrency(money, ess))));
return;
}
if (exp != null && exp > 0 && SetExpFix.getTotalExperience(user.getBase()) < exp) {
future.completeExceptionally(new ChargeException(tl("notEnoughExperience")));
future.completeExceptionally(new ChargeException("notEnoughExperience"));
}
}
@@ -225,52 +239,33 @@ public class Trade {
user.giveMoney(getMoney());
}
if (getItemStack() != null) {
// This stores the would be overflow
final Map<Integer, ItemStack> overFlow = InventoryWorkaround.addAllItems(user.getBase().getInventory(), getItemStack());
if (type == OverflowType.ABORT && !Inventories.hasSpace(user.getBase(), 0, false, getItemStack())) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "abort paying " + user.getName() + " itemstack " + getItemStack().toString() + " due to lack of inventory space ");
}
return Collections.singletonMap(0, getItemStack());
}
if (overFlow != null) {
switch (type) {
case ABORT:
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "abort paying " + user.getName() + " itemstack " + getItemStack().toString() + " due to lack of inventory space ");
final Map<Integer, ItemStack> leftover = Inventories.addItem(user.getBase(), getItemStack());
user.getBase().updateInventory();
if (!leftover.isEmpty()) {
if (type == OverflowType.RETURN) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "paying " + user.getName() + " partial itemstack " + getItemStack().toString() + " with overflow " + leftover.get(0).toString());
}
return leftover;
} else {
for (final ItemStack itemStack : leftover.values()) {
int spillAmount = itemStack.getAmount();
while (spillAmount > 0) {
itemStack.setAmount(Math.min(spillAmount, itemStack.getMaxStackSize()));
user.getBase().getWorld().dropItemNaturally(user.getBase().getLocation(), itemStack);
spillAmount -= itemStack.getAmount();
}
return overFlow;
case RETURN:
// Pay the user the items, and return overflow
final Map<Integer, ItemStack> returnStack = InventoryWorkaround.addItems(user.getBase().getInventory(), getItemStack());
user.getBase().updateInventory();
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "paying " + user.getName() + " partial itemstack " + getItemStack().toString() + " with overflow " + returnStack.get(0).toString());
}
return returnStack;
case DROP:
// Pay the users the items directly, and drop the rest, will always return no overflow.
final Map<Integer, ItemStack> leftOver = InventoryWorkaround.addItems(user.getBase().getInventory(), getItemStack());
final Location loc = user.getBase().getLocation();
for (final ItemStack loStack : leftOver.values()) {
final int maxStackSize = loStack.getType().getMaxStackSize();
final int stacks = loStack.getAmount() / maxStackSize;
final int leftover = loStack.getAmount() % maxStackSize;
final Item[] itemStacks = new Item[stacks + (leftover > 0 ? 1 : 0)];
for (int i = 0; i < stacks; i++) {
final ItemStack stack = loStack.clone();
stack.setAmount(maxStackSize);
itemStacks[i] = loc.getWorld().dropItem(loc, stack);
}
if (leftover > 0) {
final ItemStack stack = loStack.clone();
stack.setAmount(leftover);
itemStacks[stacks] = loc.getWorld().dropItem(loc, stack);
}
}
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "paying " + user.getName() + " partial itemstack " + getItemStack().toString() + " and dropping overflow " + leftOver.get(0).toString());
}
break;
}
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "paying " + user.getName() + " partial itemstack " + getItemStack().toString() + " and dropping overflow " + leftover.get(0).toString());
}
}
} else if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "paying " + user.getName() + " itemstack " + getItemStack().toString());
@@ -306,7 +301,7 @@ public class Trade {
ess.getLogger().log(Level.INFO, "charging user " + user.getName() + " money " + getMoney().toPlainString());
}
if (!user.canAfford(getMoney()) && getMoney().signum() > 0) {
future.completeExceptionally(new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(getMoney(), ess))));
future.completeExceptionally(new ChargeException("notEnoughMoney", AdventureUtil.parsed(NumberUtil.displayCurrency(getMoney(), ess))));
return;
}
user.takeMoney(getMoney());
@@ -315,17 +310,17 @@ public class Trade {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "charging user " + user.getName() + " itemstack " + getItemStack().toString());
}
if (!user.getBase().getInventory().containsAtLeast(getItemStack(), getItemStack().getAmount())) {
future.completeExceptionally(new ChargeException(tl("missingItems", getItemStack().getAmount(), getItemStack().getType().toString().toLowerCase(Locale.ENGLISH).replace("_", " "))));
if (!Inventories.containsAtLeast(user.getBase(), getItemStack(), getItemStack().getAmount())) {
future.completeExceptionally(new ChargeException("missingItems", getItemStack().getAmount(), getItemStack().getType().toString().toLowerCase(Locale.ENGLISH).replace("_", " ")));
return;
}
user.getBase().getInventory().removeItem(getItemStack());
Inventories.removeItemAmount(user.getBase(), getItemStack(), getItemStack().getAmount());
user.getBase().updateInventory();
}
if (command != null) {
final BigDecimal cost = getCommandCost(user);
if (!user.canAfford(cost) && cost.signum() > 0) {
future.completeExceptionally(new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(cost, ess))));
future.completeExceptionally(new ChargeException("notEnoughMoney", AdventureUtil.parsed(NumberUtil.displayCurrency(cost, ess))));
return;
}
user.takeMoney(cost);
@@ -336,7 +331,7 @@ public class Trade {
}
final int experience = SetExpFix.getTotalExperience(user.getBase());
if (experience < getExperience() && getExperience() > 0) {
future.completeExceptionally(new ChargeException(tl("notEnoughExperience")));
future.completeExceptionally(new ChargeException("notEnoughExperience"));
return;
}
SetExpFix.setTotalExperience(user.getBase(), experience - getExperience());
@@ -1,155 +1,19 @@
package com.earth2me.essentials;
import com.google.common.io.Files;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.regex.Pattern;
@Deprecated
public class UUIDMap {
private static final ScheduledExecutorService writeScheduler = Executors.newScheduledThreadPool(1);
private static boolean pendingWrite;
private static boolean loading = false;
private final transient net.ess3.api.IEssentials ess;
private final File userList;
private final transient Pattern splitPattern = Pattern.compile(",");
private final Runnable writeTaskRunnable;
public UUIDMap(final net.ess3.api.IEssentials ess) {
this.ess = ess;
userList = new File(ess.getDataFolder(), "usermap.csv");
pendingWrite = false;
writeTaskRunnable = () -> {
if (pendingWrite) {
try {
new WriteRunner(ess.getDataFolder(), userList, ess.getUserMap().getNames()).run();
} catch (final Throwable t) { // bad code to prevent task from being suppressed
t.printStackTrace();
}
}
};
writeScheduler.scheduleWithFixedDelay(writeTaskRunnable, 5, 5, TimeUnit.SECONDS);
}
public void loadAllUsers(final ConcurrentSkipListMap<String, UUID> names, final ConcurrentSkipListMap<UUID, ArrayList<String>> history) {
try {
if (!userList.exists()) {
userList.createNewFile();
}
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "Reading usermap from disk");
}
if (loading) {
return;
}
names.clear();
history.clear();
loading = true;
try (final BufferedReader reader = new BufferedReader(new FileReader(userList))) {
while (true) {
final String line = reader.readLine();
if (line == null) {
break;
} else {
final String[] values = splitPattern.split(line);
if (values.length == 2) {
final String name = values[0];
final UUID uuid = UUID.fromString(values[1]);
names.put(name, uuid);
if (!history.containsKey(uuid)) {
final ArrayList<String> list = new ArrayList<>();
list.add(name);
history.put(uuid, list);
} else {
final ArrayList<String> list = history.get(uuid);
if (!list.contains(name)) {
list.add(name);
}
}
}
}
}
}
loading = false;
} catch (final IOException ex) {
Essentials.getWrappedLogger().log(Level.SEVERE, ex.getMessage(), ex);
}
public UUIDMap() {
}
public void writeUUIDMap() {
pendingWrite = true;
//no-op
}
public void forceWriteUUIDMap() {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "Forcing usermap write to disk");
}
pendingWrite = true;
writeTaskRunnable.run();
//no-op
}
public void shutdown() {
writeScheduler.submit(writeTaskRunnable);
writeScheduler.shutdown();
}
private static final class WriteRunner implements Runnable {
private final File location;
private final File endFile;
private final Map<String, UUID> names;
private WriteRunner(final File location, final File endFile, final Map<String, UUID> names) {
this.location = location;
this.endFile = endFile;
this.names = new HashMap<>(names);
}
@Override
public void run() {
pendingWrite = false;
if (loading || names.isEmpty()) {
return;
}
File configFile = null;
try {
configFile = File.createTempFile("usermap", ".tmp.csv", location);
final BufferedWriter bWriter = new BufferedWriter(new FileWriter(configFile));
for (final Map.Entry<String, UUID> entry : names.entrySet()) {
bWriter.write(entry.getKey() + "," + entry.getValue().toString());
bWriter.newLine();
}
bWriter.close();
Files.move(configFile, endFile);
} catch (final IOException ex) {
try {
if (configFile != null && configFile.exists()) {
Files.move(configFile, new File(endFile.getParentFile(), "usermap.bak.csv"));
}
} catch (final Exception ex2) {
Essentials.getWrappedLogger().log(Level.SEVERE, ex2.getMessage(), ex2);
}
Essentials.getWrappedLogger().log(Level.WARNING, ex.getMessage(), ex);
}
}
//no-op
}
}
@@ -1,6 +1,9 @@
package com.earth2me.essentials;
import com.earth2me.essentials.adventure.AdventureUtil;
import com.earth2me.essentials.adventure.ComponentHolder;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.economy.EconomyLayer;
import com.earth2me.essentials.economy.EconomyLayers;
import com.earth2me.essentials.messaging.IMessageRecipient;
@@ -14,10 +17,13 @@ import com.earth2me.essentials.utils.VersionUtil;
import com.google.common.collect.Lists;
import net.ess3.api.IEssentials;
import net.ess3.api.MaxMoneyException;
import net.ess3.api.TranslatableException;
import net.ess3.api.events.AfkStatusChangeEvent;
import net.ess3.api.events.JailStatusChangeEvent;
import net.ess3.api.events.MuteStatusChangeEvent;
import net.ess3.api.events.UserBalanceUpdateEvent;
import net.ess3.provider.PlayerLocaleProvider;
import net.essentialsx.api.v2.events.PreTransactionEvent;
import net.essentialsx.api.v2.events.TransactionEvent;
import net.essentialsx.api.v2.services.mail.MailSender;
import org.bukkit.Location;
@@ -27,7 +33,6 @@ import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
@@ -49,7 +54,8 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
import static com.earth2me.essentials.I18n.tlLocale;
public class User extends UserData implements Comparable<User>, IMessageRecipient, net.ess3.api.IUser {
private static final Statistic PLAY_ONE_TICK = EnumUtil.getStatistic("PLAY_ONE_MINUTE", "PLAY_ONE_TICK");
@@ -57,10 +63,11 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
// User modules
private final IMessageRecipient messageRecipient;
private transient final AsyncTeleport teleport;
private transient final Teleport legacyTeleport;
// User command confirmation strings
private final Map<User, BigDecimal> confirmingPayments = new WeakHashMap<>();
private String confirmingClearCommand;
private String lastHomeConfirmation;
// User teleport variables
private final transient LinkedHashMap<String, TpaRequest> teleportRequestQueue = new LinkedHashMap<>();
@@ -74,29 +81,32 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
private boolean recipeSee = false;
private boolean enderSee = false;
private boolean ignoreMsg = false;
private Boolean toggleShout;
private boolean freeze = false;
// User afk variables
private String afkMessage;
private long afkSince;
private transient Location afkPosition = null;
// Misc
// Timestamps
private transient long lastOnlineActivity;
private transient long lastThrottledAction;
private transient long lastActivity = System.currentTimeMillis();
private transient long teleportInvulnerabilityTimestamp = 0;
private String confirmingClearCommand;
private long lastNotifiedAboutMailsMs;
private String lastHomeConfirmation;
private long lastHomeConfirmationTimestamp;
private Boolean toggleShout;
// Misc
private transient final List<String> signCopy = Lists.newArrayList("", "", "", "");
private transient long lastVanishTime = System.currentTimeMillis();
private transient int flightTick = -1;
private String lastLocaleString;
private Locale playerLocale;
public User(final Player base, final IEssentials ess) {
super(base, ess);
teleport = new AsyncTeleport(this, ess);
legacyTeleport = new Teleport(this, ess);
if (isAfk()) {
afkPosition = this.getLocation();
}
@@ -106,7 +116,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
this.messageRecipient = new SimpleMessageRecipient(ess, this);
}
void update(final Player base) {
public void update(final Player base) {
setBase(base);
}
@@ -126,6 +136,10 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
@Override
public boolean isAuthorized(final String node) {
if (Essentials.TESTING) {
return false;
}
final boolean result = isAuthorizedCheck(node);
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "checking if " + base.getName() + " has " + node + " - " + result);
@@ -133,8 +147,25 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
return result;
}
@Override
public boolean isAuthorizedCached(final String node) {
if (Essentials.TESTING) {
return false;
}
final boolean result = isAuthorizedCachedCheck(node);
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "checking if " + base.getName() + " has " + node + " (cached) - " + result);
}
return result;
}
@Override
public boolean isPermissionSet(final String node) {
if (Essentials.TESTING) {
return false;
}
final boolean result = isPermSetCheck(node);
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "checking if " + base.getName() + " has " + node + " (set-explicit) - " + result);
@@ -151,7 +182,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
private boolean isAuthorizedCheck(final String node) {
if (base instanceof OfflinePlayer) {
if (base instanceof OfflinePlayerStub) {
return false;
}
@@ -168,8 +199,26 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
}
private boolean isAuthorizedCachedCheck(final String node) {
if (base instanceof OfflinePlayerStub) {
return false;
}
try {
return ess.getPermissionsHandler().hasPermissionCached(base, node);
} catch (final Exception ex) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.SEVERE, "Permission System Error: " + ess.getPermissionsHandler().getName() + " returned: " + ex.getMessage(), ex);
} else {
ess.getLogger().log(Level.SEVERE, "Permission System Error: " + ess.getPermissionsHandler().getName() + " returned: " + ex.getMessage());
}
return false;
}
}
private boolean isPermSetCheck(final String node) {
if (base instanceof OfflinePlayer) {
if (base instanceof OfflinePlayerStub) {
return false;
}
@@ -187,7 +236,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
private TriState isAuthorizedExactCheck(final String node) {
if (base instanceof OfflinePlayer) {
if (base instanceof OfflinePlayerStub) {
return TriState.UNSET;
}
@@ -214,7 +263,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
cooldownTime.add(Calendar.SECOND, (int) cooldown);
cooldownTime.add(Calendar.MILLISECOND, (int) ((cooldown * 1000.0) % 1000.0));
if (cooldownTime.after(now) && !isAuthorized("essentials.heal.cooldown.bypass")) {
throw new Exception(tl("timeBeforeHeal", DateUtil.formatDateDiff(cooldownTime.getTimeInMillis())));
throw new TranslatableException("timeBeforeHeal", DateUtil.formatDateDiff(cooldownTime.getTimeInMillis()));
}
}
setLastHealTimestamp(now.getTimeInMillis());
@@ -235,9 +284,9 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
return;
}
setMoney(getMoney().add(value), cause);
sendMessage(tl("addedToAccount", NumberUtil.displayCurrency(value, ess)));
sendTl("addedToAccount", AdventureUtil.parsed(NumberUtil.displayCurrency(value, ess)));
if (initiator != null) {
initiator.sendMessage(tl("addedToOthersAccount", NumberUtil.displayCurrency(value, ess), this.getDisplayName(), NumberUtil.displayCurrency(getMoney(), ess)));
initiator.sendTl("addedToOthersAccount", AdventureUtil.parsed(NumberUtil.displayCurrency(value, ess)), getDisplayName(), AdventureUtil.parsed(NumberUtil.displayCurrency(getMoney(), ess)));
}
}
@@ -246,20 +295,28 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
payUser(reciever, value, UserBalanceUpdateEvent.Cause.UNKNOWN);
}
public void payUser(final User reciever, final BigDecimal value, final UserBalanceUpdateEvent.Cause cause) throws Exception {
public void payUser(final User reciever, BigDecimal value, final UserBalanceUpdateEvent.Cause cause) throws Exception {
if (value.compareTo(BigDecimal.ZERO) < 1) {
throw new Exception(tl("payMustBePositive"));
throw new Exception(tlLocale(playerLocale, "payMustBePositive"));
}
if (canAfford(value)) {
// Call an event for pre-transaction
final PreTransactionEvent preTransactionEvent = new PreTransactionEvent(this.getSource(), reciever, value);
ess.getServer().getPluginManager().callEvent(preTransactionEvent);
if (preTransactionEvent.isCancelled()) {
return;
}
value = preTransactionEvent.getAmount();
setMoney(getMoney().subtract(value), cause);
reciever.setMoney(reciever.getMoney().add(value), cause);
sendMessage(tl("moneySentTo", NumberUtil.displayCurrency(value, ess), reciever.getDisplayName()));
reciever.sendMessage(tl("moneyRecievedFrom", NumberUtil.displayCurrency(value, ess), getDisplayName()));
sendTl("moneySentTo", AdventureUtil.parsed(NumberUtil.displayCurrency(value, ess)), reciever.getDisplayName());
reciever.sendTl("moneyRecievedFrom", AdventureUtil.parsed(NumberUtil.displayCurrency(value, ess)), getDisplayName());
final TransactionEvent transactionEvent = new TransactionEvent(this.getSource(), reciever, value);
ess.getServer().getPluginManager().callEvent(transactionEvent);
} else {
throw new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(value, ess)));
throw new ChargeException("notEnoughMoney", AdventureUtil.parsed(NumberUtil.displayCurrency(value, ess)));
}
}
@@ -282,9 +339,9 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
} catch (final MaxMoneyException ex) {
ess.getLogger().log(Level.WARNING, "Invalid call to takeMoney, total balance can't be more than the max-money limit.", ex);
}
sendMessage(tl("takenFromAccount", NumberUtil.displayCurrency(value, ess)));
sendTl("takenFromAccount", AdventureUtil.parsed(NumberUtil.displayCurrency(value, ess)));
if (initiator != null) {
initiator.sendMessage(tl("takenFromOthersAccount", NumberUtil.displayCurrency(value, ess), this.getDisplayName(), NumberUtil.displayCurrency(getMoney(), ess)));
initiator.sendTl("takenFromOthersAccount", AdventureUtil.parsed(NumberUtil.displayCurrency(value, ess)), getDisplayName(), AdventureUtil.parsed(NumberUtil.displayCurrency(getMoney(), ess)));
}
}
@@ -310,7 +367,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
private void _dispose() {
if (!base.isOnline()) {
this.base = new OfflinePlayer(getConfigUUID(), ess.getServer());
this.base = new OfflinePlayerStub(getConfigUUID(), ess.getServer());
}
cleanup();
}
@@ -324,8 +381,9 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
return true;
if (VersionUtil.PRE_FLATTENING) {
//noinspection deprecation
final int id = material.getId();
if (isAuthorized("essentials.itemspawn.item-" + id)) return true;
return isAuthorized("essentials.itemspawn.item-" + id);
}
return false;
@@ -362,12 +420,6 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
teleportRequestQueue.put(request.getName(), request);
}
@Override
@Deprecated
public boolean hasOutstandingTeleportRequest() {
return getNextTpaRequest(false, false, false) != null;
}
public Collection<String> getPendingTpaKeys() {
return teleportRequestQueue.keySet();
}
@@ -394,7 +446,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
teleportRequestQueue.remove(playerUsername);
if (inform) {
sendMessage(tl("requestTimedOutFrom", ess.getUser(request.getRequesterUuid()).getDisplayName()));
sendTl("requestTimedOutFrom", ess.getUser(request.getRequesterUuid()).getDisplayName());
}
return null;
}
@@ -428,7 +480,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
} else {
if (inform) {
sendMessage(tl("requestTimedOutFrom", ess.getUser(request.getRequesterUuid()).getDisplayName()));
sendTl("requestTimedOutFrom", ess.getUser(request.getRequesterUuid()).getDisplayName());
}
teleportRequestQueue.remove(key);
}
@@ -443,14 +495,15 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
/**
* Needed for backwards compatibility.
*/
public String getNick(final boolean longnick) {
public String getNick(@SuppressWarnings("unused") final boolean longNick) {
return getNick(true, true);
}
/**
* Needed for backwards compatibility.
*/
public String getNick(final boolean longnick, final boolean withPrefix, final boolean withSuffix) {
@SuppressWarnings("unused")
public String getNick(@SuppressWarnings("unused") final boolean longNick, final boolean withPrefix, final boolean withSuffix) {
return getNick(withPrefix, withSuffix);
}
@@ -464,7 +517,11 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
} else if (nick.equalsIgnoreCase(getName())) {
nickname = nick;
} else {
nickname = FormatUtil.replaceFormat(ess.getSettings().getNicknamePrefix()) + nick;
if (isAuthorized("essentials.nick.hideprefix")) {
nickname = nick;
} else {
nickname = FormatUtil.replaceFormat(ess.getSettings().getNicknamePrefix()) + nick;
}
suffix = "§r";
}
@@ -528,6 +585,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
@Override
public String getDisplayName() {
//noinspection ConstantConditions
return super.getBase().getDisplayName() == null || (ess.getSettings().hideDisplayNameInVanish() && isHidden()) ? super.getBase().getName() : super.getBase().getDisplayName();
}
@@ -545,15 +603,6 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
return teleport;
}
/**
* @deprecated This API is not asynchronous. Use {@link User#getAsyncTeleport()}
*/
@Override
@Deprecated
public Teleport getTeleport() {
return legacyTeleport;
}
public long getLastOnlineActivity() {
return lastOnlineActivity;
}
@@ -624,6 +673,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
}
@SuppressWarnings("deprecation")
@Override
public void setAfk(final boolean set) {
setAfk(set, AfkStatusChangeEvent.Cause.UNKNOWN);
@@ -637,7 +687,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
return;
}
this.getBase().setSleepingIgnored(this.isAuthorized("essentials.sleepingignored") || (set && ess.getSettings().sleepIgnoresAfkPlayers()));
this.getBase().setSleepingIgnored(this.isAuthorized("essentials.sleepingignored") || set && ess.getSettings().sleepIgnoresAfkPlayers());
if (set && !isAfk()) {
afkPosition = this.getLocation();
this.afkSince = System.currentTimeMillis();
@@ -674,6 +724,9 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
@Override
public boolean isHiddenFrom(Player player) {
if (getBase() instanceof OfflinePlayerStub || player instanceof OfflinePlayerStub) {
return true;
}
return !player.canSee(getBase());
}
@@ -701,7 +754,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
public boolean isHidden(final Player player) {
return hidden || !player.canSee(getBase());
return hidden || isHiddenFrom(player);
}
@Override
@@ -714,6 +767,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
//Returns true if status expired during this check
@SuppressWarnings("UnusedReturnValue")
public boolean checkJailTimeout(final long currentTime) {
if (getJailTimeout() > 0) {
@@ -731,7 +785,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
setJailTimeout(0);
setOnlineJailedTime(0);
setJailed(false);
sendMessage(tl("haveBeenReleased"));
sendTl("haveBeenReleased");
setJail(null);
if (ess.getSettings().getTeleportWhenFreePolicy() == ISettings.TeleportWhenFreePolicy.BACK) {
final CompletableFuture<Boolean> future = new CompletableFuture<>();
@@ -751,6 +805,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
//Returns true if status expired during this check
@SuppressWarnings("UnusedReturnValue")
public boolean checkMuteTimeout(final long currentTime) {
if (getMuteTimeout() > 0 && getMuteTimeout() < currentTime && isMuted()) {
final MuteStatusChangeEvent event = new MuteStatusChangeEvent(this, null, false, getMuteTimeout(), getMuteReason());
@@ -758,7 +813,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
if (!event.isCancelled()) {
setMuteTimeout(0);
sendMessage(tl("canTalkAgain"));
sendTl("canTalkAgain");
setMuted(false);
setMuteReason(null);
return true;
@@ -767,6 +822,11 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
return false;
}
@Override
public long getLastActivityTime() {
return this.lastActivity;
}
@Deprecated
public void updateActivity(final boolean broadcast) {
updateActivity(broadcast, AfkStatusChangeEvent.Cause.UNKNOWN);
@@ -777,15 +837,10 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
setAfk(false, cause);
if (broadcast && !isHidden() && !isAfk()) {
setDisplayNick();
final String msg = tl("userIsNotAway", getDisplayName());
final String selfmsg = tl("userIsNotAwaySelf", getDisplayName());
if (!msg.isEmpty() && ess.getSettings().broadcastAfkMessage()) {
// exclude user from receiving general AFK announcement in favor of personal message
ess.broadcastMessage(this, msg, u -> u == this);
}
if (!selfmsg.isEmpty()) {
this.sendMessage(selfmsg);
if (ess.getSettings().broadcastAfkMessage()) {
ess.broadcastTl(this, u -> u == this, "userIsNotAway", getDisplayName());
}
sendTl("userIsNotAwaySelf", getDisplayName());
}
}
lastActivity = System.currentTimeMillis();
@@ -806,47 +861,56 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
public void updateActivityOnChat(final boolean broadcast) {
if (ess.getSettings().cancelAfkOnChat()) {
//Chat happens async, make sure we have a sync context
ess.scheduleSyncDelayedTask(() -> {
updateActivity(broadcast, AfkStatusChangeEvent.Cause.CHAT);
});
ess.scheduleSyncDelayedTask(() -> updateActivity(broadcast, AfkStatusChangeEvent.Cause.CHAT));
}
}
public void checkActivity() {
// Graceful time before the first afk check call.
// Graceful time before the first afk check call.
if (System.currentTimeMillis() - lastActivity <= 10000) {
return;
}
final long autoafkkick = ess.getSettings().getAutoAfkKick();
if (autoafkkick > 0
&& lastActivity > 0 && (lastActivity + (autoafkkick * 1000)) < System.currentTimeMillis()
final long autoafktimeout = ess.getSettings().getAutoAfkTimeout();
// Checks if the player has been inactive for longer than the configured auto-afk-timeout time.
if (autoafktimeout > 0
&& lastActivity > 0 && (lastActivity + (autoafktimeout * 1000)) < System.currentTimeMillis()
&& !isAuthorized("essentials.kick.exempt")
&& !isAuthorized("essentials.afk.kickexempt")) {
final String kickReason = tl("autoAfkKickReason", autoafkkick / 60.0);
lastActivity = 0;
this.getBase().kickPlayer(kickReason);
final double kickTime = autoafktimeout / 60.0;
for (final User user : ess.getOnlineUsers()) {
if (user.isAuthorized("essentials.kick.notify")) {
user.sendMessage(tl("playerKicked", Console.DISPLAY_NAME, getName(), kickReason));
// If `afk-timeout-command` in config.yml is empty, use default Essentials kicking behaviour instead of executing a command.
if (ess.getSettings().getAfkTimeoutCommands().isEmpty()) {
this.getBase().kickPlayer(ess.getAdventureFacet().miniToLegacy(playerTl("autoAfkKickReason", kickTime)));
for (final User user : ess.getOnlineUsers()) {
if (user.isAuthorized("essentials.kick.notify")) {
user.sendTl("playerKicked", Console.displayName(), getName(), user.playerTl("autoAfkKickReason", kickTime));
}
}
} else {
// If `afk-timeout-commands` in config.yml is populated, execute the command(s) instead of kicking the player.
for (final String command : ess.getSettings().getAfkTimeoutCommands()) {
if (command == null || command.isEmpty()){
continue;
}
// Replace placeholders in the command with actual values.
final String cmd = command.replace("{USERNAME}", getName()).replace("{KICKTIME}", String.valueOf(kickTime));
ess.getServer().dispatchCommand(ess.getServer().getConsoleSender(), cmd);
}
}
}
final long autoafk = ess.getSettings().getAutoAfk();
if (!isAfk() && autoafk > 0 && lastActivity + autoafk * 1000 < System.currentTimeMillis() && isAuthorized("essentials.afk.auto")) {
if (!isAfk() && autoafk > 0 && lastActivity + autoafk * 1000 < System.currentTimeMillis() && isAuthorizedCached("essentials.afk.auto")) {
setAfk(true, AfkStatusChangeEvent.Cause.ACTIVITY);
if (isAfk() && !isHidden()) {
setDisplayNick();
final String msg = tl("userIsAway", getDisplayName());
final String selfmsg = tl("userIsAwaySelf", getDisplayName());
if (!msg.isEmpty() && ess.getSettings().broadcastAfkMessage()) {
// exclude user from receiving general AFK announcement in favor of personal message
ess.broadcastMessage(this, msg, u -> u == this);
}
if (!selfmsg.isEmpty()) {
this.sendMessage(selfmsg);
if (ess.getSettings().broadcastAfkMessage()) {
ess.broadcastTl(this, u -> u == this, "userIsAway", getDisplayName());
}
sendTl("userIsAwaySelf", getDisplayName());
}
}
}
@@ -861,6 +925,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
// This enables the no-god-in-worlds functionality where the actual player god mode state is never modified in disabled worlds,
// but this method gets called every time the player takes damage. In the case that the world has god-mode disabled then this method
// will return false and the player will take damage, even though they are in god mode (isGodModeEnabledRaw()).
//noinspection ConstantConditions
if (!ess.getSettings().getNoGodWorlds().contains(this.getLocation().getWorld().getName())) {
return true;
}
@@ -902,6 +967,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
return ess.getPermissionsHandler().canBuild(base, getGroup());
}
@SuppressWarnings("deprecation")
@Override
@Deprecated
public long getTeleportRequestTime() {
@@ -970,6 +1036,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
if (set) {
for (final User user : ess.getOnlineUsers()) {
if (!user.isAuthorized("essentials.vanish.see")) {
//noinspection deprecation
user.getBase().hidePlayer(getBase());
}
}
@@ -985,6 +1052,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
} else {
for (final Player p : ess.getOnlinePlayers()) {
//noinspection deprecation
p.showPlayer(getBase());
}
setHidden(false);
@@ -1044,6 +1112,52 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
}
@Override
public void sendComponent(ComponentHolder component) {
ess.getAdventureFacet().send(base, component);
}
@Override
public ComponentHolder tlComponent(String tlKey, Object... args) {
final String translation = playerTl(tlKey, args);
return ess.getAdventureFacet().deserializeMiniMessage(translation);
}
@Override
public void sendTl(String tlKey, Object... args) {
final String translation = playerTl(tlKey, args);
if (translation.trim().isEmpty()) {
return;
}
sendComponent(ess.getAdventureFacet().deserializeMiniMessage(translation));
}
@Override
public String playerTl(String tlKey, Object... args) {
if (ess.getSettings().isPerPlayerLocale()) {
final PlayerLocaleProvider provider = ess.provider(PlayerLocaleProvider.class);
final Locale locale = base != null ? getPlayerLocale(provider.getLocale(base)) : playerLocale;
if (locale != null) {
return tlLocale(locale, tlKey, args);
}
}
return tlLiteral(tlKey, args);
}
@Override
public String tlSender(String tlKey, Object... args) {
return playerTl(tlKey, args);
}
public Locale getPlayerLocale(final String locale) {
if (locale == null || locale.equals(lastLocaleString)) {
return playerLocale;
}
lastLocaleString = locale;
return playerLocale = I18n.getLocale(locale);
}
@Override
public int compareTo(final User other) {
return FormatUtil.stripFormat(getDisplayName()).compareToIgnoreCase(FormatUtil.stripFormat(other.getDisplayName()));
@@ -1065,7 +1179,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
@Override
public CommandSource getSource() {
return new CommandSource(getBase());
return new CommandSource(ess, getBase());
}
@Override
@@ -1137,12 +1251,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
* Returns the {@link ItemStack} in the main hand or off-hand. If the main hand is empty then the offhand item is returned - also nullable.
*/
public ItemStack getItemInHand() {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_9_R01)) {
return getBase().getInventory().getItemInHand();
} else {
final PlayerInventory inventory = getBase().getInventory();
return inventory.getItemInMainHand() != null ? inventory.getItemInMainHand() : inventory.getItemInOffHand();
}
return Inventories.getItemInHand(getBase());
}
@Override
@@ -1155,6 +1264,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
ess.getMail().sendMail(this, sender, message, expireAt);
}
@SuppressWarnings("deprecation")
@Override
@Deprecated
public void addMail(String mail) {
@@ -1166,7 +1276,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
if (unread != 0) {
final int notifyPlayerOfMailCooldown = ess.getSettings().getNotifyPlayerOfMailCooldown() * 1000;
if (System.currentTimeMillis() - lastNotifiedAboutMailsMs >= notifyPlayerOfMailCooldown) {
sendMessage(tl("youHaveNewMail", unread));
sendTl("youHaveNewMail", unread);
lastNotifiedAboutMailsMs = System.currentTimeMillis();
}
}
@@ -1192,6 +1302,16 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
return signCopy;
}
@Override
public boolean isFreeze() {
return freeze;
}
@Override
public void setFreeze(boolean freeze) {
this.freeze = freeze;
}
public boolean isBaltopExempt() {
if (getBase().isOnline()) {
final boolean exempt = isAuthorized("essentials.balancetop.exclude");
@@ -1229,4 +1349,12 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
return toggleShout == null ? toggleShout = ess.getSettings().isShoutDefault() : toggleShout;
}
public int getFlightTick() {
return flightTick;
}
public void setFlightTick(int flightTick) {
this.flightTick = flightTick;
}
}
@@ -5,18 +5,19 @@ import com.earth2me.essentials.config.EssentialsUserConfiguration;
import com.earth2me.essentials.config.entities.CommandCooldown;
import com.earth2me.essentials.config.entities.LazyLocation;
import com.earth2me.essentials.config.holders.UserConfigHolder;
import com.earth2me.essentials.userstorage.ModernUserMap;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.StringUtil;
import com.google.common.base.Charsets;
import net.ess3.api.IEssentials;
import net.ess3.api.MaxMoneyException;
import net.ess3.api.TranslatableException;
import net.essentialsx.api.v2.services.mail.MailMessage;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.spongepowered.configurate.serialize.SerializationException;
import java.io.File;
import java.math.BigDecimal;
@@ -31,31 +32,24 @@ import java.util.UUID;
import java.util.logging.Level;
import java.util.regex.Pattern;
import static com.earth2me.essentials.I18n.tl;
public abstract class UserData extends PlayerExtension implements IConf {
protected final transient IEssentials ess;
protected final transient Essentials ess;
private final EssentialsUserConfiguration config;
private UserConfigHolder holder;
private BigDecimal money;
protected UserData(final Player base, final IEssentials ess) {
super(base);
this.ess = ess;
this.ess = (Essentials) ess;
final File folder = new File(ess.getDataFolder(), "userdata");
if (!folder.exists()) {
folder.mkdirs();
if (!folder.exists() && !folder.mkdirs()) {
throw new RuntimeException("Unable to create userdata folder!");
}
String filename;
try {
filename = base.getUniqueId().toString();
} catch (final Throwable ex) {
ess.getLogger().warning("Falling back to old username system for " + base.getName());
filename = base.getName();
}
config = new EssentialsUserConfiguration(base.getName(), base.getUniqueId(), new File(folder, filename + ".yml"));
config = new EssentialsUserConfiguration(base.getName(), base.getUniqueId(), new File(folder, base.getUniqueId() + ".yml"));
config.setSaveHook(() -> {
config.setRootHolder(UserConfigHolder.class, holder);
});
reloadConfig();
if (config.getUsername() == null) {
@@ -65,18 +59,22 @@ public abstract class UserData extends PlayerExtension implements IConf {
public final void reset() {
config.blockingSave();
config.getFile().delete();
if (!config.getFile().delete()) {
ess.getLogger().warning("Unable to delete data file for " + config.getFile().getName());
}
if (config.getUsername() != null) {
ess.getUserMap().removeUser(config.getUsername());
final ModernUserMap users = (ModernUserMap) ess.getUsers();
users.invalidate(config.getUuid());
if (isNPC()) {
final String uuid = UUID.nameUUIDFromBytes(("NPC:" + StringUtil.safeString(config.getUsername())).getBytes(Charsets.UTF_8)).toString();
ess.getUserMap().removeUserUUID(uuid);
final String name = ess.getSettings().isSafeUsermap() ? StringUtil.safeString(config.getUsername()) : config.getUsername();
users.invalidate(UUID.nameUUIDFromBytes(("NPC:" + name).getBytes(Charsets.UTF_8)));
}
}
}
public final void cleanup() {
config.blockingSave();
ess.getUsers().removeCache(getConfigUUID());
}
@Override
@@ -84,18 +82,10 @@ public abstract class UserData extends PlayerExtension implements IConf {
config.load();
try {
holder = config.getRootNode().get(UserConfigHolder.class);
} catch (SerializationException e) {
} catch (Throwable e) {
ess.getLogger().log(Level.SEVERE, "Error while reading user config: " + config.getFile().getName(), e);
throw new RuntimeException(e);
}
config.setSaveHook(() -> {
try {
config.getRootNode().set(UserConfigHolder.class, holder);
} catch (SerializationException e) {
ess.getLogger().log(Level.SEVERE, "Error while saving user config: " + config.getFile().getName(), e);
throw new RuntimeException(e);
}
});
money = _getMoney();
}
@@ -204,7 +194,17 @@ public abstract class UserData extends PlayerExtension implements IConf {
holder.homes().remove(search);
config.save();
} else {
throw new Exception(tl("invalidHome", search));
throw new TranslatableException("invalidHome", search);
}
}
public void renameHome(final String name, final String newName) throws Exception {
final LazyLocation location = holder.homes().remove(name);
if (location != null) {
holder.homes().put(StringUtil.safeString(newName), location);
config.save();
} else {
throw new TranslatableException("invalidHome", name);
}
}
@@ -272,6 +272,10 @@ public abstract class UserData extends PlayerExtension implements IConf {
return !holder.powertools().isEmpty();
}
public Map<String, List<String>> getAllPowertools() {
return holder.powertools();
}
public Location getLastLocation() {
final LazyLocation lastLocation = holder.lastLocation();
return lastLocation != null ? lastLocation.location() : null;
@@ -349,7 +353,17 @@ public abstract class UserData extends PlayerExtension implements IConf {
}
public int getMailAmount() {
return holder.mail() == null ? 0 : holder.mail().size();
if (holder.mail() == null) {
return 0;
}
int amount = 0;
for (MailMessage element : holder.mail()) {
if (!element.isExpired()) {
amount++;
}
}
return amount;
}
public int getUnreadMailAmount() {
@@ -359,7 +373,7 @@ public abstract class UserData extends PlayerExtension implements IConf {
int unread = 0;
for (MailMessage element : holder.mail()) {
if (!element.isRead()) {
if (!element.isRead() && !element.isExpired()) {
unread++;
}
}
@@ -455,6 +469,15 @@ public abstract class UserData extends PlayerExtension implements IConf {
config.save();
}
public boolean isFlyModeEnabled() {
return holder.flyMode();
}
public void setFlyModeEnabled(final boolean set) {
holder.flyMode(set);
config.save();
}
public boolean getMuted() {
return holder.muted();
}
@@ -589,9 +612,13 @@ public abstract class UserData extends PlayerExtension implements IConf {
}
public void setLastAccountName(final String lastAccountName) {
if (getLastAccountName() != null && !getLastAccountName().equals(lastAccountName)) {
final List<String> usernames = holder.pastUsernames();
usernames.add(0, getLastAccountName());
holder.pastUsernames(usernames);
}
holder.lastAccountName(lastAccountName);
config.save();
ess.getUserMap().trackUUID(getConfigUUID(), lastAccountName, true);
}
public boolean arePowerToolsEnabled() {
@@ -722,6 +749,17 @@ public abstract class UserData extends PlayerExtension implements IConf {
config.save();
}
public List<String> getPastUsernames() {
return holder.pastUsernames();
}
public void addPastUsername(String username) {
final List<String> usernames = holder.pastUsernames();
usernames.add(0, username);
holder.pastUsernames(usernames);
config.save();
}
public boolean isShouting() {
if (holder.shouting() == null) {
holder.shouting(ess.getSettings().isShoutDefault());
@@ -1,383 +1,70 @@
package com.earth2me.essentials;
import com.earth2me.essentials.api.UserDoesNotExistException;
import com.earth2me.essentials.utils.StringUtil;
import com.google.common.base.Charsets;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.UncheckedExecutionException;
import net.ess3.api.IEssentials;
import net.ess3.api.MaxMoneyException;
import com.earth2me.essentials.userstorage.ModernUserMap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.regex.Pattern;
public class UserMap extends CacheLoader<String, User> implements IConf {
private static boolean legacy = false;
private static Method getLegacy;
private final transient IEssentials ess;
private final transient ConcurrentSkipListSet<UUID> keys = new ConcurrentSkipListSet<>();
private final transient ConcurrentSkipListMap<String, UUID> names = new ConcurrentSkipListMap<>();
private final transient ConcurrentSkipListMap<UUID, ArrayList<String>> history = new ConcurrentSkipListMap<>();
private final UUIDMap uuidMap;
private final transient Cache<String, User> users;
private final Pattern validUserPattern = Pattern.compile("^[a-zA-Z0-9_]{2,16}$");
@Deprecated
public class UserMap {
private final transient ModernUserMap userMap;
private final transient UUIDMap uuidMap;
private static final String WARN_UUID_NOT_REPLACE = "Found UUID {0} for player {1}, but player already has a UUID ({2}). Not replacing UUID in usermap.";
public UserMap(final IEssentials ess) {
super();
this.ess = ess;
uuidMap = new UUIDMap(ess);
//RemovalListener<UUID, User> remListener = new UserMapRemovalListener();
//users = CacheBuilder.newBuilder().maximumSize(ess.getSettings().getMaxUserCacheCount()).softValues().removalListener(remListener).build(this);
final CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder();
final int maxCount = ess.getSettings().getMaxUserCacheCount();
try {
cacheBuilder.maximumSize(maxCount);
} catch (final NoSuchMethodError nsme) {
legacy = true;
legacyMaximumSize(cacheBuilder, maxCount);
}
cacheBuilder.softValues();
if (!legacy) {
users = cacheBuilder.build(this);
} else {
users = legacyBuild(cacheBuilder);
}
}
private void loadAllUsersAsync(final IEssentials ess) {
ess.runTaskAsynchronously(() -> {
synchronized (users) {
final File userdir = new File(ess.getDataFolder(), "userdata");
if (!userdir.exists()) {
return;
}
keys.clear();
users.invalidateAll();
for (final String string : userdir.list()) {
if (!string.endsWith(".yml")) {
continue;
}
final String name = string.substring(0, string.length() - 4);
try {
keys.add(UUID.fromString(name));
} catch (final IllegalArgumentException ex) {
//Ignore these users till they rejoin.
}
}
uuidMap.loadAllUsers(names, history);
}
});
}
public boolean userExists(final UUID uuid) {
return keys.contains(uuid);
public UserMap(final ModernUserMap userMap) {
this.userMap = userMap;
this.uuidMap = new UUIDMap();
}
public User getUser(final String name) {
final String sanitizedName = StringUtil.safeString(name);
try {
if (ess.getSettings().isDebug()) {
ess.getLogger().warning("Looking up username " + name + " (" + sanitizedName + ") ...");
}
if (names.containsKey(sanitizedName)) {
final UUID uuid = names.get(sanitizedName);
return getUser(uuid);
}
if (ess.getSettings().isDebug()) {
ess.getLogger().warning(name + "(" + sanitizedName + ") has no known usermap entry");
}
final File userFile = getUserFileFromString(sanitizedName);
if (userFile.exists()) {
ess.getLogger().info("Importing user " + name + " to usermap.");
final User user = new User(new OfflinePlayer(sanitizedName, ess.getServer()), ess);
trackUUID(user.getBase().getUniqueId(), user.getName(), true);
return user;
}
return null;
} catch (final UncheckedExecutionException ex) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.WARNING, ex, () -> String.format("Exception while getting user for %s (%s)", name, sanitizedName));
}
return null;
}
return userMap.getUser(name);
}
public User getUser(final UUID uuid) {
try {
if (!legacy) {
return ((LoadingCache<String, User>) users).get(uuid.toString());
} else {
return legacyCacheGet(uuid);
}
} catch (final ExecutionException | UncheckedExecutionException ex) {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.WARNING, ex, () -> "Exception while getting user for " + uuid);
}
return null;
}
return userMap.getUser(uuid);
}
public void trackUUID(final UUID uuid, final String name, final boolean replace) {
if (uuid != null) {
keys.add(uuid);
if (name != null && name.length() > 0) {
final String keyName = ess.getSettings().isSafeUsermap() ? StringUtil.safeString(name) : name;
if (!names.containsKey(keyName)) {
names.put(keyName, uuid);
uuidMap.writeUUIDMap();
} else if (!isUUIDMatch(uuid, keyName)) {
if (replace) {
ess.getLogger().info("Found new UUID for " + name + ". Replacing " + names.get(keyName).toString() + " with " + uuid.toString());
names.put(keyName, uuid);
uuidMap.writeUUIDMap();
} else {
ess.getLogger().log(Level.INFO, MessageFormat.format(WARN_UUID_NOT_REPLACE, uuid.toString(), name, names.get(keyName).toString()), new RuntimeException());
}
}
}
}
// no-op
}
public boolean isUUIDMatch(final UUID uuid, final String name) {
return names.containsKey(name) && names.get(name).equals(uuid);
}
@Override
public User load(final String stringUUID) throws Exception {
final UUID uuid = UUID.fromString(stringUUID);
Player player = ess.getServer().getPlayer(uuid);
if (player != null) {
final User user = new User(player, ess);
trackUUID(uuid, user.getName(), true);
return user;
}
final File userFile = getUserFileFromID(uuid);
if (userFile.exists()) {
player = new OfflinePlayer(uuid, ess.getServer());
final User user = new User(player, ess);
((OfflinePlayer) player).setName(user.getLastAccountName());
trackUUID(uuid, user.getName(), false);
return user;
}
throw new Exception("User not found!");
return userMap.load(UUID.fromString(stringUUID));
}
public User load(final org.bukkit.OfflinePlayer player) throws UserDoesNotExistException {
if (player == null) {
throw new IllegalArgumentException("Player cannot be null!");
}
final Player userPlayer;
if (player instanceof Player) {
if (ess.getSettings().isDebug()) {
ess.getLogger().info("Loading online OfflinePlayer into user map...");
}
final User user = new User((Player) player, ess);
trackUUID(player.getUniqueId(), player.getName(), true);
return user;
}
final File userFile = getUserFileFromID(player.getUniqueId());
if (ess.getSettings().isDebug()) {
ess.getLogger().info("Loading OfflinePlayer into user map. Has data: " + userFile.exists() + " for " + player);
}
final OfflinePlayer essPlayer = new OfflinePlayer(player.getUniqueId(), ess.getServer());
final User user = new User(essPlayer, ess);
if (userFile.exists()) {
essPlayer.setName(user.getLastAccountName());
userPlayer = (Player) player;
} else {
if (ess.getSettings().isDebug()) {
ess.getLogger().info("OfflinePlayer usermap load saving user data for " + player);
}
// this code makes me sad
user.startTransaction();
try {
user.setMoney(ess.getSettings().getStartingBalance());
} catch (MaxMoneyException e) {
// Shouldn't happen as it would be an illegal configuration state
throw new RuntimeException(e);
}
user.setLastAccountName(user.getName());
user.stopTransaction();
final OfflinePlayerStub essPlayer = new OfflinePlayerStub(player.getUniqueId(), Bukkit.getServer());
essPlayer.setName(player.getName());
userPlayer = essPlayer;
}
trackUUID(player.getUniqueId(), user.getName(), false);
final User user = userMap.getUser(userPlayer);
if (user == null) {
throw new UserDoesNotExistException("User not found");
}
return user;
}
@Override
public void reloadConfig() {
getUUIDMap().forceWriteUUIDMap();
loadAllUsersAsync(ess);
}
public void invalidateAll() {
users.invalidateAll();
}
public void removeUser(final String name) {
if (names == null) {
ess.getLogger().warning("Name collection is null, cannot remove user.");
return;
}
final UUID uuid = names.get(name);
if (uuid != null) {
keys.remove(uuid);
users.invalidate(uuid.toString());
}
names.remove(name);
names.remove(StringUtil.safeString(name));
}
public void removeUserUUID(final String uuid) {
users.invalidate(uuid);
}
public Set<UUID> getAllUniqueUsers() {
return Collections.unmodifiableSet(keys);
return userMap.getAllUserUUIDs();
}
public int getUniqueUsers() {
return keys.size();
return userMap.getUserCount();
}
protected ConcurrentSkipListMap<String, UUID> getNames() {
return names;
}
protected ConcurrentSkipListMap<UUID, ArrayList<String>> getHistory() {
return history;
}
public List<String> getUserHistory(final UUID uuid) {
return history.get(uuid);
return new ConcurrentSkipListMap<>(userMap.getNameCache());
}
public UUIDMap getUUIDMap() {
return uuidMap;
}
// class UserMapRemovalListener implements RemovalListener
// {
// @Override
// public void onRemoval(final RemovalNotification notification)
// {
// Object value = notification.getValue();
// if (value != null)
// {
// ((User)value).cleanup();
// }
// }
// }
private File getUserFileFromID(final UUID uuid) {
final File userFolder = new File(ess.getDataFolder(), "userdata");
return new File(userFolder, uuid.toString() + ".yml");
}
public File getUserFileFromString(final String name) {
final File userFolder = new File(ess.getDataFolder(), "userdata");
return new File(userFolder, StringUtil.sanitizeFileName(name) + ".yml");
}
@SuppressWarnings("deprecation")
public User getUserFromBukkit(String name) {
name = StringUtil.safeString(name);
if (ess.getSettings().isDebug()) {
ess.getLogger().warning("Using potentially blocking Bukkit UUID lookup for: " + name);
}
// Don't attempt to look up entirely invalid usernames
if (name == null || !validUserPattern.matcher(name).matches()) {
return null;
}
final org.bukkit.OfflinePlayer offlinePlayer = ess.getServer().getOfflinePlayer(name);
if (offlinePlayer == null) {
return null;
}
final UUID uuid;
try {
uuid = offlinePlayer.getUniqueId();
} catch (final UnsupportedOperationException | NullPointerException e) {
return null;
}
// This is how Bukkit generates fake UUIDs
if (UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)).equals(uuid)) {
return null;
} else {
names.put(name, uuid);
return getUser(uuid);
}
}
private User legacyCacheGet(final UUID uuid) {
if (getLegacy == null) {
final Class<?> usersClass = users.getClass();
for (final Method m : usersClass.getDeclaredMethods()) {
if (m.getName().equals("get")) {
getLegacy = m;
getLegacy.setAccessible(true);
break;
}
}
}
try {
return (User) getLegacy.invoke(users, uuid.toString());
} catch (final IllegalAccessException | InvocationTargetException e) {
return null;
}
}
private void legacyMaximumSize(final CacheBuilder builder, final int maxCount) {
try {
final Method maxSizeLegacy = builder.getClass().getDeclaredMethod("maximumSize", Integer.TYPE);
maxSizeLegacy.setAccessible(true);
maxSizeLegacy.invoke(builder, maxCount);
} catch (final NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
private Cache<String, User> legacyBuild(final CacheBuilder builder) {
Method build = null;
for (final Method method : builder.getClass().getDeclaredMethods()) {
if (method.getName().equals("build")) {
build = method;
break;
}
}
Cache<String, User> legacyUsers;
try {
assert build != null;
build.setAccessible(true);
legacyUsers = (Cache<String, User>) build.invoke(builder, this);
} catch (final IllegalAccessException | InvocationTargetException e) {
legacyUsers = null;
}
return legacyUsers;
}
}
@@ -1,10 +1,11 @@
package com.earth2me.essentials;
import com.earth2me.essentials.adventure.AdventureUtil;
import com.earth2me.essentials.commands.WarpNotFoundException;
import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.utils.StringUtil;
import net.ess3.api.InvalidNameException;
import net.ess3.api.InvalidWorldException;
import net.ess3.api.TranslatableException;
import org.bukkit.Location;
import java.io.File;
@@ -17,7 +18,7 @@ import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class Warps implements IConf, net.ess3.api.IWarps {
private final Map<StringIgnoreCase, EssentialsConfiguration> warpPoints = new HashMap<>();
@@ -52,7 +53,7 @@ public class Warps implements IConf, net.ess3.api.IWarps {
}
@Override
public Location getWarp(final String warp) throws WarpNotFoundException, InvalidWorldException {
public Location getWarp(final String warp) throws WarpNotFoundException {
final EssentialsConfiguration conf = warpPoints.get(new StringIgnoreCase(warp));
if (conf == null) {
throw new WarpNotFoundException();
@@ -77,7 +78,7 @@ public class Warps implements IConf, net.ess3.api.IWarps {
if (conf == null) {
final File confFile = new File(warpsFolder, filename + ".yml");
if (confFile.exists()) {
throw new Exception(tl("similarWarpExist"));
throw new TranslatableException("similarWarpExist");
}
conf = new EssentialsConfiguration(confFile);
conf.load();
@@ -109,10 +110,10 @@ public class Warps implements IConf, net.ess3.api.IWarps {
public void removeWarp(final String name) throws Exception {
final EssentialsConfiguration conf = warpPoints.get(new StringIgnoreCase(name));
if (conf == null) {
throw new Exception(tl("warpNotExist"));
throw new TranslatableException("warpNotExist");
}
if (!conf.getFile().delete()) {
throw new Exception(tl("warpDeleteError"));
throw new TranslatableException("warpDeleteError");
}
warpPoints.remove(new StringIgnoreCase(name));
}
@@ -121,7 +122,7 @@ public class Warps implements IConf, net.ess3.api.IWarps {
public final void reloadConfig() {
warpPoints.clear();
final File[] listOfFiles = warpsFolder.listFiles();
if (listOfFiles.length >= 1) {
if (listOfFiles != null) {
for (final File listOfFile : listOfFiles) {
final String filename = listOfFile.getName();
if (listOfFile.isFile() && filename.endsWith(".yml")) {
@@ -133,7 +134,7 @@ public class Warps implements IConf, net.ess3.api.IWarps {
warpPoints.put(new StringIgnoreCase(name), conf);
}
} catch (final Exception ex) {
Essentials.getWrappedLogger().log(Level.WARNING, tl("loadWarpError", filename), ex);
Essentials.getWrappedLogger().log(Level.WARNING, AdventureUtil.getAdventureFacet().miniToLegacy(tlLiteral("loadWarpError", filename)), ex);
}
}
}
@@ -3,7 +3,9 @@ package com.earth2me.essentials;
import com.earth2me.essentials.commands.NotEnoughArgumentsException;
import com.earth2me.essentials.config.ConfigurateUtil;
import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.api.TranslatableException;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.spongepowered.configurate.CommentedConfigurationNode;
@@ -12,8 +14,6 @@ import java.io.File;
import java.math.BigDecimal;
import java.util.Locale;
import static com.earth2me.essentials.I18n.tl;
public class Worth implements IConf {
private final EssentialsConfiguration config;
@@ -77,7 +77,7 @@ public class Worth implements IConf {
*/
public int getAmount(final IEssentials ess, final User user, final ItemStack is, final String[] args, final boolean isBulkSell) throws Exception {
if (is == null || is.getType() == Material.AIR) {
throw new Exception(tl("itemSellAir"));
throw new TranslatableException("itemSellAir");
}
int amount = 0;
@@ -97,11 +97,11 @@ public class Worth implements IConf {
final boolean requireStack = ess.getSettings().isTradeInStacks(is.getType());
if (requireStack && !stack) {
throw new Exception(tl("itemMustBeStacked"));
throw new TranslatableException("itemMustBeStacked");
}
int max = 0;
for (final ItemStack s : user.getBase().getInventory().getContents()) {
for (final ItemStack s : Inventories.getInventory(user.getBase(), false)) {
if (s == null || !s.isSimilar(is)) {
continue;
}
@@ -120,9 +120,9 @@ public class Worth implements IConf {
}
if (amount > max || amount < 1) {
if (!isBulkSell) {
user.sendMessage(tl("itemNotEnough2"));
user.sendMessage(tl("itemNotEnough3"));
throw new Exception(tl("itemNotEnough1"));
user.sendTl("itemNotEnough2");
user.sendTl("itemNotEnough3");
throw new TranslatableException("itemNotEnough1");
} else {
return amount;
}
@@ -0,0 +1,62 @@
package com.earth2me.essentials.adventure;
import net.kyori.adventure.text.format.NamedTextColor;
public final class AdventureUtil {
private static final String LOOKUP = "0123456789abcdefklmnor";
private static final NamedTextColor[] COLORS = new NamedTextColor[]{NamedTextColor.BLACK, NamedTextColor.DARK_BLUE, NamedTextColor.DARK_GREEN, NamedTextColor.DARK_AQUA, NamedTextColor.DARK_RED, NamedTextColor.DARK_PURPLE, NamedTextColor.GOLD, NamedTextColor.GRAY, NamedTextColor.DARK_GRAY, NamedTextColor.BLUE, NamedTextColor.GREEN, NamedTextColor.AQUA, NamedTextColor.RED, NamedTextColor.LIGHT_PURPLE, NamedTextColor.YELLOW, NamedTextColor.WHITE};
private static AdventureFacet ADVENTURE_FACET_INSTANCE = null;
private AdventureUtil() {
}
public static void setAdventureFacet(AdventureFacet adventureFacetInstance) {
ADVENTURE_FACET_INSTANCE = adventureFacetInstance;
}
public static AdventureFacet getAdventureFacet() {
return ADVENTURE_FACET_INSTANCE;
}
/**
* Get the {@link NamedTextColor} from its associated section sign char.
*/
public static NamedTextColor fromChar(final char c) {
final int index = LOOKUP.indexOf(c);
if (index == -1 || index > 15) {
return null;
}
return COLORS[index];
}
public static String fromCharName(final char c) {
final NamedTextColor namedTextColor = fromChar(c);
if (namedTextColor == null) {
return null;
}
return namedTextColor.toString();
}
/**
* Parameters for a translation message are not parsed for MiniMessage by default to avoid injection. If you want
* a parameter to be parsed for MiniMessage you must wrap it in a ParsedPlaceholder by using this method.
*/
public static ParsedPlaceholder parsed(final String literal) {
return new ParsedPlaceholder(literal);
}
public static class ParsedPlaceholder {
private final String value;
protected ParsedPlaceholder(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
}
@@ -0,0 +1,155 @@
package com.earth2me.essentials.adventure;
import com.earth2me.essentials.Essentials;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.provider.AbstractChatEvent;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.flattener.ComponentFlattener;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.Color;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Locale;
public class SpigotAdventureFacet implements AdventureFacet {
private static final LegacyComponentSerializer LEGACY_SERIALIZER;
private static final LegacyComponentSerializer LEGACY_SERIALIZER_URLS;
private static final MiniMessage MINI_MESSAGE_NO_TAGS;
static {
final LegacyComponentSerializer.Builder builder = LegacyComponentSerializer.builder()
.flattener(ComponentFlattener.basic())
.useUnusualXRepeatedCharacterHexFormat();
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_16_1_R01)) {
builder.hexColors();
}
LEGACY_SERIALIZER = builder.build();
LEGACY_SERIALIZER_URLS = builder.extractUrls(AbstractChatEvent.URL_PATTERN).build();
MINI_MESSAGE_NO_TAGS = MiniMessage.builder().strict(true).build();
}
private final Essentials ess;
private final BukkitAudiences bukkitAudiences;
private final MiniMessage miniMessageInstance;
public SpigotAdventureFacet(final Essentials ess) {
this.ess = ess.getSettings() == null ? null : ess;
bukkitAudiences = BukkitAudiences.create(ess);
miniMessageInstance = MiniMessage.builder()
.tags(TagResolver.builder()
.resolvers(TagResolver.standard())
.resolver(TagResolver.resolver("primary", supplyTag(true)))
.resolver(TagResolver.resolver("secondary", supplyTag(false)))
.build())
.build();
}
private Tag supplyTag(final boolean primary) {
final String color = primary ? (ess != null ? ess.getSettings().getPrimaryColor() : "gold") : (ess != null ? ess.getSettings().getSecondaryColor() : "red");
final TextColor textColor;
if (color.startsWith("#") && color.length() == 7 && NumberUtil.isHexadecimal(color.substring(1))) {
textColor = TextColor.color(Color.fromRGB(Integer.decode(color)).asRGB());
} else if (color.length() == 1) {
textColor = AdventureUtil.fromChar(color.charAt(0));
} else {
textColor = NamedTextColor.NAMES.value(color.toLowerCase(Locale.ENGLISH));
}
return ess != null && textColor != null ? Tag.styling(textColor) : Tag.styling(primary ? NamedTextColor.GOLD : NamedTextColor.RED);
}
@Override
public ComponentHolder deserializeMiniMessage(String message) {
return new ComponentHolder(miniMessageInstance.deserialize(message));
}
@Override
public String legacyToMini(String message) {
return legacyToMini(message, true);
}
@Override
public void send(CommandSender sender, ComponentHolder component) {
bukkitAudiences.sender(sender).sendMessage((Component) component.getComponent());
}
@Override
public void send(Player player, ComponentHolder component) {
bukkitAudiences.player(player).sendMessage((Component) component.getComponent());
}
@Override
public String adventureToLegacy(ComponentHolder component) {
return adventureToLegacy((Component) component.getComponent());
}
private String adventureToLegacy(Component component) {
return LEGACY_SERIALIZER.serialize(component);
}
@Override
public ComponentHolder legacyToAdventure(String message) {
return new ComponentHolder(LEGACY_SERIALIZER.deserialize(message));
}
@Override
public ComponentHolder text(String message) {
return new ComponentHolder(Component.text(message));
}
@Override
public String legacyToMini(String message, boolean useCustomTags) {
final Component deserializedText = LEGACY_SERIALIZER.deserialize(message);
if (useCustomTags) {
return miniMessageInstance.serialize(deserializedText);
} else {
return MINI_MESSAGE_NO_TAGS.serialize(deserializedText);
}
}
@Override
public String miniToLegacy(String message) {
return adventureToLegacy(miniMessageInstance.deserialize(message));
}
@Override
public String stripTags(String input) {
return miniMessageInstance.stripTags(input);
}
@Override
public String legacyToMiniWithUrls(String message) {
return miniMessageInstance.serialize(LEGACY_SERIALIZER_URLS.deserialize(message));
}
@Override
public String escapeTags(String input) {
return miniMessageInstance.escapeTags(input);
}
@Override
public ComponentHolder append(ComponentHolder base, ComponentHolder... addition) {
Component baseComponent = (Component) base.getComponent();
for (ComponentHolder holder : addition) {
final Component additionComponent = (Component) holder.getComponent();
baseComponent = baseComponent.append(additionComponent);
}
return new ComponentHolder(baseComponent);
}
@Override
public void close() {
bukkitAudiences.close();
}
}
@@ -3,6 +3,7 @@ package com.earth2me.essentials.api;
import com.earth2me.essentials.Trade;
import com.earth2me.essentials.User;
import com.earth2me.essentials.config.EssentialsUserConfiguration;
import com.earth2me.essentials.userstorage.ModernUserMap;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.StringUtil;
import com.google.common.base.Charsets;
@@ -41,9 +42,9 @@ public class Economy {
ess = aEss;
}
private static void createNPCFile(String name) {
private static void createNPCFile(final String unsanitizedName) {
final File folder = new File(ess.getDataFolder(), "userdata");
name = StringUtil.safeString(name);
final String name = ess.getSettings().isSafeUsermap() ? StringUtil.safeString(unsanitizedName) : unsanitizedName;
if (!folder.exists()) {
if (!folder.mkdirs()) {
throw new RuntimeException("Error while creating userdata directory!");
@@ -59,9 +60,12 @@ public class Economy {
npcConfig.load();
npcConfig.setProperty("npc", true);
npcConfig.setProperty("last-account-name", name);
npcConfig.setProperty("npc-name", unsanitizedName);
npcConfig.setProperty("money", ess.getSettings().getStartingBalance());
npcConfig.blockingSave();
ess.getUserMap().trackUUID(npcUUID, name, false);
// This will load the NPC into the UserMap + UUID cache
((ModernUserMap) ess.getUsers()).addCachedNpcName(npcUUID, name);
ess.getUsers().getUser(npcUUID);
}
private static void deleteNPC(final String name) {
@@ -319,7 +323,13 @@ public class Economy {
throw new IllegalArgumentException("Economy user cannot be null");
}
final BigDecimal result = getMoneyExact(user).add(amount, MATH_CONTEXT);
setMoney(user, result);
// Deposits (positive amounts) always improve the balance, so minMoney and
// loan checks don't apply — they exist to prevent the balance from dropping.
if (amount.signum() <= 0) {
setMoney(user, result);
} else {
user.setMoney(result, UserBalanceUpdateEvent.Cause.API);
}
Trade.log("API", "Add", "API", user.getName(), new Trade(amount, ess), null, null, null, result, ess);
}
@@ -46,17 +46,6 @@ public interface IJails extends IConf {
*/
void removeJail(String jail) throws Exception;
/**
* Attempts to send the given user to the given jail
*
* @param user the user to send to jail
* @param jail the jail to send the user to
* @throws Exception if the user is offline or jail does not exist
* @deprecated Use {@link IJails#sendToJail(IUser, String, CompletableFuture)}
*/
@Deprecated
void sendToJail(IUser user, String jail) throws Exception;
/**
* Attempts to send the given user to the given jail
*
@@ -22,9 +22,8 @@ public interface IWarps extends IConf {
* @param warp - Warp name
* @return - Location the warp is set to
* @throws WarpNotFoundException When the warp is not found
* @throws net.ess3.api.InvalidWorldException When the world the warp is in is not found
*/
Location getWarp(String warp) throws WarpNotFoundException, net.ess3.api.InvalidWorldException;
Location getWarp(String warp) throws WarpNotFoundException;
/**
* Checks if the provided name is a warp.
@@ -1,16 +1,16 @@
package com.earth2me.essentials.api;
import static com.earth2me.essentials.I18n.tl;
import net.ess3.api.TranslatableException;
/**
* @deprecated This exception is unused. Use {@link net.ess3.api.InvalidWorldException} instead.
*/
@Deprecated
public class InvalidWorldException extends Exception {
public class InvalidWorldException extends TranslatableException {
private final String world;
public InvalidWorldException(final String world) {
super(tl("invalidWorld"));
super("invalidWorld");
this.world = world;
}
@@ -1,18 +1,18 @@
package com.earth2me.essentials.api;
import java.util.UUID;
import net.ess3.api.TranslatableException;
import static com.earth2me.essentials.I18n.tl;
import java.util.UUID;
/**
* Thrown when the requested user does not exist.
*/
public class UserDoesNotExistException extends Exception {
public class UserDoesNotExistException extends TranslatableException {
public UserDoesNotExistException(final String name) {
super(tl("userDoesNotExist", name));
super("userDoesNotExist", name);
}
public UserDoesNotExistException(final UUID uuid) {
super(tl("uuidDoesNotExist", uuid.toString()));
super("uuidDoesNotExist", uuid.toString());
}
}
@@ -3,14 +3,13 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.DateUtil;
import net.ess3.api.TranslatableException;
import net.ess3.api.events.AfkStatusChangeEvent;
import org.bukkit.Server;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandafk extends EssentialsCommand {
public Commandafk() {
super("afk");
@@ -50,17 +49,23 @@ public class Commandafk extends EssentialsCommand {
if (sender.isMuted()) {
final String dateDiff = sender.getMuteTimeout() > 0 ? DateUtil.formatDateDiff(sender.getMuteTimeout()) : null;
if (dateDiff == null) {
throw new Exception(sender.hasMuteReason() ? tl("voiceSilencedReason", sender.getMuteReason()) : tl("voiceSilenced"));
if (sender.hasMuteReason()) {
throw new TranslatableException("voiceSilencedReason", sender.getMuteReason());
} else {
throw new TranslatableException("voiceSilenced");
}
}
if (sender.hasMuteReason()) {
throw new TranslatableException("voiceSilencedReasonTime", dateDiff, sender.getMuteReason());
} else {
throw new TranslatableException("voiceSilencedTime", dateDiff);
}
throw new Exception(sender.hasMuteReason() ? tl("voiceSilencedReasonTime", dateDiff, sender.getMuteReason()) : tl("voiceSilencedTime", dateDiff));
}
if (!sender.isAuthorized("essentials.afk.message")) {
throw new Exception(tl("noPermToAFKMessage"));
throw new TranslatableException("noPermToAFKMessage");
}
}
user.setDisplayNick();
String msg = "";
String selfmsg = "";
final boolean currentStatus = user.isAfk();
final boolean afterStatus = user.toggleAfk(AfkStatusChangeEvent.Cause.COMMAND);
@@ -68,38 +73,40 @@ public class Commandafk extends EssentialsCommand {
return;
}
String tlKey = "";
String selfTlKey = "";
if (!afterStatus) {
if (!user.isHidden()) {
msg = tl("userIsNotAway", user.getDisplayName());
selfmsg = tl("userIsNotAwaySelf", user.getDisplayName());
tlKey = "userIsNotAway";
selfTlKey = "userIsNotAwaySelf";
}
user.updateActivity(false, AfkStatusChangeEvent.Cause.COMMAND);
} else {
if (!user.isHidden()) {
if (message != null) {
msg = tl("userIsAwayWithMessage", user.getDisplayName(), message);
selfmsg = tl("userIsAwaySelfWithMessage", user.getDisplayName(), message);
tlKey = "userIsAwayWithMessage";
selfTlKey = "userIsAwaySelfWithMessage";
} else {
msg = tl("userIsAway", user.getDisplayName());
selfmsg = tl("userIsAwaySelf", user.getDisplayName());
tlKey = "userIsAway";
selfTlKey = "userIsAwaySelf";
}
}
user.setAfkMessage(message);
}
if (!msg.isEmpty() && ess.getSettings().broadcastAfkMessage()) {
if (!tlKey.isEmpty() && ess.getSettings().broadcastAfkMessage()) {
// exclude user from receiving general AFK announcement in favor of personal message
ess.broadcastMessage(user, msg, u -> u == user);
ess.broadcastTl(user, u -> u == user, tlKey, user.getDisplayName(), message);
}
if (!selfmsg.isEmpty()) {
user.sendMessage(selfmsg);
if (!selfTlKey.isEmpty()) {
user.sendTl(selfTlKey, user.getDisplayName(), message);
}
user.setDisplayNick(); // Set this again after toggling
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1 && sender.isAuthorized("essentials.afk.others", ess)) {
return getPlayers(server, sender);
if (args.length == 1 && sender.isAuthorized("essentials.afk.others")) {
return getPlayers(sender);
} else {
return Collections.emptyList();
}
@@ -1,10 +1,9 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import net.ess3.provider.ContainerProvider;
import org.bukkit.Server;
import static com.earth2me.essentials.I18n.tl;
public class Commandanvil extends EssentialsCommand {
public Commandanvil() {
@@ -13,11 +12,13 @@ public class Commandanvil extends EssentialsCommand {
@Override
protected void run(Server server, User user, String commandLabel, String[] args) throws Exception {
if (ess.getContainerProvider() == null) {
user.sendMessage(tl("unsupportedBrand"));
final ContainerProvider containerProvider = ess.provider(ContainerProvider.class);
if (containerProvider == null) {
user.sendTl("unsupportedBrand");
return;
}
ess.getContainerProvider().openAnvil(user.getBase());
containerProvider.openAnvil(user.getBase());
}
}
@@ -3,13 +3,12 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.Trade;
import com.earth2me.essentials.User;
import net.ess3.api.TranslatableException;
import org.bukkit.Server;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandback extends EssentialsCommand {
public Commandback() {
super("back");
@@ -37,13 +36,13 @@ public class Commandback extends EssentialsCommand {
private void parseOthers(final Server server, final CommandSource sender, final String[] args, final String commandLabel) throws Exception {
final User player = getPlayer(server, args, 0, true, false);
sender.sendMessage(tl("backOther", player.getName()));
sender.sendTl("backOther", player.getName());
teleportBack(sender, player, commandLabel);
}
private void teleportBack(final CommandSource sender, final User user, final String commandLabel) throws Exception {
if (user.getLastLocation() == null) {
throw new Exception(tl("noLocationFound"));
throw new TranslatableException("noLocationFound");
}
final String lastWorldName = user.getLastLocation().getWorld().getName();
@@ -53,11 +52,11 @@ public class Commandback extends EssentialsCommand {
requester = ess.getUser(sender.getPlayer());
if (user.getWorld() != user.getLastLocation().getWorld() && this.ess.getSettings().isWorldTeleportPermissions() && !user.isAuthorized("essentials.worlds." + lastWorldName)) {
throw new Exception(tl("noPerm", "essentials.worlds." + lastWorldName));
throw new TranslatableException("noPerm", "essentials.worlds." + lastWorldName);
}
if (!requester.isAuthorized("essentials.back.into." + lastWorldName)) {
throw new Exception(tl("noPerm", "essentials.back.into." + lastWorldName));
throw new TranslatableException("noPerm", "essentials.back.into." + lastWorldName);
}
}
@@ -78,7 +77,7 @@ public class Commandback extends EssentialsCommand {
@Override
protected List<String> getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) {
if (user.isAuthorized("essentials.back.others") && args.length == 1) {
return getPlayers(server, user);
return getPlayers(user);
} else {
return Collections.emptyList();
}
@@ -2,10 +2,9 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.Backup;
import com.earth2me.essentials.CommandSource;
import net.ess3.api.TranslatableException;
import org.bukkit.Server;
import static com.earth2me.essentials.I18n.tl;
public class Commandbackup extends EssentialsCommand {
public Commandbackup() {
super("backup");
@@ -15,13 +14,13 @@ public class Commandbackup extends EssentialsCommand {
protected void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
final Backup backup = ess.getBackup();
if (backup == null) {
throw new Exception(tl("backupDisabled"));
throw new TranslatableException("backupDisabled");
}
final String command = ess.getSettings().getBackupCommand();
if (command == null || "".equals(command) || "save-all".equalsIgnoreCase(command)) {
throw new Exception(tl("backupDisabled"));
throw new TranslatableException("backupDisabled");
}
backup.run();
sender.sendMessage(tl("backupStarted"));
sender.sendTl("backupStarted");
}
}
@@ -2,14 +2,13 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.User;
import com.earth2me.essentials.adventure.AdventureUtil;
import com.earth2me.essentials.utils.NumberUtil;
import org.bukkit.Server;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandbalance extends EssentialsCommand {
public Commandbalance() {
super("balance");
@@ -21,17 +20,17 @@ public class Commandbalance extends EssentialsCommand {
throw new NotEnoughArgumentsException();
}
final User target = getPlayer(server, args, 0, false, true);
sender.sendMessage(tl("balanceOther", target.isHidden() ? target.getName() : target.getDisplayName(), NumberUtil.displayCurrency(target.getMoney(), ess)));
final User target = getPlayer(server, args, 0, true, true);
sender.sendTl("balanceOther", target.isHidden() ? target.getName() : target.getDisplayName(), AdventureUtil.parsed(NumberUtil.displayCurrency(target.getMoney(), ess)));
}
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
if (args.length == 1 && user.isAuthorized("essentials.balance.others")) {
final User target = getPlayer(server, args, 0, true, true);
user.sendMessage(tl("balanceOther", target.isHidden() ? target.getName() : target.getDisplayName(), NumberUtil.displayCurrency(target.getMoney(), ess)));
final User target = getPlayer(server, args, 0, false, true);
user.sendTl("balanceOther", target.isHidden() ? target.getName() : target.getDisplayName(), AdventureUtil.parsed(NumberUtil.displayCurrency(target.getMoney(), ess)));
} else if (args.length < 2) {
user.sendMessage(tl("balance", NumberUtil.displayCurrency(user.getMoney(), ess)));
user.sendTl("balance", AdventureUtil.parsed(NumberUtil.displayCurrency(user.getMoney(), ess)));
} else {
throw new NotEnoughArgumentsException();
}
@@ -39,8 +38,8 @@ public class Commandbalance extends EssentialsCommand {
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1 && sender.isAuthorized("essentials.balance.others", ess)) {
return getPlayers(server, sender);
if (args.length == 1 && sender.isAuthorized("essentials.balance.others")) {
return getPlayers(sender);
} else {
return Collections.emptyList();
}
@@ -1,12 +1,19 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.User;
import com.earth2me.essentials.textreader.SimpleTextInput;
import com.earth2me.essentials.textreader.TextPager;
import com.earth2me.essentials.adventure.AdventureUtil;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.VersionUtil;
import com.google.common.collect.Lists;
import net.essentialsx.api.v2.services.BalanceTop;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.Statistic;
import org.bukkit.command.BlockCommandSender;
import java.math.BigDecimal;
import java.text.DateFormat;
@@ -17,7 +24,7 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class Commandbalancetop extends EssentialsCommand {
public static final int MINUSERS = 50;
@@ -32,8 +39,15 @@ public class Commandbalancetop extends EssentialsCommand {
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(ess.getBalanceTop().getCacheAge());
final DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
sender.sendMessage(tl("balanceTop", format.format(cal.getTime())));
new TextPager(cache).showPage(Integer.toString(page), null, "balancetop", sender);
final Runnable runnable = () -> {
sender.sendTl("balanceTop", format.format(cal.getTime()));
new TextPager(cache).showPage(Integer.toString(page), null, "balancetop", sender);
};
if (sender.getSender() instanceof BlockCommandSender) {
ess.scheduleSyncDelayedTask(runnable);
} else {
runnable.run();
}
}
@Override
@@ -56,8 +70,8 @@ public class Commandbalancetop extends EssentialsCommand {
}
// If there are less than 50 users in our usermap, there is no need to display a warning as these calculations should be done quickly
if (ess.getUserMap().getUniqueUsers() > MINUSERS) {
sender.sendMessage(tl("orderBalances", ess.getUserMap().getUniqueUsers()));
if (ess.getUsers().getUserCount() > MINUSERS) {
sender.sendTl("orderBalances", ess.getUsers().getUserCount());
}
ess.runTaskAsynchronously(new Viewer(sender, page, force));
@@ -101,17 +115,38 @@ public class Commandbalancetop extends EssentialsCommand {
future.thenRun(() -> {
if (fresh) {
final SimpleTextInput newCache = new SimpleTextInput();
newCache.getLines().add(tl("serverTotal", NumberUtil.displayCurrency(ess.getBalanceTop().getBalanceTopTotal(), ess)));
newCache.getLines().add(ess.getAdventureFacet().miniToLegacy(tlLiteral("serverTotal", AdventureUtil.parsed(NumberUtil.displayCurrency(ess.getBalanceTop().getBalanceTopTotal(), ess)))));
int pos = 1;
for (final Map.Entry<UUID, BalanceTop.Entry> entry : ess.getBalanceTop().getBalanceTopCache().entrySet()) {
if (ess.getSettings().showZeroBaltop() || entry.getValue().getBalance().compareTo(BigDecimal.ZERO) > 0) {
newCache.getLines().add(tl("balanceTopLine", pos, entry.getValue().getDisplayName(), NumberUtil.displayCurrency(entry.getValue().getBalance(), ess)));
final BigDecimal balance = entry.getValue().getBalance();
final User user = ess.getUser(entry.getKey());
final Statistic PLAY_ONE_TICK = EnumUtil.getStatistic("PLAY_ONE_MINUTE", "PLAY_ONE_TICK");
final boolean offlineStatisticSupported = VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_15_2_R01);
final long playtime;
if (user.getBase() == null || !user.getBase().isOnline()) {
if (offlineStatisticSupported) {
playtime = Bukkit.getServer().getOfflinePlayer(entry.getKey()).getStatistic(PLAY_ONE_TICK);
} else {
playtime = -1;
}
} else {
playtime = user.getBase().getStatistic(PLAY_ONE_TICK);
}
// Play time in seconds
final long playTimeSecs = Math.max(playtime / 20, 0);
// Checking if player meets the requirements of minimum balance and minimum playtime to be listed in baltop list
if ((ess.getSettings().showZeroBaltop() || balance.compareTo(BigDecimal.ZERO) > 0)
&& balance.compareTo(ess.getSettings().getBaltopMinBalance()) >= 0 &&
// Skip playtime check for offline players on versions below 1.15.2
(playtime == -1 || playTimeSecs >= ess.getSettings().getBaltopMinPlaytime())) {
newCache.getLines().add(ess.getAdventureFacet().miniToLegacy(tlLiteral("balanceTopLine", pos, entry.getValue().getDisplayName(), AdventureUtil.parsed(NumberUtil.displayCurrency(balance, ess)))));
}
pos++;
}
cache = newCache;
}
outputCache(sender, page);
});
}
@@ -2,9 +2,10 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.Console;
import com.earth2me.essentials.OfflinePlayer;
import com.earth2me.essentials.OfflinePlayerStub;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.FormatUtil;
import net.ess3.api.TranslatableException;
import org.bukkit.BanList;
import org.bukkit.Server;
@@ -12,7 +13,7 @@ import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class Commandban extends EssentialsCommand {
public Commandban() {
@@ -30,43 +31,43 @@ public class Commandban extends EssentialsCommand {
user = getPlayer(server, args, 0, true, true);
} catch (final PlayerNotFoundException e) {
nomatch = true;
user = ess.getUser(new OfflinePlayer(args[0], ess.getServer()));
user = ess.getUser(new OfflinePlayerStub(args[0], ess.getServer()));
}
if (!user.getBase().isOnline()) {
if (sender.isPlayer() && !ess.getUser(sender.getPlayer()).isAuthorized("essentials.ban.offline")) {
throw new Exception(tl("banExemptOffline"));
throw new TranslatableException("banExemptOffline");
}
} else if (user.isAuthorized("essentials.ban.exempt") && sender.isPlayer()) {
throw new Exception(tl("banExempt"));
throw new TranslatableException("banExempt");
}
final String senderName = sender.isPlayer() ? sender.getPlayer().getDisplayName() : Console.NAME;
final String senderDisplayName = sender.isPlayer() ? sender.getPlayer().getDisplayName() : Console.DISPLAY_NAME;
final String senderDisplayName = sender.isPlayer() ? sender.getPlayer().getDisplayName() : Console.displayName();
final String banReason;
if (args.length > 1) {
banReason = FormatUtil.replaceFormat(getFinalArg(args, 1).replace("\\n", "\n").replace("|", "\n"));
} else {
banReason = tl("defaultBanReason");
banReason = tlLiteral("defaultBanReason");
}
ess.getServer().getBanList(BanList.Type.NAME).addBan(user.getName(), banReason, null, senderName);
final String banDisplay = tl("banFormat", banReason, senderDisplayName);
final String banDisplay = tlLiteral("banFormat", banReason, senderDisplayName);
user.getBase().kickPlayer(banDisplay);
server.getLogger().log(Level.INFO, tl("playerBanned", senderDisplayName, user.getName(), banDisplay));
user.getBase().kickPlayer(ess.getAdventureFacet().miniToLegacy(banDisplay));
ess.getLogger().log(Level.INFO, ess.getAdventureFacet().miniToLegacy(tlLiteral("playerBanned", senderDisplayName, user.getName(), banDisplay)));
if (nomatch) {
sender.sendMessage(tl("userUnknown", user.getName()));
sender.sendTl("userUnknown", user.getName());
}
ess.broadcastMessage("essentials.ban.notify", tl("playerBanned", senderDisplayName, user.getName(), banReason));
ess.broadcastTl(null, u -> !u.isAuthorized("essentials.ban.notify"), "playerBanned", senderDisplayName, user.getName(), banReason);
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1) {
return getPlayers(server, sender);
return getPlayers(sender);
} else {
return Collections.emptyList();
}
@@ -12,7 +12,7 @@ import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class Commandbanip extends EssentialsCommand {
public Commandbanip() {
@@ -26,7 +26,7 @@ public class Commandbanip extends EssentialsCommand {
}
final String senderName = sender.isPlayer() ? sender.getPlayer().getDisplayName() : Console.NAME;
final String senderDisplayName = sender.isPlayer() ? sender.getPlayer().getDisplayName() : Console.DISPLAY_NAME;
final String senderDisplayName = sender.isPlayer() ? sender.getPlayer().getDisplayName() : Console.displayName();
String ipAddress;
if (FormatUtil.validIP(args[0])) {
@@ -48,13 +48,13 @@ public class Commandbanip extends EssentialsCommand {
if (args.length > 1) {
banReason = FormatUtil.replaceFormat(getFinalArg(args, 1).replace("\\n", "\n").replace("|", "\n"));
} else {
banReason = tl("defaultBanReason");
banReason = tlLiteral("defaultBanReason");
}
final String banDisplay = tl("banFormat", banReason, senderDisplayName);
final String banDisplay = ess.getAdventureFacet().miniToLegacy(tlLiteral("banFormat", banReason, senderDisplayName));
ess.getServer().getBanList(BanList.Type.IP).addBan(ipAddress, banReason, null, senderName);
server.getLogger().log(Level.INFO, tl("playerBanIpAddress", senderDisplayName, ipAddress, banReason));
ess.getLogger().log(Level.INFO, ess.getAdventureFacet().miniToLegacy(tlLiteral("playerBanIpAddress", senderDisplayName, ipAddress, banReason)));
for (final Player player : ess.getServer().getOnlinePlayers()) {
if (player.getAddress().getAddress().getHostAddress().equalsIgnoreCase(ipAddress)) {
@@ -62,14 +62,14 @@ public class Commandbanip extends EssentialsCommand {
}
}
ess.broadcastMessage("essentials.banip.notify", tl("playerBanIpAddress", senderDisplayName, ipAddress, banReason));
ess.broadcastTl(null, u -> !u.isAuthorized("essentials.banip.notify"), "playerBanIpAddress", senderDisplayName, ipAddress, banReason);
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1) {
// TODO: Also list IP addresses?
return getPlayers(server, sender);
return getPlayers(sender);
} else {
return Collections.emptyList();
}
@@ -7,8 +7,6 @@ import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.entity.Entity;
import static com.earth2me.essentials.I18n.tl;
public class Commandbeezooka extends EssentialsCommand {
public Commandbeezooka() {
@@ -18,7 +16,7 @@ public class Commandbeezooka extends EssentialsCommand {
@Override
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_15_R01)) {
user.sendMessage(tl("unsupportedFeature"));
user.sendTl("unsupportedFeature");
return;
}
@@ -3,6 +3,7 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.LocationUtil;
import com.google.common.collect.Lists;
import net.ess3.api.TranslatableException;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.TreeType;
@@ -10,8 +11,6 @@ import org.bukkit.TreeType;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandbigtree extends EssentialsCommand {
public Commandbigtree() {
super("bigtree");
@@ -34,13 +33,13 @@ public class Commandbigtree extends EssentialsCommand {
final Location loc = LocationUtil.getTarget(user.getBase(), ess.getSettings().getMaxTreeCommandRange()).add(0, 1, 0);
if (loc.getBlock().getType().isSolid()) {
throw new Exception(tl("bigTreeFailure"));
throw new TranslatableException("bigTreeFailure");
}
final boolean success = user.getWorld().generateTree(loc, tree);
if (success) {
user.sendMessage(tl("bigTreeSuccess"));
user.sendTl("bigTreeSuccess");
} else {
throw new Exception(tl("bigTreeFailure"));
throw new TranslatableException("bigTreeFailure");
}
}
@@ -1,20 +1,21 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.VersionUtil;
import com.google.common.collect.Lists;
import net.ess3.api.TranslatableException;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.WritableBookMeta;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandbook extends EssentialsCommand {
private static final Material WRITABLE_BOOK = EnumUtil.getMaterial("WRITABLE_BOOK", "BOOK_AND_QUILL");
@@ -25,7 +26,7 @@ public class Commandbook extends EssentialsCommand {
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
final ItemStack item = user.getItemInHand();
final ItemStack item = Inventories.getItemInMainHand(user.getBase());
final String player = user.getName();
if (item.getType() == Material.WRITTEN_BOOK) {
final BookMeta bmeta = (BookMeta) item.getItemMeta();
@@ -35,27 +36,34 @@ public class Commandbook extends EssentialsCommand {
final String newAuthor = FormatUtil.formatString(user, "essentials.book.author", getFinalArg(args, 1)).trim();
bmeta.setAuthor(newAuthor);
item.setItemMeta(bmeta);
user.sendMessage(tl("bookAuthorSet", newAuthor));
user.sendTl("bookAuthorSet", newAuthor);
} else {
throw new Exception(tl("denyChangeAuthor"));
throw new TranslatableException("denyChangeAuthor");
}
} else if (args.length > 1 && args[0].equalsIgnoreCase("title")) {
if (user.isAuthorized("essentials.book.title") && (isAuthor(bmeta, player) || user.isAuthorized("essentials.book.others"))) {
final String newTitle = FormatUtil.formatString(user, "essentials.book.title", getFinalArg(args, 1)).trim();
bmeta.setTitle(newTitle);
item.setItemMeta(bmeta);
user.sendMessage(tl("bookTitleSet", newTitle));
user.sendTl("bookTitleSet", newTitle);
} else {
throw new Exception(tl("denyChangeTitle"));
throw new TranslatableException("denyChangeTitle");
}
} else {
if (isAuthor(bmeta, player) || user.isAuthorized("essentials.book.others")) {
final ItemStack newItem = new ItemStack(WRITABLE_BOOK, item.getAmount());
newItem.setItemMeta(bmeta);
InventoryWorkaround.setItemInMainHand(user.getBase(), newItem);
user.sendMessage(tl("editBookContents"));
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_20_6_R01)) {
final WritableBookMeta wbmeta = (WritableBookMeta) newItem.getItemMeta();
//noinspection DataFlowIssue
wbmeta.setPages(bmeta.getPages());
newItem.setItemMeta(wbmeta);
} else {
newItem.setItemMeta(bmeta);
}
Inventories.setItemInMainHand(user.getBase(), newItem);
user.sendTl("editBookContents");
} else {
throw new Exception(tl("denyBookEdit"));
throw new TranslatableException("denyBookEdit");
}
}
} else if (item.getType() == WRITABLE_BOOK) {
@@ -64,11 +72,19 @@ public class Commandbook extends EssentialsCommand {
bmeta.setAuthor(player);
}
final ItemStack newItem = new ItemStack(Material.WRITTEN_BOOK, item.getAmount());
newItem.setItemMeta(bmeta);
InventoryWorkaround.setItemInMainHand(user.getBase(), newItem);
user.sendMessage(tl("bookLocked"));
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_20_6_R01)) {
final BookMeta real = (BookMeta) newItem.getItemMeta();
real.setAuthor(bmeta.getAuthor());
real.setTitle(bmeta.getTitle());
real.setPages(bmeta.getPages());
newItem.setItemMeta(real);
} else {
newItem.setItemMeta(bmeta);
}
Inventories.setItemInMainHand(user.getBase(), newItem);
user.sendTl("bookLocked");
} else {
throw new Exception(tl("holdBook"));
throw new TranslatableException("holdBook");
}
}
@@ -90,7 +106,7 @@ public class Commandbook extends EssentialsCommand {
}
return options;
} else if (args.length == 2 && args[0].equalsIgnoreCase("author") && user.isAuthorized("essentials.book.author")) {
final List<String> options = getPlayers(server, user);
final List<String> options = getPlayers(user);
options.add("Herobrine"); // #EasterEgg
return options;
} else {
@@ -0,0 +1,35 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.Trade;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.LocationUtil;
import net.ess3.provider.WorldInfoProvider;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.event.player.PlayerTeleportEvent;
import java.util.concurrent.CompletableFuture;
public class Commandbottom extends EssentialsCommand {
public Commandbottom() {
super("bottom");
}
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
final int bottomX = user.getLocation().getBlockX();
final int bottomZ = user.getLocation().getBlockZ();
final float pitch = user.getLocation().getPitch();
final float yaw = user.getLocation().getYaw();
final Location unsafe = new Location(user.getWorld(), bottomX, ess.provider(WorldInfoProvider.class).getMinHeight(user.getWorld()), bottomZ, yaw, pitch);
final Location safe = LocationUtil.getSafeDestination(ess, unsafe);
final CompletableFuture<Boolean> future = getNewExceptionFuture(user.getSource(), commandLabel);
future.thenAccept(success -> {
if (success) {
user.sendTl("teleportBottom", safe.getWorld().getName(), safe.getBlockX(), safe.getBlockY(), safe.getBlockZ());
}
});
user.getAsyncTeleport().teleport(safe, new Trade(this.getName(), ess), PlayerTeleportEvent.TeleportCause.COMMAND, future);
}
}
@@ -1,13 +1,12 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import net.ess3.api.TranslatableException;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.block.Block;
import org.bukkit.event.block.BlockBreakEvent;
import static com.earth2me.essentials.I18n.tl;
public class Commandbreak extends EssentialsCommand {
public Commandbreak() {
super("break");
@@ -22,7 +21,7 @@ public class Commandbreak extends EssentialsCommand {
throw new NoChargeException();
}
if (block.getType() == Material.BEDROCK && !user.isAuthorized("essentials.break.bedrock")) {
throw new Exception(tl("noBreakBedrock"));
throw new TranslatableException("noBreakBedrock");
}
//final List<ItemStack> list = (List<ItemStack>)block.getDrops();
//final BlockBreakEvent event = new BlockBreakEvent(block, user.getBase(), list);
@@ -1,11 +1,10 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.adventure.AdventureUtil;
import com.earth2me.essentials.utils.FormatUtil;
import org.bukkit.Server;
import static com.earth2me.essentials.I18n.tl;
public class Commandbroadcast extends EssentialsCommand {
public Commandbroadcast() {
super("broadcast");
@@ -17,6 +16,9 @@ public class Commandbroadcast extends EssentialsCommand {
throw new NotEnoughArgumentsException();
}
ess.broadcastMessage(tl("broadcast", FormatUtil.replaceFormat(getFinalArg(args, 0)).replace("\\n", "\n"), sender.getDisplayName()));
final String message = FormatUtil.replaceFormat(getFinalArg(args, 0)).replace("\\n", "\n");
ess.broadcastTl("broadcast",
AdventureUtil.parsed(ess.getAdventureFacet().legacyToMiniWithUrls(ess.getAdventureFacet().escapeTags(message))),
sender.getDisplayName());
}
}
@@ -2,21 +2,16 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.User;
import com.earth2me.essentials.textreader.IText;
import com.earth2me.essentials.textreader.KeywordReplacer;
import com.earth2me.essentials.textreader.SimpleTextInput;
import com.earth2me.essentials.adventure.AdventureUtil;
import com.earth2me.essentials.utils.FormatUtil;
import com.google.common.collect.Lists;
import net.ess3.api.TranslatableException;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandbroadcastworld extends EssentialsCommand {
public Commandbroadcastworld() {
@@ -25,13 +20,13 @@ public class Commandbroadcastworld extends EssentialsCommand {
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
if (args.length == 0) {
if (args.length < 2) {
throw new NotEnoughArgumentsException();
}
World world = user.getWorld();
String message = getFinalArg(args, 0);
if (args.length > 1 && ess.getSettings().isAllowWorldInBroadcastworld()) {
if (ess.getSettings().isAllowWorldInBroadcastworld()) {
final World argWorld = ess.getWorld(args[0]);
if (argWorld != null) {
world = argWorld;
@@ -45,12 +40,12 @@ public class Commandbroadcastworld extends EssentialsCommand {
@Override
public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length < 2) {
throw new NotEnoughArgumentsException("world");
throw new NotEnoughArgumentsException();
}
final World world = ess.getWorld(args[0]);
if (world == null) {
throw new Exception(tl("invalidWorld"));
throw new TranslatableException("invalidWorld");
}
sendBroadcast(world, sender.getSender().getName(), getFinalArg(args, 1));
}
@@ -59,22 +54,11 @@ public class Commandbroadcastworld extends EssentialsCommand {
if (message.isEmpty()) {
throw new NotEnoughArgumentsException();
}
sendToWorld(world, tl("broadcast", FormatUtil.replaceFormat(message).replace("\\n", "\n"), name));
}
private void sendToWorld(final World world, final String message) {
IText broadcast = new SimpleTextInput(message);
final Collection<Player> players = ess.getOnlinePlayers();
for (final Player player : players) {
if (player.getWorld().equals(world)) {
final User user = ess.getUser(player);
broadcast = new KeywordReplacer(broadcast, new CommandSource(player), ess, false);
for (final String messageText : broadcast.getLines()) {
user.sendMessage(messageText);
}
}
}
final String formatted = FormatUtil.replaceFormat(message).replace("\\n", "\n");
ess.broadcastTl(null, u -> !u.getBase().getWorld().equals(world), true, "broadcast",
AdventureUtil.parsed(
ess.getAdventureFacet().legacyToMiniWithUrls(ess.getAdventureFacet().escapeTags(formatted))),
AdventureUtil.parsed(ess.getAdventureFacet().legacyToMini(name)));
}
@Override
@@ -7,8 +7,6 @@ import org.bukkit.Server;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandburn extends EssentialsCommand {
public Commandburn() {
super("burn");
@@ -21,14 +19,18 @@ public class Commandburn extends EssentialsCommand {
}
final User user = getPlayer(server, sender, args, 0);
user.getBase().setFireTicks(Integer.parseInt(args[1]) * 20);
sender.sendMessage(tl("burnMsg", user.getDisplayName(), Integer.parseInt(args[1])));
final int seconds = Integer.parseInt(args[1]);
final int fireTicks = (int) Math.max(0L, Math.min((long) seconds * 20, Integer.MAX_VALUE));
user.getBase().setFireTicks(fireTicks);
sender.sendTl("burnMsg", user.getDisplayName(), Math.max(0, seconds));
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1) {
return getPlayers(server, sender);
return getPlayers(sender);
} else if (args.length == 2) {
return COMMON_DURATIONS;
} else {
@@ -1,10 +1,9 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import net.ess3.provider.ContainerProvider;
import org.bukkit.Server;
import static com.earth2me.essentials.I18n.tl;
public class Commandcartographytable extends EssentialsCommand {
public Commandcartographytable() {
@@ -13,11 +12,13 @@ public class Commandcartographytable extends EssentialsCommand {
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
if (ess.getContainerProvider() == null) {
user.sendMessage(tl("unsupportedBrand"));
final ContainerProvider containerProvider = ess.provider(ContainerProvider.class);
if (containerProvider == null) {
user.sendTl("unsupportedBrand");
return;
}
ess.getContainerProvider().openCartographyTable(user.getBase());
containerProvider.openCartographyTable(user.getBase());
}
}
@@ -2,10 +2,11 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.User;
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.StringUtil;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.api.TranslatableException;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.entity.Player;
@@ -19,11 +20,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import static com.earth2me.essentials.I18n.tl;
public class Commandclearinventory extends EssentialsCommand {
private static final int BASE_AMOUNT = 100000;
private static final int EXTENDED_CAP = 8;
public Commandclearinventory() {
@@ -57,7 +54,7 @@ public class Commandclearinventory extends EssentialsCommand {
}
if (allowAll && args.length > 0 && args[0].contentEquals("*")) {
sender.sendMessage(tl("inventoryClearingFromAll"));
sender.sendTl("inventoryClearingFromAll");
offset = 1;
players = ess.getOnlinePlayers();
} else if (allowOthers && args.length > 0 && args[0].trim().length() > 2) {
@@ -74,7 +71,7 @@ public class Commandclearinventory extends EssentialsCommand {
if (senderUser != null && senderUser.isPromptingClearConfirm()) {
if (!formattedCommand.equals(previousClearCommand)) {
senderUser.setConfirmingClearCommand(formattedCommand);
senderUser.sendMessage(tl("confirmClear", formattedCommand));
senderUser.sendTl("confirmClear", formattedCommand);
return;
}
}
@@ -84,7 +81,7 @@ public class Commandclearinventory extends EssentialsCommand {
}
}
protected void clearHandler(final CommandSource sender, final Player player, final String[] args, final int offset, final boolean showExtended) {
protected void clearHandler(final CommandSource sender, final Player player, final String[] args, final int offset, final boolean showExtended) throws TranslatableException {
ClearHandlerType type = ClearHandlerType.ALL_EXCEPT_ARMOR;
final Set<Item> items = new HashSet<>();
int amount = -1;
@@ -114,41 +111,37 @@ public class Commandclearinventory extends EssentialsCommand {
}
}
if (type == ClearHandlerType.ALL_EXCEPT_ARMOR) {
if (type != ClearHandlerType.SPECIFIC_ITEM) {
final boolean armor = type == ClearHandlerType.ALL_INCLUDING_ARMOR;
if (showExtended) {
sender.sendMessage(tl("inventoryClearingAllItems", player.getDisplayName()));
sender.sendTl(armor ? "inventoryClearingAllArmor" : "inventoryClearingAllItems", player.getDisplayName());
}
InventoryWorkaround.clearInventoryNoArmor(player.getInventory());
InventoryWorkaround.setItemInOffHand(player, null);
} else if (type == ClearHandlerType.ALL_INCLUDING_ARMOR) {
if (showExtended) {
sender.sendMessage(tl("inventoryClearingAllArmor", player.getDisplayName()));
}
InventoryWorkaround.clearInventoryNoArmor(player.getInventory());
InventoryWorkaround.setItemInOffHand(player, null);
player.getInventory().setArmorContents(null);
Inventories.removeItems(player, item -> true, armor);
} else {
for (final Item item : items) {
final ItemStack stack = new ItemStack(item.getMaterial());
if (VersionUtil.PRE_FLATTENING) {
//noinspection deprecation
stack.setDurability(item.getData());
}
// can't remove a negative amount of items. (it adds them)
if (amount < -1) {
throw new TranslatableException("cannotRemoveNegativeItems");
}
// amount -1 means all items will be cleared
if (amount == -1) {
stack.setAmount(BASE_AMOUNT);
final ItemStack removedStack = player.getInventory().removeItem(stack).get(0);
final int removedAmount = BASE_AMOUNT - removedStack.getAmount() + InventoryWorkaround.clearItemInOffHand(player, stack);
final int removedAmount = Inventories.removeItemSimilar(player, stack, true);
if (removedAmount > 0 || showExtended) {
sender.sendMessage(tl("inventoryClearingStack", removedAmount, stack.getType().toString().toLowerCase(Locale.ENGLISH), player.getDisplayName()));
sender.sendTl("inventoryClearingStack", removedAmount, stack.getType().toString().toLowerCase(Locale.ENGLISH), player.getDisplayName());
}
} else {
stack.setAmount(amount < 0 ? 1 : amount);
if (player.getInventory().containsAtLeast(stack, amount)) {
sender.sendMessage(tl("inventoryClearingStack", amount, stack.getType().toString().toLowerCase(Locale.ENGLISH), player.getDisplayName()));
player.getInventory().removeItem(stack);
if (Inventories.removeItemAmount(player, stack, amount)) {
sender.sendTl("inventoryClearingStack", amount, stack.getType().toString().toLowerCase(Locale.ENGLISH), player.getDisplayName());
} else {
if (showExtended) {
sender.sendMessage(tl("inventoryClearFail", player.getDisplayName(), amount, stack.getType().toString().toLowerCase(Locale.ENGLISH)));
sender.sendTl("inventoryClearFail", player.getDisplayName(), amount, stack.getType().toString().toLowerCase(Locale.ENGLISH));
}
}
}
@@ -160,7 +153,7 @@ public class Commandclearinventory extends EssentialsCommand {
protected List<String> getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) {
if (user.isAuthorized("essentials.clearinventory.others")) {
if (args.length == 1) {
final List<String> options = getPlayers(server, user);
final List<String> options = getPlayers(user);
if (user.isAuthorized("essentials.clearinventory.all") || user.isAuthorized("essentials.clearinventory.multiple")) {
// Assume that nobody will have the 'all' permission without the 'others' permission
options.add("*");
@@ -189,7 +182,7 @@ public class Commandclearinventory extends EssentialsCommand {
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1) {
final List<String> options = getPlayers(server, sender);
final List<String> options = getPlayers(sender);
options.add("*");
return options;
} else if (args.length == 2) {
@@ -203,7 +196,10 @@ public class Commandclearinventory extends EssentialsCommand {
}
private String formatCommand(final String commandLabel, final String[] args) {
return "/" + commandLabel + " " + StringUtil.joinList(" ", args);
if (args == null || args.length == 0) {
return "/" + commandLabel;
}
return "/" + commandLabel + " " + StringUtil.joinList(" ", (Object[]) args);
}
private enum ClearHandlerType {
@@ -3,8 +3,6 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import org.bukkit.Server;
import static com.earth2me.essentials.I18n.tl;
public class Commandclearinventoryconfirmtoggle extends EssentialsCommand {
public Commandclearinventoryconfirmtoggle() {
@@ -21,9 +19,9 @@ public class Commandclearinventoryconfirmtoggle extends EssentialsCommand {
}
user.setPromptingClearConfirm(confirmingClear);
if (confirmingClear) {
user.sendMessage(tl("clearInventoryConfirmToggleOn"));
user.sendTl("clearInventoryConfirmToggleOn");
} else {
user.sendMessage(tl("clearInventoryConfirmToggleOff"));
user.sendTl("clearInventoryConfirmToggleOff");
}
user.setConfirmingClearCommand(null);
}
@@ -3,8 +3,6 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import org.bukkit.Server;
import static com.earth2me.essentials.I18n.tl;
public class Commandcompass extends EssentialsCommand {
public Commandcompass() {
super("compass");
@@ -15,24 +13,24 @@ public class Commandcompass extends EssentialsCommand {
final int bearing = (int) (user.getLocation().getYaw() + 180 + 360) % 360;
final String dir;
if (bearing < 23) {
dir = tl("north");
dir = user.playerTl("north");
} else if (bearing < 68) {
dir = tl("northEast");
dir = user.playerTl("northEast");
} else if (bearing < 113) {
dir = tl("east");
dir = user.playerTl("east");
} else if (bearing < 158) {
dir = tl("southEast");
dir = user.playerTl("southEast");
} else if (bearing < 203) {
dir = tl("south");
dir = user.playerTl("south");
} else if (bearing < 248) {
dir = tl("southWest");
dir = user.playerTl("southWest");
} else if (bearing < 293) {
dir = tl("west");
dir = user.playerTl("west");
} else if (bearing < 338) {
dir = tl("northWest");
dir = user.playerTl("northWest");
} else {
dir = tl("north");
dir = user.playerTl("north");
}
user.sendMessage(tl("compassBearing", dir, bearing));
user.sendTl("compassBearing", dir, bearing);
}
}
@@ -4,6 +4,7 @@ import com.earth2me.essentials.ChargeException;
import com.earth2me.essentials.Trade;
import com.earth2me.essentials.Trade.OverflowType;
import com.earth2me.essentials.User;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.api.MaxMoneyException;
import org.bukkit.Material;
@@ -22,8 +23,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static com.earth2me.essentials.I18n.tl;
public class Commandcondense extends EssentialsCommand {
private final Map<ItemStack, SimpleRecipe> condenseList = new HashMap<>();
@@ -39,7 +38,7 @@ public class Commandcondense extends EssentialsCommand {
if (args.length > 0) {
is = ess.getItemDb().getMatching(user, args);
} else {
for (final ItemStack stack : user.getBase().getInventory().getContents()) {
for (final ItemStack stack : Inventories.getInventory(user.getBase(), false)) {
if (stack == null || stack.getType() == Material.AIR) {
continue;
}
@@ -57,9 +56,9 @@ public class Commandcondense extends EssentialsCommand {
user.getBase().updateInventory();
if (didConvert) {
user.sendMessage(tl("itemsConverted"));
user.sendTl("itemsConverted");
} else {
user.sendMessage(tl("itemsNotConverted"));
user.sendTl("itemsNotConverted");
throw new NoChargeException();
}
}
@@ -85,7 +84,7 @@ public class Commandcondense extends EssentialsCommand {
int amount = 0;
for (final ItemStack contents : user.getBase().getInventory().getContents()) {
for (final ItemStack contents : Inventories.getInventory(user.getBase(), false)) {
if (contents != null && contents.isSimilar(stack)) {
amount += contents.getAmount();
}
@@ -2,8 +2,11 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.User;
import com.earth2me.essentials.craftbukkit.Inventories;
import com.earth2me.essentials.utils.DateUtil;
import com.earth2me.essentials.utils.PasteUtil;
import net.ess3.api.TranslatableException;
import net.ess3.provider.SerializationProvider;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.inventory.ItemStack;
@@ -20,7 +23,7 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class Commandcreatekit extends EssentialsCommand {
public Commandcreatekit() {
@@ -37,23 +40,25 @@ public class Commandcreatekit extends EssentialsCommand {
// Command handler will auto fail if this fails.
final long delay = Long.parseLong(args[1]);
final String kitname = args[0];
final ItemStack[] items = user.getBase().getInventory().getContents();
final ItemStack[] items = Inventories.getInventory(user.getBase(), true);
final List<String> list = new ArrayList<>();
final SerializationProvider serializationProvider = ess.provider(SerializationProvider.class);
boolean useSerializationProvider = ess.getSettings().isUseBetterKits();
if (useSerializationProvider && ess.getSerializationProvider() == null) {
ess.showError(user.getSource(), new Exception(tl("createKitUnsupported")), commandLabel);
if (useSerializationProvider && serializationProvider == null) {
ess.showError(user.getSource(), new TranslatableException("createKitUnsupported"), commandLabel);
useSerializationProvider = false;
}
for (ItemStack is : items) {
for (int i = 0; i < items.length; i++) {
final ItemStack is = items[i];
if (is != null && is.getType() != null && is.getType() != Material.AIR) {
final String serialized;
if (useSerializationProvider) {
serialized = "@" + Base64Coder.encodeLines(ess.getSerializationProvider().serializeItem(is));
serialized = "slot:" + i + " @" + Base64Coder.encodeLines(serializationProvider.serializeItem(is));
} else {
serialized = ess.getItemDb().serialize(is);
serialized = "slot:" + i + " " + ess.getItemDb().serialize(is);
}
list.add(serialized);
}
@@ -61,7 +66,7 @@ public class Commandcreatekit extends EssentialsCommand {
// Some users might want to directly write to config knowing the consequences. *shrug*
if (!ess.getSettings().isPastebinCreateKit()) {
ess.getKits().addKit(kitname, list, delay);
user.sendMessage(tl("createdKit", kitname, list.size(), delay));
user.sendTl("createdKit", kitname, list.size(), delay);
} else {
uploadPaste(user.getSource(), kitname, delay, list);
}
@@ -85,10 +90,10 @@ public class Commandcreatekit extends EssentialsCommand {
final CompletableFuture<PasteUtil.PasteResult> future = PasteUtil.createPaste(Collections.singletonList(new PasteUtil.PasteFile("kit_" + kitName + ".yml", fileContents)));
future.thenAccept(result -> {
if (result != null) {
final String separator = tl("createKitSeparator");
final String separator = tlLiteral("createKitSeparator");
final String delayFormat = delay <= 0 ? "0" : DateUtil.formatDateDiff(System.currentTimeMillis() + (delay * 1000));
sender.sendMessage(separator);
sender.sendMessage(tl("createKitSuccess", kitName, delayFormat, result.getPasteUrl()));
sender.sendTl("createKitSuccess", kitName, delayFormat, result.getPasteUrl());
sender.sendMessage(separator);
if (ess.getSettings().isDebug()) {
ess.getLogger().info(sender.getSender().getName() + " created a kit: " + result.getPasteUrl());
@@ -96,12 +101,12 @@ public class Commandcreatekit extends EssentialsCommand {
}
});
future.exceptionally(throwable -> {
sender.sendMessage(tl("createKitFailed", kitName));
sender.sendTl("createKitFailed", kitName);
ess.getLogger().log(Level.SEVERE, "Error creating kit: ", throwable);
return null;
});
} catch (Exception e) {
sender.sendMessage(tl("createKitFailed", kitName));
sender.sendTl("createKitFailed", kitName);
ess.getLogger().log(Level.SEVERE, "Error creating kit: ", e);
}
});
@@ -3,6 +3,9 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.IUser;
import com.earth2me.essentials.User;
import net.ess3.api.TranslatableException;
import net.essentialsx.api.v2.events.HomeModifyEvent;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import java.util.ArrayList;
@@ -10,13 +13,29 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import static com.earth2me.essentials.I18n.tl;
public class Commanddelhome extends EssentialsCommand {
public Commanddelhome() {
super("delhome");
}
private void deleteHome(CommandSource sender, User user, String home) {
final HomeModifyEvent event = new HomeModifyEvent(sender.getUser(), user, home, user.getHome(home), false);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
if (ess.getSettings().isDebug()) {
ess.getLogger().info("HomeModifyEvent canceled for /delhome execution by " + sender.getDisplayName());
}
return;
}
try {
user.delHome(home);
sender.sendTl("deleteHome", home);
} catch (Exception e) {
sender.sendTl("invalidHome", home);
}
}
@Override
public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length < 1) {
@@ -37,31 +56,38 @@ public class Commanddelhome extends EssentialsCommand {
if (expandedArg.length > 1 && (user == null || user.isAuthorized("essentials.delhome.others"))) {
user = getPlayer(server, expandedArg, 0, true, true);
name = expandedArg[1];
name = expandedArg[1].toLowerCase(Locale.ENGLISH);
} else if (user == null) {
throw new NotEnoughArgumentsException();
} else {
name = expandedArg[0];
name = expandedArg[0].toLowerCase(Locale.ENGLISH);
}
if (name.equalsIgnoreCase("bed")) {
throw new Exception(tl("invalidHomeName"));
switch (name) {
case "bed":
throw new TranslatableException("invalidHomeName");
case "*":
final List<String> homes = user.getHomes();
for (String home : homes) {
deleteHome(sender, user, home);
}
break;
default:
deleteHome(sender, user, name);
break;
}
user.delHome(name.toLowerCase(Locale.ENGLISH));
sender.sendMessage(tl("deleteHome", name));
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
final IUser user = sender.getUser(ess);
final boolean canDelOthers = sender.isAuthorized("essentials.delhome.others", ess);
final IUser user = sender.getUser();
final boolean canDelOthers = sender.isAuthorized("essentials.delhome.others");
if (args.length == 1) {
final List<String> homes = user == null ? new ArrayList<>() : user.getHomes();
if (canDelOthers) {
final int sepIndex = args[0].indexOf(':');
if (sepIndex < 0) {
getPlayers(server, sender).forEach(player -> homes.add(player + ":"));
getPlayers(sender).forEach(player -> homes.add(player + ":"));
} else {
final String namePart = args[0].substring(0, sepIndex);
final User otherUser;
@@ -71,6 +97,7 @@ public class Commanddelhome extends EssentialsCommand {
return homes;
}
otherUser.getHomes().forEach(home -> homes.add(namePart + ":" + home));
homes.add(namePart + ":" + "*");
}
}
return homes;
@@ -1,14 +1,13 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import net.ess3.api.TranslatableException;
import org.bukkit.Server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commanddeljail extends EssentialsCommand {
public Commanddeljail() {
super("deljail");
@@ -21,11 +20,11 @@ public class Commanddeljail extends EssentialsCommand {
}
if (ess.getJails().getJail(args[0]) == null) {
throw new Exception(tl("jailNotExist"));
throw new TranslatableException("jailNotExist");
}
ess.getJails().removeJail(args[0]);
sender.sendMessage(tl("deleteJail", args[0]));
sender.sendTl("deleteJail", args[0]);
}
@Override
@@ -8,8 +8,6 @@ import org.bukkit.Server;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commanddelkit extends EssentialsCommand {
public Commanddelkit() {
super("delkit");
@@ -19,7 +17,7 @@ public class Commanddelkit extends EssentialsCommand {
public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length == 0) {
final String kitList = ess.getKits().listKits(ess, null);
sender.sendMessage(kitList.length() > 0 ? tl("kits", kitList) : tl("noKits"));
sender.sendTl(kitList.length() > 0 ? "kits" : "noKits", kitList);
throw new NoChargeException();
} else {
final String kitName = ess.getKits().matchKit(args[0]);
@@ -30,7 +28,7 @@ public class Commanddelkit extends EssentialsCommand {
}
ess.getKits().removeKit(kitName);
sender.sendMessage(tl("deleteKit", kitName));
sender.sendTl("deleteKit", kitName);
}
}
@@ -1,6 +1,7 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import net.ess3.api.TranslatableException;
import net.essentialsx.api.v2.events.WarpModifyEvent;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@@ -10,8 +11,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commanddelwarp extends EssentialsCommand {
public Commanddelwarp() {
super("delwarp");
@@ -31,15 +30,15 @@ public class Commanddelwarp extends EssentialsCommand {
// World is unloaded/deleted
location = null;
}
final WarpModifyEvent event = new WarpModifyEvent(sender.getUser(this.ess), args[0], location, null, WarpModifyEvent.WarpModifyCause.DELETE);
final WarpModifyEvent event = new WarpModifyEvent(sender.getUser(), args[0], location, null, WarpModifyEvent.WarpModifyCause.DELETE);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
ess.getWarps().removeWarp(args[0]);
sender.sendMessage(tl("deleteWarp", args[0]));
sender.sendTl("deleteWarp", args[0]);
} else {
throw new Exception(tl("warpNotExist"));
throw new TranslatableException("warpNotExist");
}
}
@@ -3,8 +3,6 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import org.bukkit.Server;
import static com.earth2me.essentials.I18n.tl;
public class Commanddepth extends EssentialsCommand {
public Commanddepth() {
super("depth");
@@ -14,11 +12,11 @@ public class Commanddepth extends EssentialsCommand {
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
final int depth = user.getLocation().getBlockY() - user.getWorld().getSeaLevel();
if (depth > 0) {
user.sendMessage(tl("depthAboveSea", depth));
user.sendTl("depthAboveSea", depth);
} else if (depth < 0) {
user.sendMessage(tl("depthBelowSea", -depth));
user.sendTl("depthBelowSea", -depth);
} else {
user.sendMessage(tl("depth"));
user.sendTl("depth");
}
}
}
@@ -3,8 +3,6 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import org.bukkit.Server;
import static com.earth2me.essentials.I18n.tl;
public class Commanddisposal extends EssentialsCommand {
public Commanddisposal() {
@@ -13,8 +11,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.sendMessage(tl("openingDisposal"));
user.getBase().openInventory(ess.getServer().createInventory(user.getBase(), 36, tl("disposal")));
user.sendTl("openingDisposal");
user.getBase().openInventory(ess.getServer().createInventory(user.getBase(), 36, user.playerTl("disposal")));
}
}
@@ -3,10 +3,13 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.ChargeException;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.User;
import com.earth2me.essentials.adventure.AdventureUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.google.common.collect.Lists;
import net.ess3.api.MaxMoneyException;
import net.ess3.api.TranslatableException;
import net.ess3.api.events.UserBalanceUpdateEvent;
import net.ess3.provider.PlayerLocaleProvider;
import org.bukkit.Server;
import java.math.BigDecimal;
@@ -14,8 +17,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import static com.earth2me.essentials.I18n.tl;
public class Commandeco extends EssentialsLoopCommand {
public Commandeco() {
@@ -34,7 +35,14 @@ public class Commandeco extends EssentialsLoopCommand {
try {
cmd = EcoCommands.valueOf(args[0].toUpperCase(Locale.ENGLISH));
isPercent = cmd != EcoCommands.RESET && args[2].endsWith("%");
amount = (cmd == EcoCommands.RESET) ? ess.getSettings().getStartingBalance() : new BigDecimal(args[2].replaceAll("[^0-9\\.]", ""));
if (cmd == EcoCommands.RESET) {
amount = ess.getSettings().getStartingBalance();
} else if (sender.isPlayer() && ess.getSettings().isPerPlayerLocale()) {
final String playerLocale = ess.provider(PlayerLocaleProvider.class).getLocale(sender.getPlayer());
amount = NumberUtil.parseStringToBDecimal(args[2], ess.getUser(sender.getPlayer()).getPlayerLocale(playerLocale));
} else {
amount = NumberUtil.parseStringToBDecimal(args[2]);
}
} catch (final Exception ex) {
throw new NotEnoughArgumentsException(ex);
}
@@ -54,7 +62,7 @@ public class Commandeco extends EssentialsLoopCommand {
if (player.getMoney().subtract(userAmount).compareTo(ess.getSettings().getMinMoney()) >= 0) {
player.takeMoney(userAmount, sender, UserBalanceUpdateEvent.Cause.COMMAND_ECO);
} else {
ess.showError(sender, new Exception(tl("minimumBalanceError", NumberUtil.displayCurrency(ess.getSettings().getMinMoney(), ess))), commandLabel);
ess.showError(sender, new TranslatableException("minimumBalanceError", AdventureUtil.parsed(NumberUtil.displayCurrency(ess.getSettings().getMinMoney(), ess))), commandLabel);
}
break;
}
@@ -65,8 +73,8 @@ public class Commandeco extends EssentialsLoopCommand {
final boolean underMin = userAmount.compareTo(minBal) < 0;
final boolean aboveMax = userAmount.compareTo(maxBal) > 0;
player.setMoney(underMin ? minBal : aboveMax ? maxBal : userAmount, UserBalanceUpdateEvent.Cause.COMMAND_ECO);
player.sendMessage(tl("setBal", NumberUtil.displayCurrency(player.getMoney(), ess)));
sender.sendMessage(tl("setBalOthers", player.getDisplayName(), NumberUtil.displayCurrency(player.getMoney(), ess)));
player.sendTl("setBal", AdventureUtil.parsed(NumberUtil.displayCurrency(player.getMoney(), ess)));
sender.sendTl("setBalOthers", player.getDisplayName(), AdventureUtil.parsed(NumberUtil.displayCurrency(player.getMoney(), ess)));
break;
}
}
@@ -87,7 +95,7 @@ public class Commandeco extends EssentialsLoopCommand {
}
return options;
} else if (args.length == 2) {
return getPlayers(server, sender);
return getPlayers(sender);
} else if (args.length == 3 && !args[0].equalsIgnoreCase(EcoCommands.RESET.name())) {
if (args[0].equalsIgnoreCase(EcoCommands.SET.name())) {
return Lists.newArrayList("0", ess.getSettings().getStartingBalance().toString());
@@ -3,17 +3,26 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.VersionUtil;
import com.google.common.collect.Lists;
import net.ess3.api.TranslatableException;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Server;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.HangingSign;
import org.bukkit.block.data.type.WallHangingSign;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.block.sign.Side;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.util.Vector;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandeditsign extends EssentialsCommand {
public Commandeditsign() {
super("editsign");
@@ -27,67 +36,113 @@ public class Commandeditsign extends EssentialsCommand {
final Block target = user.getTargetBlock(5); //5 is a good number
if (!(target.getState() instanceof Sign)) {
throw new Exception(tl("editsignCommandTarget"));
throw new TranslatableException("editsignCommandTarget");
}
final Sign sign = (Sign) target.getState();
final ModifiableSign sign = wrapSign((Sign) target.getState(), user);
try {
if (args[0].equalsIgnoreCase("set") && args.length > 2) {
final String[] existingLines = sign.getLines();
final int line = Integer.parseInt(args[1]) - 1;
final String text = FormatUtil.formatString(user, "essentials.editsign", getFinalArg(args, 2)).trim();
if (ChatColor.stripColor(text).length() > 15 && !user.isAuthorized("essentials.editsign.unlimited")) {
throw new Exception(tl("editsignCommandLimit"));
throw new TranslatableException("editsignCommandLimit");
}
sign.setLine(line, text);
sign.update();
user.sendMessage(tl("editsignCommandSetSuccess", line + 1, text));
existingLines[line] = text;
if (callSignEvent(sign, user, existingLines)) {
return;
}
user.sendTl("editsignCommandSetSuccess", line + 1, text);
} else if (args[0].equalsIgnoreCase("clear")) {
if (args.length == 1) {
final String[] existingLines = sign.getLines();
for (int i = 0; i < 4; i++) { // A whole one line of line savings!
sign.setLine(i, "");
existingLines[i] = "";
}
sign.update();
user.sendMessage(tl("editsignCommandClear"));
if (callSignEvent(sign, user, existingLines)) {
return;
}
user.sendTl("editsignCommandClear");
} else {
final String[] existingLines = sign.getLines();
final int line = Integer.parseInt(args[1]) - 1;
sign.setLine(line, "");
sign.update();
user.sendMessage(tl("editsignCommandClearLine", line + 1));
existingLines[line] = "";
if (callSignEvent(sign, user, existingLines)) {
return;
}
user.sendTl("editsignCommandClearLine", line + 1);
}
} else if (args[0].equalsIgnoreCase("copy") || args[0].equalsIgnoreCase("paste")) {
final boolean copy = args[0].equalsIgnoreCase("copy");
final String tlPrefix = copy ? "editsignCopy" : "editsignPaste";
} else if (args[0].equalsIgnoreCase("copy")) {
final int line = args.length == 1 ? -1 : Integer.parseInt(args[1]) - 1;
if (line == -1) {
for (int i = 0; i < 4; i++) {
processSignCopyPaste(user, sign, i, copy);
// We use unformat here to prevent players from copying signs with colors that they do not have permission to use.
user.getSignCopy().set(i, FormatUtil.unformatString(user, "essentials.editsign", sign.getLine(i)));
}
user.sendMessage(tl(tlPrefix, commandLabel));
user.sendTl("editsignCopy", commandLabel);
} else {
processSignCopyPaste(user, sign, line, copy);
user.sendMessage(tl(tlPrefix + "Line", line + 1, commandLabel));
// We use unformat here to prevent players from copying signs with colors that they do not have permission to use.
user.getSignCopy().set(line, FormatUtil.unformatString(user, "essentials.editsign", sign.getLine(line)));
user.sendTl("editsignCopyLine", line + 1, commandLabel);
}
if (!copy) {
sign.update();
} else if (args[0].equalsIgnoreCase("paste")) {
final int line = args.length == 1 ? -1 : Integer.parseInt(args[1]) - 1;
final String[] existingLines = sign.getLines();
if (line == -1) {
for (int i = 0; i < 4; i++) {
existingLines[i] = FormatUtil.formatString(user, "essentials.editsign", user.getSignCopy().get(i));
}
if (callSignEvent(sign, user, existingLines)) {
return;
}
user.sendTl("editsignPaste", commandLabel);
} else {
existingLines[line] = FormatUtil.formatString(user, "essentials.editsign", user.getSignCopy().get(line));
if (callSignEvent(sign, user, existingLines)) {
return;
}
user.sendTl("editsignPasteLine", line + 1, commandLabel);
}
} else {
throw new NotEnoughArgumentsException();
}
} catch (final IndexOutOfBoundsException e) {
throw new Exception(tl("editsignCommandNoLine"), e);
throw new TranslatableException(e, "editsignCommandNoLine");
}
}
private void processSignCopyPaste(final User user, final Sign sign, final int index, final boolean copy) {
if (copy) {
// We use unformat here to prevent players from copying signs with colors that they do not have permission to use.
user.getSignCopy().set(index, FormatUtil.unformatString(user, "essentials.editsign", sign.getLine(index)));
return;
private boolean callSignEvent(final ModifiableSign sign, final User user, final String[] lines) {
final SignChangeEvent event;
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_20_1_R01)) {
if (sign.isWaxed() && !user.isAuthorized("essentials.editsign.waxed.exempt")) {
return true;
}
event = new SignChangeEvent(sign.getBlock(), user.getBase(), lines, sign.isFront() ? Side.FRONT : Side.BACK);
} else {
//noinspection deprecation
event = new SignChangeEvent(sign.getBlock(), user.getBase(), lines);
}
final String line = FormatUtil.formatString(user, "essentials.editsign", user.getSignCopy().get(index));
sign.setLine(index, line == null ? "" : line);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
if (ess.getSettings().isDebug()) {
ess.getLogger().info("SignChangeEvent canceled for /editsign execution by " + user.getName());
}
return true;
}
for (int i = 0; i < 4; i++) {
sign.setLine(i, lines[i]);
}
sign.update();
return false;
}
@Override
@@ -100,7 +155,7 @@ public class Commandeditsign extends EssentialsCommand {
final int line = Integer.parseInt(args[1]);
final Block target = user.getTargetBlock(5);
if (target.getState() instanceof Sign && line <= 4) {
final Sign sign = (Sign) target.getState();
final ModifiableSign sign = wrapSign((Sign) target.getState(), user);
return Lists.newArrayList(FormatUtil.unformatString(user, "essentials.editsign", sign.getLine(line - 1)));
}
return Collections.emptyList();
@@ -108,4 +163,105 @@ public class Commandeditsign extends EssentialsCommand {
return Collections.emptyList();
}
}
private ModifiableSign wrapSign(final Sign sign, final User user) {
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_20_1_R01)) {
final Vector eyeLocLessSign = user.getBase().getEyeLocation().toVector().subtract(sign.getLocation().add(.5, .5, .5).toVector());
final BlockData signBlockData = sign.getBlockData();
final BlockFace signDirection;
if (signBlockData instanceof org.bukkit.block.data.type.Sign) {
signDirection = ((org.bukkit.block.data.type.Sign) signBlockData).getRotation();
} else if (signBlockData instanceof WallSign) {
signDirection = ((WallSign) signBlockData).getFacing();
} else if (signBlockData instanceof HangingSign) {
signDirection = ((HangingSign) signBlockData).getRotation();
} else if (signBlockData instanceof WallHangingSign) {
signDirection = ((WallHangingSign) signBlockData).getFacing();
} else {
throw new IllegalStateException("Unknown block data for sign: " + signBlockData.getClass());
}
final Side side = eyeLocLessSign.dot(signDirection.getDirection()) > 0 ? Side.FRONT : Side.BACK;
return new ModifiableSign(sign) {
@Override
String[] getLines() {
return sign.getSide(side).getLines();
}
@Override
String getLine(int line) {
return sign.getSide(side).getLine(line);
}
@Override
void setLine(int line, String text) {
sign.getSide(side).setLine(line, text);
}
@Override
boolean isFront() {
return side == Side.FRONT;
}
@Override
boolean isWaxed() {
return sign.isWaxed();
}
};
}
return new ModifiableSign(sign) {
@Override
String[] getLines() {
return sign.getLines();
}
@Override
String getLine(int line) {
return sign.getLine(line);
}
@Override
void setLine(int line, String text) {
sign.setLine(line, text);
}
@Override
boolean isFront() {
return true;
}
@Override
boolean isWaxed() {
return false;
}
};
}
private abstract static class ModifiableSign {
protected final Sign sign;
protected ModifiableSign(final Sign sign) {
this.sign = sign;
}
abstract String getLine(int line);
abstract String[] getLines();
abstract void setLine(int line, String text);
abstract boolean isFront();
abstract boolean isWaxed();
Block getBlock() {
return sign.getBlock();
}
void update() {
sign.update();
}
}
}
@@ -3,9 +3,9 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.Enchantments;
import com.earth2me.essentials.MetaItemStack;
import com.earth2me.essentials.User;
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
import com.earth2me.essentials.utils.StringUtil;
import com.google.common.collect.Lists;
import net.ess3.api.TranslatableException;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.enchantments.Enchantment;
@@ -14,13 +14,10 @@ import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import static com.earth2me.essentials.I18n.tl;
public class Commandenchant extends EssentialsCommand {
public Commandenchant() {
super("enchant");
@@ -31,18 +28,18 @@ public class Commandenchant extends EssentialsCommand {
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
final ItemStack stack = user.getItemInHand();
if (stack == null || stack.getType() == Material.AIR) {
throw new Exception(tl("nothingInHand"));
throw new TranslatableException("nothingInHand");
}
if (args.length == 0) {
final Set<String> usableEnchants = new TreeSet<>();
for (final Map.Entry<String, Enchantment> entry : Enchantments.entrySet()) {
final String name = entry.getValue().getName().toLowerCase(Locale.ENGLISH);
final String name = Enchantments.getRealName(entry.getValue());
if (usableEnchants.contains(name) || (user.isAuthorized("essentials.enchantments." + name) && entry.getValue().canEnchantItem(stack))) {
usableEnchants.add(entry.getKey());
}
}
throw new NotEnoughArgumentsException(tl("enchantments", StringUtil.joinList(usableEnchants.toArray())));
throw new NotEnoughArgumentsException(user.playerTl("enchantments", StringUtil.joinList(usableEnchants.toArray())));
}
int level = 1;
@@ -57,13 +54,13 @@ public class Commandenchant extends EssentialsCommand {
final MetaItemStack metaStack = new MetaItemStack(stack);
final Enchantment enchantment = metaStack.getEnchantment(user, args[0]);
metaStack.addEnchantment(user.getSource(), ess.getSettings().allowUnsafeEnchantments() && user.isAuthorized("essentials.enchantments.allowunsafe"), enchantment, level);
InventoryWorkaround.setItemInMainHand(user.getBase(), metaStack.getItemStack());
stack.setItemMeta(metaStack.getItemStack().getItemMeta());
user.getBase().updateInventory();
final String enchantName = enchantment.getName().toLowerCase(Locale.ENGLISH).replace('_', ' ');
final String enchantName = Enchantments.getRealName(enchantment).replace('_', ' ');
if (level == 0) {
user.sendMessage(tl("enchantmentRemoved", enchantName));
user.sendTl("enchantmentRemoved", enchantName);
} else {
user.sendMessage(tl("enchantmentApplied", enchantName));
user.sendTl("enchantmentApplied", enchantName);
}
}
@@ -26,7 +26,7 @@ public class Commandenderchest extends EssentialsCommand {
@Override
protected List<String> getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) {
if (args.length == 1 && user.isAuthorized("essentials.enderchest.others")) {
return getPlayers(server, user);
return getPlayers(user);
} else {
return Collections.emptyList();
}
@@ -1,860 +1,31 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.EssentialsUpgrade;
import com.earth2me.essentials.User;
import com.earth2me.essentials.UserMap;
import com.earth2me.essentials.economy.EconomyLayer;
import com.earth2me.essentials.economy.EconomyLayers;
import com.earth2me.essentials.utils.CommandMapUtil;
import com.earth2me.essentials.utils.DateUtil;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.FloatUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.PasteUtil;
import com.earth2me.essentials.utils.VersionUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gson.JsonArray;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static com.earth2me.essentials.I18n.tl;
import com.earth2me.essentials.commands.essentials.CleanupCommand;
import com.earth2me.essentials.commands.essentials.CommandMapCommand;
import com.earth2me.essentials.commands.essentials.DebugCommand;
import com.earth2me.essentials.commands.essentials.DumpCommand;
import com.earth2me.essentials.commands.essentials.HomesCommand;
import com.earth2me.essentials.commands.essentials.ItemTestCommand;
import com.earth2me.essentials.commands.essentials.MooCommand;
import com.earth2me.essentials.commands.essentials.NyanCommand;
import com.earth2me.essentials.commands.essentials.ReloadCommand;
import com.earth2me.essentials.commands.essentials.UsermapCommand;
import com.earth2me.essentials.commands.essentials.VersionCommand;
// This command has 4 undocumented behaviours #EasterEgg
public class Commandessentials extends EssentialsCommand {
private static final Sound NOTE_HARP = EnumUtil.valueOf(Sound.class, "BLOCK_NOTE_BLOCK_HARP", "BLOCK_NOTE_HARP", "NOTE_PIANO");
private static final Sound MOO_SOUND = EnumUtil.valueOf(Sound.class, "COW_IDLE", "ENTITY_COW_MILK");
private static final String HOMES_USAGE = "/<command> homes (fix | delete [world])";
private static final String NYAN_TUNE = "1D#,1E,2F#,,2A#,1E,1D#,1E,2F#,2B,2D#,2E,2D#,2A#,2B,,2F#,,1D#,1E,2F#,2B,2C#,2A#,2B,2C#,2E,2D#,2E,2C#,,2F#,,2G#,,1D,1D#,,1C#,1D,1C#,1B,,1B,,1C#,,1D,,1D,1C#,1B,1C#,1D#,2F#,2G#,1D#,2F#,1C#,1D#,1B,1C#,1B,1D#,,2F#,,2G#,1D#,2F#,1C#,1D#,1B,1D,1D#,1D,1C#,1B,1C#,1D,,1B,1C#,1D#,2F#,1C#,1D,1C#,1B,1C#,,1B,,1C#,,2F#,,2G#,,1D,1D#,,1C#,1D,1C#,1B,,1B,,1C#,,1D,,1D,1C#,1B,1C#,1D#,2F#,2G#,1D#,2F#,1C#,1D#,1B,1C#,1B,1D#,,2F#,,2G#,1D#,2F#,1C#,1D#,1B,1D,1D#,1D,1C#,1B,1C#,1D,,1B,1C#,1D#,2F#,1C#,1D,1C#,1B,1C#,,1B,,1B,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1B,,";
private static final String[] CONSOLE_MOO = new String[] {" (__)", " (oo)", " /------\\/", " / | ||", " * /\\---/\\", " ~~ ~~", "....\"Have you mooed today?\"..."};
private static final String[] PLAYER_MOO = new String[] {" (__)", " (oo)", " /------\\/", " / | | |", " * /\\---/\\", " ~~ ~~", "....\"Have you mooed today?\"..."};
private static final List<String> versionPlugins = Arrays.asList(
"Vault", // API
"Reserve", // API
"PlaceholderAPI", // API
"CMI", // potential for issues
"Towny", // past issues; admins should ensure latest
"ChestShop", // past issues; admins should ensure latest
"Citizens", // fires player events
"LuckPerms", // permissions (recommended)
"UltraPermissions",
"PermissionsEx", // permissions (unsupported)
"GroupManager", // permissions (unsupported)
"bPermissions", // permissions (unsupported)
"DiscordSRV", // potential for issues if EssentialsXDiscord is installed
// Chat signing bypass plugins that can potentially break EssentialsChat
"AntiPopup",
"NoChatReports",
"NoEncryption"
);
private static final List<String> officialPlugins = Arrays.asList(
"EssentialsAntiBuild",
"EssentialsChat",
"EssentialsDiscord",
"EssentialsGeoIP",
"EssentialsProtect",
"EssentialsSpawn",
"EssentialsXMPP"
);
private static final List<String> warnPlugins = Arrays.asList(
"PermissionsEx",
"GroupManager",
"bPermissions",
// Brain-dead chat signing bypass that break EssentialsChat
"NoChatReports",
"NoEncryption"
);
private transient TuneRunnable currentTune = null;
public class Commandessentials extends EssentialsTreeCommand {
public Commandessentials() {
super("essentials");
}
@Override
public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length == 0) {
showUsage(sender);
}
switch (args[0]) {
// Info commands
case "debug":
case "verbose":
runDebug(server, sender, commandLabel, args);
break;
case "ver":
case "version":
runVersion(server, sender, commandLabel, args);
break;
case "cmd":
case "commands":
runCommands(server, sender, commandLabel, args);
break;
case "dump":
runDump(server, sender, commandLabel, args);
break;
// Data commands
case "reload":
runReload(server, sender, commandLabel, args);
break;
case "reset":
runReset(server, sender, commandLabel, args);
break;
case "cleanup":
runCleanup(server, sender, commandLabel, args);
break;
case "homes":
runHomes(server, sender, commandLabel, args);
break;
case "uuidconvert":
runUUIDConvert(server, sender, commandLabel, args);
break;
case "uuidtest":
runUUIDTest(server, sender, commandLabel, args);
break;
// "#EasterEgg"
case "nya":
case "nyan":
runNya(server, sender, commandLabel, args);
break;
case "moo":
runMoo(server, sender, commandLabel, args);
break;
default:
showUsage(sender);
break;
}
}
// Displays the command's usage.
private void showUsage(final CommandSource sender) throws Exception {
throw new NotEnoughArgumentsException();
}
// Lists commands that are being handed over to other plugins.
private void runCommands(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (ess.getAlternativeCommandsHandler().disabledCommands().size() == 0) {
sender.sendMessage(tl("blockListEmpty"));
return;
}
sender.sendMessage(tl("blockList"));
for (final Map.Entry<String, String> entry : ess.getAlternativeCommandsHandler().disabledCommands().entrySet()) {
sender.sendMessage(entry.getKey() + " => " + entry.getValue());
}
}
// Generates a paste of useful information
private void runDump(Server server, CommandSource sender, String commandLabel, String[] args) {
sender.sendMessage(tl("dumpCreating"));
final JsonObject dump = new JsonObject();
final JsonObject meta = new JsonObject();
meta.addProperty("timestamp", Instant.now().toEpochMilli());
meta.addProperty("sender", sender.getPlayer() != null ? sender.getPlayer().getName() : null);
meta.addProperty("senderUuid", sender.getPlayer() != null ? sender.getPlayer().getUniqueId().toString() : null);
dump.add("meta", meta);
final JsonObject serverData = new JsonObject();
serverData.addProperty("bukkit-version", Bukkit.getBukkitVersion());
serverData.addProperty("server-version", Bukkit.getVersion());
serverData.addProperty("server-brand", Bukkit.getName());
serverData.addProperty("online-mode", ess.getOnlineModeProvider().getOnlineModeString());
final JsonObject supportStatus = new JsonObject();
final VersionUtil.SupportStatus status = VersionUtil.getServerSupportStatus();
supportStatus.addProperty("status", status.name());
supportStatus.addProperty("supported", status.isSupported());
supportStatus.addProperty("trigger", VersionUtil.getSupportStatusClass());
serverData.add("support-status", supportStatus);
dump.add("server-data", serverData);
final JsonObject environment = new JsonObject();
environment.addProperty("java-version", System.getProperty("java.version"));
environment.addProperty("operating-system", System.getProperty("os.name"));
environment.addProperty("uptime", DateUtil.formatDateDiff(ManagementFactory.getRuntimeMXBean().getStartTime()));
environment.addProperty("allocated-memory", (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");
dump.add("environment", environment);
final JsonObject essData = new JsonObject();
essData.addProperty("version", ess.getDescription().getVersion());
final JsonObject updateData = new JsonObject();
updateData.addProperty("id", ess.getUpdateChecker().getVersionIdentifier());
updateData.addProperty("branch", ess.getUpdateChecker().getVersionBranch());
updateData.addProperty("dev", ess.getUpdateChecker().isDevBuild());
essData.add("update-data", updateData);
final JsonObject econLayer = new JsonObject();
econLayer.addProperty("enabled", !ess.getSettings().isEcoDisabled());
econLayer.addProperty("selected-layer", EconomyLayers.isLayerSelected());
final EconomyLayer layer = EconomyLayers.getSelectedLayer();
econLayer.addProperty("name", layer == null ? "null" : layer.getName());
econLayer.addProperty("layer-version", layer == null ? "null" : layer.getPluginVersion());
econLayer.addProperty("backend-name", layer == null ? "null" : layer.getBackendName());
essData.add("economy-layer", econLayer);
final JsonArray addons = new JsonArray();
final JsonArray plugins = new JsonArray();
final ArrayList<Plugin> alphabetical = new ArrayList<>();
Collections.addAll(alphabetical, Bukkit.getPluginManager().getPlugins());
alphabetical.sort(Comparator.comparing(o -> o.getName().toUpperCase(Locale.ENGLISH)));
for (final Plugin plugin : alphabetical) {
final JsonObject pluginData = new JsonObject();
final PluginDescriptionFile info = plugin.getDescription();
final String name = info.getName();
pluginData.addProperty("name", name);
pluginData.addProperty("version", info.getVersion());
pluginData.addProperty("description", info.getDescription());
pluginData.addProperty("main", info.getMain());
pluginData.addProperty("enabled", plugin.isEnabled());
pluginData.addProperty("official", plugin == ess || officialPlugins.contains(name));
pluginData.addProperty("unsupported", warnPlugins.contains(name));
final JsonArray authors = new JsonArray();
for (final String author : info.getAuthors()) {
authors.add(author == null ? JsonNull.INSTANCE : new JsonPrimitive(author));
}
pluginData.add("authors", authors);
if (name.startsWith("Essentials") && !name.equals("Essentials")) {
addons.add(pluginData);
}
plugins.add(pluginData);
}
essData.add("addons", addons);
dump.add("ess-data", essData);
dump.add("plugins", plugins);
final List<PasteUtil.PasteFile> files = new ArrayList<>();
files.add(new PasteUtil.PasteFile("dump.json", dump.toString()));
final Plugin essDiscord = Bukkit.getPluginManager().getPlugin("EssentialsDiscord");
final Plugin essSpawn = Bukkit.getPluginManager().getPlugin("EssentialsSpawn");
final Map<String, Command> knownCommandsCopy = new HashMap<>(ess.getKnownCommandsProvider().getKnownCommands());
final Map<String, String> disabledCommandsCopy = new HashMap<>(ess.getAlternativeCommandsHandler().disabledCommands());
// Further operations will be heavy IO
ess.runTaskAsynchronously(() -> {
boolean config = false;
boolean discord = false;
boolean kits = false;
boolean log = false;
boolean worth = false;
boolean tpr = false;
boolean spawns = false;
boolean commands = false;
for (final String arg : args) {
if (arg.equals("*") || arg.equalsIgnoreCase("all")) {
config = true;
discord = true;
kits = true;
log = true;
worth = true;
tpr = true;
spawns = true;
commands = true;
break;
} else if (arg.equalsIgnoreCase("config")) {
config = true;
} else if (arg.equalsIgnoreCase("discord")) {
discord = true;
} else if (arg.equalsIgnoreCase("kits")) {
kits = true;
} else if (arg.equalsIgnoreCase("log")) {
log = true;
} else if (arg.equalsIgnoreCase("worth")) {
worth = true;
} else if (arg.equalsIgnoreCase("tpr")) {
tpr = true;
} else if (arg.equalsIgnoreCase("spawns")) {
spawns = true;
} else if (arg.equalsIgnoreCase("commands")) {
commands = true;
}
}
if (config) {
try {
files.add(new PasteUtil.PasteFile("config.yml", new String(Files.readAllBytes(ess.getSettings().getConfigFile().toPath()), StandardCharsets.UTF_8)));
} catch (IOException e) {
sender.sendMessage(tl("dumpErrorUpload", "config.yml", e.getMessage()));
}
}
if (discord && essDiscord != null) {
try {
files.add(new PasteUtil.PasteFile("discord-config.yml",
new String(Files.readAllBytes(essDiscord.getDataFolder().toPath().resolve("config.yml")), StandardCharsets.UTF_8)
.replaceAll("[A-Za-z\\d]{24}\\.[\\w-]{6}\\.[\\w-]{27}", "<censored token>")));
} catch (IOException e) {
sender.sendMessage(tl("dumpErrorUpload", "discord-config.yml", e.getMessage()));
}
}
if (kits) {
try {
files.add(new PasteUtil.PasteFile("kits.yml", new String(Files.readAllBytes(ess.getKits().getFile().toPath()), StandardCharsets.UTF_8)));
} catch (IOException e) {
sender.sendMessage(tl("dumpErrorUpload", "kits.yml", e.getMessage()));
}
}
if (log) {
try {
files.add(new PasteUtil.PasteFile("latest.log", new String(Files.readAllBytes(Paths.get("logs", "latest.log")), StandardCharsets.UTF_8)
.replaceAll("(?m)^\\[\\d\\d:\\d\\d:\\d\\d] \\[.+/(?:DEBUG|TRACE)]: .+\\s(?:[A-Za-z.]+:.+\\s(?:\\t.+\\s)*)?\\s*(?:\"[A-Za-z]+\" : .+[\\s}\\]]+)*", "")
.replaceAll("(?:[0-9]{1,3}\\.){3}[0-9]{1,3}", "<censored ip address>")));
} catch (IOException e) {
sender.sendMessage(tl("dumpErrorUpload", "latest.log", e.getMessage()));
}
}
if (worth) {
try {
files.add(new PasteUtil.PasteFile("worth.yml", new String(Files.readAllBytes(ess.getWorth().getFile().toPath()), StandardCharsets.UTF_8)));
} catch (IOException e) {
sender.sendMessage(tl("dumpErrorUpload", "worth.yml", e.getMessage()));
}
}
if (tpr) {
try {
files.add(new PasteUtil.PasteFile("tpr.yml", new String(Files.readAllBytes(ess.getRandomTeleport().getFile().toPath()), StandardCharsets.UTF_8)));
} catch (IOException e) {
sender.sendMessage(tl("dumpErrorUpload", "tpr.yml", e.getMessage()));
}
}
if (spawns && essSpawn != null) {
try {
files.add(new PasteUtil.PasteFile("spawn.yml", new String(Files.readAllBytes(ess.getDataFolder().toPath().resolve("spawn.yml")), StandardCharsets.UTF_8)));
} catch (IOException e) {
sender.sendMessage(tl("dumpErrorUpload", "spawn.yml", e.getMessage()));
}
}
if (commands) {
try {
files.add(new PasteUtil.PasteFile("commands.yml", new String(Files.readAllBytes(Paths.get("commands.yml")), StandardCharsets.UTF_8)));
files.add(new PasteUtil.PasteFile("commandmap.json", CommandMapUtil.toJsonPretty(ess, knownCommandsCopy)));
files.add(new PasteUtil.PasteFile("commandoverride.json", disabledCommandsCopy.toString()));
} catch (IOException e) {
sender.sendMessage(tl("dumpErrorUpload", "commands.yml", e.getMessage()));
}
}
final CompletableFuture<PasteUtil.PasteResult> future = PasteUtil.createPaste(files);
future.thenAccept(result -> {
if (result != null) {
final String dumpUrl = "https://essentialsx.net/dump.html?id=" + result.getPasteId();
sender.sendMessage(tl("dumpUrl", dumpUrl));
sender.sendMessage(tl("dumpDeleteKey", result.getDeletionKey()));
if (sender.isPlayer()) {
ess.getLogger().info(tl("dumpConsoleUrl", dumpUrl));
ess.getLogger().info(tl("dumpDeleteKey", result.getDeletionKey()));
}
}
files.clear();
});
future.exceptionally(throwable -> {
sender.sendMessage(tl("dumpError", throwable.getMessage()));
return null;
});
});
}
// Resets the given player's user data.
private void runReset(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length < 2) {
throw new Exception("/<command> reset <player>");
}
final User user = getPlayer(server, args, 1, true, true);
user.reset();
sender.sendMessage("Reset Essentials userdata for player: " + user.getDisplayName());
}
// Toggles debug mode.
private void runDebug(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
ess.getSettings().setDebug(!ess.getSettings().isDebug());
sender.sendMessage("Essentials " + ess.getDescription().getVersion() + " debug mode " + (ess.getSettings().isDebug() ? "enabled" : "disabled"));
}
// Reloads all reloadable configs.
private void runReload(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
ess.reload();
sender.sendMessage(tl("essentialsReload", ess.getDescription().getVersion()));
}
// Pop tarts.
private void runNya(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (currentTune != null) {
currentTune.cancel();
}
currentTune = new TuneRunnable(NYAN_TUNE, NOTE_HARP, ess::getOnlinePlayers);
currentTune.runTaskTimer(ess, 20, 2);
}
// Cow farts.
private void runMoo(final Server server, final CommandSource sender, final String command, final String[] args) {
if (args.length == 2 && args[1].equals("moo")) {
for (final String s : CONSOLE_MOO) {
ess.getLogger().info(s);
}
for (final Player player : ess.getOnlinePlayers()) {
player.sendMessage(PLAYER_MOO);
player.playSound(player.getLocation(), MOO_SOUND, 1, 1.0f);
}
} else {
if (sender.isPlayer()) {
sender.getSender().sendMessage(PLAYER_MOO);
final Player player = sender.getPlayer();
player.playSound(player.getLocation(), MOO_SOUND, 1, 1.0f);
} else {
sender.getSender().sendMessage(CONSOLE_MOO);
}
}
}
// Cleans up inactive users.
private void runCleanup(final Server server, final CommandSource sender, final String command, final String[] args) throws Exception {
if (args.length < 2 || !NumberUtil.isInt(args[1])) {
sender.sendMessage("This sub-command will delete users who haven't logged in in the last <days> days.");
sender.sendMessage("Optional parameters define the minimum amount required to prevent deletion.");
sender.sendMessage("Unless you define larger default values, this command will ignore people who have more than 0 money/homes.");
throw new Exception("/<command> cleanup <days> [money] [homes]");
}
sender.sendMessage(tl("cleaning"));
final long daysArg = Long.parseLong(args[1]);
final double moneyArg = args.length >= 3 ? FloatUtil.parseDouble(args[2].replaceAll("[^0-9\\.]", "")) : 0;
final int homesArg = args.length >= 4 && NumberUtil.isInt(args[3]) ? Integer.parseInt(args[3]) : 0;
final UserMap userMap = ess.getUserMap();
ess.runTaskAsynchronously(() -> {
final long currTime = System.currentTimeMillis();
for (final UUID u : userMap.getAllUniqueUsers()) {
final User user = ess.getUserMap().getUser(u);
if (user == null) {
continue;
}
long lastLog = user.getLastLogout();
if (lastLog == 0) {
lastLog = user.getLastLogin();
}
if (lastLog == 0) {
user.setLastLogin(currTime);
}
if (user.isNPC()) {
continue;
}
final long timeDiff = currTime - lastLog;
final long milliDays = daysArg * 24L * 60L * 60L * 1000L;
final int homeCount = user.getHomes().size();
final double moneyCount = user.getMoney().doubleValue();
if ((lastLog == 0) || (timeDiff < milliDays) || (homeCount > homesArg) || (moneyCount > moneyArg)) {
continue;
}
if (ess.getSettings().isDebug()) {
ess.getLogger().info("Deleting user: " + user.getName() + " Money: " + moneyCount + " Homes: " + homeCount + " Last seen: " + DateUtil.formatDateDiff(lastLog));
}
user.reset();
}
sender.sendMessage(tl("cleaned"));
});
}
private void runHomes(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length < 2) {
sender.sendMessage("This sub-command provides a utility to mass-delete homes based on user options:");
sender.sendMessage("Use \"fix\" to delete all homes inside non-existent or unloaded worlds.");
sender.sendMessage("Use \"delete\" to delete all existing homes.");
sender.sendMessage("Use \"delete <worldname>\" to delete all homes inside a specific world.");
throw new Exception(HOMES_USAGE);
}
final UserMap userMap = ess.getUserMap();
switch (args[1]) {
case "fix":
sender.sendMessage(tl("fixingHomes"));
ess.runTaskAsynchronously(() -> {
for (final UUID u : userMap.getAllUniqueUsers()) {
final User user = ess.getUserMap().getUser(u);
if (user == null) {
continue;
}
for (String homeName : user.getHomes()) {
try {
if (user.getHome(homeName) == null) {
user.delHome(homeName);
}
} catch (Exception e) {
ess.getLogger().info("Unable to delete home " + homeName + " for " + user.getName());
}
}
}
sender.sendMessage(tl("fixedHomes"));
});
break;
case "delete":
final boolean filterByWorld = args.length >= 3;
if (filterByWorld && server.getWorld(args[2]) == null) {
throw new Exception(tl("invalidWorld"));
}
sender.sendMessage(filterByWorld ? tl("deletingHomesWorld", args[2]) : tl("deletingHomes"));
ess.runTaskAsynchronously(() -> {
for (final UUID u : userMap.getAllUniqueUsers()) {
final User user = ess.getUserMap().getUser(u);
if (user == null) {
continue;
}
for (String homeName : user.getHomes()) {
try {
final Location home = user.getHome(homeName);
if (!filterByWorld || (home != null && home.getWorld() != null && home.getWorld().getName().equals(args[2]))) {
user.delHome(homeName);
}
} catch (Exception e) {
ess.getLogger().info("Unable to delete home " + homeName + " for " + user.getName());
}
}
}
sender.sendMessage(filterByWorld ? tl("deletedHomesWorld", args[2]) : tl("deletedHomes"));
});
break;
default:
throw new Exception(HOMES_USAGE);
}
}
// Forces a rerun of userdata UUID conversion.
private void runUUIDConvert(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
sender.sendMessage("Starting Essentials UUID userdata conversion; this may lag the server.");
final Boolean ignoreUFCache = args.length > 2 && args[1].toLowerCase(Locale.ENGLISH).contains("ignore");
EssentialsUpgrade.uuidFileConvert(ess, ignoreUFCache);
sender.sendMessage("UUID conversion complete. Check your server log for more information.");
}
// Looks up various UUIDs for a user.
private void runUUIDTest(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length < 2) {
throw new Exception("/<command> uuidtest <name>");
}
final String name = args[1];
sender.sendMessage("Looking up UUID for " + name);
UUID onlineUUID = null;
for (final Player player : ess.getOnlinePlayers()) {
if (player.getName().equalsIgnoreCase(name)) {
onlineUUID = player.getUniqueId();
break;
}
}
final UUID essUUID = ess.getUserMap().getUser(name).getConfigUUID();
final org.bukkit.OfflinePlayer player = ess.getServer().getOfflinePlayer(name);
final UUID bukkituuid = player.getUniqueId();
sender.sendMessage("Bukkit Lookup: " + bukkituuid.toString());
if (onlineUUID != null && onlineUUID != bukkituuid) {
sender.sendMessage("Online player: " + onlineUUID.toString());
}
if (essUUID != null && essUUID != bukkituuid) {
sender.sendMessage("Essentials config: " + essUUID.toString());
}
final UUID npcuuid = UUID.nameUUIDFromBytes(("NPC:" + name).getBytes(Charsets.UTF_8));
sender.sendMessage("NPC UUID: " + npcuuid.toString());
final UUID offlineuuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8));
sender.sendMessage("Offline Mode UUID: " + offlineuuid.toString());
}
// Displays versions of EssentialsX and related plugins.
private void runVersion(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (sender.isPlayer() && !ess.getUser(sender.getPlayer()).isAuthorized("essentials.version")) return;
boolean isMismatched = false;
boolean isVaultInstalled = false;
boolean isUnsupported = false;
final VersionUtil.SupportStatus supportStatus = VersionUtil.getServerSupportStatus();
final PluginManager pm = server.getPluginManager();
final String essVer = pm.getPlugin("Essentials").getDescription().getVersion();
final String serverMessageKey;
if (supportStatus.isSupported()) {
serverMessageKey = "versionOutputFine";
} else if (supportStatus == VersionUtil.SupportStatus.UNSTABLE) {
serverMessageKey = "versionOutputUnsupported";
} else {
serverMessageKey = "versionOutputWarn";
}
sender.sendMessage(tl(serverMessageKey, "Server", server.getBukkitVersion() + " " + server.getVersion()));
sender.sendMessage(tl(serverMessageKey, "Brand", server.getName()));
sender.sendMessage(tl("versionOutputFine", "EssentialsX", essVer));
for (final Plugin plugin : pm.getPlugins()) {
final PluginDescriptionFile desc = plugin.getDescription();
String name = desc.getName();
final String version = desc.getVersion();
if (name.startsWith("Essentials") && !name.equalsIgnoreCase("Essentials")) {
if (officialPlugins.contains(name)) {
name = name.replace("Essentials", "EssentialsX");
if (!version.equalsIgnoreCase(essVer)) {
isMismatched = true;
sender.sendMessage(tl("versionOutputWarn", name, version));
} else {
sender.sendMessage(tl("versionOutputFine", name, version));
}
} else {
sender.sendMessage(tl("versionOutputUnsupported", name, version));
isUnsupported = true;
}
}
if (versionPlugins.contains(name)) {
if (warnPlugins.contains(name)) {
sender.sendMessage(tl("versionOutputUnsupported", name, version));
isUnsupported = true;
} else {
sender.sendMessage(tl("versionOutputFine", name, version));
}
}
if (name.equals("Vault")) isVaultInstalled = true;
}
final String layer;
if (ess.getSettings().isEcoDisabled()) {
layer = "Disabled";
} else if (EconomyLayers.isLayerSelected()) {
final EconomyLayer economyLayer = EconomyLayers.getSelectedLayer();
layer = economyLayer.getName() + " (" + economyLayer.getBackendName() + ")";
} else {
layer = "None";
}
sender.sendMessage(tl("versionOutputEconLayer", layer));
if (isMismatched) {
sender.sendMessage(tl("versionMismatchAll"));
}
if (!isVaultInstalled) {
sender.sendMessage(tl("versionOutputVaultMissing"));
}
if (isUnsupported) {
sender.sendMessage(tl("versionOutputUnsupportedPlugins"));
}
switch (supportStatus) {
case NMS_CLEANROOM:
sender.sendMessage(ChatColor.DARK_RED + tl("serverUnsupportedCleanroom"));
break;
case DANGEROUS_FORK:
sender.sendMessage(ChatColor.DARK_RED + tl("serverUnsupportedDangerous"));
break;
case STUPID_PLUGIN:
sender.sendMessage(ChatColor.DARK_RED + tl("serverUnsupportedDumbPlugins"));
break;
case UNSTABLE:
sender.sendMessage(ChatColor.DARK_RED + tl("serverUnsupportedMods"));
break;
case OUTDATED:
sender.sendMessage(ChatColor.RED + tl("serverUnsupported"));
break;
case LIMITED:
sender.sendMessage(ChatColor.RED + tl("serverUnsupportedLimitedApi"));
break;
}
if (VersionUtil.getSupportStatusClass() != null) {
sender.sendMessage(ChatColor.RED + tl("serverUnsupportedClass", VersionUtil.getSupportStatusClass()));
}
sender.sendMessage(tl("versionFetching"));
ess.runTaskAsynchronously(() -> {
for (String str : ess.getUpdateChecker().getVersionMessages(true, true)) {
sender.sendMessage(str);
}
});
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1) {
final List<String> options = Lists.newArrayList();
options.add("reload");
options.add("version");
options.add("dump");
options.add("commands");
options.add("debug");
options.add("reset");
options.add("cleanup");
options.add("homes");
//options.add("uuidconvert");
//options.add("uuidtest");
//options.add("nya");
//options.add("moo");
return options;
}
switch (args[0]) {
case "moo":
if (args.length == 2) {
return Lists.newArrayList("moo");
}
break;
case "reset":
case "uuidtest":
if (args.length == 2) {
return getPlayers(server, sender);
}
break;
case "cleanup":
if (args.length == 2) {
return COMMON_DURATIONS;
} else if (args.length == 3 || args.length == 4) {
return Lists.newArrayList("-1", "0");
}
break;
case "homes":
if (args.length == 2) {
return Lists.newArrayList("fix", "delete");
} else if (args.length == 3 && args[1].equalsIgnoreCase("delete")) {
return server.getWorlds().stream().map(World::getName).collect(Collectors.toList());
}
break;
case "uuidconvert":
if (args.length == 2) {
return Lists.newArrayList("ignoreUFCache");
}
break;
case "dump":
final List<String> list = Lists.newArrayList("config", "kits", "log", "discord", "worth", "tpr", "spawns", "commands", "all");
for (String arg : args) {
if (arg.equals("*") || arg.equalsIgnoreCase("all")) {
list.clear();
return list;
}
list.remove(arg.toLowerCase(Locale.ENGLISH));
}
return list;
}
return Collections.emptyList();
}
private static class TuneRunnable extends BukkitRunnable {
private static final Map<String, Float> noteMap = ImmutableMap.<String, Float>builder()
.put("1F#", 0.5f)
.put("1G", 0.53f)
.put("1G#", 0.56f)
.put("1A", 0.6f)
.put("1A#", 0.63f)
.put("1B", 0.67f)
.put("1C", 0.7f)
.put("1C#", 0.76f)
.put("1D", 0.8f)
.put("1D#", 0.84f)
.put("1E", 0.9f)
.put("1F", 0.94f)
.put("2F#", 1.0f)
.put("2G", 1.06f)
.put("2G#", 1.12f)
.put("2A", 1.18f)
.put("2A#", 1.26f)
.put("2B", 1.34f)
.put("2C", 1.42f)
.put("2C#", 1.5f)
.put("2D", 1.6f)
.put("2D#", 1.68f)
.put("2E", 1.78f)
.put("2F", 1.88f)
.build();
private final String[] tune;
private final Sound sound;
private final Supplier<Collection<Player>> players;
private int i = 0;
TuneRunnable(final String tuneStr, final Sound sound, final Supplier<Collection<Player>> players) {
this.tune = tuneStr.split(",");
this.sound = sound;
this.players = players;
}
@Override
public void run() {
final String note = tune[i];
i++;
if (i >= tune.length) {
cancel();
}
if (note == null || note.isEmpty()) {
return;
}
for (final Player onlinePlayer : players.get()) {
onlinePlayer.playSound(onlinePlayer.getLocation(), sound, 1, noteMap.get(note));
}
}
registerNode(new VersionCommand());
registerNode(new DebugCommand());
registerNode(new CommandMapCommand());
registerNode(new DumpCommand());
registerNode(new ReloadCommand());
registerNode(new CleanupCommand());
registerNode(new HomesCommand());
registerNode(new UsermapCommand());
registerNode(new ItemTestCommand());
registerNode(new NyanCommand());
registerNode(new MooCommand());
}
}
@@ -13,8 +13,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import static com.earth2me.essentials.I18n.tl;
public class Commandexp extends EssentialsLoopCommand {
public Commandexp() {
super("exp");
@@ -22,7 +20,7 @@ public class Commandexp extends EssentialsLoopCommand {
@Override
public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
final IUser user = sender.getUser(ess);
final IUser user = sender.getUser();
if (args.length == 0 || (args.length < 2 && user == null)) {
if (user == null) {
throw new NotEnoughArgumentsException();
@@ -39,7 +37,7 @@ public class Commandexp extends EssentialsLoopCommand {
}
if (!cmd.hasPermission(user)) {
user.sendMessage(tl("noAccessSubCommand", "/" + commandLabel + " " + cmd.name().toLowerCase(Locale.ENGLISH)));
user.sendTl("noAccessSubCommand", "/" + commandLabel + " " + cmd.name().toLowerCase(Locale.ENGLISH));
return;
}
@@ -99,7 +97,7 @@ public class Commandexp extends EssentialsLoopCommand {
}
private void showExp(final CommandSource sender, final IUser target) {
sender.sendMessage(tl("exp", target.getDisplayName(), SetExpFix.getTotalExperience(target.getBase()), target.getBase().getLevel(), SetExpFix.getExpUntilNextLevel(target.getBase())));
sender.sendTl("exp", target.getDisplayName(), SetExpFix.getTotalExperience(target.getBase()), target.getBase().getLevel(), SetExpFix.getExpUntilNextLevel(target.getBase()));
}
//TODO: Limit who can give negative exp?
@@ -131,7 +129,7 @@ public class Commandexp extends EssentialsLoopCommand {
amount = 0L;
}
SetExpFix.setTotalExperience(target.getBase(), (int) amount);
sender.sendMessage(tl("expSet", target.getDisplayName(), amount));
sender.sendTl("expSet", target.getDisplayName(), amount);
}
@Override
@@ -156,7 +154,7 @@ public class Commandexp extends EssentialsLoopCommand {
}
}
if (user.isAuthorized("essentials.exp.others")) {
return getPlayers(server, user);
return getPlayers(user);
}
} else if (args.length == 3 && !(args[0].equalsIgnoreCase("show") || args[0].equalsIgnoreCase("reset"))) {
final String levellessArg = args[2].toLowerCase(Locale.ENGLISH).replaceAll("l", "");
@@ -184,10 +182,10 @@ public class Commandexp extends EssentialsLoopCommand {
return Collections.emptyList();
}
} else { // even without 'show'
return getPlayers(server, sender);
return getPlayers(sender);
}
} else if (args.length == 3 && (args[0].equalsIgnoreCase("set") || args[0].equalsIgnoreCase("give"))) {
return getPlayers(server, sender);
return getPlayers(sender);
} else {
return Collections.emptyList();
}
@@ -8,8 +8,6 @@ import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandext extends EssentialsLoopCommand {
public Commandext() {
super("ext");
@@ -32,13 +30,13 @@ public class Commandext extends EssentialsLoopCommand {
}
extPlayer(user.getBase());
user.sendMessage(tl("extinguish"));
user.sendTl("extinguish");
}
@Override
protected void updatePlayer(final Server server, final CommandSource sender, final User player, final String[] args) {
extPlayer(player.getBase());
sender.sendMessage(tl("extinguishOthers", player.getDisplayName()));
sender.sendTl("extinguishOthers", player.getDisplayName());
}
private void extPlayer(final Player player) {
@@ -48,7 +46,7 @@ public class Commandext extends EssentialsLoopCommand {
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1) {
return getPlayers(server, sender);
return getPlayers(sender);
} else {
return Collections.emptyList();
}
@@ -9,8 +9,6 @@ import org.bukkit.event.entity.FoodLevelChangeEvent;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandfeed extends EssentialsLoopCommand {
public Commandfeed() {
super("feed");
@@ -28,7 +26,7 @@ public class Commandfeed extends EssentialsLoopCommand {
}
feedPlayer(user.getBase());
user.sendMessage(tl("feed"));
user.sendTl("feed");
}
@Override
@@ -44,7 +42,7 @@ public class Commandfeed extends EssentialsLoopCommand {
protected void updatePlayer(final Server server, final CommandSource sender, final User player, final String[] args) throws PlayerExemptException {
try {
feedPlayer(player.getBase());
sender.sendMessage(tl("feedOther", player.getDisplayName()));
sender.sendTl("feedOther", player.getDisplayName());
} catch (final QuietAbortException e) {
//Handle Quietly
}
@@ -66,8 +64,8 @@ public class Commandfeed extends EssentialsLoopCommand {
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1 && sender.isAuthorized("essentials.feed.others", ess)) {
return getPlayers(server, sender);
if (args.length == 1 && sender.isAuthorized("essentials.feed.others")) {
return getPlayers(sender);
} else {
return Collections.emptyList();
}
@@ -5,6 +5,7 @@ import com.earth2me.essentials.utils.FloatUtil;
import com.earth2me.essentials.utils.VersionUtil;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import net.ess3.api.TranslatableException;
import org.bukkit.Server;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.DragonFireball;
@@ -18,6 +19,7 @@ import org.bukkit.entity.Snowball;
import org.bukkit.entity.SplashPotion;
import org.bukkit.entity.ThrownExpBottle;
import org.bukkit.entity.Trident;
import org.bukkit.entity.WindCharge;
import org.bukkit.entity.WitherSkull;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.util.Vector;
@@ -27,8 +29,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static com.earth2me.essentials.I18n.tl;
public class Commandfireball extends EssentialsCommand {
public static final String FIREBALL_META_KEY = "ess_fireball_proj";
@@ -56,6 +56,10 @@ public class Commandfireball extends EssentialsCommand {
builder.put("trident", Trident.class);
}
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_21_R01)) {
builder.put("windcharge", WindCharge.class);
}
types = builder.build();
}
@@ -78,7 +82,7 @@ public class Commandfireball extends EssentialsCommand {
}
if (!user.isAuthorized("essentials.fireball." + type)) {
throw new Exception(tl("noPerm", "essentials.fireball." + type));
throw new TranslatableException("noPerm", "essentials.fireball." + type);
}
final Vector direction = user.getBase().getEyeLocation().getDirection().multiply(speed);
@@ -1,14 +1,15 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.MetaItemStack;
import com.earth2me.essentials.MobCompat;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.MaterialUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.google.common.collect.Lists;
import net.ess3.api.TranslatableException;
import org.bukkit.DyeColor;
import org.bukkit.FireworkEffect;
import org.bukkit.Server;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Firework;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.FireworkMeta;
@@ -17,8 +18,6 @@ import org.bukkit.util.Vector;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
//This command has quite a complicated syntax, in theory it has 4 seperate syntaxes which are all variable:
//
//1: /firework clear - This clears all of the effects on a firework stack
@@ -49,21 +48,21 @@ public class Commandfirework extends EssentialsCommand {
final ItemStack stack = user.getItemInHand();
if (!MaterialUtil.isFirework(stack.getType())) {
throw new Exception(tl("holdFirework"));
throw new TranslatableException("holdFirework");
}
if (args[0].equalsIgnoreCase("clear")) {
final FireworkMeta fmeta = (FireworkMeta) stack.getItemMeta();
fmeta.clearEffects();
stack.setItemMeta(fmeta);
user.sendMessage(tl("fireworkEffectsCleared"));
user.sendTl("fireworkEffectsCleared");
} else if (args.length > 1 && (args[0].equalsIgnoreCase("power") || args[0].equalsIgnoreCase("p"))) {
final FireworkMeta fmeta = (FireworkMeta) stack.getItemMeta();
try {
final int power = Integer.parseInt(args[1]);
fmeta.setPower(power > 3 ? 4 : power);
} catch (final NumberFormatException e) {
throw new Exception(tl("invalidFireworkFormat", args[1], args[0]));
throw new TranslatableException("invalidFireworkFormat", args[1], args[0]);
}
stack.setItemMeta(fmeta);
} else if ((args[0].equalsIgnoreCase("fire") || args[0].equalsIgnoreCase("f")) && user.isAuthorized("essentials.firework.fire")) {
@@ -75,14 +74,14 @@ public class Commandfirework extends EssentialsCommand {
amount = Integer.parseInt(args[1]);
if (amount > serverLimit) {
amount = serverLimit;
user.sendMessage(tl("mobSpawnLimit"));
user.sendTl("mobSpawnLimit");
}
} else {
direction = true;
}
}
for (int i = 0; i < amount; i++) {
final Firework firework = (Firework) user.getWorld().spawnEntity(user.getLocation(), EntityType.FIREWORK);
final Firework firework = (Firework) user.getWorld().spawnEntity(user.getLocation(), MobCompat.FIREWORK_ROCKET);
final FireworkMeta fmeta = (FireworkMeta) stack.getItemMeta();
if (direction) {
final Vector vector = user.getBase().getEyeLocation().getDirection().multiply(0.070);
@@ -99,7 +98,7 @@ public class Commandfirework extends EssentialsCommand {
try {
mStack.addFireworkMeta(user.getSource(), true, arg, ess);
} catch (final Exception e) {
user.sendMessage(tl("fireworkSyntax"));
user.sendTl("fireworkSyntax");
throw e;
}
}
@@ -108,13 +107,13 @@ public class Commandfirework extends EssentialsCommand {
final FireworkMeta fmeta = (FireworkMeta) mStack.getItemStack().getItemMeta();
final FireworkEffect effect = mStack.getFireworkBuilder().build();
if (fmeta.getEffects().size() > 0 && !user.isAuthorized("essentials.firework.multiple")) {
throw new Exception(tl("multipleCharges"));
throw new TranslatableException("multipleCharges");
}
fmeta.addEffect(effect);
stack.setItemMeta(fmeta);
} else {
user.sendMessage(tl("fireworkSyntax"));
throw new Exception(tl("fireworkColor"));
user.sendTl("fireworkSyntax");
throw new TranslatableException("fireworkColor");
}
}
}
Loaded 100 of 579 files, more files were not shown because too many files have changed in this diff. Show more