Compare commits

...
785 Commits
Author SHA1 Message Date
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
Josh Roy 8f7b7d0a5f Release 2.19.6 2022-08-12 16:59:48 -04:00
Josh Roy f379323d7a Fix mappings in ReflOnlineModeProvider 2022-08-12 16:59:02 -04:00
MD f2dde4154c Release 2.19.5 2022-08-11 21:05:44 +01:00
Flask Bot 12c2a4f2b5 Update translations from Crowdin (#4823) 2022-08-11 20:24:59 +01:00
BoomEaro ee7c1b1cf1 Fix wrong message length check (#5041) 2022-08-08 14:53:59 -07:00
Sam 422f25af8b Fix erroneous addition to VersionUtil class check (#5037) 2022-08-07 19:41:27 +00:00
Josh RoyandMD 728c746200 Add commands info to /ess dump (#4965)
See #4961 and EssentialsX/Website#71.

Adds `commands.yml`, the known command map and `AlternativeCommandHandler` handover information to `/ess dump`.

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2022-08-07 11:43:59 +01:00
MD 6dfa18ca53 Implement chat message signing for 1.19.1+ (#5030)
**Known issue: this inadvertently reformats `/minecraft:tell`. There's not much we can do about this in 2.19.x.**

This commit refactors EssentialsX Chat in order to support chat previews and signed chat messages in 1.19.1+.
2022-08-06 22:27:58 +01:00
Josh Roy a394760677 Update VersionUtil and /ess version (#5036) 2022-08-06 15:48:26 -04:00
MD 5f48cdef13 Bump to 1.19.2 (#5035) 2022-08-06 12:14:07 -04:00
pop4959andMD c56a9e6741 Add spawnable boat variants (#5027)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2022-08-02 14:18:57 -04:00
pop4959 5b20f30350 Fix ice other message (#5028)
Fix `iceOther` being sent to the target player instead of the sender. Fixes #5002.
2022-08-02 08:40:10 +01:00
pop4959 037577162b Add swift sneak enchantment (#5026)
Closes #5024.
2022-07-31 19:03:31 +00:00
Josh Roy 908189a233 Bump Spigot to 1.19.1 2022-07-28 11:30:24 -04:00
Josh Roy c938767b2d Prepare for 1.19.1 2022-07-28 11:30:24 -04:00
Josh Roy b135fcf086 Fix leave messages being sent when vanished (#5009) 2022-07-23 18:35:14 -04:00
Warrior 09dc49aacb Fix nickname feedback message when change-displayname is off (#4980)
Make the feedback message use the nickname instead of the player's display name if change-displayname is disabled. Fixes #4979.
2022-07-11 08:02:10 +00:00
Josh Roy f4b6197a49 Fix boolean logic for sleep ignore during afk status change (#4985) 2022-07-10 22:28:08 -07:00
Josh Roy 4a53cfe7ac Prevent role snowflakes from resolving roles with ids as names (#4983)
Fixes #4981.
2022-07-10 00:24:04 +01:00
DeltricandJosh Roy 2bba577567 Fix /unlimited with changing stack sizes (#4877)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2022-07-05 17:01:13 +00:00
pop4959andJosh Roy 6e674c73e0 Update unsupported message (#4976)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2022-07-05 09:52:14 -04:00
Josh Roy a60f3c067f Fix Essentials' logger breaking on 1.8.8-1.12.2 (#4975) 2022-07-02 14:50:45 -04:00
Josh RoyandMD 6816eb4e18 Use component logger on Paper for console colors (#4941)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2022-06-27 14:54:10 -04:00
Tristan Hermanns 9c5536b2e6 Fix minecraft namespace not working in give command (#4960) 2022-06-25 17:40:43 -04:00
Josh Roy ff80338350 Log filenames during configuration serialization errors (#4959) 2022-06-21 22:01:25 +00:00
Josh RoyandMD 8d856dd6db Fix paths and farmland not being solid (#4915)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2022-06-18 19:31:54 +00:00
Josh Roy 96c6c951e8 Don't teleport to a safe location for AFK freeze when flying (#4944)
Fixes #4902.
2022-06-15 15:43:57 +00:00
Josh Roy 0931b21f6d Fix places where Discord formatting wasn't sanitized (#4945)
Fixes #4891
2022-06-15 15:35:12 +00:00
Josh Roy 9147d1036d Fix ANSI color codes not getting stripped on Paper (#4942)
Paper uses 0x7f as an intermediate character between adventure and its ANSI pattern converter, we need to strip this.
2022-06-15 16:31:42 +01:00
MD a7f602e2ad Remove commons-lang3 dependency
`commons-lang3` is scheduled for removal by Spigot.

This commit reimplements the methods we use from StringUtils in StringUtil, and replaces NumberUtils.isDigits(String) with NumberUtil.isLong(String).
2022-06-15 16:18:35 +01:00
Flask Bot 0c81980ead Update items.json for 1.19
Note: this action was performed by a human (@mdcfe) under the
Flask-Bot account.
2022-06-15 16:18:35 +01:00
MD 2d5794f68d Add mangrove signs to MaterialUtil 2022-06-15 16:18:35 +01:00
MD 6b80e0b113 Bump run-paper MC version to 1.19 2022-06-15 16:18:35 +01:00
Josh Roy 18f9af898e Add 1.19 mobs/data 2022-06-15 16:18:35 +01:00
Josh Roy d04c933abe Fix server state reflection provider mappings 2022-06-15 16:18:35 +01:00
Josh Roy b916488de2 Bump VersionUtil and Spigot 2022-06-15 16:18:35 +01:00
MDandJosh Roy c6066bd2d8 Update README and contributing guide (#4851)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>

This PR brings the README up-to-date, reduces the number of references to upstream Essentials, and fleshes out the contributing guide with much more detail.
2022-06-10 13:06:24 +00:00
Josh Roy 3af931740b Don't progress playtime for vanished users (#4918) 2022-06-04 01:43:12 +00:00
Josh Roy 1c5a7e9ffb Bump down to 2.19.5 (#4914)
Mojang will be releasing 1.19 within two weeks and 2.20.0 is not ready for that timeframe. We want to release 2.19.5 with 1.19 support before that.
2022-05-30 21:21:19 +01:00
pop4959 ea5cdc097d Fix erroneous TeleportWarmupEvent event call (#4913) 2022-05-22 17:20:39 -04:00
Deltric d891268143 Fix /nick off breaking with changecolor permissions (#4876) 2022-03-31 19:57:36 -04:00
lucko 4bd1b3c09f Reduce user lookups for LuckPerms contexts (#4869) 2022-03-27 09:07:36 -04:00
MD c4f10d9c1d Fix double charge for /tpa when target has /tpauto on (#4862)
If a player uses `/tpa` to request a teleport to someone with `/tpauto` enabled, `/tpa` manually creates a charge for the teleportation.  
This PR changes the subsequent code to throw a `NoChargeException` instead of returning, meaning only the `AsyncTeleport` charge is applied, not the standard command one.

Fixes #4854.
2022-03-20 11:19:20 +00:00
MD e12f1b5022 Bump run-paper to 1.18.2 (#4857) 2022-03-15 07:56:52 -04:00
MDandJosh Roy 31732cec95 Prepare for 2.20.0 dev builds (#4849)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2022-03-12 19:33:28 -05:00
MD f104b6ec15 Release 2.19.4 2022-03-12 23:00:24 +00:00
MD 695546420f Run update check using Bukkit scheduler (#4839)
Fixes #4825.
2022-03-12 12:02:20 +00:00
Josh Roy 997fb1800e Fix preemptive setting of owner metadata on to trade signs (#4835)
Fixes #4836.
2022-03-08 15:08:34 +00:00
MD 3984bddd9d Fix 2.19.4 dev build version (#4834) 2022-03-06 00:06:48 +00:00
MD d92ce8bdfe Bump to 2.19.4 dev (#4831) 2022-03-05 18:33:04 +00:00
MD 3348bd9129 Release 2.19.3 2022-03-04 11:41:56 +00:00
Josh Roy 1a5526867c Fix IllegalAccessException with advancement listener on 1.12.2 (#4826)
Fixes #4809.
2022-03-03 20:38:39 +00:00
MD c5253bc5e2 Update to Minecraft 1.18.2 (#4821)
This PR updates the build setup to Gradle 7.4 and updates EssentialsX to Minecraft 1.18.2.
2022-02-28 20:51:26 +00:00
Flask Bot 29a2a865c5 New Crowdin updates (#4744) 2022-02-28 20:31:02 +00:00
MD c3b994808f Use GitHub proxy fallback for update checker (#4818) 2022-02-26 17:09:54 -05:00
triagonal bc2da98703 Make OfflinePlayer#setName protected for CMI Importer (#4805) 2022-02-14 07:40:48 -05:00
Josh Roy cbb012853c Fix toggleshout causing issues with question/local chat 2022-02-13 22:44:37 -05:00
Josh Roy 4bf4bd432a Add some color to the local prefix by default 2022-02-13 22:44:37 -05:00
Josh Roy 83d0e0d9a0 Add permissions to receive certain channel channels 2022-02-13 22:44:37 -05:00
Josh Roy 2f5c24d114 Add permission for local channel chat 2022-02-13 22:44:37 -05:00
Josh Roy 88c8ccd29b Add config for default shout state and for persisting shout state 2022-02-13 22:44:37 -05:00
Josh Roy 94edbcfeb1 Add chat question config option 2022-02-13 22:44:37 -05:00
Josh RoyandMD d5822e9a41 Add config for max /tree and /bigtree range (#4728)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2022-02-13 16:46:18 -05:00
Josh Roy debf09437e Add UUID support to trade and protection signs (#4713) 2022-02-13 15:54:19 -05:00
Josh Roy a9e5f079a9 Fix trade sign length validation issues (#4748) 2022-02-13 15:50:43 -05:00
Josh Roy f00c2dcf92 Fix keepinv policies ignoring offhand (#4725) 2022-02-13 20:37:25 +00:00
Josh Roy 63cbf7e2da Prevent legacy material support from being initiated (#4697) 2022-02-13 15:33:51 -05:00
Josh Roy 19837f9309 Add some more dump files (#4785)
Adds worth, tpr, and spawns configs.

See also https://github.com/EssentialsX/Website/pull/68
2022-02-08 09:26:05 +00:00
Josh Roy 4b8b770c30 Allow /nick without change-displayname (#4758)
Closes #4709.
2022-02-06 18:23:17 +00:00
Josh Roy ddc258ed11 Fix teleport request queue being reversed order (#4755)
Fix #4753
2022-02-06 18:18:40 +00:00
Josh Roy d23796dade Support doubles in /tppos (#4729)
Closes #1785
2022-02-06 18:01:51 +00:00
Josh Roy 41f5cc3175 Use Paper command forwarding API (#4794)
Uses API added in PaperMC/Paper@9940bca, when available.
2022-02-06 17:56:13 +00:00
Josh Roy c6fe160b47 Fix Paper command executor on modern Paper versions (#4791) 2022-02-01 22:13:16 +00:00
sxphirus 5c46bea2ca Add translation key for /near entries (#4740) 2022-01-20 10:05:31 -05:00
Flask Bot a9585bb525 New Crowdin updates (#4668) 2022-01-09 16:27:25 +00:00
Josh Roy bb88a6a749 Fix rate limit exception in update checker (#4731) 2022-01-02 17:24:32 +00:00
Pierre Dedrie 739600eb05 Fix safelogin for 1.18 and above by using worldProvider minHeight (#4715) 2021-12-26 23:56:03 +00:00
Josh Roy 5f98d3fac5 Add mitigation for JDK-8274349 (#4711) 2021-12-24 21:40:41 -05:00
Josh Roy 84326cf13e Fix LocationUtil#getSafeDestination NSME on older versions (#4708)
Fixes a NoSuchMethodError from old guava versions in old MC versions.

Fixes #4703
2021-12-23 11:55:31 +00:00
Josh Roy 685084219e Use futureproof Paper component serializer when available (#4706)
#plainSerializer will be removed when adventure 5.0.0 releases. This PR prevents this from breaking in the future.

Closes #4705
2021-12-23 10:57:41 +00:00
Josh Roy c4e62ae22b Remove usused configuration classes (#4707)
These were not used since configurate, no plugin should be using this, and it causes compile errors on the latest spigot version.
2021-12-23 01:18:02 +00:00
MD 05ce11f657 Bump to 2.19.3 dev 2021-12-15 21:29:45 +00:00
MD bcb0fb5825 Bump to 2.19.2 release 2021-12-15 20:58:38 +00:00
MD 91f4493fe0 Run Actions builds for dev branches (#4691) 2021-12-15 20:55:37 +00:00
MD d53ff36a2a Prevent IndexOutOfBoundsException in interaction replies (#4690) 2021-12-15 20:55:37 +00:00
MD 98917d97c8 Bump down to 2.19.2-dev 2021-12-15 20:55:37 +00:00
Josh Roy 7e1d18fee4 Prepare for 2.20.0 dev builds 2021-12-14 21:26:44 +00:00
Josh Roy 0b7e7801d0 Release 2.19.1 (#4681) 2021-12-14 19:16:08 +00:00
Josh Roy 840035944a Truncate Discord interaction messages to proper length (#4686) 2021-12-14 18:46:59 +00:00
Josh RoyandMD 20a7d1c7ee Add DiscordRelayEvent to edit relayed Discord->MC messages (#4656)
Implements the event suggested in #4580.

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2021-12-14 16:26:57 +00:00
pop4959 253185bd0e Remove ignore teleport metadata 2021-12-12 22:23:56 +00:00
Josh Roy dae519494e Fix stack overflow when disabling commands from other addons (#4680) 2021-12-11 19:16:23 -08:00
Josh Roy dc27af78bf Update to MC 1.18.1 2021-12-11 12:13:50 +00:00
Flask Bot 41861a8551 New Crowdin updates (#4467) 2021-12-04 14:53:26 +00:00
Josh Roy 9b629e42d8 Bump JDA + discord-webhooks dependencies (#4655) 2021-12-04 14:47:40 +00:00
7794634d37 Add support for multiple queued TPA requests (#3801)
This PR adds support for players to receive multiple teleport requests, which are queued and can be managed independently of one another.

All commands should retain their current behavior but have some new additions;
* `/tpaccept`: now allows you to specify a player or `*` to accept a specific player's or all players' teleport request(s) respectively.
  - Using a wildcard will only accept all tpahere requests, as players can't teleport to multiple places simultaneously.
* `/tpdeny`: now allows you to specify a player or `*` to deny a specific player's or all players' teleport request(s) respectively.

This PR also adds a new setting for the maximum amount of pending TPA requests a user can have at once.
```yml
# The maximum amount of simultaneous tpa requests that can be pending for any given user.
# Once at this threshold, any new tpa requests will bump the oldest tpa requests out of queue.
# Defaults to 5.
tpa-max-amount: 5
```

Closes #3769
Closes #1550

Co-authored-by: Mariell Hoversholm <proximyst@proximy.st>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2021-12-04 14:40:06 +00:00
Josh Roy d091d6902c Fix safelogin support with 1.18 world heights (#4667)
Fixes #4665.
2021-12-03 20:31:36 +00:00
MD c45c741210 Bump supported version to 1.18 release 2021-11-30 16:33:05 +00:00
MD e35c65b976 Refactor world info provider; only clamp to logical height when already below
Fixes #4657.
2021-11-30 16:33:05 +00:00
MDandJason 0983167740 Port remaining groovy build logic to kotlin
Co-authored-by: Jason <11360596+jpenilla@users.noreply.github.com>
2021-11-30 16:33:05 +00:00
1509cf8978 Support 0>y>256 for safe/random teleports (#4641)
Co-authored-by: pop4959 <pop4959@gmail.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-11-30 16:33:05 +00:00
MD c0082ee734 Update to Gradle 7.3 and toolchain to JDK 17 2021-11-30 16:33:05 +00:00
Flask Bot db2ec1b436 Update items.json for Minecraft 1.18 2021-11-30 16:33:05 +00:00
Josh Roy 7a1e50d10f Fix reflection mappings for the ServerStateProvider in 1.18 2021-11-30 16:33:05 +00:00
Josh Roy b6ba1768a1 Mark 1.18 as a supported version 2021-11-30 16:33:05 +00:00
Josh Roy c38dd997d2 Update Spigot to 1.18 2021-11-30 16:33:05 +00:00
Josh Roy f8ca1bae5d Fix offline player error in /playtime for versions < 1.15.2 (#4649) 2021-11-25 19:50:49 +00:00
Josh Roy 3c60bcf2de Allow unsafe homes when force-disable-teleport-safety is enabled (#4650) 2021-11-25 19:47:42 +00:00
Josh Roy 3f857e3183 Fix DiscordUtil#getOrCreateWebhook using webhooks without tokens 2021-11-25 14:03:56 -05:00
Josh Roy 668c9634ca Ensure JDA is started before allowing calls to the JDA object 2021-11-25 14:03:56 -05:00
nik2143andJosh Roy 51c49b0de6 Set a metadata value for vanished players (#4646)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-11-24 09:57:55 -05:00
Lax 5723dac6ba Send discord config in dump even when plugin is disabled 2021-11-17 19:15:02 -05:00
Josh Roy 680d3230ea Add /discord command to display invite code (#4620) 2021-11-06 19:18:37 -04:00
Josh Roy 8f679de352 Add offline user support to /playtime (#4619) 2021-11-06 15:41:38 -07:00
Josh Roy 86d435eee1 Move UUID below alts in /seen (#4615) 2021-11-06 13:50:56 -07:00
Bobcat00andBobcat00 e4cf965bae Add translation for /list real name (#4614)
Co-authored-by: Bobcat00 <Bobcat00@users.noreply.github.com>
2021-11-05 22:24:21 +00:00
Andre_601 78c846a0a8 Minor typo corrections for EssentialsXDiscord (#4603) 2021-11-01 21:41:58 -04:00
pop4959andJosh Roy c466c9c370 Add metadata value to bypass last location setting on teleport (#4597)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-10-31 17:25:18 -04:00
Josh Roy 6994005a5f Fix light blocks being considering solid (#4601) 2021-10-30 22:25:03 -07:00
Josh Roy e43f06bfa6 Add /mail sendtempall (#4561)
Implements #4558
2021-10-24 16:45:01 +00:00
Josh Roy 9601687190 Add console regex filter for discord module (#4534) 2021-10-24 12:12:42 -04:00
Josh Roy 2ed94f1048 Add provider to use new Paper advancement API (#4588)
Paper added in PaperMC/Paper@40ee634
2021-10-24 14:27:07 +00:00
Josh Roy 70045c30f3 Migrate reflection in MetaItemStack to a provider (#4592) 2021-10-24 15:17:06 +01:00
Josh Roy ce88c0e1e4 Fix kit issues with potions in 1.8 (#4591)
Fixes #2867
2021-10-24 14:12:20 +00:00
Josh Roy 494e82e581 Fix ability to delete warps in unloaded worlds (#4590)
Fixes #4584.
2021-10-24 15:05:10 +01:00
MelluhandJosh Roy edd15132fd Add playtime command (#4562)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-10-23 15:29:37 -04:00
Tadhg BoyleandJosh Roy add664441e Add notify messages to jail command (#4576)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-10-23 15:18:51 -04:00
Josh Roy 58b0fb089f Improve error message when primary channel is improperly defined (#4586)
* Improve message verboseness when primary channel is improperly defined

* Clarify primary channel behavior
2021-10-20 13:09:56 +01:00
Josh Roy 90e4845627 Remove config reloads on world load/unload (#4573)
Since the Configurate rewrite, we hold locations using `LazyLocation`, which is converted to a `Location` with world lookups on demand; as such we no longer need to reload configs on each world load since invalid locations will now softly fail.
2021-10-13 18:41:48 +01:00
Josh RoyandMD 43f4d306be Add discordbroadcast command (#4541)
* Add discordbroadcast command

* Update EssentialsDiscord/src/main/resources/plugin.yml

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

* Add emoji parsing/tab complete for discordbroadcast command

Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2021-10-11 15:54:55 -04:00
Josh Roy cb7d9baf7a Fix exception while running vanilla commands for chat command relay (#4552)
Fixes #4545
2021-10-11 19:32:41 +00:00
Josh Roy f3aea5e6ec Improve error message for updater checker rate limit (#4560)
Fixes #4556
2021-10-11 20:06:18 +01:00
Josh Roy a7a1fcd98c Fix Discord token regex censor pattern (#4563)
Discord changed their token format again
2021-10-07 09:22:41 +01:00
ef8bcc8bc4 Add kits subfolder to allow for multiple kit files (#4407)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-10-02 19:14:41 -04:00
MDandJosh Roy b3692d18b0 Update plugin.yml authors to reflect EssentialsX contributions (#4551)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-09-28 15:49:55 -04:00
Josh Roy a7b2b675d5 Add essentials.seen.uuid permission to show uuids in seen (#4531)
Adds `essentials.seen.uuid` permission requirement to show uuids in `/seen`. People were complaining this cluttered it so whatever. Also adds said permission to the `essentials.seen.extra` permission group.

Closes #4499.
2021-09-28 13:13:50 +00:00
Josh RoyandMD 7d7a8e47ac Fix verbose command usages not being disabled properly (#4530)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2021-09-28 13:09:04 +00:00
MDandJosh Roy f0da17b75c Asynchronously filter commands on Paper when possible (#4460)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-09-15 17:23:31 -04:00
Josh Roy d86b471402 Add action message type (#4520)
Adds a message type which will show when a player runs the /action (/me) command.

Message Type:
```yml
# Configure which Discord channels different messages will be sent to.
# You can either use the names of the channels listed above or just the id of a channel.
# If an invalid channel is used, the primary channel will be used instead.
#
# To disable a message from showing, use 'none' as the channel name.
message-types:
  ...
  # Action messages sent when a player runs the /me or /action commands.
  action: primary
```

Message Format:
```yml
# The following entries allow you to customize the formatting of messages sent by the plugin.
# Each message has a description of how it is used along with placeholders that can be used.
messages:
  ...
  # This is the message sent to Discord when a player runs the /me or /action command.
  # The following placeholders can be used here:
  # - {username}: The name of the user who ran the command
  # - {displayname}: The display name of the user who ran the command
  # - {action}: The action (message) the user used in the command.
  # ... PlaceholderAPI placeholders are also supported here too!
  action: ":person_biking: {displayname} *{action}*"
```
2021-09-15 14:52:14 +00:00
Josh Roy 767508fb87 Fix bed spawn setting when waking up villagers (#4524)
Fixes #4461.
2021-09-15 15:40:23 +01:00
LaxandJosh Roy c85d772e1b Add /ess dump all as alias for /ess dump * (#4517)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-09-08 17:48:47 -04:00
MDandJosh Roy fde6524e46 Fix error with commands ending in single colon (#4508)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-09-01 13:37:46 -04:00
b2886969f0 Update to bStats v2 (#4350)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-09-01 13:33:43 -04:00
Josh RoyandMD 19ca5186e4 Replace unneeded inputs with new /ess dump in issue template (#4479)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2021-09-01 13:26:54 +01:00
Josh Roy b86a34c909 Prepare for 2.19.1 dev builds (#4505) 2021-09-01 12:55:37 +01:00
MD d145fbc5cc Require forceSign property even on releases 2021-08-31 21:27:35 +01:00
Josh Roy 3ca960e31b Release 2.19.0 2021-08-31 13:04:34 -04:00
MD 8bbaf7da68 Update Gradle to 7.2; set run-paper to 1.17.1 with toolchain (#4496)
:chatting:
2021-08-31 16:53:09 +01:00
Josh Roy 14c2ab5ddd Add online mode data for /ess dump (#4497)
EssentialsX/Website#60
2021-08-31 15:34:10 +00:00
Josh Roy 6f20a789f9 Delete (& backup) invalid NPC accounts created by poor econ code (#4490)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

my magnum opus

---

* Delete (& backup) invalid NPC accounts created by poor econ code

* Add feedback for large migrations

* Improve feedback messages

- Improve the wording of console messages
- Include details about how to resume purging userdata in case it gets interrupted
- Scale the message delay based on the number of userdata files to be processed
- Always cancel the repeating task and shut down the executor when we're done

* Update Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java

* Add less ranty context to rant

At least I had caffeine before writing this
2021-08-31 16:28:44 +01:00
Josh Roy 4bee15956a Fix economy usernames being unsanitized in some places (#4484)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

This PR fixes various issues with NPC accounts:
- Fixes some NPC account names not being sanitised
- Fixes wrong keys being used when manually generating a NPC account file
- Adds some debug logging to `UserMap` name lookups
2021-08-28 17:32:45 +01:00
pop4959andJosh Roy e4c179f5ed Allow auto equip in nbt serialized kits (#4491)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-08-28 11:46:38 -04:00
Josh Roy 4811eb1558 Fix /ess dump on 1.8.8 (#4489)
Fixes #4487

pain
2021-08-27 21:50:41 +01:00
Josh Roy 69850d82c8 Fix NoClassDefFoundError on < 1.12 with Discord execute command (#4478) 2021-08-26 16:12:52 -04:00
Josh Roy df88a89c14 Add missing translation for argument pipe color (#4480) 2021-08-26 16:09:16 -04:00
Josh Roy c746a1ce17 Bump Configurate (#4475)
Some performance improvements, <3 zml
2021-08-24 11:35:53 +01:00
Flask Bot ecdaed7d11 New Crowdin updates (#4281) 2021-08-19 20:11:35 +00:00
CyberKitsuneandMD c062651821 Add option for tab complete to use displaynames (#4432)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

This feature adds a new configuration option, `change-tab-complete-name`

When `change-tab-complete-name` is turned on, `getPlayers()` in `EssentialsCommand` will use `getDisplayName()` instead of `getName()`; populating the list with display names instead of player names.

Closes #4431.
2021-08-19 20:00:06 +00:00
Josh RoyandMD 3692740762 Add /ess dump command to generate a debug dump output (#4361)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

Command usage: /essentials dump [config] [discord] [kits] [log]

Either of the optional args can be used to add the given data to the dump.

Related: EssentialsX/Website#51
2021-08-19 20:35:19 +01:00
Josh Roy 1179caab88 Fix bot-command-relay option not ignoring itself (#4453) 2021-08-12 12:37:19 +01:00
Josh Roy 35b1a284f3 Fix improper MOTD delay check (#4454) 2021-08-10 10:51:33 -04:00
Josh RoyandMD 36432c65e9 Fix NPE on webhooks messages with show-webhook-messages enabled (#4452)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

Fixes #4451.
2021-08-09 21:30:30 +00:00
Josh Roy 871e718f63 Add online/unique placeholders to join/leave formats for discord (#4427) 2021-08-09 16:23:49 -04:00
Josh Roy 42293596f3 Add config option to disable verbose usage strings (#4396)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

```yml
# Whether or not Essentials should show detailed command usages.
# If set to false, Essentials will collapse all usages in to one single usage message.
verbose-command-usages: true
```

This PR also fixes a regression in legacy usage strings where the `<command>` wasn't replaced.
2021-08-09 19:56:38 +00:00
Josh Roy ac8a13f039 Add name parameter for Discord avatar URL (#4369)
Allows offline mode/Geyser users to configure their own avatar URL using a service that accepts usernames.
2021-08-09 19:48:43 +00:00
Josh RoyandMD d56ecaacdc Allow world name fallback for LazyLocation (#4428)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2021-08-09 20:39:55 +01:00
Josh Roy 14fbfe360e Allow disabling MOTD task and cancel on player logout (#4411)
This PR allows setting `delay-motd` to a negative value in `config.yml` to disable the MOTD join task without disabling `/motd`, and fixes an issue where delayed MOTD tasks would run even after a player logged out (in case someone previously set an insanely high MOTD delay to "disable" the MOTD on join).

Fixes #4408.
2021-08-09 20:22:09 +01:00
Josh Roy 5334a3fd34 Move update check message out of MOTD task (#4410)
Update message will now show after mail, and will not be affected by insane MOTD delays.
2021-08-09 19:14:15 +00:00
Josh Roy 2fa9c6486e Fix disabled-commands not removing aliases (#4399)
Fixes #4383, an issue where only the main command name is disabled instead of the alias specified in `config.yml`.
2021-08-09 18:59:36 +00:00
Josh Roy c221e96122 Make discord chat format world name use world alias (#4419) 2021-08-09 19:57:03 +01:00
Josh Roy 26c016fea2 Add config option to allow webhooks/bots to send commands (#4442)
```yml
# Console relay settings
# The console relay sends every message shown in the console to a Discord channel.
console:
  ...
  # Set to true if bots/webhooks should be able to send commands through the command relay.
  bot-command-relay: false
```
2021-08-09 18:48:01 +00:00
Josh Roy b6a08ec712 Fix force-disable-teleport-safety enforcing world border (#4371) 2021-08-09 18:44:13 +00:00
Josh Roy 9179c6c1e2 Update EssXDiscord tutorial for new developer mode location (#4409) 2021-08-09 19:38:04 +01:00
Josh Roy 3f9aceb922 Add DiscordService#sendChatMessage
Allows plugins to send messages to the chat channel via API using the format from our config
2021-08-07 17:37:02 -07:00
Josh Roy 3da8efae49 Fix incorrect parameter name for allowGroupMentions 2021-08-07 17:37:02 -07:00
Josh Roy b84207f955 Fix regression in invalid home check (#4425) 2021-08-02 11:23:40 -04:00
1stGlitchandJosh Roy f57de40668 Fix invalid mob data in spawnmob on versions < 1.17 (#4376)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-08-02 10:33:49 -04:00
Daniil ZandJosh Roy e95df57642 Fix barrier considered as unsafe block to teleport (#4348)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-08-02 10:22:07 -04:00
Josh Roy aaddb2af1f Fix remaining Configurate nullability issues (#4370) 2021-08-02 12:32:41 +00:00
Josh Roy 082950cc18 Fix bStats becoming a transitive dependency (#4426) 2021-08-02 13:21:53 +01:00
triagonal 90828965df Fix NPE during reload with invalid discord bot tokens (#4417) 2021-07-31 10:19:20 -04:00
Josh Roy 17e026f9aa Require MessageType's start with a letter
May cause issues with YML serialization as it can get finicky with keys starting with numbers.
2021-07-28 16:24:09 -07:00
Josh Roy f1d7e50dc0 Don't warn about unregistered channel-id based MessageType's
You shouldn't be registering these, there is no point for a user to configure where these go because it would be ignored.
2021-07-28 16:24:09 -07:00
Josh Roy e2cb7b2102 Fix advanced relay breaking when multiple channels were defined 2021-07-28 16:24:09 -07:00
Josh Roy 28578a5d8a Fix some typos in the tutorial 2021-07-28 16:24:09 -07:00
Josh Roy 0d8e90b8da Move FakeServer to 1.8 provider (#4395)
Moving this removes the need to needlessly update the FakeServer class with new methods when upstream changes it.
2021-07-27 21:23:20 +01:00
Josh Roy 1556b911b2 Fix webhook messages being parsed unnecessarily (#4346)
This wasted CPU time parsing responses from Discord that we will never use, plus caused problems when parsing webhooks that our library couldn't parse correctly (MinnDevelopment/discord-webhooks#36).
2021-07-11 20:36:46 +01:00
MD eed73e3157 Cache overridden-commands and player-commands in Settings (#4345) 2021-07-11 12:25:25 -04:00
triagonal c020526b1e Strip formatting in webhook name (#4344) 2021-07-11 09:57:46 -04:00
Josh Roy cca98e817f Add show-displayname option 2021-07-10 11:08:51 -04:00
Josh Roy 46a8a7d04c Remove join/quit message null checks
If you don't want to see them, don't configure them.
2021-07-10 11:08:51 -04:00
Josh Roy d4ddc87d81 Fix less strict length check on messageQueue add rather than iteration 2021-07-10 11:08:51 -04:00
Chew b473460ffc Use native Discord timestamps for console timestamps 2021-07-10 11:08:51 -04:00
Josh Roy db027bced3 Add option to log channels to console 2021-07-10 11:08:51 -04:00
Josh Roy 12cc0b99f9 Fix some minor grammer issues in config 2021-07-10 11:08:51 -04:00
Josh Roy 15cac529c5 Improve death message validation
Now takes gamerules in to account and will not send blank or null death messages.
2021-07-10 11:08:51 -04:00
Josh Roy feed2d6a45 Fix improper bitwise logic in DiscordUtil#getRoleColorFormat 2021-07-10 11:08:51 -04:00
Josh Roy b516db669c Fix prefixes/suffixes not stripping hex format 2021-07-10 11:08:51 -04:00
Josh Roy 3bc58264ea Add advancement message type 2021-07-10 11:08:51 -04:00
Josh Roy 0a0416ae38 Fix disabling a single command causing all commands to be disabled 2021-07-10 11:08:51 -04:00
Josh Roy d244956b4b Ensure no JDA code is called during invalid shutdown 2021-07-10 11:08:51 -04:00
Josh Roy 37e3730e9d Add fake vanish join/leave + exclude vanished messages 2021-07-10 11:08:51 -04:00
triagonal 48532a0ae6 Fix NPE in UserData#getLogoutLocation (#4341) 2021-07-10 09:17:26 -04:00
Josh Roy 0b6b0f9c61 Fix NPE with custom join messages (#4334)
This fixes a bug in #4290 in which custom join messages would throw a NullPointerException if the user hasn't changed their username.
2021-07-09 16:33:44 +00:00
Josh Roy 435ac80ff9 Fix delay not working in /spawn (#4335) 2021-07-09 11:55:52 -04:00
triagonal a098055fc9 Fix NPE when teleporting offline players (#4332) 2021-07-08 09:24:40 -04:00
cba5063b33 Add config option to change discord show-avatar url (#4330)
Co-authored-by: T41US <adityakomp@gmail.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-07-07 19:55:43 +00:00
AnsandrandJosh Roy 3787b80286 Add UserTeleportSpawnEvent (#4328)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-07-07 14:52:49 -04:00
pop4959andJosh Roy 2858bd3784 Add role placeholder to Discord message format (#4329)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-07-07 11:55:25 -04:00
OliviaandJosh Roy ff58d8e443 Add config option for new username join messages (#4290)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-07-07 15:46:54 +00:00
Josh Roy 5e178943a0 Update to Minecraft 1.17.1 (#4324) 2021-07-06 18:34:40 +01:00
triagonal 33ca3b2981 Fix behavior of itemdb command on legacy versions (#4320) 2021-07-05 21:29:24 -07:00
triagonal 8c8c85d753 Fix server shutdown not setting logout location (#4318) 2021-07-05 15:47:42 -04:00
Josh Roy 6535edf4e0 Fix MailMessageSerializer not handling null UUIDs (#4314) 2021-07-03 19:32:34 -04:00
Josh Roy 7e25570344 Remove usage of java.awt.Color (#4312)
This fixes issues on headless JREs which lack java.awt classes.
2021-07-03 20:46:09 +01:00
Josh Roy 409210ccde Fix issues with 3rd-party Vault economy providers (#4303)
Turns out that when you depend on a plugin, get this, the plugin will load before you. So we need to rework how we do things here.

Fixes #4075
Fixes #4304
2021-07-03 16:54:01 +01:00
Josh Roy 555a62c582 Fix baltop something having a null name for offline players 2021-07-03 01:04:59 -04:00
Josh Roy 36a070be4c Fix Configurate conversions not saving 2021-07-03 01:04:59 -04:00
Josh Roy d903e40a53 Fix weird behavior with legacy mails (#4307) 2021-07-02 21:52:49 -07:00
Josh Roy d78729f343 Disallow empty discord filters (#4298) 2021-07-01 15:59:35 -07:00
Josh Roy a55289196b Add server start/stop messages 2021-07-01 15:56:46 -04:00
Josh Roy 3505b47d8f Fix vanilla command execution on discord 2021-07-01 15:56:46 -04:00
Josh Roy 11836e6662 Ensure MessageFormat's are clean before parsing 2021-07-01 15:56:46 -04:00
Josh Roy 4ce38bde36 Allow uppercase variables 2021-07-01 15:56:46 -04:00
Josh Roy 711f701d62 Fix typo in mail tab complete (#4296) 2021-07-01 19:22:48 +00:00
triagonal 94c509b1e2 Prevent exception when retrieving enchantments (#4297) 2021-07-01 15:18:05 -04:00
triagonalandJosh Roy 5c08a0e72c Ignore case when matching custom item aliases (#4295)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-07-01 16:30:08 +00:00
Josh Roy 9c451271e0 Rework Mail System (#3710)
* New `/mail sendtemp <time diff> <message>` command to send mail that will self-destruct after time diff.
* New `/mail clear <number>` command to clear a specific mail item.
* `/mail read` now tracks which mails you read and won't nag you about them.
* A bunch of other flexibility since we store actual data instead of strings
2021-07-01 11:23:32 -04:00
Josh Roy 55db6c2476 Fix afk list NPE (#4294) 2021-07-01 16:15:24 +01:00
0861427bf3 Discord Module (#3844)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
Co-authored-by: pop4959 <pop4959@gmail.com>
Co-authored-by: Riley Park <riley.park@meino.net>
Co-authored-by: Jason <11360596+jpenilla@users.noreply.github.com>
2021-07-01 09:43:35 -04:00
Josh Roy 9d3bf337e1 Improve itemlore and itemname tab completion (#4280)
- Improve tab completion for itemname and itemlore
- Improve air type matching
- Fix incorrect /itemlore usage messages
2021-06-28 20:19:02 +01:00
Flask BotandMD 4e4eb35240 New Crowdin updates (#4182)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2021-06-27 14:46:04 +01:00
Josh Roy e7e3f58c8f Fix missing translation param in ice command 2021-06-27 14:40:14 +01:00
pop4959andJosh Roy c18830ce75 Fix pickup delay with essentials.build.pickup permission (#4273)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-06-27 02:36:28 +00:00
triagonal 3f36a52685 Prevent NPEs in /powertool (#4276)
This PR fixes a long-standing bug where using the `a:` or `r:` modes in `/powertool` with no commands already set on the item would result in an NPE. To prevent this, the powertool command list is now immediately initialized if it doesn't exist upon retrieval.
2021-06-26 20:11:03 +00:00
Josh Roy 64eb39a417 Add serialization support to kits (#3248)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

This PR makes use of Paper's item serialization to serialize items into base64 allowing for all items to be properly stored without needing to manual write in special cases for every complex item. There is a config option to disable using this new serialization and use the legacy ItemDB serializer since the new serializer removes the ability to manually edit/read kits.

Defaults to not enabled

Note: The new serializer places an @ sign in front of items serialized by the new format in order to quickly determine what is serialized by the new serializer and also to retain backward compatibility with the old serializer.

Att #3114
Att #2867
Att #1694
Att #31
Att #1283
2021-06-26 20:03:27 +00:00
MD 736ecae5ff Add missing FakeServer methods 2021-06-26 20:34:04 +01:00
Flask Bot 6bd4a6a037 Update items.json from generator 2021-06-26 20:34:04 +01:00
Josh Roy 4271938c3a Add ice commmand #EasterEgg 2021-06-26 20:34:04 +01:00
Josh RoyandMD e1441c1d33 Update mobs and NMS usage to 1.17
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2021-06-26 20:34:04 +01:00
triagonal 9bd35a2cd2 Fix regression in /back behavior (#4264) 2021-06-23 08:44:47 -04:00
triagonal d4e194fbf7 Fix NPE when RTP center isn't set (#4251) 2021-06-19 12:23:00 -04:00
Josh Roy 003d556f3d Fix incomplete cooldowns being added to data (#4249) 2021-06-18 19:28:10 -04:00
triagonal 1da3b3f08e Implement home cleanup utility (#4244) 2021-06-18 11:24:43 -04:00
Josh Roy f08c176b68 Fix invalid home locations giving blank errors (#4240) 2021-06-17 20:28:07 +01:00
Josh Roy 780aadedf2 Fix invalid empty value for BigDecimal (#4237) 2021-06-14 22:15:25 -04:00
Josh Roy 3fe279e229 Fix NPE in UserData#getHome (#4234) 2021-06-14 07:28:42 -04:00
Josh Roy c43af5d3d9 Fix NPE for list group (#4230) 2021-06-12 14:34:58 -07:00
DebugandJosh Roy 65b80200bd Fix incorrect behavior from last PR (#4228)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-06-12 14:14:19 -04:00
DebugandJosh Roy 6e5a41a880 Add option to hide balances <=0 from baltop (#4226)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-06-12 12:31:39 -04:00
Daniil ZandJosh Roy fa87c74b56 Allow enchants on fireworks/charges/potions/banners (#3882)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-06-11 11:13:43 -04:00
antiPerson 8142da608c Fix damage from exploding beds being cancelled (#3874) 2021-06-11 15:06:12 +00:00
Debug eacc748b09 Fix /book command not supporting multi-word title & author (#4180) 2021-06-11 15:00:21 +00:00
Josh Roy def41802b8 Fix broken cooldowns causing exceptions (#4219) 2021-06-09 21:20:51 -04:00
Josh Roy 38dcdff659 Fix invalid mail key (#4218) 2021-06-09 17:03:29 +01:00
Josh Roy bd9540cea3 Fix EssentialsSpawn not defaulting on default group (#4217)
Fix regression from #4072, tested in #essx-support on MOSS
2021-06-09 14:54:47 +01:00
triagonal 0d71fe65f6 Prevent getting jail worlds before world load (#4216) 2021-06-09 08:34:14 -04:00
Josh Roy 54c70581a2 Add config option to respawn jailed players to spawn (#4132) 2021-06-08 21:04:30 -04:00
pop4959 3abddd6f84 Fix tab complete on /enchant failing on empty key (#4215) 2021-06-08 20:50:17 -04:00
Josh Roy 8e712bc827 Fix empty paths with Configurate (#4214) 2021-06-09 00:42:45 +01:00
Jason 2018149395 Clean up Bukkit API exclusion, use dep constraint for SnakeYAML (#4213) 2021-06-08 23:36:43 +01:00
Josh Roy ecea17195a Fix parsing of command cooldowns with invalid data (#4212) 2021-06-08 16:51:47 -04:00
Josh Roy a28d0823b2 Fix invalid CommandCooldowns causing NPEs (#4210)
* Fix invalid CommandCooldowns causing NPEs

* Add Map support
2021-06-08 12:40:52 -04:00
Josh Roy 6e6561a267 Minimize (#4209) 2021-06-07 23:48:18 -04:00
Josh Roy 8b1db22a53 Fix setting of locations to LazyLocations + Shade CheckerQual (#4208) 2021-06-07 23:30:37 -04:00
Josh Roy 3d09ea386b Fix remaining uses of Location over LazyLocation (#4206) 2021-06-07 22:23:13 +00:00
Josh Roy 300daea4f9 Lazy load locations in Configurate (#4203) 2021-06-07 15:52:12 -04:00
Josh Roy 7653da0e4f Migrate to Configurate (#4072)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
Co-authored-by: Riley Park <riley.park@meino.net>
Co-authored-by: zml <zml@aoeu.xyz>

Migrates all uses of SnakeYAML and Bukkit's Configuration API to Sponge's Configurate.

Configurate enables us to the do the following stuff:
* Serialize YAML off the main thread
* (in the future) Automatically update our config
* (in the future) Manipulate comments in configs
* Be epic

This commit also *finally* strips out the 3.x storage/object mapping system in favour of Configurate's object mapper.
2021-06-07 13:49:33 +01:00
JasonandJosh Roy 19b4da07b9 Clean up gradle build scripts
Adds run-paper plugin support as well as moves majority of logic to kotlin build scrips

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-06-07 08:17:39 -04:00
Josh Roy ff9f93fba4 Fix sleeping ignored vanish setting overriding permission (#4201) 2021-06-06 16:12:07 -07:00
Josh Roy fed26071ac Add option to ignore vanished players from sleeping (#4200) 2021-06-06 17:05:16 -04:00
Josh Roy a4fbfbef02 Fix offline player account creation (#4199)
This bug occurs when a player has joined before EssentialsX was installed and a player account creation is requested through Vault while they are offline.

Fixes #4195
2021-06-06 19:40:55 +01:00
Josh RoyandMD 347751a826 Fix branch name when building main branch on GH (#4193)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

Fixes #4191.
2021-06-03 17:03:14 +01:00
Josh Roy fbb3a9a0a4 Fix various NPC account lookup issues (#4188) 2021-06-01 15:47:39 -04:00
Josh Roy f8f1facf43 Check legacy "NPC:" UUIDs in VaultEconomyProvider#hasAccount (#4178) 2021-05-31 15:12:06 +00:00
Flask BotandMD 0d6441d0b6 New Crowdin updates (#4145)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2021-05-29 23:18:25 +01:00
Josh Roy a009b2f9b5 Only use convert spawners tagged by the give command (#3963) 2021-05-28 14:29:52 -04:00
Josh RoyandMD 7852aedabd Add book page serialization (#4048)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

Allows serialization of books in `/createkit`. Closes #31.
2021-05-28 18:52:18 +01:00
Josh RoyandMD b2d7d1b4cb Add prevent-type permissions for /itemname (#4133)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

Adds `essentials.itemname.prevent-type.<material_name>` permissions. Closes #4044.
2021-05-28 17:35:33 +00:00
Josh Roy ec50b28f4b Add ItemStack 1.8.8+ cross-version PersistentDataContainer (#4143)
This PR itself does nothing on its own but creates the underlying backbone I need to make a less hacky solution in #3963 lmfao.

This PR creates a provider which uses NBT on 1.8.8-1.13 to mimic the exact structure of a PersistentDataContainer on 1.14+ which will allow us make any possible upgrades (which don't die from the lack of DFU on >1.13) work as expected. Additionally, this does not use reflection on modern Minecraft versions and thus will not need to be maintained/updated on MC version updates.

In the future, we will need to find a way to store data on tile entities (signs namely) so that we are able to store UUIDs on signs for future plans, but for now ItemStacks work to fix our spawner issues.
2021-05-28 11:23:44 +00:00
Josh Roy 4ed36d9bbe Add support for dev/rc/beta/alpha build types (#4177)
🍍
2021-05-27 21:16:26 +01:00
l1ttleO 887772a428 Add {PREFIX} and {SUFFIX} to custom join/quit messages (#4169) 2021-05-21 13:28:22 -04:00
MDandJosh Roy e9169b890d Set Gradle toolchain to JDK 16 (#4168)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-05-21 13:15:47 +00:00
MDandJosh Roy b716c04d96 Fix minor issue with server support minor issue fix (#4166)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-05-21 07:24:39 -04:00
l1ttleO 2d3cbc9783 Fix Java version command in pull request templates (#4167) 2021-05-21 07:15:05 -04:00
Josh Roy 23a497abf6 Update Gradle to 7.0.2; misc build improvements (#4148)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

* Bumped Gradle to 7.0.2
* Now uses Java 16 for compiling to prepare for MC 1.17
  * Release target is still set to 8 however
* Bumped Actions versions
* Bumped indra version(s)
* Switched from grgit to indra-git
* Fix PR GitHub Actions jars having invalid branch names
2021-05-19 12:34:00 +01:00
Pretty Princess Sailoriae d00b90cd61 Fix formatting issue in with comma-separated lists (#4157) 2021-05-17 10:12:14 -04:00
Jason f4fe8808a4 Remove the /itemdb alias suggestion (#4152) 2021-05-14 19:26:23 -04:00
Josh Roy 02193b0523 Fix ClassNotFoundException when vault is missing (#4151) 2021-05-14 17:33:25 +01:00
Josh Roy c9310ea429 Fix a minor issue with support status detection (#4150) 2021-05-14 16:01:50 +00:00
Josh Roy c76e4239bb Ensure NPC userdata creation for Citizen NPCs (#4149)
https://github.com/CitizensDev/Citizens2/blob/0ddfcbd0fa99678934e7b086f1a7b92e6a1bc49e/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/HumanController.java#L36-L40

why????????????????????????????????????????????????????????????
2021-05-13 22:30:37 +01:00
Josh Roy 8a8d45c91e Fix vault provider account creation (#4147)
Removed an extraneous name->uuid check
  This check swallowed any warnings spit out by UserMap#trackUUID causing problems
  for plugins which didn't check the success of this method and thus leading to
  possible stack overflow exceptions or other unexpected behavior

Actually save accounts created with VaultEconomyProvider#createPlayerAccount
  Complete oversight as I was under the impression tracking the UUID would create
  the files for us.
2021-05-13 00:52:26 +00:00
Josh Roy 071f99560e Migrate to new Vault provider and economy integration (#3975)
This commit:
- Implements a new provider for VaultAPI's `Economy`
  - The legacy provider built into Vault uses player names, and has not changed since Vault was invented in 1864.
  - This properly supports UUIDs and works more predictably with EssentialsX.
- Replaces the Register method economy abstraction layer abstraction layer with a new `EconomyLayer` economy abstraction layer abstraction layer.
  - This opens the pathway for future economy abstraction layers to be supported.
  - This change also removes dubiously-licensed code from the project.

For users encountering userdata issues on this build, see this FAQ entry:
https://github.com/EssentialsX/Essentials/issues/3956#issuecomment-779254544

Fixes #4110.
Closes #3344.
Closes #2401.
2021-05-10 19:36:09 +00:00
Josh Royandtriagonal 8b23c2c4cd Add helpful command argument descriptions (#4057)
Co-authored-by: triagonal <10545540+triagonal@users.noreply.github.com>
2021-05-10 18:36:30 +00:00
Josh Taylor 0d640d1215 Fix netlify deploy message (#4141) 2021-05-05 09:36:43 -04:00
MD b4d63857ce Fix Javadoc preparation step (#4140) 2021-05-05 08:32:49 -04:00
Josh RoyandMariell Hoversholm 4976484d91 Unformat item lore and name when serializing items (#4095)
Co-authored-by: Mariell Hoversholm <proximyst@proximyst.com>
2021-05-02 16:18:35 -04:00
Josh Roy 16627d10b6 Add way for time and weather signs to display the time/weather (#4131) 2021-05-02 19:34:38 +00:00
Josh Roy 83ca7d2574 Prevent stack overflow when finding an EssX cmd as alternative (#4128) 2021-05-02 15:08:18 -04:00
Josh Roy 956afc0382 Fix kit-auto-equip not working on versions < 1.14 (#4130) 2021-05-02 19:03:43 +00:00
MDandJosh Roy 312ff110ec [CI-SKIP] Automatically publish 2.x javadocs (#4083)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-05-01 16:37:24 -04:00
NotMyFault 214d07c468 [CI-SKIP] Address template deprecations (#4125) 2021-04-25 16:45:04 +00:00
l1ttleOandJosh Roy 2c68d1b866 Fix /pay working on vanished players (#4114)
Vanished players are now treated as offline players.

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-04-18 10:08:07 -04:00
Josh Roy 5171215817 Fix various trade sign issues (#4056)
Fixes various bugs with line length validation and fixes issues with overfilled stacks.
2021-04-14 12:24:24 -04:00
Jason 195148a2a2 Fix disabled commands not marking the command map as dirty (#4108) 2021-04-12 19:08:19 -04:00
pop4959andJosh Roy edf664e19a Unregister disabled commands (#4026)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-04-07 21:37:21 -04:00
Suyeol Jeon 0668e4c48a Fix regression in permission based item spawning (#4100) 2021-04-03 11:38:04 -04:00
triagonal d75787726c Add some extra keywords (#4098) 2021-03-29 13:07:55 -04:00
triagonal 1e0d7fb0a3 Add missing respawn-at-home-bed config option (#4071)
This PR adds a missing config option that was accidentally left out in #3802 (thank you @mibby for the heads up).
2021-03-20 16:09:59 +00:00
Josh Roy 454698bf98 Fix delayed futures never being completed correctly (#4066)
This PR fixes issues related to timed teleports. All timed teleports before this PR would create their own future to send back to the AsyncTeleport api causing the futures returned in the base methods never being completed.

This PR pases the future returned in AsyncTeleport methods to the timed teleport and completes it after the timed teleport has been completed.

Fixes #4065.
2021-03-20 15:48:40 +00:00
Josh Roy bfeb0ef2ad Fix update check on 3+ digit build numbers (#4069) 2021-03-19 00:14:45 -04:00
Josh Roy 1cf2b11f1e Improve currency validation in signs (#3987)
Sanitise currency strings in more cases when handling signs. Should fix #3979.
2021-03-13 16:36:18 +00:00
Josh Roy 0ce4029483 Change createkit to use paste.gg (#4049) 2021-03-09 21:28:07 -05:00
Josh Roy 504e82e9fd Update FakeWorld (#4043) 2021-03-08 15:39:52 -08:00
Josh Roy eca6a9f247 Improve update checker startup message logging (#4039)
* Use plugin logger for update checker
* Use info log level when update checker is disabled

Closes #4038.
2021-03-06 23:52:10 +00:00
Josh Roy 94d4385d41 Fix list command exposing groups with vanished players (#4032)
Fixes #4018.
2021-03-06 19:42:59 +00:00
Josh Roy c41bb0ec42 Delay GeoIP join task to ensure display name is set (#3968)
Delays the GeoIP join messages by 2 ticks to ensure the user's display name has been set by Essentials' `DelayedJoin` task.

Fixes #2388.
2021-03-06 19:22:00 +00:00
Josh Roy 00b71cafc9 Add netherite related items to spawnmob (#4031)
Allows spawning skeletons and zombies with netherite armor and swords. Fixes #4029.
2021-03-06 19:06:31 +00:00
MD 3cd69dabe9 Fix metrics initialising before update checker (#4037)
This PR fixes an issue introduced in #3855, where bStats inits before the update checker and so fails to grab the current release branch.
2021-03-07 03:58:35 +11:00
Josh Roy 10fa3b5a31 Add update checker (#3855)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

Adds an update checker to Essentials that will check for the latest version on startup, on player join (permission is `essentials.updatecheck`), or manually with `/ess version`.

On startup, the update checker will fetch build information from a resource generated at compile time and determine if the build is a dev or stable build. If it's a stable build, the update checker will only check for a new release; while a dev build will check for any new commits.

There are 6 different types of messages the update checker will return;
* Identical: The current build is the latest stable release or latest dev build. This message is only shown in the `/ess version` command.
* Behind: If the current build is stable, it's an entire stable build behind, otherwise it's one or more dev builds behind.
* Diverged: The current build was made from a branch other than `2.x` and is also one or more dev builds behind the latest commit on `2.x`.
* Diverged Latest: The current build was made from a branch other than `2.x` but is based on the latest commit from `2.x`.
* Unknown: The current build either has invalid build information or was customly built. This message is show everywhere but on player join.
* Error: There was an error while fetching the latest version information.

Update checks can be disabled using the `update-check` option in `config.yml`.
2021-03-06 16:29:42 +00:00
Josh Roy b43790e9d2 Fix failing gradle build (#4025)
Paper has recently added adventure as a first part library, and thus has shaded it in. Adventure is on maven central, so we must add it in order for Essentials to build.
2021-03-02 04:52:05 +00:00
Frank van der HeijdenandJosh Roy ee5f4b9b42 Fix some futures in AsyncTeleport never completing (#4016)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-02-27 11:19:28 -05:00
MD e5db842dd6 Document XMPP config and require server TLS by default (#4002)
Small changes related to #3962.
2021-02-21 10:46:32 -05:00
Josh Roy 657a11b1f4 Add permission to adjust delay in /spawner command (#3964) 2021-02-21 08:59:57 -05:00
Josh Roy 0ef1e23c69 Show UUIDs in /seen (#3906) 2021-02-21 13:57:19 +00:00
Josh Roy 141512f2f7 Fix afk message being sent when event cancelled (#3915) 2021-02-21 13:54:02 +00:00
Josh Roy 671983724a Fix invalid configuration section for world aliases (#4000) 2021-02-21 00:09:34 -05:00
Josh Roy 81571fc014 Add /toggleshout command (#3965)
Adds a command to toggle shout mode of a user allowing them not to have to prefix all their messages with `!` if they're shouting a lot.

Closes #3958.
2021-02-20 16:37:16 +00:00
Frank van der Heijden f806409d80 Add support for commands.yml aliases in command cooldowns (#3744)
Fixes #2058.
2021-02-20 16:30:07 +00:00
Josh RoyandMD f3c347424b Add new signs for recently added virtual commands (#3961)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

Closes #3919.
2021-02-20 15:59:30 +00:00
Josh Roy 8cbd9b7935 Add /editsign copy + paste (#3989)
Adds `copy` and `paste` subcommands to `/editsign` to allow users to copy and paste the contents of signs without having to use the tab-completion output of `/editsign set`.

The `/editsign copy` command will also perform format-permission checks to ensure the user copying the sign has the proper permission to use said formatting.

Closes #3973.
2021-02-19 15:13:49 +00:00
Josh Roy 158d273dbe Allow blank currency symbols (#3946)
Fixes a behavior previously possible before c6de771

I wrote the proper logic in c6de771 to handle blank currency symbols but forgot to remove the Settings check that was preventing blank currency symbols.

Fixes #3945.
2021-02-19 14:58:42 +00:00
Josh Roy 1258d2f0a1 Fix further incorrect messages for jail countdowns (#3941)
Fixes #3939, fixes #3940.
2021-02-16 13:56:57 +00:00
Josh Roy 36422ab22b Add Baltop API (#3702)
Co-authored-by: Mariell <proximyst@proximyst.com>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>

This moves storage of balances from the baltop command into the UserMap. This was needed by Glare to able to get a hold of all users balances without causing jvm hell on the usermap.

To access this API as an end user;
```java
import net.essentialsx.api.v2.services.BalanceTop;
//...
BalanceTop api = Bukkit.getServer().getServicesManager().load(BalanceTop.class);
```

Closes #3100, closes #3540
2021-02-15 15:43:10 +00:00
MD 191cea7fb3 Update issue template for new config format (#3982)
This PR updates the issue form to the new schema, which will be introduced on February 19th.

The legacy issue form will be used until the switchover date.
2021-02-15 11:51:26 +00:00
Josh Roy f8cc0e41d1 Update FakeWorld (#3985) 2021-02-12 17:36:38 -08:00
MD f29f9e1aba Relocate GeoIP dependencies (#3976)
Relocates jackson, javatar and the MaxMind database libraries to avoid conflicts with other plugins.
2021-02-10 09:27:56 -05:00
MD 34fdcf8f6f Improve logging of dangerous userdata edge cases (#3969)
This commit improves the logging of a few edge cases that can lead to loss of userdata:
- Third-party plugins forcing creation of a NPC account which could not be found on the usermap, even if the account file exists
- UserMap#trackUUID being called with a conflicting UUID but replace set to false
2021-02-08 15:50:57 +00:00
Josh Roy adef08af3e Add world aliases for Chat (#3912)
Allows world names to be overridden with a defined value from the config in EssentialsX Chat.

Closes #1793.
2021-02-05 20:31:13 +00:00
Josh Roy 1301e8fc99 Show custom item aliases in /itemdb (#3907)
Shows items from the custom_items.yml resolver in /itemdb.

Closes #3686.
2021-02-05 20:27:33 +00:00
Josh Roy eee1c0628b Add API packages (#3938)
Adds packages for future EssentialsX 2.x API and services.
2021-02-05 20:13:03 +00:00
MD 9ce839c9d8 Update issue chooser with forms and Discussions links (#3936)
Co-authored-by: triagonal <10545540+triagonal@users.noreply.github.com>
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>

This commit:
- Introduces a bug reporting form
- Moves feature requests and support requests to Discussions
- Adds a default PR template instructing people to use the correct template
- Adds GitHub Sponsors and Open Collective to the Sponsor button
2021-02-05 19:58:26 +00:00
Josh Roy 401fc4d2eb Fix AntiBuild performing place checks for block state changes (#3914)
Bukkit sometimes decides that state changes are also a block placement. Work around this by comparing the previous block state with the new block type.

Fixes #3150.
2021-02-05 19:44:43 +00:00
Josh Roy 4e7f1377bf Infer jail name when the server only has one jail configured (#3911)
Closes #1122.
2021-02-05 19:39:20 +00:00
Josh Royanddarbyjack fccf796eeb Add /kitreset command (#3909)
Co-authored-by: darbyjack <admin@glaremasters.me>

Closes #163. Ports #2645, which had its branch messed up by merges.
2021-02-05 19:35:38 +00:00
Josh Roy 500edb7860 Add config option to disable message social spy (#3910)
Adds a config option to allow to prevent socialspy from reading private messages. Useful if all you want from social spy is to read commands defined in the list.

Closes #1095.
2021-02-05 19:02:26 +00:00
pop4959andJosh Roy cb168b0c8a Add support for material tags in enchant signs (#3694)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>

Closes #3653.
2021-02-05 18:57:32 +00:00
Simon 74131f816c Update VersionUtil Support Status Check (#3954)
Removed AirplaneLite as an unsupported fork as they removed their concerning patch.
2021-02-02 09:15:01 -05:00
luisc99andJosh Roy f1ef21eb0d Fix duplicate nickname check with custom display names (#3922)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-01-28 10:08:38 -05:00
DaniilandJosh Roy 79406613c1 Add /alts alias for /seen (#3931)
Adds `essentials.alts` also to allow server admins to not give mods the ips of users.

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-01-28 09:29:50 -05:00
Josh Roy 89c1e3b60d Fix invalid formatted jail time strings in whois/seen (#3917)
Fixes #3916, a "slight" discrepancy on jail time reporting in whois and seen commands
2021-01-23 17:41:24 -05:00
224634d8d2 Improve VersionUtil version checks (#3812)
Co-authored-by: Dylan Xaldin <Puremin0rez515@gmail.com>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2021-01-19 07:40:05 -05:00
Josh Roy e42d37e86d Fix various problems with firework meta (de)serialization (#3905)
Adds support for parsing fireworks charges (would previously cause exceptions due to illegal casts to FireworkMeta) and fixes createkit from producing invalid color values

Thanks to triagonal for reporting the exception caused by createkit.

Fixes #1283.
2021-01-19 07:30:09 -05:00
DaniilandJosh Roy 191c9e9302 Fix bPermissions not showing up as unsupported (#3928)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-01-18 21:22:04 -05:00
Daniil a482ddf0f6 Fix warp deleting message not showing (#3927)
Fixes #3926
2021-01-18 21:18:13 -05:00
Josh Roy 7245e84f00 Update to 1.16.5 (#3923) 2021-01-16 01:56:56 -05:00
DaniilandJosh Roy e287600c34 Add option to disable custom server-full message (#3899)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-01-11 00:10:07 -05:00
vk2gpz 817585a9a6 Properly detect enchantments with improper names (#3900) 2021-01-09 15:26:07 -05:00
triagonal eae8bc05c9 Prevent sending usage info when executing alternative commands (#3904) 2021-01-09 13:47:58 -05:00
Josh Roy 026d279556 Add option to only count online jailed time (#3705)
Adds `jail-online-time` config option to only count a player's online time to the jail cap.

This also fixes memory leak in `EssentialsTimer` which I found in the process of testing.

Closes #429.
2021-01-08 20:43:32 +00:00
Josh Roy 8177893e28 Add auto armor equip for kits (#3629)
Adds a config option to have player auto-equip armor from kits.

Also adds a performance improvement for all uses of EnumUtil#getAllMatching by switching it to an EnumSet.

Closes #347, closes #3452.
2021-01-08 20:39:32 +00:00
Josh Roy 70de83827d Allow paying offline users via /pay (#3625)
Closes #3577.
2021-01-08 20:35:00 +00:00
JoshuaandJosh Roy 5af9536db1 Add WarpModifyEvent (#3875)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-01-08 15:20:03 -05:00
Josh Roy cb7b2dabf5 Block removing hats with curse of binding using direct-hat (#3895)
Fixed loophole that allowed users to bypass curse of binding when using direct hat feature.

Related to #3299, which added similar checks to `/hat`.
2021-01-08 20:11:35 +00:00
LuukandJRoy 01987a0374 Fix AbstractItemDb#getMatching improper block filtering (#3691)
Co-authored-by: JRoy <joshroy126@gmail.com>
2021-01-03 12:45:32 -05:00
Josh Roy 5528b5b92c Allow users to middle click in armor invsee (#3893) 2021-01-02 15:36:12 -05:00
RyanandJosh Roy 1838354b70 Add ending user balance as a final field in trade.log (#3721)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-01-02 00:12:53 -05:00
1f1edd9b23 Add UserKickEvent (#3490)
Co-authored-by: MD <1917406+md678685@users.noreply.github.com>
Co-authored-by: JRoy <joshroy126@gmail.com>
2021-01-02 00:04:13 -05:00
kmecppandJRoy 2b7a4364b1 Don't lowercase item names in IItemDb#get(String, int) (#3636)
Co-authored-by: JRoy <joshroy126@gmail.com>
2021-01-01 23:16:24 -05:00
2f45d3edf3 Add API to register enchantment names/aliases (#3669)
Co-authored-by: MD <1917406+md678685@users.noreply.github.com>
Co-authored-by: JRoy <joshroy126@gmail.com>
2021-01-01 23:11:22 -05:00
Olivia a20d20574e Add TransactionEvent for successful transactions (#3649) 2021-01-01 22:45:52 -05:00
pop4959andJosh Roy d78832498e Fix warp messages sending incorrectly with teleport delays (#3696)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-01-01 22:06:09 -05:00
MDandJosh Roy e2c6170eba Add option to disable players respawning at beds (#3802)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-01-01 22:02:55 -05:00
powercas_gamerandJosh Roy 6864af762c Update plugin.yml (#3832)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
2021-01-01 21:58:59 -05:00
OmegaWeaponDev a8daa2160f Separate option to cancel afk on chat from interact (#3863) 2020-12-31 19:20:08 -05:00
triagonal b84aaa1dab Fix behaviour of /list when showing real names (#3858) 2020-12-31 19:14:39 -05:00
Josh Roy c6de77130f Rework Currency Symbol Parsing (#3628)
* Made currency symbol standardized in Kit 
  * Kits should be in a standardized format.
  * Having monetary rewards in kits should not break based off of a config value oriented around currency display (suffix).
  * Additionally, the dollar sign should be the standard money symbol which works on all servers and should not be tied to the server's individual currency symbol.
  * Note that the server's individual currency symbol will still work but probably should not be used due its volatility.
* Reworked config currency symbol parsing in Settings 
  * Simplifies the number of actions needed to sanitize the input.
  * Now just defaults to `$` if the currency-symbol cannot be parsed.
* Removed symbol-suffixed parsing in NumberUtil#sanitizeCurrencyString
2020-12-30 15:37:30 -05:00
Josh Roy 02ba924f33 Fix AlternativeCommandsHandler not detecting some aliases (#3856) 2020-12-30 11:59:38 -08:00
Josh Roy 318df64e54 Fix Improper Use of Event API (#3853)
* Fix Improper Use of Event API

* Checkstyle OOOO
2020-12-21 18:35:58 +00:00
Josh Roy cb45867de8 Remove Jenkins from README (#3850)
EssX website is PogU, Jenkins is PogO
2020-12-21 15:21:36 +00:00
Jason c4ca0e51e2 Add restart note to respawn listener priority option (#3789) 2020-12-20 20:41:26 -08:00
triagonal 7bb12fd33c Fix incorrect radius check in /me command (#3848) 2020-12-20 20:08:11 -08:00
MD 001213ee2c Update bStats Metrics class to 1.8 (#3845)
Replace modified bStats Metrics 1.7 class with official 1.8 artifact. 1.8 adds random submit intervals and changes the way submissions are scheduled, and our custom modifications are no longer needed as we now use the plugin ID.
2020-12-19 16:27:07 +00:00
Josh Roy f320705216 Fix build script not working when git repo isn't cloned (#3831) 2020-12-13 20:54:54 -08:00
Josh Roy d4ed77fdc6 Update FakeServer (#3829) 2020-12-13 18:11:29 -08:00
Josh Roy ef1202923e Fix command handler not finding dynamically registered commands (#3816)
Fixes #3815.
2020-12-12 13:50:34 +00:00
e1e98f5048 Add /warpinfo command (#3820)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
2020-12-11 16:38:10 +00:00
MD 5bb3cc88e5 Dispatch commands on main thread when run from XMPP (#3803)
This PR fixes an issue reported on Discord, where commands executed through XMPP are dispatched async from the XMPP listener thread.
2020-12-11 16:36:29 +00:00
Josh Roy 43eff69a2f Fix incompatibility on TacoSpigot and derivatives (#3817)
The countless support tickets from users of TacoSpigot (and forks of it)
have become so numerous it's not worth holding our ground on this. Taco fucked up
by going against upstream revision numbers, but as they're defunct, it's not
going to change now. We will not fix server implementations that do this in the
future, but hopefully they have all learned their lesson to use proper
versioning. The way md_5 intended.
2020-12-11 16:31:40 +00:00
Roman Zabaluev 450da59057 Allow console name to be translated (#3700)
Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>

Closes #2756. 

Notes:
- I've kept original name for internal purposes (storing bans, etc), just in case user wants to rename it to something unbearable;
- User can message console via both names (since he can try both, duh);
- Some refactoring. Just a little.
2020-12-11 16:29:48 +00:00
Josh Roy dda4d58a23 Move paperlib off snapshot (#3808) 2020-12-02 19:44:35 +00:00
Josh Roy f5d5ed9844 Fix respawn anchor explosion protection (#3778)
Fixes #3723, where respawn anchors explode at charges > 0 when glowstone isn't in the players hand, or at charges = 4 on right click regardless of item.
2020-12-02 19:43:01 +00:00
MD 2342d53dba Revert Spanish translation spam 2020-11-30 16:33:24 +00:00
MD 005d83c6fe Fix checkstyle-idea default settings 2020-11-30 15:01:14 +00:00
Flask Bot 8c4fb3fefb New Crowdin updates (#3793) 2020-11-28 11:10:49 +00:00
MD b0fa0d1ec8 Remove mavenLocal repo from default repositories 2020-11-26 12:39:52 +00:00
MD 2ce5797d5e Update metrics warnings 2020-11-26 12:26:08 +00:00
MD c2617e5a1d Fix javadoc warning suppression 2020-11-26 01:19:16 +00:00
MD 3f73e31d9e Suppress deprecation and javadoc warnings by default 2020-11-26 01:12:58 +00:00
MD 84a48adbdd Change resolution strategy to refresh snapshot dependencies more frequently 2020-11-26 01:04:11 +00:00
MD 63c29a318e Implement missing FakeWorld methods 2020-11-26 00:42:26 +00:00
MD 69748fd72e Make resource processing depend on version string 2020-11-26 00:38:27 +00:00
MD 35278c6e90 Upload GH artifacts before publish 2020-11-25 20:43:23 +00:00
MD 088fc35435 Mark gradlew as executable 2020-11-25 20:37:45 +00:00
MD 4baad6b89c Fix missing "s" which prevented GH Actions deployment 2020-11-25 20:33:33 +00:00
Josh Roy 9a23f806fe Refactor Project to Gradle (#3720)
Gradle is better than Maven, don't @ me. okay but actually it's [faster](https://www.youtube.com/watch?v=atuFSv2bLa8&feature=youtu.be&t=77), compiles and tests in parallel more efficiently, and more epic stuff).
2020-11-25 20:24:24 +00:00
powercas_gamer 82b466db0b update website links in config.yml (#3779)
Updates the website links in the config.yml to `essentialsx.net` instead of `essentialsx.cf`
2020-11-18 10:23:45 +00:00
862 changed files with 108996 additions and 47131 deletions

No files matched your search

+1 -1
View File
@@ -24,7 +24,7 @@
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
<module name="SuppressionFilter">
<property name="file" value="${checkstyle.suppressions.file}"/>
<property name="file" value="${configDirectory}/suppressions.xml"/>
</module>
<!-- https://checkstyle.org/config_filters.html#SuppressWithPlainTextCommentFilter -->
+1 -1
View File
@@ -3,5 +3,5 @@
<suppressions>
<suppress files=".*[\\/]test[\\/]java[\\/].*" checks="RequireExplicitVisibilityModifier"/>
<!-- TODO: don't suppress I-prefixed API interfaces under com.earth2me.essentials -->
<suppress files="(com[\\/]earth2me[\\/]essentials[\\/]|net[\\/]ess3[\\/])(?:[^\\/]+$|(?!api)[^\\/]+[\\/])" checks="MissingJavadoc(Method|Type)"/>
<suppress files="(com[\\/]earth2me[\\/]essentials[\\/]|net[\\/]ess3[\\/]|net[\\/]essentialsx[\\/])(?:[^\\/]+$|(?!api)[^\\/]+[\\/])" checks="MissingJavadoc(Method|Type)"/>
</suppressions>
+2 -2
View File
@@ -1,10 +1,10 @@
# Currently used
patreon: EssentialsX
ko_fi: essentialsx
github: EssentialsX
open_collective: EssentialsX
# Currently not used
#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#open_collective: # Replace with a single Open Collective username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
+14 -2
View File
@@ -1,5 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: MOSS Discord Server
- name: View common issues
url: https://github.com/EssentialsX/Essentials/issues/3956
about: Check the list of known common issues to see if it solves your problem.
- name: Check past help requests
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/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: Get help from the community on Discord
url: https://discord.gg/casfFyh
about: Need help with using EssentialsX? Join the MOSS Discord server for help.
about: Join the MOSS Discord for community-powered EssentialsX support!
-86
View File
@@ -1,86 +0,0 @@
---
name: Help!
about: Encountered a problem with EssentialsX? Not sure how to fix it?
labels: 'type: question'
---
<!-- EssentialsX help request guide
NOTE: Failure to fill out this template properly may result in your issue being
delayed or ignored.
Don't type between any arrows in the template, as this text will be hidden.
This includes this header block and any other explanatory text blocks.
Want faster support? Come join our Discord server to get help from both
developers and community members: https://discord.gg/F7gexAQ
If you're happy to wait (or you were sent here from Discord), read on:
1. Check the Common Issues page.
Read through the wiki page to see if you've encountered a regular issue:
https://essentialsx.net/wiki/Common-Issues.html
2. Fill out the template.
Run the commands in the console. Don't just put "latest" as a version, or
we will ignore it. This will help us understand what problem you've
encountered and help us find a solution.
3. 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 when trying to solve issues in the
future.
**DO NOT drag logs into this text box!**
4. If you are encountering a performance issue, please include a link to a
Timings and/or profiler report.
-->
### Information
**Full output of `/ess version`:**
<!--
Run /ess version in the console, then paste the full output of the command
between the ```s.
-->
```
```
**Server startup log:**
<!--
Restart your server and upload `logs/latest.log` to
https://gist.github.com/. You may redact sensitive data (such as IP
addresses), but you should make it clear what data was removed. Once
uploaded, paste the link below this block.
-->
**EssentialsX config:**
<!--
Upload `plugins/Essentials/config.yml` (and other config files if relevant)
to https://gist.github.com/ then paste the link below this block.
-->
### Help request
**Problem:**
<!-- What problem did you encounter? Type below this line. -->
**What I have tried:**
<!-- What have you tried so far? Type below this line. -->
**Console stack trace:**
<!--
If you are reporting an error in the console, paste it between the ```s.
-->
```
```
**Screenshots:**
<!-- If relevant, include any screenshots or a video below this line. -->
-83
View File
@@ -1,83 +0,0 @@
---
name: Report a bug
about: Report an EssentialsX bug. Only use this if you're 100% sure there's something wrong with EssentialsX - otherwise, try "Help!".
labels: 'bug: unconfirmed'
---
<!-- EssentialsX bug reporting guide
NOTE: Failure to fill out this template properly may result in your issue being
delayed or ignored.
Don't type between any arrows in the template, as this text will be hidden.
This includes this header block and any other explanatory text blocks.
If you are reporting a bug, please follow the following steps:
1. Fill out the template in full.
Run the commands in the console. Don't just put "latest" as a version,
or we will ignore it.
2. When linking 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. Avoid using
Hastebin or Pastebin, as this makes providing support more difficult.
**DO NOT drag files into this text box!**
3. If you are reporting a performance issue, please include a link to a
Timings and/or profiler report.
4. If you are reporting a bug with commands or something else in-game,
please include screenshots to help us diagnose the problem.
-->
### Information
**Full output of `/ess version`:**
<!--
Run /ess version in the console, then paste the full output of the command
between the ```s.
-->
```
```
**Server startup log:**
<!--
Restart your server and upload `logs/latest.log` to
https://gist.github.com/. You may redact sensitive data (such as IP
addresses), but you should make it clear what data was removed. Once
uploaded, paste the link below this block.
-->
**EssentialsX config:**
<!--
Upload `plugins/Essentials/config.yml` (and other config files if relevant)
to https://gist.github.com/ then paste the link below this block.
-->
### Details
**Description:**
<!-- What is the bug? Type a brief summary below this line. -->
**Steps to reproduce:**
<!-- How did you cause it? Describe what you did to cause the bug below. -->
**Expected behavior:**
<!-- What did you expect to happen? Type below this line. -->
**Console stack trace:**
<!--
If you are encountering an error in the console, paste it between the ```s.
-->
```
```
**Screenshots:**
<!-- If necessary, include screenshots or a video below this line. -->
+83
View File
@@ -0,0 +1,83 @@
name: Report bug
description: Report a bug in EssentialsX.
labels: 'bug: unconfirmed'
type: Bug
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report an EssentialsX bug! Fill out the form below to provide us with info to help fix the bug.
Only use this if you're 100% sure you've found a bug and can reproduce it. If you're looking for general help with EssentialsX, try the Q&A forum or MOSS Discord server.
- type: dropdown
attributes:
label: Type of bug
description: What type of bug is this? Choose all that apply.
multiple: true
options:
- Performance issue or memory leak
- Data loss
- Exploit
- Compatibility issue
- Error in console
- Other unexpected behaviour
validations:
required: true
- type: input
attributes:
label: "`/ess dump all` output"
description: Run `/ess dump all` in the console, then copy and paste the link from the output of the command into this box. (If this command doesn't work, you should [update EssentialsX](https://essentialsx.net/downloads.html) and try again.)
validations:
required: true
- type: input
attributes:
label: Error log (if applicable)
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:
label: Bug description
description: Describe roughly what the bug is here.
placeholder: |
Example: "When running /nuke after putting everyone into adventure mode, there aren't any explosions..."
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Provide an example of how to trigger the bug.
placeholder: |
Example:
1. Have at least 3 people online
2. Run `/gma *` to put everyone into adventure mode
3. Run `/nuke`
validations:
required: true
- type: textarea
attributes:
label: Expected behaviour
description: Explain what you should expect to happen.
placeholder: |
Example: "Everything should explode!"
validations:
required: true
- type: textarea
attributes:
label: Actual behaviour
description: Explain what actually happens.
placeholder: |
Example: "Everything doesn't explode :("
validations:
required: true
- type: textarea
attributes:
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
@@ -1,52 +0,0 @@
---
name: Request a feature
about: Suggest a feature you want to see in EssentialsX!
labels: 'type: enhancement'
---
<!-- EssentialsX feature request guide
NOTE: Failure to fill out this template properly may result in your issue being
delayed or ignored.
Don't type between any arrows in the template, as this text will be hidden.
This includes this header block and any other explanatory text blocks.
Not sure if your feature fits in EssentialsX? Feel free to ask on our Discord
server: https://discord.gg/F7gexAQ
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.
-->
### Feature request
**Feature description:**
<!-- What feature are you suggesting? Type below this line. -->
**How the feature is useful:**
<!--
How is the feature useful to players, server owners and/or developers?
Type below this block.
-->
@@ -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
+32 -20
View File
@@ -1,24 +1,30 @@
---
name: Bug fix
about: Use this if your PR fixes a bug in EssentialsX.
labels: 'type: bugfix'
---
<!--
<!-- EssentialsX bug fix submission guide
EssentialsX bug fix submission guide
====================================
NOTE: Failure to fill out this template properly may result in your PR being
delayed or ignored without warning.
Don't type between any arrows in the template, as this text will be hidden.
This includes this header block and any other explanatory text blocks.
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.
@@ -34,14 +40,15 @@ If you are submitting a bug fix, please follow the following steps:
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 into this text box!**
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 may drag screenshots directly into this box.)
(You can drag screenshots into the bottom of the editor.)
-->
@@ -61,17 +68,21 @@ This PR fixes #nnnn.
**Environments tested:**
<!--
Below this line, put an "x" inside the box for the environments you have
tested this bug fix on, and if relevant alter the OS and Java version
accordingly. If this feature does not apply to an environment, strike
through the environment using ~~strikethrough~~. If you have tested on
other environments, add a new line with relevant details.
-->
- [ ] [Latest](https://papermc.io/downloads) Paper Version (any OS, any Java 8+ version)
- [ ] CraftBukkit/Spigot/Paper 1.12.2 (any OS, any Java 8+ version)
- [ ] CraftBukkit 1.8.8 (any OS, any Java 8+ version)
<!-- Type the OS you have used below. -->
OS:
<!-- Type the JDK version (from java -version) you have used below. -->
Java version:
<!--
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
**Demonstration:**
@@ -80,3 +91,4 @@ This PR fixes #nnnn.
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.
-->
+35 -24
View File
@@ -1,24 +1,30 @@
---
name: Feature addition
about: Use this if your PR adds a new feature to EssentialsX.
labels: 'type: enhancement'
---
<!--
<!-- EssentialsX feature submission guide
EssentialsX feature submission guide
====================================
NOTE: Failure to fill out this template properly may result in your PR being
delayed or ignored without warning.
Don't type between any arrows in the template, as this text will be hidden.
This includes this header block and any other explanatory text blocks.
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.
@@ -57,26 +63,31 @@ This PR closes #nnnn.
### Details
**Proposed feature**
**Proposed feature:**
<!-- Type a description of your proposed feature below this line. -->
**Environments tested:**
<!--
Below this block, put an "x" inside the box for the environments you have
tested this bug fix on, and if relevant alter the OS and Java version
accordingly. If this feature does not apply to an environment, strike
through the environment using ~~strikethrough~~. If you have tested on
other environments, add a new line with relevant details.
-->
- [ ] [Latest](https://papermc.io/downloads) Paper Version (any OS, any Java 8+ version)
- [ ] CraftBukkit/Spigot/Paper 1.12.2 (any OS, any Java 8+ version)
- [ ] CraftBukkit 1.8.8 (any OS, any Java 8+ version)
**Environments tested:**
<!-- Type the OS you have used below. -->
OS:
<!-- Type the JDK version (from java -version) you have used below. -->
Java version:
<!--
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
**Demonstration:**
<!--
Below this block, include screenshots/code 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.
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.
-->
+10
View File
@@ -0,0 +1,10 @@
You need to select a pull request template!
If you're adding a new feature, copy and paste the following parameter at the end of the URL:
?template=new-feature.md
If you're fixing a bug, copy and paste the following parameter at the end of the URL:
?template=bug-fix.md
For more information about contributing to EssentialsX, see CONTRIBUTING.md:
https://github.com/EssentialsX/Essentials/blob/2.x/CONTRIBUTING.md
+97
View File
@@ -0,0 +1,97 @@
name: Build EssentialsX
on:
push:
branches:
- 2.x
- dev/*
release:
types:
- published
jobs:
build:
name: Build and upload
runs-on: ubuntu-latest
steps:
- name: Checkout Git repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
- 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@v4
with:
name: EssentialsX plugin jars
path: jars/
- name: Deploy to Maven repo
if: ${{ success() && github.event_name == 'push' && github.repository == 'EssentialsX/Essentials' && github.ref == 'refs/heads/2.x' }}
env:
ORG_GRADLE_PROJECT_essxUsername: ${{ secrets.ESSENTIALSX_DEPLOY_USERNAME }}
ORG_GRADLE_PROJECT_essxPassword: ${{ secrets.ESSENTIALSX_DEPLOY_PASSWORD }}
run: |
./gradlew publish
- name: Prepare Javadocs
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: Archive Javadocs
uses: actions/upload-artifact@v4
with:
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@v4
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 }}
+54
View File
@@ -0,0 +1,54 @@
name: Build Pull Request
on:
push:
branches:
- mc/* # MC version updates
- pr/* # PR rebases
pull_request:
branches:
- 2.x
- mc/*
- dev/*
merge_group:
types: [checks_requested]
permissions:
checks: write
jobs:
build:
name: Build and upload
runs-on: ubuntu-latest
steps:
- name: Checkout Git repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
- 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@v4
with:
name: EssentialsX plugin jars
path: jars/
-38
View File
@@ -1,38 +0,0 @@
name: Build EssentialsX
on:
push:
branches:
- 2.x
- mc/*
pull_request:
branches:
- 2.x
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Git repo
uses: actions/checkout@v1
- name: Restore Maven cache
uses: actions/cache@v1
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn package verify --file pom.xml
- name: Copy artifacts
run: mkdir -p ./out/ && cp -t ./out/ **/target/Essentials*.jar
- uses: actions/upload-artifact@master
with:
name: Plugin jars
path: out/
+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: ./
+13 -2
View File
@@ -15,8 +15,19 @@
/Essentials/kits.yml
/Essentials/userdata/testplayer1.yml
/Essentials/usermap.csv
/Essentials/usermap.bin
/Essentials/uuids.bin
# Build files
target/
jars/
.gradle/
/jars/
generated/
out/
build/
target/
*.class
build-logic/.kotlin/
build-logic/bin/
# Run directory
/run/
+23 -15
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.checkstyle.suppressions.file" value=".checkstyle/suppressions.xml" />
<entry key="scan-before-checkin" value="false" />
<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>
</project>
+93 -22
View File
@@ -1,31 +1,102 @@
Contributing to EssentialsX
===========================
# Contributing to EssentialsX
Want to help improve EssentialsX? There are several ways you can support and contribute to the project.
*By contributing to EssentialsX, you agree to license your changes under the [GNU General Public License version 3](https://github.com/EssentialsX/Essentials/blob/2.x/LICENSE).*
If you'd like to make a financial contribution to the project, you can join our [Patreon](https://www.patreon.com/essentialsx/),
or to make a one-off donation you can visit our [Ko-fi page](https://ko-fi.com/essentialsx). If you can't make a
donation, don't worry! There are lots of other ways to contribute:
## Submitting code changes to EssentialsX
* Do you run a server? Take a look at our ["help wanted"](https://github.com/EssentialsX/Essentials/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22help+wanted%22)
and ["bug: unconfirmed"](https://github.com/EssentialsX/Essentials/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22bug%3A+unconfirmed%22)
issues, where you can find issues that need extra testing and investigation.
* Do you speak multiple languages? If so, we always welcome contributions to our [Crowdin project](https://crowdin.com/project/essentialsx-official).
* Do you enjoy helping others? If so, why not contribute to the [EssentialsX documentation](https://github.com/EssentialsX/wiki)?
You can also join the [MOSS Discord community](https://discord.gg/casfFyh) and provide direct community support to
other EssentialsX users.
* If you're a developer, you could look through our ["open to PR"](https://github.com/EssentialsX/Essentials/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22status%3A+open+to+PR%22)
issues. We're always happy to receive bug fixes and feature additions as pull requests.
If you're interested in submitting new code to EssentialsX, we accept changes via GitHub pull requests.
Submitting a PR
---------------
### Adding features
EssentialsX has extensive templates for PRs that detail how to submit your PR. To find out more, see the PR templates for:
* [Bug fixes](https://github.com/EssentialsX/Essentials/blob/2.x/.github/PULL_REQUEST_TEMPLATE/bug-fix.md)
* [New features](https://github.com/EssentialsX/Essentials/blob/2.x/.github/PULL_REQUEST_TEMPLATE/new-feature.md)
In general, we will only consider PRs for features if they have already been discussed with the team through
GitHub issues and discussions. Check the list of
["open to PR" issues](https://github.com/EssentialsX/Essentials/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22status%3A+open+to+PR%22)
and [feature request discussions](https://github.com/EssentialsX/Essentials/discussions/categories/ideas-and-feature-suggestions)
before you start working on your changes. If you don't see your desired feature listed,
[open a feature request](https://github.com/EssentialsX/Essentials/issues/new/choose) and wait for a response, otherwise
you may end up wasting your time on a feature that we aren't in a position to accept.
Want to discuss a feature before opening a PR? Join the [EssentialsX Development Discord server](https://discord.gg/CUN7qVb). Note that this server is **not for end-users** - if you need support with EssentialsX, you should join [MOSS](https://discord.gg/casfFyh) instead.
#### Keep it focused
By contributing to EssentialsX, you agree to license your code under the [GNU General Public License version 3](https://github.com/EssentialsX/Essentials/blob/2.x/LICENSE).
Please try to keep feature PRs focused around one feature. Your PR should ideally contain
one feature, or a few closely-linked features. If you submit several unrelated features
in one PR, the PR will not be accepted.
### Fixing bugs
If you're opening a PR to fix a bug, please ensure a bug report has been filed - search the
[issue tracker](https://github.com/EssentialsX/Essentials/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22bug%3A+confirmed%22%2C%22bug%3A+unconfirmed%22%2C%22bug%3A+upstream%22)
for an existing report, and if you can't find a bug report,
[create one](https://github.com/EssentialsX/Essentials/issues/new/choose) before you submit your PR.
#### Unconfirmed bugs
You may find a bug report with the label `bug: unconfirmed`. This means the EssentialsX team hasn't had the time
to review the bug report yet or hasn't been able to confirm
whether the reported issue is actually a bug in EssentialsX. You can help us by following the steps in the report and
posting whether you are able to replicate this issue, ideally on the latest versions of EssentialsX and Paper. This will
help us confirm issues and prioritise PRs accordingly.
#### Upstream bugs
You may also find bug reports with the `bug: upstream` label. This label means the issue is caused by a bug in *another
project*, not EssentialsX. This includes bugs in CraftBukkit/Spigot and bugs in other plugins. It may be appropriate to
mitigate an issue in EssentialsX, but please check with us in the [EssentialsX Development Discord server](https://discord.gg/CUN7qVb) before you submit a PR.
### Making your changes
You'll need the following to make your changes to EssentialsX:
* A GitHub account
* [`git`](https://git-scm.com/downloads): the Git command-line tool, used to track and save your changes
* [`gh`](https://cli.github.com/): the GitHub command-line tool, used to submit your changes to EssentialsX
* Your IDE of choice - we recommend [IntelliJ IDEA Community Edition](https://www.jetbrains.com/idea/download)
If you're already familiar with Git, you can skip this next section.
You'll need to clone the EssentialsX repository
(`gh repo clone EssentialsX/Essentials`), then create a new branch for your work
(`cd Essentials` then `git switch -c my-cool-pr`).
You can then make changes using your IDE of choice. Follow the instructions in `README.md` to build and test your
changes.
Once you've finished, you should commit your changes (`git commit -am "My cool commit name!"`).
### Submitting your PR
*Even if you're already familiar with GitHub, you will need to follow these steps in order to submit a PR to
EssentialsX. This ensures we have the information we need to test and review your changes.*
Next, you'll need to fork EssentialsX on GitHub and push your changes to a branch on that fork. We strongly recommend
using the `gh` command-line tool for this:
* In your terminal, run `gh pr create`.
* Select either the bug fix or feature template, depending on what your PR is for.
* If you get asked `Where should we push the '...' branch?`, select `Create a fork of EssentialsX/Essentials`.
* For the title, choose a title that describes what your PR does. For example, `Add more ducks to /spawnmob`.
* For the body, press E. You will then need to fill in the PR template in your text editor.
- Follow the instructions in the template and fill out the sections as required.
- This step is important! Without it, we won't know why you've made your changes or how to test them.
* Save the file in your text editor and close it, then return to the terminal.
* Select `Submit` to open your PR.
If you follow these steps correctly, GitHub will create a fork (if necessary) and then open a PR with your changes. You
can now sit back while we review your changes. If we need to ask questions or request further changes to your code,
we will review your PR and post a comment on GitHub. You can respond to our reviews and comments on the GitHub website,
and you can push new changes using `git commit` and `git push`.
## Submitting community translations to EssentialsX
EssentialsX relies on community translations for its messages in other languages. You can help translate EssentialsX on
[Crowdin](https://translate.essentialsx.net/). You'll need a Crowdin account to translate and improve messages.
If your language isn't listed or doesn't have an active proofreader, please let us know on Discord (see below).
## Discussing EssentialsX contributions
Want to discuss something before opening a PR or translating messages? Join the
[EssentialsX Development Discord server](https://discord.gg/CUN7qVb). Note that this server is **not for end-users** -
if you need support with EssentialsX, you should join [MOSS](https://discord.gg/casfFyh) instead.
+95
View File
@@ -0,0 +1,95 @@
plugins {
id("essentials.shadow-module")
}
dependencies {
compileOnly('com.github.milkbowl:VaultAPI:1.7') {
exclude group: "org.bukkit", module: "bukkit"
}
compileOnly 'net.luckperms:api:5.3'
api 'io.papermc:paperlib:1.0.6'
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 'net.kyori:adventure-api:4.24.0'
implementation 'net.kyori:adventure-text-minimessage:4.24.0'
implementation 'net.kyori:adventure-platform-bukkit:4.4.1'
// Providers
api(project(':providers:BaseProviders')) {
exclude(module: 'spigot-api')
}
api(project(path: ':providers:PaperProvider', configuration: 'shadow')){
exclude(module: 'paper-api')
}
api(project(':providers:NMSReflectionProvider')) {
exclude(module: 'bukkit')
}
api(project(':providers:1_8Provider')) {
exclude(module: 'spigot-api')
}
api(project(':providers:1_12Provider')) {
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'))
include (dependency('org.bstats:bstats-bukkit'))
include (dependency('org.bstats:bstats-base'))
include (dependency('org.spongepowered:configurate-yaml'))
include (dependency('org.spongepowered:configurate-core'))
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(path: ':providers:PaperProvider', configuration: 'shadow'))
include (project(':providers:NMSReflectionProvider'))
include (project(':providers:1_8Provider'))
include (project(':providers:1_12Provider'))
}
relocate 'io.papermc.lib', 'com.earth2me.essentials.paperlib'
relocate 'org.bstats', 'com.earth2me.essentials.libs.bstats'
relocate 'org.spongepowered.configurate', 'com.earth2me.essentials.libs.configurate'
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'
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'))
}
}
-118
View File
@@ -1,118 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.ess3</groupId>
<artifactId>EssentialsXParent</artifactId>
<version>2.18.2</version>
</parent>
<artifactId>EssentialsX</artifactId>
<build>
<finalName>EssentialsX-${full.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<showDeprecation>false</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>io.papermc.lib</pattern>
<shadedPattern>com.earth2me.essentials.paperlib</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.github.milkbowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<exclusions>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
</exclusion>
</exclusions>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.luckperms</groupId>
<artifactId>api</artifactId>
<version>5.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.papermc</groupId>
<artifactId>paperlib</artifactId>
<version>1.0.6-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.ess3</groupId>
<artifactId>BaseProviders</artifactId>
<version>2.18.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.ess3</groupId>
<artifactId>NMSReflectionProvider</artifactId>
<version>2.18.2</version>
<exclusions>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
</exclusion>
</exclusions>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.ess3</groupId>
<artifactId>PaperProvider</artifactId>
<version>2.18.2</version>
<exclusions>
<exclusion>
<groupId>com.destroystokyo.paper</groupId>
<artifactId>paper-api</artifactId>
</exclusion>
</exclusions>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.ess3</groupId>
<artifactId>1_8Provider</artifactId>
<version>2.18.2</version>
<exclusions>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
</exclusion>
</exclusions>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
@@ -1,108 +0,0 @@
package com.earth2me.essentials;
import net.ess3.api.IEssentials;
import org.bukkit.command.Command;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.PluginCommandYamlParser;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class AlternativeCommandsHandler {
private static final Logger LOGGER = Logger.getLogger("Essentials");
private final transient Map<String, List<PluginCommand>> altcommands = new HashMap<>();
private final transient Map<String, String> disabledList = new HashMap<>();
private final transient IEssentials ess;
public AlternativeCommandsHandler(final IEssentials ess) {
this.ess = ess;
for (final Plugin plugin : ess.getServer().getPluginManager().getPlugins()) {
if (plugin.isEnabled()) {
addPlugin(plugin);
}
}
}
public final void addPlugin(final Plugin plugin) {
if (plugin.getDescription().getMain().contains("com.earth2me.essentials")) {
return;
}
final List<Command> commands = PluginCommandYamlParser.parse(plugin);
final String pluginName = plugin.getDescription().getName().toLowerCase(Locale.ENGLISH);
for (final Command command : commands) {
final PluginCommand pc = (PluginCommand) command;
final List<String> labels = new ArrayList<>(pc.getAliases());
labels.add(pc.getName());
PluginCommand reg = ess.getServer().getPluginCommand(pluginName + ":" + pc.getName().toLowerCase(Locale.ENGLISH));
if (reg == null) {
reg = ess.getServer().getPluginCommand(pc.getName().toLowerCase(Locale.ENGLISH));
}
if (reg == null || !reg.getPlugin().equals(plugin)) {
continue;
}
for (final String label : labels) {
final List<PluginCommand> plugincommands = altcommands.computeIfAbsent(label.toLowerCase(Locale.ENGLISH), k -> new ArrayList<>());
boolean found = false;
for (final PluginCommand pc2 : plugincommands) {
if (pc2.getPlugin().equals(plugin)) {
found = true;
break;
}
}
if (!found) {
plugincommands.add(reg);
}
}
}
}
public void removePlugin(final Plugin plugin) {
final Iterator<Map.Entry<String, List<PluginCommand>>> iterator = altcommands.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<String, List<PluginCommand>> entry = iterator.next();
entry.getValue().removeIf(pc -> pc.getPlugin() == null || pc.getPlugin().equals(plugin));
if (entry.getValue().isEmpty()) {
iterator.remove();
}
}
}
public PluginCommand getAlternative(final String label) {
final List<PluginCommand> commands = altcommands.get(label);
if (commands == null || commands.isEmpty()) {
return null;
}
if (commands.size() == 1) {
return commands.get(0);
}
// return the first command that is not an alias
for (final PluginCommand command : commands) {
if (command.getName().equalsIgnoreCase(label)) {
return command;
}
}
// return the first alias
return commands.get(0);
}
public void executed(final String label, final PluginCommand pc) {
final String altString = pc.getPlugin().getName() + ":" + pc.getLabel();
if (ess.getSettings().isDebug()) {
LOGGER.log(Level.INFO, "Essentials: Alternative command " + label + " found, using " + altString);
}
disabledList.put(label, altString);
}
public Map<String, String> disabledCommands() {
return disabledList;
}
}
@@ -1,11 +0,0 @@
package com.earth2me.essentials;
public class ChargeException extends Exception {
public ChargeException(final String message) {
super(message);
}
public ChargeException(final String message, final Throwable throwable) {
super(message, throwable);
}
}
@@ -1,56 +0,0 @@
package com.earth2me.essentials;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class CommandSource {
protected CommandSender sender;
public CommandSource(final CommandSender base) {
this.sender = base;
}
public final CommandSender getSender() {
return sender;
}
public final Player getPlayer() {
if (sender instanceof Player) {
return (Player) sender;
}
return null;
}
public final net.ess3.api.IUser getUser(final IEssentials ess) {
if (sender instanceof Player) {
return ess.getUser((Player) sender);
}
return null;
}
public final boolean isPlayer() {
return sender instanceof Player;
}
public final CommandSender setSender(final CommandSender base) {
return this.sender = base;
}
public void sendMessage(final String message) {
if (!message.isEmpty()) {
sender.sendMessage(message);
}
}
public boolean isAuthorized(final String permission, final IEssentials ess) {
return !(sender instanceof Player) || getUser(ess).isAuthorized(permission);
}
public String getSelfSelector() {
return sender instanceof Player ? getPlayer().getName() : "*";
}
public String getDisplayName() {
return sender instanceof Player ? getPlayer().getDisplayName() : getSender().getName();
}
}
@@ -1,304 +0,0 @@
package com.earth2me.essentials;
import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public final class Enchantments {
private static final Map<String, Enchantment> ENCHANTMENTS = new HashMap<>();
private static final Map<String, Enchantment> ALIASENCHANTMENTS = new HashMap<>();
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);
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("undeaddamage", Enchantment.DAMAGE_UNDEAD);
ENCHANTMENTS.put("smite", Enchantment.DAMAGE_UNDEAD);
ALIASENCHANTMENTS.put("du", Enchantment.DAMAGE_UNDEAD);
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("durability", Enchantment.DURABILITY);
ALIASENCHANTMENTS.put("dura", Enchantment.DURABILITY);
ENCHANTMENTS.put("unbreaking", Enchantment.DURABILITY);
ALIASENCHANTMENTS.put("d", Enchantment.DURABILITY);
ENCHANTMENTS.put("thorns", Enchantment.THORNS);
ENCHANTMENTS.put("highcrit", Enchantment.THORNS);
ALIASENCHANTMENTS.put("thorn", Enchantment.THORNS);
ALIASENCHANTMENTS.put("highercrit", Enchantment.THORNS);
ALIASENCHANTMENTS.put("t", Enchantment.THORNS);
ENCHANTMENTS.put("fireaspect", Enchantment.FIRE_ASPECT);
ENCHANTMENTS.put("fire", Enchantment.FIRE_ASPECT);
ALIASENCHANTMENTS.put("meleefire", Enchantment.FIRE_ASPECT);
ALIASENCHANTMENTS.put("meleeflame", Enchantment.FIRE_ASPECT);
ALIASENCHANTMENTS.put("fa", Enchantment.FIRE_ASPECT);
ENCHANTMENTS.put("knockback", Enchantment.KNOCKBACK);
ALIASENCHANTMENTS.put("kback", Enchantment.KNOCKBACK);
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);
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("oxygen", Enchantment.OXYGEN);
ENCHANTMENTS.put("respiration", Enchantment.OXYGEN);
ALIASENCHANTMENTS.put("breathing", Enchantment.OXYGEN);
ENCHANTMENTS.put("breath", Enchantment.OXYGEN);
ALIASENCHANTMENTS.put("o", Enchantment.OXYGEN);
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("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);
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("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);
ENCHANTMENTS.put("projectileprotection", Enchantment.PROTECTION_PROJECTILE);
ENCHANTMENTS.put("projprot", Enchantment.PROTECTION_PROJECTILE);
ALIASENCHANTMENTS.put("pp", Enchantment.PROTECTION_PROJECTILE);
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);
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("arrowdamage", Enchantment.ARROW_DAMAGE);
ENCHANTMENTS.put("power", Enchantment.ARROW_DAMAGE);
ALIASENCHANTMENTS.put("arrowpower", Enchantment.ARROW_DAMAGE);
ALIASENCHANTMENTS.put("ad", Enchantment.ARROW_DAMAGE);
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("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);
ENCHANTMENTS.put("luck", Enchantment.LUCK);
ALIASENCHANTMENTS.put("luckofsea", Enchantment.LUCK);
ALIASENCHANTMENTS.put("luckofseas", Enchantment.LUCK);
ALIASENCHANTMENTS.put("rodluck", Enchantment.LUCK);
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) {
}
// 1.9
try {
final Enchantment frostWalker = Enchantment.getByName("FROST_WALKER");
if (frostWalker != null) {
ENCHANTMENTS.put("frostwalker", frostWalker);
ALIASENCHANTMENTS.put("frost", frostWalker);
ALIASENCHANTMENTS.put("walker", frostWalker);
}
final Enchantment mending = Enchantment.getByName("MENDING");
if (mending != null) {
ENCHANTMENTS.put("mending", mending);
}
} catch (final IllegalArgumentException ignored) {
}
// 1.11
try {
final Enchantment bindingCurse = Enchantment.getByName("BINDING_CURSE");
if (bindingCurse != null) {
ENCHANTMENTS.put("bindingcurse", bindingCurse);
ALIASENCHANTMENTS.put("bindcurse", bindingCurse);
ALIASENCHANTMENTS.put("binding", bindingCurse);
ALIASENCHANTMENTS.put("bind", bindingCurse);
}
final Enchantment vanishingCurse = Enchantment.getByName("VANISHING_CURSE");
if (vanishingCurse != null) {
ENCHANTMENTS.put("vanishingcurse", vanishingCurse);
ALIASENCHANTMENTS.put("vanishcurse", vanishingCurse);
ALIASENCHANTMENTS.put("vanishing", vanishingCurse);
ALIASENCHANTMENTS.put("vanish", vanishingCurse);
}
final Enchantment sweeping = Enchantment.getByName("SWEEPING_EDGE");
if (sweeping != null) {
ENCHANTMENTS.put("sweepingedge", sweeping);
ALIASENCHANTMENTS.put("sweepedge", sweeping);
ALIASENCHANTMENTS.put("sweeping", sweeping);
}
} catch (final IllegalArgumentException ignored) {
}
try { // 1.13
final Enchantment loyalty = Enchantment.getByName("LOYALTY");
if (loyalty != null) {
ENCHANTMENTS.put("loyalty", loyalty);
ALIASENCHANTMENTS.put("loyal", loyalty);
ALIASENCHANTMENTS.put("return", loyalty);
}
final Enchantment impaling = Enchantment.getByName("IMPALING");
if (impaling != null) {
ENCHANTMENTS.put("impaling", impaling);
ALIASENCHANTMENTS.put("impale", impaling);
ALIASENCHANTMENTS.put("oceandamage", impaling);
ALIASENCHANTMENTS.put("oceandmg", impaling);
}
final Enchantment riptide = Enchantment.getByName("RIPTIDE");
if (riptide != null) {
ENCHANTMENTS.put("riptide", riptide);
ALIASENCHANTMENTS.put("rip", riptide);
ALIASENCHANTMENTS.put("tide", riptide);
ALIASENCHANTMENTS.put("launch", riptide);
}
final Enchantment channelling = Enchantment.getByName("CHANNELING");
if (channelling != null) {
ENCHANTMENTS.put("channelling", channelling);
ALIASENCHANTMENTS.put("chanelling", channelling);
ALIASENCHANTMENTS.put("channeling", channelling);
ALIASENCHANTMENTS.put("chaneling", channelling);
ALIASENCHANTMENTS.put("channel", channelling);
}
} catch (final IllegalArgumentException ignored) {
}
try { // 1.14
final Enchantment multishot = Enchantment.getByName("MULTISHOT");
if (multishot != null) {
ENCHANTMENTS.put("multishot", multishot);
ALIASENCHANTMENTS.put("tripleshot", multishot);
}
final Enchantment quickCharge = Enchantment.getByName("QUICK_CHARGE");
if (quickCharge != null) {
ENCHANTMENTS.put("quickcharge", quickCharge);
ALIASENCHANTMENTS.put("quickdraw", quickCharge);
ALIASENCHANTMENTS.put("fastcharge", quickCharge);
ALIASENCHANTMENTS.put("fastdraw", quickCharge);
}
final Enchantment piercing = Enchantment.getByName("PIERCING");
if (piercing != null) {
ENCHANTMENTS.put("piercing", piercing);
}
} catch (final IllegalArgumentException ignored) {
}
try { // 1.16
final Enchantment soulspeed = Enchantment.getByName("SOUL_SPEED");
if (soulspeed != null) {
ENCHANTMENTS.put("soulspeed", soulspeed);
ALIASENCHANTMENTS.put("soilspeed", soulspeed);
ALIASENCHANTMENTS.put("sandspeed", soulspeed);
}
} catch (final IllegalArgumentException ignored) {
}
try {
final Class<?> namespacedKeyClass = Class.forName("org.bukkit.NamespacedKey");
final Class<?> enchantmentClass = Class.forName("org.bukkit.enchantments.Enchantment");
enchantmentClass.getDeclaredMethod("getByKey", namespacedKeyClass);
isFlat = true;
} catch (final ClassNotFoundException | NoSuchMethodException e) {
isFlat = false;
}
}
private Enchantments() {
}
public static Enchantment getByName(final String name) {
Enchantment enchantment = null;
if (isFlat) { // 1.13+ only
enchantment = Enchantment.getByKey(NamespacedKey.minecraft(name.toLowerCase()));
}
if (enchantment == null) {
enchantment = Enchantment.getByName(name.toUpperCase());
}
if (enchantment == null) {
enchantment = ENCHANTMENTS.get(name.toLowerCase(Locale.ENGLISH));
}
if (enchantment == null) {
enchantment = ALIASENCHANTMENTS.get(name.toLowerCase(Locale.ENGLISH));
}
return enchantment;
}
public static Set<Entry<String, Enchantment>> entrySet() {
return ENCHANTMENTS.entrySet();
}
public static Set<String> keySet() {
return ENCHANTMENTS.keySet();
}
}
File diff suppressed because it is too large. Load diff
@@ -1,674 +0,0 @@
package com.earth2me.essentials;
import com.google.common.io.Files;
import net.ess3.api.InvalidWorldException;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.math.MathContext;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.earth2me.essentials.I18n.tl;
public class EssentialsConf extends YamlConfiguration {
protected static final Logger LOGGER = Logger.getLogger("Essentials");
protected static final Charset UTF8 = StandardCharsets.UTF_8;
private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
protected final File configFile;
private final AtomicInteger pendingDiskWrites = new AtomicInteger(0);
private final AtomicBoolean transaction = new AtomicBoolean(false);
private final byte[] bytebuffer = new byte[1024];
protected String templateName = null;
private Class<?> resourceClass = EssentialsConf.class;
public EssentialsConf(final File configFile) {
super();
this.configFile = configFile.getAbsoluteFile();
}
public static BigDecimal toBigDecimal(final String input, final BigDecimal def) {
if (input == null || input.isEmpty()) {
return def;
} else {
try {
return new BigDecimal(input, MathContext.DECIMAL128);
} catch (final NumberFormatException | ArithmeticException e) {
return def;
}
}
}
public synchronized void load() {
if (pendingDiskWrites.get() != 0) {
LOGGER.log(Level.INFO, "File {0} not read, because it''s not yet written to disk.", configFile);
return;
}
if (!configFile.getParentFile().exists()) {
if (!configFile.getParentFile().mkdirs()) {
LOGGER.log(Level.SEVERE, tl("failedToCreateConfig", configFile.toString()));
}
}
// This will delete files where the first character is 0. In most cases they are broken.
if (configFile.exists() && configFile.length() != 0) {
try {
final InputStream input = new FileInputStream(configFile);
try {
if (input.read() == 0) {
input.close();
configFile.delete();
}
} catch (final IOException ex) {
LOGGER.log(Level.SEVERE, null, ex);
} finally {
try {
input.close();
} catch (final IOException ex) {
LOGGER.log(Level.SEVERE, null, ex);
}
}
} catch (final FileNotFoundException ex) {
LOGGER.log(Level.SEVERE, null, ex);
}
}
if (!configFile.exists()) {
if (legacyFileExists()) {
convertLegacyFile();
} else if (altFileExists()) {
convertAltFile();
} else if (templateName != null) {
LOGGER.log(Level.INFO, tl("creatingConfigFromTemplate", configFile.toString()));
createFromTemplate();
} else {
return;
}
}
try {
try (final FileInputStream inputStream = new FileInputStream(configFile)) {
final long startSize = configFile.length();
if (startSize > Integer.MAX_VALUE) {
throw new InvalidConfigurationException("File too big");
}
ByteBuffer buffer = ByteBuffer.allocate((int) startSize);
int length;
while ((length = inputStream.read(bytebuffer)) != -1) {
if (length > buffer.remaining()) {
final ByteBuffer resize = ByteBuffer.allocate(buffer.capacity() + length - buffer.remaining());
final int resizePosition = buffer.position();
// Fix builds compiled against Java 9+ breaking on Java 8
((Buffer) buffer).rewind();
resize.put(buffer);
resize.position(resizePosition);
buffer = resize;
}
buffer.put(bytebuffer, 0, length);
}
((Buffer) buffer).rewind();
final CharBuffer data = CharBuffer.allocate(buffer.capacity());
CharsetDecoder decoder = UTF8.newDecoder();
CoderResult result = decoder.decode(buffer, data, true);
if (result.isError()) {
((Buffer) buffer).rewind();
((Buffer) data).clear();
LOGGER.log(Level.INFO, "File " + configFile.getAbsolutePath() + " is not utf-8 encoded, trying " + Charset.defaultCharset().displayName());
decoder = Charset.defaultCharset().newDecoder();
result = decoder.decode(buffer, data, true);
if (result.isError()) {
throw new InvalidConfigurationException("Invalid Characters in file " + configFile.getAbsolutePath());
} else {
decoder.flush(data);
}
} else {
decoder.flush(data);
}
final int end = data.position();
((Buffer) data).rewind();
super.loadFromString(data.subSequence(0, end).toString());
}
} catch (final IOException ex) {
LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
} catch (final InvalidConfigurationException ex) {
final File broken = new File(configFile.getAbsolutePath() + ".broken." + System.currentTimeMillis());
configFile.renameTo(broken);
LOGGER.log(Level.SEVERE, "The file " + configFile.toString() + " is broken, it has been renamed to " + broken.toString(), ex.getCause());
}
}
public boolean legacyFileExists() {
return false;
}
public void convertLegacyFile() {
LOGGER.log(Level.SEVERE, "Unable to import legacy config file.");
}
public boolean altFileExists() {
return false;
}
public void convertAltFile() {
LOGGER.log(Level.SEVERE, "Unable to import alt config file.");
}
private void createFromTemplate() {
InputStream istr = null;
OutputStream ostr = null;
try {
istr = resourceClass.getResourceAsStream(templateName);
if (istr == null) {
LOGGER.log(Level.SEVERE, tl("couldNotFindTemplate", templateName));
return;
}
ostr = new FileOutputStream(configFile);
final byte[] buffer = new byte[1024];
int length = 0;
length = istr.read(buffer);
while (length > 0) {
ostr.write(buffer, 0, length);
length = istr.read(buffer);
}
} catch (final IOException ex) {
LOGGER.log(Level.SEVERE, tl("failedToWriteConfig", configFile.toString()), ex);
} finally {
try {
if (istr != null) {
istr.close();
}
} catch (final IOException ex) {
Logger.getLogger(EssentialsConf.class.getName()).log(Level.SEVERE, null, ex);
}
try {
if (ostr != null) {
ostr.close();
}
} catch (final IOException ex) {
LOGGER.log(Level.SEVERE, tl("failedToCloseConfig", configFile.toString()), ex);
}
}
}
public void setTemplateName(final String templateName) {
this.templateName = templateName;
}
public File getFile() {
return configFile;
}
public void setTemplateName(final String templateName, final Class<?> resClass) {
this.templateName = templateName;
this.resourceClass = resClass;
}
public void startTransaction() {
transaction.set(true);
}
public void stopTransaction() {
transaction.set(false);
save();
}
public void save() {
try {
save(configFile);
} catch (final IOException ex) {
LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
}
}
public void saveWithError() throws IOException {
save(configFile);
}
@Override
public synchronized void save(final File file) throws IOException {
if (!transaction.get()) {
delayedSave(file);
}
}
//This may be aborted if there are stagnant requests sitting in queue.
//This needs fixed to discard outstanding save requests.
public synchronized void forceSave() {
try {
final Future<?> future = delayedSave(configFile);
if (future != null) {
future.get();
}
} catch (final InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
}
}
public synchronized void cleanup() {
forceSave();
}
private Future<?> delayedSave(final File file) {
if (file == null) {
throw new IllegalArgumentException("File cannot be null");
}
final String data = saveToString();
if (data.length() == 0) {
return null;
}
pendingDiskWrites.incrementAndGet();
return EXECUTOR_SERVICE.submit(new WriteRunner(configFile, data, pendingDiskWrites));
}
public boolean hasProperty(final String path) {
return isSet(path);
}
public Location getLocation(final String path, final Server server) throws InvalidWorldException {
final String worldString = (path == null ? "" : path + ".") + "world";
final String worldName = getString(worldString);
if (worldName == null || worldName.isEmpty()) {
return null;
}
final World world = server.getWorld(worldName);
if (world == null) {
throw new InvalidWorldException(worldName);
}
return new Location(world, getDouble((path == null ? "" : path + ".") + "x", 0), getDouble((path == null ? "" : path + ".") + "y", 0), getDouble((path == null ? "" : path + ".") + "z", 0), (float) getDouble((path == null ? "" : path + ".") + "yaw", 0), (float) getDouble((path == null ? "" : path + ".") + "pitch", 0));
}
public void setProperty(final String path, final Location loc) {
set((path == null ? "" : path + ".") + "world", loc.getWorld().getName());
set((path == null ? "" : path + ".") + "x", loc.getX());
set((path == null ? "" : path + ".") + "y", loc.getY());
set((path == null ? "" : path + ".") + "z", loc.getZ());
set((path == null ? "" : path + ".") + "yaw", loc.getYaw());
set((path == null ? "" : path + ".") + "pitch", loc.getPitch());
}
@Override
public ItemStack getItemStack(final String path) {
final ItemStack stack = new ItemStack(Material.valueOf(getString(path + ".type", "AIR")), getInt(path + ".amount", 1), (short) getInt(path + ".damage", 0));
final ConfigurationSection enchants = getConfigurationSection(path + ".enchant");
if (enchants != null) {
for (final String enchant : enchants.getKeys(false)) {
final Enchantment enchantment = Enchantment.getByName(enchant.toUpperCase(Locale.ENGLISH));
if (enchantment == null) {
continue;
}
final int level = getInt(path + ".enchant." + enchant, enchantment.getStartLevel());
stack.addUnsafeEnchantment(enchantment, level);
}
}
return stack;
/*
* ,
* (byte)getInt(path + ".data", 0)
*/
}
public void setProperty(final String path, final ItemStack stack) {
final Map<String, Object> map = new HashMap<>();
map.put("type", stack.getType().toString());
map.put("amount", stack.getAmount());
map.put("damage", stack.getDurability());
final Map<Enchantment, Integer> enchantments = stack.getEnchantments();
if (!enchantments.isEmpty()) {
final Map<String, Integer> enchant = new HashMap<>();
for (final Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) {
enchant.put(entry.getKey().getName().toLowerCase(Locale.ENGLISH), entry.getValue());
}
map.put("enchant", enchant);
}
// getData().getData() is broken
//map.put("data", stack.getDurability());
set(path, map);
}
public void setProperty(final String path, final List object) {
set(path, new ArrayList(object));
}
public void setProperty(final String path, final Map object) {
set(path, new LinkedHashMap(object));
}
public Object getProperty(final String path) {
return get(path);
}
public void setProperty(final String path, final BigDecimal bigDecimal) {
set(path, bigDecimal.toString());
}
public void setProperty(final String path, final Object object) {
set(path, object);
}
public void removeProperty(final String path) {
set(path, null);
}
@Override
public synchronized Object get(final String path) {
return super.get(path);
}
@Override
public synchronized Object get(final String path, final Object def) {
return super.get(path, def);
}
public synchronized BigDecimal getBigDecimal(final String path, final BigDecimal def) {
final String input = super.getString(path);
return toBigDecimal(input, def);
}
@Override
public synchronized boolean getBoolean(final String path) {
return super.getBoolean(path);
}
@Override
public synchronized boolean getBoolean(final String path, final boolean def) {
return super.getBoolean(path, def);
}
@Override
public synchronized List<Boolean> getBooleanList(final String path) {
return super.getBooleanList(path);
}
@Override
public synchronized List<Byte> getByteList(final String path) {
return super.getByteList(path);
}
@Override
public synchronized List<Character> getCharacterList(final String path) {
return super.getCharacterList(path);
}
@Override
public synchronized ConfigurationSection getConfigurationSection(final String path) {
return super.getConfigurationSection(path);
}
@Override
public synchronized double getDouble(final String path) {
return super.getDouble(path);
}
@Override
public synchronized double getDouble(final String path, final double def) {
return super.getDouble(path, def);
}
@Override
public synchronized List<Double> getDoubleList(final String path) {
return super.getDoubleList(path);
}
@Override
public synchronized List<Float> getFloatList(final String path) {
return super.getFloatList(path);
}
@Override
public synchronized int getInt(final String path) {
return super.getInt(path);
}
@Override
public synchronized int getInt(final String path, final int def) {
return super.getInt(path, def);
}
@Override
public synchronized List<Integer> getIntegerList(final String path) {
return super.getIntegerList(path);
}
@Override
public synchronized ItemStack getItemStack(final String path, final ItemStack def) {
return super.getItemStack(path, def);
}
@Override
public synchronized Set<String> getKeys(final boolean deep) {
return super.getKeys(deep);
}
@Override
public synchronized List<?> getList(final String path) {
return super.getList(path);
}
@Override
public synchronized List<?> getList(final String path, final List<?> def) {
return super.getList(path, def);
}
@Override
public synchronized long getLong(final String path) {
return super.getLong(path);
}
@Override
public synchronized long getLong(final String path, final long def) {
return super.getLong(path, def);
}
@Override
public synchronized List<Long> getLongList(final String path) {
return super.getLongList(path);
}
public synchronized Map<String, Object> getMap() {
return map;
}
@Override
public synchronized List<Map<?, ?>> getMapList(final String path) {
return super.getMapList(path);
}
@Override
public synchronized OfflinePlayer getOfflinePlayer(final String path) {
return super.getOfflinePlayer(path);
}
@Override
public synchronized OfflinePlayer getOfflinePlayer(final String path, final OfflinePlayer def) {
return super.getOfflinePlayer(path, def);
}
@Override
public synchronized List<Short> getShortList(final String path) {
return super.getShortList(path);
}
@Override
public synchronized String getString(final String path) {
return super.getString(path);
}
@Override
public synchronized String getString(final String path, final String def) {
return super.getString(path, def);
}
@Override
public synchronized List<String> getStringList(final String path) {
return super.getStringList(path);
}
@Override
public synchronized Map<String, Object> getValues(final boolean deep) {
return super.getValues(deep);
}
@Override
public synchronized Vector getVector(final String path) {
return super.getVector(path);
}
@Override
public synchronized Vector getVector(final String path, final Vector def) {
return super.getVector(path, def);
}
@Override
public synchronized boolean isBoolean(final String path) {
return super.isBoolean(path);
}
@Override
public synchronized boolean isConfigurationSection(final String path) {
return super.isConfigurationSection(path);
}
@Override
public synchronized boolean isDouble(final String path) {
return super.isDouble(path);
}
@Override
public synchronized boolean isInt(final String path) {
return super.isInt(path);
}
@Override
public synchronized boolean isItemStack(final String path) {
return super.isItemStack(path);
}
@Override
public synchronized boolean isList(final String path) {
return super.isList(path);
}
@Override
public synchronized boolean isLong(final String path) {
return super.isLong(path);
}
@Override
public synchronized boolean isOfflinePlayer(final String path) {
return super.isOfflinePlayer(path);
}
@Override
public synchronized boolean isSet(final String path) {
return super.isSet(path);
}
@Override
public synchronized boolean isString(final String path) {
return super.isString(path);
}
@Override
public synchronized boolean isVector(final String path) {
return super.isVector(path);
}
@Override
public synchronized void set(final String path, final Object value) {
super.set(path, value);
}
private static final class WriteRunner implements Runnable {
private final File configFile;
private final String data;
private final AtomicInteger pendingDiskWrites;
private WriteRunner(final File configFile, final String data, final AtomicInteger pendingDiskWrites) {
this.configFile = configFile;
this.data = data;
this.pendingDiskWrites = pendingDiskWrites;
}
@Override
public void run() {
//long startTime = System.nanoTime();
synchronized (configFile) {
if (pendingDiskWrites.get() > 1) {
// Writes can be skipped, because they are stored in a queue (in the executor).
// Only the last is actually written.
pendingDiskWrites.decrementAndGet();
//LOGGER.log(Level.INFO, configFile + " skipped writing in " + (System.nanoTime() - startTime) + " nsec.");
return;
}
try {
Files.createParentDirs(configFile);
if (!configFile.exists()) {
try {
LOGGER.log(Level.INFO, tl("creatingEmptyConfig", configFile.toString()));
if (!configFile.createNewFile()) {
LOGGER.log(Level.SEVERE, tl("failedToCreateConfig", configFile.toString()));
return;
}
} catch (final IOException ex) {
LOGGER.log(Level.SEVERE, tl("failedToCreateConfig", configFile.toString()), ex);
return;
}
}
try (final FileOutputStream fos = new FileOutputStream(configFile)) {
try (final OutputStreamWriter writer = new OutputStreamWriter(fos, UTF8)) {
writer.write(data);
}
}
} catch (final IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
} finally {
//LOGGER.log(Level.INFO, configFile + " written to disk in " + (System.nanoTime() - startTime) + " nsec.");
pendingDiskWrites.decrementAndGet();
}
}
}
}
}
@@ -1,781 +0,0 @@
package com.earth2me.essentials;
import com.earth2me.essentials.craftbukkit.BanLookup;
import com.earth2me.essentials.craftbukkit.FakeWorld;
import com.earth2me.essentials.settings.Spawns;
import com.earth2me.essentials.storage.YamlStorageWriter;
import com.earth2me.essentials.utils.StringUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.Maps;
import net.ess3.api.IEssentials;
import org.bukkit.BanList;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
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.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.earth2me.essentials.I18n.tl;
public class EssentialsUpgrade {
private final static Logger LOGGER = Logger.getLogger("Essentials");
private 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*$";
private static final Pattern PATTERN_CONFIG_NAME = Pattern.compile(PATTERN_CONFIG_NAME_REGEX);
private final transient IEssentials ess;
private final transient EssentialsConf doneFile;
EssentialsUpgrade(final IEssentials essentials) {
ess = essentials;
if (!ess.getDataFolder().exists()) {
ess.getDataFolder().mkdirs();
}
doneFile = new EssentialsConf(new File(ess.getDataFolder(), "upgrades-done.yml"));
doneFile.load();
}
public 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");
if (!userdir.exists()) {
return;
}
int countFiles = 0;
int countFails = 0;
int countEssCache = 0;
int countBukkit = 0;
ess.getLogger().info("Found " + userdir.list().length + " files to convert...");
for (final String string : userdir.list()) {
if (!string.endsWith(".yml") || string.length() < 5) {
continue;
}
final int showProgress = countFiles % 250;
if (showProgress == 0) {
ess.getUserMap().getUUIDMap().forceWriteUUIDMap();
ess.getLogger().info("Converted " + countFiles + "/" + userdir.list().length);
}
countFiles++;
final String name = string.substring(0, string.length() - 4);
final EssentialsUserConf config;
UUID uuid = null;
try {
uuid = UUID.fromString(name);
} catch (final IllegalArgumentException ex) {
final File file = new File(userdir, string);
final EssentialsConf conf = new EssentialsConf(file);
conf.load();
conf.setProperty("lastAccountName", name);
conf.save();
final String uuidConf = ignoreUFCache ? "force-uuid" : "uuid";
final String uuidString = conf.getString(uuidConf, null);
for (int i = 0; i < 4; i++) {
try {
uuid = UUID.fromString(uuidString);
countEssCache++;
break;
} catch (final Exception ex2) {
if (conf.getBoolean("npc", false)) {
uuid = UUID.nameUUIDFromBytes(("NPC:" + name).getBytes(Charsets.UTF_8));
break;
}
final org.bukkit.OfflinePlayer player = ess.getServer().getOfflinePlayer(name);
uuid = player.getUniqueId();
}
if (uuid != null) {
countBukkit++;
break;
}
}
if (uuid != null) {
conf.forceSave();
config = new EssentialsUserConf(name, uuid, new File(userdir, uuid + ".yml"));
config.convertLegacyFile();
ess.getUserMap().trackUUID(uuid, name, false);
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 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;
}
LOGGER.info("Attempting to migrate ignore list to UUIDs");
final File userdataFolder = new File(ess.getDataFolder(), "userdata");
if (!userdataFolder.exists() || !userdataFolder.isDirectory()) {
return;
}
final File[] userFiles = userdataFolder.listFiles();
for (final File file : userFiles) {
if (!file.isFile() || !file.getName().endsWith(".yml")) {
continue;
}
final EssentialsConf config = new EssentialsConf(file);
try {
config.load();
if (config.hasProperty("ignore")) {
final List<String> migratedIgnores = new ArrayList<>();
for (final String name : Collections.synchronizedList(config.getStringList("ignore"))) {
if (name == null) {
continue;
}
if (pattern.matcher(name.trim()).matches()) {
LOGGER.info("Detected already migrated ignore list!");
return;
}
final User user = ess.getOfflineUser(name);
if (user != null && user.getBase() != null) {
migratedIgnores.add(user.getBase().getUniqueId().toString());
}
}
config.removeProperty("ignore");
config.setProperty("ignore", migratedIgnores);
config.forceSave();
}
} catch (final RuntimeException ex) {
LOGGER.log(Level.INFO, "File: " + file.toString());
throw ex;
}
}
doneFile.setProperty("updateUsersIgnoreListUUID", true);
doneFile.save();
LOGGER.info("Done converting ignore list.");
}
public void convertKits() {
final Kits kits = ess.getKits();
final EssentialsConf config = kits.getConfig();
if (doneFile.getBoolean("kitsyml", false)) {
return;
}
LOGGER.info("Attempting to convert old kits in config.yml to new kits.yml");
final ConfigurationSection section = ess.getSettings().getKitSection();
if (section == null) {
LOGGER.info("No kits found to migrate.");
return;
}
final Map<String, Object> legacyKits = ess.getSettings().getKitSection().getValues(true);
for (final Map.Entry<String, Object> entry : legacyKits.entrySet()) {
LOGGER.info("Converting " + entry.getKey());
config.set("kits." + entry.getKey(), entry.getValue());
}
config.save();
doneFile.setProperty("kitsyml", true);
doneFile.save();
LOGGER.info("Done converting kits.");
}
private void moveMotdRulesToFile(final String name) {
if (doneFile.getBoolean("move" + name + "ToFile", false)) {
return;
}
try {
final File file = new File(ess.getDataFolder(), name + ".txt");
if (file.exists()) {
return;
}
final File configFile = new File(ess.getDataFolder(), "config.yml");
if (!configFile.exists()) {
return;
}
final EssentialsConf conf = new EssentialsConf(configFile);
conf.load();
final List<String> lines = conf.getStringList(name);
if (lines != null && !lines.isEmpty()) {
if (!file.createNewFile()) {
throw new IOException("Failed to create file " + file);
}
final PrintWriter writer = new PrintWriter(file);
for (final String line : lines) {
writer.println(line);
}
writer.close();
}
doneFile.setProperty("move" + name + "ToFile", true);
doneFile.save();
} catch (final IOException e) {
LOGGER.log(Level.SEVERE, tl("upgradingFilesError"), e);
}
}
private void removeLinesFromConfig(final File file, final String regex, final String info) throws Exception {
boolean needUpdate = false;
final BufferedReader bReader = new BufferedReader(new FileReader(file));
final File tempFile = File.createTempFile("essentialsupgrade", ".tmp.yml", ess.getDataFolder());
final BufferedWriter bWriter = new BufferedWriter(new FileWriter(tempFile));
do {
final String line = bReader.readLine();
if (line == null) {
break;
}
if (line.matches(regex)) {
if (!needUpdate && info != null) {
bWriter.write(info, 0, info.length());
bWriter.newLine();
}
needUpdate = true;
} else {
if (line.endsWith("\r\n")) {
bWriter.write(line, 0, line.length() - 2);
} else if (line.endsWith("\r") || line.endsWith("\n")) {
bWriter.write(line, 0, line.length() - 1);
} else {
bWriter.write(line, 0, line.length());
}
bWriter.newLine();
}
} while (true);
bReader.close();
bWriter.close();
if (needUpdate) {
if (!file.renameTo(new File(file.getParentFile(), file.getName().concat("." + System.currentTimeMillis() + ".upgradebackup")))) {
throw new Exception(tl("configFileMoveError"));
}
if (!tempFile.renameTo(file)) {
throw new Exception(tl("configFileRenameError"));
}
} else {
tempFile.delete();
}
}
private void updateUsersPowerToolsFormat() {
if (doneFile.getBoolean("updateUsersPowerToolsFormat", false)) {
return;
}
final File userdataFolder = new File(ess.getDataFolder(), "userdata");
if (!userdataFolder.exists() || !userdataFolder.isDirectory()) {
return;
}
final File[] userFiles = userdataFolder.listFiles();
for (final File file : userFiles) {
if (!file.isFile() || !file.getName().endsWith(".yml")) {
continue;
}
final EssentialsConf config = new EssentialsConf(file);
try {
config.load();
if (config.hasProperty("powertools")) {
final Map<String, Object> powertools = config.getConfigurationSection("powertools").getValues(false);
if (powertools == null) {
continue;
}
for (final Map.Entry<String, Object> entry : powertools.entrySet()) {
if (entry.getValue() instanceof String) {
final List<String> temp = new ArrayList<>();
temp.add((String) entry.getValue());
powertools.put(entry.getKey(), temp);
}
}
config.forceSave();
}
} catch (final RuntimeException ex) {
LOGGER.log(Level.INFO, "File: " + file.toString());
throw ex;
}
}
doneFile.setProperty("updateUsersPowerToolsFormat", true);
doneFile.save();
}
private void updateUsersHomesFormat() {
if (doneFile.getBoolean("updateUsersHomesFormat", false)) {
return;
}
final File userdataFolder = new File(ess.getDataFolder(), "userdata");
if (!userdataFolder.exists() || !userdataFolder.isDirectory()) {
return;
}
final File[] userFiles = userdataFolder.listFiles();
for (final File file : userFiles) {
if (!file.isFile() || !file.getName().endsWith(".yml")) {
continue;
}
final EssentialsConf config = new EssentialsConf(file);
try {
config.load();
if (config.hasProperty("home") && config.hasProperty("home.default")) {
final String defworld = (String) config.getProperty("home.default");
final Location defloc = getFakeLocation(config, "home.worlds." + defworld);
if (defloc != null) {
config.setProperty("homes.home", defloc);
}
final Set<String> worlds = config.getConfigurationSection("home.worlds").getKeys(false);
Location loc;
String worldName;
if (worlds == null) {
continue;
}
for (final String world : worlds) {
if (defworld.equalsIgnoreCase(world)) {
continue;
}
loc = getFakeLocation(config, "home.worlds." + world);
if (loc == null) {
continue;
}
worldName = loc.getWorld().getName().toLowerCase(Locale.ENGLISH);
if (worldName != null && !worldName.isEmpty()) {
config.setProperty("homes." + worldName, loc);
}
}
config.removeProperty("home");
config.forceSave();
}
} catch (final RuntimeException ex) {
LOGGER.log(Level.INFO, "File: " + file.toString());
throw ex;
}
}
doneFile.setProperty("updateUsersHomesFormat", true);
doneFile.save();
}
private void sanitizeAllUserFilenames() {
if (doneFile.getBoolean("sanitizeAllUserFilenames", false)) {
return;
}
final File usersFolder = new File(ess.getDataFolder(), "userdata");
if (!usersFolder.exists()) {
return;
}
final File[] listOfFiles = usersFolder.listFiles();
for (final File listOfFile : listOfFiles) {
final String filename = listOfFile.getName();
if (!listOfFile.isFile() || !filename.endsWith(".yml")) {
continue;
}
final String sanitizedFilename = StringUtil.sanitizeFileName(filename.substring(0, filename.length() - 4)) + ".yml";
if (sanitizedFilename.equals(filename)) {
continue;
}
final File tmpFile = new File(listOfFile.getParentFile(), sanitizedFilename + ".tmp");
final File newFile = new File(listOfFile.getParentFile(), sanitizedFilename);
if (!listOfFile.renameTo(tmpFile)) {
LOGGER.log(Level.WARNING, tl("userdataMoveError", filename, sanitizedFilename));
continue;
}
if (newFile.exists()) {
LOGGER.log(Level.WARNING, tl("duplicatedUserdata", filename, sanitizedFilename));
continue;
}
if (!tmpFile.renameTo(newFile)) {
LOGGER.log(Level.WARNING, tl("userdataMoveBackError", sanitizedFilename, sanitizedFilename));
}
}
doneFile.setProperty("sanitizeAllUserFilenames", true);
doneFile.save();
}
private World getFakeWorld(final String name) {
final File bukkitDirectory = ess.getDataFolder().getParentFile().getParentFile();
final File worldDirectory = new File(bukkitDirectory, name);
if (worldDirectory.exists() && worldDirectory.isDirectory()) {
return new FakeWorld(worldDirectory.getName(), World.Environment.NORMAL);
}
return null;
}
public Location getFakeLocation(final EssentialsConf config, final String path) {
final String worldName = config.getString((path != null ? path + "." : "") + "world");
if (worldName == null || worldName.isEmpty()) {
return null;
}
final World world = getFakeWorld(worldName);
if (world == null) {
return null;
}
return new Location(world, config.getDouble((path != null ? path + "." : "") + "x", 0), config.getDouble((path != null ? path + "." : "") + "y", 0), config.getDouble((path != null ? path + "." : "") + "z", 0), (float) config.getDouble((path != null ? path + "." : "") + "yaw", 0), (float) config.getDouble((path != null ? path + "." : "") + "pitch", 0));
}
private void deleteOldItemsCsv() {
if (doneFile.getBoolean("deleteOldItemsCsv", false)) {
return;
}
final File file = new File(ess.getDataFolder(), "items.csv");
if (file.exists()) {
try {
final Set<BigInteger> oldconfigs = new HashSet<>();
oldconfigs.add(new BigInteger("66ec40b09ac167079f558d1099e39f10", 16)); // sep 1
oldconfigs.add(new BigInteger("34284de1ead43b0bee2aae85e75c041d", 16)); // crlf
oldconfigs.add(new BigInteger("c33bc9b8ee003861611bbc2f48eb6f4f", 16)); // jul 24
oldconfigs.add(new BigInteger("6ff17925430735129fc2a02f830c1daa", 16)); // crlf
final MessageDigest digest = ManagedFile.getDigest();
final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
final byte[] buffer = new byte[1024];
try (final DigestInputStream dis = new DigestInputStream(bis, digest)) {
while (dis.read(buffer) != -1) {
}
}
final BigInteger hash = new BigInteger(1, digest.digest());
if (oldconfigs.contains(hash) && !file.delete()) {
throw new IOException("Could not delete file " + file.toString());
}
doneFile.setProperty("deleteOldItemsCsv", true);
doneFile.save();
} catch (final IOException ex) {
Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
}
}
}
private void updateSpawnsToNewSpawnsConfig() {
if (doneFile.getBoolean("updateSpawnsToNewSpawnsConfig", false)) {
return;
}
final File configFile = new File(ess.getDataFolder(), "spawn.yml");
if (configFile.exists()) {
final EssentialsConf config = new EssentialsConf(configFile);
try {
config.load();
if (!config.hasProperty("spawns")) {
final Spawns spawns = new Spawns();
final Set<String> keys = config.getKeys(false);
for (final String group : keys) {
final Location loc = getFakeLocation(config, group);
spawns.getSpawns().put(group.toLowerCase(Locale.ENGLISH), loc);
}
if (!configFile.renameTo(new File(ess.getDataFolder(), "spawn.yml.old"))) {
throw new Exception(tl("fileRenameError", "spawn.yml"));
}
try (final PrintWriter writer = new PrintWriter(configFile)) {
new YamlStorageWriter(writer).save(spawns);
}
}
} catch (final Exception ex) {
Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
}
}
doneFile.setProperty("updateSpawnsToNewSpawnsConfig", true);
doneFile.save();
}
private void updateJailsToNewJailsConfig() {
if (doneFile.getBoolean("updateJailsToNewJailsConfig", false)) {
return;
}
final File configFile = new File(ess.getDataFolder(), "jail.yml");
if (configFile.exists()) {
final EssentialsConf config = new EssentialsConf(configFile);
try {
config.load();
if (!config.hasProperty("jails")) {
final com.earth2me.essentials.settings.Jails jails = new com.earth2me.essentials.settings.Jails();
final Set<String> keys = config.getKeys(false);
for (final String jailName : keys) {
final Location loc = getFakeLocation(config, jailName);
jails.getJails().put(jailName.toLowerCase(Locale.ENGLISH), loc);
}
if (!configFile.renameTo(new File(ess.getDataFolder(), "jail.yml.old"))) {
throw new Exception(tl("fileRenameError", "jail.yml"));
}
try (final PrintWriter writer = new PrintWriter(configFile)) {
new YamlStorageWriter(writer).save(jails);
}
}
} catch (final Exception ex) {
Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
}
}
doneFile.setProperty("updateJailsToNewJailsConfig", true);
doneFile.save();
}
private void warnMetrics() {
if (doneFile.getBoolean("warnMetrics", false)) {
return;
}
doneFile.setProperty("warnMetrics", true);
doneFile.save();
}
private void uuidFileChange() {
if (doneFile.getBoolean("uuidFileChange", false)) {
return;
}
final Boolean ignoreUFCache = doneFile.getBoolean("ignore-userfiles-cache", false);
final File userdir = new File(ess.getDataFolder(), "userdata");
if (!userdir.exists()) {
return;
}
int countFiles = 0;
int countReqFiles = 0;
for (final String string : userdir.list()) {
if (!string.endsWith(".yml") || string.length() < 5) {
continue;
}
countFiles++;
final String name = string.substring(0, string.length() - 4);
UUID uuid = null;
try {
uuid = UUID.fromString(name);
} catch (final IllegalArgumentException ex) {
countReqFiles++;
}
if (countFiles > 100) {
break;
}
}
if (countReqFiles < 1) {
return;
}
ess.getLogger().info("#### Starting Essentials UUID userdata conversion in a few seconds. ####");
ess.getLogger().info("We recommend you take a backup of your server before upgrading from the old username system.");
try {
Thread.sleep(15000);
} catch (final InterruptedException ex) {
// NOOP
}
uuidFileConvert(ess, ignoreUFCache);
doneFile.setProperty("uuidFileChange", true);
doneFile.save();
}
public void banFormatChange() {
if (doneFile.getBoolean("banFormatChange", false)) {
return;
}
ess.getLogger().info("Starting Essentials ban format conversion");
final File userdir = new File(ess.getDataFolder(), "userdata");
if (!userdir.exists()) {
return;
}
int countFiles = 0;
ess.getLogger().info("Found " + userdir.list().length + " files to convert...");
for (final String string : userdir.list()) {
if (!string.endsWith(".yml") || string.length() < 5) {
continue;
}
final int showProgress = countFiles % 250;
if (showProgress == 0) {
ess.getLogger().info("Converted " + countFiles + "/" + userdir.list().length);
}
countFiles++;
final File pFile = new File(userdir, string);
final EssentialsConf conf = new EssentialsConf(pFile);
conf.load();
String banReason;
long banTimeout;
try {
banReason = conf.getConfigurationSection("ban").getString("reason");
} catch (final NullPointerException n) {
banReason = null;
}
final String playerName = conf.getString("lastAccountName");
if (playerName != null && playerName.length() > 1 && banReason != null && banReason.length() > 1) {
try {
if (conf.getConfigurationSection("ban").contains("timeout")) {
banTimeout = Long.parseLong(conf.getConfigurationSection("ban").getString("timeout"));
} else {
banTimeout = 0L;
}
} catch (final NumberFormatException n) {
banTimeout = 0L;
}
if (BanLookup.isBanned(ess, playerName)) {
updateBan(playerName, banReason, banTimeout);
}
}
conf.removeProperty("ban");
conf.save();
}
doneFile.setProperty("banFormatChange", true);
doneFile.save();
ess.getLogger().info("Ban format update complete.");
}
private void updateBan(final String playerName, final String banReason, final Long banTimeout) {
if (banTimeout == 0) {
Bukkit.getBanList(BanList.Type.NAME).addBan(playerName, banReason, null, Console.NAME);
} else {
Bukkit.getBanList(BanList.Type.NAME).addBan(playerName, banReason, new Date(banTimeout), Console.NAME);
}
}
private void repairUserMap() {
if (doneFile.getBoolean("userMapRepaired", false)) {
return;
}
ess.getLogger().info("Starting usermap repair");
final File userdataFolder = new File(ess.getDataFolder(), "userdata");
if (!userdataFolder.isDirectory()) {
ess.getLogger().warning("Missing userdata folder, aborting");
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);
return;
}
}
ess.getUserMap().getNames().putAll(names);
ess.getUserMap().reloadConfig();
doneFile.setProperty("userMapRepaired", true);
doneFile.save();
ess.getLogger().info("Completed usermap repair.");
}
public void beforeSettings() {
if (!ess.getDataFolder().exists()) {
ess.getDataFolder().mkdirs();
}
moveMotdRulesToFile("motd");
moveMotdRulesToFile("rules");
}
public void afterSettings() {
sanitizeAllUserFilenames();
updateUsersPowerToolsFormat();
updateUsersHomesFormat();
deleteOldItemsCsv();
updateSpawnsToNewSpawnsConfig();
updateJailsToNewJailsConfig();
uuidFileChange();
banFormatChange();
warnMetrics();
repairUserMap();
convertIgnoreList();
}
}
@@ -1,62 +0,0 @@
package com.earth2me.essentials;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import org.bukkit.Bukkit;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
import java.util.UUID;
import java.util.logging.Level;
public class EssentialsUserConf extends EssentialsConf {
public final String username;
public final UUID uuid;
public EssentialsUserConf(final String username, final UUID uuid, final File configFile) {
super(configFile);
this.username = username;
this.uuid = uuid;
}
@Override
public boolean legacyFileExists() {
final File file = new File(configFile.getParentFile(), username + ".yml");
return file.exists();
}
@Override
public void convertLegacyFile() {
final File file = new File(configFile.getParentFile(), username + ".yml");
try {
Files.move(file, new File(configFile.getParentFile(), uuid + ".yml"));
} catch (final IOException ex) {
Bukkit.getLogger().log(Level.WARNING, "Failed to migrate user: " + username, ex);
}
setProperty("lastAccountName", username);
}
private File getAltFile() {
final UUID fn = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username.toLowerCase(Locale.ENGLISH)).getBytes(Charsets.UTF_8));
return new File(configFile.getParentFile(), fn.toString() + ".yml");
}
@Override
public boolean altFileExists() {
if (username.equals(username.toLowerCase())) {
return false;
}
return getAltFile().exists();
}
@Override
public void convertAltFile() {
try {
Files.move(getAltFile(), new File(configFile.getParentFile(), uuid + ".yml"));
} catch (final IOException ex) {
Bukkit.getLogger().log(Level.WARNING, "Failed to migrate user: " + username, ex);
}
}
}
@@ -1,210 +0,0 @@
package com.earth2me.essentials;
import net.ess3.api.IEssentials;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
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 ResourceBundle NULL_BUNDLE = new ResourceBundle() {
public Enumeration<String> getKeys() {
return null;
}
protected Object handleGetObject(final String key) {
return null;
}
};
private static I18n instance;
private final transient Locale defaultLocale = Locale.getDefault();
private final transient ResourceBundle defaultBundle;
private final transient IEssentials ess;
private transient Locale currentLocale = defaultLocale;
private transient ResourceBundle customBundle;
private transient ResourceBundle localeBundle;
private transient 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) {
if (instance == null) {
return "";
}
if (objects.length == 0) {
return NODOUBLEMARK.matcher(instance.translate(string)).replaceAll("'");
} else {
return instance.format(string, 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);
}
public void onEnable() {
instance = this;
}
public void onDisable() {
instance = null;
}
@Override
public Locale getCurrentLocale() {
return currentLocale;
}
private String translate(final String string) {
try {
try {
return customBundle.getString(string);
} catch (final MissingResourceException ex) {
return localeBundle.getString(string);
}
} catch (final MissingResourceException ex) {
Logger.getLogger("Essentials").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);
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\\]");
messageFormat = new MessageFormat(format);
}
messageFormatCache.put(format, messageFormat);
}
return messageFormat.format(objects).replace(' ', ' '); // replace nbsp with a space
}
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]);
}
}
ResourceBundle.clearCache();
messageFormatCache = new HashMap<>();
Logger.getLogger("Essentials").log(Level.INFO, String.format("Using locale %s", currentLocale.toString()));
try {
localeBundle = ResourceBundle.getBundle(MESSAGES, currentLocale, new UTF8PropertiesControl());
} 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;
}
}
/**
* 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;
FileResClassLoader(final ClassLoader classLoader, final IEssentials ess) {
super(classLoader);
this.dataFolder = ess.getDataFolder();
}
@Override
public URL getResource(final String string) {
final File file = new File(dataFolder, string);
if (file.exists()) {
try {
return file.toURI().toURL();
} catch (final MalformedURLException ignored) {
}
}
return null;
}
@Override
public InputStream getResourceAsStream(final String string) {
final File file = new File(dataFolder, string);
if (file.exists()) {
try {
return new FileInputStream(file);
} catch (final FileNotFoundException ignored) {
}
}
return null;
}
}
/**
* 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 {
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;
InputStream stream = null;
if (reload) {
final URL url = loader.getResource(resourceName);
if (url != null) {
final URLConnection connection = url.openConnection();
if (connection != null) {
connection.setUseCaches(false);
stream = connection.getInputStream();
}
}
} else {
stream = loader.getResourceAsStream(resourceName);
}
if (stream != null) {
try {
// use UTF-8 here, this is the important bit
bundle = new PropertyResourceBundle(new InputStreamReader(stream, StandardCharsets.UTF_8));
} finally {
stream.close();
}
}
return bundle;
}
}
}
@@ -1,213 +0,0 @@
package com.earth2me.essentials;
import com.earth2me.essentials.api.IAsyncTeleport;
import com.earth2me.essentials.commands.IEssentialsCommand;
import net.ess3.api.ITeleport;
import net.ess3.api.MaxMoneyException;
import net.ess3.api.events.AfkStatusChangeEvent;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* Provides access to the user abstraction and stored data. Maintainers should add methods to <i>this interface</i>.
*
* @deprecated External plugins should use {@link net.ess3.api.IUser} instead of this interface, in case future APIs are added.
*/
@Deprecated
public interface IUser {
boolean isAuthorized(String node);
boolean isAuthorized(IEssentialsCommand cmd);
boolean isAuthorized(IEssentialsCommand cmd, String permissionPrefix);
boolean isPermissionSet(String node);
void healCooldown() throws Exception;
void giveMoney(BigDecimal value) throws MaxMoneyException;
void giveMoney(final BigDecimal value, final CommandSource initiator) throws MaxMoneyException;
void payUser(final User reciever, final BigDecimal value) throws Exception;
void takeMoney(BigDecimal value);
void takeMoney(final BigDecimal value, final CommandSource initiator);
boolean canAfford(BigDecimal value);
Boolean canSpawnItem(final Material material);
void setLastLocation();
void setLogoutLocation();
void requestTeleport(final User player, final boolean here);
/**
* Returns whether this user has an outstanding teleport request to deal with.
*
* @return whether there is a teleport request
*/
boolean hasOutstandingTeleportRequest();
/**
* @deprecated This API is not asynchronous. Use {@link com.earth2me.essentials.api.IAsyncTeleport IAsyncTeleport} with {@link IUser#getAsyncTeleport()}
*/
@Deprecated
ITeleport getTeleport();
IAsyncTeleport getAsyncTeleport();
BigDecimal getMoney();
void setMoney(final BigDecimal value) throws MaxMoneyException;
void setAfk(final boolean set, final AfkStatusChangeEvent.Cause cause);
/**
* 'Hidden' Represents when a player is hidden from others. This status includes when the player is hidden via other
* supported plugins. Use isVanished() if you want to check if a user is vanished by Essentials.
*
* @return If the user is hidden or not
* @see IUser#isVanished()
*/
boolean isHidden();
void setHidden(boolean vanish);
boolean isGodModeEnabled();
String getGroup();
boolean inGroup(final String group);
boolean canBuild();
long getTeleportRequestTime();
void enableInvulnerabilityAfterTeleport();
void resetInvulnerabilityAfterTeleport();
boolean hasInvulnerabilityAfterTeleport();
/**
* 'Vanished' Represents when a player is hidden from others by Essentials. This status does NOT include when the
* player is hidden via other plugins. Use isHidden() if you want to check if a user is vanished by any supported
* plugin.
*
* @return If the user is vanished or not
* @see IUser#isHidden()
*/
boolean isVanished();
void setVanished(boolean vanish);
boolean isIgnoreExempt();
void sendMessage(String message);
/*
* UserData
*/
Location getHome(String name) throws Exception;
Location getHome(Location loc) throws Exception;
List<String> getHomes();
void setHome(String name, Location loc);
void delHome(String name) throws Exception;
boolean hasHome();
Location getLastLocation();
Location getLogoutLocation();
long getLastTeleportTimestamp();
void setLastTeleportTimestamp(long time);
String getJail();
void setJail(String jail);
List<String> getMails();
void addMail(String mail);
boolean isAfk();
@Deprecated
void setAfk(final boolean set);
boolean isIgnoreMsg();
void setIgnoreMsg(boolean ignoreMsg);
void setConfigProperty(String node, Object object);
Set<String> getConfigKeys();
Map<String, Object> getConfigMap();
Map<String, Object> getConfigMap(String node);
Map<Pattern, Long> getCommandCooldowns();
Date getCommandCooldownExpiry(String label);
void addCommandCooldown(Pattern pattern, Date expiresAt, boolean save);
boolean clearCommandCooldown(Pattern pattern);
/*
* PlayerExtension
*/
Player getBase();
CommandSource getSource();
String getName();
String getDisplayName();
String getAfkMessage();
void setAfkMessage(final String message);
long getAfkSince();
boolean isAcceptingPay();
void setAcceptingPay(boolean acceptingPay);
boolean isPromptingPayConfirm();
void setPromptingPayConfirm(boolean prompt);
boolean isPromptingClearConfirm();
void setPromptingClearConfirm(boolean prompt);
boolean isLastMessageReplyRecipient();
void setLastMessageReplyRecipient(boolean enabled);
Map<User, BigDecimal> getConfirmingPayments();
Block getTargetBlock(int maxDistance);
}
@@ -1,134 +0,0 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.NumberUtil;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration;
import java.io.File;
import java.math.BigDecimal;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static com.earth2me.essentials.I18n.capitalCase;
import static com.earth2me.essentials.I18n.tl;
public class Kits implements IConf {
private final EssentialsConf config;
private ConfigurationSection kits;
public Kits(final IEssentials essentials) {
config = new EssentialsConf(new File(essentials.getDataFolder(), "kits.yml"));
config.setTemplateName("/kits.yml");
reloadConfig();
}
@Override
public void reloadConfig() {
config.load();
kits = _getKits();
}
private ConfigurationSection _getKits() {
if (config.isConfigurationSection("kits")) {
final ConfigurationSection section = config.getConfigurationSection("kits");
final ConfigurationSection newSection = new MemoryConfiguration();
for (final String kitItem : section.getKeys(false)) {
if (section.isConfigurationSection(kitItem)) {
newSection.set(kitItem.toLowerCase(Locale.ENGLISH), section.getConfigurationSection(kitItem));
}
}
return newSection;
}
return null;
}
public EssentialsConf getConfig() {
return config;
}
public ConfigurationSection getKits() {
return kits;
}
public Map<String, Object> getKit(String name) {
name = name.replace('.', '_').replace('/', '_');
if (getKits() != null) {
final ConfigurationSection kits = getKits();
// For some reason, YAML doesn't sees keys as always lowercase even if they aren't defined like that.
// Workaround is to toLowercase when getting from the config, but showing normally elsewhere.
// ODDLY ENOUGH when you get the configuration section for ALL kits, it will return the proper
// case of each kit. But when you check for each kit's configuration section, it won't return the kit
// you just found if you don't toLowercase it.
if (kits.isConfigurationSection(name.toLowerCase())) {
return kits.getConfigurationSection(name.toLowerCase()).getValues(true);
}
}
return null;
}
// Tries to find an existing kit name that matches the given name, ignoring case. Returns null if no match.
public String matchKit(final String name) {
if (config.isConfigurationSection("kits")) {
final ConfigurationSection section = config.getConfigurationSection("kits");
if (section != null) {
for (final String kitName : section.getKeys(false)) {
if (kitName.equalsIgnoreCase(name)) {
return kitName;
}
}
}
}
return null;
}
public void addKit(final String name, final List<String> lines, final long delay) {
// Will overwrite but w/e
config.set("kits." + name + ".delay", delay);
config.set("kits." + name + ".items", lines);
kits = _getKits();
config.save();
}
public void removeKit(final String name) {
config.set("kits." + name, null);
kits = _getKits();
config.save();
}
public String listKits(final net.ess3.api.IEssentials ess, final User user) throws Exception {
try {
final ConfigurationSection kits = config.getConfigurationSection("kits");
final StringBuilder list = new StringBuilder();
for (final String kitItem : kits.getKeys(false)) {
if (user == null) {
list.append(" ").append(capitalCase(kitItem));
} else if (user.isAuthorized("essentials.kits." + kitItem.toLowerCase(Locale.ENGLISH))) {
String cost = "";
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));
}
final Kit kit = new Kit(kitItem, ess);
final double nextUse = kit.getNextUse(user);
if (nextUse == -1 && ess.getSettings().isSkippingUsedOneTimeKitsFromKitList()) {
continue;
} else if (nextUse != 0) {
name = tl("kitDelay", name);
}
list.append(" ").append(name).append(cost);
}
}
return list.toString().trim();
} catch (final Exception ex) {
throw new Exception(tl("kitError"), ex);
}
}
}
@@ -1,128 +0,0 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.FormatUtil;
import org.bukkit.ChatColor;
import org.bukkit.Server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static com.earth2me.essentials.I18n.tl;
public final class PlayerList {
private PlayerList() {
}
// Cosmetic list formatting
public static String listUsers(final IEssentials ess, final List<User> users, final String seperator) {
final StringBuilder groupString = new StringBuilder();
Collections.sort(users);
boolean needComma = false;
for (final User user : users) {
if (needComma) {
groupString.append(seperator);
}
needComma = true;
if (user.isAfk()) {
groupString.append(tl("listAfkTag"));
}
if (user.isHidden()) {
groupString.append(tl("listHiddenTag"));
}
user.setDisplayNick();
groupString.append(user.getDisplayName());
if (ess.getSettings().realNamesOnList() && !ChatColor.stripColor(user.getDisplayName()).equals(user.getName())) {
groupString.append(" (").append(user.getName()).append(")");
}
groupString.append("\u00a7f");
}
return groupString.toString();
}
// Produce a user summary: There are 5 out of maximum 10 players online.
public static String listSummary(final IEssentials ess, final User user, final boolean showHidden) {
final Server server = ess.getServer();
int playerHidden = 0;
int hiddenCount = 0;
for (final User onlinePlayer : ess.getOnlineUsers()) {
if (onlinePlayer.isHidden() || (user != null && !user.getBase().canSee(onlinePlayer.getBase()))) {
playerHidden++;
if (showHidden || user != null && user.getBase().canSee(onlinePlayer.getBase())) {
hiddenCount++;
}
}
}
final String online;
if (hiddenCount > 0) {
online = tl("listAmountHidden", ess.getOnlinePlayers().size() - playerHidden, hiddenCount, server.getMaxPlayers());
} else {
online = tl("listAmount", ess.getOnlinePlayers().size() - playerHidden, server.getMaxPlayers());
}
return online;
}
// 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()))) {
continue;
}
final String group = FormatUtil.stripFormat(FormatUtil.stripEssentialsFormat(onlineUser.getGroup().toLowerCase()));
final List<User> list = playerList.computeIfAbsent(group, k -> new ArrayList<>());
list.add(onlineUser);
}
return playerList;
}
// Handle the merging of groups
public static List<User> getMergedList(final IEssentials ess, final Map<String, List<User>> playerList, final String groupName) {
final Set<String> configGroups = ess.getSettings().getListGroupConfig().keySet();
final List<User> users = new ArrayList<>();
for (final String configGroup : configGroups) {
if (configGroup.equalsIgnoreCase(groupName)) {
final String[] groupValues = ess.getSettings().getListGroupConfig().get(configGroup).toString().trim().split(" ");
for (String groupValue : groupValues) {
groupValue = groupValue.toLowerCase(Locale.ENGLISH);
if (groupValue.isEmpty()) {
continue;
}
final List<User> u = playerList.get(groupValue.trim());
if (u == null || u.isEmpty()) {
continue;
}
playerList.remove(groupValue);
users.addAll(u);
}
}
}
return users;
}
// Output a playerlist of just a single group, /list <groupname>
public static String listGroupUsers(final IEssentials ess, final Map<String, List<User>> playerList, final String groupName) throws Exception {
final List<User> users = getMergedList(ess, playerList, groupName);
final List<User> groupUsers = playerList.get(groupName);
if (groupUsers != null && !groupUsers.isEmpty()) {
users.addAll(groupUsers);
}
if (users.isEmpty()) {
throw new Exception(tl("groupDoesNotExist"));
}
final String displayGroupName = Character.toTitleCase(groupName.charAt(0)) +
groupName.substring(1);
return outputFormat(displayGroupName, listUsers(ess, users, ", "));
}
// Build the output string
public static String outputFormat(final String group, final String message) {
return tl("listGroupTag", FormatUtil.replaceFormat(group)) +
message;
}
}
@@ -1,209 +0,0 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.LocationUtil;
import com.earth2me.essentials.utils.VersionUtil;
import io.papermc.lib.PaperLib;
import net.ess3.api.InvalidWorldException;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Biome;
import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
public class RandomTeleport implements IConf {
private static final Random RANDOM = new Random();
private static final int HIGHEST_BLOCK_Y_OFFSET = VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_15_R01) ? 1 : 0;
private final IEssentials essentials;
private final EssentialsConf config;
private final ConcurrentLinkedQueue<Location> cachedLocations = new ConcurrentLinkedQueue<>();
public RandomTeleport(final IEssentials essentials) {
this.essentials = essentials;
final File file = new File(essentials.getDataFolder(), "tpr.yml");
config = new EssentialsConf(file);
config.setTemplateName("/tpr.yml");
config.options().copyHeader(true);
reloadConfig();
}
@Override
public void reloadConfig() {
config.load();
cachedLocations.clear();
}
public Location getCenter() {
try {
final Location center = config.getLocation("center", essentials.getServer());
if (center != null) {
return center;
}
} catch (final InvalidWorldException ignored) {
}
final Location center = essentials.getServer().getWorlds().get(0).getWorldBorder().getCenter();
center.setY(center.getWorld().getHighestBlockYAt(center) + 1);
setCenter(center);
return center;
}
public void setCenter(final Location center) {
config.setProperty("center", center);
config.save();
}
public double getMinRange() {
return config.getDouble("min-range", 0d);
}
public void setMinRange(final double minRange) {
config.setProperty("min-range", minRange);
config.save();
}
public double getMaxRange() {
return config.getDouble("max-range", getCenter().getWorld().getWorldBorder().getSize() / 2);
}
public void setMaxRange(final double maxRange) {
config.setProperty("max-range", maxRange);
config.save();
}
public Set<Biome> getExcludedBiomes() {
final List<String> biomeNames = config.getStringList("excluded-biomes");
final Set<Biome> excludedBiomes = new HashSet<>();
for (final String biomeName : biomeNames) {
try {
excludedBiomes.add(Biome.valueOf(biomeName.toUpperCase()));
} catch (final IllegalArgumentException ignored) {
}
}
return excludedBiomes;
}
public int getFindAttempts() {
return config.getInt("find-attempts", 10);
}
public int getCacheThreshold() {
return config.getInt("cache-threshold", 10);
}
public boolean getPreCache() {
return config.getBoolean("pre-cache", false);
}
public Queue<Location> getCachedLocations() {
return cachedLocations;
}
// 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();
// Try to build up the cache if it is below the threshold
if (cachedLocations.size() < this.getCacheThreshold()) {
cacheRandomLocations(center, minRange, maxRange);
}
final CompletableFuture<Location> future = new CompletableFuture<>();
// Return a random location immediately if one is available, otherwise try to find one now
if (cachedLocations.isEmpty()) {
attemptRandomLocation(findAttempts, center, minRange, maxRange).thenAccept(future::complete);
} else {
future.complete(cachedLocations.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) {
essentials.getServer().getScheduler().scheduleSyncDelayedTask(essentials, () -> {
for (int i = 0; i < this.getFindAttempts(); ++i) {
calculateRandomLocation(center, minRange, maxRange).thenAccept(location -> {
if (isValidRandomLocation(location)) {
this.getCachedLocations().add(location);
}
});
}
});
}
// Recursively attempt to find a random location. After a maximum number of attempts, the center is returned.
private CompletableFuture<Location> attemptRandomLocation(final int attempts, final Location center, final double minRange, final double maxRange) {
final CompletableFuture<Location> future = new CompletableFuture<>();
if (attempts > 0) {
calculateRandomLocation(center, minRange, maxRange).thenAccept(location -> {
if (isValidRandomLocation(location)) {
future.complete(location);
} else {
attemptRandomLocation(attempts - 1, center, minRange, maxRange).thenAccept(future::complete);
}
});
} else {
future.complete(center);
}
return future;
}
// Calculates a random location asynchronously.
private CompletableFuture<Location> calculateRandomLocation(final Location center, final double minRange, final double maxRange) {
final CompletableFuture<Location> future = new CompletableFuture<>();
// Find an equally distributed offset by randomly rotating a point inside a rectangle about the origin
final double rectX = RANDOM.nextDouble() * (maxRange - minRange) + minRange;
final double rectZ = RANDOM.nextDouble() * (maxRange + minRange) - minRange;
final double offsetX;
final double offsetZ;
final int transform = RANDOM.nextInt(4);
if (transform == 0) {
offsetX = rectX;
offsetZ = rectZ;
} else if (transform == 1) {
offsetX = -rectZ;
offsetZ = rectX;
} else if (transform == 2) {
offsetX = -rectX;
offsetZ = -rectZ;
} else {
offsetX = rectZ;
offsetZ = -rectX;
}
final Location location = new Location(
center.getWorld(),
center.getX() + offsetX,
center.getWorld().getMaxHeight(),
center.getZ() + offsetZ,
360 * RANDOM.nextFloat() - 180,
0
);
PaperLib.getChunkAtAsync(location).thenAccept(chunk -> {
if (World.Environment.NETHER.equals(center.getWorld().getEnvironment())) {
location.setY(getNetherYAt(location));
} else {
location.setY(center.getWorld().getHighestBlockYAt(location) + HIGHEST_BLOCK_Y_OFFSET);
}
future.complete(location);
});
return future;
}
// Returns an appropriate elevation for a given location in the nether, or -1 if none is found
private double getNetherYAt(final Location location) {
for (int y = 32; y < location.getWorld().getMaxHeight() / 2; ++y) {
if (!LocationUtil.isBlockUnsafe(location.getWorld(), location.getBlockX(), y, location.getBlockZ())) {
return y;
}
}
return -1;
}
private boolean isValidRandomLocation(final Location location) {
return location.getBlockY() > 0 && !this.getExcludedBiomes().contains(location.getBlock().getBiome());
}
}
@@ -1,51 +0,0 @@
package com.earth2me.essentials;
import net.ess3.api.IEssentials;
import org.bukkit.entity.LivingEntity;
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);
}
}
@EventHandler(priority = EventPriority.LOW)
public void onEntityExplode(final EntityExplodeEvent event) {
if (!enabled) {
return;
}
if (event.getEntity() instanceof LivingEntity) {
return;
}
if (event.blockList().size() < 1) {
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(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,156 +0,0 @@
package com.earth2me.essentials;
import com.google.common.io.Files;
import org.bukkit.Bukkit;
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;
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) {
Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
}
}
public void writeUUIDMap() {
pendingWrite = true;
}
public void forceWriteUUIDMap() {
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "Forcing usermap write to disk");
}
pendingWrite = true;
writeTaskRunnable.run();
}
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) {
Bukkit.getLogger().log(Level.SEVERE, ex2.getMessage(), ex2);
}
Bukkit.getLogger().log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
File diff suppressed because it is too large. Load diff
@@ -1,313 +0,0 @@
package com.earth2me.essentials;
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 org.bukkit.entity.Player;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.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}$");
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 User getUser(final String name) {
try {
final String sanitizedName = StringUtil.safeString(name);
if (names.containsKey(sanitizedName)) {
final UUID uuid = names.get(sanitizedName);
return getUser(uuid);
}
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) {
return null;
}
}
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) {
return null;
}
}
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 (!names.get(keyName).equals(uuid)) {
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 {
if (ess.getSettings().isDebug()) {
ess.getLogger().info("Found old UUID for " + name + " (" + uuid.toString() + "). Not adding to usermap.");
}
}
}
}
}
}
@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!");
}
@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 Set<UUID> getAllUniqueUsers() {
return Collections.unmodifiableSet(keys);
}
public int getUniqueUsers() {
return keys.size();
}
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);
}
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,15 +0,0 @@
package com.earth2me.essentials.api;
/**
* Represents a storage object that is reloadable.
*
* @deprecated This is a remnant of the abandoned 3.x storage system. Neither future 2.x code nor external plugins
* should use this interface.
*/
@Deprecated
public interface IReload {
/**
* Reloads the given storage object.
*/
void onReload();
}
@@ -1,176 +0,0 @@
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.utils.NumberUtil;
import com.google.common.collect.Lists;
import org.bukkit.Server;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.earth2me.essentials.I18n.tl;
public class Commandbalancetop extends EssentialsCommand {
public static final int MINUSERS = 50;
private static final int CACHETIME = 2 * 60 * 1000;
private static final SimpleTextInput cache = new SimpleTextInput();
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private static long cacheage = 0;
public Commandbalancetop() {
super("balancetop");
}
private static void outputCache(final CommandSource sender, final int page) {
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(cacheage);
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);
}
@Override
protected void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
int page = 0;
boolean force = false;
if (args.length > 0) {
try {
page = Integer.parseInt(args[0]);
} catch (final NumberFormatException ex) {
if (args[0].equalsIgnoreCase("force") && (!sender.isPlayer() || ess.getUser(sender.getPlayer()).isAuthorized("essentials.balancetop.force"))) {
force = true;
}
}
}
if (!force && lock.readLock().tryLock()) {
try {
if (cacheage > System.currentTimeMillis() - CACHETIME) {
outputCache(sender, page);
return;
}
if (ess.getUserMap().getUniqueUsers() > MINUSERS) {
sender.sendMessage(tl("orderBalances", ess.getUserMap().getUniqueUsers()));
}
} finally {
lock.readLock().unlock();
}
} else {
if (ess.getUserMap().getUniqueUsers() > MINUSERS) {
sender.sendMessage(tl("orderBalances", ess.getUserMap().getUniqueUsers()));
}
}
ess.runTaskAsynchronously(new Viewer(sender, commandLabel, page, force));
}
@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("1");
if (!sender.isPlayer() || ess.getUser(sender.getPlayer()).isAuthorized("essentials.balancetop.force")) {
options.add("force");
}
return options;
} else {
return Collections.emptyList();
}
}
private class Calculator implements Runnable {
private final transient Viewer viewer;
private final boolean force;
Calculator(final Viewer viewer, final boolean force) {
this.viewer = viewer;
this.force = force;
}
@Override
public void run() {
lock.writeLock().lock();
try {
if (force || cacheage <= System.currentTimeMillis() - CACHETIME) {
cache.getLines().clear();
final Map<String, BigDecimal> balances = new HashMap<>();
BigDecimal totalMoney = BigDecimal.ZERO;
if (ess.getSettings().isEcoDisabled()) {
if (ess.getSettings().isDebug()) {
ess.getLogger().info("Internal economy functions disabled, aborting baltop.");
}
} else {
for (final UUID u : ess.getUserMap().getAllUniqueUsers()) {
final User user = ess.getUserMap().getUser(u);
if (user != null) {
if (!ess.getSettings().isNpcsInBalanceRanking() && user.isNPC()) {
// Don't list NPCs in output
continue;
}
if (!user.isAuthorized("essentials.balancetop.exclude")) {
final BigDecimal userMoney = user.getMoney();
user.updateMoneyCache(userMoney);
totalMoney = totalMoney.add(userMoney);
final String name = user.isHidden() ? user.getName() : user.getDisplayName();
balances.put(name, userMoney);
}
}
}
}
final List<Map.Entry<String, BigDecimal>> sortedEntries = new ArrayList<>(balances.entrySet());
sortedEntries.sort((entry1, entry2) -> entry2.getValue().compareTo(entry1.getValue()));
cache.getLines().add(tl("serverTotal", NumberUtil.displayCurrency(totalMoney, ess)));
int pos = 1;
for (final Map.Entry<String, BigDecimal> entry : sortedEntries) {
cache.getLines().add(tl("balanceTopLine", pos, entry.getKey(), NumberUtil.displayCurrency(entry.getValue(), ess)));
pos++;
}
cacheage = System.currentTimeMillis();
}
} finally {
lock.writeLock().unlock();
}
ess.runTaskAsynchronously(viewer);
}
}
private class Viewer implements Runnable {
private final transient CommandSource sender;
private final transient int page;
private final transient boolean force;
private final transient String commandLabel;
Viewer(final CommandSource sender, final String commandLabel, final int page, final boolean force) {
this.sender = sender;
this.page = page;
this.force = force;
this.commandLabel = commandLabel;
}
@Override
public void run() {
lock.readLock().lock();
try {
if (!force && cacheage > System.currentTimeMillis() - CACHETIME) {
outputCache(sender, page);
return;
}
} finally {
lock.readLock().unlock();
}
ess.runTaskAsynchronously(new Calculator(new Viewer(sender, commandLabel, page, false), force));
}
}
}
@@ -1,126 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.DateUtil;
import com.google.common.base.Charsets;
import com.google.common.io.CharStreams;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration;
import org.bukkit.configuration.file.YamlConstructor;
import org.bukkit.configuration.file.YamlRepresenter;
import org.bukkit.inventory.ItemStack;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.representer.Representer;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static com.earth2me.essentials.I18n.tl;
public class Commandcreatekit extends EssentialsCommand {
private static final String PASTE_URL = "https://hastebin.com/";
private static final String PASTE_UPLOAD_URL = PASTE_URL + "documents";
private static final Gson GSON = new Gson();
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private final DumperOptions yamlOptions = new DumperOptions();
private final Representer yamlRepresenter = new YamlRepresenter();
private final YamlConstructor yamlConstructor = new YamlConstructor();
public Commandcreatekit() {
super("createkit");
yamlOptions.setDefaultFlowStyle(FlowStyle.BLOCK);
yamlRepresenter.setDefaultFlowStyle(FlowStyle.BLOCK);
}
// /createkit <name> <delay>
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
if (args.length != 2) {
throw new NotEnoughArgumentsException();
}
// 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 List<String> list = new ArrayList<>();
for (final ItemStack is : items) {
if (is != null && is.getType() != null && is.getType() != Material.AIR) {
final String serialized = ess.getItemDb().serialize(is);
list.add(serialized);
}
}
// 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));
} else {
final ConfigurationSection config = new MemoryConfiguration();
config.set("kits." + kitname + ".delay", delay);
config.set("kits." + kitname + ".items", list);
final Yaml yaml = new Yaml(yamlConstructor, yamlRepresenter, yamlOptions);
String fileContents = "# Copy the kit code below into the kits section in your config.yml file\n";
fileContents += yaml.dump(config.getValues(false));
uploadPaste(user.getSource(), kitname, delay, fileContents);
}
}
private void uploadPaste(final CommandSource sender, final String kitName, final long delay, final String contents) {
executorService.submit(() -> {
try {
final HttpURLConnection connection = (HttpURLConnection) new URL(PASTE_UPLOAD_URL).openConnection();
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestProperty("User-Agent", "EssentialsX plugin");
try (final OutputStream os = connection.getOutputStream()) {
os.write(contents.getBytes(Charsets.UTF_8));
}
// Error
if (connection.getResponseCode() >= 400) {
sender.sendMessage(tl("createKitFailed", kitName));
final String message = CharStreams.toString(new InputStreamReader(connection.getErrorStream(), Charsets.UTF_8));
ess.getLogger().severe("Error creating kit: " + message);
return;
}
// Read URL
final JsonObject object = GSON.fromJson(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8), JsonObject.class);
final String pasteUrl = PASTE_URL + object.get("key").getAsString();
connection.disconnect();
final String separator = tl("createKitSeparator");
String delayFormat = "0";
if (delay > 0) {
delayFormat = DateUtil.formatDateDiff(System.currentTimeMillis() + (delay * 1000));
}
sender.sendMessage(separator);
sender.sendMessage(tl("createKitSuccess", kitName, delayFormat, pasteUrl));
sender.sendMessage(separator);
if (ess.getSettings().isDebug()) {
ess.getLogger().info(sender.getSender().getName() + " created a kit: " + pasteUrl);
}
} catch (final Exception e) {
sender.sendMessage(tl("createKitFailed", kitName));
e.printStackTrace();
}
});
}
}
@@ -1,35 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
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 Commanddelwarp extends EssentialsCommand {
public Commanddelwarp() {
super("delwarp");
}
@Override
public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length == 0) {
throw new NotEnoughArgumentsException();
}
ess.getWarps().removeWarp(args[0]);
sender.sendMessage(tl("deleteWarp", args[0]));
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1) {
return new ArrayList<>(ess.getWarps().getList());
} else {
return Collections.emptyList();
}
}
}
@@ -1,82 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.google.common.collect.Lists;
import org.bukkit.ChatColor;
import org.bukkit.Server;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandeditsign extends EssentialsCommand {
public Commandeditsign() {
super("editsign");
}
@Override
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
if (args.length == 0 || (args.length > 1 && !NumberUtil.isInt(args[1]))) {
throw new NotEnoughArgumentsException();
}
final Block target = user.getTargetBlock(5); //5 is a good number
if (!(target.getState() instanceof Sign)) {
throw new Exception(tl("editsignCommandTarget"));
}
final Sign sign = (Sign) target.getState();
try {
if (args[0].equalsIgnoreCase("set") && args.length > 2) {
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"));
}
sign.setLine(line, text);
sign.update();
user.sendMessage(tl("editsignCommandSetSuccess", line + 1, text));
} else if (args[0].equalsIgnoreCase("clear")) {
if (args.length == 1) {
for (int i = 0; i < 4; i++) { // A whole one line of line savings!
sign.setLine(i, "");
}
sign.update();
user.sendMessage(tl("editsignCommandClear"));
} else {
final int line = Integer.parseInt(args[1]) - 1;
sign.setLine(line, "");
sign.update();
user.sendMessage(tl("editsignCommandClearLine", line + 1));
}
} else {
throw new NotEnoughArgumentsException();
}
} catch (final IndexOutOfBoundsException e) {
throw new Exception(tl("editsignCommandNoLine"));
}
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) {
if (args.length == 1) {
return Lists.newArrayList("set", "clear");
} else if (args.length == 2) {
return Lists.newArrayList("1", "2", "3", "4");
} else if (args.length == 3 && args[0].equalsIgnoreCase("set") && NumberUtil.isPositiveInt(args[1])) {
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();
return Lists.newArrayList(FormatUtil.unformatString(user, "essentials.editsign", sign.getLine(line - 1)));
}
return Collections.emptyList();
} else {
return Collections.emptyList();
}
}
}
@@ -1,492 +0,0 @@
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.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.VersionUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.bukkit.Server;
import org.bukkit.Sound;
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.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
import static com.earth2me.essentials.I18n.tl;
// 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 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)
);
private static final List<String> officialPlugins = Arrays.asList(
"EssentialsAntiBuild",
"EssentialsChat",
"EssentialsGeoIP",
"EssentialsProtect",
"EssentialsSpawn",
"EssentialsXMPP"
);
private static final List<String> warnPlugins = Arrays.asList(
"PermissionsEx",
"GroupManager",
"bPremissions"
);
private transient TuneRunnable currentTune = null;
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;
// 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 "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("/<command> <reload/debug/commands>");
}
// 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) {
final StringBuilder disabledCommands = new StringBuilder();
for (final Map.Entry<String, String> entry : ess.getAlternativeCommandsHandler().disabledCommands().entrySet()) {
if (disabledCommands.length() > 0) {
disabledCommands.append("\n");
}
disabledCommands.append(entry.getKey()).append(" => ").append(entry.getValue());
}
if (disabledCommands.length() > 0) {
sender.sendMessage(tl("blockList"));
sender.sendMessage(disabledCommands.toString());
} else {
sender.sendMessage(tl("blockListEmpty"));
}
}
// 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) {
logger.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"));
});
}
// 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("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;
}
if (isMismatched) {
sender.sendMessage(tl("versionMismatchAll"));
}
if (!isVaultInstalled) {
sender.sendMessage(tl("versionOutputVaultMissing"));
}
if (isUnsupported) {
sender.sendMessage(tl("versionOutputUnsupportedPlugins"));
}
switch (supportStatus) {
case UNSTABLE:
sender.sendMessage(tl("serverUnsupportedMods"));
break;
case OUTDATED:
sender.sendMessage(tl("serverUnsupported"));
break;
case LIMITED:
sender.sendMessage(tl("serverUnsupportedLimitedApi"));
break;
}
}
@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("debug");
options.add("commands");
options.add("version");
options.add("reload");
options.add("reset");
options.add("cleanup");
//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 "uuidconvert":
if (args.length == 2) {
return Lists.newArrayList("ignoreUFCache");
}
break;
}
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));
}
}
}
}
@@ -1,48 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.Console;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.FormatUtil;
import org.bukkit.Server;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import static com.earth2me.essentials.I18n.tl;
public class Commandhelpop extends EssentialsCommand {
public Commandhelpop() {
super("helpop");
}
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
user.setDisplayNick();
final String message = sendMessage(server, user.getDisplayName(), args);
if (!user.isAuthorized("essentials.helpop.receive")) {
user.sendMessage(message);
}
}
@Override
public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
sendMessage(server, Console.NAME, args);
}
private String sendMessage(final Server server, final String from, final String[] args) throws Exception {
if (args.length < 1) {
throw new NotEnoughArgumentsException();
}
final String message = tl("helpOp", from, FormatUtil.stripFormat(getFinalArg(args, 0)));
server.getLogger().log(Level.INFO, message);
ess.broadcastMessage("essentials.helpop.receive", message);
return message;
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
return Collections.emptyList();
}
}
@@ -1,75 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.google.common.collect.Lists;
import org.bukkit.Server;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commanditemlore extends EssentialsCommand {
public Commanditemlore() {
super("itemlore");
}
@Override
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
final ItemStack item = user.getBase().getItemInHand();
if (item.getType().name().contains("AIR")) {
throw new Exception(tl("itemloreInvalidItem"));
}
if (args.length == 0) {
throw new NotEnoughArgumentsException();
}
final ItemMeta im = item.getItemMeta();
if (args[0].equalsIgnoreCase("add") && args.length > 1) {
final String line = FormatUtil.formatString(user, "essentials.itemlore", getFinalArg(args, 1)).trim();
final List<String> lore = im.hasLore() ? im.getLore() : new ArrayList<>();
lore.add(line);
im.setLore(lore);
item.setItemMeta(im);
user.sendMessage(tl("itemloreSuccess", line));
} else if (args[0].equalsIgnoreCase("clear")) {
im.setLore(new ArrayList<>());
item.setItemMeta(im);
user.sendMessage(tl("itemloreClear"));
} else if (args[0].equalsIgnoreCase("set") && args.length > 2 && NumberUtil.isInt(args[1])) {
if (!im.hasLore()) {
throw new Exception(tl("itemloreNoLore"));
}
final int line = Integer.parseInt(args[1]);
final String newLine = FormatUtil.formatString(user, "essentials.itemlore", getFinalArg(args, 2)).trim();
final List<String> lore = im.getLore();
try {
lore.set(line - 1, newLine);
} catch (final Exception e) {
throw new Exception(tl("itemloreNoLine", line), e);
}
im.setLore(lore);
item.setItemMeta(im);
user.sendMessage(tl("itemloreSuccessLore", line, newLine));
} else {
throw new NotEnoughArgumentsException();
}
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) {
if (args.length == 1) {
return Lists.newArrayList("add", "set", "clear");
} else {
return Collections.emptyList();
}
}
}
@@ -1,38 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.FormatUtil;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.Locale;
import static com.earth2me.essentials.I18n.tl;
public class Commanditemname extends EssentialsCommand {
public Commanditemname() {
super("itemname");
}
@Override
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
final ItemStack item = user.getBase().getItemInHand();
if (item.getType() == Material.AIR) {
user.sendMessage(tl("itemnameInvalidItem", item.getType().toString().toLowerCase(Locale.ENGLISH).replace('_', ' ')));
return;
}
String name = FormatUtil.formatString(user, "essentials.itemname", getFinalArg(args, 0)).trim();
if (name.isEmpty()) {
name = null;
}
final ItemMeta im = item.getItemMeta();
im.setDisplayName(name);
item.setItemMeta(im);
user.sendMessage(name == null ? tl("itemnameClear") : tl("itemnameSuccess", name));
}
}
@@ -1,132 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.PlayerList;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.NumberUtil;
import org.bukkit.Server;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.earth2me.essentials.I18n.tl;
public class Commandlist extends EssentialsCommand {
public Commandlist() {
super("list");
}
@Override
public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
boolean showHidden = true;
User user = null;
if (sender.isPlayer()) {
user = ess.getUser(sender.getPlayer());
showHidden = user.isAuthorized("essentials.list.hidden") || user.canInteractVanished();
}
sender.sendMessage(PlayerList.listSummary(ess, user, showHidden));
final Map<String, List<User>> playerList = PlayerList.getPlayerLists(ess, user, showHidden);
if (args.length > 0) {
sender.sendMessage(PlayerList.listGroupUsers(ess, playerList, args[0].toLowerCase()));
} else {
sendGroupedList(sender, commandLabel, playerList);
}
}
// Output the standard /list output, when no group is specified
private void sendGroupedList(final CommandSource sender, final String commandLabel, final Map<String, List<User>> playerList) {
final Set<String> configGroups = ess.getSettings().getListGroupConfig().keySet();
final List<String> asterisk = new ArrayList<>();
// Loop through the custom defined groups and display them
for (final String oConfigGroup : configGroups) {
final String groupValue = ess.getSettings().getListGroupConfig().get(oConfigGroup).toString().trim();
final String configGroup = oConfigGroup.toLowerCase();
// If the group value is an asterisk, then skip it, and handle it later
if (groupValue.equals("*")) {
asterisk.add(oConfigGroup);
continue;
}
// If the group value is hidden, we don't need to display it
if (groupValue.equalsIgnoreCase("hidden")) {
playerList.remove(configGroup);
continue;
}
final List<User> outputUserList;
final List<User> matchedList = playerList.get(configGroup);
// If the group value is an int, then we might need to truncate it
if (NumberUtil.isInt(groupValue)) {
if (matchedList != null && !matchedList.isEmpty()) {
playerList.remove(configGroup);
outputUserList = new ArrayList<>(matchedList);
final int limit = Integer.parseInt(groupValue);
if (matchedList.size() > limit) {
sender.sendMessage(PlayerList.outputFormat(oConfigGroup, tl("groupNumber", matchedList.size(), commandLabel, FormatUtil.stripFormat(configGroup))));
} else {
sender.sendMessage(PlayerList.outputFormat(oConfigGroup, PlayerList.listUsers(ess, outputUserList, ", ")));
}
continue;
}
}
outputUserList = PlayerList.getMergedList(ess, playerList, configGroup);
// If we have no users, than we don't need to continue parsing this group
if (outputUserList.isEmpty()) {
continue;
}
sender.sendMessage(PlayerList.outputFormat(oConfigGroup, PlayerList.listUsers(ess, outputUserList, ", ")));
}
final Set<String> var = playerList.keySet();
String[] onlineGroups = var.toArray(new String[0]);
Arrays.sort(onlineGroups, String.CASE_INSENSITIVE_ORDER);
// If we have an asterisk group, then merge all remaining groups
if (!asterisk.isEmpty()) {
final List<User> asteriskUsers = new ArrayList<>();
for (final String onlineGroup : onlineGroups) {
asteriskUsers.addAll(playerList.get(onlineGroup));
}
for (final String key : asterisk) {
playerList.put(key, asteriskUsers);
}
onlineGroups = asterisk.toArray(new String[0]);
}
// If we have any groups remaining after the custom groups loop through and display them
for (final String onlineGroup : onlineGroups) {
final List<User> users = playerList.get(onlineGroup);
String groupName = asterisk.isEmpty() ? users.get(0).getGroup() : onlineGroup;
if (ess.getPermissionsHandler().getName().equals("ConfigPermissions")) {
groupName = tl("connectedPlayers");
}
if (users == null || users.isEmpty()) {
continue;
}
sender.sendMessage(PlayerList.outputFormat(groupName, PlayerList.listUsers(ess, users, ", ")));
}
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1) {
return new ArrayList<>(PlayerList.getPlayerLists(ess, sender.getUser(ess), true).keySet());
} else {
return Collections.emptyList();
}
}
}
@@ -1,204 +0,0 @@
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.SimpleTextInput;
import com.earth2me.essentials.textreader.TextPager;
import com.earth2me.essentials.utils.DateUtil;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.StringUtil;
import com.google.common.collect.Lists;
import org.bukkit.Server;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static com.earth2me.essentials.I18n.tl;
public class Commandmail extends EssentialsCommand {
private static int mailsPerMinute = 0;
private static long timestamp = 0;
public Commandmail() {
super("mail");
}
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
if (args.length >= 1 && "read".equalsIgnoreCase(args[0])) {
final List<String> mail = user.getMails();
if (mail.isEmpty()) {
user.sendMessage(tl("noMail"));
throw new NoChargeException();
}
final IText input = new SimpleTextInput(mail);
final TextPager pager = new TextPager(input);
pager.showPage(args.length > 1 ? args[1] : null, null, commandLabel + " " + args[0], user.getSource());
user.sendMessage(tl("mailClear"));
return;
}
if (args.length >= 3 && "send".equalsIgnoreCase(args[0])) {
if (!user.isAuthorized("essentials.mail.send")) {
throw new Exception(tl("noPerm", "essentials.mail.send"));
}
if (user.isMuted()) {
final String dateDiff = user.getMuteTimeout() > 0 ? DateUtil.formatDateDiff(user.getMuteTimeout()) : null;
if (dateDiff == null) {
throw new Exception(user.hasMuteReason() ? tl("voiceSilencedReason", user.getMuteReason()) : tl("voiceSilenced"));
}
throw new Exception(user.hasMuteReason() ? tl("voiceSilencedReasonTime", dateDiff, user.getMuteReason()) : tl("voiceSilencedTime", dateDiff));
}
final User u;
try {
u = getPlayer(server, args[1], true, true);
} catch (final PlayerNotFoundException e) {
throw new Exception(tl("playerNeverOnServer", args[1]));
}
final String mail = tl("mailFormat", user.getName(), FormatUtil.formatMessage(user, "essentials.mail", StringUtil.sanitizeString(FormatUtil.stripFormat(getFinalArg(args, 2)))));
if (mail.length() > 1000) {
throw new Exception(tl("mailTooLong"));
}
if (!u.isIgnoredPlayer(user)) {
if (Math.abs(System.currentTimeMillis() - timestamp) > 60000) {
timestamp = System.currentTimeMillis();
mailsPerMinute = 0;
}
mailsPerMinute++;
if (mailsPerMinute > ess.getSettings().getMailsPerMinute()) {
throw new Exception(tl("mailDelay", ess.getSettings().getMailsPerMinute()));
}
u.addMail(tl("mailMessage", mail));
}
user.sendMessage(tl("mailSentTo", u.getDisplayName(), u.getName()));
user.sendMessage(mail);
return;
}
if (args.length > 1 && "sendall".equalsIgnoreCase(args[0])) {
if (!user.isAuthorized("essentials.mail.sendall")) {
throw new Exception(tl("noPerm", "essentials.mail.sendall"));
}
ess.runTaskAsynchronously(new SendAll(tl("mailFormat", user.getName(),
FormatUtil.formatMessage(user, "essentials.mail", StringUtil.sanitizeString(FormatUtil.stripFormat(getFinalArg(args, 1)))))));
user.sendMessage(tl("mailSent"));
return;
}
if (args.length >= 1 && "clear".equalsIgnoreCase(args[0])) {
if (user.getMails() == null || user.getMails().isEmpty()) {
user.sendMessage(tl("noMail"));
throw new NoChargeException();
}
user.setMails(null);
user.sendMessage(tl("mailCleared"));
return;
}
throw new NotEnoughArgumentsException();
}
@Override
protected void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length >= 1 && "read".equalsIgnoreCase(args[0])) {
throw new Exception(tl("onlyPlayers", commandLabel + " read"));
} else if (args.length >= 1 && "clear".equalsIgnoreCase(args[0])) {
throw new Exception(tl("onlyPlayers", commandLabel + " clear"));
} else if (args.length >= 3 && "send".equalsIgnoreCase(args[0])) {
final User u;
try {
u = getPlayer(server, args[1], true, true);
} catch (final PlayerNotFoundException e) {
throw new Exception(tl("playerNeverOnServer", args[1]));
}
u.addMail(tl("mailFormat", "Server", FormatUtil.replaceFormat(getFinalArg(args, 2))));
sender.sendMessage(tl("mailSent"));
return;
} else if (args.length >= 2 && "sendall".equalsIgnoreCase(args[0])) {
ess.runTaskAsynchronously(new SendAll(tl("mailFormat", "Server", FormatUtil.replaceFormat(getFinalArg(args, 1)))));
sender.sendMessage(tl("mailSent"));
return;
} else if (args.length >= 2) {
//allow sending from console without "send" argument, since it's the only thing the console can do
final User u;
try {
u = getPlayer(server, args[0], true, true);
} catch (final PlayerNotFoundException e) {
throw new Exception(tl("playerNeverOnServer", args[0]));
}
u.addMail(tl("mailFormat", "Server", FormatUtil.replaceFormat(getFinalArg(args, 1))));
sender.sendMessage(tl("mailSent"));
return;
}
throw new NotEnoughArgumentsException();
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) {
if (args.length == 1) {
final List<String> options = Lists.newArrayList("read", "clear");
if (user.isAuthorized("essentials.mail.send")) {
options.add("send");
}
if (user.isAuthorized("essentials.mail.sendall")) {
options.add("sendall");
}
return options;
} else if (args.length == 2 && args[0].equalsIgnoreCase("send") && user.isAuthorized("essentials.mail.send")) {
return getPlayers(server, user);
} else if (args.length == 2 && args[0].equalsIgnoreCase("read")) {
final List<String> mail = user.getMails();
final int pages = mail.size() / 9 + (mail.size() % 9 > 0 ? 1 : 0);
if (pages == 0) {
return Lists.newArrayList("0");
} else {
final List<String> options = Lists.newArrayList("1");
if (pages > 1) {
options.add(String.valueOf(pages));
}
return options;
}
} else if ((args.length > 2 && args[0].equalsIgnoreCase("send") && user.isAuthorized("essentials.mail.send")) || (args.length > 1 && args[0].equalsIgnoreCase("sendall") && user.isAuthorized("essentials.mail.sendall"))) {
return null; // Use vanilla handler
} else {
return Collections.emptyList();
}
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) {
if (args.length == 1) {
return Lists.newArrayList("send", "sendall");
} else if (args.length == 2 && args[0].equalsIgnoreCase("send")) {
return getPlayers(server, sender);
} else if ((args.length > 2 && args[0].equalsIgnoreCase("send")) || (args.length > 1 && args[0].equalsIgnoreCase("sendall"))) {
return null; // Use vanilla handler
} else {
return Collections.emptyList();
}
}
private class SendAll implements Runnable {
final String message;
SendAll(final String message) {
this.message = message;
}
@Override
public void run() {
for (final UUID userid : ess.getUserMap().getAllUniqueUsers()) {
final User user = ess.getUserMap().getUser(userid);
if (user != null) {
user.addMail(message);
}
}
}
}
}
@@ -1,42 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.RandomTeleport;
import com.earth2me.essentials.User;
import org.bukkit.Server;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class Commandsettpr extends EssentialsCommand {
public Commandsettpr() {
super("settpr");
}
@Override
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
final RandomTeleport randomTeleport = ess.getRandomTeleport();
randomTeleport.getCachedLocations().clear();
if (args.length == 0 || "center".equalsIgnoreCase(args[0])) {
randomTeleport.setCenter(user.getLocation());
user.sendMessage(tl("settpr"));
} else if (args.length > 1) {
if ("minrange".equalsIgnoreCase(args[0])) {
randomTeleport.setMinRange(Double.parseDouble(args[1]));
} else if ("maxrange".equalsIgnoreCase(args[0])) {
randomTeleport.setMaxRange(Double.parseDouble(args[1]));
}
user.sendMessage(tl("settprValue", args[0].toLowerCase(), args[1].toLowerCase()));
}
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) {
if (args.length == 1) {
return Arrays.asList("center", "minrange", "maxrange");
}
return Collections.emptyList();
}
}
@@ -1,43 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import com.earth2me.essentials.api.IWarps;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.StringUtil;
import net.ess3.api.InvalidWorldException;
import org.bukkit.Location;
import org.bukkit.Server;
import static com.earth2me.essentials.I18n.tl;
public class Commandsetwarp extends EssentialsCommand {
public Commandsetwarp() {
super("setwarp");
}
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
if (args.length == 0) {
throw new NotEnoughArgumentsException();
}
if (NumberUtil.isInt(args[0]) || args[0].isEmpty()) {
throw new Exception(tl("invalidWarpName"));
}
final IWarps warps = ess.getWarps();
Location warpLoc = null;
try {
warpLoc = warps.getWarp(args[0]);
} catch (final WarpNotFoundException | InvalidWorldException ignored) {
}
if (warpLoc == null || user.isAuthorized("essentials.warp.overwrite." + StringUtil.safeString(args[0]))) {
warps.setWarp(user, args[0], user.getLocation());
} else {
throw new Exception(tl("warpOverwrite"));
}
user.sendMessage(tl("warpSet", args[0]));
}
}
@@ -1,98 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.MaterialUtil;
import com.google.common.collect.Lists;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import static com.earth2me.essentials.I18n.tl;
public class Commandskull extends EssentialsCommand {
private static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$");
private static final Material SKULL_ITEM = EnumUtil.getMaterial("PLAYER_HEAD", "SKULL_ITEM");
public Commandskull() {
super("skull");
}
@Override
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
final String owner;
if (args.length > 0 && user.isAuthorized("essentials.skull.others")) {
if (!NAME_PATTERN.matcher(args[0]).matches()) {
throw new IllegalArgumentException(tl("alphaNames"));
}
owner = args[0];
} else {
owner = user.getName();
}
ItemStack itemSkull = user.getItemInHand();
final SkullMeta metaSkull;
boolean spawn = false;
if (itemSkull != null && MaterialUtil.isPlayerHead(itemSkull.getType(), itemSkull.getDurability())) {
metaSkull = (SkullMeta) itemSkull.getItemMeta();
} else if (user.isAuthorized("essentials.skull.spawn")) {
itemSkull = new ItemStack(SKULL_ITEM, 1, (byte) 3);
metaSkull = (SkullMeta) itemSkull.getItemMeta();
spawn = true;
} else {
throw new Exception(tl("invalidSkull"));
}
if (metaSkull.hasOwner() && !user.isAuthorized("essentials.skull.modify")) {
throw new Exception(tl("noPermissionSkull"));
}
editSkull(user, itemSkull, metaSkull, owner, spawn);
}
private void editSkull(final User user, final ItemStack stack, final SkullMeta skullMeta, final String owner, final boolean spawn) {
new BukkitRunnable() {
@Override
public void run() {
//Run this stuff async because SkullMeta#setOwner causes a http request.
skullMeta.setDisplayName("§fSkull of " + owner);
skullMeta.setOwner(owner);
new BukkitRunnable() {
@Override
public void run() {
stack.setItemMeta(skullMeta);
if (spawn) {
InventoryWorkaround.addItems(user.getBase().getInventory(), stack);
user.sendMessage(tl("givenSkull", owner));
return;
}
user.sendMessage(tl("skullChanged", owner));
}
}.runTask(ess);
}
}.runTaskAsynchronously(ess);
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) {
if (args.length == 1) {
if (user.isAuthorized("essentials.skull.others")) {
return getPlayers(server, user);
} else {
return Lists.newArrayList(user.getName());
}
} else {
return Collections.emptyList();
}
}
}
@@ -1,137 +0,0 @@
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.events.JailStatusChangeEvent;
import org.bukkit.Server;
import org.bukkit.event.player.PlayerTeleportEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static com.earth2me.essentials.I18n.tl;
public class Commandtogglejail extends EssentialsCommand {
public Commandtogglejail() {
super("togglejail");
}
@Override
public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length == 0) {
throw new NotEnoughArgumentsException();
}
final User player = getPlayer(server, args, 0, true, true);
if (args.length >= 2 && !player.isJailed()) {
if (!player.getBase().isOnline()) {
if (sender.isPlayer() && !ess.getUser(sender.getPlayer()).isAuthorized("essentials.togglejail.offline")) {
sender.sendMessage(tl("mayNotJailOffline"));
return;
}
}
if (player.isAuthorized("essentials.jail.exempt")) {
sender.sendMessage(tl("mayNotJail"));
return;
}
final JailStatusChangeEvent event = new JailStatusChangeEvent(player, sender.isPlayer() ? ess.getUser(sender.getPlayer()) : null, true);
ess.getServer().getPluginManager().callEvent(event);
if (!event.isCancelled()) {
long preTimeDiff = 0;
if (args.length > 2) {
final String time = getFinalArg(args, 2);
preTimeDiff = DateUtil.parseDateDiff(time, true);
}
final long timeDiff = preTimeDiff;
final CompletableFuture<Boolean> future = getNewExceptionFuture(sender, commandLabel);
future.thenAccept(success -> {
if (success) {
player.setJailed(true);
player.sendMessage(tl("userJailed"));
player.setJail(null);
player.setJail(args[1]);
if (args.length > 2) {
player.setJailTimeout(timeDiff);
}
sender.sendMessage(timeDiff > 0 ? tl("playerJailedFor", player.getName(), DateUtil.formatDateDiff(timeDiff)) : tl("playerJailed", player.getName()));
}
});
if (player.getBase().isOnline()) {
ess.getJails().sendToJail(player, args[1], future);
} else {
// Check if jail exists
ess.getJails().getJail(args[1]);
future.complete(true);
}
}
return;
}
if (args.length >= 2 && player.isJailed() && !args[1].equalsIgnoreCase(player.getJail())) {
sender.sendMessage(tl("jailAlreadyIncarcerated", player.getJail()));
return;
}
if (args.length >= 2 && player.isJailed() && args[1].equalsIgnoreCase(player.getJail())) {
final long timeDiff = DateUtil.parseDateDiff(getFinalArg(args, 2), true);
player.setJailTimeout(timeDiff);
sender.sendMessage(tl("jailSentenceExtended", DateUtil.formatDateDiff(timeDiff)));
return;
}
if (args.length == 1 || (args.length == 2 && args[1].equalsIgnoreCase(player.getJail()))) {
if (!player.isJailed()) {
throw new NotEnoughArgumentsException();
}
final JailStatusChangeEvent event = new JailStatusChangeEvent(player, sender.isPlayer() ? ess.getUser(sender.getPlayer()) : null, false);
ess.getServer().getPluginManager().callEvent(event);
if (!event.isCancelled()) {
player.setJailed(false);
player.setJailTimeout(0);
player.sendMessage(tl("jailReleasedPlayerNotify"));
player.setJail(null);
if (player.getBase().isOnline() && ess.getSettings().isTeleportBackWhenFreedFromJail()) {
final CompletableFuture<Boolean> future = getNewExceptionFuture(sender, commandLabel);
player.getAsyncTeleport().back(future);
future.thenAccept(success -> {
if (success) {
sender.sendMessage(tl("jailReleased", player.getName()));
}
});
future.exceptionally(e -> {
player.getAsyncTeleport().respawn(null, PlayerTeleportEvent.TeleportCause.PLUGIN, new CompletableFuture<>());
sender.sendMessage(tl("jailReleased", player.getName()));
return false;
});
return;
}
sender.sendMessage(tl("jailReleased", player.getName()));
}
}
}
@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);
} else if (args.length == 2) {
try {
return new ArrayList<>(ess.getJails().getList());
} catch (final Exception e) {
return Collections.emptyList();
}
} else {
return COMMON_DATE_DIFFS;
}
}
}
@@ -1,77 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.AsyncTeleport;
import com.earth2me.essentials.Trade;
import com.earth2me.essentials.User;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import java.util.concurrent.CompletableFuture;
import static com.earth2me.essentials.I18n.tl;
public class Commandtpaccept extends EssentialsCommand {
public Commandtpaccept() {
super("tpaccept");
}
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
final User requester;
try {
requester = ess.getUser(user.getTeleportRequest());
} catch (final Exception ex) {
throw new Exception(tl("noPendingRequest"));
}
if (!requester.getBase().isOnline()) {
throw new Exception(tl("noPendingRequest"));
}
if (user.isTpRequestHere() && ((!requester.isAuthorized("essentials.tpahere") && !requester.isAuthorized("essentials.tpaall")) || (user.getWorld() != requester.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !user.isAuthorized("essentials.worlds." + user.getWorld().getName())))) {
throw new Exception(tl("noPendingRequest"));
}
if (!user.isTpRequestHere() && (!requester.isAuthorized("essentials.tpa") || (user.getWorld() != requester.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !user.isAuthorized("essentials.worlds." + requester.getWorld().getName())))) {
throw new Exception(tl("noPendingRequest"));
}
if (args.length > 0 && !requester.getName().contains(args[0])) {
throw new Exception(tl("noPendingRequest"));
}
if (!user.hasOutstandingTeleportRequest()) {
user.requestTeleport(null, false);
throw new Exception(tl("requestTimedOut"));
}
final Trade charge = new Trade(this.getName(), ess);
user.sendMessage(tl("requestAccepted"));
requester.sendMessage(tl("requestAcceptedFrom", user.getDisplayName()));
final CompletableFuture<Boolean> future = getNewExceptionFuture(requester.getSource(), commandLabel);
future.exceptionally(e -> {
user.sendMessage(tl("pendingTeleportCancelled"));
return false;
});
if (user.isTpRequestHere()) {
final Location loc = user.getTpRequestLocation();
final AsyncTeleport teleport = requester.getAsyncTeleport();
teleport.setTpType(AsyncTeleport.TeleportType.TPA);
future.thenAccept(success -> {
if (success) {
requester.sendMessage(tl("teleporting", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
}
});
teleport.teleportPlayer(user, user.getTpRequestLocation(), charge, TeleportCause.COMMAND, future);
} else {
final AsyncTeleport teleport = requester.getAsyncTeleport();
teleport.setTpType(AsyncTeleport.TeleportType.TPA);
teleport.teleport(user.getBase(), charge, TeleportCause.COMMAND, future);
}
user.requestTeleport(null, false);
throw new NoChargeException();
}
}
@@ -1,27 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.User;
import org.bukkit.Server;
import static com.earth2me.essentials.I18n.tl;
public class Commandtpdeny extends EssentialsCommand {
public Commandtpdeny() {
super("tpdeny");
}
@Override
public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
if (user.getTeleportRequest() == null) {
throw new Exception(tl("noPendingRequest"));
}
final User player = ess.getUser(user.getTeleportRequest());
if (player == null) {
throw new Exception(tl("noPendingRequest"));
}
user.sendMessage(tl("requestDenied"));
player.sendMessage(tl("requestDeniedFrom", user.getDisplayName()));
user.requestTeleport(null, false);
}
}
@@ -1,48 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.RandomTeleport;
import com.earth2me.essentials.Trade;
import com.earth2me.essentials.User;
import net.ess3.api.events.UserRandomTeleportEvent;
import org.bukkit.Server;
import org.bukkit.event.player.PlayerTeleportEvent;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static com.earth2me.essentials.I18n.tl;
public class Commandtpr extends EssentialsCommand {
public Commandtpr() {
super("tpr");
}
@Override
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
final Trade charge = new Trade(this.getName(), ess);
charge.isAffordableFor(user);
final RandomTeleport randomTeleport = ess.getRandomTeleport();
final UserRandomTeleportEvent event = new UserRandomTeleportEvent(user, randomTeleport.getCenter(), randomTeleport.getMinRange(), randomTeleport.getMaxRange());
server.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
randomTeleport.getRandomLocation(event.getCenter(), event.getMinRange(), event.getMaxRange()).thenAccept(location -> {
final CompletableFuture<Boolean> future = getNewExceptionFuture(user.getSource(), commandLabel);
user.getAsyncTeleport().teleport(location, charge, PlayerTeleportEvent.TeleportCause.COMMAND, future);
future.thenAccept(success -> {
if (success) {
user.sendMessage(tl("tprSuccess"));
}
});
});
throw new NoChargeException();
}
@Override
protected List<String> getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) {
return Collections.emptyList();
}
}
@@ -1,84 +0,0 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.User;
import com.earth2me.essentials.craftbukkit.SetExpFix;
import com.earth2me.essentials.utils.DateUtil;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.NumberUtil;
import org.bukkit.Server;
import org.bukkit.Statistic;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import static com.earth2me.essentials.I18n.tl;
public class Commandwhois extends EssentialsCommand {
// For some reason, in 1.13 PLAY_ONE_MINUTE = ticks played = what used to be PLAY_ONE_TICK
// https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/commits/b848d8ce633871b52115247b089029749c02f579
private static final Statistic PLAY_ONE_TICK = EnumUtil.getStatistic("PLAY_ONE_MINUTE", "PLAY_ONE_TICK");
public Commandwhois() {
super("whois");
}
@Override
public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
if (args.length < 1) {
throw new NotEnoughArgumentsException();
}
final User user = getPlayer(server, sender, args, 0);
sender.sendMessage(tl("whoisTop", user.getName()));
user.setDisplayNick();
sender.sendMessage(tl("whoisNick", user.getDisplayName()));
sender.sendMessage(tl("whoisUuid", user.getBase().getUniqueId().toString()));
sender.sendMessage(tl("whoisHealth", user.getBase().getHealth()));
sender.sendMessage(tl("whoisHunger", user.getBase().getFoodLevel(), user.getBase().getSaturation()));
sender.sendMessage(tl("whoisExp", SetExpFix.getTotalExperience(user.getBase()), user.getBase().getLevel()));
sender.sendMessage(tl("whoisLocation", user.getLocation().getWorld().getName(), user.getLocation().getBlockX(), user.getLocation().getBlockY(), user.getLocation().getBlockZ()));
final long playtimeMs = System.currentTimeMillis() - (user.getBase().getStatistic(PLAY_ONE_TICK) * 50);
sender.sendMessage(tl("whoisPlaytime", DateUtil.formatDateDiff(playtimeMs)));
if (!ess.getSettings().isEcoDisabled()) {
sender.sendMessage(tl("whoisMoney", NumberUtil.displayCurrency(user.getMoney(), ess)));
}
if (!sender.isPlayer() || ess.getUser(sender.getPlayer()).isAuthorized("essentials.whois.ip")) {
sender.sendMessage(tl("whoisIPAddress", user.getBase().getAddress().getAddress().toString()));
}
final String location = user.getGeoLocation();
if (location != null && (!sender.isPlayer() || ess.getUser(sender.getPlayer()).isAuthorized("essentials.geoip.show"))) {
sender.sendMessage(tl("whoisGeoLocation", location));
}
sender.sendMessage(tl("whoisGamemode", tl(user.getBase().getGameMode().toString().toLowerCase(Locale.ENGLISH))));
sender.sendMessage(tl("whoisGod", user.isGodModeEnabled() ? tl("true") : tl("false")));
sender.sendMessage(tl("whoisOp", user.getBase().isOp() ? tl("true") : tl("false")));
sender.sendMessage(tl("whoisFly", user.getBase().getAllowFlight() ? tl("true") : tl("false"), user.getBase().isFlying() ? tl("flying") : tl("notFlying")));
sender.sendMessage(tl("whoisSpeed", user.getBase().isFlying() ? user.getBase().getFlySpeed() : user.getBase().getWalkSpeed()));
if (user.isAfk()) {
sender.sendMessage(tl("whoisAFKSince", tl("true"), DateUtil.formatDateDiff(user.getAfkSince())));
} else {
sender.sendMessage(tl("whoisAFK", tl("false")));
}
sender.sendMessage(tl("whoisJail", user.isJailed() ? user.getJailTimeout() > 0 ? DateUtil.formatDateDiff(user.getJailTimeout()) : tl("true") : tl("false")));
final long muteTimeout = user.getMuteTimeout();
if (!user.hasMuteReason()) {
sender.sendMessage(tl("whoisMuted", user.isMuted() ? muteTimeout > 0 ? DateUtil.formatDateDiff(muteTimeout) : tl("true") : tl("false")));
} else {
sender.sendMessage(tl("whoisMutedReason", user.isMuted() ? muteTimeout > 0 ? DateUtil.formatDateDiff(muteTimeout) : tl("true") : tl("false"),
user.getMuteReason()));
}
}
@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);
} else {
return Collections.emptyList();
}
}
}
@@ -1,7 +0,0 @@
package com.earth2me.essentials.commands;
public class PlayerExemptException extends NoSuchFieldException {
public PlayerExemptException(final String message) {
super(message);
}
}
@@ -1,9 +0,0 @@
package com.earth2me.essentials.commands;
import static com.earth2me.essentials.I18n.tl;
public class PlayerNotFoundException extends NoSuchFieldException {
public PlayerNotFoundException() {
super(tl("playerNotFound"));
}
}
@@ -1,13 +0,0 @@
package com.earth2me.essentials.commands;
import static com.earth2me.essentials.I18n.tl;
public class WarpNotFoundException extends Exception {
public WarpNotFoundException() {
super(tl("warpNotExist"));
}
public WarpNotFoundException(final String message) {
super(message);
}
}
File diff suppressed because it is too large. Load diff
@@ -1,288 +0,0 @@
package com.earth2me.essentials.craftbukkit;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/*
* This class can be removed when https://github.com/Bukkit/CraftBukkit/pull/193 is accepted to CraftBukkit
*/
public final class InventoryWorkaround {
/*
Spigot 1.9, for whatever reason, decided to merge the armor and main player inventories without providing a way
to access the main inventory. There's lots of ugly code in here to work around that.
*/
private static final int USABLE_PLAYER_INV_SIZE = 36;
// Hot-ish code so cache
private static Boolean hasMainHandSupport = null;
private InventoryWorkaround() {
}
private static int firstPartial(final Inventory inventory, final ItemStack item, final int maxAmount) {
if (item == null) {
return -1;
}
final ItemStack[] stacks = inventory.getContents();
for (int i = 0; i < stacks.length; i++) {
final ItemStack cItem = stacks[i];
if (cItem != null && cItem.getAmount() < maxAmount && cItem.isSimilar(item)) {
return i;
}
}
return -1;
}
private static boolean isCombinedInventory(final Inventory inventory) {
return inventory instanceof PlayerInventory && inventory.getContents().length > USABLE_PLAYER_INV_SIZE;
}
// Clears inventory without clearing armor
public static void clearInventoryNoArmor(final PlayerInventory inventory) {
if (isCombinedInventory(inventory)) {
for (int i = 0; i < USABLE_PLAYER_INV_SIZE; i++) {
inventory.setItem(i, null);
}
} else {
inventory.clear();
}
}
private static Inventory makeTruncatedPlayerInventory(final PlayerInventory playerInventory) {
final Inventory fakeInventory = Bukkit.getServer().createInventory(null, USABLE_PLAYER_INV_SIZE);
fakeInventory.setContents(Arrays.copyOf(playerInventory.getContents(), fakeInventory.getSize()));
return fakeInventory;
}
// Returns what it couldn't store
// This will will abort if it couldn't store all items
public static Map<Integer, ItemStack> addAllItems(final Inventory inventory, final ItemStack... items) {
final ItemStack[] contents = inventory.getContents();
final Inventory fakeInventory;
if (isCombinedInventory(inventory)) {
fakeInventory = makeTruncatedPlayerInventory((PlayerInventory) inventory);
} else {
fakeInventory = Bukkit.getServer().createInventory(null, inventory.getType());
fakeInventory.setContents(contents);
}
final Map<Integer, ItemStack> overflow = addItems(fakeInventory, items);
if (overflow.isEmpty()) {
addItems(inventory, items);
return null;
}
return addItems(fakeInventory, items);
}
public static Map<Integer, ItemStack> addAllOversizedItems(final Inventory inventory, final int oversizedStacks, final ItemStack... items) {
final ItemStack[] contents = inventory.getContents();
final Inventory fakeInventory;
if (isCombinedInventory(inventory)) {
fakeInventory = makeTruncatedPlayerInventory((PlayerInventory) inventory);
} else {
fakeInventory = Bukkit.getServer().createInventory(null, inventory.getType());
fakeInventory.setContents(contents);
}
final Map<Integer, ItemStack> overflow = addOversizedItems(fakeInventory, oversizedStacks, items);
if (overflow.isEmpty()) {
addOversizedItems(inventory, oversizedStacks, items);
return null;
}
return overflow;
}
// Returns what it couldn't store
public static Map<Integer, ItemStack> addItems(final Inventory inventory, final ItemStack... items) {
return addOversizedItems(inventory, 0, items);
}
// Returns what it couldn't store
// Set oversizedStack to below normal stack size to disable oversized stacks
public static Map<Integer, ItemStack> addOversizedItems(final Inventory inventory, final int oversizedStacks, final ItemStack... items) {
if (isCombinedInventory(inventory)) {
final Inventory fakeInventory = makeTruncatedPlayerInventory((PlayerInventory) inventory);
final Map<Integer, ItemStack> overflow = addOversizedItems(fakeInventory, oversizedStacks, items);
for (int i = 0; i < fakeInventory.getContents().length; i++) {
inventory.setItem(i, fakeInventory.getContents()[i]);
}
return overflow;
}
final Map<Integer, ItemStack> leftover = new HashMap<>();
/*
* TODO: some optimization - Create a 'firstPartial' with a 'fromIndex' - Record the lastPartial per Material -
* Cache firstEmpty result
*/
// combine items
final ItemStack[] combined = new ItemStack[items.length];
for (final ItemStack item : items) {
if (item == null || item.getAmount() < 1) {
continue;
}
for (int j = 0; j < combined.length; j++) {
if (combined[j] == null) {
combined[j] = item.clone();
break;
}
if (combined[j].isSimilar(item)) {
combined[j].setAmount(combined[j].getAmount() + item.getAmount());
break;
}
}
}
for (int i = 0; i < combined.length; i++) {
final ItemStack item = combined[i];
if (item == null || item.getType() == Material.AIR) {
continue;
}
while (true) {
// Do we already have a stack of it?
final int maxAmount = Math.max(oversizedStacks, item.getType().getMaxStackSize());
final int firstPartial = firstPartial(inventory, item, maxAmount);
// Drat! no partial stack
if (firstPartial == -1) {
// Find a free spot!
final int firstFree = inventory.firstEmpty();
if (firstFree == -1) {
// No space at all!
leftover.put(i, item);
break;
} else {
// More than a single stack!
if (item.getAmount() > maxAmount) {
final ItemStack stack = item.clone();
stack.setAmount(maxAmount);
inventory.setItem(firstFree, stack);
item.setAmount(item.getAmount() - maxAmount);
} else {
// Just store it
inventory.setItem(firstFree, item);
break;
}
}
} else {
// So, apparently it might only partially fit, well lets do just that
final ItemStack partialItem = inventory.getItem(firstPartial);
final int amount = item.getAmount();
final int partialAmount = partialItem.getAmount();
// Check if it fully fits
if (amount + partialAmount <= maxAmount) {
partialItem.setAmount(amount + partialAmount);
break;
}
// It fits partially
partialItem.setAmount(maxAmount);
item.setAmount(amount + partialAmount - maxAmount);
}
}
}
return leftover;
}
@SuppressWarnings("deprecation")
public static void setItemInMainHand(final Player p, final ItemStack item) {
if (hasMainHandSupport == null) {
try {
p.getInventory().setItemInMainHand(item);
hasMainHandSupport = true;
} catch (final Throwable e) {
p.setItemInHand(item);
hasMainHandSupport = false;
}
} else {
if (hasMainHandSupport) {
p.getInventory().setItemInMainHand(item);
} else {
p.setItemInHand(item);
}
}
}
@SuppressWarnings("deprecation")
public static void setItemInMainHand(final EntityEquipment invent, final ItemStack item) {
if (hasMainHandSupport == null) {
try {
invent.setItemInMainHand(item);
hasMainHandSupport = true;
} catch (final Throwable e) {
invent.setItemInHand(item);
hasMainHandSupport = false;
}
} else {
if (hasMainHandSupport) {
invent.setItemInMainHand(item);
} else {
invent.setItemInHand(item);
}
}
}
@SuppressWarnings("deprecation")
public static void setItemInMainHandDropChance(final EntityEquipment invent, final float chance) {
if (hasMainHandSupport == null) {
try {
invent.setItemInMainHandDropChance(chance);
hasMainHandSupport = true;
} catch (final Throwable e) {
invent.setItemInHandDropChance(chance);
hasMainHandSupport = false;
}
} else {
if (hasMainHandSupport) {
invent.setItemInMainHandDropChance(chance);
} else {
invent.setItemInHandDropChance(chance);
}
}
}
public static void setItemInOffHand(final Player p, final ItemStack item) {
// This assumes that all builds that support a main hand also support an off hand.
if (hasMainHandSupport == null || hasMainHandSupport) {
try {
p.getInventory().setItemInOffHand(item);
hasMainHandSupport = true;
} catch (final Throwable e) {
hasMainHandSupport = false;
}
}
}
public static int clearItemInOffHand(final Player p, final ItemStack item) {
// This should be added because if `/clear` itself is not initilized it will return an Error: null.
if (hasMainHandSupport == null || hasMainHandSupport) {
try {
int removedAmount = 0;
if (p.getInventory().getItemInOffHand().getType().equals(item.getType())) {
removedAmount = p.getInventory().getItemInOffHand().getAmount();
p.getInventory().setItemInOffHand(null);
}
hasMainHandSupport = true;
return removedAmount;
} catch (final Throwable e) {
hasMainHandSupport = false;
}
}
return 0;
}
}
@@ -1,729 +0,0 @@
package com.earth2me.essentials.metrics;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicePriority;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
/**
* bStats collects some data for plugin authors.
* <p>
* Check out https://bStats.org/ to learn more about bStats!
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class Metrics {
// The version of this bStats class
public static final int B_STATS_VERSION = 1;
// The url to which the data is sent
private static final String URL = "https://bStats.org/submitData/bukkit";
// Should failed requests be logged?
private static boolean logFailedRequests;
// Should the sent data be logged?
private static boolean logSentData;
// Should the response text be logged?
private static boolean logResponseStatusText;
// The uuid of the server
private static String serverUUID;
static {
// You can use the property to disable the check in your test environment
if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) {
// Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D
final String defaultPackage = new String(
new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'});
final String examplePackage = new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
// We want to make sure nobody just copy & pastes the example and use the wrong package names
if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) {
throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
}
}
}
// Is bStats enabled on this server?
private final boolean enabled;
// The plugin
private final Plugin plugin;
// The plugin id
private final int pluginId;
// A list with all custom charts
private final List<CustomChart> charts = new ArrayList<>();
/**
* Class constructor.
*
* @param plugin The plugin which stats should be submitted.
* @param pluginId The id of the plugin.
* It can be found at <a href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
*/
public Metrics(final Plugin plugin, final int pluginId) {
if (plugin == null) {
throw new IllegalArgumentException("Plugin cannot be null!");
}
this.plugin = plugin;
this.pluginId = pluginId;
// Get the config file
final File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
final File configFile = new File(bStatsFolder, "config.yml");
final YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
// Check if the config file exists
if (!config.isSet("serverUuid")) {
// Add default values
config.addDefault("enabled", true);
// Every server gets it's unique random id.
config.addDefault("serverUuid", UUID.randomUUID().toString());
// Should failed request be logged?
config.addDefault("logFailedRequests", false);
// Should the sent data be logged?
config.addDefault("logSentData", false);
// Should the response text be logged?
config.addDefault("logResponseStatusText", false);
// Inform the server owners about bStats
config.options().header(
"bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
"To honor their work, you should not disable it.\n" +
"This has nearly no effect on the server performance!\n" +
"Check out https://bStats.org/ to learn more :)"
).copyDefaults(true);
try {
config.save(configFile);
} catch (final IOException ignored) {
}
}
// Load the data
enabled = config.getBoolean("enabled", true);
serverUUID = config.getString("serverUuid");
logFailedRequests = config.getBoolean("logFailedRequests", false);
logSentData = config.getBoolean("logSentData", false);
logResponseStatusText = config.getBoolean("logResponseStatusText", false);
if (enabled) {
boolean found = false;
// Search for all other bStats Metrics classes to see if we are the first one
for (final Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
try {
service.getField("B_STATS_VERSION"); // Our identifier :)
found = true; // We aren't the first
break;
} catch (final NoSuchFieldException ignored) {
}
}
// Register our service
Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal);
if (!found) {
// We are the first!
startSubmitting();
}
}
}
/**
* Sends the data to the bStats server.
*
* @param plugin Any plugin. It's just used to get a logger instance.
* @param data The data to send.
* @throws Exception If the request failed.
*/
private static void sendData(final Plugin plugin, final JsonObject data) throws Exception {
if (data == null) {
throw new IllegalArgumentException("Data cannot be null!");
}
if (Bukkit.isPrimaryThread()) {
throw new IllegalAccessException("This method must not be called from the main thread!");
}
if (logSentData) {
plugin.getLogger().info("Sending data to bStats: " + data);
}
final HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
// Compress the data to save bandwidth
final byte[] compressedData = compress(data.toString());
// Add headers
connection.setRequestMethod("POST");
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format
connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);
// Send data
connection.setDoOutput(true);
try (final DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
outputStream.write(compressedData);
}
final StringBuilder builder = new StringBuilder();
try (final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
}
if (logResponseStatusText) {
plugin.getLogger().info("Sent data to bStats and received response: " + builder);
}
}
/**
* Gzips the given String.
*
* @param str The string to gzip.
* @return The gzipped String.
* @throws IOException If the compression failed.
*/
private static byte[] compress(final String str) throws IOException {
if (str == null) {
return null;
}
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (final GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
gzip.write(str.getBytes(StandardCharsets.UTF_8));
}
return outputStream.toByteArray();
}
/**
* Checks if bStats is enabled.
*
* @return Whether bStats is enabled or not.
*/
public boolean isEnabled() {
return enabled;
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(final CustomChart chart) {
if (chart == null) {
throw new IllegalArgumentException("Chart cannot be null!");
}
charts.add(chart);
}
/**
* Starts the Scheduler which submits our data every 30 minutes.
*/
private void startSubmitting() {
final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (!plugin.isEnabled()) { // Plugin was disabled
timer.cancel();
return;
}
// Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler
// Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;)
Bukkit.getScheduler().runTask(plugin, () -> submitData());
}
}, 1000 * 60 * 5, 1000 * 60 * 30);
// Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start
// WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
// WARNING: Just don't do it!
}
/**
* Gets the plugin specific data.
* This method is called using Reflection.
*
* @return The plugin specific data.
*/
public JsonObject getPluginData() {
final JsonObject data = new JsonObject();
final String pluginName = plugin.getDescription().getName().replace("Essentials", "EssentialsX");
final String pluginVersion = plugin.getDescription().getVersion();
data.addProperty("pluginName", pluginName); // Append the name of the plugin
data.addProperty("id", pluginId); // Append the id of the plugin
data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin
final JsonArray customCharts = new JsonArray();
for (final CustomChart customChart : charts) {
// Add the data of the custom charts
final JsonObject chart = customChart.getRequestJsonObject();
if (chart == null) { // If the chart is null, we skip it
continue;
}
customCharts.add(chart);
}
data.add("customCharts", customCharts);
return data;
}
/**
* Gets the server specific data.
*
* @return The server specific data.
*/
private JsonObject getServerData() {
// Minecraft specific data
int playerAmount;
try {
// Around MC 1.8 the return type was changed to a collection from an array,
// This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
final Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers");
playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class)
? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
: ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
} catch (final Exception e) {
playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed
}
final int onlineMode = Bukkit.getOnlineMode() ? 1 : 0;
final String bukkitVersion = Bukkit.getVersion();
final String bukkitName = Bukkit.getName();
// OS/Java specific data
final String javaVersion = System.getProperty("java.version");
final String osName = System.getProperty("os.name");
final String osArch = System.getProperty("os.arch");
final String osVersion = System.getProperty("os.version");
final int coreCount = Runtime.getRuntime().availableProcessors();
final JsonObject data = new JsonObject();
data.addProperty("serverUUID", serverUUID);
data.addProperty("playerAmount", playerAmount);
data.addProperty("onlineMode", onlineMode);
data.addProperty("bukkitVersion", bukkitVersion);
data.addProperty("bukkitName", bukkitName);
data.addProperty("javaVersion", javaVersion);
data.addProperty("osName", osName);
data.addProperty("osArch", osArch);
data.addProperty("osVersion", osVersion);
data.addProperty("coreCount", coreCount);
return data;
}
/**
* Collects the data and sends it afterwards.
*/
private void submitData() {
final JsonObject data = getServerData();
final JsonArray pluginData = new JsonArray();
// Search for all other bStats Metrics classes to get their plugin data
for (final Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
try {
service.getField("B_STATS_VERSION"); // Our identifier :)
for (final RegisteredServiceProvider<?> provider : Bukkit.getServicesManager().getRegistrations(service)) {
try {
final Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider());
if (plugin instanceof JsonObject) {
pluginData.add((JsonObject) plugin);
} else { // old bstats version compatibility
try {
final Class<?> jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject");
if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) {
final Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString");
jsonStringGetter.setAccessible(true);
final String jsonString = (String) jsonStringGetter.invoke(plugin);
final JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject();
pluginData.add(object);
}
} catch (final ClassNotFoundException e) {
// minecraft version 1.14+
if (logFailedRequests) {
this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e);
}
}
}
} catch (final NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
}
}
} catch (final NoSuchFieldException ignored) {
}
}
data.add("plugins", pluginData);
// Create a new thread for the connection to the bStats server
new Thread(() -> {
try {
// Send the data
sendData(plugin, data);
} catch (final Exception e) {
// Something went wrong! :(
if (logFailedRequests) {
plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e);
}
}
}).start();
}
/**
* Represents a custom chart.
*/
public static abstract class CustomChart {
// The id of the chart
final String chartId;
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
CustomChart(final String chartId) {
if (chartId == null || chartId.isEmpty()) {
throw new IllegalArgumentException("ChartId cannot be null or empty!");
}
this.chartId = chartId;
}
private JsonObject getRequestJsonObject() {
final JsonObject chart = new JsonObject();
chart.addProperty("chartId", chartId);
try {
final JsonObject data = getChartData();
if (data == null) {
// If the data is null we don't send the chart.
return null;
}
chart.add("data", data);
} catch (final Throwable t) {
if (logFailedRequests) {
Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t);
}
return null;
}
return chart;
}
protected abstract JsonObject getChartData() throws Exception;
}
/**
* Represents a custom simple pie.
*/
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimplePie(final String chartId, final Callable<String> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
final JsonObject data = new JsonObject();
final String value = callable.call();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
data.addProperty("value", value);
return data;
}
}
/**
* Represents a custom advanced pie.
*/
public static class AdvancedPie extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedPie(final String chartId, final Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
final JsonObject data = new JsonObject();
final JsonObject values = new JsonObject();
final Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (final Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.addProperty(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom drilldown pie.
*/
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(final String chartId, final Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObject getChartData() throws Exception {
final JsonObject data = new JsonObject();
final JsonObject values = new JsonObject();
final Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (final Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
final JsonObject value = new JsonObject();
boolean allSkipped = true;
for (final Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
value.addProperty(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
values.add(entryValues.getKey(), value);
}
}
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom single line chart.
*/
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(final String chartId, final Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
final JsonObject data = new JsonObject();
final int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
data.addProperty("value", value);
return data;
}
}
/**
* Represents a custom multi line chart.
*/
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(final String chartId, final Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
final JsonObject data = new JsonObject();
final JsonObject values = new JsonObject();
final Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (final Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.addProperty(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom simple bar chart.
*/
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(final String chartId, final Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
final JsonObject data = new JsonObject();
final JsonObject values = new JsonObject();
final Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (final Map.Entry<String, Integer> entry : map.entrySet()) {
final JsonArray categoryValues = new JsonArray();
categoryValues.add(new JsonPrimitive(entry.getValue()));
values.add(entry.getKey(), categoryValues);
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom advanced bar chart.
*/
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(final String chartId, final Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
final JsonObject data = new JsonObject();
final JsonObject values = new JsonObject();
final Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (final Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
continue; // Skip this invalid
}
allSkipped = false;
final JsonArray categoryValues = new JsonArray();
for (final int categoryValue : entry.getValue()) {
categoryValues.add(new JsonPrimitive(categoryValue));
}
values.add(entry.getKey(), categoryValues);
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
}
@@ -1,163 +0,0 @@
package com.earth2me.essentials.metrics;
import com.earth2me.essentials.Essentials;
import com.earth2me.essentials.register.payment.Methods;
import com.google.common.collect.ImmutableList;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MetricsWrapper {
private static final List<String> KNOWN_FORCED_METRICS = ImmutableList.of("ChatControl");
private static boolean hasWarned = false;
private final Essentials ess;
private final Metrics metrics;
private final Map<String, Boolean> commands = new HashMap<>();
private final Plugin plugin;
public MetricsWrapper(final Plugin plugin, final int pluginId, final boolean includeCommands) {
this.plugin = plugin;
this.ess = (Essentials) Bukkit.getPluginManager().getPlugin("Essentials");
this.metrics = new Metrics(plugin, pluginId);
if (metrics.isEnabled()) {
plugin.getLogger().info("Starting Metrics. Opt-out using the global bStats config.");
} else {
plugin.getLogger().info("Metrics disabled per bStats config.");
}
checkForcedMetrics();
addPermsChart();
addEconomyChart();
// bStats' backend currently doesn't support multi-line charts or advanced bar charts
// These are included for when bStats is ready to accept this data
addVersionHistoryChart();
if (includeCommands) addCommandsChart();
}
public void markCommand(final String command, final boolean state) {
commands.put(command, state);
}
public void addCustomChart(final Metrics.CustomChart chart) {
metrics.addCustomChart(chart);
}
private void addPermsChart() {
metrics.addCustomChart(new Metrics.DrilldownPie("permsPlugin", () -> {
final Map<String, Map<String, Integer>> result = new HashMap<>();
final String handler = ess.getPermissionsHandler().getName();
final Map<String, Integer> backend = new HashMap<>();
backend.put(ess.getPermissionsHandler().getBackendName(), 1);
result.put(handler, backend);
return result;
}));
}
private void addEconomyChart() {
metrics.addCustomChart(new Metrics.DrilldownPie("econPlugin", () -> {
final Map<String, Map<String, Integer>> result = new HashMap<>();
final Map<String, Integer> backend = new HashMap<>();
if (Methods.hasMethod()) {
backend.put(Methods.getMethod().getBackend(), 1);
result.put(Methods.getMethod().getName(), backend);
} else {
backend.put("Essentials", 1);
result.put("Essentials", backend);
}
return result;
}));
}
private void addVersionHistoryChart() {
metrics.addCustomChart(new Metrics.MultiLineChart("versionHistory", () -> {
final HashMap<String, Integer> result = new HashMap<>();
result.put(plugin.getDescription().getVersion(), 1);
return result;
}));
}
private void addCommandsChart() {
for (final String command : plugin.getDescription().getCommands().keySet()) {
markCommand(command, false);
}
metrics.addCustomChart(new Metrics.AdvancedBarChart("commands", () -> {
final Map<String, int[]> result = new HashMap<>();
for (final Map.Entry<String, Boolean> entry : commands.entrySet()) {
if (entry.getValue()) {
result.put(entry.getKey(), new int[] {1, 0});
} else {
result.put(entry.getKey(), new int[] {0, 1});
}
}
return result;
}));
}
private void checkForcedMetrics() {
if (hasWarned) return;
hasWarned = true;
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
for (final Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
try {
service.getField("B_STATS_VERSION"); // Identifies bStats classes
final JavaPlugin owningPlugin = getProvidingPlugin(service);
if (owningPlugin != null && KNOWN_FORCED_METRICS.contains(owningPlugin.getName())) {
warnForcedMetrics(service);
} else {
try {
service.getDeclaredField("pluginId"); // Only present in recent bStats classes, which should also have the enabled field unless modified
} catch (final NoSuchFieldException e) {
// Old bStats class found so "enabled" field detection is unreliable.
break;
}
try {
service.getDeclaredField("enabled"); // In modified forced metrics classes, this will fail
} catch (final NoSuchFieldException e) {
warnForcedMetrics(service);
}
}
} catch (final NoSuchFieldException ignored) {
}
}
});
}
private void warnForcedMetrics(final Class<?> service) {
final Plugin servicePlugin = JavaPlugin.getProvidingPlugin(service);
plugin.getLogger().severe("WARNING: Potential forced metrics collection by plugin '" + servicePlugin.getName() + "' v" + servicePlugin.getDescription().getVersion());
plugin.getLogger().severe("Your server is running a plugin that may not respect bStats' opt-out settings.");
plugin.getLogger().severe("This may cause data to be uploaded to bStats.org for plugins that use bStats, even if you've opted out in the bStats config.");
plugin.getLogger().severe("Please report this to bStats and to the authors of '" + servicePlugin.getName() + "'. (Offending class: " + service.getName() + ")");
}
private JavaPlugin getProvidingPlugin(final Class<?> clazz) {
try {
return JavaPlugin.getProvidingPlugin(clazz);
} catch (final Exception ignored) {
}
final ClassLoader parent = clazz.getClassLoader().getParent();
if (parent.getClass().getName().equals("org.bukkit.plugin.java.PluginClassLoader")) {
try {
final Field pluginField = parent.getClass().getDeclaredField("plugin");
pluginField.setAccessible(true);
return (JavaPlugin) pluginField.get(parent);
} catch (final Exception ignored) {
}
}
return null;
}
}
@@ -1,34 +0,0 @@
package com.earth2me.essentials.perm;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
public interface IPermissionsHandler {
String getGroup(Player base);
List<String> getGroups(Player base);
boolean canBuild(Player base, String group);
boolean inGroup(Player base, String group);
boolean hasPermission(Player base, String node);
// Does not check for * permissions
boolean isPermissionSet(Player base, String node);
String getPrefix(Player base);
String getSuffix(Player base);
void registerContext(String context, Function<Player, Iterable<String>> calculator, Supplier<Iterable<String>> suggestions);
void unregisterContexts();
String getBackendName();
boolean tryProvider();
}
@@ -1,55 +0,0 @@
package com.earth2me.essentials.perm.impl;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextConsumer;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.ImmutableContextSet;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
public class LuckPermsHandler extends ModernVaultHandler {
private LuckPerms luckPerms;
private Set<ContextCalculator<Player>> contextCalculators;
@Override
public void registerContext(final String context, final Function<Player, Iterable<String>> calculator, final Supplier<Iterable<String>> suggestions) {
final ContextCalculator<Player> contextCalculator = new ContextCalculator<Player>() {
@Override
public void calculate(final Player target, final ContextConsumer consumer) {
calculator.apply(target).forEach(value -> consumer.accept(context, value));
}
@Override
public ContextSet estimatePotentialContexts() {
final ImmutableContextSet.Builder builder = ImmutableContextSet.builder();
suggestions.get().forEach(value -> builder.add(context, value));
return builder.build();
}
};
luckPerms.getContextManager().registerCalculator(contextCalculator);
contextCalculators.add(contextCalculator);
}
@Override
public void unregisterContexts() {
contextCalculators.forEach(contextCalculator -> luckPerms.getContextManager().unregisterCalculator(contextCalculator));
contextCalculators.clear();
}
@Override
public boolean tryProvider() {
final RegisteredServiceProvider<LuckPerms> provider = Bukkit.getServicesManager().getRegistration(LuckPerms.class);
if (provider != null) {
luckPerms = provider.getProvider();
contextCalculators = new HashSet<>();
}
return luckPerms != null && super.tryProvider();
}
}
@@ -1,218 +0,0 @@
package com.earth2me.essentials.register.payment;
import org.bukkit.plugin.Plugin;
/**
* Interface to be implemented by a payment method.
* Copyright (C) 2011
* AOL license <http://aol.nexua.org>
* <p>
* For more information about the licensing of this code in EssentialsX, see below:
* https://gist.github.com/md678685/0935441c9573c030c8bd5a2e604aeec3
*
* @author Nijikokun <nijikokun@shortmail.com> (@nijikokun)
*/
public interface Method {
/**
* Encodes the Plugin into an Object disguised as the Plugin. If you want the original Plugin Class you must cast it
* to the correct Plugin, to do so you have to verify the name and or version then cast.
* <p/>
* <pre>
* if(method.getName().equalsIgnoreCase("iConomy"))
* iConomy plugin = ((iConomy)method.getPlugin());</pre>
*
* @return <code>Object</code>
* @see #getName()
* @see #getVersion()
*/
Plugin getPlugin();
/**
* Set Plugin data.
*
* @param plugin Plugin
*/
void setPlugin(Plugin plugin);
/**
* Returns the actual name of this method.
*
* @return <code>String</code> Plugin name.
*/
String getName();
/**
* Returns the backend plugin of this economy method, if applicable.
*
* @return <code>String</code> Plugin name.
*/
String getBackend();
/**
* Returns the reported name of this method.
*
* @return <code>String</code> Plugin name.
*/
String getLongName();
/**
* Returns the actual version of this method.
*
* @return <code>String</code> Plugin version.
*/
String getVersion();
/**
* Returns the amount of decimal places that get stored NOTE: it will return -1 if there is no rounding
*
* @return <code>int</code> for each decimal place
*/
int fractionalDigits();
/**
* Formats amounts into this payment methods style of currency display.
*
* @param amount Double
* @return <code>String</code> - Formatted Currency Display.
*/
String format(double amount);
/**
* Allows the verification of bank API existence in this payment method.
*
* @return <code>boolean</code>
*/
boolean hasBanks();
/**
* Determines the existence of a bank via name.
*
* @param bank Bank name
* @return <code>boolean</code>
* @see #hasBanks
*/
boolean hasBank(String bank);
/**
* Determines the existence of an account via name.
*
* @param name Account name
* @return <code>boolean</code>
*/
boolean hasAccount(String name);
/**
* Check to see if an account <code>name</code> is tied to a <code>bank</code>.
*
* @param bank Bank name
* @param name Account name
* @return <code>boolean</code>
*/
boolean hasBankAccount(String bank, String name);
/**
* Forces an account creation
*
* @param name Account name
* @return <code>boolean</code>
*/
boolean createAccount(String name);
/**
* Forces an account creation
*
* @param name Account name
* @param balance Initial account balance
* @return <code>boolean</code>
*/
boolean createAccount(String name, Double balance);
/**
* Returns a <code>MethodAccount</code> class for an account <code>name</code>.
*
* @param name Account name
* @return <code>MethodAccount</code> <em>or</em> <code>Null</code>
*/
MethodAccount getAccount(String name);
/**
* Returns a <code>MethodBankAccount</code> class for an account <code>name</code>.
*
* @param bank Bank name
* @param name Account name
* @return <code>MethodBankAccount</code> <em>or</em> <code>Null</code>
*/
MethodBankAccount getBankAccount(String bank, String name);
/**
* Checks to verify the compatibility between this Method and a plugin. Internal usage only, for the most part.
*
* @param plugin Plugin
* @return <code>boolean</code>
*/
boolean isCompatible(Plugin plugin);
/**
* Contains Calculator and Balance functions for Accounts.
*/
interface MethodAccount {
double balance();
boolean set(double amount);
boolean add(double amount);
boolean subtract(double amount);
boolean multiply(double amount);
boolean divide(double amount);
boolean hasEnough(double amount);
boolean hasOver(double amount);
boolean hasUnder(double amount);
boolean isNegative();
boolean remove();
@Override
String toString();
}
/**
* Contains Calculator and Balance functions for Bank Accounts.
*/
interface MethodBankAccount {
double balance();
String getBankName();
int getBankId();
boolean set(double amount);
boolean add(double amount);
boolean subtract(double amount);
boolean multiply(double amount);
boolean divide(double amount);
boolean hasEnough(double amount);
boolean hasOver(double amount);
boolean hasUnder(double amount);
boolean isNegative();
boolean remove();
@Override
String toString();
}
}
@@ -1,245 +0,0 @@
package com.earth2me.essentials.register.payment;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import java.util.HashSet;
import java.util.Set;
/**
* The <code>Methods</code> initializes Methods that utilize the Method interface based on a "first come, first served"
* basis.
* <p/>
* Allowing you to check whether a payment method exists or not.
* <p/>
* Methods also allows you to set a preferred method of payment before it captures payment plugins in the initialization
* process.
* <p/>
* in
* <code>bukkit.yml</code>: <blockquote><pre>
* economy:
* preferred: "iConomy"
* </pre></blockquote>
* <p>
* For more information about the licensing of this code in EssentialsX, see below:
* https://gist.github.com/md678685/0935441c9573c030c8bd5a2e604aeec3
*
* @author Nijikokun <nijikokun@shortmail.com> (@nijikokun) @copyright: Copyright (C) 2011 @license: AOL license
* <http://aol.nexua.org>
*/
public final class Methods {
private static final Set<Method> Methods = new HashSet<>();
private static final Set<String> Dependencies = new HashSet<>();
private static final Set<Method> Attachables = new HashSet<>();
private static String version = null;
private static boolean self = false;
private static Method Method = null;
private static String preferred = "";
private Methods() {
}
/**
* Implement all methods along with their respective name & class.
*/
public static void init() {
if (!Methods.isEmpty()) {
throw new IllegalStateException("Methods already initialised!");
}
addMethod("Vault", new com.earth2me.essentials.register.payment.methods.VaultEco());
}
/**
* Use to reset methods during disable
*/
public static void reset() {
version = null;
self = false;
Method = null;
preferred = "";
Attachables.clear();
}
/**
* Use to get version of Register plugin
*
* @return version
*/
public static String getVersion() {
return version;
}
/**
* Used by the plugin to setup version
*
* @param v version
*/
public static void setVersion(final String v) {
version = v;
}
/**
* Returns an array of payment method names that have been loaded through the <code>_init</code> method.
*
* @return <code>Set<String></code> - Array of payment methods that are loaded.
* @see #setMethod(PluginManager)
*/
public static Set<String> getDependencies() {
return Dependencies;
}
/**
* Interprets Plugin class data to verify whether it is compatible with an existing payment method to use for
* payments and other various economic activity.
*
* @param plugin Plugin data from bukkit, Internal Class file.
* @return Method <em>or</em> Null
*/
public static Method createMethod(final Plugin plugin) {
for (final Method method : Methods) {
if (method.isCompatible(plugin)) {
method.setPlugin(plugin);
return method;
}
}
return null;
}
private static void addMethod(final String name, final Method method) {
Dependencies.add(name);
Methods.add(method);
}
/**
* Verifies if Register has set a payment method for usage yet.
*
* @return <code>boolean</code>
* @see #setMethod(PluginManager)
* @see #checkDisabled(Plugin)
*/
public static boolean hasMethod() {
return Method != null;
}
/**
* Checks Plugin Class against a multitude of checks to verify it's usability as a payment method.
*
* @param manager the plugin manager for the server
* @return <code>boolean</code> True on success, False on failure.
*/
public static boolean setMethod(final PluginManager manager) {
if (hasMethod()) {
return true;
}
if (self) {
self = false;
return false;
}
int count = 0;
boolean match = false;
Plugin plugin;
for (final String name : getDependencies()) {
if (hasMethod()) {
break;
}
plugin = manager.getPlugin(name);
if (plugin == null || !plugin.isEnabled()) {
continue;
}
final Method current = createMethod(plugin);
if (current == null) {
continue;
}
if (preferred.isEmpty()) {
Method = current;
} else {
Attachables.add(current);
}
}
if (!preferred.isEmpty()) {
do {
if (hasMethod()) {
match = true;
} else {
for (final Method attached : Attachables) {
if (attached == null) {
continue;
}
if (hasMethod()) {
match = true;
break;
}
if (preferred.isEmpty()) {
Method = attached;
}
if (count == 0) {
if (preferred.equalsIgnoreCase(attached.getName())) {
Method = attached;
} else {
Method = attached;
}
}
}
count++;
}
} while (!match);
}
return hasMethod();
}
/**
* Sets the preferred economy
*
* @return <code>boolean</code>
*/
public static boolean setPreferred(final String check) {
if (getDependencies().contains(check)) {
preferred = check;
return true;
}
return false;
}
/**
* Grab the existing and initialized (hopefully) Method Class.
*
* @return <code>Method</code> <em>or</em> <code>Null</code>
*/
public static Method getMethod() {
return Method;
}
/**
* Verify is a plugin is disabled, only does this if we there is an existing payment method initialized in
* Register.
*
* @param method Plugin data from bukkit, Internal Class file.
* @return <code>boolean</code>
*/
public static boolean checkDisabled(final Plugin method) {
if (!hasMethod()) {
return true;
}
if (Method.isCompatible(method)) {
Method = null;
}
return Method == null;
}
}
@@ -1,280 +0,0 @@
package com.earth2me.essentials.register.payment.methods;
import com.earth2me.essentials.register.payment.Method;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
public class VaultEco implements Method {
private Plugin vault;
private Economy economy;
@Override
public Plugin getPlugin() {
return this.vault;
}
@Override
public void setPlugin(final Plugin plugin) {
this.vault = plugin;
final RegisteredServiceProvider<Economy> economyProvider = this.vault.getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class);
if (economyProvider != null) {
this.economy = economyProvider.getProvider();
}
}
@Override
public boolean createAccount(final String name, final Double amount) {
if (hasAccount(name)) {
return false;
}
return false;
}
@Override
public String getName() {
return this.vault.getDescription().getName();
}
@Override
public String getBackend() {
return economy == null ? "NoEco" : economy.getName();
}
@Override
public String getLongName() {
return getName().concat(" - Economy: ").concat(getBackend());
}
@Override
public String getVersion() {
return this.vault.getDescription().getVersion();
}
@Override
public int fractionalDigits() {
return 0;
}
@Override
public String format(final double amount) {
return this.economy.format(amount);
}
@Override
public boolean hasBanks() {
return this.economy.hasBankSupport();
}
@Override
public boolean hasBank(final String bank) {
return this.economy.getBanks().contains(bank);
}
@Override
public boolean hasAccount(final String name) {
return this.economy.hasAccount(name);
}
@Override
public boolean hasBankAccount(final String bank, final String name) {
return this.economy.isBankOwner(bank, name).transactionSuccess() || this.economy.isBankMember(bank, name).transactionSuccess();
}
@Override
public boolean createAccount(final String name) {
return this.economy.createBank(name, "").transactionSuccess();
}
public boolean createAccount(final String name, final double balance) {
if (!this.economy.createBank(name, "").transactionSuccess()) {
return false;
}
return this.economy.bankDeposit(name, balance).transactionSuccess();
}
@Override
public MethodAccount getAccount(final String name) {
if (!hasAccount(name)) {
return null;
}
return new VaultAccount(name, this.economy);
}
@Override
public MethodBankAccount getBankAccount(final String bank, final String name) {
if (!hasBankAccount(bank, name)) {
return null;
}
return new VaultBankAccount(bank, economy);
}
@Override
public boolean isCompatible(final Plugin plugin) {
try {
final RegisteredServiceProvider<Economy> ecoPlugin = plugin.getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class);
return plugin.getName().equals("Vault") && ecoPlugin != null && !ecoPlugin.getProvider().getName().equals("Essentials Economy");
} catch (final LinkageError | Exception e) {
return false;
}
}
public static class VaultAccount implements MethodAccount {
private final String name;
private final Economy economy;
VaultAccount(final String name, final Economy economy) {
this.name = name;
this.economy = economy;
}
@Override
public double balance() {
return this.economy.getBalance(this.name);
}
@Override
public boolean set(final double amount) {
if (!this.economy.withdrawPlayer(this.name, this.balance()).transactionSuccess()) {
return false;
}
if (amount == 0) {
return true;
}
return this.economy.depositPlayer(this.name, amount).transactionSuccess();
}
@Override
public boolean add(final double amount) {
return this.economy.depositPlayer(this.name, amount).transactionSuccess();
}
@Override
public boolean subtract(final double amount) {
return this.economy.withdrawPlayer(this.name, amount).transactionSuccess();
}
@Override
public boolean multiply(final double amount) {
final double balance = this.balance();
return this.set(balance * amount);
}
@Override
public boolean divide(final double amount) {
final double balance = this.balance();
return this.set(balance / amount);
}
@Override
public boolean hasEnough(final double amount) {
return this.balance() >= amount;
}
@Override
public boolean hasOver(final double amount) {
return this.balance() > amount;
}
@Override
public boolean hasUnder(final double amount) {
return this.balance() < amount;
}
@Override
public boolean isNegative() {
return this.balance() < 0;
}
@Override
public boolean remove() {
return this.set(0.0);
}
}
public static class VaultBankAccount implements MethodBankAccount {
private final String bank;
private final Economy economy;
public VaultBankAccount(final String bank, final Economy economy) {
this.bank = bank;
this.economy = economy;
}
@Override
public String getBankName() {
return this.bank;
}
@Override
public int getBankId() {
return -1;
}
@Override
public double balance() {
return this.economy.bankBalance(this.bank).balance;
}
@Override
public boolean set(final double amount) {
if (!this.economy.bankWithdraw(this.bank, this.balance()).transactionSuccess()) {
return false;
}
if (amount == 0) {
return true;
}
return this.economy.bankDeposit(this.bank, amount).transactionSuccess();
}
@Override
public boolean add(final double amount) {
return this.economy.bankDeposit(this.bank, amount).transactionSuccess();
}
@Override
public boolean subtract(final double amount) {
return this.economy.bankWithdraw(this.bank, amount).transactionSuccess();
}
@Override
public boolean multiply(final double amount) {
final double balance = this.balance();
return this.set(balance * amount);
}
@Override
public boolean divide(final double amount) {
final double balance = this.balance();
return this.set(balance / amount);
}
@Override
public boolean hasEnough(final double amount) {
return this.balance() >= amount;
}
@Override
public boolean hasOver(final double amount) {
return this.balance() > amount;
}
@Override
public boolean hasUnder(final double amount) {
return this.balance() < amount;
}
@Override
public boolean isNegative() {
return this.balance() < 0;
}
@Override
public boolean remove() {
return this.set(0.0);
}
}
}
@@ -1,39 +0,0 @@
package com.earth2me.essentials.settings;
import com.earth2me.essentials.storage.MapValueType;
import com.earth2me.essentials.storage.StorageObject;
import org.bukkit.Location;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class Jails implements StorageObject {
@MapValueType(Location.class)
private Map<String, Location> jails = new HashMap<>();
public Map<String, Location> getJails() {
return jails;
}
public void setJails(final Map<String, Location> jails) {
this.jails = jails;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Jails jails1 = (Jails) o;
return Objects.equals(jails, jails1.jails);
}
@Override
public int hashCode() {
return Objects.hash(jails);
}
}
@@ -1,39 +0,0 @@
package com.earth2me.essentials.settings;
import com.earth2me.essentials.storage.MapValueType;
import com.earth2me.essentials.storage.StorageObject;
import org.bukkit.Location;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class Spawns implements StorageObject {
@MapValueType(Location.class)
private Map<String, Location> spawns = new HashMap<>();
public Map<String, Location> getSpawns() {
return spawns;
}
public void setSpawns(final Map<String, Location> spawns) {
this.spawns = spawns;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Spawns spawns1 = (Spawns) o;
return Objects.equals(spawns, spawns1.spawns);
}
@Override
public int hashCode() {
return Objects.hash(spawns);
}
}
@@ -1,11 +0,0 @@
package com.earth2me.essentials.signs;
public class SignException extends Exception {
public SignException(final String message) {
super(message);
}
public SignException(final String message, final Throwable throwable) {
super(message, throwable);
}
}
@@ -1,28 +0,0 @@
package com.earth2me.essentials.signs;
import com.earth2me.essentials.User;
import net.ess3.api.IEssentials;
import java.util.List;
import static com.earth2me.essentials.I18n.tl;
public class SignMail extends EssentialsSign {
public SignMail() {
super("Mail");
}
@Override
protected boolean onSignInteract(final ISign sign, final User player, final String username, final IEssentials ess) throws SignException {
final List<String> mail = player.getMails();
if (mail.isEmpty()) {
player.sendMessage(tl("noNewMail"));
return false;
}
for (final String s : mail) {
player.sendMessage(s);
}
player.sendMessage(tl("markMailAsRead"));
return true;
}
}
@@ -1,56 +0,0 @@
package com.earth2me.essentials.storage;
import net.ess3.api.IEssentials;
import org.bukkit.Bukkit;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.logging.Level;
public abstract class AbstractDelayedYamlFileReader<T extends StorageObject> implements Runnable {
protected final transient IEssentials plugin;
private final transient File file;
private final transient Class<T> clazz;
public AbstractDelayedYamlFileReader(final IEssentials ess, final File file, final Class<T> clazz) {
this.file = file;
this.clazz = clazz;
this.plugin = ess;
ess.runTaskAsynchronously(this);
}
public abstract void onStart();
@Override
public void run() {
onStart();
try {
final FileReader reader = new FileReader(file);
try {
final T object = new YamlStorageReader(reader, plugin).load(clazz);
onSuccess(object);
} finally {
try {
reader.close();
} catch (final IOException ex) {
Bukkit.getLogger().log(Level.SEVERE, "File can't be closed: " + file.toString(), ex);
}
}
} catch (final FileNotFoundException ex) {
onException();
if (plugin.getSettings() == null || plugin.getSettings().isDebug()) {
Bukkit.getLogger().log(Level.INFO, "File not found: " + file.toString());
}
} catch (final ObjectLoadException ex) {
onException();
Bukkit.getLogger().log(Level.SEVERE, "File broken: " + file.toString(), ex.getCause());
}
}
public abstract void onSuccess(T object);
public abstract void onException();
}
@@ -1,44 +0,0 @@
package com.earth2me.essentials.storage;
import net.ess3.api.IEssentials;
import org.bukkit.Bukkit;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.logging.Level;
public abstract class AbstractDelayedYamlFileWriter implements Runnable {
private final transient File file;
public AbstractDelayedYamlFileWriter(final IEssentials ess, final File file) {
this.file = file;
ess.runTaskAsynchronously(this);
}
public abstract StorageObject getObject();
@Override
public void run() {
PrintWriter pw = null;
try {
final StorageObject object = getObject();
final File folder = file.getParentFile();
if (!folder.exists()) {
folder.mkdirs();
}
pw = new PrintWriter(file);
new YamlStorageWriter(pw).save(object);
} catch (final FileNotFoundException ex) {
Bukkit.getLogger().log(Level.SEVERE, file.toString(), ex);
} finally {
onFinish();
if (pw != null) {
pw.close();
}
}
}
public abstract void onFinish();
}
@@ -1,128 +0,0 @@
package com.earth2me.essentials.storage;
import com.earth2me.essentials.IConf;
import net.ess3.api.IEssentials;
import net.ess3.api.IReload;
import org.bukkit.Bukkit;
import java.io.File;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
public abstract class AsyncStorageObjectHolder<T extends StorageObject> implements IConf, IStorageObjectHolder<T>, IReload {
protected final transient IEssentials ess;
private final transient ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final transient Class<T> clazz;
private transient T data;
public AsyncStorageObjectHolder(final IEssentials ess, final Class<T> clazz) {
this.ess = ess;
this.clazz = clazz;
try {
this.data = clazz.newInstance();
} catch (final IllegalAccessException | InstantiationException ex) {
Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
}
}
@Override
public T getData() {
return data;
}
@Override
public void acquireReadLock() {
rwl.readLock().lock();
}
@Override
public void acquireWriteLock() {
while (rwl.getReadHoldCount() > 0) {
rwl.readLock().unlock();
}
rwl.writeLock().lock();
rwl.readLock().lock();
}
@Override
public void close() {
unlock();
}
@Override
public void unlock() {
if (rwl.isWriteLockedByCurrentThread()) {
rwl.writeLock().unlock();
new StorageObjectDataWriter();
}
while (rwl.getReadHoldCount() > 0) {
rwl.readLock().unlock();
}
}
@Override
public void reloadConfig() {
new StorageObjectDataReader();
}
@Override
public void onReload() {
new StorageObjectDataReader();
}
public abstract void finishRead();
public abstract void finishWrite();
public abstract File getStorageFile();
private class StorageObjectDataWriter extends AbstractDelayedYamlFileWriter {
StorageObjectDataWriter() {
super(ess, getStorageFile());
}
@Override
public StorageObject getObject() {
acquireReadLock();
return getData();
}
@Override
public void onFinish() {
unlock();
finishWrite();
}
}
private class StorageObjectDataReader extends AbstractDelayedYamlFileReader<T> {
StorageObjectDataReader() {
super(ess, getStorageFile(), clazz);
}
@Override
public void onStart() {
rwl.writeLock().lock();
}
@Override
public void onSuccess(final T object) {
if (object != null) {
data = object;
}
rwl.writeLock().unlock();
finishRead();
}
@Override
public void onException() {
if (data == null) {
try {
data = clazz.newInstance();
} catch (final IllegalAccessException | InstantiationException ex) {
Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
}
}
rwl.writeLock().unlock();
}
}
}
@@ -1,214 +0,0 @@
package com.earth2me.essentials.storage;
import com.earth2me.essentials.Enchantments;
import com.earth2me.essentials.utils.NumberUtil;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.bukkit.plugin.Plugin;
import org.yaml.snakeyaml.constructor.BaseConstructor;
import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor;
import org.yaml.snakeyaml.introspector.PropertyUtils;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class BukkitConstructor extends CustomClassLoaderConstructor {
private Method constructScalarMethod = null;
public BukkitConstructor(final Class<?> clazz, final Plugin plugin) {
super(clazz, plugin.getClass().getClassLoader());
yamlClassConstructors.put(NodeId.scalar, new ConstructBukkitScalar());
yamlClassConstructors.put(NodeId.mapping, new ConstructBukkitMapping());
final PropertyUtils propertyUtils = getPropertyUtils();
propertyUtils.setSkipMissingProperties(true);
setPropertyUtils(propertyUtils);
}
protected String constructScalarRefl(final ScalarNode scalarNode) {
try {
if (constructScalarMethod == null) {
constructScalarMethod = BaseConstructor.class.getDeclaredMethod("constructScalar", ScalarNode.class);
}
return (String) constructScalarMethod.invoke(this, scalarNode);
} catch (final NoSuchMethodException
| SecurityException
| IllegalAccessException
| IllegalArgumentException
| InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private class ConstructBukkitScalar extends ConstructScalar {
@Override
public Object construct(final Node node) {
if (node.getType().equals(Material.class)) {
final String val = constructScalarRefl((ScalarNode) node);
return Material.matchMaterial(val);
}
if (node.getType().equals(MaterialData.class)) {
final String val = constructScalarRefl((ScalarNode) node);
if (val.isEmpty()) {
return null;
}
final String[] split = val.split("[:+',;.]", 2);
if (split.length == 0) {
return null;
}
final Material mat = Material.matchMaterial(split[0]);
if (mat == null) {
return null;
}
byte data = 0;
if (split.length == 2 && NumberUtil.isInt(split[1])) {
data = Byte.parseByte(split[1]);
}
return new MaterialData(mat, data);
}
if (node.getType().equals(ItemStack.class)) {
final String val = constructScalarRefl((ScalarNode) node);
if (val.isEmpty()) {
return null;
}
final String[] split1 = val.split("\\W");
if (split1.length == 0) {
return null;
}
final String[] split2 = split1[0].split("[:+',;.]", 2);
if (split2.length == 0) {
return null;
}
final Material mat = Material.matchMaterial(split2[0]);
if (mat == null) {
return null;
}
short data = 0;
if (split2.length == 2 && NumberUtil.isInt(split2[1])) {
data = Short.parseShort(split2[1]);
}
int size = mat.getMaxStackSize();
if (split1.length > 1 && NumberUtil.isInt(split1[1])) {
size = Integer.parseInt(split1[1]);
}
final ItemStack stack = new ItemStack(mat, size, data);
if (split1.length > 2) {
for (int i = 2; i < split1.length; i++) {
final String[] split3 = split1[0].split("[:+',;.]", 2);
if (split3.length < 1) {
continue;
}
final Enchantment enchantment = Enchantments.getByName(split3[0]);
if (enchantment == null) {
continue;
}
int level = enchantment.getStartLevel();
if (split3.length == 2 && NumberUtil.isInt(split3[1])) {
level = Integer.parseInt(split3[1]);
}
if (level < enchantment.getStartLevel()) {
level = enchantment.getStartLevel();
}
if (level > enchantment.getMaxLevel()) {
level = enchantment.getMaxLevel();
}
stack.addUnsafeEnchantment(enchantment, level);
}
}
return stack;
}
if (node.getType().equals(EnchantmentLevel.class)) {
final String val = constructScalarRefl((ScalarNode) node);
if (val.isEmpty()) {
return null;
}
final String[] split = val.split("[:+',;.]", 2);
if (split.length == 0) {
return null;
}
final Enchantment enchant = Enchantments.getByName(split[0]);
if (enchant == null) {
return null;
}
int level = enchant.getStartLevel();
if (split.length == 2 && NumberUtil.isInt(split[1])) {
level = Integer.parseInt(split[1]);
}
if (level < enchant.getStartLevel()) {
level = enchant.getStartLevel();
}
if (level > enchant.getMaxLevel()) {
level = enchant.getMaxLevel();
}
return new EnchantmentLevel(enchant, level);
}
return super.construct(node);
}
}
private class ConstructBukkitMapping extends ConstructMapping {
@Override
public Object construct(final Node node) {
if (node.getType().equals(Location.class)) {
//TODO: NPE checks
final MappingNode mnode = (MappingNode) node;
String worldName = "";
double x = 0, y = 0, z = 0;
float yaw = 0, pitch = 0;
if (mnode.getValue().size() < 4) {
return null;
}
for (final NodeTuple nodeTuple : mnode.getValue()) {
final String key = constructScalarRefl((ScalarNode) nodeTuple.getKeyNode());
final ScalarNode snode = (ScalarNode) nodeTuple.getValueNode();
if (key.equalsIgnoreCase("world")) {
worldName = constructScalarRefl(snode);
}
if (key.equalsIgnoreCase("x")) {
x = Double.parseDouble(constructScalarRefl(snode));
}
if (key.equalsIgnoreCase("y")) {
y = Double.parseDouble(constructScalarRefl(snode));
}
if (key.equalsIgnoreCase("z")) {
z = Double.parseDouble(constructScalarRefl(snode));
}
if (key.equalsIgnoreCase("yaw")) {
yaw = Float.parseFloat(constructScalarRefl(snode));
}
if (key.equalsIgnoreCase("pitch")) {
pitch = Float.parseFloat(constructScalarRefl(snode));
}
}
if (worldName == null || worldName.isEmpty()) {
return null;
}
final World world = Bukkit.getWorld(worldName);
if (world == null) {
return null;
}
return new Location(world, x, y, z, yaw, pitch);
}
return super.construct(node);
}
}
}
@@ -1,14 +0,0 @@
package com.earth2me.essentials.storage;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Comment {
String[] value() default "";
}
@@ -1,66 +0,0 @@
package com.earth2me.essentials.storage;
import org.bukkit.enchantments.Enchantment;
import java.util.Map.Entry;
public class EnchantmentLevel implements Entry<Enchantment, Integer> {
private Enchantment enchantment;
private int level;
public EnchantmentLevel(final Enchantment enchantment, final int level) {
this.enchantment = enchantment;
this.level = level;
}
public Enchantment getEnchantment() {
return enchantment;
}
public void setEnchantment(final Enchantment enchantment) {
this.enchantment = enchantment;
}
public int getLevel() {
return level;
}
public void setLevel(final int level) {
this.level = level;
}
@Override
public Enchantment getKey() {
return enchantment;
}
@Override
public Integer getValue() {
return level;
}
@Override
public Integer setValue(final Integer v) {
final int t = level;
level = v;
return t;
}
@Override
public int hashCode() {
return enchantment.hashCode() ^ level;
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof Entry) {
final Entry entry = (Entry) obj;
if (entry.getKey() instanceof Enchantment && entry.getValue() instanceof Integer) {
final Enchantment enchant = (Enchantment) entry.getKey();
final Integer lvl = (Integer) entry.getValue();
return this.enchantment.equals(enchant) && this.level == lvl;
}
}
return false;
}
}
@@ -1,13 +0,0 @@
package com.earth2me.essentials.storage;
public interface IStorageObjectHolder<T extends StorageObject> {
T getData();
void acquireReadLock();
void acquireWriteLock();
void close();
void unlock();
}
@@ -1,5 +0,0 @@
package com.earth2me.essentials.storage;
public interface IStorageReader {
<T extends StorageObject> T load(final Class<? extends T> clazz) throws ObjectLoadException;
}
@@ -1,5 +0,0 @@
package com.earth2me.essentials.storage;
public interface IStorageWriter {
void save(final StorageObject object);
}
@@ -1,12 +0,0 @@
package com.earth2me.essentials.storage;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MapKeyType {
Class value() default String.class;
}
@@ -1,7 +0,0 @@
package com.earth2me.essentials.storage;
public class ObjectLoadException extends Exception {
public ObjectLoadException(final Throwable thrwbl) {
super(thrwbl);
}
}
@@ -1,4 +0,0 @@
package com.earth2me.essentials.storage;
public interface StorageObject {
}
@@ -1,97 +0,0 @@
package com.earth2me.essentials.storage;
import org.bukkit.plugin.Plugin;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import java.io.Reader;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
public class YamlStorageReader implements IStorageReader {
private transient static final Map<Class, Yaml> PREPARED_YAMLS = Collections.synchronizedMap(new HashMap<>());
private transient static final Map<Class, ReentrantLock> LOCKS = new HashMap<>();
private transient final Reader reader;
private transient final Plugin plugin;
public YamlStorageReader(final Reader reader, final Plugin plugin) {
this.reader = reader;
this.plugin = plugin;
}
@Override
public <T extends StorageObject> T load(final Class<? extends T> clazz) throws ObjectLoadException {
Yaml yaml = PREPARED_YAMLS.get(clazz);
if (yaml == null) {
yaml = new Yaml(prepareConstructor(clazz));
PREPARED_YAMLS.put(clazz, yaml);
}
ReentrantLock lock;
synchronized (LOCKS) {
lock = LOCKS.get(clazz);
if (lock == null) {
lock = new ReentrantLock();
}
}
lock.lock();
try {
T object = yaml.load(reader);
if (object == null) {
object = clazz.newInstance();
}
return object;
} catch (final IllegalAccessException | InstantiationException ex) {
throw new ObjectLoadException(ex);
} finally {
lock.unlock();
}
}
private Constructor prepareConstructor(final Class<?> clazz) {
final Constructor constructor = new BukkitConstructor(clazz, plugin);
final Set<Class> classes = new HashSet<>();
prepareConstructor(constructor, classes, clazz);
return constructor;
}
private void prepareConstructor(final Constructor constructor, final Set<Class> classes, final Class clazz) {
classes.add(clazz);
final TypeDescription description = new TypeDescription(clazz);
for (final Field field : clazz.getDeclaredFields()) {
prepareList(field, description, classes, constructor);
prepareMap(field, description, classes, constructor);
if (StorageObject.class.isAssignableFrom(field.getType()) && !classes.contains(field.getType())) {
prepareConstructor(constructor, classes, field.getType());
}
}
constructor.addTypeDescription(description);
}
private void prepareList(final Field field, final TypeDescription description, final Set<Class> classes, final Constructor constructor) {
final ListType listType = field.getAnnotation(ListType.class);
if (listType != null) {
description.putListPropertyType(field.getName(), listType.value());
if (StorageObject.class.isAssignableFrom(listType.value()) && !classes.contains(listType.value())) {
prepareConstructor(constructor, classes, listType.value());
}
}
}
private void prepareMap(final Field field, final TypeDescription description, final Set<Class> classes, final Constructor constructor) {
final MapValueType mapType = field.getAnnotation(MapValueType.class);
if (mapType != null) {
final MapKeyType mapKeyType = field.getAnnotation(MapKeyType.class);
description.putMapPropertyType(field.getName(), mapKeyType == null ? String.class : mapKeyType.value(), mapType.value());
if (StorageObject.class.isAssignableFrom(mapType.value()) && !classes.contains(mapType.value())) {
prepareConstructor(constructor, classes, mapType.value());
}
}
}
}
@@ -1,254 +0,0 @@
package com.earth2me.essentials.storage;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.yaml.snakeyaml.Yaml;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
public class YamlStorageWriter implements IStorageWriter {
private transient static final Pattern NON_WORD_PATTERN = Pattern.compile("\\W");
private transient static final Yaml YAML = new Yaml();
private transient final PrintWriter writer;
public YamlStorageWriter(final PrintWriter writer) {
this.writer = writer;
}
@Override
public void save(final StorageObject object) {
try {
writeToFile(object, 0, object.getClass());
} catch (final IllegalArgumentException | IllegalAccessException ex) {
Logger.getLogger(YamlStorageWriter.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void writeToFile(final Object object, final int depth, final Class clazz) throws IllegalAccessException {
for (final Field field : clazz.getDeclaredFields()) {
final int modifier = field.getModifiers();
if (Modifier.isPrivate(modifier) && !Modifier.isTransient(modifier) && !Modifier.isStatic(modifier)) {
field.setAccessible(true);
final Object data = field.get(object);
if (writeKey(field, depth, data)) {
continue;
}
if (data instanceof StorageObject) {
writer.println();
writeToFile(data, depth + 1, data.getClass());
} else if (data instanceof Map) {
writeMap((Map<Object, Object>) data, depth + 1);
} else if (data instanceof Collection) {
writeCollection((Collection<Object>) data, depth + 1);
} else if (data instanceof Location) {
writeLocation((Location) data, depth + 1);
} else {
writeScalar(data);
writer.println();
}
}
}
}
private boolean writeKey(final Field field, final int depth, final Object data) {
final boolean commentPresent = writeComment(field, depth);
if (data == null && !commentPresent) {
return true;
}
writeIndention(depth);
if (data == null && commentPresent) {
writer.print('#');
}
final String name = field.getName();
writer.print(name);
writer.print(": ");
if (data == null && commentPresent) {
writer.println();
writer.println();
return true;
}
return false;
}
private boolean writeComment(final Field field, final int depth) {
final boolean commentPresent = field.isAnnotationPresent(Comment.class);
if (commentPresent) {
final Comment comments = field.getAnnotation(Comment.class);
for (final String comment : comments.value()) {
final String trimmed = comment.trim();
if (trimmed.isEmpty()) {
continue;
}
writeIndention(depth);
writer.print("# ");
writer.print(trimmed);
writer.println();
}
}
return commentPresent;
}
private void writeCollection(final Collection<Object> data, final int depth) throws IllegalAccessException {
writer.println();
if (data.isEmpty()) {
writer.println();
}
for (final Object entry : data) {
if (entry != null) {
writeIndention(depth);
writer.print("- ");
if (entry instanceof StorageObject) {
writer.println();
writeToFile(entry, depth + 1, entry.getClass());
} else if (entry instanceof Location) {
writeLocation((Location) entry, depth + 1);
} else {
writeScalar(entry);
}
}
}
writer.println();
}
private void writeMap(final Map<Object, Object> data, final int depth) throws IllegalArgumentException, IllegalAccessException {
writer.println();
if (data.isEmpty()) {
writer.println();
}
for (final Entry<Object, Object> entry : data.entrySet()) {
final Object value = entry.getValue();
if (value != null) {
writeIndention(depth);
writeKey(entry.getKey());
writer.print(": ");
if (value instanceof StorageObject) {
writer.println();
writeToFile(value, depth + 1, value.getClass());
} else if (value instanceof Collection) {
writeCollection((Collection<Object>) value, depth + 1);
} else if (value instanceof Location) {
writeLocation((Location) value, depth + 1);
} else {
writeScalar(value);
writer.println();
}
}
}
}
private void writeIndention(final int depth) {
for (int i = 0; i < depth; i++) {
writer.print(" ");
}
}
private void writeScalar(final Object data) {
if (data instanceof String || data instanceof Boolean || data instanceof Number) {
synchronized (YAML) {
YAML.dumpAll(Collections.singletonList(data).iterator(), writer);
}
} else if (data instanceof Material) {
writeMaterial(data);
writer.println();
} else if (data instanceof MaterialData) {
writeMaterialData(data);
writer.println();
} else if (data instanceof ItemStack) {
writeItemStack(data);
writer.println();
} else if (data instanceof EnchantmentLevel) {
writeEnchantmentLevel(data);
writer.println();
} else {
throw new UnsupportedOperationException();
}
}
private void writeKey(final Object data) {
if (data instanceof String || data instanceof Boolean || data instanceof Number) {
final String output = data.toString();
if (NON_WORD_PATTERN.matcher(output).find()) {
writer.print('"');
writer.print(output.replace("\"", "\\\""));
writer.print('"');
} else {
writer.print(output);
}
} else if (data instanceof Material) {
writeMaterial(data);
} else if (data instanceof MaterialData) {
writeMaterialData(data);
} else if (data instanceof EnchantmentLevel) {
writeEnchantmentLevel(data);
} else {
throw new UnsupportedOperationException();
}
}
private void writeMaterial(final Object data) {
writer.print(data.toString().toLowerCase(Locale.ENGLISH));
}
private void writeMaterialData(final Object data) {
final MaterialData matData = (MaterialData) data;
writeMaterial(matData.getItemType());
if (matData.getData() > 0) {
writer.print(':');
writer.print(matData.getData());
}
}
private void writeItemStack(final Object data) {
final ItemStack itemStack = (ItemStack) data;
writeMaterialData(itemStack.getData());
writer.print(' ');
writer.print(itemStack.getAmount());
for (final Entry<Enchantment, Integer> entry : itemStack.getEnchantments().entrySet()) {
writer.print(' ');
writeEnchantmentLevel(entry);
}
}
private void writeEnchantmentLevel(final Object data) {
final Entry<Enchantment, Integer> enchLevel = (Entry<Enchantment, Integer>) data;
writer.print(enchLevel.getKey().getName().toLowerCase(Locale.ENGLISH));
writer.print(':');
writer.print(enchLevel.getValue());
}
private void writeLocation(final Location entry, final int depth) {
writer.println();
writeIndention(depth);
writer.print("world: ");
writeScalar(entry.getWorld().getName());
writeIndention(depth);
writer.print("x: ");
writeScalar(entry.getX());
writeIndention(depth);
writer.print("y: ");
writeScalar(entry.getY());
writeIndention(depth);
writer.print("z: ");
writeScalar(entry.getZ());
writeIndention(depth);
writer.print("yaw: ");
writeScalar(entry.getYaw());
writeIndention(depth);
writer.print("pitch: ");
writeScalar(entry.getPitch());
}
}
@@ -1,30 +0,0 @@
package com.earth2me.essentials.textreader;
import com.earth2me.essentials.CommandSource;
import java.util.List;
public class SimpleTextPager {
private final transient IText text;
public SimpleTextPager(final IText text) {
this.text = text;
}
public void showPage(final CommandSource sender) {
for (final String line : text.getLines()) {
sender.sendMessage(line);
}
}
public List<String> getLines() {
return text.getLines();
}
public String getLine(final int line) {
if (text.getLines().size() < line) {
return null;
}
return text.getLines().get(line);
}
}
@@ -1,156 +0,0 @@
package com.earth2me.essentials.utils;
import org.bukkit.Bukkit;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.material.MaterialData;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.Set;
public final class MaterialUtil {
public static final Material SPAWNER = EnumUtil.getMaterial("MOB_SPAWNER", "SPAWNER");
private static final Set<Material> BEDS;
private static final Set<Material> BANNERS;
private static final Set<Material> FIREWORKS;
private static final Set<Material> LEGACY_SKULLS;
private static final Set<Material> LEATHER_ARMOR;
private static final Set<Material> MOB_HEADS;
// includes TIPPED_ARROW which also has potion effects
private static final Set<Material> PLAYER_HEADS;
private static final Set<Material> POTIONS;
private static final Set<Material> SIGN_POSTS;
private static final Set<Material> WALL_SIGNS;
static {
BEDS = EnumUtil.getAllMatching(Material.class, "BED", "BED_BLOCK", "WHITE_BED", "ORANGE_BED",
"MAGENTA_BED", "LIGHT_BLUE_BED", "YELLOW_BED", "LIME_BED", "PINK_BED", "GRAY_BED",
"LIGHT_GRAY_BED", "CYAN_BED", "PURPLE_BED", "BLUE_BED", "BROWN_BED", "GREEN_BED",
"RED_BED", "BLACK_BED");
BANNERS = EnumUtil.getAllMatching(Material.class, "BANNER", "WHITE_BANNER",
"ORANGE_BANNER", "MAGENTA_BANNER", "LIGHT_BLUE_BANNER", "YELLOW_BANNER", "LIME_BANNER",
"PINK_BANNER", "GRAY_BANNER", "LIGHT_GRAY_BANNER", "CYAN_BANNER", "PURPLE_BANNER",
"BLUE_BANNER", "BROWN_BANNER", "GREEN_BANNER", "RED_BANNER", "BLACK_BANNER", "SHIELD");
FIREWORKS = EnumUtil.getAllMatching(Material.class, "FIREWORK", "FIREWORK_ROCKET",
"FIREWORK_CHARGE", "FIREWORK_STAR");
LEATHER_ARMOR = EnumUtil.getAllMatching(Material.class, "LEATHER_HELMET",
"LEATHER_CHESTPLATE", "LEATHER_LEGGINGS", "LEATHER_BOOTS");
LEGACY_SKULLS = EnumUtil.getAllMatching(Material.class, "SKULL", "SKULL_ITEM");
MOB_HEADS = EnumUtil.getAllMatching(Material.class, "SKELETON_SKULL",
"SKELETON_WALL_SKULL", "WITHER_SKELETON_SKULL", "WITHER_SKELETON_WALL_SKULL",
"CREEPER_HEAD", "CREEPER_WALL_HEAD", "ZOMBIE_HEAD", "ZOMBIE_WALL_HEAD", "DRAGON_HEAD"
, "DRAGON_WALL_HEAD");
PLAYER_HEADS = EnumUtil.getAllMatching(Material.class, "PLAYER_HEAD", "PLAYER_WALL_HEAD");
POTIONS = EnumUtil.getAllMatching(Material.class, "POTION", "SPLASH_POTION",
"LINGERING_POTION", "TIPPED_ARROW");
SIGN_POSTS = EnumUtil.getAllMatching(Material.class, "SIGN", "SIGN_POST",
"ACACIA_SIGN", "BIRCH_SIGN",
"DARK_OAK_SIGN", "JUNGLE_SIGN",
"OAK_SIGN", "SPRUCE_SIGN",
"CRIMSON_SIGN", "WARPED_SIGN");
WALL_SIGNS = EnumUtil.getAllMatching(Material.class, "WALL_SIGN",
"ACACIA_WALL_SIGN", "BIRCH_WALL_SIGN",
"DARK_OAK_WALL_SIGN", "JUNGLE_WALL_SIGN",
"OAK_WALL_SIGN", "SPRUCE_WALL_SIGN",
"CRIMSON_WALL_SIGN", "WARPED_WALL_SIGN");
}
private MaterialUtil() {
}
public static boolean isBed(final Material material) {
return BEDS.contains(material);
}
public static boolean isBanner(final Material material) {
return BANNERS.contains(material);
}
public static boolean isFirework(final Material material) {
return FIREWORKS.contains(material);
}
public static boolean isLeatherArmor(final Material material) {
return LEATHER_ARMOR.contains(material);
}
public static boolean isMobHead(final Material material, final int durability) {
if (MOB_HEADS.contains(material)) {
return true;
}
return LEGACY_SKULLS.contains(material) && (durability != 3);
}
public static boolean isPlayerHead(final Material material, final int durability) {
if (PLAYER_HEADS.contains(material)) {
return true;
}
return LEGACY_SKULLS.contains(material) && durability == 3;
}
public static boolean isPotion(final Material material) {
return POTIONS.contains(material);
}
public static boolean isSignPost(final Material material) {
return SIGN_POSTS.contains(material);
}
public static boolean isWallSign(final Material material) {
return WALL_SIGNS.contains(material);
}
public static boolean isSign(final Material material) {
return isSignPost(material) || isWallSign(material);
}
public static boolean isSkull(final Material material) {
return isPlayerHead(material, -1) || isMobHead(material, -1);
}
public static Material convertFromLegacy(final int id, final byte damage) {
for (final Material material : EnumSet.allOf(Material.class)) {
if (material.getId() == id) {
try {
return Bukkit.getUnsafe().fromLegacy(new MaterialData(material, damage));
} catch (final NoSuchMethodError error) {
break;
}
}
}
try {
//noinspection JavaReflectionMemberAccess
final Method getMaterialFromId = Material.class.getDeclaredMethod("getMaterial", int.class);
return (Material) getMaterialFromId.invoke(null, id);
} catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
}
return null;
}
public static DyeColor getColorOf(final Material material) {
for (final DyeColor color : DyeColor.values()) {
if (material.toString().contains(color.name())) {
return color;
}
}
return DyeColor.WHITE;
}
}
@@ -1,118 +0,0 @@
package com.earth2me.essentials.utils;
import net.ess3.api.IEssentials;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
import static com.earth2me.essentials.I18n.tl;
public final class NumberUtil {
private static final DecimalFormat twoDPlaces = new DecimalFormat("#,###.##");
private static final DecimalFormat currencyFormat = new DecimalFormat("#0.00", DecimalFormatSymbols.getInstance(Locale.US));
// This field is likely to be modified in com.earth2me.essentials.Settings when loading currency format.
// This ensures that we can supply a constant formatting.
private static NumberFormat PRETTY_FORMAT = NumberFormat.getInstance(Locale.US);
static {
twoDPlaces.setRoundingMode(RoundingMode.HALF_UP);
currencyFormat.setRoundingMode(RoundingMode.FLOOR);
PRETTY_FORMAT.setRoundingMode(RoundingMode.FLOOR);
PRETTY_FORMAT.setGroupingUsed(true);
PRETTY_FORMAT.setMinimumFractionDigits(2);
PRETTY_FORMAT.setMaximumFractionDigits(2);
}
private NumberUtil() {
}
// this method should only be called by Essentials
public static void internalSetPrettyFormat(final NumberFormat prettyFormat) {
PRETTY_FORMAT = prettyFormat;
}
public static String shortCurrency(final BigDecimal value, final IEssentials ess) {
if (ess.getSettings().isCurrencySymbolSuffixed()) {
return formatAsCurrency(value) + ess.getSettings().getCurrencySymbol();
}
return ess.getSettings().getCurrencySymbol() + formatAsCurrency(value);
}
public static String formatDouble(final double value) {
return twoDPlaces.format(value);
}
public static String formatAsCurrency(final BigDecimal value) {
String str = currencyFormat.format(value);
if (str.endsWith(".00")) {
str = str.substring(0, str.length() - 3);
}
return str;
}
public static String formatAsPrettyCurrency(final BigDecimal value) {
String str = PRETTY_FORMAT.format(value);
if (str.endsWith(".00")) {
str = str.substring(0, str.length() - 3);
}
return str;
}
public static String displayCurrency(final BigDecimal value, final IEssentials ess) {
String currency = formatAsPrettyCurrency(value);
String sign = "";
if (value.signum() < 0) {
currency = currency.substring(1);
sign = "-";
}
if (ess.getSettings().isCurrencySymbolSuffixed()) {
return sign + tl("currency", currency, ess.getSettings().getCurrencySymbol());
}
return sign + tl("currency", ess.getSettings().getCurrencySymbol(), currency);
}
public static String displayCurrencyExactly(final BigDecimal value, final IEssentials ess) {
String currency = value.toPlainString();
String sign = "";
if (value.signum() < 0) {
currency = currency.substring(1);
sign = "-";
}
if (ess.getSettings().isCurrencySymbolSuffixed()) {
return sign + tl("currency", currency, ess.getSettings().getCurrencySymbol());
}
return sign + tl("currency", ess.getSettings().getCurrencySymbol(), currency);
}
public static String sanitizeCurrencyString(final String input, final IEssentials ess) {
final String symbol = ess.getSettings().getCurrencySymbol();
final boolean suffix = ess.getSettings().isCurrencySymbolSuffixed();
if (input.contains(symbol)) {
return suffix ? input.substring(0, input.indexOf(symbol)) : input.substring(symbol.length());
}
return input;
}
public static boolean isInt(final String sInt) {
try {
Integer.parseInt(sInt);
} catch (final NumberFormatException e) {
return false;
}
return true;
}
public static boolean isPositiveInt(final String sInt) {
if (!isInt(sInt)) {
return false;
}
return Integer.parseInt(sInt) > 0;
}
}
@@ -1,224 +0,0 @@
package com.earth2me.essentials.utils;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import io.papermc.lib.PaperLib;
import org.bukkit.Bukkit;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class VersionUtil {
public static final BukkitVersion v1_14_R01 = BukkitVersion.fromString("1.14-R0.1-SNAPSHOT");
public static final BukkitVersion v1_8_8_R01 = BukkitVersion.fromString("1.8.8-R0.1-SNAPSHOT");
public static final BukkitVersion v1_9_R01 = BukkitVersion.fromString("1.9-R0.1-SNAPSHOT");
public static final BukkitVersion v1_9_4_R01 = BukkitVersion.fromString("1.9.4-R0.1-SNAPSHOT");
public static final BukkitVersion v1_10_R01 = BukkitVersion.fromString("1.10-R0.1-SNAPSHOT");
public static final BukkitVersion v1_10_2_R01 = BukkitVersion.fromString("1.10.2-R0.1-SNAPSHOT");
public static final BukkitVersion v1_11_R01 = BukkitVersion.fromString("1.11-R0.1-SNAPSHOT");
public static final BukkitVersion v1_11_2_R01 = BukkitVersion.fromString("1.11.2-R0.1-SNAPSHOT");
public static final BukkitVersion v1_12_0_R01 = BukkitVersion.fromString("1.12.0-R0.1-SNAPSHOT");
public static final BukkitVersion v1_12_2_R01 = BukkitVersion.fromString("1.12.2-R0.1-SNAPSHOT");
public static final BukkitVersion v1_13_0_R01 = BukkitVersion.fromString("1.13.0-R0.1-SNAPSHOT");
public static final BukkitVersion v1_13_2_R01 = BukkitVersion.fromString("1.13.2-R0.1-SNAPSHOT");
public static final BukkitVersion v1_14_4_R01 = BukkitVersion.fromString("1.14.4-R0.1-SNAPSHOT");
public static final BukkitVersion v1_15_R01 = BukkitVersion.fromString("1.15-R0.1-SNAPSHOT");
public static final BukkitVersion v1_15_2_R01 = BukkitVersion.fromString("1.15.2-R0.1-SNAPSHOT");
public static final BukkitVersion v1_16_1_R01 = BukkitVersion.fromString("1.16.1-R0.1-SNAPSHOT");
public static final BukkitVersion v1_16_2_R01 = BukkitVersion.fromString("1.16.2-R0.1-SNAPSHOT");
public static final BukkitVersion v1_16_4_R01 = BukkitVersion.fromString("1.16.4-R0.1-SNAPSHOT");
private static final Set<BukkitVersion> supportedVersions = ImmutableSet.of(v1_8_8_R01, v1_9_4_R01, v1_10_2_R01, v1_11_2_R01, v1_12_2_R01, v1_13_2_R01, v1_14_4_R01, v1_15_2_R01, v1_16_4_R01);
private static BukkitVersion serverVersion = null;
private VersionUtil() {
}
public static BukkitVersion getServerBukkitVersion() {
if (serverVersion == null) {
serverVersion = BukkitVersion.fromString(Bukkit.getServer().getBukkitVersion());
}
return serverVersion;
}
public static SupportStatus getServerSupportStatus() {
try {
Class.forName("net.minecraftforge.common.MinecraftForge");
return SupportStatus.UNSTABLE;
} catch (final ClassNotFoundException ignored) {
}
if (!supportedVersions.contains(getServerBukkitVersion())) {
return SupportStatus.OUTDATED;
}
return PaperLib.isPaper() ? SupportStatus.FULL : SupportStatus.LIMITED;
}
public static boolean isServerSupported() {
return getServerSupportStatus().isSupported();
}
public static final class BukkitVersion implements Comparable<BukkitVersion> {
private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\.(\\d+)\\.?([0-9]*)?(?:-pre(\\d))?(?:-?R?([\\d.]+))?(?:-SNAPSHOT)?");
private final int major;
private final int minor;
private final int prerelease;
private final int patch;
private final double revision;
private BukkitVersion(final int major, final int minor, final int patch, final double revision, final int prerelease) {
this.major = major;
this.minor = minor;
this.patch = patch;
this.revision = revision;
this.prerelease = prerelease;
}
public static BukkitVersion fromString(final String string) {
Preconditions.checkNotNull(string, "string cannot be null.");
Matcher matcher = VERSION_PATTERN.matcher(string);
if (!matcher.matches()) {
if (!Bukkit.getName().equals("Essentials Fake Server")) {
throw new IllegalArgumentException(string + " is not in valid version format. e.g. 1.8.8-R0.1");
}
matcher = VERSION_PATTERN.matcher(v1_14_R01.toString());
Preconditions.checkArgument(matcher.matches(), string + " is not in valid version format. e.g. 1.8.8-R0.1");
}
return from(matcher.group(1), matcher.group(2), matcher.group(3), matcher.groupCount() < 5 ? "" : matcher.group(5), matcher.group(4));
}
private static BukkitVersion from(final String major, final String minor, String patch, String revision, String prerelease) {
if (patch == null || patch.isEmpty()) patch = "0";
if (revision == null || revision.isEmpty()) revision = "0";
if (prerelease == null || prerelease.isEmpty()) prerelease = "-1";
return new BukkitVersion(Integer.parseInt(major),
Integer.parseInt(minor),
Integer.parseInt(patch),
Double.parseDouble(revision),
Integer.parseInt(prerelease));
}
public boolean isHigherThan(final BukkitVersion o) {
return compareTo(o) > 0;
}
public boolean isHigherThanOrEqualTo(final BukkitVersion o) {
return compareTo(o) >= 0;
}
public boolean isLowerThan(final BukkitVersion o) {
return compareTo(o) < 0;
}
public boolean isLowerThanOrEqualTo(final BukkitVersion o) {
return compareTo(o) <= 0;
}
public int getMajor() {
return major;
}
public int getMinor() {
return minor;
}
public int getPatch() {
return patch;
}
public double getRevision() {
return revision;
}
public int getPrerelease() {
return prerelease;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final BukkitVersion that = (BukkitVersion) o;
return major == that.major &&
minor == that.minor &&
patch == that.patch &&
revision == that.revision &&
prerelease == that.prerelease;
}
@Override
public int hashCode() {
return Objects.hashCode(major, minor, patch, revision, prerelease);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(major + "." + minor);
if (patch != 0) {
sb.append(".").append(patch);
}
if (prerelease != -1) {
sb.append("-pre").append(prerelease);
}
return sb.append("-R").append(revision).toString();
}
@Override
public int compareTo(final BukkitVersion o) {
if (major < o.major) {
return -1;
} else if (major > o.major) {
return 1;
} else { // equal major
if (minor < o.minor) {
return -1;
} else if (minor > o.minor) {
return 1;
} else { // equal minor
if (patch < o.patch) {
return -1;
} else if (patch > o.patch) {
return 1;
} else { // equal patch
if (prerelease < o.prerelease) {
return -1;
} else if (prerelease > o.prerelease) {
return 1;
} else { // equal prerelease
return Double.compare(revision, o.revision);
}
}
}
}
}
}
public enum SupportStatus {
FULL(true),
LIMITED(true),
UNSTABLE(false),
OUTDATED(false)
;
private final boolean supported;
SupportStatus(final boolean supported) {
this.supported = supported;
}
public boolean isSupported() {
return supported;
}
}
}
File diff suppressed because it is too large. Load diff
-8
View File
@@ -1,8 +0,0 @@
# This file stores custom item aliases.
# We recommend you use the `/itemdb alias` command to manage these.
# NOTE: If you try and alias an item to another entry in this file, the alias won't work.
aliases:
bluepaint: blue_dye
snad: sand
breakfast: cooked_porkchop
@@ -0,0 +1,149 @@
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;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
public class AlternativeCommandsHandler {
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;
public AlternativeCommandsHandler(final IEssentials ess) {
this.ess = ess;
for (final Plugin plugin : ess.getServer().getPluginManager().getPlugins()) {
if (plugin.isEnabled()) {
addPlugin(plugin);
}
}
}
public final void addPlugin(final Plugin plugin) {
if (plugin.getDescription().getMain().contains("com.earth2me.essentials") || plugin.getDescription().getMain().contains("net.essentialsx")) {
return;
}
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<WeakReference<Command>> pluginCommands = altCommands.computeIfAbsent(commandName.toLowerCase(Locale.ENGLISH), k -> new ArrayList<>());
boolean found = false;
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) cmd).getPlugin().equals(plugin)) {
found = true;
break;
}
}
if (!found) {
pluginCommands.add(new WeakReference<>(command));
}
}
}
private List<Map.Entry<String, Command>> getPluginCommands(Plugin plugin) {
final Map<String, Command> commands = new HashMap<>();
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());
}
}
// 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<WeakReference<Command>>>> iterator = altCommands.entrySet().iterator();
while (iterator.hasNext()) {
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();
}
}
}
public Command getAlternative(final String label) {
final List<WeakReference<Command>> commands = altCommands.get(label);
if (commands == null || commands.isEmpty()) {
return null;
}
if (commands.size() == 1) {
return commands.get(0).get();
}
// return the first command that is not an alias
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).get();
}
public void executed(final String label, final Command pc) {
if (pc instanceof PluginIdentifiableCommand) {
final String altString = ((PluginIdentifiableCommand) pc).getPlugin().getName() + ":" + pc.getName();
if (ess.getSettings().isDebug()) {
ess.getLogger().log(Level.INFO, "Essentials: Alternative command " + label + " found, using " + altString);
}
disabledList.put(label, altString);
}
}
public Map<String, String> disabledCommands() {
return disabledList;
}
}
@@ -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,11 +127,18 @@ 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());
}
});
}
@Override
public void nowUnsafe(Location loc, TeleportCause cause, CompletableFuture<Boolean> future) {
final CompletableFuture<Boolean> paperFuture = PaperLib.teleportAsync(teleportOwner.getBase(), loc, cause);
paperFuture.thenAccept(future::complete);
paperFuture.exceptionally(future::completeExceptionally);
}
private void runOnMain(final Runnable runnable) throws ExecutionException, InterruptedException {
if (Bukkit.isPrimaryThread()) {
runnable.run();
@@ -153,13 +158,13 @@ public class AsyncTeleport implements IAsyncTeleport {
final PreTeleportEvent event = new PreTeleportEvent(teleportee, cause, target);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
future.complete(false);
return;
}
teleportee.setLastLocation();
if (!ess.getSettings().isForcePassengerTeleport() && !teleportee.getBase().isEmpty()) {
if (!ess.getSettings().isTeleportPassengerDismount()) {
future.completeExceptionally(new Exception(tl("passengerTeleportFail")));
future.completeExceptionally(new TranslatableException("passengerTeleportFail"));
return;
}
@@ -170,15 +175,19 @@ public class AsyncTeleport implements IAsyncTeleport {
return;
}
}
teleportee.setLastLocation();
if (teleportee.isAuthorized("essentials.back.onteleport")) {
teleportee.setLastLocation();
}
final Location targetLoc = target.getLocation();
if (ess.getSettings().isTeleportSafetyEnabled() && LocationUtil.isBlockOutsideWorldBorder(targetLoc.getWorld(), targetLoc.getBlockX(), targetLoc.getBlockZ())) {
if (ess.getSettings().isTeleportSafetyEnabled() && !ess.getSettings().isForceDisableTeleportSafety() && LocationUtil.isBlockOutsideWorldBorder(targetLoc.getWorld(), targetLoc.getBlockX(), targetLoc.getBlockZ())) {
targetLoc.setX(LocationUtil.getXInsideWorldBorder(targetLoc.getWorld(), targetLoc.getBlockX()));
targetLoc.setZ(LocationUtil.getZInsideWorldBorder(targetLoc.getWorld(), targetLoc.getBlockZ()));
}
PaperLib.getChunkAtAsync(targetLoc).thenAccept(chunk -> {
PaperLib.getChunkAtAsync(targetLoc.getWorld(), targetLoc.getBlockX() >> 4, targetLoc.getBlockZ() >> 4, true, true).thenAccept(chunk -> {
Location loc = targetLoc;
if (LocationUtil.isBlockUnsafeForUser(teleportee, chunk.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())) {
if (LocationUtil.isBlockUnsafeForUser(ess, teleportee, chunk.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())) {
if (ess.getSettings().isTeleportSafetyEnabled()) {
if (ess.getSettings().isForceDisableTeleportSafety()) {
//The chunk we're teleporting to is 100% going to be loaded here, no need to teleport async.
@@ -193,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 {
@@ -209,6 +218,9 @@ public class AsyncTeleport implements IAsyncTeleport {
}
}
future.complete(true);
}).exceptionally(th -> {
future.completeExceptionally(th);
return null;
});
}
@@ -219,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);
}
@@ -234,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());
}
});
}
@@ -246,6 +258,7 @@ public class AsyncTeleport implements IAsyncTeleport {
final TeleportWarmupEvent event = new TeleportWarmupEvent(teleportee, cause, target, delay);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
future.complete(false);
return;
}
delay = event.getDelay();
@@ -266,10 +279,12 @@ public class AsyncTeleport implements IAsyncTeleport {
}
if (cooldown(true, future)) {
future.complete(false);
return;
}
if (delay <= 0 || teleportOwner.isAuthorized("essentials.teleport.timer.bypass") || teleportee.isAuthorized("essentials.teleport.timer.bypass")) {
if (cooldown(false, future)) {
future.complete(false);
return;
}
nowAsync(teleportee, target, cause, future);
@@ -279,18 +294,19 @@ public class AsyncTeleport implements IAsyncTeleport {
return;
}
}
future.complete(true);
return;
}
cancel(false);
warnUser(teleportee, delay);
initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false);
initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false, future);
}
private void teleportOther(final IUser teleporter, final IUser teleportee, final ITarget target, final Trade chargeFor, final TeleportCause cause, final CompletableFuture<Boolean> future) {
double delay = ess.getSettings().getTeleportDelay();
final TeleportWarmupEvent event = new TeleportWarmupEvent(teleportee, cause, target, delay);
final TeleportWarmupEvent event = new TeleportWarmupEvent(teleporter, teleportee, cause, target, delay);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
@@ -330,12 +346,13 @@ public class AsyncTeleport implements IAsyncTeleport {
return;
}
}
future.complete(true);
return;
}
cancel(false);
warnUser(teleportee, delay);
initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false);
initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false, future);
}
@Override
@@ -366,12 +383,13 @@ public class AsyncTeleport implements IAsyncTeleport {
if (chargeFor != null) {
chargeFor.charge(teleportOwner, future);
}
future.complete(true);
return;
}
cancel(false);
warnUser(teleportOwner, delay);
initTimer((long) (delay * 1000.0), teleportOwner, null, chargeFor, cause, true);
initTimer((long) (delay * 1000.0), teleportOwner, null, chargeFor, cause, true, future);
}
void respawnNow(final IUser teleportee, final TeleportCause cause, final CompletableFuture<Boolean> future) {
@@ -387,6 +405,9 @@ public class AsyncTeleport implements IAsyncTeleport {
ess.getServer().getPluginManager().callEvent(pre);
nowAsync(teleportee, new LocationTarget(pre.getRespawnLocation()), cause, future);
}
}).exceptionally(th -> {
future.completeExceptionally(th);
return null;
});
}
@@ -402,14 +423,19 @@ 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;
}
otherUser.sendMessage(tl("warpingTo", warp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
if (!otherUser.equals(teleportOwner)) {
teleportOwner.sendMessage(tl("warpingTo", warp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
}
final String finalWarp = warp;
future.thenAccept(success -> {
if (success) {
otherUser.sendTl("warpingTo", finalWarp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
if (!otherUser.equals(teleportOwner)) {
teleportOwner.sendTl("warpingTo", finalWarp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
}
}
});
teleport(otherUser, new LocationTarget(loc), chargeFor, cause, future);
}
@@ -422,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);
}
@@ -443,8 +469,8 @@ public class AsyncTeleport implements IAsyncTeleport {
}
}
private void initTimer(final long delay, final IUser teleportUser, final ITarget target, final Trade chargeFor, final TeleportCause cause, final boolean respawn) {
timedTeleport = new AsyncTimedTeleport(teleportOwner, ess, this, delay, teleportUser, target, chargeFor, cause, respawn);
private void initTimer(final long delay, final IUser teleportUser, final ITarget target, final Trade chargeFor, final TeleportCause cause, final boolean respawn, CompletableFuture<Boolean> future) {
timedTeleport = new AsyncTimedTeleport(teleportOwner, ess, this, delay, future, teleportUser, target, chargeFor, cause, respawn);
}
public enum TeleportType {
@@ -8,8 +8,6 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import static com.earth2me.essentials.I18n.tl;
public class AsyncTimedTeleport implements Runnable {
private static final double MOVE_CONSTANT = 0.3;
private final IUser teleportOwner;
@@ -18,6 +16,7 @@ public class AsyncTimedTeleport implements Runnable {
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
private final CompletableFuture<Boolean> parentFuture;
// 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
@@ -33,6 +32,10 @@ public class AsyncTimedTeleport implements Runnable {
private double timer_health;
AsyncTimedTeleport(final IUser user, final IEssentials ess, final AsyncTeleport teleport, final long delay, final IUser teleportUser, final ITarget target, final Trade chargeFor, final TeleportCause cause, final boolean respawn) {
this(user, ess, teleport, delay, null, teleportUser, target, chargeFor, cause, respawn);
}
AsyncTimedTeleport(final IUser user, final IEssentials ess, final AsyncTeleport teleport, final long delay, final CompletableFuture<Boolean> future, final IUser teleportUser, final ITarget target, final Trade chargeFor, final TeleportCause cause, final boolean respawn) {
this.teleportOwner = user;
this.ess = ess;
this.teleport = teleport;
@@ -50,6 +53,18 @@ public class AsyncTimedTeleport implements Runnable {
this.timer_canMove = user.isAuthorized("essentials.teleport.timer.move");
timer_task = ess.runTaskTimerAsynchronously(this, 20, 20).getTaskId();
if (future != null) {
this.parentFuture = future;
return;
}
final CompletableFuture<Boolean> cFuture = new CompletableFuture<>();
cFuture.exceptionally(e -> {
ess.showError(teleportOwner.getSource(), e, "\\ teleport");
return false;
});
this.parentFuture = cFuture;
}
@Override
@@ -89,29 +104,25 @@ 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");
final CompletableFuture<Boolean> future = new CompletableFuture<>();
future.exceptionally(e -> {
ess.showError(teleportOwner.getSource(), e, "\\ teleport");
return false;
});
if (timer_chargeFor != null) {
timer_chargeFor.isAffordableFor(teleportOwner);
}
if (timer_respawn) {
teleport.respawnNow(teleportUser, timer_cause, future);
teleport.respawnNow(teleportUser, timer_cause, parentFuture);
} else {
teleport.nowAsync(teleportUser, timer_teleportTarget, timer_cause, future);
teleport.nowAsync(teleportUser, timer_teleportTarget, timer_cause, parentFuture);
}
future.thenAccept(success -> {
parentFuture.thenAccept(success -> {
if (timer_chargeFor != null) {
try {
timer_chargeFor.charge(teleportOwner);
@@ -139,9 +150,9 @@ public class AsyncTimedTeleport implements Runnable {
try {
ess.getServer().getScheduler().cancelTask(timer_task);
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 {
@@ -1,5 +1,6 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.AdventureUtil;
import net.ess3.api.IEssentials;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
@@ -10,12 +11,10 @@ import java.io.InputStreamReader;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.earth2me.essentials.I18n.tl;
import static com.earth2me.essentials.I18n.tlLiteral;
public class Backup implements Runnable {
private static final Logger LOGGER = Logger.getLogger("Essentials");
private transient final Server server;
private transient final IEssentials ess;
private final AtomicBoolean pendingShutdown = new AtomicBoolean(false);
@@ -81,7 +80,7 @@ public class Backup implements Runnable {
taskLock.complete(new Object());
return;
}
LOGGER.log(Level.INFO, tl("backupStarted"));
ess.getLogger().log(Level.INFO, AdventureUtil.miniToLegacy(tlLiteral("backupStarted")));
final CommandSender cs = server.getConsoleSender();
server.dispatchCommand(cs, "save-all");
server.dispatchCommand(cs, "save-off");
@@ -99,17 +98,17 @@ public class Backup implements Runnable {
do {
line = reader.readLine();
if (line != null) {
LOGGER.log(Level.INFO, line);
ess.getLogger().log(Level.INFO, line);
}
} while (line != null);
}
} catch (final IOException ex) {
LOGGER.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) {
LOGGER.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
@@ -120,7 +119,7 @@ public class Backup implements Runnable {
}
active = false;
taskLock.complete(new Object());
LOGGER.log(Level.INFO, tl("backupFinished"));
ess.getLogger().log(Level.INFO, AdventureUtil.miniToLegacy(tlLiteral("backupFinished")));
}
}
@@ -0,0 +1,94 @@
package com.earth2me.essentials;
import net.ess3.api.IEssentials;
import net.essentialsx.api.v2.services.BalanceTop;
import org.bukkit.plugin.ServicePriority;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class BalanceTopImpl implements BalanceTop {
private final IEssentials ess;
private LinkedHashMap<UUID, BalanceTop.Entry> topCache = new LinkedHashMap<>();
private BigDecimal balanceTopTotal = BigDecimal.ZERO;
private long cacheAge = 0;
private CompletableFuture<Void> cacheLock;
public BalanceTopImpl(IEssentials ess) {
this.ess = ess;
ess.getServer().getServicesManager().register(BalanceTop.class, this, ess, ServicePriority.Normal);
}
private void calculateBalanceTopMap() {
final List<Entry> entries = new LinkedList<>();
BigDecimal newTotal = BigDecimal.ZERO;
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
continue;
}
if (!user.isBaltopExempt()) {
final BigDecimal userMoney = user.getMoney();
user.updateMoneyCache(userMoney);
newTotal = newTotal.add(userMoney);
final String name;
if (user.getBase() instanceof OfflinePlayerStub) {
name = user.getLastAccountName();
} else if (user.isHidden()) {
name = user.getName();
} else {
name = user.getDisplayName();
}
entries.add(new BalanceTop.Entry(user.getUUID(), name, userMoney));
}
}
}
final LinkedHashMap<UUID, Entry> sortedMap = new LinkedHashMap<>();
entries.sort((entry1, entry2) -> entry2.getBalance().compareTo(entry1.getBalance()));
for (Entry entry : entries) {
sortedMap.put(entry.getUuid(), entry);
}
topCache = sortedMap;
balanceTopTotal = newTotal;
cacheAge = System.currentTimeMillis();
cacheLock.complete(null);
cacheLock = null;
}
@Override
public CompletableFuture<Void> calculateBalanceTopMapAsync() {
if (cacheLock != null) {
return cacheLock;
}
cacheLock = new CompletableFuture<>();
ess.runTaskAsynchronously(this::calculateBalanceTopMap);
return cacheLock;
}
@Override
public Map<UUID, Entry> getBalanceTopCache() {
return Collections.unmodifiableMap(topCache);
}
@Override
public long getCacheAge() {
return cacheAge;
}
@Override
public BigDecimal getBalanceTopTotal() {
return balanceTopTotal;
}
public boolean isCacheLocked() {
return cacheLock != null;
}
}
Loaded 100 of 862 files, more files were not shown because too many files have changed in this diff. Show more