Compare commits

...
325 Commits
Author SHA1 Message Date
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
693 changed files with 58354 additions and 19394 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
+17 -2
View File
@@ -1,5 +1,20 @@
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/discussions/categories/ideas-and-feature-suggestions
about: Visit the Feature Suggestions forum to view and discuss existing feature suggestions.
- name: Create help request
url: https://github.com/EssentialsX/Essentials/discussions/new?category=q-a
about: Create a support ticket to get help from developers.
- name: Suggest a feature
url: https://github.com/EssentialsX/Essentials/discussions/new?category=ideas-and-feature-suggestions
about: Suggest new features for EssentialsX!
- name: Get help from the community on Discord
url: https://discord.gg/casfFyh
about: 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. -->
+79
View File
@@ -0,0 +1,79 @@
name: Report bug
description: Report a bug in EssentialsX.
labels: 'bug: unconfirmed'
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://paste.gg or https://gist.github.com, save and the paste the link in this box. If you included those files in the same paste as your startup log, paste the same link here.
placeholder: "Example: https://paste.gg/p/anonymous/109dd6a10a734a3aa430d5a351ea5210"
- 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: markdown
attributes:
value: |
In the text box below, 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.
@@ -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.
-->
+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
+75
View File
@@ -0,0 +1,75 @@
name: Build EssentialsX
on:
push:
branches:
- 2.x
- dev/*
jobs:
build:
name: Build and upload
runs-on: ubuntu-latest
steps:
- name: Checkout Git repo
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Restore Gradle cache
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Set up JDK 16
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 16
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew build --stacktrace
- name: Archive plugin jars on GitHub
uses: actions/upload-artifact@master
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
if: ${{ success() && github.event_name == 'push' && github.repository == 'EssentialsX/Essentials' && github.ref == 'refs/heads/2.x' }}
run: |
mv Essentials/build/docs/javadoc/ javadocs/
cp -r EssentialsAntiBuild/build/docs/javadoc/ javadocs/EssentialsAntiBuild/
cp -r EssentialsChat/build/docs/javadoc/ javadocs/EssentialsChat/
cp -r EssentialsDiscord/build/docs/javadoc/ javadocs/EssentialsDiscord/
cp -r EssentialsGeoIP/build/docs/javadoc/ javadocs/EssentialsGeoIP/
cp -r EssentialsProtect/build/docs/javadoc/ javadocs/EssentialsProtect/
cp -r EssentialsSpawn/build/docs/javadoc/ javadocs/EssentialsSpawn/
cp -r EssentialsXMPP/build/docs/javadoc/ javadocs/EssentialsXMPP/
- name: Deploy Javadocs
if: ${{ success() && github.event_name == 'push' && github.repository == 'EssentialsX/Essentials' && github.ref == 'refs/heads/2.x' }}
uses: netlify/actions/cli@master
with:
args: deploy --dir=javadocs/ --prod --message="GitHubActionsDeploy"
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_JD_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_JD_2X_SITE_ID }}
+51
View File
@@ -0,0 +1,51 @@
name: Build Pull Request
on:
push:
branches:
- mc/* # MC version updates
- pr/* # PR rebases
pull_request:
branches:
- 2.x
- mc/*
- dev/*
jobs:
build:
name: Build and upload
runs-on: ubuntu-latest
steps:
- name: Checkout Git repo
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Restore Gradle cache
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Set up JDK 16
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 16
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew build --stacktrace
- name: Archive plugin jars on GitHub
uses: actions/upload-artifact@master
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/
+8 -2
View File
@@ -17,6 +17,12 @@
/Essentials/usermap.csv
# Build files
target/
jars/
.gradle/
/jars/
out/
build/
target/
*.class
# Run directory
/run/
+3 -3
View File
@@ -9,11 +9,11 @@
<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="property-2.configDirectory" value=".checkstyle/" />
<entry key="scan-before-checkin" value="true" />
<entry key="scanscope" value="JavaOnly" />
<entry key="suppress-errors" value="false" />
</map>
</option>
</component>
</project>
</project>
+58
View File
@@ -0,0 +1,58 @@
plugins {
id("essentials.shadow-module")
}
dependencies {
compileOnly('com.github.milkbowl:VaultAPI:1.7') {
exclude group: "org.bukkit", module: "bukkit"
}
compileOnly 'net.luckperms:api:5.0'
api 'io.papermc:paperlib:1.0.6'
api 'org.bstats:bstats-bukkit:2.2.1'
implementation 'org.spongepowered:configurate-yaml:4.1.2'
implementation 'org.checkerframework:checker-qual:3.14.0'
// Providers
api project(':providers:BaseProviders')
api project(':providers:PaperProvider')
api(project(':providers:NMSReflectionProvider')) {
exclude group: "org.bukkit", module: "bukkit"
}
api(project(':providers:1_8Provider')) {
exclude group: "org.spigotmc", module: "spigot"
}
api(project(':providers:1_12Provider')) {
exclude group: "org.bukkit", module: "bukkit"
}
}
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 (project(':providers:BaseProviders'))
include (project(':providers:PaperProvider'))
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'
minimize {
include(dependency('org.checkerframework:checker-qual'))
}
}
-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,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);
}
}
}
File diff suppressed because it is too large. Load diff
@@ -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,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,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);
}
}
File diff suppressed because it is too large. Load diff
@@ -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,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,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 ListType {
Class value() default String.class;
}
@@ -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());
}
}
@@ -0,0 +1,107 @@
package com.earth2me.essentials;
import org.bukkit.command.Command;
import org.bukkit.command.PluginIdentifiableCommand;
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<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).entrySet()) {
final String[] commandSplit = entry.getKey().split(":", 2);
final String commandName = commandSplit.length > 1 ? commandSplit[1] : entry.getKey();
final Command command = entry.getValue();
final List<Command> pluginCommands = altcommands.computeIfAbsent(commandName.toLowerCase(Locale.ENGLISH), k -> new ArrayList<>());
boolean found = false;
for (final Command pc2 : pluginCommands) {
// Safe cast, everything that's added comes from getPluginCommands which already performs the cast check.
if (((PluginIdentifiableCommand) pc2).getPlugin().equals(plugin)) {
found = true;
break;
}
}
if (!found) {
pluginCommands.add(command);
}
}
}
private Map<String, Command> getPluginCommands(Plugin plugin) {
final Map<String, Command> commands = new HashMap<>();
for (final Map.Entry<String, Command> entry : ess.getKnownCommandsProvider().getKnownCommands().entrySet()) {
if (entry.getValue() instanceof PluginIdentifiableCommand && ((PluginIdentifiableCommand) entry.getValue()).getPlugin().equals(plugin)) {
commands.put(entry.getKey(), entry.getValue());
}
}
return commands;
}
public void removePlugin(final Plugin plugin) {
final Iterator<Map.Entry<String, List<Command>>> iterator = altcommands.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<String, List<Command>> entry = iterator.next();
entry.getValue().removeIf(pc -> !(pc instanceof PluginIdentifiableCommand) || ((PluginIdentifiableCommand) pc).getPlugin().equals(plugin));
if (entry.getValue().isEmpty()) {
iterator.remove();
}
}
}
public Command getAlternative(final String label) {
final List<Command> 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 Command command : commands) {
if (command.getName().equalsIgnoreCase(label)) {
return command;
}
}
// return the first alias
return commands.get(0);
}
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()) {
LOGGER.log(Level.INFO, "Essentials: Alternative command " + label + " found, using " + altString);
}
disabledList.put(label, altString);
}
}
public Map<String, String> disabledCommands() {
return disabledList;
}
}
@@ -134,6 +134,13 @@ public class AsyncTeleport implements IAsyncTeleport {
});
}
@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,9 +160,9 @@ 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()) {
@@ -170,15 +177,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.
@@ -209,6 +220,9 @@ public class AsyncTeleport implements IAsyncTeleport {
}
}
future.complete(true);
}).exceptionally(th -> {
future.completeExceptionally(th);
return null;
});
}
@@ -246,6 +260,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 +281,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,12 +296,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);
}
private void teleportOther(final IUser teleporter, final IUser teleportee, final ITarget target, final Trade chargeFor, final TeleportCause cause, final CompletableFuture<Boolean> future) {
@@ -330,12 +348,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 +385,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 +407,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;
});
}
@@ -406,10 +429,15 @@ public class AsyncTeleport implements IAsyncTeleport {
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.sendMessage(tl("warpingTo", finalWarp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
if (!otherUser.equals(teleportOwner)) {
teleportOwner.sendMessage(tl("warpingTo", finalWarp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
}
}
});
teleport(otherUser, new LocationTarget(loc), chargeFor, cause, future);
}
@@ -443,8 +471,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 {
@@ -18,6 +18,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 +34,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 +55,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
@@ -98,20 +115,16 @@ public class AsyncTimedTeleport implements Runnable {
cancelTimer(false);
teleportUser.sendMessage(tl("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);
@@ -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.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.isBaltopExempt()) {
final BigDecimal userMoney = user.getMoney();
user.updateMoneyCache(userMoney);
newTotal = newTotal.add(userMoney);
final String name;
if (user.getBase() instanceof OfflinePlayer) {
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;
}
}
@@ -6,8 +6,13 @@ import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.UUID;
import static com.earth2me.essentials.I18n.tl;
public final class Console implements IMessageRecipient {
public static final String NAME = "Console";
public static final String DISPLAY_NAME = tl("consoleName");
private static Console instance; // Set in essentials
private final IEssentials ess;
@@ -43,9 +48,14 @@ public final class Console implements IMessageRecipient {
return Console.NAME;
}
@Override
public UUID getUUID() {
return null;
}
@Override
public String getDisplayName() {
return Console.NAME;
return Console.DISPLAY_NAME;
}
@Override
@@ -277,14 +277,27 @@ public final class Enchantments {
}
public static Enchantment getByName(final String name) {
if (name == null || name.isEmpty()) {
return null;
}
Enchantment enchantment = null;
if (isFlat) { // 1.13+ only
enchantment = Enchantment.getByKey(NamespacedKey.minecraft(name.toLowerCase()));
try {
enchantment = Enchantment.getByKey(NamespacedKey.minecraft(name.toLowerCase()));
} catch (IllegalArgumentException ignored) {
// NamespacedKey throws IAE if key does not match regex
}
}
if (enchantment == null) {
enchantment = Enchantment.getByName(name.toUpperCase());
}
if (enchantment == null) {
enchantment = Enchantment.getByName(name.toLowerCase());
}
if (enchantment == null) {
enchantment = Enchantment.getByName(name);
}
if (enchantment == null) {
enchantment = ENCHANTMENTS.get(name.toLowerCase(Locale.ENGLISH));
}
@@ -301,4 +314,20 @@ public final class Enchantments {
public static Set<String> keySet() {
return ENCHANTMENTS.keySet();
}
public static void registerEnchantment(String name, Enchantment enchantment) {
if (ENCHANTMENTS.containsKey(name) || ALIASENCHANTMENTS.containsKey(name)) {
return;
}
ENCHANTMENTS.put(name, enchantment);
}
public static void registerAlias(String name, Enchantment enchantment) {
if (ENCHANTMENTS.containsKey(name) || ALIASENCHANTMENTS.containsKey(name) || !ENCHANTMENTS.containsValue(enchantment)) {
return;
}
ALIASENCHANTMENTS.put(name, enchantment);
}
}
@@ -21,7 +21,10 @@ import com.earth2me.essentials.commands.EssentialsCommand;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.commands.NoChargeException;
import com.earth2me.essentials.commands.NotEnoughArgumentsException;
import com.earth2me.essentials.commands.PlayerNotFoundException;
import com.earth2me.essentials.commands.QuietAbortException;
import com.earth2me.essentials.economy.EconomyLayers;
import com.earth2me.essentials.economy.vault.VaultEconomyProvider;
import com.earth2me.essentials.items.AbstractItemDb;
import com.earth2me.essentials.items.CustomItemResolver;
import com.earth2me.essentials.items.FlatItemDb;
@@ -29,14 +32,14 @@ import com.earth2me.essentials.items.LegacyItemDb;
import com.earth2me.essentials.metrics.MetricsWrapper;
import com.earth2me.essentials.perm.PermissionsDefaults;
import com.earth2me.essentials.perm.PermissionsHandler;
import com.earth2me.essentials.register.payment.Methods;
import com.earth2me.essentials.signs.SignBlockListener;
import com.earth2me.essentials.signs.SignEntityListener;
import com.earth2me.essentials.signs.SignPlayerListener;
import com.earth2me.essentials.textreader.IText;
import com.earth2me.essentials.textreader.KeywordReplacer;
import com.earth2me.essentials.textreader.SimpleTextInput;
import com.earth2me.essentials.utils.DateUtil;
import com.earth2me.essentials.updatecheck.UpdateChecker;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.VersionUtil;
import io.papermc.lib.PaperLib;
import net.ess3.api.Economy;
@@ -44,28 +47,50 @@ import net.ess3.api.IEssentials;
import net.ess3.api.IItemDb;
import net.ess3.api.IJails;
import net.ess3.api.ISettings;
import net.ess3.nms.refl.providers.ReflDataWorldInfoProvider;
import net.ess3.nms.refl.providers.ReflFormattedCommandAliasProvider;
import net.ess3.nms.refl.providers.ReflKnownCommandsProvider;
import net.ess3.nms.refl.providers.ReflOnlineModeProvider;
import net.ess3.nms.refl.providers.ReflPersistentDataProvider;
import net.ess3.nms.refl.providers.ReflServerStateProvider;
import net.ess3.nms.refl.providers.ReflSpawnEggProvider;
import net.ess3.nms.refl.providers.ReflSpawnerBlockProvider;
import net.ess3.nms.refl.providers.ReflKnownCommandsProvider;
import net.ess3.nms.refl.providers.ReflSyncCommandsProvider;
import net.ess3.provider.ContainerProvider;
import net.ess3.provider.FormattedCommandAliasProvider;
import net.ess3.provider.ItemUnbreakableProvider;
import net.ess3.provider.KnownCommandsProvider;
import net.ess3.provider.MaterialTagProvider;
import net.ess3.provider.PersistentDataProvider;
import net.ess3.provider.PotionMetaProvider;
import net.ess3.provider.ProviderListener;
import net.ess3.provider.SerializationProvider;
import net.ess3.provider.ServerStateProvider;
import net.ess3.provider.SpawnEggProvider;
import net.ess3.provider.SpawnerBlockProvider;
import net.ess3.provider.SpawnerItemProvider;
import net.ess3.provider.SyncCommandsProvider;
import net.ess3.provider.WorldInfoProvider;
import net.ess3.provider.providers.BasePotionDataProvider;
import net.ess3.provider.providers.BlockMetaSpawnerItemProvider;
import net.ess3.provider.providers.BukkitMaterialTagProvider;
import net.ess3.provider.providers.BukkitSpawnerBlockProvider;
import net.ess3.provider.providers.FixedHeightWorldInfoProvider;
import net.ess3.provider.providers.FlatSpawnEggProvider;
import net.ess3.provider.providers.LegacyItemUnbreakableProvider;
import net.ess3.provider.providers.LegacyPotionMetaProvider;
import net.ess3.provider.providers.LegacySpawnEggProvider;
import net.ess3.provider.providers.ModernItemUnbreakableProvider;
import net.ess3.provider.providers.ModernPersistentDataProvider;
import net.ess3.provider.providers.ModernDataWorldInfoProvider;
import net.ess3.provider.providers.PaperContainerProvider;
import net.ess3.provider.providers.PaperKnownCommandsProvider;
import net.ess3.provider.providers.PaperMaterialTagProvider;
import net.ess3.provider.providers.PaperRecipeBookListener;
import net.ess3.provider.providers.PaperSerializationProvider;
import net.ess3.provider.providers.PaperServerStateProvider;
import net.essentialsx.api.v2.services.BalanceTop;
import net.essentialsx.api.v2.services.mail.MailService;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
@@ -74,6 +99,7 @@ import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
@@ -84,24 +110,26 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.plugin.InvalidDescriptionException;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.yaml.snakeyaml.error.YAMLException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Predicate;
@@ -125,7 +153,9 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
private transient PermissionsHandler permissionsHandler;
private transient AlternativeCommandsHandler alternativeCommandsHandler;
private transient UserMap userMap;
private transient BalanceTopImpl balanceTop;
private transient ExecuteTimer execTimer;
private transient MailService mail;
private transient I18n i18n;
private transient MetricsWrapper metrics;
private transient EssentialsTimer timer;
@@ -135,14 +165,23 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
private transient PotionMetaProvider potionMetaProvider;
private transient ServerStateProvider serverStateProvider;
private transient ContainerProvider containerProvider;
private transient SerializationProvider serializationProvider;
private transient KnownCommandsProvider knownCommandsProvider;
private transient FormattedCommandAliasProvider formattedCommandAliasProvider;
private transient ProviderListener recipeBookEventProvider;
private transient MaterialTagProvider materialTagProvider;
private transient SyncCommandsProvider syncCommandsProvider;
private transient PersistentDataProvider persistentDataProvider;
private transient ReflOnlineModeProvider onlineModeProvider;
private transient ItemUnbreakableProvider unbreakableProvider;
private transient WorldInfoProvider worldInfoProvider;
private transient Kits kits;
private transient RandomTeleport randomTeleport;
private transient UpdateChecker updateChecker;
private transient Map<String, IEssentialsCommand> commandMap = new HashMap<>();
static {
// TODO: improve legacy code
Methods.init();
EconomyLayers.init();
}
public Essentials() {
@@ -177,7 +216,9 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
LOGGER.log(Level.INFO, tl("usingTempFolderForTesting"));
LOGGER.log(Level.INFO, dataFolder.toString());
settings = new Settings(this);
mail = new MailServiceImpl(this);
userMap = new UserMap(this);
balanceTop = new BalanceTopImpl(this);
permissionsHandler = new PermissionsHandler(this, false);
Economy.setEss(this);
confList = new ArrayList<>();
@@ -186,6 +227,17 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
kits = new Kits(this);
}
@Override
public void onLoad() {
try {
// Vault registers their Essentials provider at low priority, so we have to use normal priority here
Class.forName("net.milkbowl.vault.economy.Economy");
getServer().getServicesManager().register(net.milkbowl.vault.economy.Economy.class, new VaultEconomyProvider(this), this, ServicePriority.Normal);
} catch (final ClassNotFoundException ignored) {
// Probably safer than fetching for the plugin as bukkit may not have marked it as enabled at this point in time
}
}
@Override
public void onEnable() {
try {
@@ -201,6 +253,12 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
Console.setInstance(this);
switch (VersionUtil.getServerSupportStatus()) {
case NMS_CLEANROOM:
getLogger().severe(tl("serverUnsupportedCleanroom"));
break;
case DANGEROUS_FORK:
getLogger().severe(tl("serverUnsupportedDangerous"));
break;
case UNSTABLE:
getLogger().severe(tl("serverUnsupportedMods"));
break;
@@ -212,6 +270,10 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
break;
}
if (VersionUtil.getSupportStatusClass() != null) {
getLogger().info(tl("serverUnsupportedClass", VersionUtil.getSupportStatusClass()));
}
final PluginManager pm = getServer().getPluginManager();
for (final Plugin plugin : pm.getPlugins()) {
if (plugin.getDescription().getName().startsWith("Essentials") && !plugin.getDescription().getVersion().equals(this.getDescription().getVersion()) && !plugin.getDescription().getName().equals("EssentialsAntiCheat")) {
@@ -219,131 +281,163 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
}
}
try {
final EssentialsUpgrade upgrade = new EssentialsUpgrade(this);
upgrade.beforeSettings();
execTimer.mark("Upgrade");
final EssentialsUpgrade upgrade = new EssentialsUpgrade(this);
upgrade.beforeSettings();
execTimer.mark("Upgrade");
confList = new ArrayList<>();
settings = new Settings(this);
confList.add(settings);
execTimer.mark("Settings");
confList = new ArrayList<>();
settings = new Settings(this);
confList.add(settings);
execTimer.mark("Settings");
userMap = new UserMap(this);
confList.add(userMap);
execTimer.mark("Init(Usermap)");
mail = new MailServiceImpl(this);
execTimer.mark("Init(Mail)");
kits = new Kits(this);
confList.add(kits);
upgrade.convertKits();
execTimer.mark("Kits");
userMap = new UserMap(this);
confList.add(userMap);
execTimer.mark("Init(Usermap)");
upgrade.afterSettings();
execTimer.mark("Upgrade2");
balanceTop = new BalanceTopImpl(this);
execTimer.mark("Init(BalanceTop)");
warps = new Warps(getServer(), this.getDataFolder());
confList.add(warps);
execTimer.mark("Init(Warp)");
kits = new Kits(this);
confList.add(kits);
upgrade.convertKits();
execTimer.mark("Kits");
worth = new Worth(this.getDataFolder());
confList.add(worth);
execTimer.mark("Init(Worth)");
upgrade.afterSettings();
execTimer.mark("Upgrade2");
itemDb = getItemDbFromConfig();
confList.add(itemDb);
execTimer.mark("Init(ItemDB)");
warps = new Warps(this.getDataFolder());
confList.add(warps);
execTimer.mark("Init(Warp)");
randomTeleport = new RandomTeleport(this);
if (randomTeleport.getPreCache()) {
randomTeleport.cacheRandomLocations(randomTeleport.getCenter(), randomTeleport.getMinRange(), randomTeleport.getMaxRange());
}
confList.add(randomTeleport);
execTimer.mark("Init(RandomTeleport)");
worth = new Worth(this.getDataFolder());
confList.add(worth);
execTimer.mark("Init(Worth)");
customItemResolver = new CustomItemResolver(this);
try {
itemDb.registerResolver(this, "custom_items", customItemResolver);
confList.add(customItemResolver);
} catch (final Exception e) {
e.printStackTrace();
customItemResolver = null;
}
execTimer.mark("Init(CustomItemResolver)");
itemDb = getItemDbFromConfig();
confList.add(itemDb);
execTimer.mark("Init(ItemDB)");
jails = new Jails(this);
confList.add(jails);
execTimer.mark("Init(Jails)");
//Spawner item provider only uses one but it's here for legacy...
spawnerItemProvider = new BlockMetaSpawnerItemProvider();
//Spawner block providers
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_12_0_R01)) {
spawnerBlockProvider = new ReflSpawnerBlockProvider();
} else {
spawnerBlockProvider = new BukkitSpawnerBlockProvider();
}
//Spawn Egg Providers
if (VersionUtil.getServerBukkitVersion().isLowerThanOrEqualTo(VersionUtil.v1_8_8_R01)) {
spawnEggProvider = new LegacySpawnEggProvider();
} else if (VersionUtil.getServerBukkitVersion().isLowerThanOrEqualTo(VersionUtil.v1_12_2_R01)) {
spawnEggProvider = new ReflSpawnEggProvider();
} else {
spawnEggProvider = new FlatSpawnEggProvider();
}
//Potion Meta Provider
if (VersionUtil.getServerBukkitVersion().isLowerThanOrEqualTo(VersionUtil.v1_8_8_R01)) {
potionMetaProvider = new LegacyPotionMetaProvider();
} else {
potionMetaProvider = new BasePotionDataProvider();
}
//Server State Provider
//Container Provider
if (PaperLib.isPaper() && VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_15_2_R01)) {
serverStateProvider = new PaperServerStateProvider();
containerProvider = new PaperContainerProvider();
} else {
serverStateProvider = new ReflServerStateProvider(getLogger());
}
//Event Providers
if (PaperLib.isPaper()) {
try {
Class.forName("com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent");
recipeBookEventProvider = new PaperRecipeBookListener(event -> {
if (this.getUser(((PlayerEvent) event).getPlayer()).isRecipeSee()) {
((Cancellable) event).setCancelled(true);
}
});
} catch (final ClassNotFoundException ignored) {
}
}
//Known Commands Provider
if (PaperLib.isPaper() && VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_11_2_R01)) {
knownCommandsProvider = new PaperKnownCommandsProvider();
} else {
knownCommandsProvider = new ReflKnownCommandsProvider();
}
execTimer.mark("Init(Providers)");
reload();
// The item spawn blacklist is loaded with all other settings, before the item
// DB, but it depends on the item DB, so we need to reload it again here:
((Settings) settings)._lateLoadItemSpawnBlacklist();
} catch (final YAMLException exception) {
if (pm.getPlugin("EssentialsUpdate") != null) {
LOGGER.log(Level.SEVERE, tl("essentialsHelp2"));
} else {
LOGGER.log(Level.SEVERE, tl("essentialsHelp1"));
}
handleCrash(exception);
return;
randomTeleport = new RandomTeleport(this);
if (randomTeleport.getPreCache()) {
randomTeleport.cacheRandomLocations(randomTeleport.getCenter(), randomTeleport.getMinRange(), randomTeleport.getMaxRange());
}
confList.add(randomTeleport);
execTimer.mark("Init(RandomTeleport)");
customItemResolver = new CustomItemResolver(this);
try {
itemDb.registerResolver(this, "custom_items", customItemResolver);
confList.add(customItemResolver);
} catch (final Exception e) {
e.printStackTrace();
customItemResolver = null;
}
execTimer.mark("Init(CustomItemResolver)");
jails = new Jails(this);
confList.add(jails);
execTimer.mark("Init(Jails)");
EconomyLayers.onEnable(this);
//Spawner item provider only uses one but it's here for legacy...
spawnerItemProvider = new BlockMetaSpawnerItemProvider();
//Spawner block providers
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_12_0_R01)) {
spawnerBlockProvider = new ReflSpawnerBlockProvider();
} else {
spawnerBlockProvider = new BukkitSpawnerBlockProvider();
}
//Spawn Egg Providers
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_9_R01)) {
spawnEggProvider = new LegacySpawnEggProvider();
} else if (VersionUtil.getServerBukkitVersion().isLowerThanOrEqualTo(VersionUtil.v1_12_2_R01)) {
spawnEggProvider = new ReflSpawnEggProvider();
} else {
spawnEggProvider = new FlatSpawnEggProvider();
}
//Potion Meta Provider
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_9_R01)) {
potionMetaProvider = new LegacyPotionMetaProvider();
} else {
potionMetaProvider = new BasePotionDataProvider();
}
//Server State Provider
//Container Provider
if (PaperLib.isPaper() && VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_15_2_R01)) {
serverStateProvider = new PaperServerStateProvider();
containerProvider = new PaperContainerProvider();
serializationProvider = new PaperSerializationProvider();
} else {
serverStateProvider = new ReflServerStateProvider();
}
//Event Providers
if (PaperLib.isPaper()) {
try {
Class.forName("com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent");
recipeBookEventProvider = new PaperRecipeBookListener(event -> {
if (this.getUser(((PlayerEvent) event).getPlayer()).isRecipeSee()) {
((Cancellable) event).setCancelled(true);
}
});
} catch (final ClassNotFoundException ignored) {
}
}
//Known Commands Provider
if (PaperLib.isPaper() && VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_11_2_R01)) {
knownCommandsProvider = new PaperKnownCommandsProvider();
} else {
knownCommandsProvider = new ReflKnownCommandsProvider();
}
// Command aliases provider
formattedCommandAliasProvider = new ReflFormattedCommandAliasProvider(PaperLib.isPaper());
// Material Tag Providers
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_13_0_R01)) {
materialTagProvider = PaperLib.isPaper() ? new PaperMaterialTagProvider() : new BukkitMaterialTagProvider();
}
// Sync Commands Provider
syncCommandsProvider = new ReflSyncCommandsProvider();
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_14_4_R01)) {
persistentDataProvider = new ModernPersistentDataProvider(this);
} else {
persistentDataProvider = new ReflPersistentDataProvider(this);
}
onlineModeProvider = new ReflOnlineModeProvider();
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_11_2_R01)) {
unbreakableProvider = new ModernItemUnbreakableProvider();
} else {
unbreakableProvider = new LegacyItemUnbreakableProvider();
}
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_17_1_R01)) {
worldInfoProvider = new ModernDataWorldInfoProvider();
} else if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_16_5_R01)) {
worldInfoProvider = new ReflDataWorldInfoProvider();
} else {
worldInfoProvider = new FixedHeightWorldInfoProvider();
}
execTimer.mark("Init(Providers)");
reload();
// The item spawn blacklist is loaded with all other settings, before the item
// DB, but it depends on the item DB, so we need to reload it again here:
((Settings) settings)._lateLoadItemSpawnBlacklist();
backup = new Backup(this);
permissionsHandler = new PermissionsHandler(this, settings.useBukkitPermissions());
alternativeCommandsHandler = new AlternativeCommandsHandler(this);
@@ -358,8 +452,18 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
PermissionsDefaults.registerAllBackDefaults();
PermissionsDefaults.registerAllHatDefaults();
updateChecker = new UpdateChecker(this);
runTaskAsynchronously(() -> {
getLogger().log(Level.INFO, tl("versionFetching"));
for (String str : updateChecker.getVersionMessages(false, true)) {
getLogger().log(getSettings().isUpdateCheckEnabled() ? Level.WARNING : Level.INFO, str);
}
});
metrics = new MetricsWrapper(this, 858, true);
execTimer.mark("Init(External)");
final String timeroutput = execTimer.end();
if (getSettings().isDebug()) {
LOGGER.log(Level.INFO, "Essentials load {0}", timeroutput);
@@ -435,7 +539,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
user.sendMessage(tl("unvanishedReload"));
}
if (stopping) {
user.setLastLocation();
user.setLogoutLocation();
if (!user.isHidden()) {
user.setLastLogout(System.currentTimeMillis());
}
@@ -487,6 +591,21 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
registerListeners(pm);
}
private IEssentialsCommand loadCommand(final String path, final String name, final IEssentialsModule module, final ClassLoader classLoader) throws Exception {
if (commandMap.containsKey(name)) {
return commandMap.get(name);
}
final IEssentialsCommand cmd = (IEssentialsCommand) classLoader.loadClass(path + name).getDeclaredConstructor().newInstance();
cmd.setEssentials(this);
cmd.setEssentialsModule(module);
commandMap.put(name, cmd);
return cmd;
}
public Map<String, IEssentialsCommand> getCommandMap() {
return commandMap;
}
@Override
public List<String> onTabComplete(final CommandSender sender, final Command command, final String commandLabel, final String[] args) {
return onTabCompleteEssentials(sender, command, commandLabel, args, Essentials.class.getClassLoader(),
@@ -498,10 +617,10 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
final ClassLoader classLoader, final String commandPath, final String permissionPrefix,
final IEssentialsModule module) {
if (!getSettings().isCommandOverridden(command.getName()) && (!commandLabel.startsWith("e") || commandLabel.equalsIgnoreCase(command.getName()))) {
final PluginCommand pc = alternativeCommandsHandler.getAlternative(commandLabel);
if (pc != null) {
final Command pc = alternativeCommandsHandler.getAlternative(commandLabel);
if (pc instanceof PluginCommand) {
try {
final TabCompleter completer = pc.getTabCompleter();
final TabCompleter completer = ((PluginCommand) pc).getTabCompleter();
if (completer != null) {
return completer.onTabComplete(cSender, command, commandLabel, args);
}
@@ -522,14 +641,18 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
// Check for disabled commands
if (getSettings().isCommandDisabled(commandLabel)) {
if (getKnownCommandsProvider().getKnownCommands().containsKey(commandLabel)) {
final Command newCmd = getKnownCommandsProvider().getKnownCommands().get(commandLabel);
if (!(newCmd instanceof PluginIdentifiableCommand) || ((PluginIdentifiableCommand) newCmd).getPlugin() != this) {
return newCmd.tabComplete(cSender, commandLabel, args);
}
}
return Collections.emptyList();
}
final IEssentialsCommand cmd;
try {
cmd = (IEssentialsCommand) classLoader.loadClass(commandPath + command.getName()).newInstance();
cmd.setEssentials(this);
cmd.setEssentialsModule(module);
cmd = loadCommand(commandPath, command.getName(), module, classLoader);
} catch (final Exception ex) {
sender.sendMessage(tl("commandNotLoaded", commandLabel));
LOGGER.log(Level.SEVERE, tl("commandNotLoaded", commandLabel), ex);
@@ -574,16 +697,19 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
public boolean onCommandEssentials(final CommandSender cSender, final Command command, final String commandLabel, final String[] args, final ClassLoader classLoader, final String commandPath, final String permissionPrefix, final IEssentialsModule module) {
// Allow plugins to override the command via onCommand
if (!getSettings().isCommandOverridden(command.getName()) && (!commandLabel.startsWith("e") || commandLabel.equalsIgnoreCase(command.getName()))) {
final PluginCommand pc = alternativeCommandsHandler.getAlternative(commandLabel);
if (getSettings().isDebug()) {
LOGGER.log(Level.INFO, "Searching for alternative to: " + commandLabel);
}
final Command pc = alternativeCommandsHandler.getAlternative(commandLabel);
if (pc != null) {
alternativeCommandsHandler.executed(commandLabel, pc);
try {
return pc.execute(cSender, commandLabel, args);
pc.execute(cSender, commandLabel, args);
} catch (final Exception ex) {
Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
cSender.sendMessage(tl("internalError"));
return true;
}
return true;
}
}
@@ -621,15 +747,19 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
// Check for disabled commands
if (getSettings().isCommandDisabled(commandLabel)) {
if (getKnownCommandsProvider().getKnownCommands().containsKey(commandLabel)) {
final Command newCmd = getKnownCommandsProvider().getKnownCommands().get(commandLabel);
if (!(newCmd instanceof PluginIdentifiableCommand) || !isEssentialsPlugin(((PluginIdentifiableCommand) newCmd).getPlugin())) {
return newCmd.execute(cSender, commandLabel, args);
}
}
sender.sendMessage(tl("commandDisabled", commandLabel));
return true;
}
final IEssentialsCommand cmd;
try {
cmd = (IEssentialsCommand) classLoader.loadClass(commandPath + command.getName()).newInstance();
cmd.setEssentials(this);
cmd.setEssentialsModule(module);
cmd = loadCommand(commandPath, command.getName(), module, classLoader);
} catch (final Exception ex) {
sender.sendMessage(tl("commandNotLoaded", commandLabel));
LOGGER.log(Level.SEVERE, tl("commandNotLoaded", commandLabel), ex);
@@ -645,7 +775,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
if (user != null && user.isJailed() && !user.isAuthorized(cmd, "essentials.jail.allow.")) {
if (user.getJailTimeout() > 0) {
user.sendMessage(tl("playerJailedFor", user.getName(), DateUtil.formatDateDiff(user.getJailTimeout())));
user.sendMessage(tl("playerJailedFor", user.getName(), user.getFormattedJailTime()));
} else {
user.sendMessage(tl("jailMessage"));
}
@@ -663,8 +793,17 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
} catch (final NoChargeException | QuietAbortException ex) {
return true;
} catch (final NotEnoughArgumentsException ex) {
sender.sendMessage(command.getDescription());
sender.sendMessage(command.getUsage().replaceAll("<command>", commandLabel));
if (getSettings().isVerboseCommandUsages() && !cmd.getUsageStrings().isEmpty()) {
sender.sendMessage(tl("commandHelpLine1", commandLabel));
sender.sendMessage(tl("commandHelpLine2", command.getDescription()));
sender.sendMessage(tl("commandHelpLine3"));
for (Map.Entry<String, String> usage : cmd.getUsageStrings().entrySet()) {
sender.sendMessage(tl("commandHelpLineUsage", usage.getKey().replace("<command>", commandLabel), usage.getValue()));
}
} else {
sender.sendMessage(command.getDescription());
sender.sendMessage(command.getUsage().replace("<command>", commandLabel));
}
if (!ex.getMessage().isEmpty()) {
sender.sendMessage(ex.getMessage());
}
@@ -685,6 +824,10 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
}
}
private boolean isEssentialsPlugin(Plugin plugin) {
return plugin.getDescription().getMain().contains("com.earth2me.essentials") || plugin.getDescription().getMain().contains("net.essentialsx");
}
public void cleanupOpenInventories() {
for (final User user : getOnlineUsers()) {
if (user.isRecipeSee()) {
@@ -743,6 +886,11 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
return randomTeleport;
}
@Override
public UpdateChecker getUpdateChecker() {
return updateChecker;
}
@Deprecated
@Override
public User getUser(final Object base) {
@@ -789,6 +937,94 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
return user;
}
@Override
public User matchUser(final Server server, final User sourceUser, final String searchTerm, final Boolean getHidden, final boolean getOffline) throws PlayerNotFoundException {
final User user;
Player exPlayer;
try {
exPlayer = server.getPlayer(UUID.fromString(searchTerm));
} catch (final IllegalArgumentException ex) {
if (getOffline) {
exPlayer = server.getPlayerExact(searchTerm);
} else {
exPlayer = server.getPlayer(searchTerm);
}
}
if (exPlayer != null) {
user = getUser(exPlayer);
} else {
user = getUser(searchTerm);
}
if (user != null) {
if (!getOffline && !user.getBase().isOnline()) {
throw new PlayerNotFoundException();
}
if (getHidden || canInteractWith(sourceUser, user)) {
return user;
} else { // not looking for hidden and cannot interact (i.e is hidden)
if (getOffline && user.getName().equalsIgnoreCase(searchTerm)) { // if looking for offline and got an exact match
return user;
}
}
throw new PlayerNotFoundException();
}
final List<Player> matches = server.matchPlayer(searchTerm);
if (matches.isEmpty()) {
final String matchText = searchTerm.toLowerCase(Locale.ENGLISH);
for (final User userMatch : getOnlineUsers()) {
if (getHidden || canInteractWith(sourceUser, userMatch)) {
final String displayName = FormatUtil.stripFormat(userMatch.getDisplayName()).toLowerCase(Locale.ENGLISH);
if (displayName.contains(matchText)) {
return userMatch;
}
}
}
} else {
for (final Player player : matches) {
final User userMatch = getUser(player);
if (userMatch.getDisplayName().startsWith(searchTerm) && (getHidden || canInteractWith(sourceUser, userMatch))) {
return userMatch;
}
}
final User userMatch = getUser(matches.get(0));
if (getHidden || canInteractWith(sourceUser, userMatch)) {
return userMatch;
}
}
throw new PlayerNotFoundException();
}
@Override
public boolean canInteractWith(final CommandSource interactor, final User interactee) {
if (interactor == null) {
return !interactee.isHidden();
}
if (interactor.isPlayer()) {
return canInteractWith(getUser(interactor.getPlayer()), interactee);
}
return true; // console
}
@Override
public boolean canInteractWith(final User interactor, final User interactee) {
if (interactor == null) {
return !interactee.isHidden();
}
if (interactor.equals(interactee)) {
return true;
}
return interactor.getBase().canSee(interactee.getBase());
}
//This will create a new user if there is not a match.
@Override
public User getUser(final Player base) {
@@ -947,6 +1183,11 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
return userMap;
}
@Override
public BalanceTop getBalanceTop() {
return balanceTop;
}
@Override
public I18n getI18n() {
return i18n;
@@ -957,6 +1198,11 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
return timer;
}
@Override
public MailService getMail() {
return mail;
}
@Override
public List<String> getVanishedPlayers() {
return Collections.unmodifiableList(new ArrayList<>(vanishedPlayers));
@@ -1011,6 +1257,10 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
return serverStateProvider;
}
public MaterialTagProvider getMaterialTagProvider() {
return materialTagProvider;
}
@Override
public ContainerProvider getContainerProvider() {
return containerProvider;
@@ -1021,6 +1271,46 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
return knownCommandsProvider;
}
@Override
public SerializationProvider getSerializationProvider() {
return serializationProvider;
}
@Override
public FormattedCommandAliasProvider getFormattedCommandAliasProvider() {
return formattedCommandAliasProvider;
}
@Override
public SyncCommandsProvider getSyncCommandsProvider() {
return syncCommandsProvider;
}
@Override
public PersistentDataProvider getPersistentDataProvider() {
return persistentDataProvider;
}
@Override
public ReflOnlineModeProvider getOnlineModeProvider() {
return onlineModeProvider;
}
@Override
public ItemUnbreakableProvider getItemUnbreakableProvider() {
return unbreakableProvider;
}
@Override
public WorldInfoProvider getWorldInfoProvider() {
return worldInfoProvider;
}
@Override
public PluginCommand getPluginCommand(final String cmd) {
return this.getCommand(cmd);
}
private AbstractItemDb getItemDbFromConfig() {
final String setting = settings.getItemDbType();
@@ -1049,25 +1339,6 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
@EventHandler(priority = EventPriority.LOW)
public void onWorldLoad(final WorldLoadEvent event) {
PermissionsDefaults.registerBackDefaultFor(event.getWorld());
ess.getJails().onReload();
ess.getWarps().reloadConfig();
for (final IConf iConf : ((Essentials) ess).confList) {
if (iConf instanceof IEssentialsModule) {
iConf.reloadConfig();
}
}
}
@EventHandler(priority = EventPriority.LOW)
public void onWorldUnload(final WorldUnloadEvent event) {
ess.getJails().onReload();
ess.getWarps().reloadConfig();
for (final IConf iConf : ((Essentials) ess).confList) {
if (iConf instanceof IEssentialsModule) {
iConf.reloadConfig();
}
}
}
@Override
@@ -1,6 +1,5 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.LocationUtil;
import com.earth2me.essentials.utils.MaterialUtil;
import net.ess3.api.IEssentials;
import org.bukkit.GameMode;
@@ -24,15 +23,9 @@ public class EssentialsBlockListener implements Listener {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onBlockPlace(final BlockPlaceEvent event) {
// Do not rely on getItemInHand();
// http://leaky.bukkit.org/issues/663
final ItemStack is = LocationUtil.convertBlockToItem(event.getBlockPlaced());
final ItemStack is = event.getItemInHand();
if (is == null) {
return;
}
if (is.getType() == MaterialUtil.SPAWNER && event.getItemInHand() != null && event.getPlayer() != null && event.getItemInHand().getType() == MaterialUtil.SPAWNER) {
if (is.getType() == MaterialUtil.SPAWNER && ess.getPersistentDataProvider().getString(is, "convert") != null) {
final BlockState blockState = event.getBlockPlaced().getState();
if (blockState instanceof CreatureSpawner) {
final CreatureSpawner spawner = (CreatureSpawner) blockState;
@@ -48,15 +41,10 @@ public class EssentialsBlockListener implements Listener {
final User user = ess.getUser(event.getPlayer());
if (user.hasUnlimited(is) && user.getBase().getGameMode() == GameMode.SURVIVAL) {
class UnlimitedItemSpawnTask implements Runnable {
@Override
public void run() {
user.getBase().getInventory().addItem(is);
user.getBase().updateInventory();
}
}
ess.scheduleSyncDelayedTask(new UnlimitedItemSpawnTask());
ess.scheduleSyncDelayedTask(() -> {
user.getBase().getInventory().addItem(is);
user.getBase().updateInventory();
});
}
}
}
@@ -6,19 +6,27 @@ import com.earth2me.essentials.textreader.KeywordReplacer;
import com.earth2me.essentials.textreader.TextInput;
import com.earth2me.essentials.textreader.TextPager;
import com.earth2me.essentials.utils.DateUtil;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.LocationUtil;
import com.earth2me.essentials.utils.MaterialUtil;
import com.earth2me.essentials.utils.VersionUtil;
import io.papermc.lib.PaperLib;
import net.ess3.api.IEssentials;
import net.ess3.api.events.AfkStatusChangeEvent;
import net.ess3.provider.CommandSendListenerProvider;
import net.ess3.provider.providers.BukkitCommandSendListenerProvider;
import net.ess3.provider.providers.PaperCommandSendListenerProvider;
import net.essentialsx.api.v2.events.AsyncUserDataLoadEvent;
import org.bukkit.BanEntry;
import org.bukkit.BanList;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.FormattedCommandAlias;
import org.bukkit.command.PluginCommand;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -32,7 +40,6 @@ import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerCommandSendEvent;
import org.bukkit.event.player.PlayerEggThrowEvent;
import org.bukkit.event.player.PlayerFishEvent;
import org.bukkit.event.player.PlayerInteractEvent;
@@ -61,15 +68,19 @@ import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import static com.earth2me.essentials.I18n.tl;
public class EssentialsPlayerListener implements Listener {
public class EssentialsPlayerListener implements Listener, FakeAccessor {
private static final Logger LOGGER = Logger.getLogger("Essentials");
private final transient IEssentials ess;
private final ConcurrentHashMap<UUID, Integer> pendingMotdTasks = new ConcurrentHashMap<>();
public EssentialsPlayerListener(final IEssentials parent) {
this.ess = parent;
@@ -93,6 +104,15 @@ public class EssentialsPlayerListener implements Listener {
}
}
private static boolean isPaperCommandSendEvent() {
try {
Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent");
return true;
} catch (final ClassNotFoundException ignored) {
return false;
}
}
private static boolean isArrowPickupEvent() {
try {
Class.forName("org.bukkit.event.player.PlayerPickupArrowEvent");
@@ -115,8 +135,10 @@ public class EssentialsPlayerListener implements Listener {
ess.getServer().getPluginManager().registerEvents(new PickupListenerPre1_12(), ess);
}
if (isCommandSendEvent()) {
ess.getServer().getPluginManager().registerEvents(new CommandSendListener(), ess);
if (isPaperCommandSendEvent()) {
ess.getServer().getPluginManager().registerEvents(new PaperCommandSendListenerProvider(new CommandSendFilter()), ess);
} else if (isCommandSendEvent()) {
ess.getServer().getPluginManager().registerEvents(new BukkitCommandSendListenerProvider(new CommandSendFilter()), ess);
}
}
@@ -162,7 +184,7 @@ public class EssentialsPlayerListener implements Listener {
}
}
user.updateActivityOnInteract(true);
user.updateActivityOnChat(true);
user.setDisplayNick();
}
@@ -195,7 +217,7 @@ public class EssentialsPlayerListener implements Listener {
to.setY(from.getY());
to.setZ(from.getZ());
try {
event.setTo(LocationUtil.getSafeDestination(to));
event.setTo(LocationUtil.getSafeDestination(ess, to));
} catch (final Exception ex) {
event.setTo(to);
}
@@ -211,6 +233,11 @@ public class EssentialsPlayerListener implements Listener {
public void onPlayerQuit(final PlayerQuitEvent event) {
final User user = ess.getUser(event.getPlayer());
final Integer pendingId = pendingMotdTasks.remove(user.getUUID());
if (pendingId != null) {
ess.getScheduler().cancelTask(pendingId);
}
if (hideJoinQuitMessages() || (ess.getSettings().allowSilentJoinQuit() && user.isAuthorized("essentials.silentquit"))) {
event.setQuitMessage(null);
} else if (ess.getSettings().isCustomQuitMessage() && event.getQuitMessage() != null) {
@@ -219,7 +246,9 @@ public class EssentialsPlayerListener implements Listener {
.replace("{PLAYER}", player.getDisplayName())
.replace("{USERNAME}", player.getName())
.replace("{ONLINE}", NumberFormat.getInstance().format(ess.getOnlinePlayers().size()))
.replace("{UPTIME}", DateUtil.formatDateDiff(ManagementFactory.getRuntimeMXBean().getStartTime()));
.replace("{UPTIME}", DateUtil.formatDateDiff(ManagementFactory.getRuntimeMXBean().getStartTime()))
.replace("{PREFIX}", FormatUtil.replaceFormat(ess.getPermissionsHandler().getPrefix(player)))
.replace("{SUFFIX}", FormatUtil.replaceFormat(ess.getPermissionsHandler().getSuffix(player)));
event.setQuitMessage(msg.isEmpty() ? null : msg);
}
@@ -298,11 +327,15 @@ public class EssentialsPlayerListener implements Listener {
user.startTransaction();
final String lastAccountName = user.getLastAccountName(); // For comparison
user.setLastAccountName(user.getBase().getName());
user.setLastLogin(currentTime);
user.setDisplayNick();
updateCompass(user);
// Check for new username. If they don't want the message, let's just say it's false.
final boolean newUsername = ess.getSettings().isCustomNewUsernameMessage() && lastAccountName != null && !lastAccountName.equals(user.getBase().getName());
if (!ess.getVanishedPlayersNew().isEmpty() && !user.isAuthorized("essentials.vanish.see")) {
for (final String p : ess.getVanishedPlayersNew()) {
final Player toVanish = ess.getServer().getPlayerExact(p);
@@ -319,36 +352,48 @@ public class EssentialsPlayerListener implements Listener {
user.getBase().setSleepingIgnored(true);
}
final String effectiveMessage;
if (ess.getSettings().allowSilentJoinQuit() && (user.isAuthorized("essentials.silentjoin") || user.isAuthorized("essentials.silentjoin.vanish"))) {
if (user.isAuthorized("essentials.silentjoin.vanish")) {
user.setVanished(true);
}
effectiveMessage = null;
} else if (message == null || hideJoinQuitMessages()) {
//NOOP
effectiveMessage = null;
} else if (ess.getSettings().isCustomJoinMessage()) {
final String msg = ess.getSettings().getCustomJoinMessage()
final String msg = (newUsername ? ess.getSettings().getCustomNewUsernameMessage() : ess.getSettings().getCustomJoinMessage())
.replace("{PLAYER}", player.getDisplayName()).replace("{USERNAME}", player.getName())
.replace("{UNIQUE}", NumberFormat.getInstance().format(ess.getUserMap().getUniqueUsers()))
.replace("{ONLINE}", NumberFormat.getInstance().format(ess.getOnlinePlayers().size()))
.replace("{UPTIME}", DateUtil.formatDateDiff(ManagementFactory.getRuntimeMXBean().getStartTime()));
.replace("{UPTIME}", DateUtil.formatDateDiff(ManagementFactory.getRuntimeMXBean().getStartTime()))
.replace("{PREFIX}", FormatUtil.replaceFormat(ess.getPermissionsHandler().getPrefix(player)))
.replace("{SUFFIX}", FormatUtil.replaceFormat(ess.getPermissionsHandler().getSuffix(player)))
.replace("{OLDUSERNAME}", lastAccountName == null ? "" : lastAccountName);
if (!msg.isEmpty()) {
ess.getServer().broadcastMessage(msg);
}
effectiveMessage = msg.isEmpty() ? null : msg;
} else if (ess.getSettings().allowSilentJoinQuit()) {
ess.getServer().broadcastMessage(message);
effectiveMessage = message;
} else {
effectiveMessage = message;
}
final int motdDelay = ess.getSettings().getMotdDelay() / 50;
final DelayMotdTask motdTask = new DelayMotdTask(user);
if (motdDelay > 0) {
ess.scheduleSyncDelayedTask(motdTask, motdDelay);
} else {
motdTask.run();
ess.runTaskAsynchronously(() -> ess.getServer().getPluginManager().callEvent(new AsyncUserDataLoadEvent(user, effectiveMessage)));
if (ess.getSettings().getMotdDelay() >= 0) {
final int motdDelay = ess.getSettings().getMotdDelay() / 50;
final DelayMotdTask motdTask = new DelayMotdTask(user);
if (motdDelay > 0) {
pendingMotdTasks.put(user.getUUID(), ess.scheduleSyncDelayedTask(motdTask, motdDelay));
} else {
motdTask.run();
}
}
if (!ess.getSettings().isCommandDisabled("mail") && user.isAuthorized("essentials.mail")) {
final List<String> mail = user.getMails();
if (mail.isEmpty()) {
if (user.getUnreadMailAmount() == 0) {
if (ess.getSettings().isNotifyNoNewMail()) {
user.sendMessage(tl("noNewMail")); // Only notify if they want us to.
}
@@ -357,9 +402,17 @@ public class EssentialsPlayerListener implements Listener {
}
}
if (user.isAuthorized("essentials.updatecheck")) {
ess.runTaskAsynchronously(() -> {
for (String str : ess.getUpdateChecker().getVersionMessages(false, false)) {
user.sendMessage(str);
}
});
}
if (user.isAuthorized("essentials.fly.safelogin")) {
user.getBase().setFallDistance(0);
if (LocationUtil.shouldFly(user.getLocation())) {
if (LocationUtil.shouldFly(ess, user.getLocation())) {
user.getBase().setAllowFlight(true);
user.getBase().setFlying(true);
if (ess.getSettings().isSendFlyEnableOnJoin()) {
@@ -398,6 +451,8 @@ public class EssentialsPlayerListener implements Listener {
@Override
public void run() {
pendingMotdTasks.remove(user.getUUID());
IText tempInput = null;
if (!ess.getSettings().isCommandDisabled("motd")) {
@@ -472,27 +527,24 @@ public class EssentialsPlayerListener implements Listener {
event.allow();
return;
}
event.disallow(Result.KICK_FULL, tl("serverFull"));
if (ess.getSettings().isCustomServerFullMessage()) {
event.disallow(Result.KICK_FULL, tl("serverFull"));
}
}
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onPlayerTeleport(final PlayerTeleportEvent event) {
final boolean backListener = ess.getSettings().registerBackInListener();
final boolean teleportInvulnerability = ess.getSettings().isTeleportInvulnerability();
if (backListener || teleportInvulnerability) {
final Player player = event.getPlayer();
if (player.hasMetadata("NPC")) {
return;
}
final User user = ess.getUser(player);
//There is TeleportCause.COMMMAND but plugins have to actively pass the cause in on their teleports.
if (user.isAuthorized("essentials.back.onteleport") && backListener && (event.getCause() == TeleportCause.PLUGIN || event.getCause() == TeleportCause.COMMAND)) {
user.setLastLocation();
}
if (teleportInvulnerability && (event.getCause() == TeleportCause.PLUGIN || event.getCause() == TeleportCause.COMMAND)) {
user.enableInvulnerabilityAfterTeleport();
}
final Player player = event.getPlayer();
if (player.hasMetadata("NPC") || !(event.getCause() == TeleportCause.PLUGIN || event.getCause() == TeleportCause.COMMAND)) {
return;
}
final User user = ess.getUser(player);
if (ess.getSettings().registerBackInListener() && user.isAuthorized("essentials.back.onteleport")) {
user.setLastLocation();
}
if (ess.getSettings().isTeleportInvulnerability()) {
user.enableInvulnerabilityAfterTeleport();
}
}
@@ -517,9 +569,30 @@ public class EssentialsPlayerListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerCommandPreprocess(final PlayerCommandPreprocessEvent event) {
final Player player = event.getPlayer();
final String cmd = event.getMessage().toLowerCase(Locale.ENGLISH).split(" ")[0].replace("/", "").toLowerCase(Locale.ENGLISH);
final int argStartIndex = event.getMessage().indexOf(" ");
final String args = argStartIndex == -1 ? "" // No arguments present
: event.getMessage().substring(argStartIndex); // arguments start at argStartIndex; substring from there.
// If the plugin command does not exist, check if it is an alias from commands.yml
if (ess.getServer().getPluginCommand(cmd) == null) {
final Command knownCommand = ess.getKnownCommandsProvider().getKnownCommands().get(cmd);
if (knownCommand instanceof FormattedCommandAlias) {
final FormattedCommandAlias command = (FormattedCommandAlias) knownCommand;
for (String fullCommand : ess.getFormattedCommandAliasProvider().createCommands(command, event.getPlayer(), args.split(" "))) {
handlePlayerCommandPreprocess(event, fullCommand);
}
return;
}
}
// Handle the command given from the event.
handlePlayerCommandPreprocess(event, cmd + args);
}
public void handlePlayerCommandPreprocess(final PlayerCommandPreprocessEvent event, final String effectiveCommand) {
final Player player = event.getPlayer();
final String cmd = effectiveCommand.toLowerCase(Locale.ENGLISH).split(" ")[0].replace("/", "").toLowerCase(Locale.ENGLISH);
final PluginCommand pluginCommand = ess.getServer().getPluginCommand(cmd);
if (ess.getSettings().getSocialSpyCommands().contains(cmd) || ess.getSettings().getSocialSpyCommands().contains("*")) {
@@ -572,12 +645,12 @@ public class EssentialsPlayerListener implements Listener {
user.updateActivityOnInteract(broadcast);
}
if (ess.getSettings().isCommandCooldownsEnabled() && pluginCommand != null
if (ess.getSettings().isCommandCooldownsEnabled()
&& !user.isAuthorized("essentials.commandcooldowns.bypass")) {
final int argStartIndex = event.getMessage().indexOf(" ");
final int argStartIndex = effectiveCommand.indexOf(" ");
final String args = argStartIndex == -1 ? "" // No arguments present
: " " + event.getMessage().substring(argStartIndex); // arguments start at argStartIndex; substring from there.
final String fullCommand = pluginCommand.getName() + args;
: " " + effectiveCommand.substring(argStartIndex); // arguments start at argStartIndex; substring from there.
final String fullCommand = pluginCommand == null ? effectiveCommand : pluginCommand.getName() + args;
// Used to determine whether a user already has an existing cooldown
// If so, no need to check for (and write) new ones.
@@ -674,6 +747,9 @@ public class EssentialsPlayerListener implements Listener {
switch (event.getAction()) {
case RIGHT_CLICK_BLOCK:
if (!event.isCancelled() && MaterialUtil.isBed(event.getClickedBlock().getType()) && ess.getSettings().getUpdateBedAtDaytime()) {
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_13_2_R01) && ((org.bukkit.block.data.type.Bed) event.getClickedBlock().getBlockData()).isOccupied()) {
break;
}
final User player = ess.getUser(event.getPlayer());
if (player.isAuthorized("essentials.sethome.bed") && player.getWorld().getEnvironment().equals(World.Environment.NORMAL)) {
player.getBase().setBedSpawnLocation(event.getClickedBlock().getLocation());
@@ -782,8 +858,8 @@ public class EssentialsPlayerListener implements Listener {
clickedInventory = event.getRawSlot() < top.getSize() ? top : event.getView().getBottomInventory();
}
final User user = ess.getUser((Player) event.getWhoClicked());
if (type == InventoryType.PLAYER) {
final User user = ess.getUser((Player) event.getWhoClicked());
final InventoryHolder invHolder = top.getHolder();
if (invHolder instanceof HumanEntity) {
final User invOwner = ess.getUser((Player) invHolder);
@@ -793,21 +869,18 @@ public class EssentialsPlayerListener implements Listener {
}
}
} else if (type == InventoryType.ENDER_CHEST) {
final User user = ess.getUser((Player) event.getWhoClicked());
if (user.isEnderSee() && !user.isAuthorized("essentials.enderchest.modify")) {
event.setCancelled(true);
refreshPlayer = user.getBase();
}
} else if (type == InventoryType.WORKBENCH) {
final User user = ess.getUser((Player) event.getWhoClicked());
if (user.isRecipeSee()) {
event.setCancelled(true);
refreshPlayer = user.getBase();
}
} else if (type == InventoryType.CHEST && top.getSize() == 9) {
final User user = ess.getUser((Player) event.getWhoClicked());
final InventoryHolder invHolder = top.getHolder();
if (invHolder instanceof HumanEntity && user.isInvSee()) {
if (invHolder instanceof HumanEntity && user.isInvSee() && event.getClick() != ClickType.MIDDLE) {
event.setCancelled(true);
refreshPlayer = user.getBase();
}
@@ -815,7 +888,8 @@ public class EssentialsPlayerListener implements Listener {
if (ess.getSettings().isDirectHatAllowed() && event.getClick() == ClickType.LEFT && event.getSlot() == 39
&& event.getCursor().getType() != Material.AIR && event.getCursor().getType().getMaxDurability() == 0
&& !MaterialUtil.isSkull(event.getCursor().getType())
&& ess.getUser(event.getWhoClicked()).isAuthorized("essentials.hat") && !ess.getUser(event.getWhoClicked()).isAuthorized("essentials.hat.prevent-type." + event.getCursor().getType().name().toLowerCase())) {
&& user.isAuthorized("essentials.hat") && !user.isAuthorized("essentials.hat.prevent-type." + event.getCursor().getType().name().toLowerCase())
&& !isPreventBindingHat(user, (PlayerInventory) clickedInventory)) {
event.setCancelled(true);
final PlayerInventory inv = (PlayerInventory) clickedInventory;
final ItemStack head = inv.getHelmet();
@@ -829,6 +903,14 @@ public class EssentialsPlayerListener implements Listener {
}
}
private boolean isPreventBindingHat(User user, PlayerInventory inventory) {
if (VersionUtil.getServerBukkitVersion().isHigherThan(VersionUtil.v1_9_4_R01)) {
final ItemStack head = inventory.getHelmet();
return head != null && head.getEnchantments().containsKey(Enchantment.BINDING_CURSE) && !user.isAuthorized("essentials.hat.ignore-binding");
}
return false;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onInventoryCloseEvent(final InventoryCloseEvent event) {
Player refreshPlayer = null;
@@ -902,15 +984,14 @@ public class EssentialsPlayerListener implements Listener {
}
}
private final class CommandSendListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL)
public void onCommandSend(final PlayerCommandSendEvent event) {
final User user = ess.getUser(event.getPlayer());
private final class CommandSendFilter implements CommandSendListenerProvider.Filter {
@Override
public Predicate<String> apply(Player player) {
final User user = ess.getUser(player);
final Set<PluginCommand> checked = new HashSet<>();
final Set<PluginCommand> toRemove = new HashSet<>();
event.getCommands().removeIf(label -> {
return label -> {
if (isEssentialsCommand(label)) {
final PluginCommand command = ess.getServer().getPluginCommand(label);
if (!checked.contains(command)) {
@@ -922,25 +1003,26 @@ public class EssentialsPlayerListener implements Listener {
return toRemove.contains(command);
}
return false;
});
if (ess.getSettings().isDebug()) {
ess.getLogger().info("Removed commands: " + toRemove.toString());
}
};
}
/**
* Returns true if all of the following are true:
* - The command is a plugin command
* - The plugin command is from a plugin in an essentials-controlled package
* - The plugin command is from an official EssentialsX plugin or addon
* - There is no known alternative OR the alternative is overridden by Essentials
*/
private boolean isEssentialsCommand(final String label) {
final PluginCommand command = ess.getServer().getPluginCommand(label);
return command != null
&& (command.getPlugin() == ess || command.getPlugin().getClass().getName().startsWith("com.earth2me.essentials"))
&& (ess.getSettings().isCommandOverridden(label) || (ess.getAlternativeCommandsHandler().getAlternative(label) == null));
&& (command.getPlugin() == ess || command.getPlugin().getClass().getName().startsWith("com.earth2me.essentials") || command.getPlugin().getClass().getName().startsWith("net.essentialsx"))
&& (ess.getSettings().isCommandOverridden(label) || (ess.getAlternativeCommandsHandler().getAlternative(label) == null));
}
}
@Override
public void getUser(Player player) {
ess.getUser(player);
}
}
@@ -1,6 +1,7 @@
package com.earth2me.essentials;
import com.earth2me.essentials.register.payment.Methods;
import com.earth2me.essentials.economy.EconomyLayer;
import com.earth2me.essentials.economy.EconomyLayers;
import net.ess3.api.IEssentials;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
@@ -25,8 +26,11 @@ public class EssentialsPluginListener implements Listener, IConf {
ess.getPermissionsHandler().setUseSuperperms(ess.getSettings().useBukkitPermissions());
ess.getPermissionsHandler().checkPermissions();
ess.getAlternativeCommandsHandler().addPlugin(event.getPlugin());
if (!Methods.hasMethod() && Methods.setMethod(ess.getServer().getPluginManager())) {
ess.getLogger().log(Level.INFO, "Payment method found (" + Methods.getMethod().getLongName() + " version: " + Methods.getMethod().getVersion() + ")");
if (EconomyLayers.isServerStarted()) {
final EconomyLayer layer = EconomyLayers.onPluginEnable(event.getPlugin());
if (layer != null) {
ess.getLogger().log(Level.INFO, "Essentials found a compatible payment resolution method: " + layer.getName() + " (v" + layer.getPluginVersion() + ")!");
}
}
}
@@ -37,10 +41,13 @@ public class EssentialsPluginListener implements Listener, IConf {
}
ess.getPermissionsHandler().checkPermissions();
ess.getAlternativeCommandsHandler().removePlugin(event.getPlugin());
// Check to see if the plugin thats being disabled is the one we are using
if (Methods.hasMethod() && Methods.checkDisabled(event.getPlugin())) {
Methods.reset();
ess.getLogger().log(Level.INFO, "Payment method was disabled. No longer accepting payments.");
if (EconomyLayers.onPluginDisable(event.getPlugin())) {
final EconomyLayer layer = EconomyLayers.getSelectedLayer();
if (layer != null) {
ess.getLogger().log(Level.INFO, "Essentials found a new compatible payment resolution method: " + layer.getName() + " (v" + layer.getPluginVersion() + ")!");
} else {
ess.getLogger().log(Level.INFO, "Active payment resolution method has been disabled! Falling back to Essentials' default payment resolution system!");
}
}
}
@@ -44,6 +44,7 @@ public class EssentialsTimer implements Runnable {
}
lastPoll = startTime;
int count = 0;
onlineUsers.clear();
for (final Player player : ess.getOnlinePlayers()) {
count++;
if (skip1 > 0) {
@@ -1,18 +1,21 @@
package com.earth2me.essentials;
import com.earth2me.essentials.config.ConfigurateUtil;
import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.config.EssentialsUserConfiguration;
import com.earth2me.essentials.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 com.google.common.io.Files;
import com.google.gson.reflect.TypeToken;
import net.ess3.api.IEssentials;
import net.essentialsx.api.v2.services.mail.MailMessage;
import org.bukkit.BanList;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.spongepowered.configurate.CommentedConfigurationNode;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
@@ -24,6 +27,7 @@ import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.DigestInputStream;
import java.security.MessageDigest;
@@ -37,6 +41,11 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
@@ -52,14 +61,14 @@ public class EssentialsUpgrade {
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;
private final transient EssentialsConfiguration 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 = new EssentialsConfiguration(new File(ess.getDataFolder(), "upgrades-done.yml"));
doneFile.load();
}
@@ -93,13 +102,13 @@ public class EssentialsUpgrade {
countFiles++;
final String name = string.substring(0, string.length() - 4);
final EssentialsUserConf config;
final EssentialsUserConfiguration 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);
final EssentialsConfiguration conf = new EssentialsConfiguration(file);
conf.load();
conf.setProperty("lastAccountName", name);
conf.save();
@@ -130,8 +139,8 @@ public class EssentialsUpgrade {
}
if (uuid != null) {
conf.forceSave();
config = new EssentialsUserConf(name, uuid, new File(userdir, uuid + ".yml"));
conf.blockingSave();
config = new EssentialsUserConfiguration(name, uuid, new File(userdir, uuid + ".yml"));
config.convertLegacyFile();
ess.getUserMap().trackUUID(uuid, name, false);
continue;
@@ -146,6 +155,237 @@ public class EssentialsUpgrade {
ess.getLogger().info("To rerun the conversion type /essentials uuidconvert");
}
public void convertMailList() {
if (doneFile.getBoolean("updateUsersMailList", false)) {
return;
}
final File userdataFolder = new File(ess.getDataFolder(), "userdata");
if (!userdataFolder.exists() || !userdataFolder.isDirectory()) {
return;
}
final File[] userFiles = userdataFolder.listFiles();
for (File file : userFiles) {
if (!file.isFile() || !file.getName().endsWith(".yml")) {
continue;
}
final EssentialsConfiguration config = new EssentialsConfiguration(file);
try {
config.load();
if (config.hasProperty("mail") && config.isList("mail")) {
final ArrayList<MailMessage> messages = new ArrayList<>();
for (String mailStr : Collections.synchronizedList(config.getList("mail", String.class))) {
if (mailStr == null) {
continue;
}
messages.add(new MailMessage(false, true, null, null, 0L, 0L, mailStr));
}
config.removeProperty("mail");
config.setExplicitList("mail", messages, new TypeToken<List<MailMessage>>() {}.getType());
config.blockingSave();
}
} catch (RuntimeException ex) {
LOGGER.log(Level.INFO, "File: " + file);
throw ex;
}
}
doneFile.setProperty("updateUsersMailList", true);
doneFile.save();
LOGGER.info("Done converting mail list.");
}
public void convertStupidCamelCaseUserdataKeys() {
if (doneFile.getBoolean("updateUsersStupidLegacyPathNames", false)) {
return;
}
LOGGER.info("Attempting to migrate legacy userdata keys to Configurate");
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 EssentialsConfiguration config = new EssentialsConfiguration(file);
try {
config.load();
if (config.hasProperty("muteReason")) {
final String reason = config.getString("muteReason", null);
config.removeProperty("muteReason");
config.setProperty("mute-reason", reason);
}
if (config.hasProperty("ipAddress")) {
final String ip = config.getString("ipAddress", null);
config.removeProperty("ipAddress");
config.setProperty("ip-address", ip);
}
if (config.hasProperty("lastAccountName")) {
final String name = config.getString("lastAccountName", null);
config.removeProperty("lastAccountName");
config.setProperty("last-account-name", name);
}
if (config.hasProperty("acceptingPay")) {
final boolean isPay = config.getBoolean("acceptingPay", true);
config.removeProperty("acceptingPay");
config.setProperty("accepting-pay", isPay);
}
config.blockingSave();
} catch (final RuntimeException ex) {
LOGGER.log(Level.INFO, "File: " + file);
throw ex;
}
}
doneFile.setProperty("updateUsersStupidLegacyPathNames", true);
doneFile.save();
LOGGER.info("Done converting legacy userdata keys to Configurate.");
}
/**
* This migration cleans up unused files left behind by the chaos resulting from Vault's questionable economy
* integration, and upstream Essentials' rushed and untested 1.7.10 UUID support.
* Both of these have been fixed in EssentialsX as of 2.18.x and 2.19.x respectively, but the leftover userdata
* files can reach into the tens of thousands and can cause excessive memory and storage usage, so this migration
* relocates these files to a backup folder to be removed by the server owner at a later date.
* <p>
* To quote JRoy, who suffered immensely while trying to debug and fix various related issues:
* <p>
* "Essentials decided when adding its initial support for UUIDs, it wanted an implementation which would cause
* eternal pain and suffering for any person who dared touch any of the code in the future. This code that was made
* was so bad, it managed to somehow not maintain any actual UUID support for any external integrations/plugins.
* Up until 2.19.0 and 2.18.0 respectively, our Vault integration and entire Economy system was entirely based off
* username strings, and thanks to Vault being a flawed standard, for some reason exposes account create to third
* party plugins rather than letting the implementation handle it. That doesn't seem like a huge problem at the
* surface, but there was one small problem: whoever made the Vault integration for Essentials suffered a stroke in
* the process of creating it. The implementation for the createAccount method, regardless of whether it was an
* actual player or an NPC account (which the Vault spec NEVER accounted for but plugins just have to guess when
* to support them), it would always create an NPC account. This caused any plugin integrating with Vault, creating
* NPC accounts for pretty much every single player on the server. It still, to this day, amazes me how nobody saw
* this code and didn't die without rewriting it; Or how everybody simply didn't stop using this plugin because how
* awful that godforsaken code was. Anyways, this upgrade does its best to delete NPC accounts created by the
* horrible economy code, as any operation which loads all user data into memory will load all these NPC accounts
* and spam the console with warnings."
*/
public void purgeBrokenNpcAccounts() {
if (doneFile.getBoolean("updatePurgeBrokenNpcAccounts", false)) {
return;
}
final File userdataFolder = new File(ess.getDataFolder(), "userdata");
if (!userdataFolder.exists() || !userdataFolder.isDirectory()) {
return;
}
final File[] userFiles = userdataFolder.listFiles();
if (userFiles.length == 0) {
return;
}
final File backupFolder = new File(ess.getDataFolder(), "userdata-npc-backup");
if (backupFolder.exists()) {
LOGGER.info("NPC backup folder already exists; skipping NPC purge.");
LOGGER.info("To finish purging broken NPC accounts, rename the \"plugins/Essentials/userdata-npc-backup\" folder and restart your server.");
return;
} else if (!backupFolder.mkdir()) {
LOGGER.info("Skipping NPC purge due to error creating backup folder.");
return;
}
LOGGER.info("#===========================================================================#");
LOGGER.info(" EssentialsX will now purge any NPC accounts which were incorrectly created.");
LOGGER.info(" Only NPC accounts with the default starting balance will be deleted. If");
LOGGER.info(" they turn out to be valid NPC accounts, they will be re-created as needed.");
LOGGER.info(" Any files deleted here will be backed up to the ");
LOGGER.info(" \"plugins/Essentials/userdata-npc-backup\" folder. If you notice any files");
LOGGER.info(" have been purged incorrectly, you should restore it from the backup and");
LOGGER.info(" report it to us on GitHub:");
LOGGER.info(" https://github.com/EssentialsX/Essentials/issues/new/choose");
LOGGER.info("");
LOGGER.info(" NOTE: This is a one-time process and will take several minutes if you have");
LOGGER.info(" a lot of userdata files! If you interrupt this process, EssentialsX will");
LOGGER.info(" skip the process until you rename or remove the backup folder.");
LOGGER.info("#===========================================================================#");
final int totalUserFiles = userFiles.length;
LOGGER.info("Found ~" + totalUserFiles + " files under \"plugins/Essentials/userdata\"...");
final AtomicInteger movedAccounts = new AtomicInteger(0);
final AtomicInteger totalAccounts = new AtomicInteger(0);
// Less spammy feedback for greater userdata counts, eg 100 files -> 5 seconds, 1k -> 7s, 10k -> 9s, 100k -> 11s, 1m -> 14s
final long feedbackInterval = Math.min(15, 1 + Math.round(2.1 * Math.log10(userFiles.length)));
final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
final ScheduledFuture<?> feedbackTask = executor.scheduleWithFixedDelay(
() -> LOGGER.info("Scanned " + totalAccounts.get() + "/" + totalUserFiles + " accounts; moved " + movedAccounts.get() + " accounts"),
5, feedbackInterval, TimeUnit.SECONDS);
for (final File file : userFiles) {
if (!file.isFile() || !file.getName().endsWith(".yml")) {
continue;
}
final EssentialsConfiguration config = new EssentialsConfiguration(file);
try {
totalAccounts.incrementAndGet();
config.load();
if (config.getKeys().size() > 4) {
continue;
}
if (!config.getBoolean("npc", false)) {
continue;
}
final BigDecimal money = config.getBigDecimal("money", null);
if (money == null || money.compareTo(ess.getSettings().getStartingBalance()) != 0) {
continue;
}
if (config.getKeys().size() == 4 && !config.hasProperty("last-account-name") && config.hasProperty("mail")) {
continue;
}
try {
//noinspection UnstableApiUsage
Files.move(file, new File(backupFolder, file.getName()));
movedAccounts.incrementAndGet();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error while moving NPC file", e);
}
} catch (final RuntimeException ex) {
LOGGER.log(Level.INFO, "File: " + file);
feedbackTask.cancel(false);
executor.shutdown();
throw ex;
}
}
feedbackTask.cancel(false);
executor.shutdown();
doneFile.setProperty("updatePurgeBrokenNpcAccounts", true);
doneFile.save();
LOGGER.info("#===========================================================================#");
LOGGER.info(" EssentialsX has finished purging NPC accounts.");
LOGGER.info("");
LOGGER.info(" Deleted accounts: " + movedAccounts);
LOGGER.info(" Total accounts processed: " + totalAccounts);
LOGGER.info("");
LOGGER.info(" Purged accounts have been backed up to");
LOGGER.info(" \"plugins/Essentials/userdata-npc-backup\", and can be restored from there");
LOGGER.info(" if needed. Please report any files which have been incorrectly deleted");
LOGGER.info(" to us on GitHub:");
LOGGER.info(" https://github.com/EssentialsX/Essentials/issues/new/choose");
LOGGER.info("#===========================================================================#");
}
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)) {
@@ -164,12 +404,12 @@ public class EssentialsUpgrade {
if (!file.isFile() || !file.getName().endsWith(".yml")) {
continue;
}
final EssentialsConf config = new EssentialsConf(file);
final EssentialsConfiguration config = new EssentialsConfiguration(file);
try {
config.load();
if (config.hasProperty("ignore")) {
final List<String> migratedIgnores = new ArrayList<>();
for (final String name : Collections.synchronizedList(config.getStringList("ignore"))) {
for (final String name : Collections.synchronizedList(config.getList("ignore", String.class))) {
if (name == null) {
continue;
}
@@ -184,10 +424,10 @@ public class EssentialsUpgrade {
}
config.removeProperty("ignore");
config.setProperty("ignore", migratedIgnores);
config.forceSave();
config.blockingSave();
}
} catch (final RuntimeException ex) {
LOGGER.log(Level.INFO, "File: " + file.toString());
LOGGER.log(Level.INFO, "File: " + file);
throw ex;
}
}
@@ -198,24 +438,24 @@ public class EssentialsUpgrade {
public void convertKits() {
final Kits kits = ess.getKits();
final EssentialsConf config = kits.getConfig();
final EssentialsConfiguration config = kits.getRootConfig();
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();
final CommentedConfigurationNode 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);
final Map<String, Object> legacyKits = ConfigurateUtil.getRawMap(section);
for (final Map.Entry<String, Object> entry : legacyKits.entrySet()) {
LOGGER.info("Converting " + entry.getKey());
config.set("kits." + entry.getKey(), entry.getValue());
config.setRaw("kits." + entry.getKey(), entry.getValue());
}
config.save();
@@ -237,9 +477,9 @@ public class EssentialsUpgrade {
if (!configFile.exists()) {
return;
}
final EssentialsConf conf = new EssentialsConf(configFile);
final EssentialsConfiguration conf = new EssentialsConfiguration(configFile);
conf.load();
final List<String> lines = conf.getStringList(name);
final List<String> lines = conf.getList(name, String.class);
if (lines != null && !lines.isEmpty()) {
if (!file.createNewFile()) {
throw new IOException("Failed to create file " + file);
@@ -313,12 +553,12 @@ public class EssentialsUpgrade {
if (!file.isFile() || !file.getName().endsWith(".yml")) {
continue;
}
final EssentialsConf config = new EssentialsConf(file);
final EssentialsConfiguration config = new EssentialsConfiguration(file);
try {
config.load();
if (config.hasProperty("powertools")) {
final Map<String, Object> powertools = config.getConfigurationSection("powertools").getValues(false);
if (powertools == null) {
final Map<String, Object> powertools = ConfigurateUtil.getRawMap(config.getSection("powertools"));
if (powertools.isEmpty()) {
continue;
}
for (final Map.Entry<String, Object> entry : powertools.entrySet()) {
@@ -328,10 +568,11 @@ public class EssentialsUpgrade {
powertools.put(entry.getKey(), temp);
}
}
config.forceSave();
config.setRaw("powertools", powertools);
config.blockingSave();
}
} catch (final RuntimeException ex) {
LOGGER.log(Level.INFO, "File: " + file.toString());
LOGGER.log(Level.INFO, "File: " + file);
throw ex;
}
}
@@ -353,43 +594,41 @@ public class EssentialsUpgrade {
if (!file.isFile() || !file.getName().endsWith(".yml")) {
continue;
}
final EssentialsConf config = new EssentialsConf(file);
final EssentialsConfiguration config = new EssentialsConfiguration(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);
final String defworld = config.getString("home.default", null);
final Location defloc = getFakeLocation(config.getRootNode(), "home.worlds." + defworld);
if (defloc != null) {
config.setProperty("homes.home", defloc);
}
final Set<String> worlds = config.getConfigurationSection("home.worlds").getKeys(false);
final Set<String> worlds = ConfigurateUtil.getKeys(config.getSection("home.worlds"));
Location loc;
String worldName;
if (worlds == null) {
if (worlds.isEmpty()) {
continue;
}
for (final String world : worlds) {
if (defworld.equalsIgnoreCase(world)) {
continue;
}
loc = getFakeLocation(config, "home.worlds." + world);
loc = getFakeLocation(config.getRootNode(), "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.setProperty("homes." + worldName, loc);
}
config.removeProperty("home");
config.forceSave();
config.blockingSave();
}
} catch (final RuntimeException ex) {
LOGGER.log(Level.INFO, "File: " + file.toString());
LOGGER.log(Level.INFO, "File: " + file);
throw ex;
}
}
@@ -442,7 +681,7 @@ public class EssentialsUpgrade {
return null;
}
public Location getFakeLocation(final EssentialsConf config, final String path) {
public Location getFakeLocation(final CommentedConfigurationNode config, final String path) {
final String worldName = config.getString((path != null ? path + "." : "") + "world");
if (worldName == null || worldName.isEmpty()) {
return null;
@@ -451,7 +690,8 @@ public class EssentialsUpgrade {
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));
return new Location(world, config.node("x").getDouble(0), config.node("y").getDouble(0),
config.node("z").getDouble(0), config.node("yaw").getFloat(0), config.node("pitch").getFloat(0));
}
private void deleteOldItemsCsv() {
@@ -477,7 +717,7 @@ public class EssentialsUpgrade {
final BigInteger hash = new BigInteger(1, digest.digest());
if (oldconfigs.contains(hash) && !file.delete()) {
throw new IOException("Could not delete file " + file.toString());
throw new IOException("Could not delete file " + file);
}
doneFile.setProperty("deleteOldItemsCsv", true);
doneFile.save();
@@ -494,22 +734,18 @@ public class EssentialsUpgrade {
final File configFile = new File(ess.getDataFolder(), "spawn.yml");
if (configFile.exists()) {
final EssentialsConf config = new EssentialsConf(configFile);
final EssentialsConfiguration config = new EssentialsConfiguration(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);
for (final Map.Entry<String, CommentedConfigurationNode> entry : config.getMap().entrySet()) {
final Location loc = getFakeLocation(entry.getValue(), entry.getKey());
config.setProperty(entry.getKey(), 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);
}
config.blockingSave();
}
} catch (final Exception ex) {
Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
@@ -526,22 +762,18 @@ public class EssentialsUpgrade {
final File configFile = new File(ess.getDataFolder(), "jail.yml");
if (configFile.exists()) {
final EssentialsConf config = new EssentialsConf(configFile);
final EssentialsConfiguration config = new EssentialsConfiguration(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);
for (final Map.Entry<String, CommentedConfigurationNode> entry : config.getMap().entrySet()) {
final Location loc = getFakeLocation(entry.getValue(), entry.getKey());
config.setProperty(entry.getKey(), 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);
}
config.blockingSave();
}
} catch (final Exception ex) {
Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
@@ -642,23 +874,23 @@ public class EssentialsUpgrade {
countFiles++;
final File pFile = new File(userdir, string);
final EssentialsConf conf = new EssentialsConf(pFile);
final EssentialsConfiguration conf = new EssentialsConfiguration(pFile);
conf.load();
String banReason;
final String banReason;
long banTimeout;
try {
banReason = conf.getConfigurationSection("ban").getString("reason");
} catch (final NullPointerException n) {
if (conf.hasProperty("ban.reason")) {
banReason = conf.getString("ban.reason", null);
} else {
banReason = null;
}
final String playerName = conf.getString("lastAccountName");
final String playerName = conf.getString("lastAccountName", null);
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"));
if (conf.hasProperty("ban.timeout")) {
banTimeout = Long.parseLong(conf.getString("ban.timeout", null));
} else {
banTimeout = 0L;
}
@@ -680,11 +912,7 @@ public class EssentialsUpgrade {
}
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);
}
Bukkit.getBanList(BanList.Type.NAME).addBan(playerName, banReason, banTimeout == 0 ? null : new Date(banTimeout), Console.NAME);
}
private void repairUserMap() {
@@ -777,5 +1005,8 @@ public class EssentialsUpgrade {
warnMetrics();
repairUserMap();
convertIgnoreList();
convertStupidCamelCaseUserdataKeys();
convertMailList();
purgeBrokenNpcAccounts();
}
}
@@ -11,7 +11,7 @@ import java.util.UUID;
import java.util.logging.Level;
public class EssentialsUserConf extends EssentialsConf {
public final String username;
public String username;
public final UUID uuid;
public EssentialsUserConf(final String username, final UUID uuid, final File configFile) {
@@ -20,6 +20,10 @@ public class EssentialsUserConf extends EssentialsConf {
this.uuid = uuid;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public boolean legacyFileExists() {
final File file = new File(configFile.getParentFile(), username + ".yml");
@@ -88,7 +88,9 @@ public class I18n implements net.ess3.api.II18n {
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);
if (ess == null || ess.getSettings().isDebug()) {
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);
}
}
@@ -3,15 +3,30 @@ package com.earth2me.essentials;
import com.earth2me.essentials.api.IItemDb;
import com.earth2me.essentials.api.IJails;
import com.earth2me.essentials.api.IWarps;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.commands.PlayerNotFoundException;
import com.earth2me.essentials.perm.PermissionsHandler;
import com.earth2me.essentials.updatecheck.UpdateChecker;
import net.ess3.nms.refl.providers.ReflOnlineModeProvider;
import net.ess3.provider.ContainerProvider;
import net.ess3.provider.FormattedCommandAliasProvider;
import net.ess3.provider.ItemUnbreakableProvider;
import net.ess3.provider.KnownCommandsProvider;
import net.ess3.provider.MaterialTagProvider;
import net.ess3.provider.PersistentDataProvider;
import net.ess3.provider.ServerStateProvider;
import net.ess3.provider.SerializationProvider;
import net.ess3.provider.SpawnerBlockProvider;
import net.ess3.provider.SpawnerItemProvider;
import net.ess3.provider.SyncCommandsProvider;
import net.ess3.provider.WorldInfoProvider;
import net.essentialsx.api.v2.services.BalanceTop;
import net.essentialsx.api.v2.services.mail.MailService;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
@@ -19,6 +34,7 @@ import org.bukkit.scheduler.BukkitTask;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
@@ -27,6 +43,8 @@ public interface IEssentials extends Plugin {
void reload();
Map<String, IEssentialsCommand> getCommandMap();
List<String> onTabCompleteEssentials(CommandSender sender, Command command, String commandLabel, String[] args, ClassLoader classLoader, String commandPath, String permissionPrefix, IEssentialsModule module);
boolean onCommandEssentials(CommandSender sender, Command command, String commandLabel, String[] args, ClassLoader classLoader, String commandPath, String permissionPrefix, IEssentialsModule module);
@@ -40,6 +58,12 @@ public interface IEssentials extends Plugin {
User getUser(Player base);
User matchUser(Server server, User sourceUser, String searchTerm, Boolean getHidden, boolean getOffline) throws PlayerNotFoundException;
boolean canInteractWith(CommandSource interactor, User interactee);
boolean canInteractWith(User interactor, User interactee);
I18n getI18n();
User getOfflineUser(String name);
@@ -70,6 +94,8 @@ public interface IEssentials extends Plugin {
RandomTeleport getRandomTeleport();
UpdateChecker getUpdateChecker();
BukkitTask runTaskAsynchronously(Runnable run);
BukkitTask runTaskLaterAsynchronously(Runnable run, long delay);
@@ -94,8 +120,12 @@ public interface IEssentials extends Plugin {
UserMap getUserMap();
BalanceTop getBalanceTop();
EssentialsTimer getTimer();
MailService getMail();
/**
* Get a list of players who are vanished.
*
@@ -115,7 +145,25 @@ public interface IEssentials extends Plugin {
ServerStateProvider getServerStateProvider();
MaterialTagProvider getMaterialTagProvider();
ContainerProvider getContainerProvider();
KnownCommandsProvider getKnownCommandsProvider();
SerializationProvider getSerializationProvider();
FormattedCommandAliasProvider getFormattedCommandAliasProvider();
SyncCommandsProvider getSyncCommandsProvider();
PersistentDataProvider getPersistentDataProvider();
ReflOnlineModeProvider getOnlineModeProvider();
ItemUnbreakableProvider getItemUnbreakableProvider();
WorldInfoProvider getWorldInfoProvider();
PluginCommand getPluginCommand(String cmd);
}
@@ -4,9 +4,10 @@ import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.signs.EssentialsSign;
import com.earth2me.essentials.textreader.IText;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.EventPriority;
import org.spongepowered.configurate.CommentedConfigurationNode;
import java.io.File;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.List;
@@ -17,6 +18,8 @@ import java.util.function.Predicate;
import java.util.regex.Pattern;
public interface ISettings extends IConf {
File getConfigFile();
boolean areSignsDisabled();
IText getAnnounceNewPlayerFormat();
@@ -33,6 +36,8 @@ public interface ISettings extends IConf {
String getChatFormat(String group);
String getWorldAlias(String world);
int getChatRadius();
int getNearRadius();
@@ -59,28 +64,12 @@ public interface ISettings extends IConf {
boolean getSocialSpyListenMutedPlayers();
boolean isSocialSpyMessages();
Set<String> getMuteCommands();
/**
* @Deprecated in favor of {@link Kits#getKits()}
*/
@Deprecated
ConfigurationSection getKits();
/**
* @Deprecated in favor of {@link Kits#getKit(String)}
*/
@Deprecated
Map<String, Object> getKit(String kit);
/**
* @Deprecated in favor of {@link Kits#addKit(String, List, long)}}
*/
@Deprecated
void addKit(String name, List<String> lines, long delay);
@Deprecated
ConfigurationSection getKitSection();
CommentedConfigurationNode getKitSection();
boolean isSkippingUsedOneTimeKitsFromKitList();
@@ -138,6 +127,10 @@ public interface ISettings extends IConf {
boolean isCommandDisabled(String label);
Set<String> getDisabledCommands();
boolean isVerboseCommandUsages();
boolean isCommandOverridden(String name);
boolean isDebug();
@@ -179,6 +172,8 @@ public interface ISettings extends IConf {
boolean changePlayerListName();
boolean changeTabCompleteName();
boolean isPlayerCommand(String string);
boolean useBukkitPermissions();
@@ -199,8 +194,12 @@ public interface ISettings extends IConf {
boolean cancelAfkOnInteract();
boolean cancelAfkOnChat();
boolean sleepIgnoresAfkPlayers();
boolean sleepIgnoresVanishedPlayers();
boolean isAfkListName();
String getAfkListName();
@@ -239,6 +238,8 @@ public interface ISettings extends IConf {
long getTpaAcceptCancellation();
int getTpaMaxRequests();
long getTeleportInvulnerability();
boolean isTeleportInvulnerability();
@@ -283,6 +284,12 @@ public interface ISettings extends IConf {
String getCustomQuitMessage();
String getCustomNewUsernameMessage();
boolean isCustomNewUsernameMessage();
boolean isCustomServerFullMessage();
boolean isNotifyNoNewMail();
boolean isDropItemsIfFull();
@@ -329,8 +336,12 @@ public interface ISettings extends IConf {
List<EssentialsSign> getUnprotectedSignNames();
boolean isKitAutoEquip();
boolean isPastebinCreateKit();
boolean isUseBetterKits();
boolean isAllowBulkBuySell();
boolean isAllowSellNamedItems();
@@ -349,7 +360,9 @@ public interface ISettings extends IConf {
boolean isConfirmCommandEnabledByDefault(String commandName);
boolean isTeleportBackWhenFreedFromJail();
TeleportWhenFreePolicy getTeleportWhenFreePolicy();
boolean isJailOnlineTime();
boolean isCompassTowardsHomePerm();
@@ -377,10 +390,22 @@ public interface ISettings extends IConf {
boolean infoAfterDeath();
boolean isRespawnAtBed();
boolean isUpdateCheckEnabled();
boolean showZeroBaltop();
enum KeepInvPolicy {
KEEP,
DELETE,
DROP
}
enum TeleportWhenFreePolicy {
SPAWN,
BACK,
OFF
}
}
@@ -2,19 +2,26 @@ package com.earth2me.essentials;
import com.earth2me.essentials.api.IAsyncTeleport;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.config.entities.CommandCooldown;
import net.ess3.api.ITeleport;
import net.ess3.api.MaxMoneyException;
import net.ess3.api.events.AfkStatusChangeEvent;
import net.essentialsx.api.v2.services.mail.MailMessage;
import net.essentialsx.api.v2.services.mail.MailSender;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
/**
@@ -57,8 +64,11 @@ public interface IUser {
/**
* Returns whether this user has an outstanding teleport request to deal with.
*
* @deprecated The teleport request system has been moved into a multi-user teleport request queue.
* @see IUser#hasPendingTpaRequests(boolean, boolean)
* @return whether there is a teleport request
*/
@Deprecated
boolean hasOutstandingTeleportRequest();
/**
@@ -94,6 +104,11 @@ public interface IUser {
boolean canBuild();
/**
* @deprecated The teleport request system has been moved into a multi-user teleport request queue.
* @see IUser#getNextTpaRequest(boolean, boolean, boolean)
*/
@Deprecated
long getTeleportRequestTime();
void enableInvulnerabilityAfterTeleport();
@@ -145,10 +160,24 @@ public interface IUser {
void setJail(String jail);
String getFormattedJailTime();
@Deprecated
List<String> getMails();
@Deprecated
void addMail(String mail);
void sendMail(MailSender sender, String message);
void sendMail(MailSender sender, String message, long expireAt);
ArrayList<MailMessage> getMailMessages();
void setMailList(ArrayList<MailMessage> messages);
int getMailAmount();
boolean isAfk();
@Deprecated
@@ -158,6 +187,7 @@ public interface IUser {
void setIgnoreMsg(boolean ignoreMsg);
@Deprecated
void setConfigProperty(String node, Object object);
Set<String> getConfigKeys();
@@ -166,8 +196,11 @@ public interface IUser {
Map<String, Object> getConfigMap(String node);
@Deprecated
Map<Pattern, Long> getCommandCooldowns();
List<CommandCooldown> getCooldownsList();
Date getCommandCooldownExpiry(String label);
void addCommandCooldown(Pattern pattern, Date expiresAt, boolean save);
@@ -183,8 +216,12 @@ public interface IUser {
String getName();
UUID getUUID();
String getDisplayName();
String getFormattedNickname();
String getAfkMessage();
void setAfkMessage(final String message);
@@ -210,4 +247,78 @@ public interface IUser {
Map<User, BigDecimal> getConfirmingPayments();
Block getTargetBlock(int maxDistance);
void setToggleShout(boolean toggleShout);
boolean isToggleShout();
/**
* Gets information about the most-recently-made, non-expired TPA request in the tpa queue of this {@link IUser}.
* <p>
* The TPA Queue is Last-In-First-Out queue which stores all the active pending teleport
* requests of this {@link IUser}. Timeout calculations are also done during the
* iteration process of this method, ensuring that teleport requests made past the timeout
* period are removed from queue and therefore not returned here. The maximum size of this
* queue is determined by {@link ISettings#getTpaMaxRequests()}.
*
* @param inform true if the underlying {@link IUser} should be informed if a request expires during iteration.
* @param performExpirations true if this method should not spend time validating time for all items in the queue and just return the first item in the queue.
* @param excludeHere true if /tphere requests should be ignored in fetching the next tpa request.
* @return A {@link TpaRequest} corresponding to the next available request or null if no valid request is present.
*/
@Nullable TpaRequest getNextTpaRequest(boolean inform, boolean performExpirations, boolean excludeHere);
/**
* Whether or not this {@link IUser} has any valid TPA requests in queue.
*
* @param inform true if the user should be informed if a request expires during iteration.
* @param excludeHere true if /tpahere requests should be ignored in checking if a tpa request is available.
* @return true if the user has an available pending request in queue.
*/
boolean hasPendingTpaRequests(boolean inform, boolean excludeHere);
class TpaRequest {
private final String name;
private final UUID requesterUuid;
private boolean here;
private Location location;
private long time;
public TpaRequest(String name, UUID requesterUuid) {
this.name = name;
this.requesterUuid = requesterUuid;
}
public String getName() {
return name;
}
public UUID getRequesterUuid() {
return requesterUuid;
}
public boolean isHere() {
return here;
}
public void setHere(boolean here) {
this.here = here;
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}
}
@@ -1,9 +1,10 @@
package com.earth2me.essentials;
import com.earth2me.essentials.storage.AsyncStorageObjectHolder;
import com.earth2me.essentials.config.ConfigurateUtil;
import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.config.entities.LazyLocation;
import net.ess3.api.IEssentials;
import net.ess3.api.IUser;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
@@ -23,28 +24,52 @@ import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.plugin.PluginManager;
import org.spongepowered.configurate.CommentedConfigurationNode;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.earth2me.essentials.I18n.tl;
public class Jails extends AsyncStorageObjectHolder<com.earth2me.essentials.settings.Jails> implements net.ess3.api.IJails {
private static final transient Logger LOGGER = Bukkit.getLogger();
public class Jails implements net.ess3.api.IJails {
private static final transient Logger LOGGER = Logger.getLogger("Essentials");
private static transient boolean enabled = false;
private final IEssentials ess;
private final EssentialsConfiguration config;
private final Map<String, LazyLocation> jails = new HashMap<>();
public Jails(final IEssentials ess) {
super(ess, com.earth2me.essentials.settings.Jails.class);
this.ess = ess;
this.config = new EssentialsConfiguration(new File(ess.getDataFolder(), "jail.yml"));
reloadConfig();
}
@Override
public void reloadConfig() {
synchronized (jails) {
config.load();
jails.clear();
final CommentedConfigurationNode jailsNode = config.getSection("jails");
for (final Map.Entry<String, CommentedConfigurationNode> entry : ConfigurateUtil.getMap(jailsNode).entrySet()) {
final CommentedConfigurationNode jailNode = entry.getValue();
final String worldId = jailNode.node("world").getString();
if (worldId == null || worldId.isEmpty()) {
continue;
}
jails.put(entry.getKey().toLowerCase(Locale.ENGLISH), new LazyLocation(worldId, jailNode.node("world-name").getString(""), jailNode.node("x").getDouble(), jailNode.node("y").getDouble(),
jailNode.node("z").getDouble(), jailNode.node("yaw").getFloat(), jailNode.node("pitch").getFloat()));
}
checkRegister();
}
}
private void registerListeners() {
enabled = true;
final PluginManager pluginManager = ess.getServer().getPluginManager();
@@ -55,21 +80,6 @@ public class Jails extends AsyncStorageObjectHolder<com.earth2me.essentials.sett
}
}
@Override
public File getStorageFile() {
return new File(ess.getDataFolder(), "jail.yml");
}
@Override
public void finishRead() {
checkRegister();
}
@Override
public void finishWrite() {
checkRegister();
}
public void resetListener() {
enabled = false;
checkRegister();
@@ -82,92 +92,104 @@ public class Jails extends AsyncStorageObjectHolder<com.earth2me.essentials.sett
}
@Override
public Location getJail(final String jailName) throws Exception {
acquireReadLock();
try {
if (getData().getJails() == null || jailName == null || !getData().getJails().containsKey(jailName.toLowerCase(Locale.ENGLISH))) {
public void startTransaction() {
config.startTransaction();
}
@Override
public void stopTransaction(final boolean blocking) {
config.stopTransaction(blocking);
}
@Override
public Location getJail(String jailName) throws Exception {
if (jailName == null) {
throw new Exception(tl("jailNotExist"));
}
jailName = jailName.toLowerCase(Locale.ENGLISH);
synchronized (jails) {
if (!jails.containsKey(jailName)) {
throw new Exception(tl("jailNotExist"));
}
final Location loc = getData().getJails().get(jailName.toLowerCase(Locale.ENGLISH));
if (loc == null || loc.getWorld() == null) {
throw new Exception(tl("jailNotExist"));
final Location location = jails.get(jailName).location();
if (location == null) {
throw new Exception(tl("jailWorldNotExist"));
}
return loc;
} finally {
unlock();
return location;
}
}
@Override
public Collection<String> getList() throws Exception {
acquireReadLock();
try {
if (getData().getJails() == null) {
return Collections.emptyList();
}
return new ArrayList<>(getData().getJails().keySet());
} finally {
unlock();
synchronized (jails) {
return new ArrayList<>(jails.keySet());
}
}
@Override
public void removeJail(final String jail) throws Exception {
acquireWriteLock();
try {
if (getData().getJails() == null) {
return;
public void removeJail(String jail) throws Exception {
if (jail == null) {
return;
}
jail = jail.toLowerCase(Locale.ENGLISH);
synchronized (jails) {
if (jails.remove(jail) != null) {
config.getSection("jails").node(jail).set(null);
config.save();
}
getData().getJails().remove(jail.toLowerCase(Locale.ENGLISH));
} finally {
unlock();
}
}
/**
* @deprecated This method does not use asynchronous teleportation. Use {@link Jails#sendToJail(IUser, String, CompletableFuture)}
*/
@SuppressWarnings("deprecation")
@Override
@Deprecated
public void sendToJail(final IUser user, final String jail) throws Exception {
acquireReadLock();
try {
if (user.getBase().isOnline()) {
final Location loc = getJail(jail);
user.getTeleport().now(loc, false, TeleportCause.COMMAND);
public void sendToJail(final IUser user, String jail) throws Exception {
if (jail == null || jail.isEmpty()) {
return;
}
jail = jail.toLowerCase(Locale.ENGLISH);
synchronized (jails) {
if (jails.containsKey(jail)) {
if (user.getBase().isOnline()) {
user.getTeleport().now(getJail(jail), false, TeleportCause.COMMAND);
}
user.setJail(jail);
}
user.setJail(jail);
} finally {
unlock();
}
}
@Override
public void sendToJail(final IUser user, final String jail, final CompletableFuture<Boolean> future) throws Exception {
acquireReadLock();
try {
if (user.getBase().isOnline()) {
final Location loc = getJail(jail);
user.getAsyncTeleport().now(loc, false, TeleportCause.COMMAND, future);
future.thenAccept(success -> user.setJail(jail));
return;
public void sendToJail(final IUser user, final String jailName, final CompletableFuture<Boolean> future) throws Exception {
if (jailName == null || jailName.isEmpty()) {
return;
}
final String jail = jailName.toLowerCase(Locale.ENGLISH);
synchronized (jails) {
if (jails.containsKey(jail)) {
if (user.getBase().isOnline()) {
user.getAsyncTeleport().now(getJail(jail), false, TeleportCause.COMMAND, future);
future.thenAccept(success -> user.setJail(jail));
return;
}
user.setJail(jail);
}
user.setJail(jail);
} finally {
unlock();
}
}
@Override
public void setJail(final String jailName, final Location loc) throws Exception {
acquireWriteLock();
try {
if (getData().getJails() == null) {
getData().setJails(new HashMap<>());
}
getData().getJails().put(jailName.toLowerCase(Locale.ENGLISH), loc);
} finally {
unlock();
public void setJail(String jailName, final Location loc) throws Exception {
jailName = jailName.toLowerCase(Locale.ENGLISH);
synchronized (jails) {
jails.put(jailName, LazyLocation.fromLocation(loc));
config.setProperty("jails." + jailName, loc);
config.save();
}
}
@@ -7,12 +7,15 @@ import com.earth2me.essentials.textreader.IText;
import com.earth2me.essentials.textreader.KeywordReplacer;
import com.earth2me.essentials.textreader.SimpleTextInput;
import com.earth2me.essentials.utils.DateUtil;
import com.earth2me.essentials.utils.MaterialUtil;
import com.earth2me.essentials.utils.NumberUtil;
import net.ess3.api.IEssentials;
import net.ess3.api.events.KitClaimEvent;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import java.math.BigDecimal;
import java.util.ArrayList;
@@ -73,6 +76,10 @@ public class Kit {
user.setKitTimestamp(kitName, time.getTimeInMillis());
}
public void resetTime(final User user) {
user.setKitTimestamp(kitName, 0);
}
public void chargeUser(final User user) throws Exception {
charge.charge(user);
}
@@ -162,12 +169,13 @@ public class Kit {
boolean spew = false;
final boolean allowUnsafe = ess.getSettings().allowUnsafeEnchantments();
final boolean currencyIsSuffix = ess.getSettings().isCurrencySymbolSuffixed();
final boolean autoEquip = ess.getSettings().isKitAutoEquip();
final List<ItemStack> itemList = new ArrayList<>();
final List<String> commandQueue = new ArrayList<>();
final List<String> moneyQueue = new ArrayList<>();
final String currencySymbol = ess.getSettings().getCurrencySymbol().isEmpty() ? "$" : ess.getSettings().getCurrencySymbol();
for (final String kitItem : output.getLines()) {
if (!currencyIsSuffix ? kitItem.startsWith(ess.getSettings().getCurrencySymbol()) : kitItem.endsWith(ess.getSettings().getCurrencySymbol())) {
if (kitItem.startsWith("$") || kitItem.startsWith(currencySymbol)) {
moneyQueue.add(NumberUtil.sanitizeCurrencyString(kitItem, ess));
continue;
}
@@ -180,21 +188,51 @@ public class Kit {
continue;
}
final String[] parts = kitItem.split(" +");
final ItemStack parseStack = ess.getItemDb().get(parts[0], parts.length > 1 ? Integer.parseInt(parts[1]) : 1);
final ItemStack stack;
if (parseStack.getType() == Material.AIR) {
continue;
if (kitItem.startsWith("@")) {
if (ess.getSerializationProvider() == null) {
ess.getLogger().log(Level.WARNING, tl("kitError3", kitName, user.getName()));
continue;
}
stack = ess.getSerializationProvider().deserializeItem(Base64Coder.decodeLines(kitItem.substring(1)));
} else {
final String[] parts = kitItem.split(" +");
final ItemStack parseStack = ess.getItemDb().get(parts[0], parts.length > 1 ? Integer.parseInt(parts[1]) : 1);
if (parseStack.getType() == Material.AIR) {
continue;
}
final MetaItemStack metaStack = new MetaItemStack(parseStack);
if (parts.length > 2) {
// We pass a null sender here because kits should not do perm checks
metaStack.parseStringMeta(null, allowUnsafe, parts, 2, ess);
}
stack = metaStack.getItemStack();
}
final MetaItemStack metaStack = new MetaItemStack(parseStack);
if (parts.length > 2) {
// We pass a null sender here because kits should not do perm checks
metaStack.parseStringMeta(null, allowUnsafe, parts, 2, ess);
if (autoEquip) {
final Material material = stack.getType();
final PlayerInventory inventory = user.getBase().getInventory();
if (MaterialUtil.isHelmet(material) && isEmptyStack(inventory.getHelmet())) {
inventory.setHelmet(stack);
continue;
} else if (MaterialUtil.isChestplate(material) && isEmptyStack(inventory.getChestplate())) {
inventory.setChestplate(stack);
continue;
} else if (MaterialUtil.isLeggings(material) && isEmptyStack(inventory.getLeggings())) {
inventory.setLeggings(stack);
continue;
} else if (MaterialUtil.isBoots(material) && isEmptyStack(inventory.getBoots())) {
inventory.setBoots(stack);
continue;
}
}
itemList.add(metaStack.getItemStack());
itemList.add(stack);
}
final Map<Integer, ItemStack> overfilled;
@@ -253,4 +291,8 @@ public class Kit {
}
return true;
}
private boolean isEmptyStack(ItemStack stack) {
return stack == null || MaterialUtil.isAir(stack.getType());
}
}
@@ -0,0 +1,168 @@
package com.earth2me.essentials;
import com.earth2me.essentials.config.ConfigurateUtil;
import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.utils.NumberUtil;
import org.spongepowered.configurate.CommentedConfigurationNode;
import java.io.File;
import java.math.BigDecimal;
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.capitalCase;
import static com.earth2me.essentials.I18n.tl;
public class Kits implements IConf {
private final IEssentials ess;
private final EssentialsConfiguration rootConfig;
private final Map<String, EssentialsConfiguration> kitToConfigMap = new HashMap<>();
private final Map<String, Map<String, Object>> kitMap = new HashMap<>();
public Kits(final IEssentials essentials) {
this.ess = essentials;
this.rootConfig = new EssentialsConfiguration(new File(essentials.getDataFolder(), "kits.yml"), "/kits.yml");
reloadConfig();
}
@Override
public void reloadConfig() {
rootConfig.load();
parseKits();
}
public File getFile() {
return rootConfig.getFile();
}
private void parseKit(final String kitName, final CommentedConfigurationNode kitSection, final EssentialsConfiguration parentConfig) {
if (kitSection.isMap()) {
final String effectiveKitName = kitName.toLowerCase(Locale.ENGLISH);
kitToConfigMap.put(effectiveKitName, parentConfig);
kitMap.put(effectiveKitName, ConfigurateUtil.getRawMap(kitSection));
}
}
private void parseKits() {
kitToConfigMap.clear();
kitMap.clear();
// Kits from kits.yml file
final CommentedConfigurationNode fileKits = rootConfig.getSection("kits");
if (fileKits != null) {
for (final Map.Entry<String, CommentedConfigurationNode> kitEntry : ConfigurateUtil.getMap(fileKits).entrySet()) {
parseKit(kitEntry.getKey(), kitEntry.getValue(), rootConfig);
}
}
// Kits from kits subdirectory
final File kitsFolder = new File(this.ess.getDataFolder(), "kits");
if (!kitsFolder.exists() || !kitsFolder.isDirectory()) {
return;
}
final File[] kitsFiles = kitsFolder.listFiles();
//noinspection ConstantConditions - will not be null, conditions checked above.
for (final File kitFile : kitsFiles) {
if (kitFile.getName().endsWith(".yml")) {
final EssentialsConfiguration kitConfig = new EssentialsConfiguration(kitFile);
kitConfig.load();
final CommentedConfigurationNode kits = kitConfig.getSection("kits");
if (kits != null) {
for (final Map.Entry<String, CommentedConfigurationNode> kitEntry : ConfigurateUtil.getMap(kits).entrySet()) {
parseKit(kitEntry.getKey(), kitEntry.getValue(), kitConfig);
}
}
}
}
}
/**
* Should be used for EssentialsUpgrade conversions <b>only</b>.
*/
public EssentialsConfiguration getRootConfig() {
return rootConfig;
}
public Set<String> getKitKeys() {
return kitMap.keySet();
}
public Map<String, Object> getKit(final String name) {
if (name != null) {
return kitMap.get(name.replace('.', '_').replace('/', '_'));
}
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 (name != null) {
for (final String kitName : kitMap.keySet()) {
if (kitName.equalsIgnoreCase(name)) {
return kitName;
}
}
}
return null;
}
public void addKit(String name, final List<String> lines, final long delay) {
name = name.replace('.', '_').replace('/', '_').toLowerCase(Locale.ENGLISH);
// Will overwrite but w/e
rootConfig.setProperty("kits." + name + ".delay", delay);
rootConfig.setProperty("kits." + name + ".items", lines);
parseKits();
rootConfig.save();
}
public void removeKit(String name) {
name = name.replace('.', '_').replace('/', '_').toLowerCase(Locale.ENGLISH);
if (!kitToConfigMap.containsKey(name) || !kitMap.containsKey(name)) {
return;
}
final EssentialsConfiguration config = kitToConfigMap.get(name);
config.removeProperty("kits." + name);
config.blockingSave();
parseKits();
}
public String listKits(final net.ess3.api.IEssentials ess, final User user) throws Exception {
try {
final StringBuilder list = new StringBuilder();
for (final String kitItem : kitMap.keySet()) {
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);
}
}
}
@@ -0,0 +1,53 @@
package com.earth2me.essentials;
import net.ess3.api.IUser;
import net.essentialsx.api.v2.services.mail.MailService;
import net.essentialsx.api.v2.services.mail.MailMessage;
import net.essentialsx.api.v2.services.mail.MailSender;
import org.bukkit.plugin.ServicePriority;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import static com.earth2me.essentials.I18n.tl;
public class MailServiceImpl implements MailService {
private final transient ThreadLocal<SimpleDateFormat> df = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy/MM/dd HH:mm"));
public MailServiceImpl(IEssentials ess) {
ess.getServer().getServicesManager().register(MailService.class, this, ess, ServicePriority.Normal);
}
@Override
public void sendMail(IUser recipient, MailSender sender, String message) {
sendMail(recipient, sender, message, 0L);
}
@Override
public void sendMail(IUser recipient, MailSender sender, String message, long expireAt) {
sendMail(recipient, new MailMessage(false, false, sender.getName(), sender.getUUID(), System.currentTimeMillis(), expireAt, message));
}
@Override
public void sendLegacyMail(IUser recipient, String message) {
sendMail(recipient, new MailMessage(false, true, null, null, 0L, 0L, message));
}
private void sendMail(IUser recipient, MailMessage message) {
final ArrayList<MailMessage> messages = recipient.getMailMessages();
messages.add(0, message);
recipient.setMailList(messages);
}
@Override
public String getMailLine(MailMessage mail) {
final String message = mail.getMessage();
if (mail.isLegacy()) {
return tl("mailMessage", message);
}
final String expire = mail.getTimeExpire() != 0 ? "Timed" : "";
return tl((mail.isRead() ? "mailFormatNewRead" : "mailFormatNew") + expire, df.get().format(new Date(mail.getTimeSent())), mail.getSenderUsername(), message);
}
}
@@ -7,9 +7,9 @@ import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.MaterialUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.VersionUtil;
import com.google.common.base.Joiner;
import net.ess3.api.IEssentials;
import net.ess3.nms.refl.ReflUtil;
import org.bukkit.Color;
import org.bukkit.DyeColor;
import org.bukkit.FireworkEffect;
@@ -23,6 +23,7 @@ import org.bukkit.inventory.meta.BannerMeta;
import org.bukkit.inventory.meta.BlockStateMeta;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.FireworkEffectMeta;
import org.bukkit.inventory.meta.FireworkMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
@@ -32,7 +33,6 @@ import org.bukkit.potion.Potion;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -47,9 +47,6 @@ import static com.earth2me.essentials.I18n.tl;
public class MetaItemStack {
private static final Map<String, DyeColor> colorMap = new HashMap<>();
private static final Map<String, FireworkEffect.Type> fireworkShape = new HashMap<>();
private static int bukkitUnbreakableSupport = -1;
private static Method spigotMethod;
private static Method setUnbreakableMethod;
private static boolean useNewSkullMethod = true;
static {
@@ -61,12 +58,14 @@ public class MetaItemStack {
}
}
private final transient Pattern splitPattern = Pattern.compile("[:+',;.]");
private static final transient Pattern splitPattern = Pattern.compile("[:+',;.]");
private static final transient Pattern hexPattern = Pattern.compile("#([0-9a-fA-F]{6})");
private ItemStack stack;
private FireworkEffect.Builder builder = FireworkEffect.builder();
private PotionEffectType pEffectType;
private PotionEffect pEffect;
private boolean validFirework = false;
private boolean validFireworkCharge = false;
private boolean validPotionEffect = false;
private boolean validPotionDuration = false;
private boolean validPotionPower = false;
@@ -174,6 +173,15 @@ public class MetaItemStack {
}
stack.setItemMeta(fmeta);
}
if (validFireworkCharge) {
if (!hasMetaPermission(sender, "firework", true, true, ess)) {
throw new Exception(tl("noMetaFirework"));
}
final FireworkEffect effect = builder.build();
final FireworkEffectMeta meta = (FireworkEffectMeta) stack.getItemMeta();
meta.setEffect(effect);
stack.setItemMeta(meta);
}
}
}
@@ -200,7 +208,7 @@ public class MetaItemStack {
stack.setItemMeta(meta);
} else if (split[0].equalsIgnoreCase("unbreakable") && hasMetaPermission(sender, "unbreakable", false, true, ess)) {
final boolean value = split.length <= 1 || Boolean.parseBoolean(split[1]);
setUnbreakable(stack, value);
setUnbreakable(ess, stack, value);
} else if (split.length > 1 && (split[0].equalsIgnoreCase("player") || split[0].equalsIgnoreCase("owner")) && hasMetaPermission(sender, "head", false, true, ess)) {
if (MaterialUtil.isPlayerHead(stack.getType(), stack.getDurability())) {
final String owner = split[1];
@@ -208,7 +216,7 @@ public class MetaItemStack {
} else {
throw new Exception(tl("onlyPlayerSkulls"));
}
} else if (split.length > 1 && split[0].equalsIgnoreCase("book") && stack.getType() == WRITTEN_BOOK && (hasMetaPermission(sender, "book", true, true, ess) || hasMetaPermission(sender, "chapter-" + split[1].toLowerCase(Locale.ENGLISH), true, true, ess))) {
} else if (split.length > 1 && split[0].equalsIgnoreCase("book") && MaterialUtil.isEditableBook(stack.getType()) && (hasMetaPermission(sender, "book",true, true, ess) || hasMetaPermission(sender, "chapter-" + split[1].toLowerCase(Locale.ENGLISH), true, true, ess))) {
final BookMeta meta = (BookMeta) stack.getItemMeta();
final IText input = new BookInput("book", true, ess);
final BookPager pager = new BookPager(input);
@@ -226,6 +234,23 @@ public class MetaItemStack {
final BookMeta meta = (BookMeta) stack.getItemMeta();
meta.setTitle(title);
stack.setItemMeta(meta);
} else if (split.length > 1 && split[0].startsWith("page") && split[0].length() > 4 && MaterialUtil.isEditableBook(stack.getType()) && hasMetaPermission(sender, "page", false, true, ess)) {
final int page = NumberUtil.isInt(split[0].substring(4)) ? (Integer.parseInt(split[0].substring(4)) - 1) : 0;
final BookMeta meta = (BookMeta) stack.getItemMeta();
final List<String> pages = meta.hasPages() ? new ArrayList<>(meta.getPages()) : new ArrayList<>();
final List<String> lines = new ArrayList<>();
for (final String line : split[1].split("(?<!\\\\)\\|")) {
lines.add(FormatUtil.replaceFormat(line.replace('_', ' ').replace("\\|", "|")));
}
final String content = String.join("\n", lines);
if (page >= pages.size()) {
for (int i = 0; i <= page - pages.size(); i++) {
pages.add("");
}
}
pages.set(page, content);
meta.setPages(pages);
stack.setItemMeta(meta);
} else if (split.length > 1 && split[0].equalsIgnoreCase("power") && MaterialUtil.isFirework(stack.getType()) && hasMetaPermission(sender, "firework-power", false, true, ess)) {
final int power = NumberUtil.isInt(split[1]) ? Integer.parseInt(split[1]) : 0;
final FireworkMeta meta = (FireworkMeta) stack.getItemMeta();
@@ -234,20 +259,27 @@ public class MetaItemStack {
} else if (split.length > 1 && split[0].equalsIgnoreCase("itemflags") && hasMetaPermission(sender, "itemflags", false, true, ess)) {
addItemFlags(string);
} else if (MaterialUtil.isFirework(stack.getType())) {
//WARNING - Meta for fireworks will be ignored after this point.
addFireworkMeta(sender, false, string, ess);
if (!parseEnchantmentStrings(sender, allowUnsafe, split, ess)) {
//WARNING - Meta for fireworks will be ignored after this point.
addFireworkMeta(sender, false, string, ess);
}
} else if (MaterialUtil.isFireworkCharge(stack.getType())) {
if (!parseEnchantmentStrings(sender, allowUnsafe, split, ess)) {
//WARNING - Meta for fireworks will be ignored after this point.
addChargeMeta(sender, false, string, ess);
}
} else if (MaterialUtil.isPotion(stack.getType())) {
//WARNING - Meta for potions will be ignored after this point.
addPotionMeta(sender, false, string, ess);
if (split[0].equalsIgnoreCase("power") || !parseEnchantmentStrings(sender, allowUnsafe, split, ess)) {
//WARNING - Meta for potions will be ignored after this point.
addPotionMeta(sender, false, string, ess);
}
} else if (MaterialUtil.isBanner(stack.getType())) {
if (stack.getType().toString().equals("SHIELD") && Enchantments.getByName(split[0]) != null) {
parseEnchantmentStrings(sender, allowUnsafe, split, ess);
} else {
if (!parseEnchantmentStrings(sender, allowUnsafe, split, ess)) {
//WARNING - Meta for banners will be ignored after this point.
addBannerMeta(sender, false, string, ess);
}
} else if (split.length > 1 && (split[0].equalsIgnoreCase("color") || split[0].equalsIgnoreCase("colour")) && MaterialUtil.isLeatherArmor(stack.getType())) {
final String[] color = split[1].split("(\\||,)");
final String[] color = split[1].split("[|,]");
if (color.length == 1 && (NumberUtil.isInt(color[0]) || color[0].startsWith("#"))) {
// Either integer or hexadecimal
final LeatherArmorMeta meta = (LeatherArmorMeta) stack.getItemMeta();
@@ -300,6 +332,67 @@ public class MetaItemStack {
stack.setItemMeta(meta);
}
private void addChargeMeta(final CommandSource sender, final boolean allowShortName, final String string, final IEssentials ess) throws Exception {
final String[] split = splitPattern.split(string, 2);
if (split.length < 2) {
return;
}
if (split[0].equalsIgnoreCase("color") || split[0].equalsIgnoreCase("colour") || (allowShortName && split[0].equalsIgnoreCase("c"))) {
final List<Color> primaryColors = new ArrayList<>();
final String[] colors = split[1].split(",");
for (final String color : colors) {
if (colorMap.containsKey(color.toUpperCase())) {
validFireworkCharge = true;
primaryColors.add(colorMap.get(color.toUpperCase()).getFireworkColor());
} else if (hexPattern.matcher(color).matches()) {
validFireworkCharge = true;
primaryColors.add(Color.fromRGB(Integer.decode(color)));
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
}
}
builder.withColor(primaryColors);
} else if (split[0].equalsIgnoreCase("shape") || split[0].equalsIgnoreCase("type") || (allowShortName && (split[0].equalsIgnoreCase("s") || split[0].equalsIgnoreCase("t")))) {
FireworkEffect.Type finalEffect = null;
split[1] = split[1].equalsIgnoreCase("large") ? "BALL_LARGE" : split[1];
if (fireworkShape.containsKey(split[1].toUpperCase())) {
finalEffect = fireworkShape.get(split[1].toUpperCase());
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
}
if (finalEffect != null) {
builder.with(finalEffect);
}
} else if (split[0].equalsIgnoreCase("fade") || (allowShortName && split[0].equalsIgnoreCase("f"))) {
final List<Color> fadeColors = new ArrayList<>();
final String[] colors = split[1].split(",");
for (final String color : colors) {
if (colorMap.containsKey(color.toUpperCase())) {
fadeColors.add(colorMap.get(color.toUpperCase()).getFireworkColor());
} else if (hexPattern.matcher(color).matches()) {
fadeColors.add(Color.fromRGB(Integer.decode(color)));
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
}
}
if (!fadeColors.isEmpty()) {
builder.withFade(fadeColors);
}
} else if (split[0].equalsIgnoreCase("effect") || (allowShortName && split[0].equalsIgnoreCase("e"))) {
final String[] effects = split[1].split(",");
for (final String effect : effects) {
if (effect.equalsIgnoreCase("twinkle")) {
builder.flicker(true);
} else if (effect.equalsIgnoreCase("trail")) {
builder.trail(true);
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
}
}
}
}
public void addFireworkMeta(final CommandSource sender, final boolean allowShortName, final String string, final IEssentials ess) throws Exception {
if (MaterialUtil.isFirework(stack.getType())) {
final String[] split = splitPattern.split(string, 2);
@@ -328,6 +421,9 @@ public class MetaItemStack {
if (colorMap.containsKey(color.toUpperCase())) {
validFirework = true;
primaryColors.add(colorMap.get(color.toUpperCase()).getFireworkColor());
} else if (hexPattern.matcher(color).matches()) {
validFirework = true;
primaryColors.add(Color.fromRGB(Integer.decode(color)));
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
}
@@ -350,6 +446,8 @@ public class MetaItemStack {
for (final String color : colors) {
if (colorMap.containsKey(color.toUpperCase())) {
fadeColors.add(colorMap.get(color.toUpperCase()).getFireworkColor());
} else if (hexPattern.matcher(color).matches()) {
fadeColors.add(Color.fromRGB(Integer.decode(color)));
} else {
throw new Exception(tl("invalidFireworkFormat", split[1], split[0]));
}
@@ -427,14 +525,14 @@ public class MetaItemStack {
}
pmeta.addCustomEffect(pEffect, true);
stack.setItemMeta(pmeta);
if (ReflUtil.getNmsVersionObject().isHigherThanOrEqualTo(ReflUtil.V1_9_R1)) {
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_9_R01)) {
if (isSplashPotion && stack.getType() != Material.SPLASH_POTION) {
stack.setType(Material.SPLASH_POTION);
} else if (!isSplashPotion && stack.getType() != Material.POTION) {
stack.setType(Material.POTION);
}
} else {
final Potion potion = Potion.fromItemStack(stack);
final Potion potion = Potion.fromDamage(stack.getDurability());
potion.setSplash(isSplashPotion);
potion.apply(stack);
}
@@ -443,25 +541,27 @@ public class MetaItemStack {
}
}
private void parseEnchantmentStrings(final CommandSource sender, final boolean allowUnsafe, final String[] split, final IEssentials ess) throws Exception {
private boolean parseEnchantmentStrings(final CommandSource sender, final boolean allowUnsafe, final String[] split, final IEssentials ess) throws Exception {
final Enchantment enchantment = Enchantments.getByName(split[0]);
if (enchantment == null || !hasMetaPermission(sender, "enchantments." + enchantment.getName().toLowerCase(Locale.ENGLISH), false, false, ess)) {
return;
if (enchantment == null) {
return false;
}
int level = -1;
if (split.length > 1) {
try {
level = Integer.parseInt(split[1]);
} catch (final NumberFormatException ex) {
level = -1;
if (hasMetaPermission(sender, "enchantments." + enchantment.getName().toLowerCase(Locale.ENGLISH), false, false, ess)) {
int level = -1;
if (split.length > 1) {
try {
level = Integer.parseInt(split[1]);
} catch (final NumberFormatException ex) {
level = -1;
}
}
}
if (level < 0 || (!allowUnsafe && level > enchantment.getMaxLevel())) {
level = enchantment.getMaxLevel();
if (level < 0 || (!allowUnsafe && level > enchantment.getMaxLevel())) {
level = enchantment.getMaxLevel();
}
addEnchantment(sender, allowUnsafe, enchantment, level);
}
addEnchantment(sender, allowUnsafe, enchantment, level);
return true;
}
public void addEnchantment(final CommandSource sender, final boolean allowUnsafe, final Enchantment enchantment, final int level) throws Exception {
@@ -582,35 +682,9 @@ public class MetaItemStack {
}
}
private void setUnbreakable(final ItemStack is, final boolean unbreakable) {
private void setUnbreakable(final IEssentials ess, final ItemStack is, final boolean unbreakable) {
final ItemMeta meta = is.getItemMeta();
try {
if (bukkitUnbreakableSupport == -1) {
try {
ItemMeta.class.getDeclaredMethod("setUnbreakable", boolean.class);
bukkitUnbreakableSupport = 1;
} catch (final NoSuchMethodException | SecurityException ex) {
bukkitUnbreakableSupport = 0;
}
}
if (bukkitUnbreakableSupport == 1) {
meta.setUnbreakable(unbreakable);
} else {
if (spigotMethod == null) {
spigotMethod = meta.getClass().getDeclaredMethod("spigot");
spigotMethod.setAccessible(true);
}
final Object itemStackSpigot = spigotMethod.invoke(meta);
if (setUnbreakableMethod == null) {
setUnbreakableMethod = itemStackSpigot.getClass().getDeclaredMethod("setUnbreakable", Boolean.TYPE);
setUnbreakableMethod.setAccessible(true);
}
setUnbreakableMethod.invoke(itemStackSpigot, unbreakable);
}
is.setItemMeta(meta);
} catch (final Throwable t) {
t.printStackTrace();
}
ess.getItemUnbreakableProvider().setUnbreakable(meta, unbreakable);
is.setItemMeta(meta);
}
}
@@ -102,6 +102,9 @@ public enum Mob {
STRIDER("Strider", Enemies.FRIENDLY, "STRIDER"),
ZOGLIN("Zoglin", Enemies.ENEMY, "ZOGLIN"),
PIGLIN_BRUTE("PiglinBrute", Enemies.ADULT_ENEMY, "PIGLIN_BRUTE"),
AXOLOTL("Axolotl", Enemies.FRIENDLY, "AXOLOTL"),
GOAT("Goat", Enemies.NEUTRAL, "GOAT"),
GLOW_SQUID("GlowSquid", Enemies.FRIENDLY, "GLOW_SQUID"),
;
public static final Logger logger = Logger.getLogger("Essentials");
@@ -3,6 +3,7 @@ package com.earth2me.essentials;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.nms.refl.ReflUtil;
import org.bukkit.entity.Axolotl;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Fox;
@@ -34,6 +35,8 @@ public final class MobCompat {
public static final EntityType STRAY = getEntityType("STRAY");
public static final EntityType FOX = getEntityType("FOX");
public static final EntityType PHANTOM = getEntityType("PHANTOM");
public static final EntityType AXOLOTL = getEntityType("AXOLOTL");
public static final EntityType GOAT = getEntityType("GOAT");
// Constants for mobs that have changed since earlier versions
public static final EntityType CAT = getEntityType("CAT", "OCELOT");
@@ -152,6 +155,15 @@ public final class MobCompat {
}
}
public static void setAxolotlVariant(final Entity entity, final String variant) {
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_17_R01)) {
return;
}
if (entity instanceof Axolotl) {
((Axolotl) entity).setVariant(Axolotl.Variant.valueOf(variant));
}
}
public enum CatType {
// These are (loosely) Mojang names for the cats
SIAMESE("SIAMESE", "SIAMESE_CAT"),
@@ -3,13 +3,16 @@ package com.earth2me.essentials;
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.StringUtil;
import com.earth2me.essentials.utils.VersionUtil;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.ChestedHorse;
import org.bukkit.entity.Creeper;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.ExperienceOrb;
import org.bukkit.entity.Goat;
import org.bukkit.entity.Horse;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Phantom;
@@ -95,11 +98,13 @@ public enum MobData {
ALL_BLACK_CAT("allblack", MobCompat.CAT, MobCompat.CatType.BLACK, true),
BABY_ZOMBIE("baby", EntityType.ZOMBIE.getEntityClass(), Data.BABYZOMBIE, true),
ADULT_ZOMBIE("adult", EntityType.ZOMBIE.getEntityClass(), Data.ADULTZOMBIE, true),
NETHERITE_SWORD_ZOMBIE("netheritesword", EntityType.ZOMBIE.getEntityClass(), EnumUtil.getMaterial("NETHERITE_SWORD"), true),
DIAMOND_SWORD_ZOMBIE("diamondsword", EntityType.ZOMBIE.getEntityClass(), Material.DIAMOND_SWORD, true),
GOLD_SWORD_ZOMBIE("goldsword", EntityType.ZOMBIE.getEntityClass(), EnumUtil.getMaterial("GOLDEN_SWORD", "GOLD_SWORD"), true),
IRON_SWORD_ZOMBIE("ironsword", EntityType.ZOMBIE.getEntityClass(), Material.IRON_SWORD, true),
STONE_SWORD_ZOMBIE("stonesword", EntityType.ZOMBIE.getEntityClass(), Material.STONE_SWORD, false),
SWORD_ZOMBIE("sword", EntityType.ZOMBIE.getEntityClass(), Material.STONE_SWORD, true),
NETHERITE_SWORD_SKELETON("netheritesword", EntityType.SKELETON, EnumUtil.getMaterial("NETHERITE_SWORD"), true),
DIAMOND_SWORD_SKELETON("diamondsword", EntityType.SKELETON, Material.DIAMOND_SWORD, true),
GOLD_SWORD_SKELETON("goldsword", EntityType.SKELETON, EnumUtil.getMaterial("GOLDEN_SWORD", "GOLD_SWORD"), true),
IRON_SWORD_SKELETON("ironsword", EntityType.SKELETON, Material.IRON_SWORD, true),
@@ -183,6 +188,15 @@ public enum MobData {
RAID_LEADER("leader", MobCompat.RAIDER, Data.RAID_LEADER, true),
TROPICAL_FISH_BODY_COLOR("fish_body_color", Arrays.stream(DyeColor.values()).map(color -> color.name().toLowerCase(Locale.ENGLISH) + "body").collect(Collectors.toList()), MobCompat.TROPICAL_FISH, Data.FISH_BODY_COLOR, true),
TROPICAL_FISH_PATTERN_COLOR("fish_pattern_color", Arrays.stream(DyeColor.values()).map(color -> color.name().toLowerCase(Locale.ENGLISH) + "pattern").collect(Collectors.toList()), MobCompat.TROPICAL_FISH, Data.FISH_PATTERN_COLOR, true),
LUCY_AXOLOTL("lucy", MobCompat.AXOLOTL, "axolotl:LUCY", true),
LEUCISTIC_AXOLOTL("leucistic", MobCompat.AXOLOTL, "axolotl:LUCY", false),
PINK_AXOLOTL("pink", MobCompat.AXOLOTL, "axolotl:LUCY", false),
WILD_AXOLOTL("wild", MobCompat.AXOLOTL, "axolotl:WILD", true),
BROWN_AXOLOTL("brown", MobCompat.AXOLOTL, "axolotl:WILD", false),
GOLD_AXOLOTL("gold", MobCompat.AXOLOTL, "axolotl:GOLD", true),
CYAN_AXOLOTL("cyan", MobCompat.AXOLOTL, "axolotl:CYAN", true),
BLUE_AXOLOTL("blue", MobCompat.AXOLOTL, "axolotl:BLUE", true),
SCREAMING_GOAT("screaming", MobCompat.GOAT, Data.GOAT_SCREAMING, true),
;
public static final Logger logger = Logger.getLogger("Essentials");
@@ -258,12 +272,22 @@ public enum MobData {
}
public void setData(final Entity spawned, final Player target, final String rawData) throws Exception {
if (value == null) {
return;
}
if (this.value.equals(Data.ANGRY)) {
((Wolf) spawned).setAngry(true);
} else if (this.value.equals(Data.ADULT)) {
((Ageable) spawned).setAdult();
} else if (this.value.equals(Data.BABY)) {
((Ageable) spawned).setBaby();
} else if (this.value.equals(Data.CHEST)) {
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_11_R01)) {
((ChestedHorse) spawned).setCarryingChest(true);
} else {
((Horse) spawned).setCarryingChest(true);
}
} else if (this.value.equals(Data.ADULTZOMBIE)) {
((Zombie) spawned).setBaby(false);
} else if (this.value.equals(Data.BABYZOMBIE)) {
@@ -350,6 +374,8 @@ public enum MobData {
break;
}
}
} else if (this.value.equals(Data.GOAT_SCREAMING)) {
((Goat) spawned).setScreaming(true);
} else if (this.value instanceof String) {
final String[] split = ((String) this.value).split(":");
switch (split[0]) {
@@ -377,6 +403,10 @@ public enum MobData {
case "fox":
MobCompat.setFoxType(spawned, split[1]);
break;
case "axolotl": {
MobCompat.setAxolotlVariant(spawned, split[1]);
break;
}
}
} else {
logger.warning("Unknown mob data type: " + this.toString());
@@ -400,5 +430,6 @@ public enum MobData {
RAID_LEADER,
FISH_BODY_COLOR,
FISH_PATTERN_COLOR,
GOAT_SCREAMING,
}
}
@@ -1,10 +1,12 @@
package com.earth2me.essentials;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.NumberUtil;
import org.bukkit.ChatColor;
import org.bukkit.Server;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -37,10 +39,12 @@ public final class PlayerList {
}
user.setDisplayNick();
groupString.append(user.getDisplayName());
if (ess.getSettings().realNamesOnList() && !ChatColor.stripColor(user.getDisplayName()).equals(user.getName())) {
groupString.append(" (").append(user.getName()).append(")");
final String strippedNick = FormatUtil.stripFormat(user.getNickname());
if (ess.getSettings().realNamesOnList() && strippedNick != null && !strippedNick.equals(user.getName())) {
groupString.append(" ").append(tl("listRealName",user.getName()));
}
groupString.append("\u00a7f");
groupString.append(ChatColor.WHITE.toString());
}
return groupString.toString();
}
@@ -125,4 +129,88 @@ public final class PlayerList {
return tl("listGroupTag", FormatUtil.replaceFormat(group)) +
message;
}
public static List<String> prepareGroupedList(final IEssentials ess, final String commandLabel, final Map<String, List<User>> playerList) {
final List<String> output = new ArrayList<>();
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) {
output.add(outputFormat(oConfigGroup, tl("groupNumber", matchedList.size(), commandLabel, FormatUtil.stripFormat(configGroup))));
} else {
output.add(outputFormat(oConfigGroup, listUsers(ess, outputUserList, ", ")));
}
continue;
}
}
outputUserList = getMergedList(ess, playerList, configGroup);
// If we have no users, than we don't need to continue parsing this group
if (outputUserList.isEmpty()) {
continue;
}
output.add(outputFormat(oConfigGroup, 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;
}
output.add(outputFormat(groupName, listUsers(ess, users, ", ")));
}
return output;
}
}
@@ -1,5 +1,7 @@
package com.earth2me.essentials;
import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.config.entities.LazyLocation;
import com.earth2me.essentials.utils.LocationUtil;
import com.earth2me.essentials.utils.VersionUtil;
import io.papermc.lib.PaperLib;
@@ -20,16 +22,14 @@ 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 IEssentials ess;
private final EssentialsConfiguration 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);
this.ess = essentials;
config = new EssentialsConfiguration(new File(essentials.getDataFolder(), "tpr.yml"), "/tpr.yml",
"Configuration for the random teleport command.\nSome settings may be defaulted, and can be changed via the /settpr command in-game.");
reloadConfig();
}
@@ -41,14 +41,14 @@ public class RandomTeleport implements IConf {
public Location getCenter() {
try {
final Location center = config.getLocation("center", essentials.getServer());
if (center != null) {
return center;
final LazyLocation center = config.getLocation("center");
if (center != null && center.location() != null) {
return center.location();
}
} catch (final InvalidWorldException ignored) {
}
final Location center = essentials.getServer().getWorlds().get(0).getWorldBorder().getCenter();
center.setY(center.getWorld().getHighestBlockYAt(center) + 1);
final Location center = ess.getServer().getWorlds().get(0).getWorldBorder().getCenter();
center.setY(center.getWorld().getHighestBlockYAt(center) + HIGHEST_BLOCK_Y_OFFSET);
setCenter(center);
return center;
}
@@ -77,7 +77,7 @@ public class RandomTeleport implements IConf {
}
public Set<Biome> getExcludedBiomes() {
final List<String> biomeNames = config.getStringList("excluded-biomes");
final List<String> biomeNames = config.getList("excluded-biomes", String.class);
final Set<Biome> excludedBiomes = new HashSet<>();
for (final String biomeName : biomeNames) {
try {
@@ -124,7 +124,7 @@ public class RandomTeleport implements IConf {
// 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, () -> {
ess.getServer().getScheduler().scheduleSyncDelayedTask(ess, () -> {
for (int i = 0; i < this.getFindAttempts(); ++i) {
calculateRandomLocation(center, minRange, maxRange).thenAccept(location -> {
if (isValidRandomLocation(location)) {
@@ -177,7 +177,7 @@ public class RandomTeleport implements IConf {
final Location location = new Location(
center.getWorld(),
center.getX() + offsetX,
center.getWorld().getMaxHeight(),
ess.getWorldInfoProvider().getMaxHeight(center.getWorld()),
center.getZ() + offsetZ,
360 * RANDOM.nextFloat() - 180,
0
@@ -195,8 +195,8 @@ public class RandomTeleport implements IConf {
// 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())) {
for (int y = 32; y < ess.getWorldInfoProvider().getMaxHeight(location.getWorld()); ++y) {
if (!LocationUtil.isBlockUnsafe(ess, location.getWorld(), location.getBlockX(), y, location.getBlockZ())) {
return y;
}
}
@@ -204,6 +204,6 @@ public class RandomTeleport implements IConf {
}
private boolean isValidRandomLocation(final Location location) {
return location.getBlockY() > 0 && !this.getExcludedBiomes().contains(location.getBlock().getBiome());
return location.getBlockY() > ess.getWorldInfoProvider().getMinHeight(location.getWorld()) && !this.getExcludedBiomes().contains(location.getBlock().getBiome());
}
}
@@ -2,6 +2,8 @@ package com.earth2me.essentials;
import com.earth2me.essentials.api.IItemDb;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.config.ConfigurateUtil;
import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.signs.EssentialsSign;
import com.earth2me.essentials.signs.Signs;
import com.earth2me.essentials.textreader.IText;
@@ -13,10 +15,10 @@ import com.earth2me.essentials.utils.NumberUtil;
import net.ess3.api.IEssentials;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration;
import org.bukkit.command.Command;
import org.bukkit.event.EventPriority;
import org.bukkit.inventory.ItemStack;
import org.spongepowered.configurate.CommentedConfigurationNode;
import java.io.File;
import java.math.BigDecimal;
@@ -35,6 +37,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -45,10 +48,11 @@ import static com.earth2me.essentials.I18n.tl;
public class Settings implements net.ess3.api.ISettings {
private static final Logger logger = Logger.getLogger("Essentials");
private static final BigDecimal MAXMONEY = new BigDecimal("10000000000000");
private static final BigDecimal MINMONEY = new BigDecimal("-10000000000000");
private final transient EssentialsConf config;
private static final BigDecimal DEFAULT_MAX_MONEY = new BigDecimal("10000000000000");
private static final BigDecimal DEFAULT_MIN_MONEY = new BigDecimal("-10000000000000");
private final transient EssentialsConfiguration config;
private final transient IEssentials ess;
private final transient AtomicInteger reloadCount = new AtomicInteger(0);
private final Map<String, String> chatFormats = Collections.synchronizedMap(new HashMap<>());
private int chatRadius = 0;
// #easteregg
@@ -58,7 +62,10 @@ public class Settings implements net.ess3.api.ISettings {
private boolean teleportSafety;
private boolean forceDisableTeleportSafety;
private Set<String> disabledCommands = new HashSet<>();
private ConfigurationSection commandCosts;
private List<String> overriddenCommands = Collections.emptyList();
private List<String> playerCommands = Collections.emptyList();
private final transient Map<String, Command> disabledBukkitCommands = new HashMap<>();
private Map<String, BigDecimal> commandCosts;
private Set<String> socialSpyCommands = new HashSet<>();
private Set<String> muteCommands = new HashSet<>();
private String nicknamePrefix = "~";
@@ -71,8 +78,8 @@ public class Settings implements net.ess3.api.ISettings {
private boolean configDebug = false;
// #easteregg
private boolean economyDisabled = false;
private BigDecimal maxMoney = MAXMONEY;
private BigDecimal minMoney = MINMONEY;
private BigDecimal maxMoney = DEFAULT_MAX_MONEY;
private BigDecimal minMoney = DEFAULT_MIN_MONEY;
private boolean economyLog = false;
// #easteregg
private boolean economyLogUpdate = false;
@@ -111,17 +118,18 @@ public class Settings implements net.ess3.api.ISettings {
private boolean isCustomJoinMessage;
private String customQuitMessage;
private boolean isCustomQuitMessage;
private String customNewUsernameMessage;
private boolean isCustomNewUsernameMessage;
private List<String> spawnOnJoinGroups;
private Map<Pattern, Long> commandCooldowns;
private boolean npcsInBalanceRanking = false;
private NumberFormat currencyFormat;
private List<EssentialsSign> unprotectedSigns = Collections.emptyList();
private List<String> defaultEnabledConfirmCommands;
private boolean teleportBackWhenFreedFromJail;
private TeleportWhenFreePolicy teleportWhenFreePolicy;
private boolean isCompassTowardsHomePerm;
private boolean isAllowWorldInBroadcastworld;
private String itemDbType; // #EasterEgg - admins can manually switch items provider if they want
private boolean forceEnableRecipe; // https://github.com/EssentialsX/Essentials/issues/1397
private boolean allowOldIdSigns;
private boolean isWaterSafe;
private boolean isSafeUsermap;
@@ -129,14 +137,19 @@ public class Settings implements net.ess3.api.ISettings {
private Set<Predicate<String>> nickBlacklist;
private double maxProjectileSpeed;
private boolean removeEffectsOnHeal;
private Map<String, String> worldAliases;
public Settings(final IEssentials ess) {
this.ess = ess;
config = new EssentialsConf(new File(ess.getDataFolder(), "config.yml"));
config.setTemplateName("/config.yml");
config = new EssentialsConfiguration(new File(ess.getDataFolder(), "config.yml"), "/config.yml");
reloadConfig();
}
@Override
public File getConfigFile() {
return config.getFile();
}
@Override
public boolean getRespawnAtHome() {
return config.getBoolean("respawn-at-home", false);
@@ -154,8 +167,8 @@ public class Settings implements net.ess3.api.ISettings {
@Override
public Set<String> getMultipleHomes() {
final ConfigurationSection section = config.getConfigurationSection("sethome-multiple");
return section == null ? null : section.getKeys(false);
final CommentedConfigurationNode section = config.getSection("sethome-multiple");
return section == null ? null : ConfigurateUtil.getKeys(section);
}
@Override
@@ -276,12 +289,39 @@ public class Settings implements net.ess3.api.ISettings {
return disabledCommands.contains(label);
}
private Set<String> getDisabledCommands() {
@Override
public Set<String> getDisabledCommands() {
return disabledCommands;
}
@Override
public boolean isVerboseCommandUsages() {
return config.getBoolean("verbose-command-usages", true);
}
private void _addAlternativeCommand(final String label, final Command current) {
Command cmd = ess.getAlternativeCommandsHandler().getAlternative(label);
if (cmd == null) {
for (final Map.Entry<String, Command> entry : ess.getKnownCommandsProvider().getKnownCommands().entrySet()) {
final String[] split = entry.getKey().split(":");
if (entry.getValue() != current && split[split.length - 1].equals(label)) {
cmd = entry.getValue();
break;
}
}
}
if (cmd != null) {
ess.getKnownCommandsProvider().getKnownCommands().put(label, cmd);
}
}
private Set<String> _getDisabledCommands() {
final Set<String> disCommands = new HashSet<>();
for (final String c : config.getStringList("disabled-commands")) {
for (final String c : config.getList("disabled-commands", String.class)) {
disCommands.add(c.toLowerCase(Locale.ENGLISH));
}
for (final String c : config.getKeys(false)) {
for (final String c : config.getKeys()) {
if (c.startsWith("disable-")) {
disCommands.add(c.substring(8).toLowerCase(Locale.ENGLISH));
}
@@ -289,9 +329,13 @@ public class Settings implements net.ess3.api.ISettings {
return disCommands;
}
private List<String> _getPlayerCommands() {
return config.getList("player-commands", String.class);
}
@Override
public boolean isPlayerCommand(final String label) {
for (final String c : config.getStringList("player-commands")) {
for (final String c : playerCommands) {
if (!c.equalsIgnoreCase(label)) {
continue;
}
@@ -300,9 +344,13 @@ public class Settings implements net.ess3.api.ISettings {
return false;
}
private List<String> _getOverriddenCommands() {
return config.getList("overridden-commands", String.class);
}
@Override
public boolean isCommandOverridden(final String name) {
for (final String c : config.getStringList("overridden-commands")) {
for (final String c : overriddenCommands) {
if (!c.equalsIgnoreCase(name)) {
continue;
}
@@ -316,32 +364,34 @@ public class Settings implements net.ess3.api.ISettings {
return getCommandCost(cmd.getName());
}
private ConfigurationSection _getCommandCosts() {
if (config.isConfigurationSection("command-costs")) {
final ConfigurationSection section = config.getConfigurationSection("command-costs");
final ConfigurationSection newSection = new MemoryConfiguration();
for (final String command : section.getKeys(false)) {
private Map<String, BigDecimal> _getCommandCosts() {
final Map<String, CommentedConfigurationNode> section = ConfigurateUtil.getMap(config.getSection("command-costs"));
if (!section.isEmpty()) {
final Map<String, BigDecimal> newMap = new HashMap<>();
for (Map.Entry<String, CommentedConfigurationNode> entry : section.entrySet()) {
final String command = entry.getKey();
final CommentedConfigurationNode node = entry.getValue();
if (command.charAt(0) == '/') {
ess.getLogger().warning("Invalid command cost. '" + command + "' should not start with '/'.");
}
if (section.isDouble(command)) {
newSection.set(command.toLowerCase(Locale.ENGLISH), section.getDouble(command));
} else if (section.isInt(command)) {
newSection.set(command.toLowerCase(Locale.ENGLISH), (double) section.getInt(command));
} else if (section.isString(command)) {
final String costString = section.getString(command);
try {
final double cost = Double.parseDouble(costString.trim().replace(getCurrencySymbol(), "").replaceAll("\\W", ""));
newSection.set(command.toLowerCase(Locale.ENGLISH), cost);
} catch (final NumberFormatException ex) {
ess.getLogger().warning("Invalid command cost for: " + command + " (" + costString + ")");
try {
if (ConfigurateUtil.isDouble(node)) {
newMap.put(command.toLowerCase(Locale.ENGLISH), BigDecimal.valueOf(node.getDouble()));
} else if (ConfigurateUtil.isInt(node)) {
newMap.put(command.toLowerCase(Locale.ENGLISH), BigDecimal.valueOf(node.getInt()));
} else if (ConfigurateUtil.isString(node)) {
final String costString = node.getString();
//noinspection ConstantConditions
final double cost = Double.parseDouble(costString.trim().replace("$", "").replace(getCurrencySymbol(), "").replaceAll("\\W", ""));
newMap.put(command.toLowerCase(Locale.ENGLISH), BigDecimal.valueOf(cost));
} else {
ess.getLogger().warning("Invalid command cost for: " + command);
}
} else {
} catch (final Exception ex) {
ess.getLogger().warning("Invalid command cost for: " + command);
}
}
return newSection;
return newMap;
}
return null;
}
@@ -349,8 +399,8 @@ public class Settings implements net.ess3.api.ISettings {
@Override
public BigDecimal getCommandCost(String name) {
name = name.replace('.', '_').replace('/', '_');
if (commandCosts != null) {
return EssentialsConf.toBigDecimal(commandCosts.getString(name), BigDecimal.ZERO);
if (commandCosts != null && commandCosts.containsKey(name)) {
return commandCosts.get(name);
}
return BigDecimal.ZERO;
}
@@ -359,7 +409,7 @@ public class Settings implements net.ess3.api.ISettings {
final Set<String> socialspyCommands = new HashSet<>();
if (config.isList("socialspy-commands")) {
for (final String c : config.getStringList("socialspy-commands")) {
for (final String c : config.getList("socialspy-commands", String.class)) {
socialspyCommands.add(c.toLowerCase(Locale.ENGLISH));
}
} else {
@@ -379,10 +429,15 @@ public class Settings implements net.ess3.api.ISettings {
return config.getBoolean("socialspy-listen-muted-players", true);
}
@Override
public boolean isSocialSpyMessages() {
return config.getBoolean("socialspy-messages", true);
}
private Set<String> _getMuteCommands() {
final Set<String> muteCommands = new HashSet<>();
if (config.isList("mute-commands")) {
for (final String s : config.getStringList("mute-commands")) {
for (final String s : config.getList("mute-commands", String.class)) {
muteCommands.add(s.toLowerCase(Locale.ENGLISH));
}
}
@@ -415,23 +470,8 @@ public class Settings implements net.ess3.api.ISettings {
}
@Override
public ConfigurationSection getKits() {
return ess.getKits().getKits();
}
@Override
public Map<String, Object> getKit(final String name) {
return ess.getKits().getKit(name);
}
@Override
public void addKit(final String name, final List<String> lines, final long delay) {
ess.getKits().addKit(name, lines, delay);
}
@Override
public ConfigurationSection getKitSection() {
return config.getConfigurationSection("kits");
public CommentedConfigurationNode getKitSection() {
return config.getSection("kits");
}
@Override
@@ -528,6 +568,7 @@ public class Settings implements net.ess3.api.ISettings {
mFormat = mFormat.replace("{PREFIX}", "{6}");
mFormat = mFormat.replace("{SUFFIX}", "{7}");
mFormat = mFormat.replace("{USERNAME}", "{8}");
mFormat = mFormat.replace("{NICKNAME}", "{9}");
mFormat = "§r".concat(mFormat);
chatFormats.put(group, mFormat);
}
@@ -537,6 +578,24 @@ public class Settings implements net.ess3.api.ISettings {
return mFormat;
}
@Override
public String getWorldAlias(String world) {
return worldAliases.getOrDefault(world.toLowerCase(), world);
}
private Map<String, String> _getWorldAliases() {
final Map<String, String> map = new HashMap<>();
final CommentedConfigurationNode section = config.getSection("chat.world-aliases");
if (section == null) {
return map;
}
for (Map.Entry<String, CommentedConfigurationNode> entry : ConfigurateUtil.getMap(section).entrySet()) {
map.put(entry.getKey().toLowerCase(), FormatUtil.replaceFormat(entry.getValue().getString()));
}
return map;
}
@Override
public boolean getAnnounceNewPlayers() {
return !config.getString("newbies.announce-format", "-").isEmpty();
@@ -564,8 +623,9 @@ public class Settings implements net.ess3.api.ISettings {
@Override
public Map<String, Object> getListGroupConfig() {
if (config.isConfigurationSection("list")) {
final Map<String, Object> values = config.getConfigurationSection("list").getValues(false);
final CommentedConfigurationNode node = config.getSection("list");
if (node != null && node.isMap()) {
final Map<String, Object> values = ConfigurateUtil.getRawMap(node);
if (!values.isEmpty()) {
return values;
}
@@ -582,7 +642,7 @@ public class Settings implements net.ess3.api.ISettings {
@Override
public void reloadConfig() {
config.load();
noGodWorlds = new HashSet<>(config.getStringList("no-god-in-worlds"));
noGodWorlds = new HashSet<>(config.getList("no-god-in-worlds", String.class));
enabledSigns = _getEnabledSigns();
teleportSafety = _isTeleportSafetyEnabled();
forceDisableTeleportSafety = _isForceDisableTeleportSafety();
@@ -595,14 +655,66 @@ public class Settings implements net.ess3.api.ISettings {
getFreezeAfkPlayers = _getFreezeAfkPlayers();
sleepIgnoresAfkPlayers = _sleepIgnoresAfkPlayers();
afkListName = _getAfkListName();
isAfkListName = !afkListName.equalsIgnoreCase("none");
isAfkListName = afkListName != null && !afkListName.equalsIgnoreCase("none");
broadcastAfkMessage = _broadcastAfkMessage();
itemSpawnBl = _getItemSpawnBlacklist();
loginAttackDelay = _getLoginAttackDelay();
signUsePerSecond = _getSignUsePerSecond();
chatFormats.clear();
changeDisplayName = _changeDisplayName();
disabledCommands = getDisabledCommands();
disabledCommands = _getDisabledCommands();
overriddenCommands = _getOverriddenCommands();
playerCommands = _getPlayerCommands();
// This will be late loaded
if (ess.getKnownCommandsProvider() != null) {
boolean mapModified = false;
if (!disabledBukkitCommands.isEmpty()) {
if (isDebug()) {
logger.log(Level.INFO, "Re-adding " + disabledBukkitCommands.size() + " disabled commands!");
}
ess.getKnownCommandsProvider().getKnownCommands().putAll(disabledBukkitCommands);
disabledBukkitCommands.clear();
mapModified = true;
}
for (final String command : disabledCommands) {
final String effectiveAlias = command.toLowerCase(Locale.ENGLISH);
final Command toDisable = ess.getPluginCommand(effectiveAlias);
if (toDisable != null) {
if (isDebug()) {
logger.log(Level.INFO, "Attempting removal of " + effectiveAlias);
}
final Command removed = ess.getKnownCommandsProvider().getKnownCommands().remove(effectiveAlias);
if (removed != null) {
if (isDebug()) {
logger.log(Level.INFO, "Adding command " + effectiveAlias + " to disabled map!");
}
disabledBukkitCommands.put(effectiveAlias, removed);
}
// This is 2 because Settings are reloaded twice in the startup lifecycle
if (reloadCount.get() < 2) {
ess.scheduleSyncDelayedTask(() -> _addAlternativeCommand(effectiveAlias, toDisable));
} else {
_addAlternativeCommand(effectiveAlias, toDisable);
}
mapModified = true;
}
}
if (mapModified) {
if (isDebug()) {
logger.log(Level.INFO, "Syncing commands");
}
if (reloadCount.get() < 2) {
ess.scheduleSyncDelayedTask(() -> ess.getSyncCommandsProvider().syncCommands());
} else {
ess.getSyncCommandsProvider().syncCommands();
}
}
}
nicknamePrefix = _getNicknamePrefix();
operatorColor = _getOperatorColor();
changePlayerListName = _changePlayerListName();
@@ -630,6 +742,8 @@ public class Settings implements net.ess3.api.ISettings {
isCustomJoinMessage = !customJoinMessage.equals("none");
customQuitMessage = _getCustomQuitMessage();
isCustomQuitMessage = !customQuitMessage.equals("none");
customNewUsernameMessage = _getCustomNewUsernameMessage();
isCustomNewUsernameMessage = !customNewUsernameMessage.equals("none");
muteCommands = _getMuteCommands();
spawnOnJoinGroups = _getSpawnOnJoinGroups();
commandCooldowns = _getCommandCooldowns();
@@ -637,7 +751,7 @@ public class Settings implements net.ess3.api.ISettings {
currencyFormat = _getCurrencyFormat();
unprotectedSigns = _getUnprotectedSign();
defaultEnabledConfirmCommands = _getDefaultEnabledConfirmCommands();
teleportBackWhenFreedFromJail = _isTeleportBackWhenFreedFromJail();
teleportWhenFreePolicy = _getTeleportWhenFreePolicy();
isCompassTowardsHomePerm = _isCompassTowardsHomePerm();
isAllowWorldInBroadcastworld = _isAllowWorldInBroadcastworld();
itemDbType = _getItemDbType();
@@ -650,6 +764,10 @@ public class Settings implements net.ess3.api.ISettings {
removeEffectsOnHeal = _isRemovingEffectsOnHeal();
vanishingItemPolicy = _getVanishingItemsPolicy();
bindingItemPolicy = _getBindingItemsPolicy();
currencySymbol = _getCurrencySymbol();
worldAliases = _getWorldAliases();
reloadCount.incrementAndGet();
}
void _lateLoadItemSpawnBlacklist() {
@@ -663,6 +781,7 @@ public class Settings implements net.ess3.api.ISettings {
private List<Material> _getItemSpawnBlacklist() {
final List<Material> epItemSpwn = new ArrayList<>();
//noinspection deprecation
final IItemDb itemDb = ess.getItemDb();
if (itemDb == null || !itemDb.isReady()) {
logger.log(Level.FINE, "Skipping item spawn blacklist read; item DB not yet loaded.");
@@ -693,7 +812,7 @@ public class Settings implements net.ess3.api.ISettings {
final List<EssentialsSign> newSigns = new ArrayList<>();
for (String signName : config.getStringList("enabledSigns")) {
for (String signName : config.getList("enabledSigns", String.class)) {
signName = signName.trim().toUpperCase(Locale.ENGLISH);
if (signName.isEmpty()) {
continue;
@@ -751,11 +870,20 @@ public class Settings implements net.ess3.api.ISettings {
return config.getString("locale", "");
}
//This method should always only return one character due to the implementation of the calling methods
//If you need to use a string currency, for example "coins", use the translation key 'currency'.
private String currencySymbol = "$";
// A valid currency symbol value must be one non-integer character.
private String _getCurrencySymbol() {
String value = config.getString("currency-symbol", "$").trim();
if (value.length() > 1 || value.matches("\\d")) {
value = "$";
}
return value;
}
@Override
public String getCurrencySymbol() {
return config.getString("currency-symbol", "$").concat("$").substring(0, 1).replaceAll("[0-9]", "$");
return currencySymbol;
}
@Override
@@ -829,7 +957,7 @@ public class Settings implements net.ess3.api.ISettings {
}
private BigDecimal _getMaxMoney() {
return config.getBigDecimal("max-money", MAXMONEY);
return config.getBigDecimal("max-money", DEFAULT_MAX_MONEY);
}
@Override
@@ -838,7 +966,7 @@ public class Settings implements net.ess3.api.ISettings {
}
private BigDecimal _getMinMoney() {
BigDecimal min = config.getBigDecimal("min-money", MINMONEY);
BigDecimal min = config.getBigDecimal("min-money", DEFAULT_MIN_MONEY);
if (min.signum() > 0) {
min = min.negate();
}
@@ -896,6 +1024,11 @@ public class Settings implements net.ess3.api.ISettings {
return changePlayerListName;
}
@Override
public boolean changeTabCompleteName() {
return config.getBoolean("change-tab-complete-name", false);
}
@Override
public boolean useBukkitPermissions() {
return config.getBoolean("use-bukkit-permissions", false);
@@ -974,6 +1107,11 @@ public class Settings implements net.ess3.api.ISettings {
return config.getBoolean("cancel-afk-on-interact", true);
}
@Override
public boolean cancelAfkOnChat() {
return config.getBoolean("cancel-afk-on-chat", true);
}
@Override
public boolean sleepIgnoresAfkPlayers() {
return sleepIgnoresAfkPlayers;
@@ -983,6 +1121,11 @@ public class Settings implements net.ess3.api.ISettings {
return config.getBoolean("sleep-ignores-afk-players", true);
}
@Override
public boolean sleepIgnoresVanishedPlayers() {
return config.getBoolean("sleep-ignores-vanished-player", true);
}
public String _getAfkListName() {
return FormatUtil.replaceFormat(config.getString("afk-list-name", "none"));
}
@@ -1121,6 +1264,11 @@ public class Settings implements net.ess3.api.ISettings {
return config.getLong("tpa-accept-cancellation", 120);
}
@Override
public int getTpaMaxRequests() {
return config.getInt("tpa-max-requests", 5);
}
private long _getTeleportInvulnerability() {
return config.getLong("teleport-invulnerability", 0) * 1000;
}
@@ -1261,6 +1409,25 @@ public class Settings implements net.ess3.api.ISettings {
return isCustomQuitMessage;
}
public String _getCustomNewUsernameMessage() {
return FormatUtil.replaceFormat(config.getString("custom-new-username-message", "none"));
}
@Override
public String getCustomNewUsernameMessage() {
return customNewUsernameMessage;
}
@Override
public boolean isCustomNewUsernameMessage() {
return isCustomNewUsernameMessage;
}
@Override
public boolean isCustomServerFullMessage() {
return config.getBoolean("use-custom-server-full-message", true);
}
@Override
public int getJoinQuitMessagePlayerCount() {
return config.getInt("hide-join-quit-messages-above", -1);
@@ -1335,12 +1502,12 @@ public class Settings implements net.ess3.api.ISettings {
public List<String> _getSpawnOnJoinGroups() {
final List<String> def = Collections.emptyList();
if (config.isSet("spawn-on-join")) {
if (config.hasProperty("spawn-on-join")) {
if (config.isList("spawn-on-join")) {
return new ArrayList<>(config.getStringList("spawn-on-join"));
return new ArrayList<>(config.getList("spawn-on-join", String.class));
} else if (config.isBoolean("spawn-on-join")) { // List of [*] to make all groups go to spawn on join.
// This also maintains backwards compatibility with initial impl of single boolean value.
return config.getBoolean("spawn-on-join") ? Collections.singletonList("*") : def;
return config.getBoolean("spawn-on-join", true) ? Collections.singletonList("*") : def;
}
// Take whatever the value is, convert to string and add it to a list as a single value.
final String val = config.get("spawn-on-join").toString();
@@ -1356,7 +1523,7 @@ public class Settings implements net.ess3.api.ISettings {
}
@Override
public boolean isUserInSpawnOnJoinGroup(final IUser user) {
public boolean isUserInSpawnOnJoinGroup(@SuppressWarnings("deprecation") final IUser user) {
for (final String group : this.spawnOnJoinGroups) {
if (group.equals("*") || user.inGroup(group)) {
return true;
@@ -1371,12 +1538,14 @@ public class Settings implements net.ess3.api.ISettings {
}
private Map<Pattern, Long> _getCommandCooldowns() {
if (!config.isConfigurationSection("command-cooldowns")) {
final CommentedConfigurationNode section = config.getSection("command-cooldowns");
if (section == null) {
return null;
}
final ConfigurationSection section = config.getConfigurationSection("command-cooldowns");
final Map<Pattern, Long> result = new LinkedHashMap<>();
for (String cmdEntry : section.getKeys(false)) {
for (Map.Entry<String, Object> entry : ConfigurateUtil.getRawMap(section).entrySet()) {
String cmdEntry = entry.getKey();
Object value = entry.getValue();
Pattern pattern = null;
/* ================================
@@ -1394,14 +1563,13 @@ public class Settings implements net.ess3.api.ISettings {
cmdEntry = cmdEntry.substring(1);
}
final String cmd = cmdEntry
.replaceAll("\\*", ".*"); // Wildcards are accepted as asterisk * as known universally.
.replaceAll("\\*", ".*"); // Wildcards are accepted as asterisk * as known universally.
pattern = Pattern.compile(cmd + "( .*)?"); // This matches arguments, if present, to "ignore" them from the feature.
}
/* ================================
* >> Process cooldown value
* ================================ */
Object value = section.get(cmdEntry);
if (value instanceof String) {
try {
value = Double.parseDouble(value.toString());
@@ -1469,7 +1637,7 @@ public class Settings implements net.ess3.api.ISettings {
private NumberFormat _getCurrencyFormat() {
final String currencyFormatString = config.getString("currency-format", "#,##0.00");
final String symbolLocaleString = config.getString("currency-symbol-format-locale");
final String symbolLocaleString = config.getString("currency-symbol-format-locale", null);
final DecimalFormatSymbols decimalFormatSymbols;
if (symbolLocaleString != null) {
decimalFormatSymbols = DecimalFormatSymbols.getInstance(Locale.forLanguageTag(symbolLocaleString));
@@ -1499,7 +1667,7 @@ public class Settings implements net.ess3.api.ISettings {
private List<EssentialsSign> _getUnprotectedSign() {
final List<EssentialsSign> newSigns = new ArrayList<>();
for (String signName : config.getStringList("unprotected-sign-names")) {
for (String signName : config.getList("unprotected-sign-names", String.class)) {
signName = signName.trim().toUpperCase(Locale.ENGLISH);
if (signName.isEmpty()) {
continue;
@@ -1513,11 +1681,21 @@ public class Settings implements net.ess3.api.ISettings {
return newSigns;
}
@Override
public boolean isKitAutoEquip() {
return config.getBoolean("kit-auto-equip", false);
}
@Override
public boolean isPastebinCreateKit() {
return config.getBoolean("pastebin-createkit", false);
}
@Override
public boolean isUseBetterKits() {
return config.getBoolean("use-nbt-serialization-in-createkit", false);
}
@Override
public boolean isAllowBulkBuySell() {
return config.getBoolean("allow-bulk-buy-sell", false);
@@ -1564,7 +1742,7 @@ public class Settings implements net.ess3.api.ISettings {
}
private List<String> _getDefaultEnabledConfirmCommands() {
final List<String> commands = config.getStringList("default-enabled-confirm-commands");
final List<String> commands = config.getList("default-enabled-confirm-commands", String.class);
for (int i = 0; i < commands.size(); i++) {
commands.set(i, commands.get(i).toLowerCase());
}
@@ -1581,13 +1759,32 @@ public class Settings implements net.ess3.api.ISettings {
return getDefaultEnabledConfirmCommands().contains(commandName.toLowerCase());
}
private boolean _isTeleportBackWhenFreedFromJail() {
return config.getBoolean("teleport-back-when-freed-from-jail", true);
private TeleportWhenFreePolicy _getTeleportWhenFreePolicy() {
if (config.hasProperty("teleport-back-when-freed-from-jail")) {
return config.getBoolean("teleport-back-when-freed-from-jail", true) ? TeleportWhenFreePolicy.BACK : TeleportWhenFreePolicy.OFF;
}
if (config.hasProperty("teleport-when-freed")) {
// snakeyaml more like cursedyaml
final String value = config.getString("teleport-when-freed", "back").replace("false", "off");
try {
return TeleportWhenFreePolicy.valueOf(value.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
throw new RuntimeException("Invalid value \"" + value + "\" for config option \"teleport-when-freed\"!", e);
}
}
return TeleportWhenFreePolicy.BACK;
}
@Override
public boolean isTeleportBackWhenFreedFromJail() {
return teleportBackWhenFreedFromJail;
public TeleportWhenFreePolicy getTeleportWhenFreePolicy() {
return teleportWhenFreePolicy;
}
@Override
public boolean isJailOnlineTime() {
return config.getBoolean("jail-online-time", false);
}
private boolean _isCompassTowardsHomePerm() {
@@ -1659,7 +1856,7 @@ public class Settings implements net.ess3.api.ISettings {
private Set<Predicate<String>> _getNickBlacklist() {
final Set<Predicate<String>> blacklist = new HashSet<>();
config.getStringList("nick-blacklist").forEach(entry -> {
config.getList("nick-blacklist", String.class).forEach(entry -> {
try {
blacklist.add(Pattern.compile(entry).asPredicate());
} catch (final PatternSyntaxException e) {
@@ -1707,4 +1904,19 @@ public class Settings implements net.ess3.api.ISettings {
public boolean infoAfterDeath() {
return config.getBoolean("send-info-after-death", false);
}
@Override
public boolean isRespawnAtBed() {
return config.getBoolean("respawn-at-home-bed", true);
}
@Override
public boolean isUpdateCheckEnabled() {
return config.getBoolean("update-check", true);
}
@Override
public boolean showZeroBaltop() {
return config.getBoolean("show-zero-baltop", true);
}
}
@@ -5,6 +5,7 @@ import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
import com.earth2me.essentials.utils.EnumUtil;
import com.earth2me.essentials.utils.LocationUtil;
import com.earth2me.essentials.utils.StringUtil;
import com.earth2me.essentials.utils.VersionUtil;
import net.ess3.api.IEssentials;
import org.bukkit.Location;
import org.bukkit.Material;
@@ -102,7 +103,7 @@ public final class SpawnMob {
// This method spawns a mob at loc, owned by target
public static void spawnmob(final IEssentials ess, final Server server, final CommandSource sender, final User target, final Location loc, final List<String> parts, final List<String> data, int mobCount) throws Exception {
final Location sloc = LocationUtil.getSafeDestination(loc);
final Location sloc = LocationUtil.getSafeDestination(ess, loc);
for (final String part : parts) {
final Mob mob = Mob.fromName(part);
@@ -205,6 +206,11 @@ public final class SpawnMob {
final EntityEquipment invent = ((LivingEntity) spawned).getEquipment();
if (inputData.contains("noarmor") || inputData.contains("noarmour")) {
invent.clear();
} else if (inputData.contains("netherite") && VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_16_1_R01)) {
invent.setBoots(new ItemStack(Material.NETHERITE_BOOTS, 1));
invent.setLeggings(new ItemStack(Material.NETHERITE_LEGGINGS, 1));
invent.setChestplate(new ItemStack(Material.NETHERITE_CHESTPLATE, 1));
invent.setHelmet(new ItemStack(Material.NETHERITE_HELMET, 1));
} else if (inputData.contains("diamond")) {
invent.setBoots(new ItemStack(Material.DIAMOND_BOOTS, 1));
invent.setLeggings(new ItemStack(Material.DIAMOND_LEGGINGS, 1));
@@ -145,7 +145,7 @@ public class Teleport implements ITeleport {
teleportee.getBase().eject();
}
if (LocationUtil.isBlockUnsafeForUser(teleportee, loc.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())) {
if (LocationUtil.isBlockUnsafeForUser(ess, teleportee, loc.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())) {
if (ess.getSettings().isTeleportSafetyEnabled()) {
if (ess.getSettings().isForceDisableTeleportSafety()) {
PaperLib.teleportAsync(teleportee.getBase(), loc, cause);
Loaded 100 of 693 files, more files were not shown because too many files have changed in this diff. Show more