441 Commits
Author SHA1 Message Date
okx-code e2f0e609e9 this breaks the nice geometric series
this was also supposed to be decremented when the 1m/s sky buff was added
2024-01-15 17:52:33 +00:00
okx-code 13a22b6f14 Fix copper oxidising not as much as it should 2024-01-10 23:49:40 +00:00
okx-code 863cae02d3 rails shouldn't oxidise naturally 2023-12-21 04:31:58 +00:00
okx-code af9e2cfb44 fix default 2023-12-13 01:13:46 +00:00
okx-code 04f28a3d39 add faster speeds if exposed to the sky 2023-12-13 01:12:58 +00:00
okx-code 358a2aeb54 ignore cancelled (better citadel interaction) 2023-12-13 00:22:29 +00:00
okx-code edc7e08272 update damage 2023-12-13 00:18:16 +00:00
okx-code 3fd3020cc2 Add BetterRails, FasterHorses, RailDeoxidise, and miscellaneous things
- BetterRails lets you change rail speed based on the block below.
- FasterHorses increases the maximum speed of horses
- CopperRail lets you deoxidise a block below a rail by right-clicking on the rail, and oxidises copper when a minecart goes over it
- Fixed StriderBreeding randomness calculations
- Order /sah list alphabetically
- Actually disable hacks that should be disabled
2023-12-13 00:16:01 +00:00
wingzero54 c350c4f79c Merge pull request #72 from CivMC/wingzero54-patch-2
Update version
2023-07-16 09:43:33 -05:00
wingzero54 20c459210f Update version 2023-07-16 09:43:23 -05:00
AngrySoundTech 89e21d73f5 Merge pull request #70 from Protonull/ott-fix
Overhauling OTT
2023-06-19 13:06:16 -04:00
Alexander 446b15f47b Make suggested changes 2023-06-16 00:27:35 +01:00
Alexander 7ed328ea69 Inline OTT's isRestrictedDestination method
Then more specific responses can be sent.
2023-06-15 19:40:36 +01:00
Alexander b1c3d2321e Minor OTT fixup
The OTT request message now suggests the accept command rather than sending it, just in case this can be abused.

The blacklisted and unsafe materials are already immutable, so there's no point in wrapping them.

Renamed isSafeLocation to isRestrictedDestination for clarity and flipped the logic.

Sends a reject message to the rejector.

Logs to the console when an OTT is granted.

Limit OTT commands to other players.
2023-06-15 19:11:09 +01:00
Alexander f58126fbd2 Initialise commands before hacks
Also separates enabling hacks out of loadAllHacks, since loading != enabling.
2023-06-15 19:10:28 +01:00
Alexander 38522a98c2 Remove old OTT command from plugin.yml 2023-06-15 15:56:05 +01:00
Alexander a10b207de3 Add OTT grant command, add syntaxes, and print teleports to console 2023-06-15 02:34:59 +01:00
Alexander f5609cba46 Reduce duplicate code in OTT
Defers the shared OTT permissibility checks to a method and the returned enum is checked against to give the appropriate response messages.
2023-06-15 01:32:08 +01:00
Alexander 9ab2aad3f6 Add ExilePearl as a dependency 2023-06-15 01:02:17 +01:00
Alexander 0d1ee9f7c2 Improve OTT commands 2023-06-14 02:17:54 +01:00
Alexander 557b47e4ea Add config option to OTT to limit to same world 2023-06-14 02:06:50 +01:00
Alexander 332e701b89 Check pearled status in OTT 2023-06-14 02:01:45 +01:00
Alexander c271bf848a Initial fix 2023-06-14 01:32:30 +01:00
wingzero54 6439102f9c Merge pull request #68 from CivMC/wingzero54-patch-1
Bump to 2.2.2
2023-05-31 09:16:07 -05:00
wingzero54 53c8cbf1f7 Bump to 2.2.2 2023-05-31 09:15:55 -05:00
wingzero54 a99d68e3b3 Merge pull request #65 from CivMC/OTT
Add OTT hack to SAH
2023-05-31 09:14:28 -05:00
wingzero54 6104b7d80f Merge pull request #67 from CivMC/RedDevel2-patch-1
Turn safeorebreak off by default.
2023-05-31 09:10:54 -05:00
RedDevel2 7ecb9ecd7a Turn safeorebreak off by default.
Set default state for SafeOreBreak ores to disabled.
2023-05-29 22:12:07 +02:00
RedDevel2 5a12262e74 Add bastion dependancy
For OTT
2023-05-16 17:54:41 +02:00
RedDevel2 3fc3d5d7eb Create OneTimeTeleportConfig.java 2023-05-11 22:06:03 +02:00
RedDevel2 8044fda7c7 Add OTT to config.yml 2023-05-11 22:03:14 +02:00
RedDevel2 273ff0ec0e Add OTT to plugin.yml 2023-05-11 22:01:51 +02:00
RedDevel2 7e2d1225a5 Add OneTimeTeleport.java
Add OTT to SAH
2023-05-11 21:56:26 +02:00
AngrySoundTech 22a7cf2e18 Bump version to 2.2.1 2023-04-25 14:00:57 -04:00
AngrySoundTech dc345c04c6 Merge pull request #63 from awoo-civ/pr1
Notify the player on every failed ore break attempt
2023-04-21 12:26:23 -04:00
awoo-civ 14869c9da9 Notify the player on every failed ore break attempt 2023-04-11 02:55:14 +00:00
AngrySoundTech fc0e9ca9dd Merge branch 'master' of ast.github.com:CivMC/SimpleAdminHacks 2023-04-04 17:10:13 -04:00
AngrySoundTech 95907238c9 Update civgradle and bump version to 2.2.0 2023-04-04 17:10:07 -04:00
AngrySoundTech 2ed7225a41 Merge pull request #61 from okx-code/safe-ore-break
Add safe ore break
2023-03-27 09:53:44 -04:00
okx-code 8ca971c160 Use array 2023-03-23 23:37:15 +00:00
okx-code 71c2e1c663 fix print 2023-03-22 02:41:11 +00:00
okx-code 0c8a5c9a72 Simplify code 2023-03-22 02:36:59 +00:00
okx-code 2edbe1b0a9 add early return 2023-03-22 01:05:40 +00:00
okx-code 32401ad468 add safe ore break 2023-03-22 01:03:32 +00:00
AngrySoundTech 5347466bd4 Update build.gradle.kts 2022-09-05 12:49:48 -04:00
AngrySoundTech a010d56573 Merge pull request #50 from Diet-Cola/bound-fixes
Bound fixes
2022-09-05 12:49:35 -04:00
Cola 3b9d9bce18 Fix first item being unbound + bound item 2022-09-05 17:37:38 +01:00
Cola 168657a858 Check if the second item has key 2022-09-05 16:53:20 +01:00
Cola 2cd0b18aea Update inventory
Weird bug with first item?
2022-09-05 16:32:13 +01:00
Cola 0775bb0d91 Items MUST have matching uuids 2022-09-05 16:21:54 +01:00
AngrySoundTech ada854607d Update build.gradle.kts 2022-09-05 10:45:31 -04:00
AngrySoundTech 68dd8e0267 Merge pull request #49 from Diet-Cola/bound-fixes
Check anvil if the second item has armour bound pdc
2022-09-05 10:44:58 -04:00
Cola 3d27999357 Check anvil if the second item has armour bound pdc 2022-08-30 23:24:58 +01:00
wingzero54 1193ac92c9 version 2022-08-17 10:34:00 -05:00
wingzero54 88056253a5 Merge pull request #47 from Aleksey-Terzi/errors
Fix exceptions
2022-08-17 10:01:23 -05:00
wingzero54 5f08ba1399 Merge pull request #46 from CivMC/SAH-Grindstone-patch
Disable Grindstone use so they can be placed as decorative blocks
2022-08-17 10:01:03 -05:00
Aleksey Terzi 4e5e7581ee Fix BankStick to BanStick 2022-08-13 20:37:07 -06:00
Aleksey Terzi 088fc4773b Fix exception on enable of StrayStats and BedLocator, add soft dependency to BanStick 2022-08-13 12:04:28 -06:00
Aleksey Terzi 1e019768b5 Merge pull request #4 from CivMC/master
Sync
2022-08-12 20:58:30 -06:00
AhriVG 85e4fcc7d2 Add grindstone setting to config 2022-08-12 16:17:04 -03:00
AhriVG c68aaa6cbc Disable Grindstone use
Copied existing enderchest functionality
2022-08-12 16:09:01 -03:00
wingzero54 5f12e02d69 Version 2022-08-10 09:21:26 -05:00
wingzero54 901fb2abd2 Merge pull request #43 from Diet-Cola/bombing-fixes
Add onEnable to GameFixes and register listener
2022-08-10 09:11:25 -05:00
Aleksey Terzi fd67578db4 Merge pull request #3 from CivMC/master
Sync
2022-08-08 19:48:22 -06:00
Cola 7def3c7be3 Add onEnable to GameFixes and register listener 2022-08-09 00:42:12 +01:00
wingzero54 5590655ddb Merge pull request #22 from Diet-Cola/sapling-chance
Add SaplingHack
2022-07-01 12:57:32 -05:00
wingzero54 d9b69d3802 Merge pull request #23 from Diet-Cola/fix-ab-craft
Prevent Bound Armour being repaired via Crafting
2022-06-27 21:53:02 -05:00
Cola 5e26f234ae Make result null 2022-06-24 13:20:05 +01:00
Cola 6fbc3abc47 Add SaplingHack 2022-06-24 08:53:01 +01:00
Cola 0968fb35c4 Add SaplingHack 2022-06-24 02:39:16 +01:00
Cola 280a182600 Prevent Bound Armour being repaired via Crafting 2022-06-23 13:02:17 +01:00
Cola 02aafdf9b5 Add SaplingHack 2022-06-19 03:53:09 +01:00
AngrySoundTech 927ba898f2 Merge pull request #19 from Diet-Cola/fix-old-enchanting
Change target field
2022-06-13 11:49:28 -04:00
AngrySoundTech e8721db8b4 Merge pull request #17 from Aleksey-Terzi/master
Fix messages for elevator block
2022-06-13 11:48:03 -04:00
AngrySoundTech e097ab457f Merge pull request #21 from Diet-Cola/armour-bound-fixes
Check for metadata to make compat with CTP
2022-06-13 11:47:33 -04:00
Cola 2fc000c2e9 Check for metadata to make compat with CTP 2022-06-12 02:40:30 +01:00
Cola 9e8a217bdf Implement method from paper 2022-06-03 23:44:17 +01:00
Aleksey Terzi c871729a15 Fix messages for GoldBlockElevators 2022-06-02 20:02:14 -06:00
Aleksey Terzi 9c44bf9588 Merge pull request #1 from CivMC/master
Synchronization
2022-06-02 18:32:39 -06:00
AngrySoundTech 99fc5d2b31 Merge pull request #16 from Aleksey-Terzi/master
Make elevator block configurable
2022-06-02 12:23:25 -04:00
AngrySoundTech b41b0fd067 Merge pull request #15 from Diet-Cola/portal-modify
PortalModify Improvements
2022-06-02 08:46:02 -04:00
Aleksey Terzi e1932e2a81 Make elevator block configurable 2022-06-01 20:52:11 -06:00
Cola cb4f4c50d5 Improve logic 2022-05-24 19:33:39 +01:00
Cola d9dbf6ba96 Merge branch 'master' into portal-modify 2022-05-24 19:33:13 +01:00
wingzero54 520a8deb89 Merge pull request #13 from Diet-Cola/portal-modify
Portal feature
2022-05-21 10:10:55 -05:00
Cola fcb398ba1d Latest Portals 2022-05-18 01:58:28 +01:00
Cola cd4d0cfd71 Merge remote-tracking branch 'CivMC/master' into portal-modify 2022-05-13 00:30:04 +01:00
wingzero54 8a64118213 Merge pull request #9 from Gjum/round-player-list-ping
roundPlayerListPing to same 6 levels vanilla displays
2022-04-25 21:39:47 -05:00
wingzero54 c5e7e57b6a Merge pull request #6 from CivMC/1.18.2
1.18.2
2022-04-25 21:33:37 -05:00
Gjum 131e3617d7 roundPlayerListPing to same 6 levels vanilla displays 2022-04-22 17:15:58 +02:00
wingzero54 9135691c26 Merge pull request #8 from okx-code/master
Fix AttrHider not hiding enchants properly
2022-04-11 18:19:53 -05:00
okx-code c1832d04a8 fix enchants on attrhider 2022-04-11 22:51:31 +01:00
wingzero54 dd2701c6b5 Merge pull request #7 from Diet-Cola/bound-armour
Add ArmourBound
2022-03-13 10:42:57 -05:00
Cola 9e19fa6293 Default config, disabled by default 2022-03-10 02:02:06 +00:00
Cola ae47739a13 Clunky but works 2022-03-10 01:50:43 +00:00
Cola eac043ec1d Make a clone of the item in memory, delete the item, then drop the clone 2022-03-10 00:50:48 +00:00
Cola 111b930ea9 Properly set PDC, working 2022-03-10 00:47:43 +00:00
Cola 846a18a397 Latest 2022-03-09 23:39:52 +00:00
Cola d200fd356a Cleanup imports 2022-03-09 21:26:18 +00:00
Cola ec7b60d017 Retrieve the new item ourselves, working 2022-03-09 21:25:48 +00:00
Cola 85fc7ae87f Move warning to constructor, check if item is in whitelist 2022-03-08 16:51:54 +00:00
Cola 61496e1979 Initial ArmourBound attempt, need to test 2022-03-08 16:29:01 +00:00
Cola b1973290b4 1.18.2 2022-03-07 21:52:58 +00:00
AngrySoundTech d419acf751 Merge remote-tracking branch 'origin/master' 2022-01-02 11:48:29 -05:00
AngrySoundTech 3c0ef4cdd4 set executable bit 2022-01-02 11:48:22 -05:00
AngrySoundTech eb376a8633 Merge pull request #5 from CivMC/1.18
Update dependency locations
2022-01-02 11:47:21 -05:00
AngrySoundTech c2c3d5affc Update dependency locations 2022-01-02 11:46:47 -05:00
AngrySoundTech 8767153930 Merge pull request #4 from Diet-Cola/1.18
CivGradle
2022-01-02 11:45:53 -05:00
Cola ae26a331d5 Add workflows 2022-01-02 05:37:06 +00:00
Cola 3f4e3152ae update url 2022-01-02 05:30:49 +00:00
Cola 265362037c 1.18.1 / CivGradle
Errors when parsing config for some reason?
2022-01-02 05:26:38 +00:00
Cola cdb05c75ec Fix ordering 2021-12-27 23:05:18 +00:00
Cola 9e64acd6f1 Fix ori beekeeping & update protocollib 2021-12-22 22:42:08 +00:00
Cola 9125e37d8c part 2 2021-12-22 22:19:36 +00:00
Cola 080ff8e58c World floor is lower now dummy 2021-12-22 22:17:44 +00:00
Cola 5a453b2ed8 Stop commands being registered too early 2021-12-22 20:10:45 +00:00
Cola 614088e75b Fix plugin.yml 2021-12-21 08:26:10 +00:00
Cola 50d3903c19 Gradlefication / 1.18
At the cost of tests
2021-12-21 08:25:08 +00:00
Cola 5931893f28 Fix errors 2021-11-02 05:17:51 +00:00
Cola d6e398a309 Merge remote-tracking branch 'origin/master' into 16+17 2021-11-02 05:14:24 +00:00
wingzero54 9223431c96 Merge pull request #92 from Diet-Cola/toggle-gold-blocks
Fix oopsie
2021-10-25 17:43:19 -05:00
Cola 4d8d8302a7 Fix oopsie 2021-10-24 19:44:33 +01:00
wingzero54 3b91fcc158 Merge pull request #91 from Diet-Cola/anti-statis-chambers
Change where the config is parsed
2021-10-23 10:37:34 -05:00
Cola a108d7e13a Change where the config is parsed 2021-10-23 16:36:09 +01:00
wingzero54 4c61211f51 Merge pull request #89 from Diet-Cola/anti-statis-chambers
Nerf Statis Chambers
2021-10-23 09:20:05 -05:00
wingzero54 33add0491e Merge pull request #90 from Diet-Cola/toggle-gold-blocks
Toggleable Jump/Sneak or Left/Right click gold blocks
2021-10-23 09:19:59 -05:00
Cola 5468d12b5d Plan ahead for 1.18, Double interact fixer 2021-09-27 01:47:48 +01:00
Cola 901eb1317f Make Gold block tps a basic hack 2021-09-27 01:34:06 +01:00
Cola 806d94070d Update dog facts to use SettingsManager 2021-09-27 01:33:50 +01:00
Cola 13b1428aad Address max comments 2021-09-26 14:33:38 +01:00
Cola 6ceb63af9f Change to PDC & removal based on time in flight 2021-09-22 13:07:15 +01:00
Cola f13ef25335 Nerf Statis Chambers 2021-09-22 03:21:02 +01:00
Cola b7a3f5671e Merge remote-tracking branch 'origin/master' into 16+17 2021-09-22 02:53:28 +01:00
wingzero54 7b16512907 Merge pull request #88 from Protonull/elytra-features
Expand upon ElytraFeatures
2021-09-16 23:13:44 -05:00
Alexander 4e55c09912 ElytraFeatures :: convert scalar from boolean to double 2021-09-11 11:21:45 +00:00
Alexander 561d8794e3 Move ElytraFeatures from BasicHack to SimpleHack section 2021-09-11 10:45:37 +00:00
Alexander cfe75015d7 Expand ElytraFeatures 2021-09-11 10:41:07 +00:00
Cola afb1e70fb6 Update imports 2021-09-08 21:04:25 +01:00
Cola 1784d609b0 Update OldEnchanting to not use illegal reflection 2021-09-08 21:02:32 +01:00
Cola e4967f711d 1.17.1
This commit removed the book limiter in SAH, the source of dupes on an old MC version, this is now patched by paper itself.
2021-07-19 00:26:01 +01:00
Cola a425393d8a Merge remote-tracking branch 'origin/master' into 16+17 2021-07-19 00:07:02 +01:00
wingzero54 d7ef6a0c18 Merge pull request #84 from iykHvfIvfR/patch-1
Fix unintuitive gold block error messages
2021-07-17 21:39:53 -05:00
awoo 5df5f0a1d9 Fix unintuitive gold block error messages 2021-07-08 21:55:12 +00:00
wingzero54 7bd5a8881f Merge pull request #80 from Protonull/anti-fast-break-fix
Fix AntiFastBreak spam
2021-07-08 16:37:21 -05:00
Cola a78be7a860 Latest CMC 2021-07-05 20:42:38 +01:00
Cola 31c81e68d6 Fix OldEnchanting's randomiser field 2021-07-02 23:03:56 +01:00
Cola ac81dd3bd8 Fix AttrHider & Make gold block respect 1.17 part 2 2021-07-02 19:32:25 +01:00
Cola 7ec29408b1 Fix commands 2021-07-01 15:24:40 +01:00
Cola f0d92e91d0 v1.5.0 - Java 16 & 1.17 2021-07-01 15:08:53 +01:00
wingzero54 8fdde13fe6 Merge pull request #82 from xFier/master
Add support for condensing mobs and merge all portal-spawned mobs into a single type.
2021-06-25 21:22:58 -05:00
wingzero54 89e39858e5 Merge pull request #83 from Protonull/convert-gamefeatures-weepingangel-ms
Update WeepingAngel to use MS instead of minutes
2021-06-25 11:31:20 -05:00
Alexander 1f6374c9f8 Oops, use milliseconds not microseconds 2021-06-25 16:45:32 +01:00
Alexander 5f3497ed27 Fix ban message saying minutes 2021-06-25 16:39:15 +01:00
Alexander 2efa814ff7 Update WeepingAngel to use MS instead of minutes 2021-06-25 16:04:10 +01:00
Brandon 65511beffe Disable giving piglins armour via a dispenser 2021-06-22 23:06:02 +01:00
Brandon ca0f4d9a68 Only spawn baby eggs from mob eggs with a chance 2021-06-22 23:05:21 +01:00
Brandon 45ef2aecc4 Added a material modification whitelist for mob condensing.
This will mean that only items in the list can be increased/decreased.
2021-06-22 21:22:11 +01:00
Brandon 0643ee0ab5 Use SpawnEggUtils to get the EntityType of the Spawn Egg 2021-06-19 22:32:40 +01:00
Brandon 07b5f15418 Consume mob egg on failed spawn 2021-06-19 20:22:57 +01:00
Brandon 0624bd8af7 Add log message 2021-06-19 20:18:10 +01:00
Brandon 70066459c5 Update MobCondenser to support per-entity values 2021-06-19 19:31:50 +01:00
Brandon 6da18c04b8 Add a Mob Condenser to reduce spawns and increase drops 2021-06-19 15:33:25 +01:00
Brandon 16644cacdb Update PortalSpawnModifier to replace Withers and Ghasts with "simulated" variants. 2021-06-19 04:13:22 +01:00
Alexander 3264c5b124 Merge branch 'master' of https://github.com/CivClassic/SimpleAdminHacks into anti-fast-break-fix 2021-06-18 23:49:46 +01:00
wingzero54 cd18b59e35 Merge pull request #81 from Protonull/elytra-restrictions
Add ElytraFeatures hack
2021-06-18 16:28:37 -05:00
Alexander ac06fef951 oopsie, missed this 2021-06-18 02:53:07 +01:00
Alexander fc25b9c27b Add ElytraFeatures hack 2021-06-18 02:51:15 +01:00
Alexander 182c7e7e50 Fix (#42) 2021-06-16 17:58:44 +01:00
wingzero54 c47e8d9851 Merge pull request #78 from Protonull/patch-1
Fix NPE in ItemMetaConverterHack
2021-06-02 09:41:58 -05:00
Alexander eb8871bca2 Fix NPE 2021-06-02 10:41:09 +01:00
wingzero54 3f634a5142 Merge pull request #77 from Protonull/patch-1
Update CivModCore version to 1.8.3
2021-05-27 10:42:38 -05:00
Alexander f5923dedfb Update CivModCore version to 1.8.3 2021-05-27 16:38:52 +01:00
wingzero54 0037789858 Merge pull request #72 from Protonull/master
Update ItemMetaConverterHack
2021-05-27 10:33:59 -05:00
wingzero54 be5261a9d6 Merge pull request #74 from Protonull/disable-ai-hack
Add DisableAI hack
2021-05-27 10:33:05 -05:00
wingzero54 78a3cfbf09 Merge pull request #75 from Protonull/add-bed-locator
Add BedLocator hack
2021-05-27 10:31:00 -05:00
Alexander 5711d9db7f Add secondary config example for DisableAI hack 2021-05-27 16:29:29 +01:00
Alexander 98cc72b074 Add bed locator hack 2021-05-26 01:18:24 +01:00
Alexander d123f472af Update ItemMetaConverterHack 2021-05-26 00:51:22 +01:00
Alexander 9badfea656 Merge branch 'master' into disable-ai-hack
* master:
  Remove redundant generate function on basic hacks
  Remove UnblockingHack since CraftEnhance replaced it
  Allow hack configs to manually call wireup themselves
  Fix GHSA-269g-pwp5-87pp
  Update StrayStats to use renamed BanStick getter
  Switch EventHandlerList from TextUtil to StringUtils
  Switch BeeKeeping over to Kyori components
  Update to 1.16.5
2021-05-25 03:41:45 +01:00
Alexander 22dbd18ecc Add DisableAI hack 2021-05-25 03:41:31 +01:00
wingzero54 6d5cc52a1e Merge pull request #73 from Protonull/patch-1
Update to 1.16.5
2021-05-24 21:03:03 -05:00
Alexander 7d65f459bf Remove redundant generate function on basic hacks 2021-05-23 22:31:43 +01:00
Alexander 38a3cb1229 Remove UnblockingHack since CraftEnhance replaced it 2021-05-23 22:31:43 +01:00
Alexander c27648318d Allow hack configs to manually call wireup themselves 2021-05-23 22:31:43 +01:00
Alexander c3042f5397 Fix GHSA-269g-pwp5-87pp
https://github.com/advisories/GHSA-269g-pwp5-87pp
2021-05-23 22:31:43 +01:00
Alexander b78afb808f Update StrayStats to use renamed BanStick getter 2021-05-23 22:31:43 +01:00
Alexander 974bb42a84 Switch EventHandlerList from TextUtil to StringUtils 2021-05-23 22:31:36 +01:00
Alexander 1216dd2565 Switch BeeKeeping over to Kyori components 2021-05-23 21:59:20 +01:00
Alexander 562d13c7d2 Update to 1.16.5 2021-05-23 21:27:25 +01:00
wingzero54 e28c761ab2 Merge pull request #68 from Protonull/stats-hack
Statistics Hack
2021-03-25 22:35:21 -05:00
Alexander 1165a2583f Make compilation async 2021-03-24 18:53:41 +00:00
Alexander 87c383b526 Remove NameLayer requirement since it's no longer necessary 2021-03-24 08:46:16 +00:00
Alexander ed975a1e90 Access database directly 2021-03-24 07:59:43 +00:00
Alexander ceb5a4e750 Merge commit 'b138ae5f175106c91a34b3d67c2428d086c6e721' into stats-hack
* commit 'b138ae5f175106c91a34b3d67c2428d086c6e721':
  Framework changes
2021-03-24 07:45:39 +00:00
Alexander 132b4b6248 Add StrayStats 2021-03-24 07:44:38 +00:00
wingzero54 6d7ec368ea Merge pull request #67 from Protonull/unblocker-hack
Unblocker hack
2021-03-21 21:40:42 -05:00
Alexander b138ae5f17 Framework changes 2021-03-22 02:15:40 +00:00
Alexander 94e999e6cc Merged 2021-03-21 21:17:21 +00:00
Alexander decc71effa Add unblocking hack 2021-03-21 21:10:27 +00:00
wingzero54 a3b03a9d78 Merge pull request #64 from TheRealArchie/POMfix
POMfix
2021-03-12 13:17:15 -06:00
wingzero54 8e344f4d43 Merge pull request #63 from Diet-Cola/dogfactsv2
Make DogFacts JSON
2021-03-12 11:18:26 -06:00
Archie a6dcfd10bb POMfix 2021-03-12 13:02:15 +01:00
Cola 313e7e9ad3 Add site used to generate JSON to config 2021-03-12 11:48:59 +00:00
Cola bc24553751 Make DogFacts JSON 2021-03-12 11:41:12 +00:00
wingzero54 aa58cc919a Merge pull request #62 from TheRealArchie/command
Added DisableGapples and Chunklimit command
2021-03-09 13:31:52 -06:00
Archie af1da1fcf4 fixed another mistake 2021-03-09 14:25:21 +01:00
Archie 176a081f6c fixed mistake 2021-03-09 14:20:23 +01:00
Archie 61d3445986 added chunklimit command 2021-03-09 14:12:48 +01:00
Archie 52e7e167ce added chunklimits command 2021-03-09 14:07:39 +01:00
Archie c2ae416c70 added warning message to eating gapples when disableGapples enabled 2021-03-09 12:44:27 +01:00
Archie d407397f4a added disableGapples option 2021-03-09 12:23:54 +01:00
wingzero54 daf6dad716 Stop console spam with debug 2021-03-02 16:00:00 -06:00
wingzero54 60a451de97 Merge pull request #59 from TheOrangeWizard/patch-1
fix portal skeletons to use same spawn method as ghasts
2021-02-26 15:36:14 -06:00
orange wizard 97f10075e5 fix stupid 2021-02-27 10:22:22 +13:00
orange wizard e9731a64dc fix portal skeletons to use same spawn method as ghasts 2021-02-26 11:21:29 +13:00
wingzero54 abaa692f25 Merge pull request #58 from Diet-Cola/classics-fix
Fix a derp
2021-02-23 15:08:01 -06:00
Cola dcbf499a4b Less spammy 2021-02-22 20:45:55 +00:00
Cola 0ca6cac405 Let's not send console a message for every person we announce to 2021-02-22 20:38:24 +00:00
Cola 625cd6cbee Tell console we're doing dogfact & fix a derp 2021-02-22 20:30:51 +00:00
wingzero54 2fa7f27110 Merge pull request #55 from Diet-Cola/dogfacts
Add DogFacts
2021-02-20 15:05:32 -06:00
wingzero54 92743bb78f Merge pull request #54 from Diet-Cola/respawn_kit
Add option to spawn IntroKit for those who randomspawn
2021-02-20 15:04:57 -06:00
wingzero54 c1699522be Merge pull request #52 from Diet-Cola/strider-breeding
Strider Breeding
2021-02-20 15:00:12 -06:00
wingzero54 01b0b86c41 Merge pull request #51 from Diet-Cola/ftb_elevators
Change Gold Block TPs up and down
2021-02-20 11:39:50 -06:00
Cola cf487aaae3 Tested & Working 2021-01-31 23:09:53 +00:00
Cola c890e33b7b Convert to local variables 2021-01-31 21:38:50 +00:00
Cola 9d0b683cec Changes 2021-01-31 16:00:33 +00:00
Cola 5eafdb5c3e Move settings setup 2021-01-30 23:02:58 +00:00
Cola 24b917bf38 DogFacts! 2021-01-30 22:52:40 +00:00
Cola 60df84bd9c Add checks to make sure within bounds 2021-01-26 10:17:45 +00:00
Cola 27b8385d51 Add option to spawn IntroKit for those who randomspawn 2021-01-25 16:00:31 +00:00
Maxopoly 7ed0b35181 Merge pull request #53 from Protonull/main
Fix enable bug + misc
2021-01-17 06:22:00 +01:00
Alexander c2073ff43b Legacy hack quick fix
This is an unfortunate and innocent consequence of the framework update, which was intended to be as unobtrusive as possible and simply update management of hacks behind the scenes. Part of the update was to move the enabled state away from the config, so the enabled value is only used to determine whether the hack *should* be enabled, not whether it *is* enabled. And so since TimingsHacks is disabled by default (I assume) and is riddled with `if (config.isEnabled()) return;` means that it's enabled but nothing happens. This introduces a quick fix, adding legacy support for `this.config.isEnabled()` state tracking though making it explicitly deprecated. That being said, most of the hacks are in dire need of an update and clean up.
2021-01-16 18:04:27 +00:00
Alexander abd5f66d05 BeeKeeping # Do not calculate spawn chance if spawning is assured 2021-01-16 17:15:40 +00:00
Alexander 55f7928357 NewfriendAssist # Add SessionTime as a child class to reduce clutter 2021-01-16 17:14:54 +00:00
Alexander 4ff5a8b32d config.yml # Add missing values to config 2021-01-16 17:14:23 +00:00
Alexander 61db049346 Merge branch 'master' of https://github.com/CivClassic/SimpleAdminHacks into main
* 'master' of https://github.com/CivClassic/SimpleAdminHacks:
2021-01-16 17:12:48 +00:00
Cola 8574186f40 Add comment 2021-01-15 08:03:24 +00:00
Cola 3f87cfaf4d Integrate HorseStats to Striders 2021-01-13 21:19:34 +00:00
Cola edbf658d13 Latest 2021-01-12 23:21:20 +00:00
Alexander 05747e5025 Remove orphaned hack configs 2021-01-11 05:24:30 +00:00
Cola e7fc2004b4 Add sounds + fix Amels comment 2021-01-10 22:29:34 +00:00
Cola b06dea82ea Make closer to vanilla 2021-01-10 13:41:46 +00:00
Cola 227712b906 Update max health 2021-01-10 13:18:31 +00:00
Cola 21215dbf3f Change Gold Block TPs
Jump = Go up
Sneak = Go down

Thanks to Papers PlayerJumpEvent we can make this a lot more like FTB mods and also more intuitive imo
2021-01-10 13:10:35 +00:00
Maxopoly f906a35a78 Merge pull request #50 from Protonull/main
BEEEEEEEEEEEEEEEES
2021-01-09 01:21:48 +01:00
Alexander 493e2003ea BeeKeeping # Remove flower check 2021-01-09 00:20:16 +00:00
Alexander f7abb05238 config.yml # Fix typo 2021-01-09 00:19:22 +00:00
Cola da7114572b Adds parent importance 2021-01-08 23:13:02 +00:00
Cola b16cf4ec6f Add config option 2021-01-08 19:01:38 +00:00
Cola 9ffc1f39ac Add methods
Still needs work to use parent stats
2021-01-08 18:39:31 +00:00
Cola f2f17f30dc Add file 2021-01-07 10:04:55 +00:00
Alexander c497cb3189 config.yml # Updating BeeKeeping config comments 2021-01-07 03:12:28 +00:00
Alexander 336ce09761 BeeKeeping # Add configurable spawning and combat 2021-01-07 03:03:08 +00:00
Alexander 59e6e58e10 De-deprecate new GameFeatures stuff 2021-01-06 04:39:23 +00:00
Alexander 3801c9f157 Merge branch 'master' of https://github.com/CivClassic/SimpleAdminHacks into main
* 'master' of https://github.com/CivClassic/SimpleAdminHacks:
  Change default option to match config
  Add msg and Improve logic
  Cancel vanilla netherite crafting
2021-01-06 04:36:43 +00:00
Alexander 97831bd19b BEEEEEEEEEEEEEEEES 2021-01-06 04:35:34 +00:00
Alexander 4b4405ad80 Upgrade to CivModCore 1.8.2 2021-01-05 23:25:05 +00:00
Maxopoly 509f6601ab Merge pull request #49 from Diet-Cola/master
Cancel vanilla netherite crafting
2021-01-04 01:41:02 +01:00
Cola 6acc4bcdb3 Change default option to match config 2021-01-03 22:59:02 +00:00
Cola b67e9c817a Add msg and Improve logic 2021-01-02 19:58:26 +00:00
Cola 7cf625c49a Cancel vanilla netherite crafting
Thanks ori & max
2021-01-02 19:34:53 +00:00
Alexander d76edcd5ba AttrHider # Fix Entity Equipment for 1.16.4 packet 2021-01-01 16:59:36 +00:00
Maxopoly f6875720e1 Update to 1.16.4 2021-01-01 03:11:07 +01:00
Maxopoly 12bb3dad81 Merge pull request #47 from Protonull/main
Update SAH and merge AttrHider and OldEnchanting
2021-01-01 02:13:55 +01:00
Alexander af2b564a81 DebugWand # Update 2020-12-31 15:37:56 +00:00
Alexander 849521144e Cleanup 2020-12-31 15:37:36 +00:00
Alexander 1f9ca0ee08 AttrHider # Fix half heart bug 2020-12-30 12:27:31 +00:00
Alexander 3e3d29a20b OldEnchanting # Fix interaction bug 2020-12-29 23:49:12 +00:00
Alexander 364d2de942 GameFixes # Make the hard book limit actually work 2020-12-29 22:47:49 +00:00
Alexander 889b8734dd AntiFastBreak # Update this mess of a hack 2020-12-28 15:17:20 +00:00
Alexander 0997c40f57 Update README 2020-12-28 15:12:51 +00:00
Alexander 4cceb7a48d POM # Use Paper server instead 2020-12-28 15:06:19 +00:00
Alexander 4ec22bc42f Merge AttrHider 2020-12-28 15:05:54 +00:00
Alexander 60457e0817 Merge OldEnchanting 2020-12-28 13:21:06 +00:00
Alexander b717f69bdf ShipOutOfLuck # Fix potential bug 2020-12-28 13:17:06 +00:00
Alexander e9b45a387d Flatten EventHandlerList 2020-12-28 13:15:49 +00:00
Alexander fae8b37456 Flatten AutoRespawn 2020-12-28 13:06:46 +00:00
Alexander 662de45731 Okay, no, what are you doing? 2020-12-28 13:05:59 +00:00
Maxopoly cce14e6a45 Enforce book page count 2020-12-28 05:01:10 +01:00
Maxopoly 4629e77b45 Merge pull request #46 from okx-code/master
Fix phantoms
2020-12-28 03:52:11 +01:00
okx-code a8b115b333 fix phantoms 2020-12-28 02:48:17 +00:00
Maxopoly 0abe0a7a16 Explicitly disable ProtocolLib listeners to prevent likely source of deadlock 2020-12-16 12:22:44 +01:00
Maxopoly 90510f61ed Merge pull request #41 from Protonull/main
Framework update + some upgrades
2020-11-28 16:17:00 +01:00
Alexander d8a1e4e7f5 README # Update but still needs Insight description 2020-11-25 17:11:02 +00:00
Alexander 383c77b6dd PlayerStatistics # Fix command annotations 2020-11-25 16:59:03 +00:00
Alexander ee6fd69e6d config.yml # Remove deprecated hacks 2020-11-25 16:58:22 +00:00
Alexander ca96dc6fa5 ShipOutOfLuck # Migrated 2020-11-25 07:04:42 +00:00
Alexander 417459afaf PhantomMenace # Upgrade and convert to BasicHack 2020-11-25 06:59:10 +00:00
Alexander e5a91e6366 PlayerStatistics # Upgrade 2020-11-25 06:48:19 +00:00
Alexander 9c0cb1385e PlayerRevive # Upgrade 2020-11-25 06:46:25 +00:00
Alexander 62dc4127e0 BuffSpanker # Upgrade and convert to BasicHack 2020-11-25 06:44:54 +00:00
Alexander b85b9d5421 EventHandlerList # Upgrade 2020-11-25 06:42:14 +00:00
Alexander 42461a2b74 AutoSpawner # Upgrade and convert to BasicHack 2020-11-25 06:34:10 +00:00
Alexander 0343638c61 config.yml # Reorganised config + added parsing tester 2020-11-25 06:25:05 +00:00
Alexander f26710bb38 Framework update (big scary)
Modernises the boilerplate of SimpleAdminHacks.

The vast majority of changes to hacks should be import optimisations.
2020-11-25 03:45:53 +00:00
Maxopoly c2d5110c75 Merge pull request #35 from Protonull/fix-ice-boat-exploit
Fix ice boat exploit + Misc
2020-11-25 01:22:23 +01:00
Alexander 5935302ae7 ShipOutOfLuck # Few minor additions from vc 2020-11-25 00:21:48 +00:00
Alexander b3ad5cc8e3 ShipOutOfLuck # Small optimisations and readability changes 2020-11-24 00:58:40 +00:00
Alexander 8807595f20 Merge branch 'master' of https://github.com/CivClassic/SimpleAdminHacks into fix-ice-boat-exploit
* 'master' of https://github.com/CivClassic/SimpleAdminHacks:
  Feedback is always nice too
  Check we're right clicking respawn anchor
2020-11-23 19:16:14 +00:00
Alexander 762eed1d18 ShipOutOfLuck # Update to bounds check 2020-11-23 19:14:24 +00:00
Maxopoly 64ef171b7d Merge pull request #36 from Diet-Cola/master
Check we're right clicking respawn anchor
2020-11-23 16:54:03 +01:00
Cola 57219547f1 Feedback is always nice too 2020-11-23 05:50:36 +00:00
Cola 84d51e1994 Check we're right clicking respawn anchor 2020-11-23 05:45:41 +00:00
Alexander 4d6eaf7f1d AutoRespawn # Remove Iteration.isNullOrEmpty() 2020-11-17 12:53:50 +00:00
Alexander 431ffd6066 ShipOutOfLuck # Add Incongruous Boats option + BasicHack 2020-11-17 12:53:23 +00:00
Alexander 73ba70856c Small framework changes 2020-11-17 12:52:37 +00:00
Maxopoly 49d8e8b65f Fix antifastbreak for newer clients which dont send start digigng packets on reinforced blocks 2020-11-15 19:00:08 +01:00
Maxopoly a4fa1ef776 Severely improve antifastbreak 2020-11-09 16:13:22 +01:00
Maxopoly 4fd71874a9 Actually fix gold tps probably 2020-08-22 21:25:24 +02:00
Maxopoly 7da58ba1ea Keep pitch and yaw on gold block tp 2020-08-21 13:08:34 +02:00
Maxopoly afd66bdeca More gold tp fixes 2020-08-20 20:30:28 +02:00
Maxopoly da4a208cd6 Do alt lookup for teleport location from bedrock 2020-08-20 19:03:09 +02:00
Maxopoly 85182bd1e0 Dont teleport into blocks 2020-08-20 18:52:30 +02:00
Maxopoly b474c28911 fix gold tps 2020-08-20 18:51:35 +02:00
Maxopoly 51e1e2e22e Merge pull request #31 from squareblob/master
Option to reduce the amount it rains
2020-08-17 21:59:04 +02:00
BuildTools 238db28b3f fix typo 2020-08-16 15:10:52 -04:00
BuildTools 313535a88a rain reduction 2020-08-16 15:10:16 -04:00
BuildTools ae0e1a513e Rain reduction 2020-08-16 14:31:13 -04:00
Maxopoly bb1af42d54 Add gold block tps 2020-08-14 16:10:18 +02:00
Maxopoly 4c7fa938d0 Merge pull request #30 from Protonull/civ14
ShipOutOfLuck :: add hack to disable certain boat use cases
2020-08-11 15:32:53 +02:00
Alexander 0b7b4a6696 ShipOutOfLuck :: suggested changes
After some testing, a 3x3 check below the boat is not necessary, plus some additional unnecessary checks removed
2020-08-11 14:25:59 +01:00
Alexander deaa8b3ec4 ShipOutOfLuck :: rename "scuttles" to "boatBreakers" 2020-08-11 13:59:07 +01:00
Alexander 6b5da6e016 PhantomOfTheOpera :: renamed to a more memey name :D 2020-08-11 13:55:39 +01:00
Alexander 4ea3b4dd93 Merge branch 'master' of https://github.com/CivClassic/SimpleAdminHacks into civ14 2020-08-11 13:35:40 +01:00
Alexander d29e8e9528 ShipOutOfLuck :: add hack to disable certain boat use cases 2020-08-11 13:30:24 +01:00
Maxopoly cf80b02157 Merge pull request #28 from Protonull/civ14
BuffSpanker :: add buff denier >:3
2020-08-11 00:57:39 +02:00
Alexander 7284a1d769 PhantomOfTheOpera :: make the stat checker a little more reliable
(it may not necessarily be reliable that each event call is an increment of one)
2020-08-10 22:09:04 +01:00
Alexander 6086460c84 DebugWand :: allow usage on entities 2020-08-10 22:08:14 +01:00
Alexander 28ae35aee7 BuffSpanker :: add buff denier >:3 2020-08-10 17:12:17 +01:00
Maxopoly cff35013b2 Reward for good breaks in Anti civbreak 2020-08-10 15:48:23 +02:00
Maxopoly 48865b04c0 Set default for AntiFastBreak denying 2020-08-10 01:31:45 +02:00
Maxopoly 559629ac76 Merge pull request #27 from squareblob/master
Fix obsidian generators, formatting
2020-08-06 21:56:55 +02:00
BuildTools 93b2001b76 Fix obsidian generators, formatting 2020-08-06 15:44:28 -04:00
Maxopoly 1f33f68959 Merge pull request #25 from Falvyu/anchorbomb
Fixed exception when clicking in the air
2020-08-05 02:28:46 +02:00
Falvyu ca3534ec58 Fixed exception when clicking in the air 2020-08-04 22:57:47 +02:00
Maxopoly 0530aad684 Cleanse names and lore 2020-08-04 22:41:38 +02:00
Maxopoly 6dfd311157 Convert legacy lore 2020-08-04 05:59:12 +02:00
Maxopoly fd23a54520 Merge pull request #24 from Falvyu/horsestats
Check horse stats with item
2020-08-03 19:28:27 +02:00
Maxopoly 75df7c70d1 Merge pull request #23 from Falvyu/anchorbomb
Added option to disabled anchor bombing outside the nether
2020-08-03 19:27:59 +02:00
Falvyu 0cfa1a9425 Removed debug messages and unused import 2020-08-03 18:42:52 +02:00
Falvyu 14c0c4bf65 Added option to disable anchor bombing outside the nether 2020-08-03 18:39:22 +02:00
Falvyu 59be41125c Added config option to change wand
Additional:
- Removed debugging messages
- Added default config
2020-08-03 03:20:52 +02:00
Falvyu 66db1ee8e0 Added horsestats module 2020-08-03 01:56:45 +02:00
Maxopoly c28373cda2 Keep other entities from portalling 2020-08-03 01:27:00 +02:00
Maxopoly 7260a0f3c7 Disable hoglins 2020-07-30 21:55:40 +02:00
Maxopoly 3b10a48e86 Update to 1.16 2020-07-30 21:55:40 +02:00
Maxopoly 3ced8ad563 Merge pull request #21 from squareblob/master
getChunkLimitsExceededMessage : Allow specifying %Limit% and %Material%
2020-07-26 18:02:43 +02:00
BuildTools 1f2ce52479 getChunkLimitsExceededMessage : Specify %Limit% and %Material% 2020-07-26 10:48:01 -04:00
Maxopoly 0c7c2b8006 Merge remote-tracking branch 'civclassic/master' into civ14 2020-07-10 00:58:32 +02:00
Maxopoly 3002807ab5 Dont disable mining fatigue from plugins 2020-07-10 00:58:14 +02:00
Maxopoly a7011eceed Merge pull request #20 from Protonull/civ14
DebugWand :: new basic hack
2020-07-06 14:29:33 +02:00
Alexander bb547f147f DebugWand :: new basic hack 2020-07-06 12:27:10 +01:00
Maxopoly 967b8d84fa Third attempt to fix javadoc 2020-07-03 01:14:06 +02:00
Maxopoly b6715f833a Second attempt to fix javadoc 2020-07-03 01:11:40 +02:00
Maxopoly 3edbf4fda1 Try and fix javadoc 2020-07-03 01:08:40 +02:00
Maxopoly 112697d753 Merge pull request #19 from Protonull/civ14
Updates and additions
2020-07-03 00:46:30 +02:00
Alexander 727dee5c1c plugin.yml :: add CivModCore as hard dependency 2020-07-02 23:45:45 +01:00
Alexander df542e0077 PlayerRevive :: remove unused import 2020-07-02 23:45:33 +01:00
Alexander 36dc01d2aa Smoll fix + improvement 2020-06-28 21:26:50 +01:00
Alexander 237d26ffa7 EventHandlerList :: show longer name + add hack to config + add EventDebugHack 2020-06-28 21:20:01 +01:00
Alexander 7a1ed7aa52 PlayerStatistics :: redo status 2020-06-28 21:09:54 +01:00
Alexander 5e10246876 PlayerRevive :: add command complete + status 2020-06-28 21:09:40 +01:00
Alexander 76172e1a9e EventHandlerList :: new basic hack 2020-06-28 21:09:07 +01:00
Alexander 2b3747272a PhantomOfTheOperaConfig :: might as well 2020-06-28 20:51:09 +01:00
Alexander eec9335a90 PlayerRevive :: new basic hack 2020-06-28 20:48:39 +01:00
Alexander de56704363 PlayerStatistics :: new basic hack 2020-06-28 20:40:26 +01:00
Alexander dd46a44c89 AutoRespawn :: might as well do these few improvements 2020-06-28 17:59:39 +01:00
Alexander f69b98aae9 SimpleAdminHacksConfig :: refactor root config parser 2020-06-28 17:57:02 +01:00
Alexander 5d37db2b9a SimpleAdminHacks :: convert from JavaPlugin to ACivMod 2020-06-28 13:48:03 +01:00
Alexander 60977667f9 plugin.yml :: fix a small permission issue 2020-06-28 13:43:21 +01:00
Maxopoly 0bd136de59 Update Jenkinsfile 2020-06-11 23:40:11 +02:00
Maxopoly 344004d773 Add jenkins stuff 2020-05-29 06:43:08 +02:00
Maxopoly 7193713c75 Merge pull request #18 from Diet-Cola/master
Add null check
2020-05-21 03:09:37 +02:00
Cola b63f4b4868 Add null check 2020-05-21 02:04:22 +01:00
Maxopoly 5271989382 Merge pull request #17 from Diet-Cola/master
Fix EnderDragon cancelling & Stop End Gateway TP
2020-05-21 02:41:20 +02:00
Cola 26f9313d4c Fix 2 2020-05-21 01:40:07 +01:00
Cola 0b3cd922b4 Fixes 2020-05-21 01:39:00 +01:00
Cola e302e38092 Disable end crystal placement in end 2020-05-21 01:31:46 +01:00
Cola ac3a5ecd58 Fix EnderDragon cancelling & Stop End Gateway TP
#16
2020-05-21 00:17:53 +01:00
Maxopoly 2db8d0cb0a Merge pull request #15 from Diet-Cola/master
Fix Eye of Ender throwing
2020-05-13 07:08:32 +02:00
Cola 4f40c81f3a Updated logic 2020-05-13 05:17:35 +01:00
Cola a2e4c25614 Fix Eye of Ender throwing 2020-05-13 03:22:44 +01:00
Maxopoly bb72921ee9 Disable throwing eye of ender 2020-05-10 16:26:46 +02:00
Maxopoly c50602a9e9 Fix merge conflict with civclassic master 2020-05-02 03:56:21 +02:00
Maxopoly 5b06e5fd52 Merge pull request #11 from Protonull/civ14
Added Phantom Management
2020-04-29 05:03:33 +02:00
Alexander 396dd976c5 Added Phantom Management 2020-04-29 03:34:50 +01:00
Maxopoly 91c384d3b9 Merge pull request #10 from Falvyu/civ14_2
Added options for disabling Phantoms, Bad Omen and Patrols
2020-04-27 23:02:31 +02:00
Maxopoly eb7b4bbaf5 Readd Humbugs Pearl TP fixes 2020-04-27 20:47:27 +02:00
Maxopoly bbd784cfb8 Advanced anti fast break 2020-04-27 03:57:44 +02:00
Maxopoly 136999e3b1 Implement anti fastbreak 2020-04-27 00:12:15 +02:00
Falvyu 6e34681227 Fixed GameFeaturesConfig so that phantomSpawning is properly set 2020-04-25 15:28:26 +02:00
Falvyu 677d1ce4d0 Added option to disable Phantoms 2020-04-25 02:44:11 +02:00
Falvyu c3d608131c Completed status() function with the other GameTuning options 2020-04-25 02:44:11 +02:00
Falvyu d2a99e9fd1 Added config option to disable pillager patrols 2020-04-25 02:44:11 +02:00
Falvyu ea20108c87 Added configuration option to disable the Bad Omen effect given by
Pillager patrols
2020-04-25 02:44:11 +02:00
Maxopoly 4ef71b76dd Prevent cobble mountains and wandering trader 2020-04-24 05:12:43 +02:00
Maxopoly bdc233c47c Working eventdebug 2020-04-24 02:42:30 +02:00
Maxopoly cf3f1bebfb Begin implementing EventDebugger 2020-04-23 14:16:32 +02:00
Maxopoly 89bfa33fbf FIx banner hack 2020-04-22 12:10:33 +02:00
Alexander 67e90ac89b Cherry pick autorespawn 2020-04-22 12:06:25 +02:00
Maxopoly e13f45c831 Merge pull request #9 from Falvyu/civ14_2
Fixed issue where onPlayerInteract() would re-enable cancelled event and removed duplicated enderchest check
2020-04-13 05:40:18 +02:00
Falvyu a66b625459 Fixed issue where onPlayerInteract() would re-enable cancelled event
and removed duplicated enderchest check
2020-04-13 00:40:49 +02:00
Maxopoly 22e5d9d255 Merge pull request #8 from Protonull/master
Added AutoRespawn
2020-02-20 14:41:27 +01:00
Alexander 100be4e00b Maaajor POM fix + bump version: 1.2.5 2020-02-17 11:32:41 +00:00
Alexander 0a30937f9a Added AutoRespawn as a hack 2020-02-17 11:21:14 +00:00
Maxopoly 118d6c7cad Actually make everything new work 2019-10-27 19:17:22 +01:00
Maxopoly 28beac2c8c Actually launch hacks 2019-10-27 18:06:47 +01:00
Maxopoly 7056077cd5 Add api version to plugin.yml 2019-10-25 23:41:47 +02:00
Maxopoly b6ad1a0ab7 Last fixes to make compile 2019-10-25 23:20:38 +02:00
Maxopoly 64cb6b3e81 Resolve merge conflict 2019-10-25 23:10:26 +02:00
Maxopoly 3746205277 Fix up ArthropodEgg hack 2019-10-25 00:44:14 +02:00
ProgrammerDan 118e28f9ae Adding in dragon controls, might need to separate out a bit, but adds a new dragon damage for servers that want to allow dragon spawning but would prefer they dont remove the world en mass. Untested at yet. Some other fixes as noted in my self-review. 2019-09-10 01:09:24 -04:00
ProgrammerDan 9cb7b0d344 Reverting some null checks, I know this will cause warnings in IDE but some were hard earned. I do hope that Spigot is more faithful and careful in these later releases, but better to check and be sure then not check and be unsure. Some I did not add back in. Any I added back in I wrote why I added it. I also switched around some orderings to use equals() checks, and also addressed some changes to map views in new releases, although there is more to do there. As well, expanded certain block lists and biome lists even further, lots of new content in these new minecraft releases. Probably first of several commits, as I test. 2019-09-04 00:36:23 -04:00
Maxopoly a2a8f51509 Add all of Humbug 2019-08-22 03:03:35 +02:00
Maxopoly 4d52acf0be Mostly complete 1.14 update 2019-08-20 23:27:18 +02:00
Maxopoly ab1e6adf85 Partial 1.14 update 2019-08-20 22:18:10 +02:00
Maxopoly 5f0d04df2c Add new BasicHack and autoloading infrastructure 2019-08-20 21:41:56 +02:00
Maxopoly 067aadc8ea Merge pull request #6 from bigbrainiac10/master
Removed debug messages
2019-04-22 22:47:58 +02:00
Longboyy 7977358ae1 Removed debug messages 2019-04-22 19:49:20 +01:00
Maxopoly 1ae55c56f1 Merge branch 'master' of github.com:CivClassic/SimpleAdminHacks 2019-04-22 02:55:52 +02:00
Maxopoly 8dccbcb637 Merge pull request #5 from bigbrainiac10/master
Added ToggleLamp
2019-04-22 02:53:27 +02:00
Longboyy 47135983f3 Added ToggleLamp 2019-04-11 15:10:22 +01:00
Maxopoly 6d4d5162fe Cleanup and remove some weirdness to merge into Devoted 2019-03-03 21:58:39 +01:00
Lazersmoke a30f0ceea4 Merge pull request #4 from iykHvfIvfR/pr2
Distinguish between player and environmental deaths
2018-04-17 15:15:41 -05:00
iykHvfIvfR 161de50759 Distinguish between player and environmental deaths 2018-04-13 21:03:42 +00:00
iykHvfIvfR 012fa0d838 Refactor onPlayerDeath() 2018-04-13 20:50:04 +00:00
iykHvfIvfR 057f38dbf6 Fix formatting 2018-04-13 20:42:13 +00:00
iykHvfIvfR 04de0fcfb4 Trim trailing whitespace 2018-04-13 20:40:24 +00:00
ribagi cfb7d00c63 Merge pull request #3 from iykHvfIvfR/pr1
Fix minecart teleporters
2018-04-12 19:00:44 -07:00
iykHvfIvfR dd218b36bc Fix minecart teleporters 2018-04-13 01:54:03 +00:00
TealNerd 2cabf45a03 4 blocks > 3 blocks 2018-04-01 18:00:33 -05:00
biggestnerd c26351cb64 Start scan below world height to match old humbug functionality 2018-04-01 10:55:15 -05:00
TealNerd a37714cf2d fix some logging and removed npc stuff 2017-12-13 02:57:33 -06:00
TealNerd 587ce331f5 initialize noplace set so this actually works 2017-12-12 15:45:56 -06:00
TealNerd 7335ebf130 Fix falling through bedrock 2017-12-12 15:27:25 -06:00
TealNerd 0db079b57c I'm an idiot who doesn't know the minecraft coorindate system 2017-12-11 13:42:52 -06:00
TealNerd 135b783b2b fix bedrock hack 2017-12-11 13:37:58 -06:00
TealNerd 3264717e54 Update combattag to latest version 2017-12-11 13:24:21 -06:00
TealNerd 94e43c1e78 Added in most relevant humbug features 2017-12-11 13:21:43 -06:00
TealNerd e03052c0a7 Removed crazy crates 2017-12-11 13:21:20 -06:00
ProgrammerDan ef6c2ad5ff Fixing an issue where soft depends were behaving like real depends. 2017-08-02 00:57:21 -04:00
126 changed files with 11795 additions and 4995 deletions

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
name: Build
on:
push:
branches:
- '*'
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
java-version: 17
distribution: temurin
- uses: gradle/gradle-build-action@v2
with:
arguments: build
+25
View File
@@ -0,0 +1,25 @@
name: Publish
on:
push:
tags:
- 'v*.*.*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
java-version: 17
distribution: temurin
- uses: gradle/gradle-build-action@v2
with:
arguments: publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CIVMC_NEXUS_USER: ${{ secrets.CIVMC_NEXUS_USER }}
CIVMC_NEXUS_PASSWORD: ${{ secrets.CIVMC_NEXUS_PASSWORD }}
+6 -1
View File
@@ -3,4 +3,9 @@
/bin/
.project
.classpath
.metadata
.metadata
.idea/
*.iml
logs
build
.gradle
View File
File renamed without changes.
+154 -83
View File
@@ -5,16 +5,14 @@ Landing spot for fixes, tuning, micro game modifications that have no other home
This is tweak central.
Brought to you by DevotedMC, we're taking a pretty inclusive and permissive view
for this plugin; if you need a particular "hack" to get things working in your
use case, send us a PR for inclusion, we'd love to host it.
Brought to you by DevotedMC, we're taking a pretty inclusive and permissive view for this plugin; if you need a
particular "hack" to get things working in your use case, send us a PR for inclusion, we'd love to host it.
This plugin is designed from the ground up to support easy creation of "microplugins"
-- things with featuresets too small to make sense for a full sized plugin, or fixes
for game problems or simple heuristic loggers.
This plugin is designed from the ground up to support easy creation of "microplugins" -- things with featuresets too
small to make sense for a full sized plugin, or fixes for game problems or simple heuristic loggers.
Making a new "Hack" is always an option, but take a look at this list of active
Hacks and see if your contribution might fit with an existing module.
Making a new "Hack" is always an option, but take a look at this list of active Hacks and see if your contribution
might fit with an existing module.
## Active Hacks
@@ -28,131 +26,204 @@ Type `hacks <hackname>` to see a readout of that particular hack's status or con
Type `hacks <hackname> get <configitem>` to see the current configuration setting for that config element in that hack.
Type `hacks <hackname> set <configitem> <new value>` to set a new configuratoin value for that config element in that hack. Not all hacks support this.
Type `hacks <hackname> set <configitem> <new value>` to set a new configuratoin value for that config element in that
hack. Not all hacks support this.
### AntiFastBreak
This hack offers some defence against insta-break / fast-break client hacks by denying continuous breaks.
### ArthropodEggHack
Allows players to occasionally pick up spawn eggs of mobs they kill with Bane of Arthropods.
### AttrHider
Stops clients from receiving extraneous entity and item data that could be exploited custom clients.
### AutoRespawn
This hack allows administrators to enforce a limit on how long players can stay dead, preventing zombie scouting and
safe bots.
### BuffSpanker
Disables particular \[potion\] effects from being applied to players through standard means.
### DebugWand
Gives administrators the ability to spawn in a wand that when right clicked on blocks and entities will display useful
debug information.
### EventDebugHack
This hack allows administrators to inspect an event as it passes through the stages of priority, what listeners are
handling the event at what stage, and if applicable when exactly the even is cancelled.
### EventHandlerList
This hack lets you search for all the events *currently* being listened for with your current plugin setup and those
listeners' class names.
### HumbugBatchOne
A new BasicHack (thanks Maxopoly) which encompasses a number of previously unincluded Humbug gameplay modifications.
Specifically, sheep wool dying, anvil use, enchanting table use, cauldrons sourcing infinite water, disabling Ender
Dragon, disabling iron farms, disabling Ender Crystal damage, disable mining fatigue, and equipping banners as hats.
### ItemMetaConverterHack
Passively upgrades the lore and display name text under the hood from legacy strings to components.
### OldEnchanting
This hack allows admins to regulate XP and enchantments in particular ways.
### PhantomMenace
Allows administrators to regulate the behaviour of phantoms and their spawning.
### PlayerRevive
Administrators can force revive a player where they died.
### PlayerStatistics
Allows administrators to view and set a player's [statistics](https://minecraft.gamepedia.com/Statistics).
### PortalSpawnModifier
Another new BasicHack which allows control of the spawn of creatures rate from nether portals
### ShipOutOfLuck
Allows administrators to disallow boating on particular blocks.
### TestConfigHack
Test hack for SimpleAdminHacks itself. Please [make a fuss](https://github.com/CivClassic/SimpleAdminHacks/issues/new)
if you see any red.
### BadBoyWatch
This hack keeps track of who breaks what, and what they broke to get there. I've got
bigger plans for more heuristics but for now, that's it. Please, feel free to improve
on this.
This hack keeps track of who breaks what, and what they broke to get there. I've got bigger plans for more heuristics
but for now, that's it. Please, feel free to improve on this.
### CTAnnounce
An integration into CombatTagPlus, this announces to in-game operators when combat
is under way. Invaluable when you like your admins to monitor in-game fights.
An integration into CombatTagPlus, this announces to in-game operators when combat is under way. Invaluable when you
like your admins to monitor in-game fights.
### Experimental
This is a catch-all for anything micro that has no other home. Useful features like
`/serialize` command live here too, but mostly its logging trackers that are great
for debugging edge cases and useless for normal runtime.
This is a catch-all for anything micro that has no other home. Useful features like `/serialize` command live here too,
but mostly its logging trackers that are great for debugging edge cases and useless for normal runtime.
### GameFeatures
The principle focus here is on enabling and disabling game features, like elytra or
skulker boxes or those sorts of things. Try to keep the focus on boolean or otherwise
simple on / off or discrete state options that relate to existing Minecraft game
mechanics.
The principle focus here is on enabling and disabling game features, like elytra or skulker boxes or those sorts of
things. Try to keep the focus on boolean or otherwise simple on / off or discrete state options that relate to existing
Minecraft game mechanics.
### GameFixes
Sometimes things in Minecraft just... don't work. Or don't work the way you want them
to. Put those fixes here.
Sometimes things in Minecraft just... don't work. Or don't work the way you want them to. Put those fixes here.
### GameTuning
Similar to Game Fixes, but distinct in that the intent here is on hacks that _tune_
or alter existing mechanics without fully disabling them or without fixing them.
Things like altering armor damage reduction or tool impact would be good fits.
Similar to Game Fixes, but distinct in that the intent here is on hacks that *tune* or alter existing mechanics without
fully disabling them or without fixing them. Things like altering armor damage reduction or tool impact would be good
fits.
### HackBot
### ~~HackBot~~
An experimental module whose complexity is approaching divorce from SAH, this is
a bit of mid-work code that allows operators to define static Bots that show up
like "real" players but aren't.
(Removed in 1.2.4)
~~An experimental module whose complexity is approaching divorce from SAH, this is a bit of mid-work code that allows
operators to define static Bots that show up like "real" players but aren't.~~
### HorseStats
This hack allows player's to view a horse's stats.
### Introbook
Allows the creation of customizeable, helpful first join books that are distributed
to players when they first join the server. Safe to turn off but super useful
if you have a core set of additional "good to know" information or introductory
material you'd like players to receive.
Allows the creation of customizeable, helpful first join books that are distributed to players when they first join the
server. Safe to turn off but super useful if you have a core set of additional "good to know" information or
introductory material you'd like players to receive.
### InvControl
Host of the `/invsee` command, gives insight into active player inventory, armor,
health and more. I hope / intend to add another portion to this that allows online
and offline inventory management via `/invmod` but this is as of now not started.
Host of the `/invsee` command, gives insight into active player inventory, armor, health and more. I hope / intend to
add another portion to this that allows online and offline inventory management via `/invmod` but this is as of now not
started.
### InvisibleFix and InvisibleFixTwo
### ~~InvisibleFix and InvisibleFixTwo~~
My hope is to remove these in time, but for 1.9 and 1.10 minecraft players
go invisible all the time. Both of these hacks use different approaches to help
preserve the visiblity of players. The second hack is the more invasive, sending
explicit location packets when the situation indicates its usefulness.
(Removed in 1.3.0)
~~My hope is to remove these in time, but for 1.9 and 1.10 minecraft players go invisible all the time. Both of these
hacks use different approaches to help preserve the visiblity of players. The second hack is the more invasive, sending
explicit location packets when the situation indicates its usefulness.~~
### NewfriendAssist
A new join tracker, that also doubles as an introkit delivery mechanism.
It also exposes the `introkit` command you can use to send specific people a new
introkit if they lost theirs.
A new join tracker, that also doubles as an introkit delivery mechanism. It also exposes the `introkit` command you can
use to send specific people a new introkit if they lost theirs.
### ReinforcedChestBreak
Another useful alerting hack, sends a message to online operators when players
are actively breaking reinforced containers.
Another useful alerting hack, sends a message to online operators when players are actively breaking reinforced
containers.
### SanityHack
This one is pretty Devoted iteration 3 specific. Allows tracking server wide of
all breaks or builds under a specific y level.
This one is pretty Devoted iteration 3 specific. Allows tracking server wide of all breaks or builds under a specific
Y level.
### TimingsHack
This is mature enough to be its own plugin now, but for the moment I'm leaving it in
because IMHO it is an essential component for debugging and maintaining your server.
This is mature enough to be its own plugin now, but for the moment I'm leaving it in because IMHO it is an essential
component for debugging and maintaining your server.
This hack begins where /timings and warmroast end. It generates in-game visualizations
of per-tick time utilization, and allows you to investigate with extreme effectiveness
exactly what is causing instantaneous or irregular lag. Things that average out, but lead
to bad experiences for players.
This hack begins where /timings and warmroast end. It generates in-game visualizations of per-tick time utilization,
and allows you to investigate with extreme effectiveness exactly what is causing instantaneous or irregular lag. Things
that average out, but lead to bad experiences for players.
To use it, there are a number of important commands.
First:
* `/showtimings` This gives the requester a "map" object that paints a heatmap, heightmap,
and linegraph of TPS. Each vertical pixel is a full 20 ticks; each dot in the heatmap
is a single tick. The heightmap above the heatmap (heatmap is on the bottom) is both
colored and sized based on time-in-tick. At the very top is line graph of relative "health"
of the tick. The closer to the grey line the better; the closer to the top, the worse.
* `/thresholdtimings` This starts a very high resolution (1ms) stack inspection tracker.
It keeps track _per tick_ with usually 50 samples per tick of which methods and classes
are being taking up time within the tick. Give it a threshold factor -- please, something more
then 1, 5 to 10 works well -- where any tick that takes longer then threshold factor * average
tick length, dumps the class/method list to requestor (either console or in-game character).
The list is in sorted order of class where most time is spent down to class where least time
is spent.
* `/listtimings` In you already suspect something, you can use this command to begin a spammy
dump of all classes encountered during high-resolution tracking. Only new classes are printed,
and it shuts itself off if nothing new has been printed for a full second.
* `/bindtimings` This generates an in-game map displaying (like `/showtimings`) a heatmap, heightmap,
and line graph of per-tick utilization of any Classes that _contain_ whatever is passed in after
bindtimings. Note this is saved, and on reload of the server if HQ timings are restarted those
maps will re-bind to the saved bindings. This is a massively useful deep inspection tool;
you can put the "based" class of a suspect plugin as the parameter, and watch its actual per-tick
time utilization in real-time. Destroy the map object to release the drain on overall TPS that
results from the comparison functions used to update the map.
* `/stoptimings` This turns off any HQ functions. Has the impact of _pausing_ any bindtimings maps,
and releasing all HQ functions from impacting Tick (also stops tracking data, so data tracking will
reset if HQ restarted by calling any other HQ-tagged command).
* `/showtimings` This gives the requester a "map" object that paints a heatmap, heightmap, and linegraph of TPS. Each
vertical pixel is a full 20 ticks; each dot in the heatmap is a single tick. The heightmap above the heatmap
(heatmap is on the bottom) is both colored and sized based on time-in-tick. At the very top is line graph of
relative "health" of the tick. The closer to the grey line the better; the closer to the top, the worse.
* `/thresholdtimings` This starts a very high resolution (1ms) stack inspection tracker. It keeps track *per tick*
with usually 50 samples per tick of which methods and classes are being taking up time within the tick. Give it a
threshold factor -- please, something more then 1, 5 to 10 works well -- where any tick that takes longer then
threshold factor * average tick length, dumps the class/method list to requestor (either console or in-game
character). The list is in sorted order of class where most time is spent down to class where least time is spent.
* `/listtimings` In you already suspect something, you can use this command to begin a spammy dump of all classes
encountered during high-resolution tracking. Only new classes are printed, and it shuts itself off if nothing new
has been printed for a full second.
* `/bindtimings` This generates an in-game map displaying (like `/showtimings`) a heatmap, heightmap, and line graph
of per-tick utilization of any Classes that _contain_ whatever is passed in after bindtimings. Note this is saved,
and on reload of the server if HQ timings are restarted those maps will re-bind to the saved bindings. This is a
massively useful deep inspection tool; you can put the "based" class of a suspect plugin as the parameter, and
watch its actual per-tick time utilization in real-time. Destroy the map object to release the drain on overall TPS
that results from the comparison functions used to update the map.
* `/stoptimings` This turns off any HQ functions. Has the impact of _pausing_ any bindtimings maps, and releasing all
HQ functions from impacting Tick (also stops tracking data, so data tracking will reset if HQ restarted by calling
any other HQ-tagged command).
In game example with some descriptions:
* From live server: http://imgur.com/a/zWNWo
### ToggleLamp
A solid piece of contributed code, this hack lets player's turn on and off redstone lamps without requiring any
additional redstone infrastructure.
## Conclusion
There is room for many more hacks and the list grows regularly. The pattern is simple;
check any of the existing hacks for details by example.
There is room for many more hacks and the list grows regularly. The pattern is simple; check any of the existing hacks
for details by example.
+23
View File
@@ -0,0 +1,23 @@
import net.civmc.civgradle.CivGradleExtension
plugins {
id("net.civmc.civgradle") version "2.+" apply false
}
subprojects {
apply(plugin = "java-library")
apply(plugin = "maven-publish")
apply(plugin = "net.civmc.civgradle")
configure<CivGradleExtension> {
pluginName = project.property("pluginName") as String
}
repositories {
mavenCentral()
maven("https://repo.civmc.net/repository/maven-public")
maven("https://repo.dmulloy2.net/repository/public/")
maven("https://ci.frostcast.net/plugin/repository/everything")
maven("https://jitpack.io")
}
}
+7
View File
@@ -0,0 +1,7 @@
# Gradle properties
group = net.civmc.simpleadminhacks
version = 2.2.3
description = SimpleAdminHacks
# Custom Properties
pluginName = SimpleAdminHacks
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+234
View File
@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+89
View File
@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+18
View File
@@ -0,0 +1,18 @@
plugins {
id("io.papermc.paperweight.userdev")
}
dependencies {
paperDevBundle("1.18.2-R0.1-SNAPSHOT")
compileOnly("net.civmc.civmodcore:civmodcore-paper:2.3.5:dev-all")
compileOnly("net.civmc.namelayer:namelayer-paper:3.0.4:dev")
compileOnly("net.civmc.citadel:citadel-paper:5.1.2:dev")
compileOnly("net.civmc.combattagplus:paper:2.0.0-SNAPSHOT:dev")
compileOnly("net.civmc.banstick:banstick-paper:2.0.1:dev")
compileOnly("net.civmc.bastion:bastion-paper:3.0.1:dev")
compileOnly("net.civmc.exilepearl:exilepearl-paper:2.1.3:dev")
compileOnly("com.comphenix.protocol:ProtocolLib:4.8.0")
}
@@ -0,0 +1,178 @@
package com.programmerdan.minecraft.simpleadminhacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.HackManager;
import com.programmerdan.minecraft.simpleadminhacks.framework.commands.CommandRegistrar;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player;
import vg.civcraft.mc.civmodcore.ACivMod;
/**
* Wrapper for simple admin hacks, each doing a thing and each configurable.
*
* @author ProgrammerDan
*/
public class SimpleAdminHacks extends ACivMod {
private static SimpleAdminHacks plugin;
private final SimpleAdminHacksConfig config;
private final HackManager manager;
private CommandRegistrar commands;
private final SimpleAdminHacksSettingManager settingManager;
public SimpleAdminHacks() {
plugin = this;
this.config = new SimpleAdminHacksConfig(this);
this.manager = new HackManager(this);
this.settingManager = new SimpleAdminHacksSettingManager();
}
@Override
public void onEnable() {
super.onEnable();
if (!this.config.parse()) {
setEnabled(false);
return;
}
this.commands = new CommandRegistrar(this);
this.manager.loadAllHacks();
this.manager.enableAllHacks();
}
@Override
public void onDisable() {
this.manager.disableAllHacks();
this.commands.reset();
this.config.reset();
super.onDisable();
}
public void registerCommand(final String identifier, final CommandExecutor executor) {
final PluginCommand command = getCommand(identifier);
if (command != null) {
command.setExecutor(executor);
}
else {
warning("Failed to register Executor for " + identifier + ", " +
"please define that command in the plugin.yml first.");
}
}
/**
* Definitely not safe. Used for Insight hack.
* @return the ClassLoader for this plugin.
*/
public ClassLoader exposeClassLoader() {
return getClassLoader();
}
/**
* @return Returns the overall config management for this plugin.
*/
public SimpleAdminHacksConfig config() {
return this.config;
}
/**
* @return Returns the hack manager
*/
public HackManager getHackManager() {
return this.manager;
}
/**
* @return Returns the command registrar
*/
public CommandRegistrar getCommands() {
return this.commands;
}
/**
* @return Returns the pseudo-singleton instance of this plugin.
*/
public static SimpleAdminHacks instance() {
return plugin;
}
/**
* Returns the CivModCore PlayerSetting Manager
* @return
*/
public SimpleAdminHacksSettingManager getSettingManager() {
return settingManager;
}
// ------------------------------------------------------------
// Deprecated
// ------------------------------------------------------------
@Deprecated
public void log(String message) {
info(message);
}
@Deprecated
public void log(Level level, String message) {
getLogger().log(level, message);
}
@Deprecated
public void log(Level level, String message, Throwable thrown) {
getLogger().log(level, message, thrown);
}
@Deprecated
public void log(Level level, String message, Object object) {
getLogger().log(level, message, object);
}
@Deprecated
public void log(Level level, String message, Object...objects) {
getLogger().log(level, message, objects);
}
@Deprecated
public boolean serverHasPlugin(String pluginName) {
return this.getServer().getPluginManager().isPluginEnabled(pluginName);
}
@Deprecated
public int serverBroadcast(String message) {
return this.getServer().broadcast(message, config().getBroadcastPermission());
}
@Deprecated
public int serverOperatorBroadcast(String message) {
int cnt = 0;
for(OfflinePlayer op : Bukkit.getOperators()) {
if (op.isOnline() && op.getPlayer() != null) {
op.getPlayer().sendMessage(message);
cnt ++;
}
}
return cnt;
}
@Deprecated
public int serverOnlineBroadcast(String message) {
int cnt = 0;
for (Player p : Bukkit.getOnlinePlayers()) {
if (p != null && p.isOnline()) {
p.sendMessage(message);
cnt ++;
}
}
return cnt;
}
@Deprecated
public void serverSendConsoleMessage(String message) {
Bukkit.getConsoleSender().sendMessage(message);
}
}
@@ -0,0 +1,45 @@
package com.programmerdan.minecraft.simpleadminhacks;
import org.bukkit.configuration.ConfigurationSection;
import vg.civcraft.mc.civmodcore.config.ConfigParser;
/**
* Baseline configuration for SimpleAdminHacks and parser for all actual Hacks.
*
* @author ProgrammerDan
*/
public class SimpleAdminHacksConfig extends ConfigParser {
private static final int EXPECTED_CONFIG_LEVEL = 1;
private static final String DEFAULT_BROADCAST_PERM = "simpleadmin.broadcast";
private String broadcastPermission;
public SimpleAdminHacksConfig(final SimpleAdminHacks plugin) {
super(plugin);
reset();
}
@Override
protected boolean parseInternal(final ConfigurationSection config) {
final int actual_config_level = config.getInt("configuration_file_version", -1);
if (actual_config_level < 0 || actual_config_level > SimpleAdminHacksConfig.EXPECTED_CONFIG_LEVEL) {
this.plugin.getLogger().severe("Invalid configuration version!");
return false;
}
this.broadcastPermission = config.getString("broadcast_permission", DEFAULT_BROADCAST_PERM);
this.plugin.getLogger().info("Broadcast permission: " + this.broadcastPermission);
return true;
}
public void reset() {
super.reset();
this.broadcastPermission = DEFAULT_BROADCAST_PERM;
}
public String getBroadcastPermission() {
return this.broadcastPermission;
}
}
@@ -0,0 +1,24 @@
package com.programmerdan.minecraft.simpleadminhacks;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI;
import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection;
public class SimpleAdminHacksSettingManager {
private MenuSection mainMenu;
public SimpleAdminHacksSettingManager() {
this.mainMenu = PlayerSettingAPI.getMainMenu().createMenuSection("SimpleAdminHacks", "Config values for all small tweaks from SAH", new ItemStack(
Material.CAULDRON));
}
/**
* Returns the Menu Section used to bundle together all SAH /config tweaks
* @return SAH /config Menu
*/
public MenuSection getMainMenu() {
return mainMenu;
}
}
@@ -1,17 +1,15 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
/**
* Configures this hack, determines which blocks to watch and the like.
*
@@ -23,17 +21,17 @@ public class BadBoyWatchConfig extends SimpleHackConfig {
private boolean clearOnMatch;
private int minDepthToMatch;
private Set<Material> watchedMaterials;
private static final List<Material> defaultWatched = Arrays.asList(
Material.CHEST,
Material.JUKEBOX,
Material.NOTE_BLOCK,
Material.BEACON,
Material.ENCHANTMENT_TABLE,
Material.ENCHANTING_TABLE,
Material.ENDER_CHEST,
Material.FURNACE,
Material.SPONGE);
public BadBoyWatchConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
@@ -48,7 +46,7 @@ public class BadBoyWatchConfig extends SimpleHackConfig {
plugin().log(Level.INFO, " Minimum Breakpath Depth To Match: {0}", this.minDepthToMatch);
@SuppressWarnings("unchecked")
List<String> watch = (List<String>) config.getList("watchedMaterials");
this.watchedMaterials = new HashSet<Material>();
this.watchedMaterials = new HashSet<>();
if (watch == null || watch.isEmpty()) {
this.watchedMaterials.addAll(defaultWatched);
plugin().log(Level.INFO, " Adding default watch set");
@@ -0,0 +1,64 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.google.common.collect.Maps;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import vg.civcraft.mc.civmodcore.utilities.CivLogger;
import java.util.*;
public final class BetterRailsConfig extends SimpleHackConfig {
private final CivLogger logger;
private Map<Material, Double> speeds;
private double baseSpeed = 8;
private Map<Material, Double> skySpeeds;
private double skySpeed = 0;
public BetterRailsConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base, false);
this.logger = CivLogger.getLogger(getClass());
wireup(base);
}
@Override
protected void wireup(ConfigurationSection config) {
this.baseSpeed = config.getDouble("base");
ConfigurationSection materials = config.getConfigurationSection("materials");
Set<String> keys = materials.getKeys(false);
this.speeds = Maps.newHashMapWithExpectedSize(keys.size());
for (String key : keys) {
this.speeds.put(Material.valueOf(key), materials.getDouble(key));
}
this.skySpeed = config.getDouble("skyBase");
ConfigurationSection skyMaterials = config.getConfigurationSection("skyMaterials");
Set<String> skyKeys = skyMaterials.getKeys(false);
this.skySpeeds = Maps.newHashMapWithExpectedSize(skyKeys.size());
for (String key : skyKeys) {
this.skySpeeds.put(Material.valueOf(key), skyMaterials.getDouble(key));
}
}
public Double getMaxSpeedMetresPerSecond(Material material) {
return speeds.get(material);
}
public Double getSkySpeedMetresPerSecond(Material material) {
return skySpeeds.get(material);
}
public double getBaseSpeed() {
return baseSpeed;
}
public double getSkySpeed() {
return skySpeed;
}
}
@@ -1,15 +1,13 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.BroadcastLevel;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.configuration.ConfigurationSection;
import com.programmerdan.minecraft.simpleadminhacks.BroadcastLevel;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
/**
* CombatTag event hook hack
*
@@ -17,7 +15,7 @@ import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
*
*/
public class CTAnnounceConfig extends SimpleHackConfig {
/**
* Who should I broadcast to?
*/
@@ -39,40 +37,40 @@ public class CTAnnounceConfig extends SimpleHackConfig {
public CTAnnounceConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
public CTAnnounceConfig(ConfigurationSection base) {
super(SimpleAdminHacks.instance(), base);
}
@Override
protected void wireup(ConfigurationSection config) {
this.broadcastDelay = config.getLong("delay", 5000l);
this.broadcastMessage = config.getString("message", "%Victim% was combat tagged by %Attacker%");
plugin().log(Level.INFO, " delay: {0}, message: {1}", this.broadcastDelay, this.broadcastMessage);
List<String> broadcastTo = config.getStringList("broadcast");
if (this.broadcast == null) {
this.broadcast = new LinkedList<BroadcastLevel>();
}
this.broadcast.clear();
if (broadcastTo == null) {
this.setEnabled(false); // disable if no broadcasters.
setShouldEnable(false); // disable if no broadcasters.
}
for (String type : broadcastTo) {
try {
broadcast.add(BroadcastLevel.valueOf(type));
plugin().log(Level.INFO, " broadcast: {0}", type);
} catch (IllegalArgumentException iae) {
// noop
}
}
}
public List<BroadcastLevel> getBroadcast() {
return broadcast;
}
@@ -0,0 +1,87 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.EntityType;
import org.bukkit.event.entity.CreatureSpawnEvent;
import vg.civcraft.mc.civmodcore.config.ConfigHelper;
import vg.civcraft.mc.civmodcore.utilities.CivLogger;
public final class DisableAIConfig extends SimpleHackConfig {
private final CivLogger logger;
private final EnumMap<EntityType, List<CreatureSpawnEvent.SpawnReason>> mobAiLimitations;
public DisableAIConfig(final SimpleAdminHacks plugin, final ConfigurationSection base) {
super(plugin, base, false);
this.logger = CivLogger.getLogger(getClass());
this.mobAiLimitations = new EnumMap<>(EntityType.class);
wireup(base);
}
@Override
protected void wireup(final ConfigurationSection config) {
for (final EntityType entityType : EntityType.values()) {
if (!config.contains(entityType.name())) {
continue;
}
if (!entityType.isAlive()) {
this.logger.warning("EntityType [" + entityType.name() + "] is not alive.");
continue;
}
final List<String> rawSpecifiedReasons = ConfigHelper.getStringList(config, entityType.name());
rawSpecifiedReasons.removeIf(StringUtils::isBlank);
if (CollectionUtils.isEmpty(rawSpecifiedReasons)) {
this.logger.warning("EntityType [" + entityType.name() + "] did not specify any spawn reasons; " +
"skipping. You should fix your config.");
continue;
}
final var allSpawnReasons = CreatureSpawnEvent.SpawnReason.values();
final var limitationReasons = this.mobAiLimitations.computeIfAbsent(entityType,
(ignored) -> new ArrayList<>(allSpawnReasons.length));
if (rawSpecifiedReasons.size() == 1 && "ALL".equalsIgnoreCase(rawSpecifiedReasons.get(0))) {
limitationReasons.addAll(List.of(allSpawnReasons));
continue;
}
for (final String rawSpecifiedReason : rawSpecifiedReasons) {
final var matchedSpawnReason = EnumUtils.getEnumIgnoreCase(
CreatureSpawnEvent.SpawnReason.class, rawSpecifiedReason);
if (matchedSpawnReason == null) {
this.logger.warning("EntityType [" + entityType.name() + "] specified an " +
"invalid spawn reason: " + rawSpecifiedReason);
continue;
}
if (limitationReasons.contains(matchedSpawnReason)) {
this.logger.warning("EntityType [" + entityType.name() + "] already specifies " +
"reason: " + rawSpecifiedReason);
continue;
}
limitationReasons.add(matchedSpawnReason);
}
}
}
public void reset() {
this.mobAiLimitations.forEach((type, reasons) -> reasons.clear());
this.mobAiLimitations.clear();
}
public boolean isLimitingEntityAI(final EntityType entityType, final CreatureSpawnEvent.SpawnReason spawnReason) {
if (entityType == null || spawnReason == null) {
return false;
}
final var limitationReasons = this.mobAiLimitations.get(entityType);
if (CollectionUtils.isEmpty(limitationReasons)) {
return false;
}
return limitationReasons.contains(spawnReason);
}
}
@@ -0,0 +1,103 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import javax.annotation.Nonnull;
import org.bukkit.configuration.ConfigurationSection;
import vg.civcraft.mc.civmodcore.utilities.CivLogger;
public final class ElytraFeaturesConfig extends SimpleHackConfig {
private final CivLogger logger;
private boolean disableFlight;
private boolean disableFlightInCombat;
private boolean disableFireworkBoosting;
private boolean disableFireworkBoostingInCombat;
private boolean disableSafeFireworkBoosting;
private int heightDamage;
private double heightDamageScaling;
private int heightBuffer;
private long heightDamageInterval;
public ElytraFeaturesConfig(@Nonnull final SimpleAdminHacks plugin,
@Nonnull final ConfigurationSection base) {
super(plugin, base, false);
this.logger = CivLogger.getLogger(getClass());
wireup(base);
}
@Override
protected void wireup(@Nonnull final ConfigurationSection config) {
this.disableFlight = config.getBoolean("disableFlight", false);
this.disableFlightInCombat = config.getBoolean("disableFlightInCombat", false);
this.disableFireworkBoosting = config.getBoolean("disableFireworkBoosting", false);
this.disableFireworkBoostingInCombat = config.getBoolean("disableFireworkBoostingInCombat", false);
this.disableSafeFireworkBoosting = config.getBoolean("disableSafeFireworkBoosting", false);
this.heightDamage = config.getInt("heightDamage.damage", 1);
if (this.heightDamage < 0) {
this.logger.warning("[heightDamage.damage] was set to [" + this.heightDamage + "], " +
"which is invalid, defaulting to: 1");
this.heightDamage = 1;
}
this.heightDamageScaling = config.getDouble("heightDamage.scales", 1d);
if (this.heightDamageScaling <= 0d) {
this.logger.warning("[heightDamage.scales] was set to [" + this.heightDamageScaling + "], " +
"which is invalid, defaulting to: 1.0d");
this.heightDamageScaling = 1d;
}
this.heightBuffer = config.getInt("heightDamage.buffer", 5);
this.heightDamageInterval = config.getLong("heightDamage.interval", 1000L);
if (this.heightDamageInterval < 1L) {
this.logger.warning("[heightDamage.interval] was set to [" + this.heightDamageInterval + "], " +
"which is invalid, defaulting to: 1000L");
this.heightDamageInterval = 1000L;
}
}
public boolean isFlightDisabled() {
return this.disableFlight;
}
public boolean isFlightDisabledInCombat() {
return this.disableFlightInCombat;
}
public boolean isBoostingDisabled() {
return this.disableFireworkBoosting;
}
public boolean isBoostingDisabledInCombat() {
return this.disableFireworkBoostingInCombat;
}
public boolean isSafeBoostingDisabled() {
return this.disableSafeFireworkBoosting;
}
public int getHeightDamage() {
return this.heightDamage;
}
public double isHeightDamageScaling() {
return this.heightDamageScaling;
}
public int getHeightBuffer() {
return this.heightBuffer;
}
public long getHeightDamageInterval() {
return this.heightDamageInterval;
}
}
@@ -1,45 +1,44 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import org.bukkit.configuration.ConfigurationSection;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
/**
* Dumping ground for experimental stuff. Highly volatile. May explode.
*
* @author ProgrammerDan
*
*/
public class ExperimentalConfig extends SimpleHackConfig {
private boolean combatSpy;
private boolean teleportSpy;
private boolean postTeleportSpy;
private int postTeleportSpyCount;
public ExperimentalConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
@Override
protected void wireup(ConfigurationSection config) {
this.combatSpy = config.getBoolean("combatspy", false);
this.teleportSpy = config.getBoolean("teleportspy", false);
this.postTeleportSpy = config.getBoolean("postteleport.spy", false);
this.postTeleportSpyCount = config.getInt("postteleport.count", 10);
}
public boolean isCombatSpy() {
return this.combatSpy;
}
public boolean isTeleportSpy() {
return this.teleportSpy;
}
public boolean isPostTeleportSpy() {
return this.postTeleportSpy;
}
public int getPostTeleportSpyCount() {
return this.postTeleportSpyCount;
}
}
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import org.bukkit.configuration.ConfigurationSection;
/**
* Dumping ground for experimental stuff. Highly volatile. May explode.
*
* @author ProgrammerDan
*
*/
public class ExperimentalConfig extends SimpleHackConfig {
private boolean combatSpy;
private boolean teleportSpy;
private boolean postTeleportSpy;
private int postTeleportSpyCount;
public ExperimentalConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
@Override
protected void wireup(ConfigurationSection config) {
this.combatSpy = config.getBoolean("combatspy", false);
this.teleportSpy = config.getBoolean("teleportspy", false);
this.postTeleportSpy = config.getBoolean("postteleport.spy", false);
this.postTeleportSpyCount = config.getInt("postteleport.count", 10);
}
public boolean isCombatSpy() {
return this.combatSpy;
}
public boolean isTeleportSpy() {
return this.teleportSpy;
}
public boolean isPostTeleportSpy() {
return this.postTeleportSpy;
}
public int getPostTeleportSpyCount() {
return this.postTeleportSpyCount;
}
}
@@ -1,21 +1,8 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.UUID;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.ChatColor;
import org.bukkit.Bukkit;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import org.bukkit.configuration.ConfigurationSection;
/**
* Holds configurations for the GameFeatures module.
@@ -27,11 +14,13 @@ public class GameFeaturesConfig extends SimpleHackConfig {
private boolean potatoXPEnabled;
private boolean villagerTrading;
private boolean witherSpawning;
private boolean patrolSpawning;
private boolean isPhantomSpawning;
private boolean enderChestPlacement;
private boolean enderChestUse;
private boolean grindstoneUse;
private boolean shulkerBoxUse;
private boolean totemPowers;
private boolean elytraUse;
private boolean chorusFruitUse;
private boolean weepingAngel;
@@ -39,7 +28,14 @@ public class GameFeaturesConfig extends SimpleHackConfig {
private int weepingAngelPlayer;
private boolean blockWaterInHell;
private boolean minecartTeleport;
private boolean obsidianGenerators;
private boolean personalDeathMessages;
private boolean disableNetheriteCrafting;
private boolean goldBlockTeleport;
public GameFeaturesConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
@@ -55,23 +51,31 @@ public class GameFeaturesConfig extends SimpleHackConfig {
this.witherSpawning = config.getBoolean("witherSpawning", false);
if (!this.witherSpawning) plugin().log(" Wither Spawning is disabled");
this.patrolSpawning = config.getBoolean("patrolSpawning", false);
if (!this.patrolSpawning) plugin().log("Patrol Spawning is disabled");
this.isPhantomSpawning = config.getBoolean("phantomSpawning", false);
if (!this.isPhantomSpawning) plugin().log("Phantom Spawning is disabled");
this.enderChestPlacement = config.getBoolean("enderChestPlacement", true);
if (!this.enderChestPlacement) plugin().log(" Placing EnderChests is disabled");
this.enderChestUse = config.getBoolean("enderChestUse", false);
if (!this.enderChestUse) plugin().log(" Using EnderChests is disabled");
this.grindstoneUse = config.getBoolean("grindstoneUse", false);
if (!this.grindstoneUse) plugin().log(" Using Grindstones is disabled");
this.shulkerBoxUse = config.getBoolean("shulkerBoxUse", false);
if (!this.shulkerBoxUse) plugin().log(" Using Shulker Boxes is disabled");
this.totemPowers = config.getBoolean("totemPower", false);
if (!this.totemPowers) plugin().log(" Undeath via totems is disabled");
this.elytraUse = config.getBoolean("elytraUse", true);
if (!this.elytraUse) plugin().log(" Elytra use is disabled");
this.chorusFruitUse = config.getBoolean("chorusFruitTeleportation", false);
if (!this.chorusFruitUse) plugin().log(" Chorus Fruit Teleportation is disabled");
if (!this.chorusFruitUse) {
plugin().log(" Chorus Fruit Teleportation is disabled");
}
this.weepingAngel = config.getBoolean("weepingAngel.enabled", false);
if (this.weepingAngel) {
@@ -84,6 +88,21 @@ public class GameFeaturesConfig extends SimpleHackConfig {
this.blockWaterInHell = config.getBoolean("blockWaterInHell", true);
if (this.blockWaterInHell) plugin().log(" Blocking bucket use in hell biomes");
this.minecartTeleport = config.getBoolean("minecartTeleport", false);
if (this.minecartTeleport) plugin().log(" Minecart teleporter enabled");
this.obsidianGenerators = config.getBoolean("obsidianGenerators", false);
if (this.obsidianGenerators) plugin().log(" Obsidian generators enabled.");
this.personalDeathMessages = config.getBoolean("personalDeathMessages", false);
if (this.personalDeathMessages) plugin().log(" Personal death messages enabled.");
this.disableNetheriteCrafting = config.getBoolean("disableNetheriteCrafting", true);
if (this.disableNetheriteCrafting) plugin().log(" Disable Netherite Crafting enabled.");
this.goldBlockTeleport = config.getBoolean("goldBlockTeleport", false);
if (this.personalDeathMessages) plugin().log(" Gold block teleporter enabled.");
/* Add additional feature config grabs here. */
}
@@ -102,6 +121,14 @@ public class GameFeaturesConfig extends SimpleHackConfig {
return this.witherSpawning;
}
public boolean isPatrolSpawning() {
return this.patrolSpawning;
}
public boolean isPhantomSpawning() {
return this.isPhantomSpawning;
}
public boolean isEnderChestPlacement() {
return this.enderChestPlacement;
}
@@ -110,22 +137,22 @@ public class GameFeaturesConfig extends SimpleHackConfig {
return this.enderChestUse;
}
public boolean isGrindstoneUse() {
return this.grindstoneUse;
}
public boolean isShulkerBoxUse() {
return this.shulkerBoxUse;
}
public boolean isTotemPowers() {
return this.totemPowers;
}
public boolean isElytraUse() {
return this.elytraUse;
}
public boolean isChorusFruitTeleportation() {
return this.chorusFruitUse;
}
public boolean isWeepingAngel() {
return this.weepingAngel;
}
@@ -141,6 +168,26 @@ public class GameFeaturesConfig extends SimpleHackConfig {
public boolean isBlockWaterInHell() {
return this.blockWaterInHell;
}
public boolean isMinecartTeleport() {
return this.minecartTeleport;
}
public boolean isObsidianGenerators() {
return this.obsidianGenerators;
}
public boolean isPersonalDeathMessages() {
return this.personalDeathMessages;
}
public boolean isDisableNetheriteCrafting() {
return this.disableNetheriteCrafting;
}
public boolean isGoldblockTeleport() {
return goldBlockTeleport;
}
}
@@ -1,15 +1,13 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.ArrayList;
import java.util.logging.Level;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.configuration.ConfigurationSection;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
import java.util.ArrayList;
import java.util.logging.Level;
public class GameFixesConfig extends SimpleHackConfig {
private boolean blockElytraBreakBug;
@@ -19,11 +17,21 @@ public class GameFixesConfig extends SimpleHackConfig {
private boolean stopRailDupe;
private boolean stopEndPortalDeletion;
private boolean stopBedBombing;
private boolean stopAnchorBombing;
private ArrayList<BlockFace> bfArray;
private ArrayList<Material> railArray;
private ArrayList<Material> pistonArray;
private boolean preventTreeWrap;
private boolean maintainFlatBedrock;
private boolean fixPearlGlitch;
private boolean preventLongSigns;
private int signLengthLimit;
private boolean preventLongSignsAbsolute;
private boolean cancelLongSignEvent;
private boolean hardLimitBookPageSize;
public GameFixesConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
wireUpArrays();
@@ -33,10 +41,10 @@ public class GameFixesConfig extends SimpleHackConfig {
blockElytraBreakBug = config.getBoolean("blockElytraBreakBug", true);
damageOnElytraBreakBug = config.getDouble("damageOnElytraBreakBug", 0.0d);
if (blockElytraBreakBug) plugin().log(Level.INFO, " Block Elytra 1height break bug is enabled, doing {} damage to violators", damageOnElytraBreakBug);
canStorageTeleport = config.getBoolean("canStorageTeleport");
if (!canStorageTeleport) plugin().log(" Storage holder teleportation is disabled.");
stopHopperDupe = config.getBoolean("stopHopperDupe");
if (stopHopperDupe) plugin().log(" Stop Hopper self-feeding Dupe is enabled.");
@@ -45,21 +53,54 @@ public class GameFixesConfig extends SimpleHackConfig {
stopEndPortalDeletion = config.getBoolean("stopEndPortalDeletion", true);
if (stopEndPortalDeletion) plugin().log(" Stop End Portal Deletion is enabled.");
stopBedBombing = config.getBoolean("stopBedBombingInHellBiomes", true);
if (stopBedBombing) plugin().log(" Stop Bed Bombing In Hell Biomes is enabled.");
stopAnchorBombing = config.getBoolean("stopAnchorBombing", true);
if (stopAnchorBombing) plugin().log("Stop Anchor bombing outside the Nether is enabled");
preventTreeWrap = config.getBoolean("preventTreeWraparound", true);
if (preventTreeWrap) plugin().log(" Stop tree wrapping into bedrock is enabled.");
maintainFlatBedrock = config.getBoolean("maintainFlatBedrock", false);
if (maintainFlatBedrock) plugin().log(" Maintaining bedrock flatness.");
fixPearlGlitch = config.getBoolean("fixPearlGlitch", false);
if (fixPearlGlitch) plugin().log(" Pearl glitch fix enabled.");
preventLongSigns = config.getBoolean("preventLongSigns", true);
signLengthLimit = config.getInt("signLengthLimit", 100);
preventLongSignsAbsolute = config.getBoolean("preventLongSignsAbsolute", true);
cancelLongSignEvent = config.getBoolean("cancelLongSignEvent", false);
hardLimitBookPageSize = config.getBoolean("hardLimitBookPageCount", true);
}
private void wireUpArrays() {
bfArray = new ArrayList<BlockFace>();
railArray = new ArrayList<Material>();
pistonArray = new ArrayList<Material>();
bfArray = new ArrayList<>();
railArray = new ArrayList<>();
pistonArray = new ArrayList<>();
railArray.add(Material.RAILS);
railArray.add(Material.RAIL);
railArray.add(Material.ACTIVATOR_RAIL);
railArray.add(Material.DETECTOR_RAIL);
railArray.add(Material.POWERED_RAIL);
railArray.add(Material.CARPET);
railArray.add(Material.WHITE_CARPET);
railArray.add(Material.RED_CARPET);
railArray.add(Material.BLACK_CARPET);
railArray.add(Material.BLUE_CARPET);
railArray.add(Material.BROWN_CARPET);
railArray.add(Material.CYAN_CARPET);
railArray.add(Material.GRAY_CARPET);
railArray.add(Material.GREEN_CARPET);
railArray.add(Material.LIGHT_BLUE_CARPET);
railArray.add(Material.LIGHT_GRAY_CARPET);
railArray.add(Material.LIME_CARPET);
railArray.add(Material.YELLOW_CARPET);
railArray.add(Material.PURPLE_CARPET);
railArray.add(Material.PINK_CARPET);
railArray.add(Material.ORANGE_CARPET);
railArray.add(Material.MAGENTA_CARPET);
bfArray.add(BlockFace.NORTH);
bfArray.add(BlockFace.SOUTH);
@@ -68,10 +109,10 @@ public class GameFixesConfig extends SimpleHackConfig {
bfArray.add(BlockFace.UP);
bfArray.add(BlockFace.DOWN);
pistonArray.add(Material.PISTON_BASE);
pistonArray.add(Material.PISTON_EXTENSION);
pistonArray.add(Material.PISTON_MOVING_PIECE);
pistonArray.add(Material.PISTON_STICKY_BASE);
pistonArray.add(Material.PISTON);
pistonArray.add(Material.PISTON_HEAD);
pistonArray.add(Material.MOVING_PISTON);
pistonArray.add(Material.STICKY_PISTON);
}
public boolean isBlockElytraBreakBug() {
@@ -109,8 +150,45 @@ public class GameFixesConfig extends SimpleHackConfig {
public ArrayList<Material> getPistonArray() {
return pistonArray;
}
public boolean stopBedBombing() {
return this.stopBedBombing;
}
}
public boolean stopAnchorBombing() {
return this.stopAnchorBombing;
}
public boolean stopTreeWraparound() {
return preventTreeWrap;
}
public boolean hardLimitBookPageSize() {
return hardLimitBookPageSize;
}
public boolean maintainFlatBedrock() {
return maintainFlatBedrock;
}
public boolean fixPearlGlitch() {
return fixPearlGlitch;
}
public boolean isPreventLongSigns() {
return preventLongSigns;
}
public int getSignLengthLimit() {
return signLengthLimit;
}
public boolean isPreventLongSignsAbsolute() {
return preventLongSignsAbsolute;
}
public boolean isCancelLongSignEvent() {
return cancelLongSignEvent;
}
}
@@ -1,208 +1,341 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.UUID;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.ChatColor;
import org.bukkit.Bukkit;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
/**
* Holds configurations for the GameTuning module.
*
* @author ProgrammerDan
*/
public class GameTuningConfig extends SimpleHackConfig {
private boolean chunkLimitsEnabled;
private Map<Material, Integer> blockEntityLimits;
private Set<UUID> exemptFromLimits;
private String chunkLimitsExceededMessage;
private boolean daytimeBedEnabled;
private String daytimeBedSpawnSetMessage;
private boolean oneToOneNether;
private boolean returnNetherPortal;
private boolean chestedMinecartInventories;
private boolean hopperMinecartInventories;
public GameTuningConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
@Override
protected void wireup(ConfigurationSection config) {
ConfigurationSection chunkLimits = config.getConfigurationSection("chunkLimits");
wireupChunkLimits(chunkLimits);
ConfigurationSection daytimeBed = config.getConfigurationSection("daytimeBed");
wireupDaytimeBed(daytimeBed);
this.oneToOneNether = config.getBoolean("oneToOneNether", false);
if (oneToOneNether) plugin().log("One to One Nether is enabled.");
this.returnNetherPortal = config.getBoolean("returnNetherPortal", true);
if (!returnNetherPortal) plugin().log("Return Nether Portals disabled.");
this.chestedMinecartInventories = config.getBoolean("chestedMinecartInventories", true);
if (!chestedMinecartInventories) plugin().log("Chested Minecart Inventories are disabled.");
this.hopperMinecartInventories = config.getBoolean("hopperMinecartInventories", true);
if (!hopperMinecartInventories) plugin().log("Hopper Minecart Inventories are disabled.");
/* Add additional tuning config grabs here. */
}
/**
* Wireup for Chunk Limits configuration
*
* @author ProgrammerDan
*/
private void wireupChunkLimits(ConfigurationSection config) {
this.blockEntityLimits = new HashMap<Material, Integer>();
this.exemptFromLimits = new HashSet<UUID>();
this.chunkLimitsEnabled = false;
if (config == null) return;
this.chunkLimitsEnabled = config.getBoolean("enabled", false);
this.chunkLimitsExceededMessage = ChatColor.translateAlternateColorCodes('&',
config.getString("exceededMessage",
ChatColor.RED + "Limit for this chunk reached, you cannot place that! Use a different block."));
// for each "chunkLimits.tileEntities: " entry, record limit.
ConfigurationSection tileEntities = config.getConfigurationSection("tileEntities");
for (String key : tileEntities.getKeys(false)) {
int limit = tileEntities.getInt(key);
Material toBlock = Material.getMaterial(key);
if (toBlock == null) continue;
this.blockEntityLimits.put(toBlock, limit);
plugin().log(Level.INFO, " Limiting {0} to {1} per chunk", toBlock.toString(), limit);
}
// for each "chunkLimits.exempt: " entry, record limit.
List<String> exempts = config.getStringList("exempt");
for (String exempt : exempts) {
UUID pExempt = null;
try {
pExempt = UUID.fromString(exempt);
} catch (IllegalArgumentException iae) {
pExempt = null;
}
if (pExempt == null) {
OfflinePlayer ofp = Bukkit.getOfflinePlayer(exempt);
if (ofp != null) {
pExempt = ofp.getUniqueId();
}
}
if (pExempt == null) {
plugin().log(Level.INFO, " Unable to exempt {0}, player not found", exempt);
} else {
plugin().log(Level.INFO, " Adding limits exemption for {0} as {1}", exempt, pExempt);
this.exemptFromLimits.add(pExempt);
}
}
}
/**
* Wireup for enabling setting your spawn during the day.
*
* @author Amelorate
*/
private void wireupDaytimeBed(ConfigurationSection config) {
if (config == null) {
this.daytimeBedEnabled = false;
return;
}
this.daytimeBedEnabled = config.getBoolean("enabled", false);
this.daytimeBedSpawnSetMessage = ChatColor.translateAlternateColorCodes('&',
config.getString("spawnSetMessage", ChatColor.GRAY + "Your spawn has been set."));
}
/**
* @return true / false if chunk limits are on
*/
public boolean areChunkLimitsEnabled() {
return this.chunkLimitsEnabled;
}
/**
* Gets the chunk limit for a specified material.
* Returns null if no limit is set.
*
* @param mat the Material to return a limit on, if set.
* @return the max # per chunk, or null if no limit set.
*/
public Integer getChunkLimit(Material mat) {
return this.chunkLimitsEnabled ? this.blockEntityLimits.get(mat) : null;
}
/**
* Returns false if the supplied UUID is exempt from limits.
*
* @param player the UUID to check
* @returns false it the player is exempt, true otherwise.
*/
public boolean applyChunkLimits(UUID player) {
return this.chunkLimitsEnabled ? !this.exemptFromLimits.contains(player) : false;
}
/**
* @return the generic limit message. TODO: Replace with customized message tailored per player.
*/
public String getChunkLimitsExceededMessage() {
return this.chunkLimitsExceededMessage;
}
/**
* @return The message that is sent to the player if they right click on a bed and set their spawn.
* Empty string if there should not be a message, and null if daytime beds are disabled.
*/
public String getDaytimeBedSpawnSetMessage() {
return daytimeBedEnabled ? daytimeBedSpawnSetMessage : null;
}
/**
* @return If setting your spawn with a bed during the daytime is enabled.
*/
public boolean areDaytimeBedsEnabled() {
return daytimeBedEnabled;
}
/**
* @return If one to one nether is enabled.
*/
public boolean isOneToOneNether() {
return oneToOneNether;
}
/**
* @return If return portals are enabled.
*/
public boolean isReturnNetherPortal() {
return returnNetherPortal;
}
public boolean isChestedMinecartInventories() {
return chestedMinecartInventories;
}
public boolean isHopperMinecartInventories() {
return hopperMinecartInventories;
}
}
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.ConfigurationSection;
/**
* Holds configurations for the GameTuning module.
*
* @author ProgrammerDan
*/
public class GameTuningConfig extends SimpleHackConfig {
private boolean chunkLimitsEnabled;
private Map<Material, Integer> blockEntityLimits;
private Set<UUID> exemptFromLimits;
private String chunkLimitsExceededMessage;
private boolean daytimeBedEnabled;
private String daytimeBedSpawnSetMessage;
private boolean oneToOneNether;
private boolean returnNetherPortal;
private boolean allowNetherTravel;
private boolean chestedMinecartInventories;
private boolean hopperMinecartInventories;
private boolean enderChestInventories;
private boolean stopTrapHorses;
private boolean killTrapHorses;
private boolean changeSpawnerType;
private boolean allowVillagerTrading;
private boolean enderGrief;
private boolean witherGrief;
private boolean dragonGrief;
private boolean preventFallingThroughBedrock;
private boolean badOmen;
private boolean rainReduction;
private double rainOccurrenceChance;
private Set<Material> noPlace;
public GameTuningConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
@Override
protected void wireup(ConfigurationSection config) {
ConfigurationSection chunkLimits = config.getConfigurationSection("chunkLimits");
wireupChunkLimits(chunkLimits);
ConfigurationSection daytimeBed = config.getConfigurationSection("daytimeBed");
wireupDaytimeBed(daytimeBed);
ConfigurationSection rainReduction = config.getConfigurationSection("rainReduction");
wireupRainReduction(rainReduction);
this.oneToOneNether = config.getBoolean("oneToOneNether", false);
if (oneToOneNether) plugin().log("One to One Nether is enabled.");
this.returnNetherPortal = config.getBoolean("returnNetherPortal", true);
if (!returnNetherPortal) plugin().log("Return Nether Portals disabled.");
this.allowNetherTravel = config.getBoolean("allowNetherTravel", false);
if (!allowNetherTravel) plugin().log("Nether travel disabled.");
this.chestedMinecartInventories = config.getBoolean("chestedMinecartInventories", true);
if (!chestedMinecartInventories) plugin().log("Chested Minecart Inventories are disabled.");
this.hopperMinecartInventories = config.getBoolean("hopperMinecartInventories", true);
if (!hopperMinecartInventories) plugin().log("Hopper Minecart Inventories are disabled.");
this.enderChestInventories = config.getBoolean("enderChestInventories", false);
if (!enderChestInventories) plugin().log("Ender chest inventories are disabled.");
this.stopTrapHorses = config.getBoolean("stopTrapHorses", true);
if (stopTrapHorses) plugin().log("Stopping trap horses from being annoying.");
this.killTrapHorses = config.getBoolean("killTrapHorses", true);
if (killTrapHorses) plugin().log("Killing trap horses as well");
this.changeSpawnerType = config.getBoolean("changeSpawnerType", false);
if (!changeSpawnerType) plugin().log("Spawner type changing disabled");
this.allowVillagerTrading = config.getBoolean("allowVillagerTrading", false);
if (!allowVillagerTrading) plugin().log("Villager trading disabled");
this.enderGrief = config.getBoolean("enderGrief", false);
if (!enderGrief) plugin().log("Ender grief is disabled.");
this.witherGrief = config.getBoolean("witherGrief", false);
if (!witherGrief) plugin().log("Wither grief is disabled.");
this.dragonGrief = config.getBoolean("dragonGrief", true);
if (!dragonGrief) plugin().log("Dragon grief is disabled.");
this.preventFallingThroughBedrock = config.getBoolean("preventFallingThroughBedrock", true);
this.badOmen = config.getBoolean("badOmen", false);
if (!badOmen) plugin().log("Bad Omen effect is disabled.");
noPlace = new HashSet<>();
if(config.isList("noplace")) {
for(String entry : config.getStringList("noplace")) {
try {
noPlace.add(Material.valueOf(entry));
}
catch (IllegalArgumentException e) {
plugin().log(Level.WARNING, "Material " + entry + " at " + config.getCurrentPath() + " could not be parsed");
}
}
}
/* Add additional tuning config grabs here. */
}
/**
* Wireup for Chunk Limits configuration
*
* @author ProgrammerDan
*/
private void wireupChunkLimits(ConfigurationSection config) {
this.blockEntityLimits = new HashMap<>();
this.exemptFromLimits = new HashSet<>();
this.chunkLimitsEnabled = false;
if (config == null) return;
this.chunkLimitsEnabled = config.getBoolean("enabled", false);
this.chunkLimitsExceededMessage = ChatColor.translateAlternateColorCodes('&',
config.getString("exceededMessage",
ChatColor.RED + "Limit for this chunk reached, you cannot place that! Use a different block."));
// for each "chunkLimits.tileEntities: " entry, record limit.
ConfigurationSection tileEntities = config.getConfigurationSection("tileEntities");
for (String key : tileEntities.getKeys(false)) {
int limit = tileEntities.getInt(key);
Material toBlock = Material.getMaterial(key);
if (toBlock == null) continue;
this.blockEntityLimits.put(toBlock, limit);
plugin().log(Level.INFO, " Limiting {0} to {1} per chunk", toBlock.toString(), limit);
}
// for each "chunkLimits.exempt: " entry, record limit.
List<String> exempts = config.getStringList("exempt");
for (String exempt : exempts) {
UUID pExempt = null;
try {
pExempt = UUID.fromString(exempt);
} catch (IllegalArgumentException iae) {
pExempt = null;
}
if (pExempt == null) {
OfflinePlayer ofp = Bukkit.getOfflinePlayer(exempt);
if (ofp != null) {
pExempt = ofp.getUniqueId();
}
}
if (pExempt == null) {
plugin().log(Level.INFO, " Unable to exempt {0}, player not found", exempt);
} else {
plugin().log(Level.INFO, " Adding limits exemption for {0} as {1}", exempt, pExempt);
this.exemptFromLimits.add(pExempt);
}
}
}
/**
* Wireup for enabling setting your spawn during the day.
*
* @author Amelorate
*/
private void wireupDaytimeBed(ConfigurationSection config) {
if (config == null) {
this.daytimeBedEnabled = false;
return;
}
this.daytimeBedEnabled = config.getBoolean("enabled", false);
this.daytimeBedSpawnSetMessage = ChatColor.translateAlternateColorCodes('&',
config.getString("spawnSetMessage", ChatColor.GRAY + "Your spawn has been set."));
}
private void wireupRainReduction(ConfigurationSection config) {
if (config == null) {
this.rainReduction = false;
return;
}
this.rainReduction = config.getBoolean("enabled", false);
this.rainOccurrenceChance = config.getDouble("rainOccurrenceChance");
}
/**
* @return true / false if chunk limits are on
*/
public boolean areChunkLimitsEnabled() {
return this.chunkLimitsEnabled;
}
/**
* Gets the chunk limit for a specified material.
* Returns null if no limit is set.
*
* @param mat the Material to return a limit on, if set.
* @return the max # per chunk, or null if no limit set.
*/
public Integer getChunkLimit(Material mat) {
return this.chunkLimitsEnabled ? this.blockEntityLimits.get(mat) : null;
}
/**
* Returns false if the supplied UUID is exempt from limits.
*
* @param player the UUID to check
* @return false it the player is exempt, true otherwise.
*/
public boolean applyChunkLimits(UUID player) {
return this.chunkLimitsEnabled ? !this.exemptFromLimits.contains(player) : false;
}
/**
* @return the generic limit message. TODO: Replace with customized message tailored per player.
*/
public String getChunkLimitsExceededMessage() {
return this.chunkLimitsExceededMessage;
}
/**
* @return The message that is sent to the player if they right click on a bed and set their spawn.
* Empty string if there should not be a message, and null if daytime beds are disabled.
*/
public String getDaytimeBedSpawnSetMessage() {
return daytimeBedEnabled ? daytimeBedSpawnSetMessage : null;
}
/**
* @return If setting your spawn with a bed during the daytime is enabled.
*/
public boolean areDaytimeBedsEnabled() {
return daytimeBedEnabled;
}
/**
* @return If one to one nether is enabled.
*/
public boolean isOneToOneNether() {
return oneToOneNether;
}
/**
* @return If return portals are enabled.
*/
public boolean isReturnNetherPortal() {
return returnNetherPortal;
}
public boolean allowNetherTravel() {
return allowNetherTravel;
}
public boolean isChestedMinecartInventories() {
return chestedMinecartInventories;
}
public boolean isHopperMinecartInventories() {
return hopperMinecartInventories;
}
public boolean isEnderChestInventories() {
return enderChestInventories;
}
public boolean stopTrapHorses() {
return stopTrapHorses;
}
public boolean killTrapHorses() {
return killTrapHorses;
}
public boolean canChangeSpawnerType() {
return changeSpawnerType;
}
public boolean allowVillagerTrading() {
return allowVillagerTrading;
}
public boolean isEnderGrief() {
return enderGrief;
}
public boolean isWitherGrief() {
return witherGrief;
}
public boolean isDragonGrief() {
return dragonGrief;
}
public boolean isPreventFallingThroughBedrock() {
return preventFallingThroughBedrock;
}
public boolean isBadOmenEnabled() {
return badOmen;
}
public boolean isRainReductionEnabled() {
return rainReduction;
}
public double getRainOccurrenceChance() {
return rainOccurrenceChance;
}
public boolean canPlace(Material mat) {
return !noPlace.contains(mat);
}
public Map<Material, Integer> getBlockEntityLimits() {
return blockEntityLimits;
}
}
@@ -0,0 +1,24 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
public class HorseStatsConfig extends SimpleHackConfig {
private Material horseCheckerItem;
public HorseStatsConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
@Override
protected void wireup(ConfigurationSection config) {
horseCheckerItem = Material.getMaterial(config.getString("wand"));
}
public Material getHorseCheckerItem() {
return horseCheckerItem;
}
}
@@ -1,11 +1,9 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import java.util.List;
import org.bukkit.configuration.ConfigurationSection;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.List;
import org.bukkit.configuration.ConfigurationSection;
/**
* Define an "into" list of Strings where the Strings are event classpaths.
@@ -24,7 +22,7 @@ import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
*/
public class InsightConfig extends SimpleHackConfig {
private List<String> insightOn;
public InsightConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
@@ -37,7 +35,7 @@ public class InsightConfig extends SimpleHackConfig {
insightOn = null;
}
}
public List<String> getInsightOn() {
return insightOn;
}
@@ -1,10 +1,10 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.ArrayList;
import java.util.List;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
@@ -12,16 +12,13 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
/**
* Simple Config wrapper for Introbook specification.
*
* @author ProgrammerDan <programmerdan@gmail.com>
* @author ProgrammerDan programmerdan@gmail.com
*/
public class IntrobookConfig extends SimpleHackConfig {
/**
* The title of the book
*/
@@ -39,11 +36,11 @@ public class IntrobookConfig extends SimpleHackConfig {
* just drops.
*/
private boolean follow;
public IntrobookConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
public IntrobookConfig(ConfigurationSection base) {
super(SimpleAdminHacks.instance(), base);
}
@@ -53,26 +50,26 @@ public class IntrobookConfig extends SimpleHackConfig {
this.title = config.getString("contents.title");
this.author = config.getString("contents.author");
List<String> tPages = config.getStringList("contents.pages");
if (this.title != null) {
this.title = ChatColor.translateAlternateColorCodes('&', this.title);
} else {
this.setEnabled(false);
setShouldEnable(false);
plugin().log("Introbook disabled, no title given");
return;
}
if (this.author != null) {
this.author = ChatColor.translateAlternateColorCodes('&', this.author);
}
if (tPages != null && tPages.size() > 0) {
this.pages = new ArrayList<String>(tPages.size());
for (String page : tPages) {
this.pages.add(ChatColor.translateAlternateColorCodes('&', page));
}
} else {
this.setEnabled(false);
setShouldEnable(false);
plugin().log("Introbook disabled, no pages");
return;
}
@@ -105,34 +102,34 @@ public class IntrobookConfig extends SimpleHackConfig {
is.setItemMeta(bm);
return is;
}
public boolean isIntroBook(ItemStack is) {
if (Material.WRITTEN_BOOK != is.getType()) return false;
ItemMeta meta = is.getItemMeta();
if (!(meta instanceof BookMeta)) return false;
BookMeta book = (BookMeta) meta;
if (!(getTitle().contains(
book.getTitle().replaceAll("\\$\\{player\\}", "")
))) return false;
if (!(getAuthor().equals(book.getAuthor()))) return false;
return true;
}
public boolean doesFollow() {
return this.follow;
}
public String getTitle(){
return this.title;
}
public String getAuthor() {
return this.author;
}
public List<String> getPages() {
return new ArrayList<String>(this.pages);
}
@@ -1,9 +1,8 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import org.bukkit.configuration.ConfigurationSection;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import org.bukkit.configuration.ConfigurationSection;
/**
* Stub; this hack just turns on and off, and has some commands.
@@ -1,16 +1,14 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.BroadcastLevel;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import com.programmerdan.minecraft.simpleadminhacks.BroadcastLevel;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
/**
* Some simple new player related configuration options.
* Might have overlap with other plugins at some point (CivMenu, looking at you)
@@ -35,13 +33,17 @@ public class NewfriendAssistConfig extends SimpleHackConfig {
* What is the newbie kit composed of.
*/
private ItemStack[] newbieKit;
/**
* Should we give a kit to people who respawn without a bed/anchor?
*/
private boolean giveToRandomSpawners;
//private List<HelpTips> helps; // TODO
public NewfriendAssistConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
public NewfriendAssistConfig(ConfigurationSection base) {
super(SimpleAdminHacks.instance(), base);
}
@@ -49,7 +51,7 @@ public class NewfriendAssistConfig extends SimpleHackConfig {
@Override
protected void wireup(ConfigurationSection config) {
this.announceMessage = config.getString("announce", "&f%Player% is brand new!");
List<String> broadcastTo = config.getStringList("broadcast");
if (this.announceBroadcast == null) {
this.announceBroadcast = new LinkedList<BroadcastLevel>();
@@ -58,7 +60,7 @@ public class NewfriendAssistConfig extends SimpleHackConfig {
for (String type : broadcastTo) {
try {
this.announceBroadcast.add(BroadcastLevel.valueOf(type));
plugin().log(Level.INFO, " broadcast: {0}", type);
} catch (IllegalArgumentException iae) {
// noop
@@ -80,8 +82,9 @@ public class NewfriendAssistConfig extends SimpleHackConfig {
} else {
plugin().log(Level.INFO, " introkit disabled");
}
this.giveToRandomSpawners = config.getBoolean("giveIntroKitToRandomSpawners", true);
}
public List<BroadcastLevel> getAnnounceBroadcast() {
return announceBroadcast;
}
@@ -93,8 +96,12 @@ public class NewfriendAssistConfig extends SimpleHackConfig {
public boolean isIntroKitEnabled() {
return hasNewbieKit;
}
public ItemStack[] getIntroKit() {
return newbieKit;
}
public boolean giveToRandomSpawners() {
return giveToRandomSpawners;
}
}
@@ -0,0 +1,80 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.List;
import java.util.Objects;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import vg.civcraft.mc.civmodcore.config.ConfigHelper;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;
import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils;
import vg.civcraft.mc.civmodcore.utilities.CivLogger;
public final class OneTimeTeleportConfig extends SimpleHackConfig {
private final CivLogger LOGGER = CivLogger.getLogger(OneTimeTeleportConfig.class);
private List<Material> materialBlacklist;
private List<Material> unsafeMaterials;
private long timeLimitOnUsage;
private boolean limitToSameWorld;
public OneTimeTeleportConfig(
final @NotNull SimpleAdminHacks plugin,
final @NotNull ConfigurationSection config
) {
super(plugin, config);
}
@Override
protected void wireup(final ConfigurationSection config) {
// Parse blacklisted item materials
this.materialBlacklist = config.getStringList("material_blacklist")
.stream()
.map((raw) -> {
final Material material = MaterialUtils.getMaterial(raw);
if (!ItemUtils.isValidItemMaterial(material)) {
LOGGER.warning("Blacklisted material [" + raw + "] is not a valid item material!");
return null;
}
return material;
})
.filter(Objects::nonNull)
.toList();
// Parse unsafe blocks
this.unsafeMaterials = config.getStringList("unsafe_materials")
.stream()
.map((raw) -> {
final Material material = MaterialUtils.getMaterial(raw);
if (material == null || !material.isBlock()) {
LOGGER.warning("Unsafe material [" + raw + "] is not a valid block material!");
return null;
}
return material;
})
.filter(Objects::nonNull)
.toList();
// Parse maximum time limit to use OTT
this.timeLimitOnUsage = ConfigHelper.parseTime(config.getString("ott_timeout", "2d"));
// Parse same-world limit
this.limitToSameWorld = config.getBoolean("limitToSameWorld", false);
}
public @NotNull List<Material> getMaterialBlacklist() {
return Objects.requireNonNullElseGet(this.materialBlacklist, List::of);
}
public @NotNull List<Material> getUnsafeMaterials() {
return Objects.requireNonNullElseGet(this.unsafeMaterials, List::of);
}
public long getTimeLimitOnUsageInMillis() {
return this.timeLimitOnUsage;
}
public boolean isLimitingToSameWorld() {
return this.limitToSameWorld;
}
}
@@ -1,7 +1,7 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import org.bukkit.configuration.ConfigurationSection;
public class ReinforcedChestBreakConfig extends SimpleHackConfig{
@@ -1,11 +1,9 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import java.util.logging.Level;
import org.bukkit.configuration.ConfigurationSection;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.logging.Level;
import org.bukkit.configuration.ConfigurationSection;
public class SanityHackConfig extends SimpleHackConfig {
@@ -13,7 +11,7 @@ public class SanityHackConfig extends SimpleHackConfig {
private boolean trackBreak; //Track block broken
//private boolean trackOpen; //Track block opened (chest)
private int belowYLevel; //Track things below this y level
public SanityHackConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
@@ -22,29 +20,29 @@ public class SanityHackConfig extends SimpleHackConfig {
protected void wireup(ConfigurationSection config) {
this.trackPlace = config.getBoolean("trackPlace", true);
plugin().log(Level.INFO, "Tracking placement: {0}", this.trackPlace);
this.trackBreak = config.getBoolean("trackBreak", true);
plugin().log(Level.INFO, "Tracking breaking: {0}", this.trackBreak);
/*this.trackOpen = config.getBoolean("trackOpen", false);
plugin().log(Level.INFO, "Tracking opening: {0}", this.trackOpen);*/
this.belowYLevel = config.getInt("belowYLevel", 7);
plugin().log(Level.INFO, "Tracking below Y: {0}", this.belowYLevel);
}
public boolean isTrackingPlace(){
return this.trackPlace;
}
public boolean isTrackingBreak(){
return this.trackBreak;
}
/*public boolean isTrackingOpen(){
return this.trackOpen;
}*/
public int getTrackingLevel(){
return this.belowYLevel;
}
@@ -0,0 +1,45 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
public class SaplingConfig extends SimpleHackConfig {
private Map<Material, Double> chanceMap;
private boolean allowFortune;
public SaplingConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
public SaplingConfig(SimpleAdminHacks plugin, ConfigurationSection base, boolean wireup) {
super(plugin, base, wireup);
}
@Override
protected void wireup(ConfigurationSection config) {
this.allowFortune = config.getBoolean("allow_fortune", false);
this.chanceMap = new HashMap<>();
for (String replace : config.getConfigurationSection("blocks").getKeys(false)) {
Material leaves = Material.matchMaterial(replace.toUpperCase());
//Incase this breaks, default to no extra drops
Double chance = config.getConfigurationSection("blocks").getDouble(replace, 0.0);
if (leaves != null && chance != null) {
chanceMap.put(leaves, chance);
}
}
}
public Map<Material, Double> getChanceMap() {
return Collections.unmodifiableMap(this.chanceMap);
}
public boolean isAllowFortune() {
return this.allowFortune;
}
}
@@ -1,19 +1,17 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.configuration.ConfigurationSection;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
public class TimingsHackConfig extends SimpleHackConfig {
private Short timingsMap;
private Map<String, Short> bindMaps;
private Map<Short, String> reverseBindMaps;
private Integer timingsMap;
private Map<String, Integer> bindMaps;
private Map<Integer, String> reverseBindMaps;
public TimingsHackConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
@@ -21,45 +19,45 @@ public class TimingsHackConfig extends SimpleHackConfig {
@Override
protected void wireup(ConfigurationSection config) {
timingsMap = config.contains("timingMap") ? (short) config.getInt("timingMap") : null;
bindMaps = new ConcurrentHashMap<String, Short>();
reverseBindMaps = new ConcurrentHashMap<Short, String>();
timingsMap = config.contains("timingMap") ? config.getInt("timingMap") : null;
bindMaps = new ConcurrentHashMap<>();
reverseBindMaps = new ConcurrentHashMap<>();
if (config.contains("bindings")) {
ConfigurationSection bindings = config.getConfigurationSection("bindings");
Set<String> keys = bindings.getKeys(false);
for (String key : keys) {
bindMaps.put(key, (short) bindings.getInt(key));
reverseBindMaps.put((short) bindings.getInt(key), key);
bindMaps.put(key, bindings.getInt(key));
reverseBindMaps.put(bindings.getInt(key), key);
}
}
}
public Short getTimingsMap() {
public Integer getTimingsMap() {
return timingsMap;
}
public void setTimingsMap(short mapId) {
public void setTimingsMap(int mapId) {
timingsMap = mapId;
getBase().set("timingMap", mapId);
}
public Short getBindMap(String bind) {
public Integer getBindMap(String bind) {
if (bind == null) {
return null;
}
return bindMaps.get(bind);
}
public void setBindMap(String bind, short mapId) {
Short prior = bindMaps.replace(bind, mapId);
public void setBindMap(String bind, int mapId) {
Integer prior = bindMaps.replace(bind, mapId);
if (prior != null) {
reverseBindMaps.remove(prior);
}
reverseBindMaps.replace(mapId, bind);
getBase().set("bindings." + bind, mapId);
}
public String getBindFromId(short mapId) {
public String getBindFromId(int mapId) {
return reverseBindMaps.get(mapId);
}
}
@@ -0,0 +1,24 @@
package com.programmerdan.minecraft.simpleadminhacks.configs;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import org.bukkit.configuration.ConfigurationSection;
public class ToggleLampConfig extends SimpleHackConfig {
private long cooldownTime;
public ToggleLampConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
super(plugin, base);
}
@Override
protected void wireup(ConfigurationSection config) {
this.cooldownTime = config.getLong("cooldownTime", 100);
}
public long getCooldownTime() {
return this.cooldownTime;
}
}
@@ -0,0 +1,31 @@
package com.programmerdan.minecraft.simpleadminhacks.framework;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
public abstract class BasicHack extends SimpleHack<BasicHackConfig> implements Listener {
public BasicHack(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
}
// REMEMBER TO CALL SUPER
@Override
public void onEnable() {
plugin().registerListener(this);
}
// REMEMBER TO CALL SUPER
@Override
public void onDisable() {
HandlerList.unregisterAll(this);
}
// YOU DON'T HAVE TO COPY THIS! It's inherited.
public static BasicHackConfig generate(final SimpleAdminHacks plugin, final ConfigurationSection config) {
return new BasicHackConfig(plugin, config);
}
}
@@ -0,0 +1,20 @@
package com.programmerdan.minecraft.simpleadminhacks.framework;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import org.bukkit.configuration.ConfigurationSection;
/**
* Use this when your hack either doesn't have any config values, or you're loading all your values though
* {@link AutoLoad}. If you want to extend this, extend {@link SimpleHackConfig} directly.
*/
public final class BasicHackConfig extends SimpleHackConfig {
public BasicHackConfig(final SimpleAdminHacks plugin, final ConfigurationSection base) {
super(plugin, base);
}
@Override
protected void wireup(final ConfigurationSection config) { }
}
@@ -0,0 +1,205 @@
package com.programmerdan.minecraft.simpleadminhacks.framework;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.reflect.ClassPath;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.exceptions.InvalidConfigException;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.configuration.ConfigurationSection;
public final class HackManager {
private static final String HACKS_PATH = "com.programmerdan.minecraft.simpleadminhacks.hacks";
private final SimpleAdminHacks plugin;
private final List<SimpleHack<?>> hacks;
public HackManager(final SimpleAdminHacks plugin) {
this.plugin = plugin;
this.hacks = new LinkedList<>();
}
@SuppressWarnings({"UnstableApiUsage", "unchecked"})
public void loadAllHacks() {
// Now load all the Hacks and register.
final ConfigurationSection hackConfigs = plugin.getConfig().getConfigurationSection("hacks");
if (hackConfigs == null) {
this.plugin.warning("There are no hacks defined under 'hacks' the config. Is this right?");
return;
}
try {
final ClassPath getSamplersPath = ClassPath.from(plugin.exposeClassLoader());
for (final var classInfo : getSamplersPath.getTopLevelClassesRecursive(HACKS_PATH)) {
try {
final Class<?> clazz = classInfo.load();
if (clazz == null || !SimpleHack.class.isAssignableFrom(clazz)) {
continue;
}
this.plugin.info("Found hack class [" + clazz.getName() + "]");
loadHack((Class<SimpleHack<?>>) clazz, hackConfigs.getConfigurationSection(clazz.getSimpleName()));
}
catch (final NoClassDefFoundError exception) {
this.plugin.warning("Unable to load hack \"" + classInfo.getSimpleName() + "\" probably due to a " +
"dependency / import error.", exception);
//continue;
}
catch (final Exception exception) {
this.plugin.warning("Failed to complete hack discovery of: " + classInfo.getName(), exception);
//continue;
}
}
}
catch (final Exception exception) {
this.plugin.warning("Failed to complete hack registration");
exception.printStackTrace();
return;
}
if (this.hacks.isEmpty()) {
this.plugin.warning("No hacks have been loaded.");
//return;
}
}
public SimpleHack<?> loadHack(final Class<SimpleHack<?>> hackClass, final ConfigurationSection config) {
Preconditions.checkNotNull(hackClass, "Hack class cannot be null!");
if (config == null) {
this.plugin.warning("Config for \"" + hackClass.getSimpleName() + "\" not defined, skipping.");
return null;
}
final String hackName = hackClass.getSimpleName();
SimpleHackConfig hackConfig;
try {
hackConfig = (SimpleHackConfig) MethodUtils.invokeExactStaticMethod(hackClass,"generate",
new Object[] { this.plugin, config },
new Class[] { SimpleAdminHacks.class, ConfigurationSection.class });
}
catch (final NoSuchMethodException exception) {
this.plugin.warning("Could not find config generator for \"" + hackName + "\", skipping.");
return null;
}
catch (final IllegalAccessException exception) {
this.plugin.warning("Could not access config generator for \"" + hackName + "\", skipping.");
exception.printStackTrace();
return null;
}
catch (final IllegalArgumentException exception) {
this.plugin.warning("Config generator for \"" + hackName + "\" threw an argument exception, skipping.");
exception.printStackTrace();
return null;
}
catch (final InvocationTargetException exception) {
this.plugin.warning("Config generator for \"" + hackName + "\" threw an invocation target exception, " +
"skipping.");
exception.printStackTrace();
return null;
}
if (hackConfig == null) {
this.plugin.warning("Config generator for \"" + hackName + "\" returned nothing, skipping.");
return null;
}
final var configClass = hackConfig.getClass();
if (configClass.isAnonymousClass()) {
this.plugin.warning("Config for \"" + hackName + "\" is anonymous, skipping.");
return null;
}
this.plugin.info("Config for \"" + hackName + "\" found; instance [" + hackConfig.toString() + "]");
SimpleHack<?> hack;
try {
hack = hackClass.getConstructor(SimpleAdminHacks.class, configClass).newInstance(this.plugin, hackConfig);
}
catch (final InvalidConfigException exception) {
this.plugin.warning("Failed to initialise \"" + hackName + "\"'s configuration", exception);
return null;
}
catch (final Exception exception) {
this.plugin.warning("Failed to initialise \"" + hackName + "\"", exception);
return null;
}
register(hack);
this.plugin.info("Registered hack: " + hackName);
interactWithHack(hack, SimpleHack::onLoad);
return hack;
}
public void enableAllHacks() {
for (final SimpleHack<?> hack : hacks) {
if (hack.shouldEnable()) {
enableHack(hack);
}
}
}
public void enableHack(final SimpleHack<?> hack) {
Preconditions.checkNotNull(hack, "Hack cannot be null!");
interactWithHack(hack, (ignored) -> {
if (!hack.isEnabled()) {
hack.enable();
}
});
}
public void disableAllHacks() {
for (final SimpleHack<?> hack : hacks) {
disableHack(hack);
}
}
public void disableHack(final SimpleHack<?> hack) {
Preconditions.checkNotNull(hack, "Hack cannot be null!");
interactWithHack(hack, (ignored) -> {
if (hack.isEnabled()) {
hack.disable();
}
});
}
/**
* Registers a new SimpleHack.
*/
void register(final SimpleHack<?> hack) {
this.hacks.add(hack);
}
/**
* Returns a wrapped version of hacks preventing external removal but allowing
* interaction with the hacks.
*/
public List<SimpleHack<? extends SimpleHackConfig>> getHacks() {
return Collections.unmodifiableList(this.hacks);
}
public SimpleHack<? extends SimpleHackConfig> getHack(final String name) {
if (Strings.isNullOrEmpty(name)) {
return null;
}
for (final SimpleHack<? extends SimpleHackConfig> candidate : this.hacks) {
if (StringUtils.equalsIgnoreCase(name, candidate.getClass().getSimpleName())) {
return candidate;
}
}
return null;
}
private void interactWithHack(final SimpleHack<?> hack, final Consumer<SimpleHack<?>> processor) {
Preconditions.checkNotNull(hack, "Cannot interact with a null hack!");
Preconditions.checkNotNull(processor, "Cannot interact with a null processor!");
try {
processor.accept(hack);
}
catch (final NoClassDefFoundError exception) {
this.plugin.warning("Unable to interact with hack \"" + hack.getClass().getSimpleName() + "\" probably " +
"due to a dependency / import error.", exception);
}
catch (final Exception exception) {
this.plugin.warning("Unable to deactivate hack \"" + hack.getClass().getSimpleName() + "\"", exception);
}
}
}
@@ -0,0 +1,251 @@
package com.programmerdan.minecraft.simpleadminhacks.framework;
import com.google.common.base.Strings;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.util.NumberConversions;
import vg.civcraft.mc.civmodcore.utilities.CivLogger;
/**
* Baseline structure for a managed hack.
*
* @author ProgrammerDan
*/
public abstract class SimpleHack<T extends SimpleHackConfig> {
protected final CivLogger logger;
protected final SimpleAdminHacks plugin;
protected final T config;
private boolean enabled;
public SimpleHack(final SimpleAdminHacks plugin, final T config) {
this.logger = CivLogger.getLogger(getClass());
this.plugin = Objects.requireNonNull(plugin, "Plugin cannot be null!");
this.config = Objects.requireNonNull(config, "Config cannot be null!");
}
public final SimpleAdminHacks plugin() {
return this.plugin;
}
public final T config() {
return this.config;
}
public final String getName() {
return getClass().getSimpleName();
}
public boolean isEnabled() {
return this.enabled;
}
/**
* @return Returns true if this hack should be enabled automatically due to its `enabled` config option.
*/
public boolean shouldEnable() {
return this.config.shouldEnable();
}
public final void enable() {
this.enabled = true;
this.config.setEnabled(true); // Needed for legacy hacks that rely on their config for current states
populateAutoLoadFields();
onEnable();
dataBootstrap();
registerCommands();
registerListeners();
this.plugin.info(ChatColor.AQUA + getName() + " enabled!");
}
public final void disable() {
unregisterListeners();
unregisterCommands();
dataCleanup();
onDisable();
this.config.setEnabled(false); // Needed for legacy hacks that rely on their config for current states
this.enabled = false;
this.plugin.info(ChatColor.AQUA + getName() + " disabled!");
}
/**
* Called when this hack is loaded.
*/
public void onLoad() { }
/**
* Called when this hack is enabled.
*/
public void onEnable() { }
/**
* Called when this hack is disabled.
*/
public void onDisable() {
}
/**
* @deprecated Use {@link #onEnable()} instead.
*/
@Deprecated
public void registerListeners() {}
/**
* @deprecated Use {@link #onEnable()} instead.
*/
@Deprecated
public void registerCommands() {}
/**
* @deprecated Use {@link #onEnable()} instead.
*/
@Deprecated
public void dataBootstrap() {}
/**
* @deprecated Use {@link #onDisable()} instead.
*/
@Deprecated
public void unregisterListeners() {}
/**
* @deprecated Use {@link #onDisable()} instead.
*/
@Deprecated
public void unregisterCommands() {}
/**
* @deprecated Use {@link #onDisable()} instead.
*/
@Deprecated
public void dataCleanup() {}
/**
* Not optional; customized status for this SimpleHack for display in CnC
*/
public String status() {
final StringBuilder genStatus = new StringBuilder();
genStatus.append(this.getClass().getSimpleName());
genStatus.append(" is ");
if (!isEnabled()) {
genStatus.append("disabled");
return genStatus.toString();
}
genStatus.append("enabled\n");
for(final Field field : getClass().getDeclaredFields()) {
genStatus.append(field.getName());
genStatus.append(" = ");
field.setAccessible(true);
try {
genStatus.append(field.get(this));
}
catch (final IllegalArgumentException | IllegalAccessException exception) {
plugin().warning("Failed to read field", exception);
}
genStatus.append('\n');
}
return genStatus.toString();
}
/**
* Simple name equality. Don't have two hacks with the same name please.
*/
@Override
public boolean equals(final Object object) {
if (object instanceof SimpleHack) {
final SimpleHack<?> other = (SimpleHack<?>) object;
if (StringUtils.equals(getName(), other.getName())) {
return true;
}
}
return false;
}
private void populateAutoLoadFields() {
final Class<?> hackClass = getClass();
final ConfigurationSection config = config().getBase();
for (final Field field : hackClass.getDeclaredFields()) {
final AutoLoad autoLoad = field.getAnnotation(AutoLoad.class);
if (autoLoad == null) {
continue;
}
final String hackName = getClass().getSimpleName();
final String identifier = Strings.isNullOrEmpty(autoLoad.id()) ? field.getName() : autoLoad.id();
// Value type
Class<?> clazz;
if (field.getType().getName().split("\\.").length == 1) { // TODO: Is this really necessary?
clazz = Array.get(Array.newInstance(field.getType(), 1), 0).getClass(); // unwrap primitives
}
else {
clazz = field.getType();
}
// Value itself
Object value;
try {
// Handle primitives (ints, booleans, and doubles are handled just fine already)
if (byte.class.isAssignableFrom(clazz) || Byte.class.isAssignableFrom(clazz)) {
value = NumberConversions.toByte(config.getInt(identifier));
}
else if (short.class.isAssignableFrom(clazz) || Short.class.isAssignableFrom(clazz)) {
value = NumberConversions.toShort(config.getInt(identifier));
}
else if (long.class.isAssignableFrom(clazz) || Long.class.isAssignableFrom(clazz)) {
value = config.getLong(identifier);
}
else if (float.class.isAssignableFrom(clazz) || Float.class.isAssignableFrom(clazz)) {
value = NumberConversions.toFloat(config.getDouble(identifier));
}
else if (char.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz)) {
final String temp = config.getString(identifier);
value = Strings.isNullOrEmpty(temp) ? 0 : temp.toCharArray()[0];
}
else {
value = config.getObject(identifier, clazz, null);
if (value == null)
value = config.getString(identifier);
}
if (value == null) {
// do nothing
}
else if (List.class.isAssignableFrom(clazz)) {
value = ((List<?>) value).stream()
.map(autoLoad.processor()::parse)
.collect(Collectors.toList());
}
// TODO: Create a map parser
// else if (Map.class.isAssignableFrom(clazz)) { }
else {
value = autoLoad.processor().parse(value);
}
}
catch (final Exception exception) {
throw new IllegalArgumentException("Hack \"" + hackName + "\" failed to read parameter \"" +
identifier + "\"", exception);
}
if (value == null) {
this.plugin.warning("Hack \"" + hackName + " has no value for field \"" + identifier + "\"");
continue;
}
try {
FieldUtils.writeField(field, this, value, true);
}
catch (final IllegalArgumentException | IllegalAccessException exception) {
throw new IllegalStateException("\"" + clazz.getSimpleName() + "\" in \"" + hackName + "\" could " +
"not be set.", exception);
}
this.plugin.info("Loaded \"" + identifier + "\" = \"" + value + "\"");
}
}
}
@@ -0,0 +1,135 @@
package com.programmerdan.minecraft.simpleadminhacks.framework;
import com.google.common.base.Strings;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import java.util.Map;
import org.bukkit.configuration.ConfigurationSection;
/**
* Simple config shell that assumes a {@link ConfigurationSection} as a base.
*
* @author ProgrammerDan
*/
public abstract class SimpleHackConfig {
private final SimpleAdminHacks plugin;
private final ConfigurationSection base;
private final Map<String, Object> data;
private boolean shouldEnable;
private boolean enabled; // Legacy support
/**
* Constructor that sets the internal config section. That base config is not visible
* outside of this class or its subclasses.
*
* Also extracts the one shared element of all hack configs, the "name" of the hack.
*
* Calls {@link #wireup(ConfigurationSection)}
*/
public SimpleHackConfig(final SimpleAdminHacks plugin, final ConfigurationSection base) {
this(plugin, base, true);
}
/**
* Constructor that sets the internal config section. That base config is not visible
* outside of this class or its subclasses.
*
* Also extracts the one shared element of all hack configs, the "name" of the hack.
*
* Calls {@link #wireup(ConfigurationSection)}
*/
public SimpleHackConfig(final SimpleAdminHacks plugin, final ConfigurationSection base, final boolean wireup) {
this.plugin = plugin;
this.base = base;
this.data = base.getValues(true);
this.shouldEnable = this.enabled = base.getBoolean("enabled", false);
this.plugin.info("Config for \"" + base.getName() + "\"; " +
"is " + (this.shouldEnable ? "enabled" : "disabled") + "; " +
"instance [" + this + "]");
if (wireup) {
wireup(base);
}
}
/**
* Subclasses should override this method.
*/
protected abstract void wireup(final ConfigurationSection config);
/**
* Might be useful in some situations for subclasses to directly access the
* stored base config().
*/
public ConfigurationSection getBase() {
return base;
}
protected SimpleAdminHacks plugin() {
if (this.plugin == null) {
return SimpleAdminHacks.instance();
}
else {
return this.plugin;
}
}
/**
* @return Returns whether the represented hack should be enabled after being loaded.
*/
public boolean shouldEnable() {
return this.shouldEnable;
}
/**
* Allows configs to prevent / re-allow the represented hack to be enabled after load.
*
* @param shouldEnable Whether to load the represented hack after being loaded.
*/
protected void setShouldEnable(final boolean shouldEnable) {
this.shouldEnable = shouldEnable;
}
/**
* @deprecated Use {@link SimpleHack#isEnabled()} instead.
*/
@Deprecated
public boolean isEnabled() {
return enabled;
}
/**
* @deprecated Use {@link HackManager#enableHack(SimpleHack)} or
* {@link HackManager#disableHack(SimpleHack)} instead.
*/
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
public boolean has(final String key) {
if (Strings.isNullOrEmpty(key)) {
return false;
}
return this.data.containsKey(key);
}
/**
* Wrapper for base config's get, for use with the hacks command
*/
public Object get(final String key) {
if (Strings.isNullOrEmpty(key)) {
return null;
}
return this.data.get(key);
}
/**
* Wrapper for base config's set, for use with the hacks command
*/
public void set(final String key, final Object value) {
if (Strings.isNullOrEmpty(key)) {
return;
}
this.data.put(key, value);
}
}
@@ -0,0 +1,38 @@
package com.programmerdan.minecraft.simpleadminhacks.framework.autoload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AutoLoad {
//Thanks Angelia
/**
* The identifier used for this option when reading it from the hacks config yaml section. If
* this is not set, the variable name will be used
*
* @return Identifier used for this option or an empty string if the variable
* name should be used
*/
String id() default "";
/**
* Is the parameter required? If set to true, an exception will be thrown if no
* default is specified and the option is not specified in the config
*
* @return Whether this option is required
*/
boolean isRequired() default true;
/**
* The type of parser used to process the data retrieved from the config.
*
* @return Returns what processor to use for retrieved config data.
*/
DataParser processor() default DataParser.DEFAULT;
}
@@ -0,0 +1,45 @@
package com.programmerdan.minecraft.simpleadminhacks.framework.autoload;
import org.bukkit.entity.EntityType;
import org.bukkit.potion.PotionEffectType;
import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils;
public enum DataParser {
DEFAULT,
MATERIAL {
@Override
public Object parse(final Object value) {
return MaterialUtils.getMaterial(stringify(value));
}
},
POTION_EFFECT_TYPE {
@Override
public Object parse(final Object value) {
return PotionEffectType.getByName(stringify(value));
}
},
ENTITY_TYPE {
@Override
public Object parse(final Object value) {
try {
return EntityType.valueOf(stringify(value));
}
catch (final Exception exception) {
return null;
}
}
};
public Object parse(final Object value) {
return value;
}
private static String stringify(final Object value) {
return value == null ? "" : value.toString();
}
}
@@ -0,0 +1,43 @@
package com.programmerdan.minecraft.simpleadminhacks.framework.commands;
import co.aikar.commands.BukkitCommandCompletionContext;
import co.aikar.commands.CommandCompletions;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.Set;
import java.util.TreeSet;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
public class CommandRegistrar extends CommandManager {
public static final String ROOT_ALIAS = "hacks|hack|sah";
public static final String PERMISSION_HACKS = "simpleadmin.hacks";
private final SimpleAdminHacks plugin;
public CommandRegistrar(final SimpleAdminHacks plugin) {
super(plugin);
this.plugin = plugin;
init();
}
@Override
public void registerCommands() {
registerCommand(new HacksCommand(this.plugin));
}
@Override
public void registerCompletions(final CommandCompletions<BukkitCommandCompletionContext> completions) {
super.registerCompletions(completions);
completions.registerAsyncCompletion("hacks", (context) -> {
final Set<String> names = new TreeSet<>();
for (final SimpleHack<? extends SimpleHackConfig> hack : this.plugin.getHackManager().getHacks()) {
names.add(hack.getName());
}
return names;
});
}
}
@@ -0,0 +1,163 @@
package com.programmerdan.minecraft.simpleadminhacks.framework.commands;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Syntax;
import com.google.common.base.Strings;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig;
import java.util.*;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.CommandSender;
@CommandPermission(CommandRegistrar.PERMISSION_HACKS)
public class HacksCommand extends BaseCommand {
protected final SimpleAdminHacks plugin;
public HacksCommand(SimpleAdminHacks plugin) {
this.plugin = plugin;
}
@CommandAlias(CommandRegistrar.ROOT_ALIAS)
@Description("Views the status of a specific hack.")
public void viewHacksCommand(final CommandSender sender) {
sender.sendMessage("Hacks:");
final List<SimpleHack<? extends SimpleHackConfig>> hacks = this.plugin.getHackManager().getHacks();
if (hacks.isEmpty()) {
sender.sendMessage(" No hacks registered.");
}
final Map<String, SimpleHack<? extends SimpleHackConfig>> names = new TreeMap<>();
for (SimpleHack<? extends SimpleHackConfig> hack : hacks) {
names.put(hack.getName(), hack);
}
for (final SimpleHack<? extends SimpleHackConfig> hack : names.values()) {
sender.sendMessage(" • " + ChatColor.YELLOW + hack.getName() + ": " + ChatColor.AQUA +
(hack.isEnabled() ? "enabled" : "disabled"));
}
}
@CommandAlias(CommandRegistrar.ROOT_ALIAS)
@CommandCompletion("@hacks")
@Syntax("<hack>")
@Description("Views the status of a specific hack.")
public void getStatusCommand(final CommandSender sender, @Single final String hack) {
final SimpleHack<? extends SimpleHackConfig> target = this.plugin.getHackManager().getHack(hack);
if (target == null) {
sender.sendMessage(ChatColor.RED + "That hack could not be found.");
return;
}
sender.sendMessage(ChatColor.WHITE + target.getName() + ":");
sender.sendMessage(" " + target.status());
}
@CommandAlias(CommandRegistrar.ROOT_ALIAS)
@CommandCompletion("@hacks enable|disable")
@Syntax("<hack> <enable/disable>")
@Description("Enables or disables a specific hack.")
public void setStateCommand(final CommandSender sender, String hack, @Single final String action) {
final SimpleHack<? extends SimpleHackConfig> target = this.plugin.getHackManager().getHack(hack);
if (target == null) {
sender.sendMessage(ChatColor.RED + "That hack could not be found.");
return;
}
hack = target.getClass().getSimpleName();
switch (action.toLowerCase()) {
case "enable":
case "enabled":
case "start":
case "yes":
case "y": {
if (target.isEnabled()) {
sender.sendMessage(ChatColor.GOLD + hack + " is already enabled.");
return;
}
target.enable();
if (!target.isEnabled()) {
sender.sendMessage(ChatColor.RED + hack + " could not be enabled!");
return;
}
sender.sendMessage(ChatColor.GREEN + hack + " is now enabled.");
return;
}
case "disable":
case "disabled":
case "end":
case "exit":
case "quit":
case "kill":
case "no":
case "n": {
if (!target.isEnabled()) {
sender.sendMessage(ChatColor.GOLD + hack + " is not enabled.");
return;
}
target.disable();
if (target.isEnabled()) {
sender.sendMessage(ChatColor.GOLD + hack + " could not be disabled!");
return;
}
sender.sendMessage(ChatColor.GREEN + hack + " has been disabled.");
return;
}
default: {
throw new InvalidCommandArgument("Please specify whether to enable to disable the hack.");
}
}
}
@CommandAlias(CommandRegistrar.ROOT_ALIAS)
@CommandCompletion("@hacks get|set @nothing")
@Syntax("<hack> <get/set> <key> [value]")
@Description("Gets or sets a specific value for a particular hack.")
public void getSetValueCommand(final CommandSender sender, final String hack, final String action,
final String key, @Optional final String value) {
if (Strings.isNullOrEmpty(key)) {
throw new InvalidCommandArgument("You need to pass a key.");
}
final SimpleHack<? extends SimpleHackConfig> target = this.plugin.getHackManager().getHack(hack);
if (target == null) {
sender.sendMessage(ChatColor.RED + "That hack could not be found.");
return;
}
final SimpleHackConfig config = target.config();
switch (action.toLowerCase()) {
case "get": {
if (!config.has(key)) {
sender.sendMessage(target.getName() + ": " + ChatColor.YELLOW + "No value for \"" + key +
"\" has been set.");
return;
}
sender.sendMessage(target.getName() + ": " +
ChatColor.YELLOW + "\"" + key + "\" == \"" + config.get(key) + "\"");
return;
}
case "set": {
try {
config.set(key, value);
sender.sendMessage(target.getName() + ": " + ChatColor.YELLOW + "\"" + key + "\" = \"" +
value + "\"");
return;
}
catch (Exception exception) {
sender.sendMessage(target.getName() + ": " + ChatColor.RED + "Failed to set \"" + key +
"\" to \"" + value + "\"");
return;
}
}
default: {
throw new InvalidCommandArgument("Please specify whether to get to set the hack value.");
}
}
}
}
@@ -0,0 +1,16 @@
package com.programmerdan.minecraft.simpleadminhacks.framework.exceptions;
public class InvalidConfigException extends RuntimeException {
private static final long serialVersionUID = 7614429135140646756L;
public InvalidConfigException(final String message) {
super(message);
}
public InvalidConfigException(final String message, final Throwable source) {
super(message, source);
}
}
@@ -0,0 +1,24 @@
package com.programmerdan.minecraft.simpleadminhacks.framework.utilities;
import org.bukkit.Location;
import org.bukkit.World;
import vg.civcraft.mc.civmodcore.world.WorldUtils;
public final class BetterToString {
public static String location(final Location location) {
if (location == null) {
return "<null>";
}
final World world = WorldUtils.getLocationWorld(location);
return (world == null ? "<null world>" : world.getName())
+ ":"
+ "x=" + location.getX()
+ "y=" + location.getY()
+ "z=" + location.getZ()
+ ";"
+ "pitch=" + location.getPitch()
+ "yaw=" + location.getYaw();
}
}
@@ -1,4 +1,4 @@
package com.programmerdan.minecraft.simpleadminhacks;
package com.programmerdan.minecraft.simpleadminhacks.framework.utilities;
/**
* Various Broadcast targets
@@ -9,5 +9,5 @@ public enum BroadcastLevel {
OP,
PERM,
CONSOLE,
ALL;
ALL
}
@@ -0,0 +1,31 @@
package com.programmerdan.minecraft.simpleadminhacks.framework.utilities;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketAdapter;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.List;
public final class PacketManager {
private final ProtocolManager manager;
private final List<PacketAdapter> adapters;
public PacketManager() {
this.manager = ProtocolLibrary.getProtocolManager();
this.adapters = new ArrayList<>();
}
public void addAdapter(final PacketAdapter adapter) {
Preconditions.checkNotNull(adapter, "Adapter cannot be null!");
this.manager.addPacketListener(adapter);
this.adapters.add(adapter);
}
public void removeAllAdapters() {
this.adapters.forEach(this.manager::removePacketListener);
this.adapters.clear();
}
}
@@ -0,0 +1,96 @@
package com.programmerdan.minecraft.simpleadminhacks.framework.utilities;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import java.util.LinkedList;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
public class TeleportUtil {
private TeleportUtil() {
//static only, no constructor
}
public static boolean checkForTeleportSpace(Location loc) {
final Block block = loc.getBlock();
final Material mat = block.getType();
if (mat.isSolid()) {
return false;
}
final Block above = block.getRelative(BlockFace.UP);
if (above.getType().isSolid()) {
return false;
}
return true;
}
public static boolean tryToTeleportVertically(Player player, Location location, String reason) {
Location loc = location.clone();
loc.setX(Math.floor(loc.getX()) + 0.500000D);
loc.setY(Math.floor(loc.getY()) + 0.02D);
loc.setZ(Math.floor(loc.getZ()) + 0.500000D);
final Location baseLoc = loc.clone();
final World world = baseLoc.getWorld();
// Check if teleportation here is viable
boolean performTeleport = checkForTeleportSpace(loc);
if (!performTeleport) {
loc.setY(loc.getY() + 1.000000D);
performTeleport = checkForTeleportSpace(loc);
}
if (performTeleport) {
player.setVelocity(new Vector());
player.teleport(loc);
SimpleAdminHacks.instance().log(String.format(
"Player '%s' %s: Teleported to %s",
player.getName(), reason, loc.toString()));
return true;
}
loc = baseLoc.clone();
// Create a sliding window of block types and track how many of those
// are solid. Keep fetching the block below the current block to move down.
int air_count = 0;
LinkedList<Material> air_window = new LinkedList<>();
loc.setY((float)world.getMaxHeight() - 2);
Block block = world.getBlockAt(loc);
for (int i = 0; i < 4; ++i) {
Material block_mat = block.getType();
if (!block_mat.isSolid()) {
++air_count;
}
air_window.addLast(block_mat);
block = block.getRelative(BlockFace.DOWN);
}
// Now that the window is prepared, scan down the Y-axis.
while (block.getY() >= block.getWorld().getMinHeight() + 1) {
Material blockMat = block.getType();
if (blockMat.isSolid()) {
if (air_count == 4) {
player.setVelocity(new Vector());
loc = block.getLocation();
loc.setX(Math.floor(loc.getX()) + 0.500000D);
loc.setY(loc.getY() + 1.02D);
loc.setZ(Math.floor(loc.getZ()) + 0.500000D);
player.teleport(loc);
SimpleAdminHacks.instance().log(String.format(
"Player '%s' %s: Teleported to %s",
player.getName(), reason, loc.toString()));
return true;
}
} else {
++air_count;
}
air_window.addLast(blockMat);
if (!air_window.removeFirst().isSolid()) {
--air_count;
}
block = block.getRelative(BlockFace.DOWN);
}
return false;
}
}
@@ -1,10 +1,12 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.BadBoyWatchConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
@@ -16,10 +18,6 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.configs.BadBoyWatchConfig;
/**
* Watches for bad boys by tracking block breaks.
* FIRST HIT will also catch breaks that block protection plugins cancel, which if you skip
@@ -35,11 +33,11 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
*/
private Map<UUID, BadBoyRecord> boys = null;
private Map<UUID, BadBoyRecord> lowBoys = null;
public BadBoyWatch(SimpleAdminHacks plugin, BadBoyWatchConfig config) {
super(plugin, config);
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled=true)
public void breakListenLow(BlockBreakEvent bbe) {
if (!config.isEnabled()) return;
@@ -48,7 +46,7 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
Block block = bbe.getBlock();
BlockState bs = block.getState();
Material material = bs.getType();
lowTrackAndReport(player.getUniqueId(), block.getLocation(), material);
} catch (Exception e) {
// insane catchall
@@ -64,23 +62,23 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
Block block = bbe.getBlock();
BlockState bs = block.getState();
Material material = bs.getType();
trackAndReport(player.getUniqueId(), block.getLocation(), material);
} catch (Exception e) {
// insane catchall
plugin().log(Level.WARNING, "Failed to track a break for badboy", e);
}
}
private void trackAndReport(UUID uuid, Location location, Material material) {
if (uuid == null || location == null || material == null) return;
BadBoyRecord record = boys.get(uuid);
if (record == null) {
record = new BadBoyRecord(config);
boys.put(uuid, record);
}
String report = record.registerBreak(location, material);
if (report != null) {
plugin().log(uuid.toString() + "} " + report);
@@ -89,13 +87,13 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
private void lowTrackAndReport(UUID uuid, Location location, Material material) {
if (uuid == null || location == null || material == null) return;
BadBoyRecord record = lowBoys.get(uuid);
if (record == null) {
record = new BadBoyRecord(config);
lowBoys.put(uuid, record);
}
String report = record.registerBreak(location, material);
if (report != null) {
plugin().log(uuid.toString() + "} FIRST HITS " + report);
@@ -112,8 +110,8 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
@Override
public void dataBootstrap() {
boys = new ConcurrentHashMap<UUID, BadBoyRecord>();
lowBoys = new ConcurrentHashMap<UUID, BadBoyRecord>();
boys = new ConcurrentHashMap<>();
lowBoys = new ConcurrentHashMap<>();
}
@Override
@@ -136,7 +134,7 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
return "Bad Boy Listening disabled.";
}
StringBuffer status = new StringBuffer("Listening for bad boy breaks");
status.append("\n Currently watching ").append(boys.size()).append(" players.");
status.append("\n Currently watching LOW ").append(lowBoys.size()).append(" players.");
return status.toString();
@@ -148,20 +146,20 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
@Override
public void registerCommands() {}
private static class BadBoyRecord {
private int nextBreak;
private int breakCount;
private BadBoyBlock[] breaks;
private BadBoyWatchConfig config;
public BadBoyRecord(BadBoyWatchConfig config) {
this.config = config;
this.nextBreak = 0;
this.breakCount = 0;
this.breaks = new BadBoyBlock[config.getTrackingDepth() + 1];
}
/**
* Returns a string if this is a watched break that passes all checks.
* Handles abiding the config too.
@@ -170,10 +168,10 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
*/
public synchronized String registerBreak(Location location, Material material) {
breaks[nextBreak] = new BadBoyBlock(location, material);
nextBreak = (nextBreak + 1) % breaks.length;
breakCount ++;
if (config.getWatchedMaterials().contains(material)) {
String breaks = null;
if (breakCount >= config.getMinDepthToMatch()) {
@@ -187,7 +185,7 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
}
return null;
}
/**
* Clears everything tracked and resets break pointer.
*/
@@ -197,7 +195,7 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
}
nextBreak = 0;
}
/**
* Just outputs what we know right now.
* @return a String showing locations and block materials
@@ -205,30 +203,30 @@ public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listen
private String showBreaks() {
StringBuilder sb = new StringBuilder("BadBoyBreak: ");
int max = breaks.length;
for (int i = (nextBreak - 1 + max) % max; i != nextBreak; i = (i - 1 + max) % max) {
BadBoyBlock bbb = breaks[i];
if (bbb == null) break;
sb.append('[').append(bbb.toString()).append(']');
}
return sb.toString();
}
}
private static class BadBoyBlock {
public final Location location;
public final Material material;
public BadBoyBlock(Location location, Material material) {
this.location = location;
this.material = material;
}
@Override
public String toString() {
StringBuilder se = new StringBuilder();
se.append(location.getWorld().getName());
se.append('(').append(location.getBlockX()).append(',')
.append(location.getBlockY()).append(',')
@@ -0,0 +1,112 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.BetterRailsConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import org.bukkit.HeightMap;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Minecart;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.vehicle.VehicleEnterEvent;
import org.bukkit.event.vehicle.VehicleExitEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;
public final class BetterRails extends SimpleHack<BetterRailsConfig> implements Listener {
// A minecart goes at 8m/s but its internal speed is 0.4, this adjusts for that
private static final double METRES_PER_SECOND_TO_SPEED = 0.05;
private static final double VANILLA_SPEED = 0.4;
public BetterRails(SimpleAdminHacks plugin, final BetterRailsConfig config) {
super(plugin, config);
}
public static BetterRailsConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new BetterRailsConfig(plugin, config);
}
@Override
public void onEnable() {
plugin().registerListener(this);
}
@Override
public void onDisable() {
HandlerList.unregisterAll(this);
}
@EventHandler
public void on(VehicleMoveEvent event) {
if (!(event.getVehicle() instanceof Minecart minecart)) {
return;
}
Location to = event.getTo();
Location from = event.getFrom();
if (to.getBlockX() == from.getBlockX() && to.getBlockY() == from.getBlockY() && to.getBlockZ() == from.getBlockZ()) {
return;
}
for (Entity entity : minecart.getPassengers()) {
if (entity instanceof Player) {
adjustSpeed(minecart);
return;
}
}
}
@EventHandler
public void on(VehicleEnterEvent event) {
if (!(event.getVehicle() instanceof Minecart minecart)) {
return;
}
if (event.getEntered() instanceof Player) {
adjustSpeed(minecart);
}
}
@EventHandler
public void on(VehicleExitEvent event) {
if (!(event.getVehicle() instanceof Minecart minecart)) {
return;
}
// Empty minecarts should return to their vanilla speed
minecart.setMaxSpeed(VANILLA_SPEED);
}
private void adjustSpeed(Minecart minecart) {
Material belowRail = minecart.getLocation().subtract(0, 1, 0).getBlock().getType();
Material belowRail2 = minecart.getLocation().subtract(0, 2, 0).getBlock().getType();
double speedMetresPerSecond = maxOrGet(config.getMaxSpeedMetresPerSecond(belowRail), config.getMaxSpeedMetresPerSecond(belowRail2), config.getBaseSpeed());
if (minecart.getLocation().getBlockY() == minecart.getWorld().getHighestBlockYAt(minecart.getLocation(), HeightMap.WORLD_SURFACE)) {
speedMetresPerSecond += maxOrGet(config.getSkySpeedMetresPerSecond(belowRail), config.getSkySpeedMetresPerSecond(belowRail2), config.getSkySpeed());
}
minecart.setMaxSpeed(speedMetresPerSecond * METRES_PER_SECOND_TO_SPEED);
}
private double maxOrGet(Double left, Double right, double defaultAmount) {
if (left != null && right != null) {
return Math.max(left, right);
} else if (left != null) {
return left;
} else if (right != null) {
return right;
} else {
return defaultAmount;
}
}
}
@@ -1,21 +1,18 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.CTAnnounceConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.BroadcastLevel;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minelink.ctplus.event.PlayerCombatTagEvent;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.ChatColor;
import net.minelink.ctplus.event.PlayerCombatTagEvent;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.BroadcastLevel;
import com.programmerdan.minecraft.simpleadminhacks.configs.CTAnnounceConfig;
/**
* Ties into CombatTagPlus, listens for {@link PlayerCombatTagEvent}
@@ -23,11 +20,11 @@ import com.programmerdan.minecraft.simpleadminhacks.configs.CTAnnounceConfig;
* @author ProgrammerDan
*/
public class CTAnnounce extends SimpleHack<CTAnnounceConfig> implements Listener{
public static final String NAME = "CombatTagAnnounce";
private Map<UUID, Long> lastCTAnnounce;
public CTAnnounce(SimpleAdminHacks plugin, CTAnnounceConfig config) {
super(plugin, config);
@@ -36,18 +33,18 @@ public class CTAnnounce extends SimpleHack<CTAnnounceConfig> implements Listener
config.setEnabled(false);
}
}
/**
* Capture player combattag and broadcast it
*
* @param event
*/
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled = true)
public void CTEvent(PlayerCombatTagEvent event) {
public void ctEvent(PlayerCombatTagEvent event) {
if (!config.isEnabled()) return; // ignore if off
if (event.getVictim() == null || event.getAttacker() == null) return; // ignore non-pvp and admin-pvp
plugin().debug(" Victim: {0} Attacker: {1}", event.getVictim().getName(), event.getAttacker().getName());
// Throttle broadcast frequency
Long lastTag = lastCTAnnounce.get(event.getVictim().getUniqueId());
Long now = System.currentTimeMillis();
@@ -56,7 +53,7 @@ public class CTAnnounce extends SimpleHack<CTAnnounceConfig> implements Listener
// Prepare message
String cleanMessage = cleanMessage(event);
// Overlap is possible. Some people might get double-notified
for (BroadcastLevel level : config.getBroadcast()) {
plugin().debug(" Broadcast to {0}", level);
@@ -76,7 +73,7 @@ public class CTAnnounce extends SimpleHack<CTAnnounceConfig> implements Listener
}
}
}
private String cleanMessage(PlayerCombatTagEvent event) {
return ChatColor.translateAlternateColorCodes('&',
config.getBroadcastMessage()
@@ -99,7 +96,7 @@ public class CTAnnounce extends SimpleHack<CTAnnounceConfig> implements Listener
@Override
public void dataBootstrap() {
lastCTAnnounce = new ConcurrentHashMap<UUID, Long>();
lastCTAnnounce = new ConcurrentHashMap<>();
}
@Override
@@ -124,7 +121,7 @@ public class CTAnnounce extends SimpleHack<CTAnnounceConfig> implements Listener
return "CombatTagPlus.PlayerCombatTagEvent monitoring not active";
}
}
public static CTAnnounceConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new CTAnnounceConfig(plugin, config);
}
@@ -0,0 +1,63 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.DisableAIConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import net.minecraft.nbt.CompoundTag;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.v1_18_R2.persistence.CraftPersistentDataContainer;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
public final class DisableAI extends SimpleHack<DisableAIConfig> implements Listener {
private static final String HAS_BEEN_DISABLED_KEY = "SAH_AI_DISABLED";
public DisableAI(final SimpleAdminHacks plugin, final DisableAIConfig config) {
super(plugin, config);
}
public static DisableAIConfig generate(final SimpleAdminHacks plugin, final ConfigurationSection config) {
return new DisableAIConfig(plugin, config);
}
@Override
public void onEnable() {
plugin().registerListener(this);
}
@Override
public void onDisable() {
HandlerList.unregisterAll(this);
config().reset();
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void disableModAI(final EntityAddToWorldEvent event) {
final var entity = event.getEntity();
if (!(entity instanceof LivingEntity)) {
return;
}
final var livingEntity = (LivingEntity) entity;
final var nbt = new CompoundTag(((CraftPersistentDataContainer) entity.getPersistentDataContainer()).getRaw()){};
if (!config().isLimitingEntityAI(entity.getType(), entity.getEntitySpawnReason())) {
// If the entity was disabled before, re-enable it
if (nbt.contains(HAS_BEEN_DISABLED_KEY)) {
livingEntity.setAI(nbt.getBoolean(HAS_BEEN_DISABLED_KEY));
nbt.remove(HAS_BEEN_DISABLED_KEY);
}
return;
}
// If the entity has already been disabled, leave alone
if (nbt.contains(HAS_BEEN_DISABLED_KEY)) {
return;
}
nbt.putBoolean(HAS_BEEN_DISABLED_KEY, livingEntity.hasAI());
livingEntity.setAI(false);
}
}
@@ -0,0 +1,164 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.destroystokyo.paper.event.player.PlayerElytraBoostEvent;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.ElytraFeaturesConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import java.util.Comparator;
import javax.annotation.Nonnull;
import net.minelink.ctplus.CombatTagPlus;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityToggleGlideEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
public final class ElytraFeatures extends SimpleHack<ElytraFeaturesConfig> implements Listener {
public ElytraFeatures(@Nonnull final SimpleAdminHacks plugin,
@Nonnull final ElytraFeaturesConfig config) {
super(plugin, config);
}
public static ElytraFeaturesConfig generate(@Nonnull final SimpleAdminHacks plugin,
@Nonnull final ConfigurationSection config) {
return new ElytraFeaturesConfig(plugin, config);
}
@Override
public void onEnable() {
plugin().registerListener(this);
}
@Override
public void onDisable() {
HandlerList.unregisterAll(this);
}
private boolean INTERNAL_hasBypassPermission(final Entity entity) {
return entity instanceof Player && entity.hasPermission("simpleadmin.elytrabypass");
}
private boolean INTERNAL_isInCombat(final Entity entity) {
if (!(entity instanceof Player)) {
return false;
}
final PluginManager pluginManager = Bukkit.getPluginManager();
final Plugin foundPlugin = pluginManager.getPlugin("CombatTagPlus");
if (pluginManager.isPluginEnabled(foundPlugin)) {
final CombatTagPlus combatTagPlugin = (CombatTagPlus) foundPlugin;
return combatTagPlugin.getTagManager().isTagged(entity.getUniqueId());
}
return false;
}
// ------------------------------------------------------------
// Elytra flight
// ------------------------------------------------------------
@EventHandler(ignoreCancelled = true)
public void regulateElytraFlight(@Nonnull final EntityToggleGlideEvent event) {
boolean disableFlight = false;
if (config().isFlightDisabled()) {
disableFlight = true;
}
else {
if (config().isFlightDisabledInCombat()) {
disableFlight = INTERNAL_isInCombat(event.getEntity());
}
}
if (disableFlight) {
if (!INTERNAL_hasBypassPermission(event.getEntity())) {
event.setCancelled(true);
}
}
}
// ------------------------------------------------------------
// Elytra boosting
// ------------------------------------------------------------
@EventHandler(ignoreCancelled = true)
public void regulateElytraBoosting(@Nonnull final PlayerElytraBoostEvent event) {
boolean disableBoosting = false;
if (config().isBoostingDisabled()) {
disableBoosting = true;
}
else {
if (config().isBoostingDisabledInCombat()) {
if (INTERNAL_isInCombat(event.getPlayer())) {
disableBoosting = true;
}
}
if (config().isSafeBoostingDisabled()) {
if (!event.getFirework().getFireworkMeta().hasEffects()) {
disableBoosting = true;
}
}
}
if (disableBoosting) {
if (!INTERNAL_hasBypassPermission(event.getPlayer())) {
event.setCancelled(true);
}
}
}
// ------------------------------------------------------------
// Elytra damage
// ------------------------------------------------------------
private static final class ElytraDamage {
private long lastDamageTime;
private double damageToDeal;
}
private final Object2ObjectMap<Player, ElytraDamage> damageElytraFlightTracker =
new Object2ObjectAVLTreeMap<>(Comparator.comparing(Player::getUniqueId));
@EventHandler(ignoreCancelled = true)
public void damageFliersAboveWorldHeight(final PlayerMoveEvent event) {
if (config().getHeightDamage() <= 0) {
return;
}
final Player player = event.getPlayer();
if (!player.isGliding() || INTERNAL_hasBypassPermission(player)) {
return;
}
final Location fromLocation = event.getFrom();
final int allowedHeight = fromLocation.getWorld().getMaxHeight() + config().getHeightBuffer();
final int blocksAboveAllowedHeight = fromLocation.getBlockY() - allowedHeight;
// If the player is within the allowed height, do nothing
if (blocksAboveAllowedHeight <= 0) {
return;
}
final ElytraDamage details = this.damageElytraFlightTracker.computeIfAbsent(
player, (_key) -> new ElytraDamage());
details.damageToDeal = Math.max(details.damageToDeal,
config().getHeightDamage() * blocksAboveAllowedHeight * config().isHeightDamageScaling());
final long now = System.currentTimeMillis();
final long deltaInterval = now - details.lastDamageTime;
// If it's not the right time to damage the player, do nothing
if (deltaInterval < config().getHeightDamageInterval()) {
return;
}
player.damage(details.damageToDeal);
details.damageToDeal = 0;
details.lastDamageTime = now;
}
@EventHandler
public void resetDamageElytraFlightOnLogout(final PlayerQuitEvent event) {
this.damageElytraFlightTracker.remove(event.getPlayer());
}
}
@@ -1,236 +1,244 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.event.player.PlayerPortalEvent;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.entity.Player;
import org.bukkit.Location;
import org.bukkit.inventory.ItemStack;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.util.UUID;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.configs.CTAnnounceConfig;
import com.programmerdan.minecraft.simpleadminhacks.configs.ExperimentalConfig;
public class Experimental extends SimpleHack<ExperimentalConfig> implements Listener, CommandExecutor {
public static final String NAME = "Experimental";
public Experimental(SimpleAdminHacks plugin, ExperimentalConfig config) {
super(plugin, config);
}
@Override
public void registerListeners() {
if (config.isEnabled()) {
plugin().log("Registering experimental listeners");
plugin().registerListener(this);
}
}
@Override
public void registerCommands() {
if (config.isEnabled()) {
plugin().log("Registering experimental commands");
plugin().registerCommand("serialize", this);
}
}
@Override
public void dataBootstrap() {
}
@Override
public void unregisterListeners() {
}
@Override
public void unregisterCommands() {
}
@Override
public void dataCleanup() {
}
@Override
public String status() {
if (config.isEnabled()) {
StringBuilder sb = new StringBuilder("Experiments enabled:");
if (config.isCombatSpy()) {
sb.append("\n CombatSpy is on");
} else {
sb.append("\n CombatSpy is off");
}
if (config.isTeleportSpy()) {
sb.append("\n TeleportSpy is on");
} else {
sb.append("\n TeleportSpy is off");
}
return sb.toString();
} else {
return "Experiments disabled.";
}
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage("Must be a player to execute this command");
return true;
}
Player player = (Player) sender;
ItemStack item = player.getInventory().getItemInMainHand();
if (item != null) {
YamlConfiguration yml = new YamlConfiguration();
yml.set("template", item);
plugin().log(yml.saveToString());
sender.sendMessage(yml.saveToString());
}
return true;
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
private void monitorTeleportLow(PlayerTeleportEvent event) {
if (!config.isEnabled()) return;
if (!config.isTeleportSpy()) return;
StringBuffer sb = new StringBuffer("[LO] ");
logTeleport(event, sb);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = false)
private void monitorTeleportHigh(PlayerTeleportEvent event) {
if (!config.isEnabled()) return;
if (!config.isTeleportSpy()) return;
StringBuffer sb = new StringBuffer("[HI] ");
logTeleport(event, sb);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void monitorTeleportTrack(PlayerPortalEvent event) {
if (!config.isEnabled()) return;
if (!config.isPostTeleportSpy()) return;
final Player player = event.getPlayer();
new BukkitRunnable() {
final UUID playerUUID = player.getUniqueId();
private int runCount = 0;
public void run() {
runCount++;
if (runCount > config.getPostTeleportSpyCount()) {
this.cancel();
return;
}
if (playerUUID != null) {
Player player = plugin().getServer().getPlayer(playerUUID);
if (player != null) {
StringBuffer sb = new StringBuffer("Tracking: ");
sb.append(playerUUID);
logPlayer(player, sb);
plugin().log(sb.toString());
} else {
StringBuffer sb = new StringBuffer("Lost: ");
sb.append(playerUUID);
plugin().log(sb.toString());
this.cancel();
}
} else {
this.cancel();
}
}
}.runTaskTimer(plugin(), 2l, 4l);
}
private void logTeleport(PlayerTeleportEvent event, StringBuffer sb) {
sb.append(event.isCancelled() ? "C " : "A ");
sb.append(event.getCause().name());
Player player = event.getPlayer();
Location from = event.getFrom();
Location to = event.getTo();
sb.append(String.format(" %16s", player != null ? player.getName() : "--unknown--"));
if (from != null) {
sb.append(String.format(" %s,%5.0f,%3.0f,%5.0f", from.getWorld().getName(), from.getX(), from.getY(), from.getZ()));
} else {
sb.append(" (none)");
}
sb.append(" ->");
if (to != null) {
sb.append(String.format(" %s,%5.0f,%3.0f,%5.0f", to.getWorld().getName(), to.getX(), to.getY(), to.getZ()));
} else {
sb.append(" (none)");
}
sb.append(" [").append(player.getUniqueId()).append(" ");
logPlayer(player, sb);
sb.append("]");
plugin().log(sb.toString());
}
private void logPlayer(final Player player, StringBuffer sb) {
sb.append(player.getWorld().getName());
Location feet = player.getLocation();
Location eyes = player.getEyeLocation();
if (feet != null) {
sb.append(String.format(" %s,%5.0f,%3.0f,%5.0f", feet.getWorld().getName(), feet.getX(), feet.getY(), feet.getZ()));
} else {
sb.append(" (none)");
}
if (eyes != null) {
sb.append(String.format(" %s,%5.0f,%3.0f,%5.0f", eyes.getWorld().getName(), eyes.getX(), eyes.getY(), eyes.getZ()));
} else {
sb.append(" (none)");
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
private void monitorCombatLow(EntityDamageByEntityEvent event) {
if (!config.isEnabled()) return;
if (!config.isCombatSpy()) return;
StringBuffer sb = new StringBuffer("[LO] ");
logCombat(event, sb);
}
private void logCombat(EntityDamageByEntityEvent event, StringBuffer sb) {
sb.append(event.isCancelled() ? "C " : "A ");
sb.append(event.getCause().name());
sb.append(String.format(", %5.2f->%5.2f", event.getDamage(), event.getFinalDamage()));
sb.append(String.format(", %16s v %16s",
event.getDamager() != null ? event.getDamager().getName() : "--unknown--",
event.getEntity() != null ? event.getEntity().getName() : "--unknown--" ));
for (EntityDamageEvent.DamageModifier mod : EntityDamageEvent.DamageModifier.values()) {
sb.append(", ").append(mod.name());
try {
sb.append(String.format(" %5.2f->%5.2f", event.getOriginalDamage(mod), event.getDamage(mod)));
} catch (Exception e) {
sb.append(" --e--/--e--");
}
}
plugin().log(sb.toString());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false)
private void monitorCombatHigh(EntityDamageByEntityEvent event) {
if (!config.isEnabled()) return;
if (!config.isCombatSpy()) return;
StringBuffer sb = new StringBuffer("[HI]: ");
logCombat(event, sb);
}
public static ExperimentalConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new ExperimentalConfig(plugin, config);
}
}
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.ExperimentalConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.player.PlayerPortalEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
public class Experimental extends SimpleHack<ExperimentalConfig> implements Listener, CommandExecutor {
public static final String NAME = "Experimental";
public Experimental(SimpleAdminHacks plugin, ExperimentalConfig config) {
super(plugin, config);
}
@Override
public void registerListeners() {
if (config.isEnabled()) {
plugin().log("Registering experimental listeners");
plugin().registerListener(this);
}
}
@Override
public void registerCommands() {
if (config.isEnabled()) {
plugin().log("Registering experimental commands");
plugin().registerCommand("serialize", this);
}
}
@Override
public void dataBootstrap() {
}
@Override
public void unregisterListeners() {
}
@Override
public void unregisterCommands() {
}
@Override
public void dataCleanup() {
}
@Override
public String status() {
if (config.isEnabled()) {
StringBuilder sb = new StringBuilder("Experiments enabled:");
if (config.isCombatSpy()) {
sb.append("\n CombatSpy is on");
} else {
sb.append("\n CombatSpy is off");
}
if (config.isTeleportSpy()) {
sb.append("\n TeleportSpy is on");
} else {
sb.append("\n TeleportSpy is off");
}
return sb.toString();
} else {
return "Experiments disabled.";
}
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage("Must be a player to execute this command");
return true;
}
Player player = (Player) sender;
ItemStack item = player.getInventory().getItemInMainHand();
if (item != null && item.getType().isItem()) { // spigot claims @Notnull but unclear how well enforced
YamlConfiguration yml = new YamlConfiguration();
yml.set("template", item);
plugin().log(yml.saveToString());
sender.sendMessage(yml.saveToString());
} else {
sender.sendMessage("No valid item in main hand");
}
return true;
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
private void monitorTeleportLow(PlayerTeleportEvent event) {
if (!config.isEnabled()) return;
if (!config.isTeleportSpy()) return;
StringBuilder sb = new StringBuilder("[LO] ");
logTeleport(event, sb);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = false)
private void monitorTeleportHigh(PlayerTeleportEvent event) {
if (!config.isEnabled()) return;
if (!config.isTeleportSpy()) return;
StringBuilder sb = new StringBuilder("[HI] ");
logTeleport(event, sb);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void monitorTeleportTrack(PlayerPortalEvent event) {
if (!config.isEnabled()) return;
if (!config.isPostTeleportSpy()) return;
final Player player = event.getPlayer();
new BukkitRunnable() {
final UUID playerUUID = player.getUniqueId();
private int runCount = 0;
public void run() {
runCount++;
if (runCount > config.getPostTeleportSpyCount()) {
this.cancel();
return;
}
// I recall running into situation where this UUID nulled out, don't remember how
// so, I check.
if (playerUUID != null) {
Player player = plugin().getServer().getPlayer(playerUUID);
if (player != null) {
StringBuilder sb = new StringBuilder("Tracking: ");
sb.append(playerUUID);
logPlayer(player, sb);
plugin().log(sb.toString());
} else {
StringBuilder sb = new StringBuilder("Lost: ");
sb.append(playerUUID);
plugin().log(sb.toString());
this.cancel();
}
} else {
this.cancel();
}
}
}.runTaskTimer(plugin(), 2l, 4l);
}
private void logTeleport(PlayerTeleportEvent event, StringBuilder sb) {
sb.append(event.isCancelled() ? "C " : "A ");
sb.append(event.getCause().name());
Player player = event.getPlayer();
Location from = event.getFrom();
Location to = event.getTo();
// Most of these nullchecks will gen warnings with compile time contracts.
// Experience tells me these can be violated at runtime due to spigot failures,
// esp moving between worlds with corresponding state desync. Leaving them in
// consequentially.
sb.append(String.format(" %16s", player != null ? player.getName() : "--unknown--"));
if (from != null) {
sb.append(String.format(" %s,%5.0f,%3.0f,%5.0f", from.getWorld().getName(), from.getX(), from.getY(), from.getZ()));
} else {
sb.append(" (none)");
}
sb.append(" ->");
if (to != null) {
sb.append(String.format(" %s,%5.0f,%3.0f,%5.0f", to.getWorld().getName(), to.getX(), to.getY(), to.getZ()));
} else {
sb.append(" (none)");
}
sb.append(" [").append(player.getUniqueId()).append(" ");
logPlayer(player, sb);
sb.append("]");
plugin().log(sb.toString());
}
private void logPlayer(final Player player, StringBuilder sb) {
sb.append(player.getWorld().getName());
Location feet = player.getLocation();
Location eyes = player.getEyeLocation();
// Similar comment here, most/all these nullchecks are from observed failures.
// I truly hope Spigot has eliminated all sources of these but I'm not the trusting type.
if (feet != null) {
sb.append(String.format(" %s,%5.0f,%3.0f,%5.0f", feet.getWorld().getName(), feet.getX(), feet.getY(), feet.getZ()));
} else {
sb.append(" (none)");
}
if (eyes != null) {
sb.append(String.format(" %s,%5.0f,%3.0f,%5.0f", eyes.getWorld().getName(), eyes.getX(), eyes.getY(), eyes.getZ()));
} else {
sb.append(" (none)");
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
private void monitorCombatLow(EntityDamageByEntityEvent event) {
if (!config.isEnabled()) return;
if (!config.isCombatSpy()) return;
StringBuilder sb = new StringBuilder("[LO] ");
logCombat(event, sb);
}
private void logCombat(EntityDamageByEntityEvent event, StringBuilder sb) {
sb.append(event.isCancelled() ? "C " : "A ");
sb.append(event.getCause().name());
// These null checks defend against specific kinds of malformed damage events, which can occur
// at runtime or while running with older plugins.
sb.append(String.format(", %5.2f->%5.2f", event.getDamage(), event.getFinalDamage()));
sb.append(String.format(", %16s v %16s",
event.getDamager() != null ? event.getDamager().getName() : "--unknown--",
event.getEntity() != null ? event.getEntity().getName() : "--unknown--"));
for (EntityDamageEvent.DamageModifier mod : EntityDamageEvent.DamageModifier.values()) {
sb.append(", ").append(mod.name());
try {
sb.append(String.format(" %5.2f->%5.2f", event.getOriginalDamage(mod), event.getDamage(mod)));
} catch (Exception e) {
sb.append(" --e--/--e--");
}
}
plugin().log(sb.toString());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false)
private void monitorCombatHigh(EntityDamageByEntityEvent event) {
if (!config.isEnabled()) return;
if (!config.isCombatSpy()) return;
StringBuilder sb = new StringBuilder("[HI]: ");
logCombat(event, sb);
}
public static ExperimentalConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new ExperimentalConfig(plugin, config);
}
}
@@ -0,0 +1,601 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.GameFeaturesConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.TeleportUtil;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import net.md_5.bungee.api.ChatColor;
import org.apache.commons.lang3.time.DateUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.type.Dispenser;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityResurrectEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.inventory.FurnaceExtractEvent;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.inventory.PrepareSmithingEvent;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.vehicle.VehicleDestroyEvent;
import org.bukkit.event.vehicle.VehicleExitEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.SmithingInventory;
import vg.civcraft.mc.civmodcore.inventory.InventoryUtils;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;
/**
* This is a grab-bag class to hold any _features_ related configurations that impact the
* game, server-wide. Mostly focused on turning things on or off.
*
* It's part of a series of focused hacks.
*
* {@link GameFixes} is focused on things that are broken or don't work, and attempts to fix them.
* {@link GameFeatures} focuses on enabling and disabling features, like ender chests, various potion states.
* {@link GameTuning} neither fixes nor disables, but rather adjusts and reconfigures.
*
* Currently you can control the following:
* - Disable Potato XP
*/
public class GameFeatures extends SimpleHack<GameFeaturesConfig> implements Listener {
public static final String NAME = "GameFeatures";
public GameFeatures(SimpleAdminHacks plugin, GameFeaturesConfig config) {
super(plugin, config);
}
@Override
public void registerListeners() {
if (config != null && config.isEnabled()) {
plugin().log("Registering GameFeatures listeners");
plugin().registerListener(this);
}
}
@Override
public void unregisterListeners() {
// Bukkit does this for us.
}
@Override
public void registerCommands() {
}
@Override
public void unregisterCommands() {
}
@Override
public void dataBootstrap() {
}
@Override
public void dataCleanup() {
// NO-OP
}
@Override
public String status() {
StringBuilder genStatus = new StringBuilder();
genStatus.append("GameFeatures is ");
if (config != null && config.isEnabled()) {
genStatus.append("active\n");
if (config.isPotatoXPEnabled()) {
genStatus.append(" Potato XP is enabled\n");
} else {
genStatus.append(" Potato XP is disabled\n");
}
genStatus.append(" Villager Trading is ");
if (config.isVillagerTrading()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Wither Spawning is ");
if (config.isWitherSpawning()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Patrol Spawning is ");
if (config.isPatrolSpawning()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Phantom Spawning is ");
if (config.isPhantomSpawning()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Ender Chest placement is ");
if (config.isEnderChestPlacement()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Ender Chest use is ");
if (config.isEnderChestUse()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Grindstone use is ");
if (config.isGrindstoneUse()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Shulker Box use is ");
if (config.isShulkerBoxUse()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Totem of Undying effects are ");
if (config.isTotemPowers()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Chorus Fruit teleportation is ");
if (config.isChorusFruitTeleportation()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" WeepAngel is ");
if (config.isWeepingAngel()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Block water in HELL biomes is ");
if (config.isBlockWaterInHell()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Minecart teleporter is ");
if (config.isMinecartTeleport()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Obsidian generator 1.7 is ");
if (config.isObsidianGenerators()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Personal death messages is ");
if (config.isPersonalDeathMessages()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Netherite crafting in smithing table is ");
if (!config.isDisableNetheriteCrafting()) {
genStatus.append("disabled\n");
} else {
genStatus.append("enabled\n");
}
// more?
} else {
genStatus.append("inactive");
}
return genStatus.toString();
}
public static GameFeaturesConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new GameFeaturesConfig(plugin, config);
}
/* From here on, the actual meat of the hack. Above is basically boilerplate for micro-plugins.*/
/**
* Perhaps eventually generalize?
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void potatoXP(FurnaceExtractEvent event) {
if (!config.isEnabled() || config.isPotatoXPEnabled()) return;
try {
Material mat = event.getItemType();
if (Material.BAKED_POTATO.equals(mat)) {
event.setExpToDrop(0);
}
} catch (Exception e) {
plugin().log(Level.WARNING, "Failed to stop potato XP", e);
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void disableVillagerTrading(PlayerInteractEntityEvent event) {
if (!config.isEnabled()) return;
if (!config.isVillagerTrading()) {
Entity npc = event.getRightClicked();
// Don't recall specifically but considering I had this null check
// alone... I probably had an NPE here at some point. Probably plugin
// with plugin interactions.
if (npc != null && EntityType.VILLAGER.equals(npc.getType())) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void disableWitherSpawning(CreatureSpawnEvent event) {
if (!config.isEnabled()) return;
if (!config.isWitherSpawning()) {
if (EntityType.WITHER.equals(event.getEntityType())) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void disablePatrols(CreatureSpawnEvent event) {
if (!config.isEnabled()) return;
if (!config.isPatrolSpawning()) {
if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.PATROL) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void disablePhantomSpawning(CreatureSpawnEvent event) {
if (!config.isEnabled()) return;
if (!config.isPhantomSpawning()) {
if (EntityType.PHANTOM.equals(event.getEntityType())
&& event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.NATURAL) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void disableEnderChestPlacement(BlockPlaceEvent event) {
if (!config.isEnabled()) return;
if (!config.isEnderChestPlacement()) {
if (Material.ENDER_CHEST.equals(event.getBlock().getType())) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void disableEnderChestUse(PlayerInteractEvent event) {
if (!config.isEnabled()) return;
if (!config.isEnderChestUse()) {
Action action = event.getAction();
Material material = event.getClickedBlock().getType();
boolean enderChest = action == Action.RIGHT_CLICK_BLOCK &&
Material.ENDER_CHEST.equals(material);
if (enderChest) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void disableGrindstoneUse(PlayerInteractEvent event) {
if (!config.isEnabled()) return;
if (!config.isGrindstoneUse()) {
Action action = event.getAction();
Material material = event.getClickedBlock().getType();
boolean Grindstone = action == Action.RIGHT_CLICK_BLOCK &&
Material.GRINDSTONE.equals(material);
if (Grindstone) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void disableShulkerBoxUse(InventoryOpenEvent event){
if (!config.isEnabled()) return;
if (!config.isShulkerBoxUse() && InventoryType.SHULKER_BOX.equals(event.getInventory().getType())) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void disabledShulkerBoxHoppering(InventoryMoveItemEvent event) {
if (!config.isEnabled() || config.isShulkerBoxUse()) {
return;
}
if (InventoryType.SHULKER_BOX.equals(event.getDestination().getType()) ||
InventoryType.SHULKER_BOX.equals(event.getSource().getType())) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void disableTotemPowers(EntityResurrectEvent event) {
if (!config.isEnabled() || config.isTotemPowers()) return;
if (EntityType.PLAYER.equals(event.getEntityType())) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void disableChorusFruitTeleportation(PlayerTeleportEvent event) {
if (!config.isEnabled() || config.isChorusFruitTeleportation()) return;
if (PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT.equals(event.getCause())) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGH)
public void weepingAngelListener(PlayerDeathEvent event) {
if (!config.isEnabled()) return;
if (!config.isWeepingAngel()) {
return;
}
if (event.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent) {
EntityDamageByEntityEvent evt = ((EntityDamageByEntityEvent) event.getEntity().getLastDamageCause());
LivingEntity killer = null;
if (evt.getDamager() instanceof LivingEntity) {
killer = (LivingEntity) evt.getDamager();
} else if (evt.getDamager() instanceof Projectile) {
Projectile projectile = (Projectile) evt.getDamager();
if (projectile.getShooter() instanceof LivingEntity) {
killer = (LivingEntity) projectile.getShooter();
}
}
if (killer != null) {
if (killer instanceof Player) {
banPlayer(event.getEntity().getPlayer(), config.getWeepingAngelPlayer());
} else {
banPlayer(event.getEntity().getPlayer(), config.getWeepingAngelEnv());
}
}
} else {
banPlayer(event.getEntity().getPlayer(), config.getWeepingAngelEnv());
}
}
private void banPlayer(final Player player, final int banTimeMS) {
if (!config().isWeepingAngel()) {
return;
}
final String banLengthMessage;
if (banTimeMS < 60_000) { // 1 Minute
final var seconds = TimeUnit.MILLISECONDS.toSeconds(banTimeMS);
if (seconds == 1) {
banLengthMessage = "1 second";
}
else {
banLengthMessage = seconds + " seconds";
}
}
else {
final var minutes = TimeUnit.MILLISECONDS.toMinutes(banTimeMS);
if (minutes == 1) {
banLengthMessage = "1 minute";
}
else {
banLengthMessage = minutes + " minutes";
}
}
player.banPlayer(
// Ban Message
"You've been banned for " + banLengthMessage + " due to your death.",
// Ban Expiry
DateUtils.addMilliseconds(new Date(), banTimeMS),
// Ban Source
"Death",
// Kick Player If Online
true);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerBucketEmptyEvent(PlayerBucketEmptyEvent e) {
if (config.isEnabled() && config.isBlockWaterInHell()) {
if (Biome.NETHER_WASTES.equals(e.getBlockClicked().getBiome()) && Material.WATER_BUCKET.equals(e.getBucket())) {
e.setCancelled(true);
e.getItemStack().setType(Material.BUCKET);
}
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onDispenseEvent(BlockDispenseEvent event) {
if (!config.isEnabled() || !config.isBlockWaterInHell()) {
return;
}
if (Material.DISPENSER.equals(event.getBlock().getType())) {
Dispenser disp = (Dispenser) event.getBlock().getBlockData();
Biome biome = event.getBlock().getRelative(disp.getFacing()).getBiome();
if (Biome.NETHER_WASTES.equals(biome) && Material.WATER_BUCKET.equals(event.getItem().getType())) {
event.setItem(new ItemStack(Material.BUCKET, event.getItem().getAmount()));
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerQuit(PlayerQuitEvent event) {
if (config.isEnabled() && config.isMinecartTeleport()) {
Player player = event.getPlayer();
Entity vehicle = player.getVehicle();
if (vehicle == null) {
return;
}
Location vehicleLocation = vehicle.getLocation();
player.leaveVehicle();
if (!TeleportUtil.tryToTeleportVertically(player, vehicleLocation, "logged out")) {
player.setHealth(0.000000D);
plugin().log(Level.INFO, "Player '%s' logged out in vehicle: killed", player.getName());
}
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onVehicleExit(VehicleExitEvent event) {
if (config.isEnabled() && config.isMinecartTeleport()) {
final Vehicle vehicle = event.getVehicle();
final Entity passenger = event.getExited();
if (!(passenger instanceof Player)) {
return;
}
final Player player = (Player) passenger;
final Location vehicleLocation = vehicle.getLocation();
Bukkit.getScheduler().runTaskLater(plugin(), () -> {
if (!TeleportUtil.tryToTeleportVertically(player, vehicleLocation, "exiting vehicle")) {
player.setHealth(0.000000D);
plugin().log(Level.INFO, "Player '%s' exiting vehicle: killed", player.getName());
}
}, 2L);
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onVehicleDestroy(VehicleDestroyEvent event) {
if (config.isEnabled() && config.isMinecartTeleport()) {
final Vehicle vehicle = event.getVehicle();
final List<Entity> passengers = vehicle.getPassengers();
if (passengers.isEmpty()) {
return;
}
final Location vehicleLocation = vehicle.getLocation();
passengers.removeIf((passenger -> !(passenger instanceof Player)));
passengers.forEach((passenger) -> {
final Player player = (Player) passenger;
Bukkit.getScheduler().runTaskLater(plugin(), () -> {
if (!TeleportUtil.tryToTeleportVertically(player, vehicleLocation, "in destroyed vehicle")) {
player.setHealth(0.000000D);
plugin().log(Level.INFO, String.format("Player '%s' exiting vehicle: killed", player.getName()));
}
}, 2L);
});
}
}
@EventHandler
public void onBlockFromTo(BlockFromToEvent event) {
if (config.isEnabled() && config.isObsidianGenerators()) {
if (Material.LAVA.equals(event.getBlock().getType())) {
Block to = event.getToBlock();
if (Material.REDSTONE.equals(to.getType()) || Material.TRIPWIRE.equals(to.getType())) {
if (Material.WATER.equals(to.getRelative(BlockFace.NORTH).getType())
|| Material.WATER.equals(to.getRelative(BlockFace.SOUTH).getType())
|| Material.WATER.equals(to.getRelative(BlockFace.WEST).getType())
|| Material.WATER.equals(to.getRelative(BlockFace.EAST).getType())) {
event.setCancelled(true);
to.setType(Material.OBSIDIAN);
}
}
}
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerDeath(PlayerDeathEvent event) {
if (!config.isEnabled() || !config.isPersonalDeathMessages()) {
return;
}
event.setDeathMessage(null);
Player dead = event.getEntity();
String killer = "";
Location loc = dead.getLocation();
if (dead.getKiller() != null) {
killer = dead.getKiller().getDisplayName();
dead.sendMessage(ChatColor.RED + String.format("You were slain by player %s at [%s %d, %d, %d]",
killer, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
return;
}
try {
killer = dead.getLastDamageCause().getCause().toString();
} catch (NullPointerException e) {
return;
}
dead.sendMessage(ChatColor.RED + String.format("You were slain by %s at [%s %d, %d, %d]",
killer, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
}
@EventHandler(priority = EventPriority.NORMAL)
public void netheriteCrafting(PrepareSmithingEvent event) {
if (!config.isEnabled() || !config.isDisableNetheriteCrafting()) {
return;
}
//This purely disables vanilla netherite crafting in smithing tables ONLY
SmithingInventory smithingTable = event.getInventory();
ItemStack result = smithingTable.getResult();
if (!ItemUtils.isValidItem(result)) {
return;
}
switch (result.getType()) {
case NETHERITE_HELMET:
case NETHERITE_CHESTPLATE:
case NETHERITE_LEGGINGS:
case NETHERITE_BOOTS:
case NETHERITE_PICKAXE:
case NETHERITE_SHOVEL:
case NETHERITE_AXE:
case NETHERITE_HOE:
case NETHERITE_SWORD:
event.setResult(new ItemStack(Material.AIR));
Bukkit.getScheduler().runTask(this.plugin, () -> {
for (Player player : InventoryUtils.getViewingPlayers(smithingTable)) {
player.updateInventory();
player.sendMessage(ChatColor.RED + "Vanilla netherite crafting is disabled!");
}
});
break;
}
}
}
@@ -0,0 +1,552 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.GameFixesConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.PacketManager;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.type.Dispenser;
import org.bukkit.block.data.type.Hopper;
import org.bukkit.block.data.type.RespawnAnchor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.entity.EntityPortalEvent;
import org.bukkit.event.entity.EntityTeleportEvent;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.event.world.StructureGrowEvent;
import org.bukkit.inventory.InventoryHolder;
public class GameFixes extends SimpleHack<GameFixesConfig> implements Listener {
public static final String NAME = "GameFixes";
private final PacketManager protocol = new PacketManager();
public GameFixes(SimpleAdminHacks plugin, GameFixesConfig config) {
super(plugin, config);
}
@Override
public void onDisable() {
this.protocol.removeAllAdapters();
HandlerList.unregisterAll(this);
super.onDisable();
}
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, plugin);
super.onEnable();
}
@Override
public String status() {
StringBuilder genStatus = new StringBuilder();
genStatus.append("GameFixes is ");
if (config != null && config.isEnabled()) {
genStatus.append(ChatColor.GREEN).append("active\n").append(ChatColor.RESET);
genStatus.append(" Block elytra break bug is ");
if (config.isBlockElytraBreakBug()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
genStatus.append(" Will deal " + config.getDamageOnElytraBreakBug() + " damage to players\n");
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
genStatus.append(" Block storage entities from teleporting to prevents exploits ");
if (!config.canStorageTeleport()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
genStatus.append(" Hopper self-feed duplication exploit fix ");
if (config.isStopHopperDupe()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
genStatus.append(" Duplications using rails exploit fix ");
if (config.isStopRailDupe()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
genStatus.append(" End Portal removal exploit fix ");
if (config.isStopEndPortalDeletion()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
genStatus.append(" Bed Bombing in Nether / Hell Biomes fix ");
if (config.stopBedBombing()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
genStatus.append(" Tree wraparound fix ");
if (config.stopTreeWraparound()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
genStatus.append(" Maintain flat bedrock ");
if (config.maintainFlatBedrock()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
genStatus.append(" Maintain flat bedrock ");
if (config.maintainFlatBedrock()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
genStatus.append(" Fix pearl glitches ");
if (config.fixPearlGlitch()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
genStatus.append(" Prevent long signs ");
if (config.isPreventLongSigns()) {
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
} else {
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
}
} else {
genStatus.append(ChatColor.RED).append("inactive").append(ChatColor.RESET);
}
return genStatus.toString();
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
if (!config.isEnabled() || !config.isBlockElytraBreakBug()) return;
Block block = event.getBlock();
Player player = event.getPlayer();
if (!player.getLocation().equals(block.getLocation())
&& player.getEyeLocation().getBlock().getType() != Material.AIR) {
event.setCancelled(true);
player.damage(config.getDamageOnElytraBreakBug());
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onEntityTeleport(EntityTeleportEvent event) {
if (!config.isEnabled() || config.canStorageTeleport()) {
return;
}
if (event.getEntity() instanceof InventoryHolder) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onEntityPortal(EntityPortalEvent event) {
if (!config.isEnabled() || config.canStorageTeleport()) {
return;
}
if (event.getEntity() instanceof InventoryHolder) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onInventoryMoveItem(InventoryMoveItemEvent event) {
if (!config.isEnabled() || !config.isStopHopperDupe()) return;
// I recollect inventory handling to be the single biggest source of NPEs, so I'll add
// back in the nullcheck, although I do hope Spigot has addressed the sources with the
// addition of their annotations.
//
if ((event.getDestination() == null) || (event.getSource() == null) ||
!(InventoryType.HOPPER.equals(event.getDestination().getType())) ||
!(InventoryType.HOPPER.equals(event.getSource().getType())) ||
!(Material.HOPPER.equals(event.getDestination().getLocation().getBlock().getType())) ||
!(Material.HOPPER.equals(event.getSource().getLocation().getBlock().getType()))) {
return;
}
Hopper source = (Hopper) event.getSource().getLocation().getBlock().getBlockData();
Hopper dest = (Hopper) event.getDestination().getLocation().getBlock().getBlockData();
if (source.getFacing().getOppositeFace() == dest.getFacing()) {
//They're pointing into each other and will eventually dupe
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPistonPushRail(BlockPistonExtendEvent event) {
if (config.isEnabled() && config.isStopRailDupe()) {
for (Block block : event.getBlocks()) {
Material type = block.getType();
if (config.getRailArray().contains(type)) {
event.setCancelled(true);
return;
}
}
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onRailPlace(BlockPlaceEvent event) {
if (config.isEnabled() && config.isStopRailDupe()) {
Block block = event.getBlock();
Material type = block.getType();
if (config.getRailArray().contains(type)) {
for (BlockFace face : config.getBfArray()) {
type = block.getRelative(face).getType();
if (config.getPistonArray().contains(type)) {
event.setCancelled(true);
return;
}
}
}
}
}
//Trying to stop players from deleting end portals
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) {
if (config.isEnabled() && config.isStopEndPortalDeletion()) {
Block block = event.getBlockClicked().getRelative(event.getBlockFace());
if (Material.END_PORTAL.equals(block.getType())) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onDispenseEvent(BlockDispenseEvent event) {
if (config.isEnabled() && config.isStopEndPortalDeletion()) {
if (Material.DISPENSER.equals(event.getBlock().getType())) {
Dispenser disp = (Dispenser) event.getBlock().getBlockData();
Material type = event.getBlock().getRelative(disp.getFacing()).getType();
if (Material.END_PORTAL.equals(type)) {
event.setCancelled(true);
}
}
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerEnterBed(BlockPlaceEvent event) {
if (!config.isEnabled() || !config.stopBedBombing()) return;
Block b = event.getBlock();
switch (b.getType()) {
case BLACK_BED:
case BLUE_BED:
case BROWN_BED:
case CYAN_BED:
case GRAY_BED:
case GREEN_BED:
case LIME_BED:
case MAGENTA_BED:
case LIGHT_GRAY_BED:
case PURPLE_BED:
case PINK_BED:
case YELLOW_BED:
case WHITE_BED:
case RED_BED:
case ORANGE_BED:
case LIGHT_BLUE_BED:
break;
default:
return;
}
Environment env = b.getLocation().getWorld().getEnvironment();
Biome biome = b.getLocation().getBlock().getBiome();
if (Environment.NETHER.equals(env) || Environment.THE_END.equals(env) || Biome.NETHER_WASTES.equals(biome)
|| Biome.END_BARRENS.equals(biome) || Biome.END_HIGHLANDS.equals(biome)
|| Biome.END_MIDLANDS.equals(biome) || Biome.SMALL_END_ISLANDS.equals(biome)
|| Biome.THE_END.equals(biome)) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.NORMAL)
public void onAnchorInteraction(PlayerInteractEvent event) {
if (!config.isEnabled() || !config.stopAnchorBombing()) {
return;
}
if (event.getClickedBlock() == null) {
return;
}
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
if (!event.getClickedBlock().getType().equals(Material.RESPAWN_ANCHOR)) {
return;
}
RespawnAnchor anchor = (RespawnAnchor) event.getClickedBlock().getBlockData();
if (!event.getMaterial().equals(Material.GLOWSTONE)) {
event.setCancelled(true);
} else if (anchor.getCharges() == anchor.getMaximumCharges()) {
event.setCancelled(true);
}
event.getPlayer().sendMessage(ChatColor.RED + "Respawn anchor bombing is disabled");
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled=true)
public void onStructureGrow(StructureGrowEvent event) {
if(config.isEnabled() && config.stopTreeWraparound()) {
int maxY = 0;
int minY = 257;
for(BlockState bs : event.getBlocks()) {
final int y = bs.getLocation().getBlockY();
maxY = Math.max(maxY, y);
minY = Math.min(minY, y);
}
if(maxY - minY > 240) {
event.setCancelled(true);
final Location loc = event.getLocation();
plugin().log(Level.INFO, String.format("Prevented structure wraparound at %s: %d, %d, %d",
loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
}
}
}
@EventHandler
public void onTouchBedrock(PlayerInteractEvent event) {
if(config.isEnabled() && config.maintainFlatBedrock() && event.getClickedBlock() != null
&& event.getClickedBlock().getType() == Material.BEDROCK && event.getClickedBlock().getY() > 0) {
Bukkit.getScheduler().runTask(plugin(), () -> {
event.getClickedBlock().setType(Material.STONE);
});
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPearlTeleport(PlayerTeleportEvent event) {
if(config.isEnabled() && config.fixPearlGlitch() && event.getCause() == TeleportCause.ENDER_PEARL) {
Location to = event.getTo();
World world = to.getWorld();
Block toBlock = to.getBlock();
Block above = toBlock.getRelative(BlockFace.UP);
Block below = toBlock.getRelative(BlockFace.DOWN);
boolean lowerBlockBypass = false;
double height = 0.0;
switch (toBlock.getType()) {
case CHEST:
case ENDER_CHEST:
height = 0.875;
break;
case ACACIA_SLAB:
case ANDESITE_SLAB:
case BIRCH_SLAB:
case BRICK_SLAB:
case COBBLESTONE_SLAB:
case CUT_RED_SANDSTONE_SLAB:
case CUT_SANDSTONE_SLAB:
case DARK_OAK_SLAB:
case DARK_PRISMARINE_SLAB:
case DIORITE_SLAB:
case END_STONE_BRICK_SLAB:
case GRANITE_SLAB:
case JUNGLE_SLAB:
case MOSSY_COBBLESTONE_SLAB:
case STONE_SLAB:
case STONE_BRICK_SLAB:
case SPRUCE_SLAB:
case SMOOTH_STONE_SLAB:
case SMOOTH_SANDSTONE_SLAB:
case SMOOTH_RED_SANDSTONE_SLAB:
case SMOOTH_QUARTZ_SLAB:
case SANDSTONE_SLAB:
case RED_SANDSTONE_SLAB:
case RED_NETHER_BRICK_SLAB:
case QUARTZ_SLAB:
case PURPUR_SLAB:
case PRISMARINE_SLAB:
case PRISMARINE_BRICK_SLAB:
case POLISHED_GRANITE_SLAB:
case POLISHED_DIORITE_SLAB:
case POLISHED_ANDESITE_SLAB:
case PETRIFIED_OAK_SLAB:
case OAK_SLAB:
case NETHER_BRICK_SLAB:
case MOSSY_STONE_BRICK_SLAB:
lowerBlockBypass = true;
height = 0.5;
break;
case LILY_PAD:
height = 0.016;
break;
case ENCHANTING_TABLE:
lowerBlockBypass = true;
height = 0.75;
break;
case BLACK_BED:
case BLUE_BED:
case BROWN_BED:
case CYAN_BED:
case GRAY_BED:
case GREEN_BED:
case LIME_BED:
case MAGENTA_BED:
case LIGHT_GRAY_BED:
case PURPLE_BED:
case PINK_BED:
case YELLOW_BED:
case WHITE_BED:
case RED_BED:
case ORANGE_BED:
case LIGHT_BLUE_BED:
break;
case FLOWER_POT:
height = 0.375;
break;
case SKELETON_SKULL:
case WITHER_SKELETON_WALL_SKULL:
case WITHER_SKELETON_SKULL:
case SKELETON_WALL_SKULL:
case PLAYER_HEAD:
case PLAYER_WALL_HEAD:
case CREEPER_HEAD:
case CREEPER_WALL_HEAD:
case DRAGON_HEAD:
case DRAGON_WALL_HEAD:
case ZOMBIE_HEAD:
case ZOMBIE_WALL_HEAD:
height = 0.5;
break;
default:
break;
}
switch (below.getType()) {
case ACACIA_FENCE:
case ACACIA_FENCE_GATE:
case BIRCH_FENCE:
case BIRCH_FENCE_GATE:
case DARK_OAK_FENCE:
case DARK_OAK_FENCE_GATE:
case JUNGLE_FENCE:
case JUNGLE_FENCE_GATE:
case SPRUCE_FENCE_GATE:
case SPRUCE_FENCE:
case OAK_FENCE_GATE:
case OAK_FENCE:
case NETHER_BRICK_FENCE:
case ANDESITE_WALL:
case BRICK_WALL:
case STONE_BRICK_WALL:
case COBBLESTONE_WALL:
case DIORITE_WALL:
case END_STONE_BRICK_WALL:
case GRANITE_WALL:
case SANDSTONE_WALL:
case RED_SANDSTONE_WALL:
case MOSSY_COBBLESTONE_WALL:
case MOSSY_STONE_BRICK_WALL:
case PRISMARINE_WALL:
case RED_NETHER_BRICK_WALL:
case NETHER_BRICK_WALL:
height = 0.5;
break;
default:
break;
}
boolean upperBlockBypass = false;
if (height >= 0.5) {
Block aboveHeadBlock = above.getRelative(BlockFace.UP);
if (!aboveHeadBlock.getType().isSolid()) {
height = 0.5;
} else {
upperBlockBypass = true;
}
}
to.setX(Math.floor(to.getX() + 0.5000));
to.setY(Math.floor(to.getY() + height));
to.setZ(Math.floor(to.getZ() + 0.5000));
if (above.getType().isSolid() || (toBlock.getType().isSolid() && !lowerBlockBypass)
|| upperBlockBypass) {
boolean bypass = false;
if (world.getEnvironment() == Environment.NETHER &&
to.getBlockY() > 124 && to.getBlockY() < 129) {
bypass = true;
}
if (!bypass) {
event.setCancelled(true);
}
}
}
}
//fixes a small side effect of the above
BlockFace[] faces = new BlockFace[] {BlockFace.NORTH,BlockFace.SOUTH,BlockFace.EAST,BlockFace.WEST};
@EventHandler
public void preventWrongIce(BlockFormEvent event) {
Block block = event.getBlock();
if(event.getNewState().getType() == Material.ICE) {
for(BlockFace face : faces) {
if(block.getRelative(face).getType().isSolid()) return;
}
event.setCancelled(true);
}
}
@EventHandler(ignoreCancelled = true)
public void onSignChange(SignChangeEvent event) {
if(config.isEnabled() && config.isPreventLongSigns()) {
String[] signdata = event.getLines();
for(int i = 0; i < signdata.length; i++) {
if(signdata[i] != null && signdata[i].length() > config.getSignLengthLimit()) {
Player player = event.getPlayer();
Location loc = event.getBlock().getLocation();
plugin().log(Level.WARNING, String.format("Player '%s' [%s] attempted to place sign at ([%s] %d, %d, %d) with line %d having length %d > %d. Preventing.",
player.getDisplayName(), player.getUniqueId(), loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(),
i, signdata[i], config.getSignLengthLimit()));
if(config.isCancelLongSignEvent()) {
event.setCancelled(true);
return;
}
if(config.isPreventLongSignsAbsolute()) {
event.setLine(i, "");
} else {
event.setLine(i, signdata[i].substring(0, config.getSignLengthLimit()));
}
}
}
}
}
public static GameFixesConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new GameFixesConfig(plugin, config);
}
}
@@ -0,0 +1,487 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.GameTuningConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.TeleportUtil;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World.Environment;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Skeleton;
import org.bukkit.entity.SkeletonHorse;
import org.bukkit.entity.minecart.HopperMinecart;
import org.bukkit.entity.minecart.StorageMinecart;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityPortalEvent;
import org.bukkit.event.entity.EntityPotionEffectEvent;
import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.event.player.PlayerBedEnterEvent;
import org.bukkit.event.player.PlayerBedEnterEvent.BedEnterResult;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerPortalEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.weather.WeatherChangeEvent;
import org.bukkit.event.world.PortalCreateEvent;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.meta.SpawnEggMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;
/**
* This is a grab-bag class to hold any _tuning_ related configurations that impact the
* game, server-wide.
*
* It's part of a series of focused hacks.
*
* {@link GameFixes} is focused on things that are broken or don't work, and attempts to fix them.
* {@link GameFeatures} focuses on enabling and disabling features, like elytra, various potion states.
* {@link GameTuning} neither fixes nor disables, but rather adjusts and reconfigures.
*
* Currently you can control the following:
* - BlockEntity limits per chunk
* - Setting bed during the day instead of just at night
*/
public class GameTuning extends SimpleHack<GameTuningConfig> implements Listener, CommandExecutor {
public static final String NAME = "GameTuning";
public GameTuning(SimpleAdminHacks plugin, GameTuningConfig config) {
super(plugin, config);
}
@Override
public void registerListeners() {
if (config != null && config.isEnabled()) {
plugin().log("Registering GameTuning listeners");
plugin().registerListener(this);
}
}
@Override
public void unregisterListeners() {
// Bukkit does this for us.
}
@Override
public void registerCommands() {
if (config.isEnabled()) {
plugin.registerCommand("chunklimits", this);
}
}
@Override
public void unregisterCommands() {
}
@Override
public void dataBootstrap() {
}
@Override
public void dataCleanup() {
// NO-OP
}
@Override
public String status() {
StringBuilder genStatus = new StringBuilder();
genStatus.append("GameTuning is ");
if (config != null && config.isEnabled()) {
genStatus.append("active\n");
if (config.areChunkLimitsEnabled()) {
genStatus.append(" Chunk Limits are enabled\n");
} else {
genStatus.append(" Chunk Limits are disabled\n");
}
if (config.areDaytimeBedsEnabled()) {
genStatus.append(" Daytime Beds are enabled\n");
} else {
genStatus.append(" Daytime Beds are disabled\n");
}
genStatus.append(" One To One Nether is ");
if (config.isOneToOneNether()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Stop trap horses is ");
if (config.stopTrapHorses()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Kill trap horses is ");
if (config.killTrapHorses()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Changing spawner type is ");
if (config.canChangeSpawnerType()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Villager trading is ");
if (config.allowVillagerTrading()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Ender grief is ");
if (config.isEnderGrief()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Wither grief is ");
if (config.isWitherGrief()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Dragon grief is ");
if (config.isDragonGrief()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Prevent falling through bedrock is ");
if (config.isPreventFallingThroughBedrock()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Bad Omen is ");
if (config.isBadOmenEnabled()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
genStatus.append(" Rain reduction is ");
if (config.isRainReductionEnabled()) {
genStatus.append("enabled\n");
} else {
genStatus.append("disabled\n");
}
} else {
genStatus.append("inactive");
}
return genStatus.toString();
}
public static GameTuningConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new GameTuningConfig(plugin, config);
}
/* From here on, the actual meat of the hack. Above is basically boilerplate for micro-plugins.*/
/**
* Many thanks to BlackXNT for his work on this event in Humbug, which I have largely copied and expanded.
*
* This tracks block placements, and if a limit is configured and the block is a TileEntity w/ state,
* will reject the placement if otherwise it would exceed limits for the Chunk.
*
* @param event the Placement event.
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void chunkLimits(BlockPlaceEvent event) {
if (!config.isEnabled() || !config.areChunkLimitsEnabled()) return;
try {
Player player = event.getPlayer();
Block block = event.getBlock();
if (!config.applyChunkLimits(player.getUniqueId())) return;
Material mat = block.getType();
Integer limit = config.getChunkLimit(mat);
if (limit == null) return;
int current = 0;
for (BlockState state : block.getChunk().getTileEntities()) {
if (state != null && mat.equals(state.getType())) {
if (++current > limit) {
event.setCancelled(true);
player.sendMessage(config.getChunkLimitsExceededMessage()
.replaceAll("%Limit%",Integer.toString(limit))
.replaceAll("%Material%", ItemUtils.getItemName(mat))
);
return;
}
}
}
} catch (Exception e) {
plugin().log(Level.WARNING, "Failed to measure chunk limit", e);
}
}
// If any limit at all, cancel the piston event.
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void chunkLimitsExploitExtend(BlockPistonExtendEvent event) {
if (!config.isEnabled() || !config.areChunkLimitsEnabled()) return;
List<Block> blocks = event.getBlocks();
if (!blocks.isEmpty()) {
for (Block b : blocks) {
if (b != null && config.getChunkLimit(b.getType()) != null) {
event.setCancelled(true);
return; // TODO send message to nearby player warning of reason for stopping.
}
}
}
}
// Yes, this is identical ...
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void chunkLimitsExploitRetract(BlockPistonRetractEvent event) {
if (!config.isEnabled() || !config.areChunkLimitsEnabled()) return;
List<Block> blocks = event.getBlocks();
if (!blocks.isEmpty()) {
for (Block b : blocks) {
if (b != null && config.getChunkLimit(b.getType()) != null) {
event.setCancelled(true);
return; // TODO send message to nearby player warning of reason for stopping.
}
}
}
}
@EventHandler
public void enterBed(PlayerBedEnterEvent e) {
if (!config.isEnabled() || !config.areDaytimeBedsEnabled()) {
return;
}
if (BedEnterResult.NOT_POSSIBLE_NOW.equals(e.getBedEnterResult()) || BedEnterResult.NOT_SAFE.equals(e.getBedEnterResult())) {
e.getPlayer().setBedSpawnLocation(e.getBed().getLocation(), false);
e.getPlayer().sendTitle("", config.getDaytimeBedSpawnSetMessage());
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPortalTravel(EntityPortalEvent event) {
if (!config.isEnabled()) {
return;
}
if (!config.allowNetherTravel() && event.getTo() != null && event.getTo().getWorld().getEnvironment() == Environment.NETHER) {
event.setCancelled(true);
return;
}
if (config.isOneToOneNether()) {
Location newLoc = event.getFrom();
newLoc.setWorld(event.getTo().getWorld());
event.setTo(newLoc);
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerPortalTravel(PlayerPortalEvent event) {
if(config.isEnabled() && PlayerTeleportEvent.TeleportCause.NETHER_PORTAL == event.getCause()) {
if(!config.allowNetherTravel()) {
event.setCancelled(true);
} else if (config.isOneToOneNether()) {
Location newLoc = event.getFrom();
newLoc.setWorld(event.getTo().getWorld());
event.setTo(newLoc);
}
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPortalCreate(PortalCreateEvent event) {
if (config.isEnabled() && !config.isReturnNetherPortal()) {
if (PortalCreateEvent.CreateReason.FIRE.equals(event.getReason()) && Environment.NETHER.equals(event.getWorld().getEnvironment())) {
event.setCancelled(true);
}
}
}
//Trying to stop dupe bugs via minecart inventories
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onEntityRightClick(PlayerInteractEntityEvent event) {
if (!config.isEnabled()) return;
if (!config.isChestedMinecartInventories() || !config.isHopperMinecartInventories()) {
Entity target = event.getRightClicked();
if (target.getType().equals(EntityType.MINECART_CHEST) && !config.isChestedMinecartInventories()) {
event.setCancelled(true);
}
if (target.getType().equals(EntityType.MINECART_HOPPER) && !config.isHopperMinecartInventories()) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onInventoryMoveItemEvent(InventoryMoveItemEvent event) {
if (!config.isEnabled()) return;
if (!config.isChestedMinecartInventories() || !config.isHopperMinecartInventories()) {
InventoryHolder holder = event.getDestination().getHolder();
if (holder instanceof StorageMinecart && !config.isChestedMinecartInventories()) {
event.setCancelled(true);
}
if (holder instanceof HopperMinecart && !config.isHopperMinecartInventories()) {
event.setCancelled(true);
}
}
}
@EventHandler
public void onEntityTarget(EntityTargetEvent event) {
if(config.isEnabled() && config.stopTrapHorses()) {
Entity entity = event.getEntity();
if(entity instanceof Skeleton && entity.isInsideVehicle() && entity.getVehicle() instanceof SkeletonHorse) {
if(config.killTrapHorses()) {
entity.getVehicle().remove();
}
entity.remove();
}
}
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if(!config.isEnabled() || !Action.RIGHT_CLICK_BLOCK.equals(event.getAction())) {
return;
}
boolean cancel = !config.canChangeSpawnerType() && Material.SPAWNER.equals(event.getClickedBlock().getType())
&& event.getItem() != null && event.getItem().getItemMeta() instanceof SpawnEggMeta;
if (cancel) {
event.setCancelled(true);
}
}
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
if(config.isEnabled() && !config.canPlace(event.getBlock().getType())) {
event.setCancelled(true);
event.getPlayer().sendMessage(ChatColor.RED + "You're not allowed to place that!");
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
if(config.isEnabled() && !config.allowVillagerTrading()) {
Entity npc = event.getRightClicked();
// consistency, preserving null check
if(npc != null && EntityType.VILLAGER.equals(npc.getType())) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onEntityChangeBlock(EntityChangeBlockEvent event) {
if(config.isEnabled() &&
(!config.isEnderGrief() && EntityType.ENDERMAN.equals(event.getEntityType())) ||
(!config.isWitherGrief() && EntityType.WITHER.equals(event.getEntityType())) ||
(!config.isDragonGrief() && EntityType.ENDER_DRAGON.equals(event.getEntityType()))) {
event.setCancelled(true);
}
}
/**
* Speculative handler for dragon fireballs and exploding wither skulls
*
* Some examples online prefer event.getEntity() instanceof DragonFireball and WitherSkull, could try that
* if this does not work
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onEntityExplodeEvent(EntityExplodeEvent event) {
if (config.isEnabled() &&
(!config.isDragonGrief() && EntityType.DRAGON_FIREBALL.equals(event.getEntityType())) ||
(!config.isWitherGrief() && EntityType.WITHER_SKULL.equals(event.getEntityType()))) {
event.setCancelled(true);
// note this might not prevent block breaks, check on that (1.14)
}
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if(config.isEnabled() && config.isPreventFallingThroughBedrock() && event.getTo().getY() < event.getTo().getWorld().getMinHeight()
&& GameMode.SURVIVAL.equals(event.getPlayer().getGameMode())) {
TeleportUtil.tryToTeleportVertically(event.getPlayer(), event.getTo(), "falling into the void");
}
}
@EventHandler
public void onBadOmenEffect(EntityPotionEffectEvent event) {
if (!config.isEnabled() || config.isBadOmenEnabled()) {
return;
}
PotionEffect effect = event.getNewEffect();
if (effect != null && effect.getType().equals(PotionEffectType.BAD_OMEN)) {
event.setCancelled(true);
}
}
@EventHandler
public void onRain(WeatherChangeEvent event) {
if (!config.isEnabled() || !config.isRainReductionEnabled()) {
return;
}
if (event.toWeatherState() && Math.random() >= config.getRainOccurrenceChance()) {
event.setCancelled(true);
plugin().getLogger().info("Rain event was cancelled");
}
}
@Override
public boolean onCommand(@Nonnull CommandSender commandSender, @Nonnull Command command, @Nonnull String s, @Nonnull String[] strings) {
Map<Material, Integer> blockLimits = config.getBlockEntityLimits();
StringBuilder stringB = new StringBuilder();
stringB.append(ChatColor.AQUA + "-- Chunk Limitations for the following blocks --\n");
stringB.append("\n");
for (Map.Entry<Material, Integer> entries: blockLimits.entrySet()) {
Material material = entries.getKey();
Integer blockLimit = entries.getValue();
stringB.append("" + ChatColor.WHITE + material + " : " + blockLimit+ "\n");
}
commandSender.sendMessage(stringB.toString());
return true;
}
}
@@ -0,0 +1,106 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.HorseStatsConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import org.bukkit.ChatColor;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Strider;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.inventory.ItemStack;
public class HorseStats extends SimpleHack<HorseStatsConfig> implements Listener {
private static final double INTERNAL_TO_METRES_PER_SECOND = 42.15778758471;
public HorseStats(SimpleAdminHacks plugin, HorseStatsConfig config) {
super(plugin, config);
}
@EventHandler(priority = EventPriority.NORMAL)
public void onHorseStatCheck(PlayerInteractEntityEvent event) {
if (!config.isEnabled()) {
return;
}
ItemStack item = event.getPlayer().getInventory().getItemInMainHand();
if (!item.getType().equals(config.getHorseCheckerItem())) {
return;
}
Entity entity = event.getRightClicked();
if (entity instanceof AbstractHorse) {
AbstractHorse horse = (AbstractHorse)entity;
AttributeInstance attrHealth = horse.getAttribute(Attribute.GENERIC_MAX_HEALTH);
AttributeInstance attrSpeed = horse.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED);
event.getPlayer().sendMessage(String.format("%sHealth = %f, Speed = %fm/s, Jump height = %f blocks",
ChatColor.YELLOW,
attrHealth.getBaseValue(),
attrSpeed.getBaseValue() * INTERNAL_TO_METRES_PER_SECOND,
jumpHeightInBlocks(horse.getJumpStrength())));
event.setCancelled(true);
} else if (entity instanceof Strider) {
Strider strider = (Strider) entity;
AttributeInstance attrHealth = strider.getAttribute(Attribute.GENERIC_MAX_HEALTH);
AttributeInstance attrSpeed = strider.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED);
event.getPlayer().sendMessage(String.format("%sHealth = %f, Speed = %fm/s",
ChatColor.YELLOW,
attrHealth.getBaseValue(),
attrSpeed.getBaseValue() * INTERNAL_TO_METRES_PER_SECOND));
event.setCancelled(true);
} else {
return;
}
}
private double jumpHeightInBlocks(double x) {
// This is a curve-fitted formula, so not 100% accurate
return -0.1817584952 * x * x * x + 3.689713992 * x * x + 2.128599134 * x - 0.343930367;
}
@Override
public void registerListeners() {
if (config.isEnabled()) {
plugin().log("Registering HorseStats listeners");
plugin().registerListener(this);
}
}
@Override
public void registerCommands() {
}
@Override
public void dataBootstrap() {
}
@Override
public void unregisterListeners() {
}
@Override
public void unregisterCommands() {
}
@Override
public void dataCleanup() {
}
public static HorseStatsConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new HorseStatsConfig(plugin, config);
}
@Override
public String status() {
return config.isEnabled() ? "HorseStats enabled." : "HorseStats disabled.";
}
}
@@ -1,5 +1,10 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.InsightConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -7,18 +12,14 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
@@ -32,12 +33,6 @@ import org.bukkit.plugin.IllegalPluginAccessException;
import org.bukkit.plugin.RegisteredListener;
import org.bukkit.scheduler.BukkitTask;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.configs.InsightConfig;
/**
* Config example:
* <code>
@@ -47,6 +42,7 @@ import com.programmerdan.minecraft.simpleadminhacks.configs.InsightConfig;
* - org.bukkit.event.player
* - org.bukkit.event.block
* - org.bukkit.event.inventory
* </code>
*
* This "into" list defines event classpaths. Events found in that classpath are
* instrumented; listeners are reordered to put _this_ hack's listeners as first and last to run.
@@ -69,24 +65,20 @@ import com.programmerdan.minecraft.simpleadminhacks.configs.InsightConfig;
public class Insight extends SimpleHack<InsightConfig> implements CommandExecutor, Listener {
public static final String NAME = "Insight";
private Map<String, InsightStat> tracking;
private List<Class<? extends Event>> rebounders;
public Insight(SimpleAdminHacks plugin, InsightConfig config) {
super(plugin, config);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length < 1) { // show help
return false;
}
return true;
return args.length >= 1;
}
@SuppressWarnings("unchecked")
@Override
@@ -94,17 +86,17 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
if (config.isEnabled()) {
plugin().log("Registering Insight reordering hack");
plugin().registerListener(this);
plugin().log("Registering Insight event instrumentations");
if (config.getInsightOn() == null) {
plugin().log("No instrumentations defined, disabling.");
this.softDisable();
this.disable();
return;
}
try {
ClassPath getSamplersPath = ClassPath.from(plugin().exposeClassLoader());
for (String clsPackage : config.getInsightOn()) {
ImmutableSet<ClassPath.ClassInfo> clsInfoSet = getSamplersPath.getTopLevelClasses(clsPackage);
if (clsInfoSet == null || clsInfoSet.isEmpty()) {
@@ -156,7 +148,7 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
}
private BukkitTask remap = null;
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void postPluginLoad(PluginEnableEvent e) {
if (!config.isEnabled()) {
@@ -165,7 +157,7 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
if (remap != null) {
remap.cancel();
}
remap = Bukkit.getScheduler().runTaskLater(plugin(), new Runnable(){
@Override
public void run() {
@@ -174,7 +166,7 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
}
}, 60l); // 3 seconds
}
/**
* For tracked events we hijack priority ordering insertions
* We to grab the registeredlistener array, remove all listeners
@@ -188,9 +180,9 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
Method method = clazz.getDeclaredMethod("getHandlerList");
HandlerList handler = (HandlerList) method.invoke(null);
RegisteredListener[] listeners = handler.getRegisteredListeners();
ArrayList<RegisteredListener> newOrdering = new ArrayList<RegisteredListener>(listeners.length);
ArrayList<RegisteredListener> thisLow = new ArrayList<RegisteredListener>();
ArrayList<RegisteredListener> thisHigh = new ArrayList<RegisteredListener>();
ArrayList<RegisteredListener> newOrdering = new ArrayList<>(listeners.length);
ArrayList<RegisteredListener> thisLow = new ArrayList<>();
ArrayList<RegisteredListener> thisHigh = new ArrayList<>();
for (RegisteredListener l : listeners) {
handler.unregister(l);
if (l.getPriority().equals(EventPriority.LOWEST)) {
@@ -239,8 +231,8 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
@Override
public void dataBootstrap() {
if (config.isEnabled()) {
this.tracking = new ConcurrentHashMap<String, InsightStat>();
this.rebounders = new ArrayList<Class<? extends Event>>();
this.tracking = new ConcurrentHashMap<>();
this.rebounders = new ArrayList<>();
}
}
@@ -265,7 +257,7 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
public String status() {
if (config.isEnabled()) {
StringBuffer sb = new StringBuffer( "Passively providing insight into events:\n");
if (this.tracking != null) {
sb.append(ChatColor.WHITE).append(String.format(" %10s %10s %10s %7s %7s %7s %7s Event\n", "Count", "Canceled", "Handlers", "Min", "Avg", "Max", "StDev"));
this.tracking.forEach((k, v) -> {
@@ -278,13 +270,13 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
sb.append(ChatColor.AQUA).append(k).append("\n");
});
}
return sb.toString();
} else {
return "Insights fully disabled.";
}
}
/**
* 10% of tick? Green.
* 15% of tick? Dark Green.
@@ -311,13 +303,13 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
return ChatColor.RED;
}
}
private ChatColor color(double microseconds) {
return color((long) microseconds);
}
// -- SUPPORT
public void start(Event event) {
if (config.isEnabled()) {
InsightStat insight = this.tracking.compute(event.getEventName(), (key, stat) ->
@@ -326,7 +318,7 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
insight.begin(event.getHandlers().getRegisteredListeners().length);
}
}
public void end(Event event) {
if (config.isEnabled()) {
InsightStat insight = this.tracking.get(event.getEventName());
@@ -339,11 +331,11 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
}
}
}
public static InsightConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new InsightConfig(plugin, config);
}
private static class InsightStat {
// count of events
private long count;
@@ -362,9 +354,9 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
private double std;
// last execution handler list size
private long handlers;
private long lastStart;
public InsightStat() {
count = 0l;
cancels = 0l;
@@ -376,7 +368,7 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
handlers = 0l;
lastStart = 0l;
}
/**
* Starts a specific capture for this; assumes runs in LOWEST for same-event.
* Will safely skip events that had no end() called.
@@ -387,7 +379,7 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
this.lastStart = System.nanoTime() / 1000l;
this.handlers = handlers;
}
/**
* Ends a specific capture for this; assumes runs in MONITOR for same-event.
*/
@@ -396,7 +388,7 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
if (cancelled) {
cancels ++;
}
if (count == 0) {
avg = len;
max = len;
@@ -420,31 +412,31 @@ public class Insight extends SimpleHack<InsightConfig> implements CommandExecuto
}
count++; // n+1
}
public long getHandlers() {
return handlers;
}
public double getAverage() {
return avg;
}
public long getSamples() {
return count;
}
public double getStdev() {
return std;
}
public long getMin() {
return min;
}
public long getMax() {
return max;
}
public long getCancels() {
return cancels;
}
@@ -1,11 +1,13 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.IntrobookConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@@ -22,24 +24,18 @@ import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import com.programmerdan.minecraft.simpleadminhacks.CommandListener;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.configs.HackBotConfig;
import com.programmerdan.minecraft.simpleadminhacks.configs.IntrobookConfig;
/**
* Simple Hack that handles gifting introbooks if enabled. Handles respawns as well
* through first restart, effectively.
*
* @author ProgrammerDan <programmerdan@gmail.com
* @author ProgrammerDan programmerdan@gmail.com
*
*/
public class Introbook extends SimpleHack<IntrobookConfig> implements Listener, CommandExecutor {
public static final String NAME = "Introbook";
private static long bookGiftCount = 0l;
private Set<UUID> hasBook;
public Introbook(SimpleAdminHacks plugin, IntrobookConfig config) {
@@ -85,13 +81,13 @@ public class Introbook extends SimpleHack<IntrobookConfig> implements Listener,
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void determineBookDrop(PlayerDeathEvent event) {
if (!config.isEnabled() || !config.doesFollow()) return;
Player dead = event.getEntity();
if (dead == null) return;
UUID puid = dead.getUniqueId();
if (puid == null) return;
// Check if they have the book on them.
List<ItemStack> toDrop = event.getDrops();
for (int idx = 0; idx < toDrop.size(); idx++) {
@@ -107,29 +103,29 @@ public class Introbook extends SimpleHack<IntrobookConfig> implements Listener,
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void determineBookRespawn(PlayerRespawnEvent event) {
if (!config.isEnabled() || !config.doesFollow()) return;
Player dead = event.getPlayer();
if (dead == null) return;
UUID puid = dead.getUniqueId();
if (puid == null) return;
if (this.hasBook.remove(puid)) {
Inventory inv = dead.getInventory();
inv.addItem(config.getIntroBook(dead));
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void determineBookJoin(PlayerJoinEvent event) {
if (!config.isEnabled()) return;
Player alive = event.getPlayer();
if (alive == null) return;
UUID puid = alive.getUniqueId();
if (puid == null) return;
if (!alive.hasPlayedBefore() || this.hasBook.contains(puid)) {
this.hasBook.remove(puid);
Inventory inv = alive.getInventory();
@@ -171,16 +167,16 @@ public class Introbook extends SimpleHack<IntrobookConfig> implements Listener,
sb.append("\n ").append(ChatColor.RED).append("-- in error --");
}
}
return sb.toString();
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length < 1) return false;
Player p = plugin().getServer().getPlayer(args[0]);
if (p == null) {
try {
UUID pu = UUID.fromString(args[0]);
@@ -189,7 +185,7 @@ public class Introbook extends SimpleHack<IntrobookConfig> implements Listener,
p = null;
}
}
if (p == null) {
sender.sendMessage(ChatColor.RED + "Unable to find " + args[0]);
} else {
@@ -198,10 +194,10 @@ public class Introbook extends SimpleHack<IntrobookConfig> implements Listener,
Inventory inv = p.getInventory();
inv.addItem(config.getIntroBook(p));
}
return true;
}
public static IntrobookConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new IntrobookConfig(plugin, config);
}
@@ -1,14 +1,14 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.InvControlConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import vg.civcraft.mc.namelayer.NameAPI;
import net.minecraft.server.v1_12_R1.NBTTagCompound;
import net.minecraft.server.v1_12_R1.NBTTagList;
import net.minecraft.server.v1_12_R1.WorldNBTStorage;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.world.level.storage.PlayerDataStorage;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
@@ -16,32 +16,25 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.v1_12_R1.CraftServer;
import org.bukkit.craftbukkit.v1_12_R1.inventory.CraftInventoryPlayer;
import org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_18_R2.CraftServer;
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryPlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.configs.HackBotConfig;
import com.programmerdan.minecraft.simpleadminhacks.configs.InvControlConfig;
import vg.civcraft.mc.namelayer.NameAPI;
public class InvControl extends SimpleHack<InvControlConfig> implements CommandExecutor, Listener {
public static final String NAME = "InvControl";
private boolean hasNameAPI;
private Set<UUID> adminsWithInv;
public InvControl(SimpleAdminHacks plugin, InvControlConfig config) {
super(plugin, config);
}
@@ -74,9 +67,10 @@ public class InvControl extends SimpleHack<InvControlConfig> implements CommandE
}
}
if (player == null && playerUID != null) { // Go deep into NBT.
WorldNBTStorage storage = (WorldNBTStorage) (((CraftServer) plugin().getServer()).getServer().worlds.get(0).getDataManager());
NBTTagCompound rawPlayer = storage.getPlayerData(playerUID.toString());
@SuppressWarnings("resource")
PlayerDataStorage storage = ((CraftServer) plugin().getServer()).getServer().playerDataStorage;
CompoundTag rawPlayer = storage.getPlayerData(playerUID.toString());
if (rawPlayer != null) {
plugin().debug("Player {0} found in NBT data, read-only access enabled.", playername);
sender.sendMessage("Player found via alternate lookup, read-only access enabled.");
@@ -84,16 +78,16 @@ public class InvControl extends SimpleHack<InvControlConfig> implements CommandE
sender.sendMessage("Player " + playername + " does not exist or cannot be opened.");
return false;
}
float health = rawPlayer.getFloat("Health");
int food = rawPlayer.getInt("foodLevel");
// Fun NMS inventory reconstruction from file data.
net.minecraft.server.v1_12_R1.PlayerInventory nms_pl_inv = new net.minecraft.server.v1_12_R1.PlayerInventory(null);
NBTTagList inv = rawPlayer.getList("Inventory", rawPlayer.getTypeId());
nms_pl_inv.b(inv); // We use this to bypass the Craft code which requires a player object, unlike NMS.
PlayerInventory pl_inv = (PlayerInventory) new CraftInventoryPlayer(nms_pl_inv);
net.minecraft.world.entity.player.Inventory nms_pl_inv = new net.minecraft.world.entity.player.Inventory(null);
ListTag inv = rawPlayer.getList("Inventory", rawPlayer.getId());
nms_pl_inv.load(inv); // We use this to bypass the Craft code which requires a player object, unlike NMS.
PlayerInventory pl_inv = new CraftInventoryPlayer(nms_pl_inv);
invSee(sender, pl_inv, health, food, playername);
return true;
}
@@ -113,7 +107,7 @@ public class InvControl extends SimpleHack<InvControlConfig> implements CommandE
sender.sendMessage(ChatColor.RED + "Apologies, this is only for in-game operators");
} else {
Player admin = (Player) sender;
if (admin.equals(player)) {
sender.sendMessage(ChatColor.RED + "You cannot modify your own inventory in this manner.");
} else {
@@ -126,16 +120,16 @@ public class InvControl extends SimpleHack<InvControlConfig> implements CommandE
}
return true;
}
public void adminCloseInventory(InventoryCloseEvent event) {
if (event.getPlayer() != null && this.adminsWithInv.contains(event.getPlayer().getUniqueId())) {
if (this.adminsWithInv.contains(event.getPlayer().getUniqueId())) {
this.adminsWithInv.remove(event.getPlayer().getUniqueId());
}
}
private void invSee(CommandSender sender, PlayerInventory pl_inv, double health, int food, String playername) {
if (!(sender instanceof Player)) { // send text only.
StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
sb.append(playername).append("'s\n Health: ").append((int)health*2);
sb.append("\n Food: ").append(food);
sb.append("\n Inventory: ");
@@ -179,7 +173,7 @@ public class InvControl extends SimpleHack<InvControlConfig> implements CommandE
@Override
public void registerListeners() {
}
@Override
@@ -1,9 +1,13 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.NewfriendAssistConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.BroadcastLevel;
import java.util.HashMap;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@@ -15,15 +19,9 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.ChatColor;
import com.programmerdan.minecraft.simpleadminhacks.BroadcastLevel;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.configs.HackBotConfig;
import com.programmerdan.minecraft.simpleadminhacks.configs.NewfriendAssistConfig;
/**
* A simple Newfriend tracker and assist module. Keeps track of newfriends so far.
@@ -31,7 +29,7 @@ import com.programmerdan.minecraft.simpleadminhacks.configs.NewfriendAssistConfi
* @author ProgrammerDan
*/
public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implements Listener, CommandExecutor {
public static final String NAME = "NewfriendAssist";
private static long newfriendCount = 0l;
/**
@@ -49,7 +47,7 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
* Track standard quit events to monitor newfriend playtime on the day of join.
*/
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
public void NewLeaveEvent(PlayerQuitEvent exit) {
public void newLeaveEvent(PlayerQuitEvent exit) {
if (!config.isEnabled()) return;
doLeave(exit.getPlayer());
}
@@ -58,7 +56,7 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
* Track standard kick events to monitor newfriend playtime on the day of join.
*/
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
public void NewKickEvent(PlayerKickEvent exit) {
public void newKickEvent(PlayerKickEvent exit) {
if (!config.isEnabled()) return;
doLeave(exit.getPlayer());
}
@@ -95,11 +93,11 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
newfriendNames.put(newUUID, newfriend.getName());
newfriendSessionTime.put(newUUID, new SessionTime(System.currentTimeMillis()));
if (config.getAnnounceBroadcast().size() > 0) {
if (!config.getAnnounceBroadcast().isEmpty()) {
// Prepare message
String cleanMessage = cleanMessage(join);
// Overlap is possible. Some people might get double-notified
for (BroadcastLevel level : config.getAnnounceBroadcast()) {
plugin().debug(" Broadcast to {0}", level);
@@ -119,7 +117,7 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
}
}
}
if (config.isIntroKitEnabled()) {
ItemStack[] introKit = config.getIntroKit();
if (introKit != null && introKit.length > 0) {
@@ -128,9 +126,40 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
plugin().log(Level.INFO, " Gave newbit kit to {0}", newfriend.getDisplayName());
}
}
}
/**
* Expanding the usage of this class to allow us to give RandomSpawning players a kit on each respawn.
* This method checks if the respawn event is a bed or anchor respawn and if not, gives them the introkit.
* @param event PlayerRespawnEvent
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onRespawn(PlayerRespawnEvent event) {
if (!config.giveToRandomSpawners()) {
return;
}
if (event.isAnchorSpawn()) {
return;
}
if (event.isBedSpawn()) {
return;
}
//Checking here if the player hasn't played before so we don't double spawn a starter kit
if (!event.getPlayer().hasPlayedBefore()) {
return;
}
if (config.isIntroKitEnabled()) {
ItemStack[] introKit = config.getIntroKit();
if (introKit != null && introKit.length > 0) {
Inventory inv = event.getPlayer().getInventory();
inv.addItem(introKit);
plugin().log(Level.INFO, " Gave randomspawner kit to {0}", event.getPlayer().getDisplayName());
}
}
}
private String cleanMessage(PlayerJoinEvent event) {
return ChatColor.translateAlternateColorCodes('&',
config.getAnnounceMessage()
@@ -156,8 +185,8 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
@Override
public void dataBootstrap() {
this.newfriendNames = new HashMap<UUID, String>();
this.newfriendSessionTime = new HashMap<UUID, SessionTime>();
this.newfriendNames = new HashMap<>();
this.newfriendSessionTime = new HashMap<>();
}
@Override
@@ -186,7 +215,7 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
*/
@Override
public String status() {
StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
if (config != null && config.isEnabled()) {
sb.append("NewfriendAssist.PlayerJoin/Quit/KickEvent monitoring active");
} else {
@@ -195,7 +224,7 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
sb.append("\n Since server start, ").append(NewfriendAssist.newfriendCount)
.append(" new players have joined.");
if (this.newfriendNames != null && !this.newfriendNames.isEmpty()) {
sb.append("\n New players since this hack was last enabled:");
for (HashMap.Entry<UUID, String> entry : this.newfriendNames.entrySet()) {
@@ -206,7 +235,7 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
}
}
}
if (config.isIntroKitEnabled()) {
sb.append("\n Introkit gifting is enabled. Current Introkit:");
if (config.getIntroKit() != null && config.getIntroKit().length > 0 ) {
@@ -222,18 +251,18 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
return sb.toString();
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!config.isIntroKitEnabled()) {
sender.sendMessage(ChatColor.RED + "Introkit gifting is disabled.");
return true;
}
if (args.length < 1) return false;
Player p = plugin().getServer().getPlayer(args[0]);
if (p == null) {
try {
UUID pu = UUID.fromString(args[0]);
@@ -242,7 +271,7 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
p = null;
}
}
if (p == null) {
sender.sendMessage(ChatColor.RED + "Unable to find " + args[0]);
} else {
@@ -251,11 +280,51 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
Inventory inv = p.getInventory();
inv.addItem(config.getIntroKit());
}
return true;
}
public static NewfriendAssistConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new NewfriendAssistConfig(plugin, config);
}
}
/**
* Simple data storage and processing class for NewfriendAssist and elsewhere to track playtime.
*
* @author ProgrammerDan programmerdan@gmail.com
*/
public static class SessionTime {
private long playtimeTotal;
private long lastJoin;
public SessionTime(long joinTime) {
playtimeTotal = 0l;
lastJoin = joinTime;
}
public long endSession(long leaveTime) {
if (lastJoin > 0l) {
playtimeTotal = leaveTime - lastJoin;
lastJoin = 0;
}
return playtimeTotal;
}
public void startSession(long startTime) {
if (lastJoin > 0l) {
endSession(startTime);
}
lastJoin = startTime;
}
public long totalTime() {
long toReturn = playtimeTotal;
if (lastJoin > 0l && System.currentTimeMillis() > lastJoin) {
toReturn += System.currentTimeMillis() - lastJoin;
}
return toReturn;
}
}
}
@@ -0,0 +1,434 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.BukkitCommandCompletionContext;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import com.devotedmc.ExilePearl.ExilePearlPlugin;
import com.google.common.collect.Lists;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.OneTimeTeleportConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import isaac.bastion.Bastion;
import isaac.bastion.BastionBlock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.minelink.ctplus.CombatTagPlus;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import vg.civcraft.mc.civmodcore.commands.TabComplete;
import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting;
import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI;
import vg.civcraft.mc.civmodcore.players.settings.impl.LongSetting;
import vg.civcraft.mc.civmodcore.utilities.TextUtil;
import vg.civcraft.mc.civmodcore.world.WorldUtils;
public final class OneTimeTeleport extends SimpleHack<OneTimeTeleportConfig> implements Listener {
public OneTimeTeleport(
final @NotNull SimpleAdminHacks plugin,
final @NotNull OneTimeTeleportConfig config
) {
super(plugin, config);
}
public static @NotNull OneTimeTeleportConfig generate(
final @NotNull SimpleAdminHacks plugin,
final @NotNull ConfigurationSection config
) {
return new OneTimeTeleportConfig(plugin, config);
}
@Override
public void onEnable() {
// Since there's no way to unregister settings without some gnarly reflection, this will only register settings
// that aren't already registered. Also, MoreCollectionUtils.getMissing() when?
final Collection<PlayerSetting<?>> allSettings = PlayerSettingAPI.getAllSettings();
final Collection<PlayerSetting<?>> ourSettings = Lists.newArrayList(this.grantedTimestamps);
ourSettings.removeIf(allSettings::contains);
ourSettings.forEach((setting) -> PlayerSettingAPI.registerSetting(setting, null));
plugin().registerListener(this);
plugin().getCommands().registerCommand(this.commands);
}
@Override
public void onDisable() {
plugin().getCommands().unregisterCommand(this.commands);
HandlerList.unregisterAll(this);
}
private final Map<UUID, UUID> senderToReceiver = new HashMap<>();
private static final long OTT_AVAILABLE = -1L;
private static final long OTT_UNAVAILABLE = -2L;
private final LongSetting grantedTimestamps = new LongSetting(
plugin(),
OTT_AVAILABLE,
"Time since OTT granted",
"timeSinceOTTGrant"
);
private final OTTCommand commands = new OTTCommand();
@CommandAlias("ott")
private final class OTTCommand extends BaseCommand {
@Default
public void defaultCommand(
final Player sender
) {
if (checkOTT(sender.getUniqueId())) {
final long expiresIn = config().getTimeLimitOnUsageInMillis() - (System.currentTimeMillis() - OneTimeTeleport.this.grantedTimestamps.getValue(sender.getUniqueId()));
sender.sendMessage(Component.text()
.content("Your one time teleport will expire in " + TextUtil.formatDuration(expiresIn))
.color(NamedTextColor.GREEN)
.decorate(TextDecoration.UNDERLINED)
.hoverEvent(HoverEvent.showText(Component.text("Clicking this message will suggest an OTT request command")))
.clickEvent(ClickEvent.suggestCommand("/ott to "))
);
return;
}
sender.sendMessage(Component.text("You don't have a one-time teleport.", NamedTextColor.RED));
}
@Subcommand("to|request")
@Description("Makes an OTT request to a player. If accepted, you'll teleport to them.")
@Syntax("<player>")
public void requestOTT(
final Player sender,
@Flags("other")
final Player targetPlayer
) {
if (targetPlayer == sender) {
sender.sendMessage(Component.text("You cannot OTT to yourself!", NamedTextColor.RED));
return;
}
switch (testPermissibility(sender, targetPlayer)) {
case OK -> {}
case FAIL_NO_OTT -> {
sender.sendMessage(Component.text("Your are no longer able to use OTT!"));
return;
}
case FAIL_IN_COMBAT -> {
sender.sendMessage(Component.text("You cannot OTT while in combat!", NamedTextColor.RED));
return;
}
case FAIL_IS_PEARLED -> {
sender.sendMessage(Component.text("You cannot OTT while pearled!", NamedTextColor.RED));
return;
}
case FAIL_DIFFERENT_WORLD -> {
sender.sendMessage(Component.text("You cannot OTT to another world!", NamedTextColor.RED));
return;
}
}
final UUID previousRequest = OneTimeTeleport.this.senderToReceiver.put(
sender.getUniqueId(),
targetPlayer.getUniqueId()
);
if (previousRequest != null) {
final Player previousTargetPlayer = Bukkit.getPlayer(previousRequest);
if (previousTargetPlayer != null) {
previousTargetPlayer.sendMessage(Component.text(sender.getName() + " has rescinded their OTT request to you.", NamedTextColor.GREEN));
}
}
sender.sendMessage(Component.text("You have requested to teleport to " + targetPlayer.getName() + "!", NamedTextColor.GREEN));
final String commandStr = "/ott accept " + sender.getName();
targetPlayer.sendMessage(Component.text()
.content("Click me or type \"" + commandStr + "\" to accept!")
.color(NamedTextColor.DARK_GREEN)
.decorate(TextDecoration.BOLD, TextDecoration.UNDERLINED)
.hoverEvent(HoverEvent.showText(Component.text("Clicking this message will suggest an accept command.")))
.clickEvent(ClickEvent.suggestCommand(commandStr))
);
}
@Subcommand("revoke|rescind|stop")
@Description("Revokes your OTT request.")
public void rescindRequest(
final Player sender
) {
final UUID targetUUID = OneTimeTeleport.this.senderToReceiver.remove(sender.getUniqueId());
if (targetUUID == null) {
sender.sendMessage(Component.text("You have no active OTT requests.", NamedTextColor.RED));
return;
}
sender.sendMessage(Component.text("You have revoked your OTT request!", NamedTextColor.GREEN));
final Player targetPlayer = Bukkit.getPlayer(targetUUID);
if (targetPlayer != null) {
targetPlayer.sendMessage(Component.text(sender.getName() + " has rescinded their OTT request to you.", NamedTextColor.GREEN));
}
}
@TabComplete("sah_ott_requests")
public List<String> getRequestsToMe(
final @NotNull BukkitCommandCompletionContext context
) {
final UUID targetPlayerUUID = context.getPlayer().getUniqueId();
return OneTimeTeleport.this.senderToReceiver.entrySet()
.stream()
.filter((entry) -> Objects.equals(targetPlayerUUID, entry.getValue()))
.map((entry) -> Bukkit.getPlayer(entry.getKey()))
.filter(Objects::nonNull)
.map(Player::getName)
.toList();
}
@Subcommand("accept|approve|allow|yes")
@CommandCompletion("@sah_ott_requests")
@Description("Accepts an OTT request to your location.")
@Syntax("<requester>")
public void acceptOTT(
final Player sender,
@Flags("other")
final Player requestingPlayer
) {
if (requestingPlayer == sender) {
sender.sendMessage(Component.text("You cannot accept an OTT from yourself!", NamedTextColor.RED));
return;
}
if (!OneTimeTeleport.this.senderToReceiver.remove(
requestingPlayer.getUniqueId(),
sender.getUniqueId()
)) {
sender.sendMessage(Component.text("There are no active requests from that player!", NamedTextColor.RED));
return;
}
switch (testPermissibility(requestingPlayer, sender)) {
case OK -> {}
case FAIL_NO_OTT -> {
sender.sendMessage(Component.text(requestingPlayer.getName() + "'s one-time teleport has expired!"));
requestingPlayer.sendMessage(Component.text("Failed to teleport because your one-time teleport has expired!"));
return;
}
case FAIL_IN_COMBAT -> {
sender.sendMessage(Component.text(requestingPlayer.getName() + " could not one-time teleport as they're in combat!", NamedTextColor.RED));
requestingPlayer.sendMessage(Component.text(sender.getName() + " accepted your request, but you're in combat!", NamedTextColor.RED));
OneTimeTeleport.this.senderToReceiver.put(requestingPlayer.getUniqueId(), sender.getUniqueId()); // Be kind and put the request back!
return;
}
case FAIL_IS_PEARLED -> {
sender.sendMessage(Component.text(requestingPlayer.getName() + " could not one-time teleport as they're pearled!", NamedTextColor.RED));
requestingPlayer.sendMessage(Component.text(sender.getName() + " accepted your request, but you're pearled!", NamedTextColor.RED));
return;
}
case FAIL_DIFFERENT_WORLD -> {
sender.sendMessage(Component.text(requestingPlayer.getName() + " could not one-time teleport as they're in a different world!", NamedTextColor.RED));
requestingPlayer.sendMessage(Component.text(sender.getName() + " accepted your request, but you're in a different world!", NamedTextColor.RED));
return;
}
}
if (Bukkit.getPluginManager().isPluginEnabled("Bastion")) {
for (final BastionBlock bastion : Bastion.getBastionManager().getBlockingBastions(sender.getLocation())) {
if (!bastion.canPlace(sender) || !bastion.canPlace(requestingPlayer)) {
sender.sendMessage(Component.text(requestingPlayer.getName() + " could not one-time teleport to a hostile bastion!", NamedTextColor.RED));
requestingPlayer.sendMessage(Component.text(sender.getName() + " accepted your request, but they're in a hostile bastion!", NamedTextColor.RED));
OneTimeTeleport.this.senderToReceiver.put(requestingPlayer.getUniqueId(), sender.getUniqueId()); // Be kind and put the request back!
return;
}
}
}
for (final Block block : getNearbyBlocks(sender.getLocation().getBlock(), 8)) {
if (config().getUnsafeMaterials().contains(block.getType())) {
sender.sendMessage(Component.text(requestingPlayer.getName() + " could not one-time teleport with unsafe blocks nearby!", NamedTextColor.RED));
requestingPlayer.sendMessage(Component.text(sender.getName() + " accepted your request, but there's unsafe blocks nearby!", NamedTextColor.RED));
OneTimeTeleport.this.senderToReceiver.put(requestingPlayer.getUniqueId(), sender.getUniqueId()); // Be kind and put the request back!
return;
}
}
// Remove any blacklisted materials from the player's inventory
requestingPlayer.sendMessage(Component.text("You may find some items missing after teleporting, these were removed as they are blacklisted to be teleported with!", NamedTextColor.AQUA));
final Inventory requestingPlayerInventory = requestingPlayer.getInventory();
config().getMaterialBlacklist().forEach(requestingPlayerInventory::remove);
OneTimeTeleport.this.grantedTimestamps.setValue(requestingPlayer.getUniqueId(), OTT_UNAVAILABLE);
OneTimeTeleport.this.logger.info("Player[" + requestingPlayer.getName() + "] has OTT-teleported from [" + WorldUtils.getBlockLocation(requestingPlayer.getLocation()) + "] to [" + sender.getName() + "] at [" + WorldUtils.getBlockLocation(sender.getLocation()) + "]");
requestingPlayer.teleport(sender.getLocation());
sender.sendMessage(Component.text(requestingPlayer.getName() + " has been teleported to you!", NamedTextColor.GREEN));
requestingPlayer.sendMessage(Component.text("You have been teleported to " + sender.getName() + "!", NamedTextColor.GREEN));
}
@Subcommand("reject|refuse|deny|no")
@CommandCompletion("@sah_ott_requests")
@Description("Rejects an OTT to your location.")
@Syntax("<requester>")
public void rejectOTT(
final Player sender,
@Flags("other")
final Player requestingPlayer
) {
if (requestingPlayer == sender) {
sender.sendMessage(Component.text("You cannot reject an OTT from yourself!", NamedTextColor.RED));
return;
}
if (!OneTimeTeleport.this.senderToReceiver.remove(
requestingPlayer.getUniqueId(),
sender.getUniqueId()
)) {
sender.sendMessage(Component.text("There are no active requests from that player!", NamedTextColor.RED));
return;
}
sender.sendMessage(Component.text("You have rejected " + requestingPlayer.getName() + "'s OTT request!", NamedTextColor.GREEN));
requestingPlayer.sendMessage(Component.text(sender.getName() + " has denied your OTT request!", NamedTextColor.RED));
}
@Subcommand("grant|bestow|give")
@CommandPermission("simpleadmin.grantott")
@CommandCompletion("@allplayers")
@Description("Gives a player a use of OTT as if they first joined.")
@Syntax("<recipient>")
public void grantOTT(
final CommandSender sender,
final String receivingPlayerName
) {
final OfflinePlayer receivingPlayer = Bukkit.getOfflinePlayerIfCached(receivingPlayerName);
if (receivingPlayer == null) {
sender.sendMessage(Component.text("Could not find player " + receivingPlayerName + "!", NamedTextColor.RED));
return;
}
OneTimeTeleport.this.grantedTimestamps.setValue(receivingPlayer.getUniqueId(), System.currentTimeMillis());
sender.sendMessage(Component.text("You have granted " + receivingPlayer.getName() + " an OTT!", NamedTextColor.GREEN));
final Player onlineReceivingPlayer = receivingPlayer.getPlayer();
if (onlineReceivingPlayer != null) {
onlineReceivingPlayer.sendMessage(Component.text("You have been granted an OTT!", NamedTextColor.GREEN));
}
OneTimeTeleport.this.logger.info(sender.getName() + " has granted " + receivingPlayer.getName() + " an OTT!");
}
};
@EventHandler
public void onFirstJoin(final PlayerJoinEvent event) {
checkOTT(event.getPlayer().getUniqueId());
}
@EventHandler
public void onQuit(final PlayerQuitEvent event) {
final Player whoQuit = event.getPlayer();
// Remove all pending OTT's involving this player
this.senderToReceiver.entrySet().removeIf((entry) -> {
final UUID requesterUUID = entry.getKey(), targetUUID = entry.getValue();
// The player was the target of OTT requests
if (Objects.equals(whoQuit.getUniqueId(), targetUUID)) {
final Player requesterPlayer = Bukkit.getPlayer(requesterUUID);
if (requesterPlayer != null) { // Just in case
requesterPlayer.sendMessage(Component.text("Your OTT request to " + whoQuit.getName() + " has been voided."));
}
return true;
}
// The player had made an OTT request
if (Objects.equals(whoQuit.getUniqueId(), requesterUUID)) {
final Player targetPlayer = Bukkit.getPlayer(targetUUID);
if (targetPlayer != null) { // Just in case
targetPlayer.sendMessage(Component.text(whoQuit.getName() + "'s OTT request has been voided."));
}
}
return false;
});
}
/** All possible permissible states that requesting and accepting share! */
private enum OttPermissible { OK, FAIL_NO_OTT, FAIL_IN_COMBAT, FAIL_IS_PEARLED, FAIL_DIFFERENT_WORLD }
private @NotNull OttPermissible testPermissibility(
final @NotNull Player requestingPlayer,
final @NotNull Player destinationPlayer
) {
if (!checkOTT(requestingPlayer.getUniqueId())) {
return OttPermissible.FAIL_NO_OTT;
}
if (Bukkit.getPluginManager().isPluginEnabled("CombatTagPlus")) {
final CombatTagPlus combatTagPlus = JavaPlugin.getPlugin(CombatTagPlus.class);
if (combatTagPlus.getTagManager().isTagged(requestingPlayer.getUniqueId())) {
return OttPermissible.FAIL_IN_COMBAT;
}
}
if (Bukkit.getPluginManager().isPluginEnabled("ExilePearl")) {
if (ExilePearlPlugin.getApi().getPearlManager().getPearl(requestingPlayer.getUniqueId()) != null) {
return OttPermissible.FAIL_IS_PEARLED;
}
}
if (config().isLimitingToSameWorld()) {
if (!Objects.equals(requestingPlayer.getWorld(), destinationPlayer.getWorld())) {
return OttPermissible.FAIL_DIFFERENT_WORLD;
}
}
return OttPermissible.OK;
}
private @NotNull List<Block> getNearbyBlocks(
final @NotNull Block start,
final int radius
) {
if (radius <= 0) {
return new ArrayList<>(0);
}
final int iterations = (radius * 2) + 1;
final var blocks = new ArrayList<Block>(iterations * iterations * iterations);
for (int x = -radius; x <= radius; x++) {
for (int y = -radius; y <= radius; y++) {
for (int z = -radius; z <= radius; z++) {
blocks.add(start.getRelative(x, y, z));
}
}
}
return blocks;
}
private boolean checkOTT(
final @NotNull UUID uuid
) {
final long grantedTimestamp = this.grantedTimestamps.getValue(uuid);
if (grantedTimestamp == OTT_UNAVAILABLE) {
return false;
}
if (grantedTimestamp == OTT_AVAILABLE) {
this.grantedTimestamps.setValue(uuid, System.currentTimeMillis());
return true;
}
if (System.currentTimeMillis() >= (grantedTimestamp + config().getTimeLimitOnUsageInMillis())) {
this.grantedTimestamps.setValue(uuid, OTT_UNAVAILABLE);
return false;
}
return true;
}
}
@@ -1,8 +1,11 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.configs.ReinforcedChestBreakConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
@@ -15,9 +18,6 @@ import org.bukkit.event.block.BlockBreakEvent;
import vg.civcraft.mc.citadel.Citadel;
import vg.civcraft.mc.citadel.ReinforcementManager;
import java.util.*;
import java.util.logging.Level;
/**
* Sends every 3 minutes a message to the admins if a chest is broken
*/
@@ -39,7 +39,9 @@ public class ReinforcedChestBreak extends SimpleHack<ReinforcedChestBreakConfig>
@Override
public void registerListeners() {
if (!config.isEnabled()) return;
if (!config.isEnabled()) {
return;
}
Bukkit.getPluginManager().registerEvents(this, plugin());
}
@@ -48,10 +50,12 @@ public class ReinforcedChestBreak extends SimpleHack<ReinforcedChestBreakConfig>
@Override
public void dataBootstrap() {
if (!config.isEnabled()) return;
if (!config.isEnabled()) {
return;
}
messages = new HashSet<>();
manager = Citadel.getReinforcementManager();
manager = Citadel.getInstance().getReinforcementManager();
messenger = new Messenger();
Bukkit.getScheduler().runTaskTimer(plugin(), messenger, 0, config.getDelay() * 20);
@@ -77,17 +81,38 @@ public class ReinforcedChestBreak extends SimpleHack<ReinforcedChestBreakConfig>
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled=true)
public void onBlockBreak(BlockBreakEvent eve) {
if (!config.isEnabled()) return;
if (eve.getPlayer() == null) return;
if (eve.getBlock() == null) return;
if (!config.isEnabled()) {
return;
}
Material bbe = eve.getBlock().getType();
if (bbe == null) return;
if (manager == null) manager = Citadel.getReinforcementManager();
if (manager == null) {
manager = Citadel.getInstance().getReinforcementManager();
}
// the list of items with inventories has grown a bit (1.14)
if (Material.CHEST.equals(bbe) || Material.TRAPPED_CHEST.equals(bbe)
|| Material.ENDER_CHEST.equals(bbe) || Material.FURNACE.equals(bbe)
|| Material.BURNING_FURNACE.equals(bbe) || Material.DISPENSER.equals(bbe)
|| Material.DROPPER.equals(bbe) || Material.HOPPER.equals(bbe)) {
if(manager.isReinforced(eve.getBlock())) {
|| Material.BLAST_FURNACE.equals(bbe) || Material.DISPENSER.equals(bbe)
|| Material.DROPPER.equals(bbe) || Material.HOPPER.equals(bbe)
|| Material.BARREL.equals(bbe) || Material.SMOKER.equals(bbe)
|| Material.BLACK_SHULKER_BOX.equals(bbe)
|| Material.BLUE_SHULKER_BOX.equals(bbe)
|| Material.BROWN_SHULKER_BOX.equals(bbe)
|| Material.CYAN_SHULKER_BOX.equals(bbe)
|| Material.GRAY_SHULKER_BOX.equals(bbe)
|| Material.GREEN_SHULKER_BOX.equals(bbe)
|| Material.LIGHT_BLUE_SHULKER_BOX.equals(bbe)
|| Material.LIGHT_GRAY_SHULKER_BOX.equals(bbe)
|| Material.LIME_SHULKER_BOX.equals(bbe)
|| Material.MAGENTA_SHULKER_BOX.equals(bbe)
|| Material.ORANGE_SHULKER_BOX.equals(bbe)
|| Material.PINK_SHULKER_BOX.equals(bbe)
|| Material.PURPLE_SHULKER_BOX.equals(bbe)
|| Material.RED_SHULKER_BOX.equals(bbe)
|| Material.SHULKER_BOX.equals(bbe)
|| Material.WHITE_SHULKER_BOX.equals(bbe)
|| Material.YELLOW_SHULKER_BOX.equals(bbe)
|| Material.LECTERN.equals(bbe) || Material.BREWING_STAND.equals(bbe)) {
if(manager.getReinforcement(eve.getBlock()) != null) {
String name = eve.getPlayer().getDisplayName();
Location loc = eve.getBlock().getLocation();
@@ -1,7 +1,9 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.SanityHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import java.util.logging.Level;
import org.bukkit.block.Block;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
@@ -11,17 +13,13 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.configs.SanityHackConfig;
public class SanityHack extends SimpleHack<SanityHackConfig> implements Listener {
public static final String NAME = "SanityHack";
private final int trackingLevel;
private int caughtPlayers; // Just to give a useful message in status tbh
public SanityHack(SimpleAdminHacks plugin, SanityHackConfig config) {
super(plugin, config);
this.trackingLevel = config.getTrackingLevel();
@@ -31,29 +29,29 @@ public class SanityHack extends SimpleHack<SanityHackConfig> implements Listener
public void onBlockBreak(BlockBreakEvent event){
if(!config.isEnabled()) return;
if(!config.isTrackingBreak()) return;
Block block = event.getBlock();
Player player = event.getPlayer();
if (player == null) return;
if (block == null) return;
if(block.getY() < trackingLevel){
plugin().log(Level.INFO, "Player({0}, {1}) caught breaking a block({2}) below Y {3} at {4}", player.getName(), player.getUniqueId().toString(),
block.getType().toString(), trackingLevel, block.getLocation());
caughtPlayers++;
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled=true)
public void onBlockPlace(BlockPlaceEvent event){
if(!config.isEnabled()) return;
if(!config.isTrackingPlace()) return;
Block block = event.getBlock();
Player player = event.getPlayer();
if (player == null) return;
if (block == null) return;
@@ -63,7 +61,7 @@ public class SanityHack extends SimpleHack<SanityHackConfig> implements Listener
caughtPlayers++;
}
}
@Override
public void registerListeners() {
if (config.isEnabled()) {
@@ -99,13 +97,13 @@ public class SanityHack extends SimpleHack<SanityHackConfig> implements Listener
if (!config.isEnabled()) {
return "Sanity Hack listening disabled.";
}
return caughtPlayers + " caught below Y " + trackingLevel;
}
public static SanityHackConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new SanityHackConfig(plugin, config);
}
}
@@ -0,0 +1,95 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.SaplingConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import java.util.Random;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.LeavesDecayEvent;
import org.bukkit.inventory.ItemStack;
public class SaplingHack extends SimpleHack<SaplingConfig> implements Listener {
private Random random = new Random();
public SaplingHack(SimpleAdminHacks plugin, SaplingConfig config) {
super(plugin, config);
}
@Override
public void onEnable() {
super.onEnable();
this.plugin.registerListener(this);
}
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
Block brokenBlock = event.getBlock();
if (!checkIfLeafBlock(brokenBlock)) {
return;
}
if (!this.config.isAllowFortune()) {
if (checkIfItemHasFortune(event.getPlayer().getItemInUse())) {
return;
}
}
if (!this.config.getChanceMap().containsKey(brokenBlock.getType())) {
return;
}
double nextDouble = random.nextDouble();
if (nextDouble <= this.config.getChanceMap().get(brokenBlock.getType())) {
brokenBlock.getWorld().dropItemNaturally(brokenBlock.getLocation(), new ItemStack(getSaplingMaterial(brokenBlock.getType()), 1));
logger.info("Dropped extra " + getSaplingMaterial(brokenBlock.getType()) + " from being broken at " + brokenBlock.getLocation());
}
}
@EventHandler
public void onLeafDecay(LeavesDecayEvent event) {
Block decayedBlock = event.getBlock();
if (!checkIfLeafBlock(decayedBlock)) {
return;
}
double nextDouble = random.nextDouble();
if (nextDouble <= this.config.getChanceMap().get(decayedBlock.getType())) {
decayedBlock.getWorld().dropItemNaturally(decayedBlock.getLocation(), new ItemStack(getSaplingMaterial(decayedBlock.getType()), 1));
logger.info("Dropped extra " + getSaplingMaterial(decayedBlock.getType()) + " from being broken at " + decayedBlock.getLocation());
}
}
private boolean checkIfLeafBlock(Block block) {
return switch (block.getType()) {
case OAK_LEAVES, BIRCH_LEAVES, SPRUCE_LEAVES, JUNGLE_LEAVES, ACACIA_LEAVES, DARK_OAK_LEAVES -> true;
default -> false;
};
}
private boolean checkIfItemHasFortune(ItemStack itemStack) {
if (itemStack == null) {
return false;
}
return itemStack.getEnchantments().containsKey(Enchantment.LOOT_BONUS_BLOCKS);
}
private Material getSaplingMaterial(Material material) {
return switch (material) {
case OAK_LEAVES -> Material.OAK_SAPLING;
case BIRCH_LEAVES -> Material.BIRCH_SAPLING;
case SPRUCE_LEAVES -> Material.SPRUCE_SAPLING;
case JUNGLE_LEAVES -> Material.JUNGLE_SAPLING;
case ACACIA_LEAVES -> Material.ACACIA_SAPLING;
case DARK_OAK_LEAVES -> Material.DARK_OAK_SAPLING;
//We set this to air so nothing will drop if it comes to erroring
default -> Material.AIR;
};
}
public static SaplingConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new SaplingConfig(plugin, config);
}
}
@@ -1,5 +1,9 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.google.common.collect.Sets;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.TimingsHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
@@ -15,7 +19,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.command.Command;
@@ -29,7 +33,7 @@ import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.server.MapInitializeEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.MapMeta;
import org.bukkit.map.MapCanvas;
import org.bukkit.map.MapPalette;
import org.bukkit.map.MapRenderer;
@@ -37,13 +41,6 @@ import org.bukkit.map.MapView;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import com.google.common.collect.Sets;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
import com.programmerdan.minecraft.simpleadminhacks.configs.TimingsHackConfig;
import net.md_5.bungee.api.ChatColor;
/**
* This crazy hack is focused on filling a gap left by /timings and warmroast and frankly, most
* supposed diagnostic tooling. They work on "over all time averages" which doesn't reveal _why_
@@ -60,34 +57,34 @@ import net.md_5.bungee.api.ChatColor;
* Future tooling will include the ability to view the method time allocations; it's tracked, but
* atm is not accessible to prevent overwhelming data explosion / overload. I might just add a thing
* to allow expansion for select named sourceclasses.
* <br/>
* <P>
* Commands:
* <br/>
* <P>
* <b>showtimings</b> Can only be run as a player, gives the running player a map object that persistently
* displays the TPS with a per-tick heatmap organized into second, per-tick heightmap organized into
* vertical slices of "relatively sized and colored" time, and a line graph showing longer term tick
* problems where sets of ticks begin to drift far above the average.<br/>
* problems where sets of ticks begin to drift far above the average.<P>
* <b>bindtimings</b> Can only be run as a player. Starts the HQ data collector, and gives the running player
* a map that displays the fractional tick impact of any Class that contains the argument passed into
* bindtimings. Also shows a per-tick heatmap of time utilization organized into seconds, per-tick
* heightmap organized into vertical slices of "relatively sized and colored" time, and a line graph showing
* relative fraction of Tick time vs. Avg Tick Time (basically, time spent in matching Class methods vs.
* per-second average tick). VERY useful.<br/>
* per-second average tick). VERY useful.<P>
* <b>thresholdtimings</b> This is the premier function for HQ. Starts the HQ data collector, and uses
* the passed in "threshold factor" to dump Class-level inspection of time-spent on problem ticks.
* Basically, if the threshold is passed by any specific tick, dumps a sorted list of "where time was spent"
* during that problem tick. This is extremely helpful and can be used to identify things that need monitoring
* via bindtimings.<br/>
* via bindtimings.<P>
* <b>listtimings</b> This gets _very_ spammy. It starts the HQ data collector, and sends to the requester
* all new Classes encountered each tick that hasn't been announced. After nothing new is encountered for a
* while, this shuts itself off.<br/>
* <b>stoptimings</b> This just shuts of all HQ data collection.<br/>
* <br/>
* while, this shuts itself off.<P>
* <b>stoptimings</b> This just shuts of all HQ data collection.<P>
* <P>
* In all cases if errors begin to be encountered, various portions will shut itself off. HQ can be restarted
* after automatic shutdown, normal tick tracking cannot.
* <br/>
* <P>
* From live server: http://imgur.com/a/zWNWo
* <br/>
* <P>
*
* @author ProgrammerDan
*/
@@ -97,7 +94,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
private static final int DEPTH_MAX = 60;
private static final int HQ_CYCLES = 120000;
private static final int LQ_CYCLES = 12000;
/**
* Nanosecond time of last tick start.
*/
@@ -110,11 +107,11 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
* Circular array storing millisecond lengths of ticks.
*/
private long[] ticks = null;
private long sumTicks = 0l;
private long cntTicks = 0l;
private double avgTick = 0.0d;
/**
* Nanosecond time of last HQ tick start.
*/
@@ -149,7 +146,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
* Aggregated data from hq elapsed time; circular array indexed by <code>tickRecord</code>.
*/
private long[] hqToLqElapsedTime = null;
/**
* Stores the breakdown of "where time is spent" inside each tick, leveraging a high resolution
* timer task. This high resolution timer task data is aggregated into the hqTickMap on a per class/method basis.
@@ -158,24 +155,24 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
private ConcurrentHashMap<String, ClassMethod>[] hqTickMap = null;
private int tickTask = 0;
private int tickErrors = 0;
private int hqTickErrors = 0;
private BukkitTask listTask = null;
private BukkitTask thresholdTask = null;
private TimingsMap tickVisualize = null;
private Map<String, BindTimingMap> bindVisualizers = null;
private long rootThread = -1;
private ThreadMXBean threadBean = null;
private ScheduledExecutorService executor = null;
private ScheduledFuture<?> hqTask = null;
public TimingsHack(SimpleAdminHacks plugin, TimingsHackConfig config) {
super(plugin, config);
}
@@ -184,24 +181,24 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!config.isEnabled()) return true;
if (command.getName().equalsIgnoreCase("thresholdtimings")) {
if (args.length < 1) {
sender.sendMessage("Please specify a threshold - a factor * against avg tick which if a particular tick exceeds it, outputs the worst offending classes");
return true;
}
startHq();
double factor = 1.0d;
try {
factor = Double.parseDouble(args[0]);
} catch (NumberFormatException e) {
sender.sendMessage("Cannot recognize " + args[0] + " as a factor -- should be something like 1.5 or 1.25");
return true;
}
sender.sendMessage("Request for Threshold Timings at " + factor + " received.");
if (this.thresholdTask != null) {
@@ -218,7 +215,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
} else if (command.getName().equalsIgnoreCase("listtimings")) {
// do open sampling for a little while until bindtimings is called or nothing new is found.
startHq();
sender.sendMessage("Request to list timings received.");
// Start monitor task a little later that accumulates and prints Classes to bind to
if (this.listTask != null) {
@@ -233,11 +230,11 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
return true;
} else if (command.getName().equalsIgnoreCase("stoptimings")) {
stopHq();
sender.sendMessage("Advanced timings stopped");
return true;
}
if (!(sender instanceof Player)) {
sender.sendMessage("No Map visualization support on console.");
return true;
@@ -255,10 +252,11 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
if (view != null) {
view.getRenderers().forEach(view::removeRenderer);
view.addRenderer(this.tickVisualize);
ItemStack viewMap = new ItemStack(Material.MAP, 1, view.getId());
ItemMeta mapMeta = viewMap.getItemMeta();
ItemStack viewMap = new ItemStack(Material.MAP, 1);
MapMeta mapMeta = (MapMeta) viewMap.getItemMeta();
mapMeta.setMapView(view);
mapMeta.setDisplayName("Tick Health Monitor");
mapMeta.setLore(Arrays.asList(
"TPS",
@@ -267,9 +265,9 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
"Bottom - Heatmap of per-tick-time"
));
viewMap.setItemMeta(mapMeta);
player.getInventory().addItem(viewMap);
player.sendMessage("Check your inventory for a TPS visualization Map");
} else {
player.sendMessage("Unable to generate TPS visualization map. Are you in Gamemode 3?");
@@ -281,12 +279,12 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
}
// show map with graph for a specific limited element
startHq();
player.sendMessage("Request received to bind to " + args[0] + " tick time utilization");
// Delay a few ticks, then give into inventory a map that shows the class % of TPS use
MapView view = null;
Short mapId = config.getBindMap(args[0]);
Integer mapId = config.getBindMap(args[0]);
if (mapId != null) {
view = Bukkit.getMap(config.getBindMap(args[0]));
if (!bindVisualizers.containsKey(args[0])) {
@@ -302,10 +300,11 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
if (view != null) {
view.getRenderers().forEach(view::removeRenderer);
view.addRenderer(this.bindVisualizers.get(args[0]));
ItemStack viewMap = new ItemStack(Material.MAP, 1, view.getId());
ItemMeta mapMeta = viewMap.getItemMeta();
ItemStack viewMap = new ItemStack(Material.MAP);
MapMeta mapMeta = (MapMeta) viewMap.getItemMeta();
mapMeta.setMapView(view);
mapMeta.setDisplayName(args[0] + " Utilization Monitor");
mapMeta.setLore(Arrays.asList(
args[0],
@@ -314,9 +313,9 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
"Bottom - Heatmap of per-tick-time"
));
viewMap.setItemMeta(mapMeta);
player.getInventory().addItem(viewMap);
player.sendMessage("Check your inventory for a " + args[0] + " binding TPS visualization Map");
} else {
player.sendMessage("Unable to generate TPS visualization map. Are you in Gamemode 3?");
@@ -325,20 +324,20 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
}
return true;
}
@Override
public void registerListeners() {
if (!config.isEnabled()) return;
plugin().log("Registering listeners");
plugin().registerListener(this);
}
@SuppressWarnings("deprecation")
@EventHandler(ignoreCancelled = true)
public void onMapInit(MapInitializeEvent event) {
MapView view = event.getMap();
if (config.getTimingsMap() != null && view.getId() == config.getTimingsMap().shortValue()) {
if (config.getTimingsMap() != null && view.getId() == config.getTimingsMap()) {
view.getRenderers().forEach(view::removeRenderer);
view.addRenderer(this.tickVisualize);
} else {
@@ -349,32 +348,32 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
bindVisualizers.put(bind, new BindTimingMap(bind)); // on demand binding.
}
view.addRenderer(this.bindVisualizers.get(bind));
}
}
}
@EventHandler(ignoreCancelled = true)
public void onItemHeldChange(PlayerItemHeldEvent event) {
Player player = event.getPlayer();
PlayerInventory inventory = player.getInventory();
ItemStack newHeld = inventory.getItem(event.getNewSlot());
if (newHeld != null && newHeld.getType().equals(Material.MAP)) {
ItemMeta baseMeta = newHeld.getItemMeta();
MapMeta baseMeta = (MapMeta) newHeld.getItemMeta();
if (baseMeta.hasLore()) {
try {
String ID = baseMeta.getLore().get(0);
if (ID.equals("TPS")) {
MapView view = Bukkit.getMap(newHeld.getDurability());
MapView view = baseMeta.hasMapView() ? baseMeta.getMapView() : Bukkit.getMap(baseMeta.getMapId());
if (view.getRenderers().size() == 1 && !view.getRenderers().get(0).equals(this.tickVisualize)) {
view.getRenderers().forEach(view::removeRenderer);
view.addRenderer(this.tickVisualize);
}
} else {
String bind = config.getBindFromId(newHeld.getDurability());
String bind = config.getBindFromId(baseMeta.getMapId());
if (bind.equalsIgnoreCase(ID)) {
MapView view = Bukkit.getMap(newHeld.getDurability());
MapView view = baseMeta.hasMapView() ? baseMeta.getMapView() : Bukkit.getMap(baseMeta.getMapId());
BindTimingMap bindViz = null;
if (!this.bindVisualizers.containsKey(bind)) {
bindVisualizers.put(bind, new BindTimingMap(bind)); // on demand binding.
@@ -416,9 +415,9 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
hqTickMap = new ConcurrentHashMap[LQ_CYCLES];
hqActive = false;
tickErrors = 0;
for (int i = 0; i < LQ_CYCLES; i++) {
hqTickMap[i] = new ConcurrentHashMap<String, ClassMethod>(100);
hqTickMap[i] = new ConcurrentHashMap<>(100);
}
lastTick = System.nanoTime();
@@ -429,15 +428,15 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
tick();
}
}, 0l, 1l);
tickVisualize = new TimingsMap();
bindVisualizers = new ConcurrentHashMap<String, BindTimingMap>();
bindVisualizers = new ConcurrentHashMap<>();
rootThread = Thread.currentThread().getId();
threadBean = ManagementFactory.getThreadMXBean();
executor = Executors.newSingleThreadScheduledExecutor();
}
private void startHq() {
if (hqActive) return; // already alive.
SimpleAdminHacks.instance().log("Starting HQ timer sequence");
@@ -446,11 +445,11 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
hqTickRecord = 0;
hqToLqTickRecord = 0; // tails behind hqTickRecord
hqTickErrors = 0;
hqCpuTime[0] = threadBean.getThreadCpuTime(rootThread);
hqLastTick = System.nanoTime();
hqActive = true;
SimpleAdminHacks.instance().log("Starting actual HQ timer");
hqTask = executor.scheduleAtFixedRate(new Runnable() {
@Override
@@ -464,15 +463,15 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
}
}, 1l, 1l, TimeUnit.MILLISECONDS);
}
private void stopHq() {
if (!hqActive) return; // already paused
SimpleAdminHacks.instance().log("Stopping HQ timer");
hqActive = false;
hqTask.cancel(true);
}
private void tick() {
if (!isEnabled()) return;
try {
@@ -484,13 +483,13 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
tickRecord = 0;
}
hqTickMap[tickRecord].clear(); // prepare future tickmap.
ticks[tempTickRecord] = tickTime;
sumTicks += tickTime - ticks[tickRecord]; // new record - outgoing record.
cntTicks = (cntTicks == LQ_CYCLES) ? LQ_CYCLES : cntTicks + 1;
avgTick = (double) sumTicks / (double) cntTicks;
if (hqActive) {
// now do Hq tick time summary
hqToLqCpuTime[tempTickRecord] = 0l;
@@ -504,41 +503,40 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
hqToLqElapsedTime[tempTickRecord] += hqElapsedTime[hqToLqTickRecord];
}
}
if (tickRecord % 1000 == 999) {
SimpleAdminHacks.instance().log(Level.INFO, "Recorded 1000 ticks so far, avg: {0}", avgTick);
}
lastTick = newTick;
} catch (Exception e) {
plugin().log(Level.WARNING, "Tick tracking encountered an error", e);
tickErrors ++;
if (tickErrors > 10) {
stopHq();
this.softDisable();
this.disable();
plugin().log(Level.WARNING, "Too many errors encountered, Tick tracking shut down.");
}
}
}
private void hqTick() {
try {
long newTick = System.nanoTime();
long tickTime = newTick - hqLastTick;
int nextHqTick = hqTickRecord >= HQ_CYCLES - 1 ? 0 : hqTickRecord + 1;
hqCpuTime[nextHqTick] = threadBean.getThreadCpuTime(rootThread); // "start" of next record stored now.
hqCpuTime[hqTickRecord] = hqCpuTime[nextHqTick] - hqCpuTime[hqTickRecord]; // diff against old start.
hqElapsedTime[hqTickRecord] = tickTime; // tick over tick time too, not just cpu time. Evals drift / co-sharing issues.
if (hqCpuTime[hqTickRecord] == 0) {
hqCpuTime[hqTickRecord] = tickTime;
}
ThreadInfo threadInfo = threadBean.getThreadInfo(rootThread, DEPTH_MAX);
StackTraceElement[] stack = threadInfo.getStackTrace();
//SimpleAdminHacks.instance().log(Level.INFO, "HQTick: {0} {1} {2}", hqTickRecord, tickTime, stack.length);
int tmpTickRecord = tickRecord;
if (tmpTickRecord >= LQ_CYCLES) tmpTickRecord = 0;
@@ -553,17 +551,17 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
return clzMethod;
});
}
hqTickRecord++;
if (hqTickRecord >= HQ_CYCLES) {
hqTickRecord = 0;
}
if (hqTickRecord % 10000 == 9999) {
SimpleAdminHacks.instance().log("Recorded 10000 hq ticks so far");
}
hqLastTick = newTick;
} catch (Exception e) {
plugin().log(Level.WARNING, "HQ Tick tracking encountered an error", e);
@@ -574,7 +572,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
}
}
}
@Override
public void unregisterListeners() {
}
@@ -586,23 +584,23 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
@Override
public void dataCleanup() {
stopHq();
if (tickTask > 0) {
Bukkit.getScheduler().cancelTask(tickTask);
}
if (listTask != null) {
try {
listTask.cancel();
} catch (Exception e) {
}
}
if (thresholdTask != null) {
try {
thresholdTask.cancel();
} catch (Exception e) {
}
}
}
@@ -615,7 +613,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
return "Timings Hack enabled.";
}
}
public static TimingsHackConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new TimingsHackConfig(plugin, config);
}
@@ -655,7 +653,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
return 12 + (int) Math.floor(1d - ((double) avgTick / (double) avgSec) * 22d);
}
}
@SuppressWarnings("deprecation")
private byte resolveColor(long tickLength) {
if (tickLength <= 10000l) {
@@ -678,7 +676,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
return MapPalette.matchColor(0, 0, 0); //.WHITE;
}
}
private int resolveWidth(long tickLength) {
if (tickLength <= 50000000l){
return 1;
@@ -692,7 +690,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
return 5;
}
}
@Override
public void render(MapView view, MapCanvas canvas, Player player) {
if (!isEnabled()) return;
@@ -705,9 +703,9 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
int newRow = 127;
int nextCol = 105;
int downCol = 107;
long tickAvg = 0l;
for (int displace = 0; displace < 2560; displace++) {
if (newRow != lastRow) {
// clear row
@@ -720,22 +718,22 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
if (activeIdx < 0) activeIdx += LQ_CYCLES;
long recorded = ticks[activeIdx];
tickAvg += recorded;
byte color = resolveColor(recorded);
for (int j = 0; j < resolveWidth(recorded);j++,nextCol--) {
canvas.setPixel(newRow, nextCol, color);
}
canvas.setPixel(newRow, downCol++, color);
if (displace % 20 == 19) {
long localAvg = Math.floorDiv(tickAvg, 20l);
canvas.setPixel(newRow, resolveY(localAvg, (long)avgTick), resolveColor(localAvg));
if (canvas.getPixel(newRow, 12) == 0) canvas.setPixel(newRow, 12, MapPalette.LIGHT_GRAY);
if (canvas.getPixel(newRow, 85) == 0) canvas.setPixel(newRow, 85, MapPalette.DARK_GRAY);
if (canvas.getPixel(newRow, 86) == 0) canvas.setPixel(newRow, 86, MapPalette.DARK_GRAY);
if (canvas.getPixel(newRow, 106) == 0) canvas.setPixel(newRow, 106, MapPalette.DARK_GRAY);
tickAvg = 0l;
newRow--;
nextCol = 105;
@@ -747,18 +745,18 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
}
}
}
class BindTimingMap extends MapRenderer {
private String regex;
private int errorCount = 0;
public BindTimingMap(String regex) {
super();
this.regex = regex;
this.errorCount = 0;
}
/**
* A neat little shapefitting function from 0 to 34 done partwise
* @param avgSec
@@ -774,7 +772,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
return 22 + (int) Math.floor(1d - ((double) avgTick / (double) avgSec) * 12d);
}
}
/**
* Deals with color at 10% to 40% of total tick -- above 40% will be full white; 10% green, and so on.
*
@@ -803,7 +801,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
return MapPalette.matchColor(0,0,0); //WHITE;
}
}
private int resolveWidth(long tickLength) {
if (tickLength <= 5000000l){
return 1;
@@ -817,7 +815,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
return 3;
}
}
@Override
public void render(MapView view, MapCanvas canvas, Player player) {
if (!isEnabled() || !hqActive) return;
@@ -830,10 +828,10 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
int newRow = 127;
int nextCol = 105;
int downCol = 107;
long avgUtil = 0l;
long avgTick = 0l;
for (int displace = 0; displace < 2560; displace++) {
if (newRow != lastRow) {
// clear row
@@ -844,7 +842,7 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
}
int activeIdx = storeStart - displace;
if (activeIdx < 0) activeIdx += LQ_CYCLES;
Long binding = hqTickMap[activeIdx].reduceValues(10000, (ClassMethod v) -> {
if (v != null && v.matches(regex)) {
return Long.valueOf(v.max());
@@ -860,32 +858,32 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
if(binding == null) {
binding = 0l;
}
long recorded = ticks[activeIdx];
avgTick += recorded;
avgUtil += binding;
byte color = resolveColor(binding);
for (int j = 0; j < resolveWidth(binding);j++,nextCol--) {
canvas.setPixel(newRow, nextCol, color);
}
canvas.setPixel(newRow, downCol++, color);
if (displace % 20 == 19) {
// now draw avg ratio up top.
long tAvg = Math.floorDiv(avgUtil, 20l);
canvas.setPixel(newRow, resolveY(tAvg, Math.floorDiv(avgTick, 20l)), resolveColor(tAvg));
if (canvas.getPixel(newRow, 22) == 0) canvas.setPixel(newRow, 22, MapPalette.LIGHT_GRAY);
if (canvas.getPixel(newRow, 85) == 0) canvas.setPixel(newRow, 85, MapPalette.DARK_GRAY);
if (canvas.getPixel(newRow, 86) == 0) canvas.setPixel(newRow, 86, MapPalette.DARK_GRAY);
if (canvas.getPixel(newRow, 106) == 0) canvas.setPixel(newRow, 106, MapPalette.DARK_GRAY);
// then reset
avgUtil = 0l;
avgTick = 0l;
newRow--;
nextCol = 105;
downCol = 107;
@@ -911,41 +909,41 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
class ClassMethod implements Comparable<ClassMethod>{
String clazz = null;
ConcurrentHashMap<String, Long> method = null;
long max = 0l; //nanoseconds
public ClassMethod(String clazz) {
this.clazz = clazz;
this.method = new ConcurrentHashMap<String, Long>();
this.max = 0l;
}
public void inc(String method, long elapsed) {
Long newm = this.method.merge(method, elapsed, (x, y) -> x + y); // insert, or merge if already present.
if (newm > max) {
max = newm;
}
}
public String getClazz() {
return clazz;
}
public long max() {
return max;
}
public int methodCount() {
return this.method.size();
}
public double fraction(String method) {
Long num = timeIn(method);
if (max <= 0l) return 0d;
if (num <= 0l) return 0d;
return (double) num / (double) max;
}
public long timeIn(String method) {
return this.method.getOrDefault(method, 0L);
}
@@ -954,50 +952,50 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
public int compareTo(ClassMethod o) {
return clazz.compareTo(o.clazz);
}
@Override
public int hashCode() {
return clazz.hashCode();
}
public boolean matches(String regex) {
return clazz.contains(regex); //.matches(regex);
}
}
class ListTask extends BukkitRunnable {
public static final long CANCEL_AFTER = 1000l;
Set<String> classOptions = Sets.newConcurrentHashSet();
long lastAdd = 0l;;
int lastTickInternal = -1;
WeakReference<CommandSender> sender = null;
public ListTask(CommandSender sender) {
super();
this.sender = new WeakReference<CommandSender>(sender);
}
@Override
public void run() {
if (lastTickInternal == tickRecord) return;
if (lastAdd == 0l) {
lastAdd = System.currentTimeMillis();
}
int checkTick = tickRecord - 1;
if (checkTick < 0) {
checkTick += TimingsHack.LQ_CYCLES;
}
ArrayList<String> keys = new ArrayList<String>(hqTickMap[checkTick].keySet());
keys.removeAll(classOptions);
if (keys.isEmpty()) {
if (System.currentTimeMillis() - lastAdd > CANCEL_AFTER) {
this.cancel();
@@ -1014,41 +1012,41 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
this.cancel();
}
}
lastTickInternal = tickRecord;
}
}
class ThresholdTask extends BukkitRunnable {
Set<String> classOptions = Sets.newConcurrentHashSet();
int lastTickInternal = -1;
double threshold = 1.5d;
WeakReference<CommandSender> sender = null;
public ThresholdTask(CommandSender sender, double threshold) {
super();
this.sender = new WeakReference<CommandSender>(sender);
this.threshold = threshold;
}
@Override
public void run() {
if (lastTickInternal == tickRecord) return;
int checkTick = tickRecord - 1;
if (checkTick < 0) {
checkTick += TimingsHack.LQ_CYCLES;
}
if ( (double) ticks[checkTick] <= avgTick * threshold ) {
return;
}
StringBuilder badTick = new StringBuilder();
badTick.append(ChatColor.AQUA).append("Typical tick is ").append(ChatColor.GREEN).append(String.format("%.2f", avgTick)).append("ns");
badTick.append(ChatColor.AQUA).append(" this tick was ").append(ChatColor.RED).append(String.format("%d", ticks[checkTick])).append("ns");
@@ -1056,9 +1054,9 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
.append(String.format(" %13d", hqToLqCpuTime[checkTick])).append(ChatColor.AQUA).append("ns")
.append(ChatColor.AQUA).append("\n Elapsed Time: \n").append(ChatColor.BLUE)
.append(String.format(" %13d", hqToLqElapsedTime[checkTick])).append(ChatColor.AQUA).append("ns");
TreeMap<Long, String> reveals = new TreeMap<Long, String>();
TreeMap<Long, String> reveals = new TreeMap<>();
for (ClassMethod cm : hqTickMap[checkTick].values()) {
reveals.compute(cm.max(), (k, S) -> {
if (S != null) {
@@ -1068,25 +1066,25 @@ public class TimingsHack extends SimpleHack<TimingsHackConfig> implements Listen
}
});
}
for (Long key : reveals.descendingKeySet()) {
badTick.append(reveals.get(key));
}
ArrayList<String> keys = new ArrayList<String>(hqTickMap[checkTick].keySet());
keys.removeAll(classOptions);
CommandSender toSend = sender.get();
if (toSend != null) {
toSend.sendMessage(badTick.toString());
} else {
this.cancel();
}
lastTickInternal = tickRecord;
}
}
}
@@ -0,0 +1,140 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.configs.ToggleLampConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.block.data.Lightable;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.metadata.MetadataValue;
import vg.civcraft.mc.citadel.Citadel;
import vg.civcraft.mc.citadel.ReinforcementManager;
import vg.civcraft.mc.citadel.model.Reinforcement;
public class ToggleLamp extends SimpleHack<ToggleLampConfig> implements Listener {
public static final String NAME = "ToggleLamp";
private static final String META_COOLDOWN = "ToggleLamp_NextToggle";
private static final String META_TOGGLED = "ToggleLamp_Toggled";
private ReinforcementManager rm;
public ToggleLamp(SimpleAdminHacks plugin, ToggleLampConfig config) {
super(plugin, config);
}
@EventHandler(priority = EventPriority.NORMAL)
public void onInteract(PlayerInteractEvent event) {
if (!config.isEnabled()) {
return;
}
Block clickedBlock = event.getClickedBlock();
if (clickedBlock == null) {
return;
}
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
if (event.getItem() == null) {
return;
}
if (event.getHand() != EquipmentSlot.HAND || event.getItem().getType() != Material.STICK) {
return;
}
Player eventPlayer = event.getPlayer();
Material clickedBlockMat = clickedBlock.getType();
if (clickedBlockMat != Material.REDSTONE_LAMP) {
return;
}
boolean toggled = clickedBlock.hasMetadata(META_TOGGLED)
&& clickedBlock.getMetadata(META_TOGGLED).get(0).asBoolean();
if (clickedBlock.hasMetadata(META_COOLDOWN)) {
MetadataValue val = clickedBlock.getMetadata(META_COOLDOWN).get(0);
if (((long) val.value()) > System.currentTimeMillis()) {
return;
}
}
if (rm != null) {
Reinforcement rein = rm.getReinforcement(clickedBlock);
if (rein != null) {
if (!rein.getGroup().isMember(eventPlayer.getUniqueId())) {
return;
}
}
}
switchLamp(clickedBlock, !toggled);
eventPlayer.getWorld().playSound(clickedBlock.getLocation(), Sound.BLOCK_LEVER_CLICK, 0.5F, 1.0F);
}
@Override
public void registerListeners() {
if (config.isEnabled()) {
plugin().log("Registering ToggleLamp listeners");
plugin().registerListener(this);
}
}
@Override
public void registerCommands() {
}
@Override
public void dataBootstrap() {
rm = plugin().serverHasPlugin("Citadel") ? Citadel.getInstance().getReinforcementManager() : null;
}
@Override
public void unregisterListeners() {
}
@Override
public void unregisterCommands() {
}
@Override
public void dataCleanup() {
rm = null;
}
@Override
public String status() {
return config.isEnabled() ? "ToggleLamp enabled." : "ToggleLamp disabled.";
}
public static ToggleLampConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
return new ToggleLampConfig(plugin, config);
}
private void switchLamp(Block block, boolean lit) {
if (block.getType() != Material.REDSTONE_LAMP) {
return;
}
Lightable lightable = (Lightable) block.getBlockData();
lightable.setLit(lit);
block.setBlockData(lightable);
block.setMetadata(META_TOGGLED, new FixedMetadataValue(plugin(), lit));
block.setMetadata(META_COOLDOWN,
new FixedMetadataValue(plugin(), System.currentTimeMillis() + config.getCooldownTime()));
}
}
@@ -0,0 +1,225 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.PacketManager;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_18_R2.util.CraftMagicNumbers;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils;
import vg.civcraft.mc.civmodcore.utilities.TextUtil;
import vg.civcraft.mc.civmodcore.utilities.cooldowns.ICoolDownHandler;
import vg.civcraft.mc.civmodcore.utilities.cooldowns.MilliSecCoolDownHandler;
import vg.civcraft.mc.civmodcore.utilities.ratelimiting.RateLimiter;
import vg.civcraft.mc.civmodcore.utilities.ratelimiting.RateLimiting;
/**
* Prevents "CivBreak" by denying continuous block break packets.
*/
public final class AntiFastBreak extends BasicHack {
private final PacketManager packets;
private final Map<UUID, Map<Location, Long>> miningLocations;
private final RateLimiter violationLimiter;
private final RateLimiter loggerLimiter;
private ICoolDownHandler<UUID> punishCooldown;
@AutoLoad(id = "laggLenciency") // Continue to support typo
private double lagLeniency;
@AutoLoad
private long breakDenyDuration;
public AntiFastBreak(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
this.packets = new PacketManager();
this.miningLocations = new TreeMap<>();
this.violationLimiter = RateLimiting.createRateLimiter("antiCivBreak", 10, 10, 1, 2_000L);
this.loggerLimiter = RateLimiting.createRateLimiter("antiCivBreakLogger", 4, 4, 1, 10_000L);
}
@Override
public void onEnable() {
super.onEnable();
if (this.breakDenyDuration <= 0) {
this.breakDenyDuration = 3000;
plugin().warning("Invalid break deny duration, defaulting to 3000ms");
}
this.punishCooldown = new MilliSecCoolDownHandler<>(this.breakDenyDuration);
this.packets.addAdapter(new PacketAdapter(plugin(), PacketType.Play.Client.BLOCK_DIG) {
@Override
public void onPacketReceiving(final PacketEvent event) {
final PacketContainer packet = event.getPacket();
final Player player = event.getPlayer();
final BlockPosition position = packet.getBlockPositionModifier().read(0);
final Location location = position.toLocation(player.getWorld());
switch (packet.getPlayerDigTypes().read(0)) {
case START_DESTROY_BLOCK:
handleStartDigging(player, location);
return;
case STOP_DESTROY_BLOCK:
handleFinishingDigging(player, location);
return;
default:
// some other stuff we dont care about
//return;
}
}
});
}
@Override
public void onDisable() {
this.packets.removeAllAdapters();
super.onDisable();
}
@EventHandler
public void onPlayerQuit(final PlayerQuitEvent event) {
this.miningLocations.remove(event.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onBlockBreak(final BlockBreakEvent event) {
final Player player = event.getPlayer();
if (!this.punishCooldown.onCoolDown(player.getUniqueId())) {
return;
}
player.sendMessage(ChatColor.RED + "Denying break due to abnormal break speed");
plugin().debug("Denying block break for " + player.getName() + " due cooldown for another "
+ TextUtil.formatDuration(this.punishCooldown.getRemainingCoolDown(player.getUniqueId())));
event.setCancelled(true);
}
private void handleStartDigging(final Player player, final Location location) {
this.miningLocations
.computeIfAbsent(player.getUniqueId(), uuid -> new HashMap<>())
.putIfAbsent(location, System.currentTimeMillis());
}
private void reward(final Player player) {
this.violationLimiter.addTokens(player.getUniqueId(), 1);
}
private void handleFinishingDigging(final Player player, final Location location) {
final Map<Location, Long> miningLocs = this.miningLocations
.computeIfAbsent(player.getUniqueId(), uuid -> new HashMap<>());
final int ticksToBreak = getTicksToBreak(location.getBlock(), player);
final Long timeStarted = miningLocs.remove(location);
if (timeStarted == null) {
if (ticksToBreak > 1) {
plugin().debug(player.getName() + " tried to instabreak non-instabreakable block");
punish(player);
}
else {
reward(player);
}
return;
}
if (ticksToBreak == 0) {
plugin().debug(player.getName() + " instabroke allowed block " + location.getBlock());
reward(player);
return;
}
final long msToBreak = ticksToBreak * 50L;
final long now = System.currentTimeMillis();
final long timePassed = now - timeStarted;
//plugin().debug("Measured " + timePassed + " for allowed time of " + TextUtil.formatDuration(msToBreak));
miningLocs.put(location, now);
if ((timePassed * this.lagLeniency) < msToBreak) {
punish(player);
}
else {
reward(player);
}
}
private void punish(final Player player) {
plugin().debug("Attempting to decrement token count for " + player.getName());
if (!this.violationLimiter.pullToken(player)) {
plugin().debug("Could not decrement token count for " + player.getName() + ", punishing...");
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin(), () -> {
this.punishCooldown.putOnCoolDown(player.getUniqueId());
if (this.loggerLimiter.pullToken(player.getUniqueId()) || plugin().isDebugEnabled()) {
plugin().warning(player.getName() + " is possibly using civ break, fast break detected");
}
player.sendMessage(ChatColor.RED + "You are breaking blocks too fast");
});
}
}
private static float getDamagePerTick(final Material material, final Player player) {
final ItemStack tool = player.getInventory().getItemInMainHand();
final BlockState blockData = getNMSBlockData(material);
if (blockData == null) {
throw new IllegalArgumentException("Could not determine block break type for " + material);
}
// if you ever need to version upgrade this, search for a method in n.m.s.Item
// calling "getDestroySpeed(this,blockData)" in n.m.s.ItemStack
float damagePerTick = CraftItemStack.asNMSCopy(tool).getDestroySpeed(blockData);
// above method does not include efficiency or haste, so we add it ourselves
final int effLevel = tool.getEnchantmentLevel(Enchantment.DIG_SPEED);
int efficiencyBonus = 0;
if (effLevel > 0 && damagePerTick > 1.0) { //damage per tick greater than 1.0 signals proper tool
efficiencyBonus = effLevel * effLevel + 1;
}
damagePerTick += efficiencyBonus;
int hasteLevel = 0;
final PotionEffect hasteEffect = player.getPotionEffect(PotionEffectType.FAST_DIGGING);
if (hasteEffect != null) {
// amplifier of 0 is potion effect at level one
hasteLevel = hasteEffect.getAmplifier() + 1;
}
damagePerTick *= 1.0 + 0.2 * hasteLevel;
return damagePerTick;
}
private static int getTicksToBreak(final Block block, final Player player) {
final Material material = block.getType();
if (!material.isBlock() || MaterialUtils.isAir(material)) {
// lagg, player is breaking a block already gone
return 0;
}
final float damageToDeal = material.getHardness() * 30;
final float damagePerTick = getDamagePerTick(material, player);
if (damageToDeal <= damagePerTick) {
// instabreak
return 0;
}
return (int) Math.ceil(damageToDeal / damagePerTick);
}
private static BlockState getNMSBlockData(final Material material) {
final net.minecraft.world.level.block.Block nmsBlock = CraftMagicNumbers.getBlock(material);
if (nmsBlock == null) {
return null;
}
return nmsBlock.defaultBlockState();
}
}
@@ -0,0 +1,68 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.EnderPearl;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.ProjectileHitEvent;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import vg.civcraft.mc.civmodcore.config.ConfigHelper;
public class AntiStatisChamber extends BasicHack {
@AutoLoad
private String pearlLifetime;
private long lifetimeValue;
private NamespacedKey key;
public AntiStatisChamber(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
this.key = new NamespacedKey(plugin, "pearl_thrown_time");
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerThrowPearl(PlayerLaunchProjectileEvent event) {
Projectile projectile = event.getProjectile();
if (!(projectile instanceof EnderPearl)) {
return;
}
EnderPearl pearl = (EnderPearl) projectile;
PersistentDataContainer pdc = pearl.getPersistentDataContainer();
pdc.set(key, PersistentDataType.LONG, System.currentTimeMillis());
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPearlLand(ProjectileHitEvent event) {
this.lifetimeValue = ConfigHelper.parseTime(this.pearlLifetime);
Projectile projectile = event.getEntity();
if (!(projectile instanceof EnderPearl)) {
return;
}
EnderPearl pearl = (EnderPearl) projectile;
PersistentDataContainer pdc = pearl.getPersistentDataContainer();
Long pearlThrownTime = pdc.get(key, PersistentDataType.LONG);
if (pearlThrownTime == null) {
pearl.remove();
event.setCancelled(true);
return;
}
if ((System.currentTimeMillis() - pearlThrownTime) > lifetimeValue) {
//We remove the pearl here because cancelling the event doesn't prevent the pearl from actually landing
pearl.remove();
if (pearl.getShooter() instanceof Player) {
((Player) pearl.getShooter()).sendMessage(Component.text("Your pearl failed to land for being in flight for more than " + pearlLifetime).color(NamedTextColor.RED));
}
event.setCancelled(true);
}
}
}
@@ -0,0 +1,184 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import java.util.List;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.inventory.PrepareAnvilEvent;
import org.bukkit.event.inventory.PrepareItemCraftEvent;
import org.bukkit.inventory.AnvilInventory;
import org.bukkit.inventory.CraftingInventory;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import vg.civcraft.mc.civmodcore.inventory.items.MetaUtils;
public class ArmourBound extends BasicHack {
private SimpleAdminHacks plugin;
private NamespacedKey key;
@AutoLoad
private List<String> whitelist;
public ArmourBound(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
this.plugin = plugin;
this.key = new NamespacedKey(plugin, "SAH_ArmourBound");
}
@EventHandler
public void onArmourEquip(PlayerArmorChangeEvent event){
Player player = event.getPlayer();
ItemStack newItem = event.getNewItem();
if (newItem == null) {
return;
}
if (newItem.getType() == Material.AIR) {
return;
}
EquipmentSlot realSlot = getRealSlot(event.getSlotType());
if (realSlot == null) {
return;
}
//From here, we take over from the copy, get the actual itemstack ourselves
newItem = player.getInventory().getItem(realSlot);
if (!whitelist.contains(newItem.getType().toString())) {
return;
}
ItemMeta meta = newItem.getItemMeta();
PersistentDataContainer itemContainer = meta.getPersistentDataContainer();
if (itemContainer.get(this.key, PersistentDataType.STRING) == null) {
itemContainer.set(this.key, PersistentDataType.STRING, player.getUniqueId().toString());
Component boundComponent = Component.text("This armor is bound to: ").decoration(TextDecoration.ITALIC, false).color(NamedTextColor.WHITE).append(Component.text(player.getName()).color(NamedTextColor.GOLD));
MetaUtils.addComponentLore(meta, boundComponent);
newItem.setItemMeta(meta);
return;
}
UUID boundUUID = UUID.fromString(itemContainer.get(this.key, PersistentDataType.STRING));
if (!boundUUID.equals(player.getUniqueId()) && !player.hasMetadata("NPC")) {
player.closeInventory();
ItemStack toDrop = newItem.clone();
newItem.setAmount(0);
player.getWorld().dropItemNaturally(player.getLocation(), toDrop);
player.sendMessage(Component.text("This armor is not bound to you!").color(NamedTextColor.RED));
}
}
@EventHandler
public void onItemCraft(PrepareItemCraftEvent event) {
if (event.getRecipe() == null) {
return;
}
CraftingInventory inventory = event.getInventory();
ItemStack[] matrix = inventory.getMatrix();
ItemStack boundItem = null;
PersistentDataContainer container = null;
ItemMeta meta = null;
for (ItemStack item : matrix) {
if (item == null) {
continue;
}
if (!whitelist.contains(item.getType().toString())) {
continue;
}
meta = item.getItemMeta();
if (meta == null) {
continue;
}
container = meta.getPersistentDataContainer();
if (!container.has(this.key, PersistentDataType.STRING)) {
continue;
}
boundItem = item;
break;
}
if (boundItem == null) {
return;
}
inventory.setResult(null);
for (HumanEntity viewer : inventory.getViewers()) {
if (viewer instanceof final Player playerViewer) {
playerViewer.updateInventory();
}
}
}
@EventHandler
public void onAnvilRepair(PrepareAnvilEvent event) {
AnvilInventory inventory = event.getInventory();
ItemStack first = inventory.getFirstItem();
if (first == null || first.getType() == Material.AIR) {
return;
}
ItemStack second = inventory.getSecondItem();
ItemStack result = event.getResult();
if (second == null || second.getType() == Material.AIR) {
return;
}
if (result == null || result.getType() == Material.AIR) {
return;
}
if (!first.hasItemMeta()) {
return;
}
if (!second.hasItemMeta()) {
return;
}
PersistentDataContainer firstContainer = first.getItemMeta().getPersistentDataContainer();
PersistentDataContainer secondContainer = second.getItemMeta().getPersistentDataContainer();
if (!firstContainer.has(this.key, PersistentDataType.STRING)) {
if (secondContainer.has(this.key, PersistentDataType.STRING)) {
result.setItemMeta(second.getItemMeta());
result.editMeta(meta -> {
if (meta instanceof Damageable damageable) {
damageable.setDamage(((Damageable)event.getResult().getItemMeta()).getDamage());
}
});
event.setResult(result);
}
return;
}
UUID firstUUID = UUID.fromString(firstContainer.get(this.key, PersistentDataType.STRING));
if (!secondContainer.has(this.key, PersistentDataType.STRING)) {
return;
}
UUID secondUUID = UUID.fromString(secondContainer.get(this.key, PersistentDataType.STRING));
if (firstUUID.equals(secondUUID)) {
//We dont want to do anything if these both match, otherwise set the result to null
return;
}
event.setResult(null);
for (HumanEntity entity : inventory.getViewers()) {
((Player)entity).updateInventory();
}
}
private EquipmentSlot getRealSlot(PlayerArmorChangeEvent.SlotType slotType) {
switch (slotType) {
case HEAD:
return EquipmentSlot.HEAD;
case CHEST:
return EquipmentSlot.CHEST;
case LEGS:
return EquipmentSlot.LEGS;
case FEET:
return EquipmentSlot.FEET;
default:
return null;
}
}
}
@@ -0,0 +1,85 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import java.util.List;
import java.util.Map;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.inventory.ItemStack;
import vg.civcraft.mc.civmodcore.inventory.items.SpawnEggUtils;
public class ArthropodEggHack extends BasicHack {
@AutoLoad
private double eggChance;
@AutoLoad
private double lootingChance;
@AutoLoad
private boolean removeDrops;
@AutoLoad
private List<String> allowedTypes;
public ArthropodEggHack(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
}
@EventHandler
public void onEntityDeath(EntityDeathEvent event) {
Player targetPlayer = event.getEntity().getKiller();
if (null == targetPlayer) {
return;
}
String type = event.getEntity().getType().toString();
if (allowedTypes == null || !allowedTypes.contains(type)) {
return;
}
// Check for a baby animal
if (event.getEntity() instanceof Ageable) {
Ageable ageableEntity = (Ageable) event.getEntity();
if (!ageableEntity.isAdult()) {
return;
}
}
// Check the player's currently equipped weapon
ItemStack handstack = targetPlayer.getEquipment().getItemInMainHand();
// Get the map of enchantments on that item
Map<Enchantment, Integer> itemEnchants = handstack.getEnchantments();
if (itemEnchants.isEmpty()) {
return;
}
// Check if one enchantment is BaneOfArthropods
if (null == itemEnchants.get(Enchantment.DAMAGE_ARTHROPODS)) {
return;
}
double randomNum = Math.random();
double levelOfArthropod = handstack.getEnchantmentLevel(Enchantment.DAMAGE_ARTHROPODS);
double levelOfLooting = handstack.getEnchantmentLevel(Enchantment.LOOT_BONUS_MOBS);
double targetPercentage = (eggChance * levelOfArthropod) + (lootingChance * levelOfLooting);
// Check if egg should be spawned
if (randomNum < targetPercentage) {
ItemStack item = new ItemStack(SpawnEggUtils.getSpawnEgg(event.getEntityType()), 1);
if (removeDrops) {
event.getDrops().clear();
event.setDroppedExp(0);
}
event.getDrops().add(item);
}
}
}
@@ -0,0 +1,222 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.Pair;
import com.comphenix.protocol.wrappers.PlayerInfoData;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.destroystokyo.paper.MaterialTags;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.PacketManager;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.PotionData;
import java.util.ArrayList;
import java.util.List;
public final class AttrHider extends BasicHack {
public static final String BYPASS_PERMISSION = "attrhider.bypass";
private final PacketManager packets = new PacketManager();
@AutoLoad
private boolean hideItemMeta;
@AutoLoad
private boolean hideEffects;
@AutoLoad
private boolean hideHealth;
@AutoLoad
private boolean roundPlayerListPing;
public AttrHider(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
}
@Override
public void onEnable() {
super.onEnable();
if (!this.hideItemMeta) {
this.packets.addAdapter(new PacketAdapter(this.plugin, PacketType.Play.Server.ENTITY_EQUIPMENT) {
@Override
public void onPacketSending(final PacketEvent event) {
final PacketContainer packet = event.getPacket();
if (event.getPlayer().hasPermission(BYPASS_PERMISSION)) {
return;
}
final var slots = packet.getSlotStackPairLists();
final var slotItemPairs = slots.read(0);
for (final Pair<EnumWrappers.ItemSlot, ItemStack> slotItemPair : slotItemPairs) {
final ItemStack item = slotItemPair.getSecond();
if (item == null || !shouldBeObfuscated(item.getType())) {
continue;
}
final ItemMeta meta = item.getItemMeta();
final ItemStack fakeItem = item.clone();
if (meta != null) {
final ItemMeta fakeMeta = fakeItem.getItemMeta();
if (meta instanceof LeatherArmorMeta) {
final LeatherArmorMeta baseLeatherMeta = (LeatherArmorMeta) meta;
final LeatherArmorMeta fakeLeatherMeta = (LeatherArmorMeta) fakeMeta;
fakeLeatherMeta.setColor(baseLeatherMeta.getColor());
}
if (meta instanceof PotionMeta) {
final PotionMeta basePotionMeta = (PotionMeta) meta;
final PotionMeta fakePotionMeta = (PotionMeta) fakeMeta;
final PotionData basePotion = basePotionMeta.getBasePotionData();
final PotionData fakePotion = new PotionData(basePotion.getType());
fakePotionMeta.setBasePotionData(fakePotion);
}
if (meta instanceof Damageable) {
final Damageable fakeDamageable = (Damageable) fakeMeta;
fakeDamageable.setDamage(0);
}
if (meta.hasEnchants()) {
for (Enchantment enchantment : fakeMeta.getEnchants().keySet()) {
fakeMeta.removeEnchant(enchantment);
}
fakeMeta.addEnchant(Enchantment.DURABILITY, 1, true);
}
fakeItem.setItemMeta(fakeMeta);
slotItemPair.setSecond(fakeItem); // Set item
}
}
slots.write(0, slotItemPairs);
}
});
}
if (this.hideEffects) {
this.packets.addAdapter(new PacketAdapter(this.plugin, PacketType.Play.Server.ENTITY_EFFECT) {
@Override
public void onPacketSending(final PacketEvent event) {
final PacketContainer packet = event.getPacket();
final Player player = event.getPlayer();
if (player.hasPermission(BYPASS_PERMISSION)) {
return;
}
final PacketContainer cloned = packet.deepClone();
final StructureModifier<Integer> ints = cloned.getIntegers();
if (player.getEntityId() == ints.read(0)) {
return;
}
// set amplifier to 0
cloned.getBytes().write(1, (byte) 0);
// set duration to 0
ints.write(1, 0);
// The packet data is shared between events, but the event
// instance is exclusive to THIS sending of the packet
event.setPacket(cloned);
}
});
}
if (this.hideHealth) {
this.packets.addAdapter(new PacketAdapter(this.plugin, PacketType.Play.Server.ENTITY_METADATA) {
@Override
public void onPacketSending(final PacketEvent event) {
final PacketContainer packet = event.getPacket();
final Player player = event.getPlayer();
if (player.hasPermission(BYPASS_PERMISSION)) {
return;
}
final Entity entity = packet.getEntityModifier(event).read(0);
if (!(entity instanceof LivingEntity)
|| player.getEntityId() == entity.getEntityId()
|| entity.getPassengers().contains(player)) {
return;
}
final PacketContainer cloned = packet.deepClone();
for (final WrappedWatchableObject object : cloned.getWatchableCollectionModifier().read(0)) {
// Read the 8th field as a float as that's the living entity's health
// https://wiki.vg/Entity_metadata#Living_Entity
if (object.getIndex() == 9) {
if ((float) object.getValue() > 0) {
object.setValue(1f); // Half a heart
}
}
}
// The packet data is shared between events, but the event
// instance is exclusive to THIS sending of the packet
event.setPacket(cloned);
}
});
}
if (this.roundPlayerListPing) {
this.packets.addAdapter(new PacketAdapter(this.plugin, PacketType.Play.Server.PLAYER_INFO) {
@Override
public void onPacketSending(final PacketEvent event) {
final PacketContainer packet = event.getPacket();
final Player player = event.getPlayer();
if (player.hasPermission(BYPASS_PERMISSION)) {
return;
}
final PacketContainer cloned = packet.deepClone();
List<PlayerInfoData> newInfos = new ArrayList<>();
List<PlayerInfoData> oldInfos = cloned.getPlayerInfoDataLists().read(0);
for (PlayerInfoData oldInfo : oldInfos) {
int latency = oldInfo.getLatency();
// Limit player ping in the tablist to the same 6 values vanilla clients can discern visually
// this follows 1.16.5 PlayerTabOverlay#renderPingIcon()
if (latency < 0) latency = -1;
else if (latency < 150) latency = 75; // average of 0 and 150, arbitrary
else if (latency < 300) latency = 225;
else if (latency < 600) latency = 450;
else if (latency < 1000) latency = 800;
else latency = 1000;
newInfos.add(new PlayerInfoData(
oldInfo.getProfile(),
latency,
oldInfo.getGameMode(),
oldInfo.getDisplayName()));
}
cloned.getPlayerInfoDataLists().write(0, newInfos);
// The packet data is shared between events, but the event
// instance is exclusive to THIS sending of the packet
event.setPacket(cloned);
}
});
}
}
@Override
public void onDisable() {
this.packets.removeAllAdapters();
super.onDisable();
}
private static boolean shouldBeObfuscated(final Material material) {
return MaterialTags.HELMETS.isTagged(material)
|| MaterialTags.CHEST_EQUIPPABLE.isTagged(material)
|| MaterialTags.LEGGINGS.isTagged(material)
|| MaterialTags.BOOTS.isTagged(material)
|| MaterialTags.SWORDS.isTagged(material)
|| MaterialTags.AXES.isTagged(material)
|| MaterialTags.PICKAXES.isTagged(material)
|| MaterialTags.SHOVELS.isTagged(material)
|| MaterialTags.HOES.isTagged(material)
|| material == Material.FIREWORK_ROCKET
|| material == Material.WRITTEN_BOOK
|| material == Material.ENCHANTED_BOOK
|| material == Material.POTION
|| material == Material.LINGERING_POTION
|| material == Material.SPLASH_POTION;
}
}
@@ -0,0 +1,204 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.google.common.base.Strings;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import vg.civcraft.mc.civmodcore.chat.ChatUtils;
import vg.civcraft.mc.civmodcore.utilities.MoreCollectionUtils;
public final class AutoRespawn extends BasicHack {
private final Map<Player, RespawnTimer> respawnTimers = new HashMap<>();
@AutoLoad
private long respawnDelay;
@AutoLoad
private long loginRespawnDelay;
@AutoLoad
private List<String> respawnQuotes;
public AutoRespawn(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
}
@Override
public void onEnable() {
super.onEnable();
this.respawnDelay = Math.max(0L, this.respawnDelay);
this.loginRespawnDelay = Math.max(0L, this.loginRespawnDelay);
if (this.respawnQuotes == null) {
this.respawnQuotes = new ArrayList<>();
}
}
@Override
public void onDisable() {
this.respawnTimers.forEach((player, timer) -> timer.stop());
this.respawnTimers.clear();
super.onDisable();
}
@EventHandler
public void onPlayerDeath(final PlayerDeathEvent event) {
final Player player = event.getEntity();
if (this.respawnDelay <= 0) {
plugin().info("Player [" + player.getName() + "] died, respawning.");
// This is necessary as respawning the player IMMEDIATELY means also not allowing the
// death process to occur (such as dropping items) to occur prior to the respawn.
Bukkit.getScheduler().runTask(this.plugin, () -> autoRespawnPlayer(player));
}
else {
plugin().info("Player [" + player.getName() + "] died, " +
"setting respawn timer: " + this.respawnDelay);
this.respawnTimers.computeIfPresent(player, (_player, timer) -> timer.stop());
this.respawnTimers.put(player, new RespawnTimer(this, player,
this.respawnDelay, this::autoRespawnPlayer));
}
}
@EventHandler
public void onPlayerLogin(final PlayerJoinEvent event) {
final Player player = event.getPlayer();
if (!player.isDead()) {
return;
}
if (this.loginRespawnDelay <= 0) {
plugin().info("Player [" + player.getName() + "] logged in while dead, respawning.");
// This is necessary as respawning the player IMMEDIATELY means also not allowing the
// death process to occur (such as dropping items) to occur prior to the respawn.
Bukkit.getScheduler().runTask(this.plugin, () -> autoRespawnPlayer(player));
}
else {
plugin().info("Player [" + player.getName() + "] logged in while dead, " +
"setting respawn timer: " + this.loginRespawnDelay);
this.respawnTimers.computeIfPresent(player, (_player, timer) -> timer.stop());
this.respawnTimers.put(player, new RespawnTimer(this, player,
this.loginRespawnDelay, this::autoRespawnPlayer));
}
}
@EventHandler
public void onPlayerLogout(final PlayerQuitEvent event) {
final Player player = event.getPlayer();
if (player.isDead()) {
this.respawnTimers.computeIfPresent(player, (_player, timer) -> timer.stop());
plugin().info("Player [" + player.getName() + "] logged out while dead.");
}
}
@EventHandler
public void onPlayerRespawn(final PlayerRespawnEvent event) {
this.respawnTimers.computeIfPresent(event.getPlayer(), (player, timer) -> timer.stop());
}
private void autoRespawnPlayer(final Player player) {
player.spigot().respawn();
final String message = MoreCollectionUtils.randomElement(this.respawnQuotes);
if (Strings.isNullOrEmpty(message)) {
return;
}
player.sendMessage(ChatUtils.parseColor(message));
}
// ------------------------------------------------------------
// Respawn Timer
// ------------------------------------------------------------
private static final class RespawnTimer {
private final Consumer<Player> handler;
private BossBar bar;
private long previousTime;
private final long setTime;
private long timeRemaining;
private long secondTimer;
private BukkitTask processor;
RespawnTimer(final AutoRespawn hack, final Player player, final long delay, final Consumer<Player> handler) {
this.handler = handler;
this.previousTime = System.currentTimeMillis();
this.setTime = this.timeRemaining = delay;
this.secondTimer = 1000L;
this.bar = Bukkit.createBossBar(generateBarTitle(), BarColor.WHITE, BarStyle.SOLID);
this.bar.setVisible(true);
this.bar.setProgress(1.0d);
this.bar.addPlayer(player);
this.processor = new BukkitRunnable() {
@Override
public void run() {
tick();
}
}.runTaskTimer(hack.plugin(), 1L, 1L);
}
private String generateBarTitle() {
if (this.timeRemaining <= 1000L) {
return "Respawning now.";
}
if (this.timeRemaining < 60000L) {
return "Respawning in " + (this.timeRemaining / 1000L) + " seconds.";
}
if (this.timeRemaining < 120000L) {
return "Respawning in 1 minute.";
}
if (this.timeRemaining < 3600000L) {
return "Respawning in " + (this.timeRemaining / 60000L) + " minutes.";
}
return "Respawning in " + (this.timeRemaining / 3600000L) + " hours.";
}
private void tick() {
long currentTime = System.currentTimeMillis();
long timeDifference = currentTime - this.previousTime;
this.previousTime = currentTime;
this.timeRemaining -= timeDifference;
this.secondTimer -= timeDifference;
if (this.secondTimer > 0) {
return;
}
this.secondTimer = 1000L;
this.bar.setTitle(generateBarTitle());
this.bar.setProgress(Math.max((double) (this.timeRemaining - 1000L) / (double) this.setTime, 0));
if (this.timeRemaining > 0) {
return;
}
this.bar.getPlayers().forEach(this.handler);
}
public RespawnTimer stop() {
if (this.bar != null) {
this.bar.setVisible(false);
this.bar.removeAll();
this.bar = null;
}
if (this.processor != null) {
this.processor.cancel();
this.processor = null;
}
return null;
}
}
}
@@ -0,0 +1,69 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import vg.civcraft.mc.civmodcore.chat.Componentify;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
import vg.civcraft.mc.civmodcore.world.WorldUtils;
public final class BedLocator extends BasicHack {
private final CommandManager commands;
public BedLocator(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
this.commands = new CommandManager(plugin()) {
@Override
public void registerCommands() {
registerCommand(new BedLocatorCommand());
}
};
}
@Override
public void onEnable() {
super.onEnable();
commands.init();
}
@Override
public void onDisable() {
commands.reset();
super.onDisable();
}
@CommandPermission("simpleadmin.bedlocator")
public static class BedLocatorCommand extends BaseCommand {
@CommandAlias("wheresmybed|locatebed")
@Description("Tells you where your bed is")
public void giveWand(final Player sender) {
final var bedLocation = sender.getBedSpawnLocation();
if (bedLocation == null) {
sender.sendMessage(Component.text("You do not a set bed.")
.color(NamedTextColor.GREEN));
return;
}
sender.sendMessage(Component.text()
.color(NamedTextColor.GREEN)
.append(Component.text("Your bed is at "))
.append(Componentify.blockLocation(bedLocation)));
if (WorldUtils.doLocationsHaveSameWorld(sender.getLocation(), bedLocation)) {
sender.sendMessage('['
+ "name:Bed Location,"
+ "x:" + bedLocation.getBlockX() + ','
+ "y:" + bedLocation.getBlockY() + ','
+ "z:" + bedLocation.getBlockZ()
+ ']');
}
}
}
}
@@ -0,0 +1,229 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.google.common.base.Strings;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.md_5.bungee.api.ChatColor;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.animal.Bee;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.v1_18_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_18_R2.block.CraftBlock;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import vg.civcraft.mc.civmodcore.chat.ChatUtils;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;
import vg.civcraft.mc.civmodcore.utilities.MoreCollectionUtils;
public final class BeeKeeping extends BasicHack {
private static final Random RANDOM = new SecureRandom();
public BeeKeeping(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
}
// ------------------------------------------------------------
// Natural Bee Spawning
// ------------------------------------------------------------
private static final Set<EntityType> NEUTRAL_MOBS = Set.of(
EntityType.CHICKEN,
EntityType.COW,
EntityType.PIG,
EntityType.SHEEP,
EntityType.RABBIT);
@AutoLoad
private double spawnChance;
@EventHandler(ignoreCancelled = true)
public void beeSpawning(final CreatureSpawnEvent event) {
if (!NEUTRAL_MOBS.contains(event.getEntityType())
|| event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.NATURAL
|| this.spawnChance <= 0.0d
|| (this.spawnChance < 1.0d && RANDOM.nextDouble() > this.spawnChance)) {
return;
}
final Location location = event.getLocation();
final World world = location.getWorld();
world.spawnEntity(location, EntityType.BEE);
event.setCancelled(true);
}
// ------------------------------------------------------------
// Bee Combat
// ------------------------------------------------------------
@AutoLoad
private double severStingerChance;
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void attackedByBee(final EntityDamageByEntityEvent event) {
final Entity attacker = event.getDamager();
if (attacker.getType() != EntityType.BEE
|| event.getCause() != EntityDamageEvent.DamageCause.ENTITY_ATTACK // Ignore if not attacked
|| this.severStingerChance >= 1.0d // Ignore if severance is guaranteed
|| (this.severStingerChance > 0.0d && RANDOM.nextDouble() <= this.severStingerChance)) {
return;
}
final Bee bee = (Bee) ((CraftEntity) attacker).getHandle();
// Undo bee pacification and allow the bee to sting again
/** See the code in {@link IEntityAngerable#pacify()} */
final var lastDamageCause = bee.getLastHurtByMob();
final var angerTarget = bee.getPersistentAngerTarget();
final var goalTarget = bee.getTarget();
final var angerLevel = bee.getRemainingPersistentAngerTime();
Bukkit.getScheduler().runTask(this.plugin, () -> {
bee.setHasStung(false);
// Reset the above values
bee.setLastHurtByMob(lastDamageCause);
bee.setPersistentAngerTarget(angerTarget);
bee.setTarget(goalTarget, EntityTargetEvent.TargetReason.FORGOT_TARGET, false); // Params: target, cause, emit event
bee.setRemainingPersistentAngerTime(angerLevel);
});
}
// ------------------------------------------------------------
// Bee Hive/Nest Details
// ------------------------------------------------------------
private static final String BEES_LIST_KEY = "Bees";
private static final String BEE_DATA_KEY = "EntityData";
private static final String BEE_NAME_KEY = "CustomName";
private static final Set<Material> HIVE_MATERIALS = Set.of(
Material.BEE_NEST,
Material.BEEHIVE);
@EventHandler(ignoreCancelled = true)
public void showHiveDetails(final PlayerInteractEvent event) {
final Player player = event.getPlayer();
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
final ItemStack held = event.getItem();
if (ItemUtils.isValidItem(held)) {
return;
}
final Block block = Objects.requireNonNull(event.getClickedBlock());
if (!HIVE_MATERIALS.contains(block.getType())) {
return;
}
final BeehiveBlockEntity beehive = getBeeHive(block);
if (beehive.isEmpty()) {
player.sendMessage(ChatColor.GOLD + "There aren't any bees in that hive.");
return;
}
final List<BeeData> bees = getBeesFromHive(beehive);
final int numberOfUnnamed = MoreCollectionUtils.numberOfMatches(bees, BeeData::isNameless);
bees.removeIf(BeeData::isNameless);
// Start building response
final var response = Component.text().color(NamedTextColor.GOLD);
final Iterator<BeeData> nameIterator = bees.iterator();
boolean doneFirstElement = false;
while (nameIterator.hasNext()) {
final BeeData bee = nameIterator.next();
if (nameIterator.hasNext() || numberOfUnnamed > 0) {
if (doneFirstElement) {
response.append(Component.text(", "));
}
}
else if (numberOfUnnamed == 0) {
response.append(Component.text(", and "));
}
assert bee.name != null;
response.append(bee.name);
doneFirstElement = true;
}
if (numberOfUnnamed > 0) {
if (bees.isEmpty()) {
response.append(Component.text("There are " + numberOfUnnamed + " bees"));
}
else {
response.append(Component.text(", and " + numberOfUnnamed + " others are"));
}
}
else {
response.append(Component.text(" are"));
}
if (beehive.isSedated()) {
response.append(Component.text(" sedated"));
}
else {
response.append(Component.text(" happily buzzing"));
}
response.append(Component.text(" in that hive."));
player.sendMessage(response);
}
private static BeehiveBlockEntity getBeeHive(@Nonnull final Block block) {
final CraftBlock craftBlock = (CraftBlock) block;
final CraftWorld craftWorld = craftBlock.getCraftWorld();
final ServerLevel worldServer = craftWorld.getHandle();
final BlockEntity tileEntity = worldServer.getBlockEntity(craftBlock.getPosition());
return (BeehiveBlockEntity) Objects.requireNonNull(tileEntity);
}
private static List<BeeData> getBeesFromHive(@Nonnull final BeehiveBlockEntity hive) {
return hive.writeBees().stream()
.map(bee -> ((CompoundTag) bee).getCompound(BEE_DATA_KEY))
.map(BeeData::new)
.collect(Collectors.toCollection(ArrayList::new));
}
private static final class BeeData {
public final Component name;
public BeeData(@Nonnull final CompoundTag nbt) {
// Parse name
final String rawName = nbt.getString(BEE_NAME_KEY);
if (Strings.isNullOrEmpty(rawName)) {
this.name = null;
}
else {
final var componentName = GsonComponentSerializer.gson().deserialize(rawName);
this.name = ChatUtils.isNullOrEmpty(componentName) ? null : componentName;
}
}
public boolean isNameless() {
return this.name == null;
}
}
}
@@ -0,0 +1,55 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import static org.bukkit.event.entity.EntityPotionEffectEvent.Action;
import static org.bukkit.event.entity.EntityPotionEffectEvent.Cause;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.DataParser;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityPotionEffectEvent;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
public final class BuffSpanker extends BasicHack {
@AutoLoad(id = "naughty", processor = DataParser.POTION_EFFECT_TYPE)
private List<PotionEffectType> naughtyEffects;
public BuffSpanker(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
}
@Override
public void onEnable() {
super.onEnable();
if (this.naughtyEffects == null) {
this.naughtyEffects = new ArrayList<>();
}
}
@Override
public void onDisable() {
this.naughtyEffects.clear();
super.onDisable();
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void whenPotionEffectsChange(final EntityPotionEffectEvent event) {
final PotionEffect effect = event.getNewEffect();
if (effect == null || event.getAction() != Action.ADDED || event.getCause() == Cause.PLUGIN) {
return;
}
if (this.naughtyEffects.contains(effect.getType())) {
plugin().debug("Prevented [" + effect.getType().getName() + "] on: " + event.getEntity().getName());
event.setCancelled(true);
//return;
}
}
}
@@ -0,0 +1,191 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.destroystokyo.paper.MaterialTags;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.level.block.WeatheringCopper;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.craftbukkit.v1_18_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_18_R2.block.CraftBlock;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_18_R2.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Minecart;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class CopperRail extends BasicHack {
// ServerLevel has a private version of this so we will make one ourselves
private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom();
@AutoLoad
private boolean deoxidise;
@AutoLoad
private double damage;
private boolean formingBlock = false;
public CopperRail(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
}
@EventHandler
public void on(VehicleMoveEvent event) {
if (this.damage <= 0 || !(event.getVehicle() instanceof Minecart minecart)) {
return;
}
boolean hasPlayer = false;
for (Entity entity : minecart.getPassengers()) {
if (entity instanceof Player) {
hasPlayer = true;
break;
}
}
if (!hasPlayer) {
return;
}
Location to = event.getTo();
Location from = event.getFrom();
if (to.getBlockX() == from.getBlockX() && to.getBlockY() == from.getBlockY() && to.getBlockZ() == from.getBlockZ()) {
return;
}
int signX = from.getBlockX() > to.getBlockX() ? 1 : -1;
int signZ = from.getBlockZ() > to.getBlockZ() ? 1 : -1;
boolean firstBlock = true;
List<Block> copperBlocks = new ArrayList<>(4);
for (int x = to.getBlockX(); x != to.getBlockX() + (from.getBlockX() - to.getBlockX()) + signX; x += signX) {
for (int z = to.getBlockZ(); z != to.getBlockZ() + (from.getBlockZ() - to.getBlockZ()) + signZ; z += signZ) {
if (firstBlock) {
firstBlock = false;
continue;
}
Location location = new Location(minecart.getWorld(), x, from.getY(), z);
Block topCopperBlock = location.getBlock().getRelative(BlockFace.DOWN);
Optional<net.minecraft.world.level.block.Block> next = WeatheringCopper.getNext(((CraftBlock) topCopperBlock).getNMS().getBlock());
if (next.isPresent()) {
copperBlocks.add(topCopperBlock);
}
Block belowCopperBlock = topCopperBlock.getRelative(BlockFace.DOWN);
next = WeatheringCopper.getNext(((CraftBlock) belowCopperBlock).getNMS().getBlock());
if (next.isPresent()) {
copperBlocks.add(belowCopperBlock);
}
}
}
for (Block copperBlock : copperBlocks) {
CraftBlock craftBlock = (CraftBlock) copperBlock;
BlockState state = craftBlock.getNMS();
ServerLevel level = ((CraftWorld) copperBlock.getWorld()).getHandle();
// We damage the copper directly instead of using random ticking, as random ticking is easy to cheese
// by placing waxed copper next to the rail, entirely preventing the rest of the rail from oxidising.
WeatheringCopper copper = (WeatheringCopper) state.getBlock();
float chanceModifier = copper.getChanceModifier();
if (this.damage * chanceModifier > this.randomTickRandom.nextFloat()) {
copper.getNext(state).ifPresent((iblockdata2) -> {
try {
formingBlock = true;
CraftEventFactory.handleBlockFormEvent(level, craftBlock.getPosition(), iblockdata2);
} finally {
formingBlock = false;
}
});
}
}
}
@EventHandler(ignoreCancelled = true)
public void on(PlayerInteractEvent event) {
if (!this.deoxidise) {
return;
}
ItemStack item = event.getItem();
if (item == null || !MaterialTags.AXES.isTagged(item)) {
return;
}
Block block = event.getClickedBlock();
if (block == null || !MaterialTags.RAILS.isTagged(block)) {
return;
}
Block copperBlock = block.getRelative(BlockFace.DOWN);
Optional<BlockState> previous = WeatheringCopper.getPrevious(((CraftBlock) copperBlock).getNMS());
if (previous.isEmpty()) {
copperBlock = copperBlock.getRelative(BlockFace.DOWN);
}
previous = WeatheringCopper.getPrevious(((CraftBlock) copperBlock).getNMS());
if (previous.isEmpty()) {
return;
}
copperBlock.setType(previous.get().getBukkitMaterial());
block.getWorld().playSound(block.getLocation(), Sound.ITEM_AXE_SCRAPE, SoundCategory.BLOCKS, 1, 1);
block.getWorld().playEffect(block.getLocation(), Effect.OXIDISED_COPPER_SCRAPE, 0);
CraftPlayer player = (CraftPlayer) event.getPlayer();
// TODO: In 1.19 or above, this can be replaced with ItemStack#damage thanks to Paper
((CraftItemStack) item).handle.hurtAndBreak(1, player.getHandle(), p -> {
p.broadcastBreakEvent(event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND);
});
event.setCancelled(true);
}
// It's not really fair for copper blocks that are below rails to naturally oxidise,
// as it is easy to cheese by placing a waxed copper block every 9 blocks
@EventHandler
public void on(BlockFormEvent event) {
if (formingBlock) {
return;
}
Block block = event.getBlock();
Optional<net.minecraft.world.level.block.Block> next = WeatheringCopper.getNext(((CraftBlock) block).getNMS().getBlock());
if (next.isEmpty()) {
return;
}
Block railAbove = block.getRelative(BlockFace.UP);
if (!MaterialTags.RAILS.isTagged(railAbove)) {
railAbove = railAbove.getRelative(BlockFace.UP);
}
if (!MaterialTags.RAILS.isTagged(railAbove)) {
return;
}
event.setCancelled(true);
}
}
@@ -0,0 +1,129 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import java.util.Objects;
import net.minecraft.nbt.CompoundTag;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;
public final class DebugWand extends BasicHack {
private static final String PERMISSION = "simpleadmin.debugwand";
private static final ItemStack WAND = new ItemStack(Material.BLAZE_ROD);
static {
ItemUtils.setDisplayName(WAND, ChatColor.GOLD + "Block Wand");
}
private final CommandManager commands;
public DebugWand(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
this.commands = new CommandManager(plugin) {
@Override
public void registerCommands() {
registerCommand(new WandCommand());
}
};
}
@Override
public void onEnable() {
super.onEnable();
this.commands.init();
}
@Override
public void onDisable() {
this.commands.reset();
super.onDisable();
}
@CommandPermission(PERMISSION)
public static class WandCommand extends BaseCommand {
@CommandAlias("debugwand|dbwand")
@Description("Creates a wand for debugging")
public void giveWand(final Player sender) {
sender.getInventory().addItem(WAND.clone());
sender.sendMessage(ChatColor.GREEN + "Wand created.");
}
}
@EventHandler
public void onWandUsageOnBlock(final PlayerInteractEvent event) {
final Player player = event.getPlayer();
if (!player.hasPermission(PERMISSION)) {
return;
}
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
final ItemStack held = event.getItem();
if (!ItemUtils.isValidItem(held) || !ItemUtils.areItemsSimilar(held, WAND)) {
return;
}
final Block block = Objects.requireNonNull(event.getClickedBlock());
final Location blockLocation = block.getLocation();
player.sendMessage(ChatColor.GREEN + "Debug start.");
player.sendMessage(ChatColor.AQUA + "Material: " + ChatColor.WHITE + block.getType().name());
player.sendMessage(ChatColor.AQUA + "Location: "
+ ChatColor.RED + "x:" + blockLocation.getBlockX() + ChatColor.WHITE + ", "
+ ChatColor.GREEN + "y:" + blockLocation.getBlockY() + ChatColor.WHITE + ", "
+ ChatColor.BLUE + "z:" + blockLocation.getBlockZ());
final BlockData data = block.getBlockData();
player.sendMessage(ChatColor.AQUA + "Block Data: " + ChatColor.YELLOW + data.getClass().getName());
player.sendMessage(data.toString());
player.sendMessage(ChatColor.RED + "Data end.");
event.setCancelled(true);
}
@EventHandler
public void onWandUsageOnEntity(final PlayerInteractEntityEvent event) {
final Player player = event.getPlayer();
if (!player.hasPermission(PERMISSION)) {
return;
}
final ItemStack held = player.getInventory().getItem(event.getHand());
if (!ItemUtils.isValidItem(held) || !ItemUtils.areItemsSimilar(held, WAND)) {
return;
}
final Entity entity = event.getRightClicked();
final Location entityLocation = entity.getLocation();
player.sendMessage(ChatColor.GREEN + "Debug start.");
player.sendMessage(ChatColor.AQUA + "Entity: " + ChatColor.WHITE + entity.getType().name());
player.sendMessage(ChatColor.AQUA + "Location: "
+ ChatColor.RED + "x:" + entityLocation.getX() + ChatColor.WHITE + ", "
+ ChatColor.GREEN + "y:" + entityLocation.getY() + ChatColor.WHITE + ", "
+ ChatColor.BLUE + "z:" + entityLocation.getZ());
player.sendMessage(ChatColor.AQUA + "Rotation: "
+ ChatColor.YELLOW + "p:" + entityLocation.getPitch() + ", "
+ ChatColor.GOLD + "y:" + entityLocation.getYaw());
final CompoundTag nbt = new CompoundTag();
((CraftEntity) entity).getHandle().save(nbt);
nbt.remove("Pos"); // Remove redundant position
nbt.remove("Rotation"); // Remove redundant rotation
player.sendMessage(nbt.toString());
player.sendMessage(ChatColor.RED + "Data end.");
event.setCancelled(true);
}
}
@@ -0,0 +1,69 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import java.util.List;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import vg.civcraft.mc.civmodcore.config.ConfigHelper;
import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI;
import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection;
import vg.civcraft.mc.civmodcore.players.settings.impl.BooleanSetting;
public class DogFacts extends BasicHack {
@AutoLoad
private List<String> announcements;
@AutoLoad
private String intervalTime;
private BooleanSetting disableAnnouncements;
private int counter = 0;
public DogFacts(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
}
@Override
public void onEnable() {
super.onEnable();
startRunnable(announcements);
initSettings();
}
public void startRunnable(List<String> announcements){
long interval = ConfigHelper.parseTimeAsTicks(intervalTime);
int tickOffset = (int) (Math.random() * (interval));
Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin ,() -> {
if (announcements.isEmpty()) {
return;
}
if (counter >= announcements.size()) {
counter = 0;
}
String message = announcements.get(counter);
plugin.info("Broadcasting DogFact #" + counter);
for (Player player : Bukkit.getOnlinePlayers()) {
if (getDisableAnnouncements(player.getUniqueId())){
continue;
}
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + player.getDisplayName() + " " + message);
}
counter++;
}, tickOffset, interval);
}
private void initSettings() {
MenuSection menu = plugin.getSettingManager().getMainMenu();
disableAnnouncements =
new BooleanSetting(plugin, false, "Disable Announcements", "disableAnnouncements",
"Disable Announcements?");
PlayerSettingAPI.registerSetting(disableAnnouncements, menu);
}
public boolean getDisableAnnouncements(UUID uuid) {
return disableAnnouncements.getValue(uuid);
}
}
@@ -0,0 +1,226 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.commons.lang3.ClassUtils;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.EventExecutor;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener;
public class EventDebugHack extends BasicHack {
private Map<Class<?>, List<RegisteredListener>> classToListeners;
private Map<Class<?>, HandlerList> classToHandler;
public EventDebugHack(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
classToListeners = new HashMap<>();
classToHandler = new HashMap<>();
}
@Override
public void registerCommands() {
plugin().registerCommand("debugevent", new CommandExecutor() {
@Override
public boolean onCommand(CommandSender sender, Command arg1, String arg2, String[] args) {
if (args.length == 0) {
sender.sendMessage(ChatColor.RED + "You must give an event");
return false;
}
if (untargetEvent(args[0])) {
sender.sendMessage(ChatColor.GREEN + "Disabled tracking for " + args[0]);
return true;
}
targetEvent(args[0], sender);
return true;
}
});
}
public boolean untargetEvent(String eventName) {
Class<? extends Event> eventClass = getEventClass(eventName);
if (eventClass == null) {
return false;
}
HandlerList handler = classToHandler.get(eventClass);
List<RegisteredListener> listeners = classToListeners.get(eventClass);
if (handler == null || listeners == null) {
return false;
}
listeners.forEach(handler::unregister);
classToHandler.remove(eventClass);
classToListeners.remove(eventClass);
return true;
}
@SuppressWarnings("unchecked")
private static Class<? extends Event> getEventClass(String eventName) {
if (eventName.startsWith(".")) {
eventName = "org.bukkit.event" + eventName;
}
try {
return (Class<Event>) Class.forName(eventName);
} catch (ClassCastException | ClassNotFoundException e) {
return null;
}
}
public void targetEvent(String eventName, CommandSender sender) {
Class<? extends Event> eventClass = getEventClass(eventName);
if (eventClass == null) {
sender.sendMessage(ChatColor.RED + "No event class with that path exists");
return;
}
if (classToHandler.containsKey(eventClass)) {
sender.sendMessage(ChatColor.RED + "Event is already being investigated");
return;
}
Method handlerGetter;
try {
handlerGetter = eventClass.getMethod("getHandlerList");
} catch (NoSuchMethodException | SecurityException e) {
sender.sendMessage(ChatColor.RED + "The class did not have a handler getter");
return;
}
if (handlerGetter.getReturnType() != HandlerList.class) {
sender.sendMessage(ChatColor.RED + "Getter had wrong return type");
return;
}
HandlerList handlerList;
try {
handlerList = (HandlerList) handlerGetter.invoke(eventClass);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
sender.sendMessage(ChatColor.RED + "Could not invoke handler getter");
return;
}
sender.sendMessage(
ChatColor.GOLD + "Currently registered listeners for " + eventClass.getSimpleName() + " are:");
for (RegisteredListener listener : handlerList.getRegisteredListeners()) {
for (Method method : listener.getListener().getClass().getMethods()) {
if (method.isBridge() || method.isSynthetic()) {
continue;
}
if (Modifier.isStatic(method.getModifiers())) {
continue;
}
if (!method.isAnnotationPresent(EventHandler.class)) {
continue;
}
if (method.getParameterCount() != 1) {
continue;
}
Class<?> clazz = method.getParameterTypes()[0];
if (clazz != eventClass) {
continue;
}
sender.sendMessage(String.format("[%s] on %s is %s", listener.getPlugin().getName(),
listener.getPriority(), listener.getListener().getClass().getSimpleName()));
}
}
Listener fakeListener = new Listener() {
};
// need to hack our listener in, because can't dynamically declare an actual
// class with annotations etc.
List<RegisteredListener> listeners = new LinkedList<>();
for (EventPriority prio : EventPriority.values()) {
RegisteredListener listener = new RegisteredListener(fakeListener, new EventExecutor() {
@Override
public void execute(Listener arg0, Event arg1) throws EventException {
printEventState(prio, arg1);
}
}, prio, SimpleAdminHacks.instance(), false);
listeners.add(listener);
handlerList.register(listener);
}
classToHandler.put(eventClass, handlerList);
classToListeners.put(eventClass, listeners);
}
private void printEventState(EventPriority prio, Event event) {
StringBuilder sb = new StringBuilder();
sb.append("At stage " + prio.toString() + " " + event.getEventName() + " is as follows: \n");
deepInspectObject(event, "event", sb, 0, new HashSet<>());
SimpleAdminHacks.instance().getLogger().info(sb.toString());
}
private static void deepInspectObject(Object o, String fieldName, StringBuilder sb, int depth, Set<Object> seen) {
String pre = new String(new char[depth * 2]).replace("\0", "-") + " ";
if (o == null) {
sb.append(pre + fieldName + ": null\n");
return;
}
if (ClassUtils.isPrimitiveOrWrapper(o.getClass()) || straightPrint(o)) {
sb.append(pre + o.getClass().getSimpleName() + " " + fieldName + " = " + o.toString() + '\n');
return;
}
sb.append(pre + o.getClass().getSimpleName() + " " + fieldName + ":");
if (seen.contains(o)) {
sb.append(pre + "omitted\n");
return;
} else {
sb.append('\n');
}
seen.add(o);
for (Field field : getAllFields(new LinkedList<>(), o.getClass())) {
field.setAccessible(true);
Object member;
try {
member = field.get(o);
} catch (IllegalArgumentException | IllegalAccessException e) {
SimpleAdminHacks.instance().getLogger()
.severe("Failed to get member in deep inspection " + e.getMessage());
continue;
}
deepInspectObject(member, field.getName(), sb, depth + 1, seen);
}
}
public static List<Field> getAllFields(List<Field> fields, Class<?> type) {
fields.addAll(Arrays.asList(type.getDeclaredFields()));
if (type.getSuperclass() != null) {
getAllFields(fields, type.getSuperclass());
}
return fields;
}
private static boolean straightPrint(Object o) {
return o instanceof Entity || o instanceof String || o instanceof World || o instanceof Plugin
|| o instanceof Collection || o instanceof Map || o instanceof Server || o instanceof Location
|| o instanceof Block || o.getClass().isEnum() || o instanceof HandlerList || o instanceof Random
|| o instanceof Logger || o instanceof ItemStack;
}
}
@@ -0,0 +1,181 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.BukkitCommandCompletionContext;
import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Syntax;
import com.google.common.base.Strings;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
import vg.civcraft.mc.civmodcore.commands.TabComplete;
public final class EventHandlerList extends BasicHack {
private final CommandManager commands;
private final HandlersList handlers;
public EventHandlerList(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
this.handlers = new HandlersList();
this.commands = new CommandManager(plugin) {
@Override
public void registerCommands() {
registerCommand(new HandlersCommand(handlers));
}
};
}
@Override
public void onEnable() {
super.onEnable();
this.commands.init();
this.plugin.registerListener(this.handlers);
}
@Override
public void onDisable() {
HandlerList.unregisterAll(this.handlers);
this.commands.reset();
super.onDisable();
}
// ------------------------------------------------------------
// Handlers Command
// ------------------------------------------------------------
@CommandPermission("simpleadmin.eventdebug")
private static final class HandlersCommand extends BaseCommand {
private final HandlersList handlers;
public HandlersCommand(final HandlersList handlers) {
this.handlers = handlers;
}
@CommandAlias("handlers|handler|listeners|listener")
@Description("Gets the details for a handler.")
@Syntax("<handler>")
@CommandCompletion("@handlers")
public void onListHandlers(final CommandSender sender, @Single final String handler) {
if (Strings.isNullOrEmpty(handler)) {
throw new InvalidCommandArgument("Please enter an event handler.");
}
sender.sendMessage(ChatColor.GOLD + "Getting handlers for: " + ChatColor.WHITE + handler);
for (final Map.Entry<Class<? extends Event>, Map<Plugin, Set<Class<? extends Listener>>>> entry :
this.handlers.getHandlerCache().entrySet()) {
if (!StringUtils.equals(entry.getKey().getName(), handler)) {
continue;
}
for (final Map.Entry<Plugin, Set<Class<? extends Listener>>> details : entry.getValue().entrySet()) {
sender.sendMessage(ChatColor.LIGHT_PURPLE + details.getKey().getName() + ":");
for (final Class<? extends Listener> clazz : details.getValue()) {
sender.sendMessage(" - " + clazz.getName());
}
}
}
sender.sendMessage(ChatColor.AQUA + "End of handlers.");
}
@TabComplete("handlers")
public List<String> tabCompleteEvents(final BukkitCommandCompletionContext context) {
final List<String> results = new ArrayList<>();
for (final Class<? extends Event> clazz : this.handlers.getHandlerCache().keySet()) {
final String path = clazz.getName();
if (!StringUtils.startsWith(path, context.getInput())) {
continue;
}
results.add(path);
}
return results;
}
}
// ------------------------------------------------------------
// Handlers List
// ------------------------------------------------------------
private static final class HandlersList implements Listener {
private final Map<Class<? extends Event>, Map<Plugin, Set<Class<? extends Listener>>>> handlers;
private boolean handlersCollected;
public HandlersList() {
this.handlers = new HashMap<>();
this.handlersCollected = false;
}
@SuppressWarnings("unchecked")
public Map<Class<? extends Event>, Map<Plugin, Set<Class<? extends Listener>>>> getHandlerCache() {
if (!this.handlersCollected) {
this.handlers.clear();
for (final HandlerList handlers : HandlerList.getHandlerLists()) {
for (final RegisteredListener listener : handlers.getRegisteredListeners()) {
for (final Method method : listener.getListener().getClass().getMethods()) {
if (method.isBridge() || method.isSynthetic()) {
continue;
}
if (Modifier.isStatic(method.getModifiers())) {
continue;
}
if (!method.isAnnotationPresent(EventHandler.class)) {
continue;
}
if (method.getParameterCount() != 1) {
continue;
}
final Class<?> clazz = method.getParameterTypes()[0];
if (!Event.class.isAssignableFrom(clazz)) {
continue;
}
this.handlers.
computeIfAbsent((Class<? extends Event>) clazz, (k) -> new HashMap<>()).
computeIfAbsent(listener.getPlugin(), (k) -> new HashSet<>()).
add(listener.getListener().getClass());
}
}
}
this.handlersCollected = true;
}
return this.handlers;
}
@EventHandler
public void onPluginEnable(final PluginEnableEvent event) {
this.handlersCollected = false;
}
@EventHandler
public void onPluginDisable(final PluginDisableEvent event) {
this.handlersCollected = false;
}
}
}
@@ -0,0 +1,63 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityBreedEvent;
import java.util.logging.Level;
public class FasterHorses extends BasicHack {
@AutoLoad
private double minSpeed;
@AutoLoad
private double maxSpeed;
public FasterHorses(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
}
@EventHandler(priority = EventPriority.HIGH)
public void on(EntityBreedEvent event) {
if (event.getEntity().getType() != EntityType.HORSE) {
return;
}
double dadSpeed = event.getFather().getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).getBaseValue();
double mumSpeed = event.getMother().getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).getBaseValue();
double irwinHallDist = (Math.random() * 0.3 + Math.random() * 0.3 + Math.random() * 0.3) * ((this.maxSpeed - this.minSpeed) / 0.9) + this.minSpeed;
double newSpeed = (dadSpeed + mumSpeed + irwinHallDist) / 3;
if (newSpeed < minSpeed) {
newSpeed = minSpeed;
} else if (newSpeed > maxSpeed) {
newSpeed = maxSpeed;
}
event.getEntity().getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(newSpeed);
plugin.getLogger().log(Level.INFO, "Horse breed to have speed: " + newSpeed);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void on(CreatureSpawnEvent event) {
if (event.getEntity().getType() != EntityType.HORSE) {
return;
}
if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.BREEDING) {
return;
}
AttributeInstance moveSpeed = event.getEntity().getAttribute(Attribute.GENERIC_MOVEMENT_SPEED);
if (moveSpeed == null) {
return;
}
double irwinHallDist = (Math.random() * 0.3 + Math.random() * 0.3 + Math.random() * 0.3) * ((this.maxSpeed - this.minSpeed) / 0.9) + this.minSpeed;
moveSpeed.setBaseValue(irwinHallDist);
plugin.getLogger().log(Level.INFO, "Setting Horse Speed to: " + irwinHallDist);
}
}
@@ -0,0 +1,172 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.destroystokyo.paper.event.player.PlayerJumpEvent;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.DataParser;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.TeleportUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;
import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI;
import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection;
import vg.civcraft.mc.civmodcore.players.settings.impl.BooleanSetting;
import vg.civcraft.mc.civmodcore.utilities.DoubleInteractFixer;
public class GoldBlockElevators extends BasicHack {
@AutoLoad(processor = DataParser.MATERIAL)
private Material elevatorBlock;
private BooleanSetting useJumpSneakTP;
private DoubleInteractFixer interactFixer;
private String blockName;
public GoldBlockElevators(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
this.interactFixer = new DoubleInteractFixer(plugin);
}
@Override
public void onEnable() {
super.onEnable();
if (elevatorBlock == null)
elevatorBlock = Material.GOLD_BLOCK;
this.blockName = ItemUtils.getItemName(this.elevatorBlock).toLowerCase();
if (this.blockName.startsWith("block of"))
this.blockName = this.blockName.substring("block of ".length());
initSettings();
}
private void initSettings() {
MenuSection mainMenu = plugin.getSettingManager().getMainMenu();
String name = String.format("Use Jump or Sneak to teleport on %s blocks", this.blockName);
String description = String.format("When true, jumping or sneaking on %s blocks will teleport you up/down, when false, right or left clicking will do the same.",
this.blockName);
this.useJumpSneakTP = new BooleanSetting(plugin, true, name, "jumpOrSneakTp", description);
PlayerSettingAPI.registerSetting(this.useJumpSneakTP, mainMenu);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void goldBlockSneak(PlayerToggleSneakEvent event) {
if (!this.useJumpSneakTP.getValue(event.getPlayer())) {
return;
}
Block below = event.getPlayer().getLocation().getBlock().getRelative(BlockFace.DOWN);
if (below.getType() != elevatorBlock) {
return;
}
if (!event.isSneaking()) {
return;
}
for (int y = (below.getY() - 1); y > below.getWorld().getMinHeight(); y--) {
if (doTeleport(below, event.getPlayer(), y)) {
return;
}
}
String message = String.format("No %s block to teleport you down to. Jump to teleport up instead.", this.blockName);
event.getPlayer().sendMessage(Component.text(message).color(NamedTextColor.RED));
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void goldBlockJump(PlayerJumpEvent event) {
if (!this.useJumpSneakTP.getValue(event.getPlayer())) {
return;
}
Block below = event.getFrom().getBlock().getRelative(BlockFace.DOWN);
if (below.getType() != elevatorBlock) {
return;
}
for (int y = below.getY() + 1; y <= below.getWorld().getMaxHeight(); y++) {
if (doTeleport(below, event.getPlayer(), y)) {
return;
}
}
String message = String.format("No %s block to teleport you up to. Sneak to teleport down instead.", this.blockName);
event.getPlayer().sendMessage(Component.text(message).color(NamedTextColor.RED));
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void goldBlockInteract(PlayerInteractEvent event) {
if (this.useJumpSneakTP.getValue(event.getPlayer())) {
return;
}
if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
if (interactFixer.checkInteracted(event.getPlayer(), event.getClickedBlock())) {
return;
}
} else if (event.getAction() != Action.LEFT_CLICK_BLOCK) {
return;
}
Block below = event.getPlayer().getLocation().getBlock().getRelative(BlockFace.DOWN);
if (below.getType() != elevatorBlock) {
return;
}
if (event.getClickedBlock() == null) {
return;
}
if (!event.getClickedBlock().equals(below)) {
return;
}
if (event.getAction() == Action.LEFT_CLICK_BLOCK) {
for (int y = below.getY() - 1; y > below.getWorld().getMinHeight(); y--) {
if (doTeleport(below, event.getPlayer(), y)) {
return;
}
}
String message = String.format("No %s block to teleport you down to. Right click to teleport up instead.", this.blockName);
event.getPlayer().sendMessage(Component.text(message).color(NamedTextColor.RED));
} else {
for (int y = below.getY() + 1; y <= below.getWorld().getMaxHeight(); y++) {
if (doTeleport(below, event.getPlayer(), y)) {
return;
}
}
String message = String.format("No %s block to teleport you up to. Left click to teleport down instead.", this.blockName);
event.getPlayer().sendMessage(Component.text(message).color(NamedTextColor.RED));
}
}
private boolean doTeleport(Block source, Player player, int y) {
Block target = source.getWorld().getBlockAt(source.getX(), y, source.getZ());
if (target.getType() != elevatorBlock) {
return false;
}
if (!TeleportUtil.checkForTeleportSpace(target.getRelative(BlockFace.UP).getLocation())) {
return false;
}
source.getWorld().playSound(source.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0F, 8.0F);
Location adjustedLocation = target.getLocation().clone();
adjustedLocation.add(0.5, 1.02, 0.5);
adjustedLocation.setYaw(player.getLocation().getYaw());
adjustedLocation.setPitch(player.getLocation().getPitch());
player.playSound(adjustedLocation, Sound.ENTITY_ENDERMAN_TELEPORT, 1.0F, 8.0F);
player.teleport(adjustedLocation);
return true;
}
}
@@ -0,0 +1,411 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.World.Environment;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.Levelled;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.CauldronLevelChangeEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntityPotionEffectEvent;
import org.bukkit.event.entity.EntityPotionEffectEvent.Cause;
import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.event.entity.SheepDyeWoolEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerItemConsumeEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType;
import vg.civcraft.mc.civmodcore.world.WorldUtils;
public class HumbugBatchOne extends BasicHack {
@AutoLoad
private boolean allowSheepDying;
@AutoLoad
private boolean disableGapples;
@AutoLoad
private boolean allowUsingAnvils;
@AutoLoad
private boolean allowUsingEnchantingTables;
@AutoLoad
private boolean infiniteCauldrons;
@AutoLoad
private boolean disableEnderDragon;
@AutoLoad
private boolean disableIronFarms;
@AutoLoad
private boolean disableEnderCrystalDamage;
@AutoLoad
private boolean disableMiningFatigue;
@AutoLoad
private boolean canEquipBanners;
@AutoLoad
private boolean disableLavaCobbleMountains;
@AutoLoad
private boolean disableWanderingTrader;
@AutoLoad
private boolean preventPearlGlitching;
@AutoLoad
private boolean preventUsingEyeOfEnder;
@AutoLoad
private boolean disableEndGatewayTP;
@AutoLoad
private boolean disablePiglins;
public HumbugBatchOne(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onDyeWool(SheepDyeWoolEvent event) {
if (!allowSheepDying) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onDisallowedBlockUse(PlayerInteractEvent event) {
if (event.getClickedBlock() == null) {
return;
}
Material mat = event.getClickedBlock().getType();
if (!allowUsingEnchantingTables
&& (mat == Material.ANVIL || mat == Material.CHIPPED_ANVIL || mat == Material.DAMAGED_ANVIL)) {
event.setCancelled(true);
return;
}
if (!allowUsingEnchantingTables && mat == Material.ENCHANTING_TABLE) {
event.setCancelled(true);
}
}
@EventHandler
public void disableGapples(PlayerItemConsumeEvent event) {
if(!disableGapples) {
return;
}
if(event.getItem().getType() == Material.ENCHANTED_GOLDEN_APPLE) {
event.setCancelled(true);
event.getPlayer().sendMessage(ChatColor.RED + "You are not allowed to eat Golden Apples.");
}
}
@EventHandler
public void cauldronEmpty(CauldronLevelChangeEvent e) {
if (infiniteCauldrons && e.getReason() == CauldronLevelChangeEvent.ChangeReason.BOTTLE_FILL) {
e.setNewLevel(e.getOldLevel());
}
}
@EventHandler
public void preventEndCrystalUsage(PlayerInteractEvent e) {
if (!disableEnderDragon) {
return;
}
if (e.getItem() == null) {
return;
}
Player p = e.getPlayer();
Environment env = p.getWorld().getEnvironment();
if (!(env == Environment.THE_END)) {
return;
}
if (e.getAction() == Action.RIGHT_CLICK_BLOCK && e.getItem().getType() == Material.END_CRYSTAL) {
e.setCancelled(true);
e.getPlayer().sendMessage(ChatColor.RED + "Sorry, placing end crystals is disabled in this world!");
}
}
@EventHandler
public void gatewayTP(PlayerTeleportEvent e) {
if (!disableEndGatewayTP) {
return;
}
if (e.getCause() == TeleportCause.END_GATEWAY) {
e.setCancelled(true);
e.getPlayer().sendMessage(ChatColor.RED + "Sorry, these are disabled");
}
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void golemDeath(EntityDeathEvent e) {
if (!disableIronFarms) {
return;
}
if (e.getEntity().getType() != EntityType.IRON_GOLEM) {
return;
}
Iterator<ItemStack> iter = e.getDrops().iterator();
while (iter.hasNext()) {
ItemStack is = iter.next();
if (is.getType() == Material.IRON_INGOT) {
iter.remove();
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void disableEnderCrystal(EntityDamageByEntityEvent e) {
if (disableEnderCrystalDamage && e.getDamager().getType() == EntityType.ENDER_CRYSTAL) {
e.setCancelled(true);
}
}
@EventHandler
public void adminAccessBlockedChest(PlayerInteractEvent e) {
if (!e.getPlayer().hasPermission("simpleadmin.chestsee") && !e.getPlayer().isOp()) {
return;
}
if (e.getAction() != Action.RIGHT_CLICK_AIR && e.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
Player p = e.getPlayer();
Set<Material> s = new TreeSet<>();
s.add(Material.AIR);
s.add(Material.OBSIDIAN); // probably in a vault
List<Block> blocks = p.getLineOfSight(s, 8);
for (Block b : blocks) {
Material m = b.getType();
if (m == Material.CHEST || m == Material.TRAPPED_CHEST) {
if (b.getRelative(BlockFace.UP).getType().isOccluding()) {
// dont show inventory twice if a normal chest is opened
final Inventory chestInv = ((InventoryHolder) b.getState()).getInventory();
p.openInventory(chestInv);
p.updateInventory();
}
break;
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void potionEffect(EntityPotionEffectEvent event) {
if (!disableMiningFatigue) {
return;
}
if (event.getAction() == org.bukkit.event.entity.EntityPotionEffectEvent.Action.REMOVED || event.getCause() == Cause.PLUGIN) {
return;
}
if (event.getModifiedType() == PotionEffectType.SLOW_DIGGING) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void equipBanner(PlayerInteractEvent event) {
if (!canEquipBanners) {
return;
}
if (event.getItem() == null || !Tag.BANNERS.isTagged(event.getItem().getType()) // API
|| (event.getAction() != Action.LEFT_CLICK_AIR && event.getAction() != Action.LEFT_CLICK_BLOCK)) {
return;
}
Player player = event.getPlayer();
ItemStack banner = new ItemStack(event.getItem());
banner.setAmount(1);
player.getInventory().removeItem(banner);
if (player.getEquipment().getHelmet() != null) {
if (player.getInventory().addItem(player.getEquipment().getHelmet()).size() != 0) {
player.getWorld().dropItem(player.getLocation(), player.getEquipment().getHelmet());
}
}
player.getEquipment().setHelmet(banner);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void tradeWanderer(PlayerInteractEntityEvent event) {
if (!disableWanderingTrader) {
return;
}
if (event.getRightClicked().getType() == EntityType.WANDERING_TRADER) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onTeleport(PlayerTeleportEvent event) {
if (!preventPearlGlitching || event.getCause() != TeleportCause.ENDER_PEARL) {
return;
}
Location to = event.getTo();
// From and To are feet positions. Check and make sure we can teleport to a
// location with air
// above the To location.
Block toBlock = to.getBlock();
Block aboveBlock = toBlock.getRelative(BlockFace.UP);
Block belowBlock = toBlock.getRelative(BlockFace.DOWN);
boolean lowerBlockBypass = false;
double height = 0.0;
Material mat = toBlock.getType();
if (Tag.SLABS.isTagged(mat)) {
lowerBlockBypass = true;
height = 0.5;
}
else if (Tag.BEDS.isTagged(mat)) {
height = 0.562;
}
else if (Tag.FLOWER_POTS.isTagged(mat)) {
height = 0.375;
}
else switch (mat) {
case CHEST:
case TRAPPED_CHEST:
case ENDER_CHEST:
height = 0.875;
break;
case LILY_PAD:
height = 0.016;
break;
case ENCHANTING_TABLE:
height = 0.016;
break;
case PLAYER_WALL_HEAD:
case PLAYER_HEAD:
height = 0.5;
break;
}
// Check if the below block is difficult
// This is added because if you face downward directly on a gate, it will
// teleport your feet INTO the gate, thus bypassing the gate until you leave
// that block.
Material belowMat = belowBlock.getType();
if (Tag.FENCE_GATES.isTagged(belowMat) || Tag.FENCES.isTagged(belowMat) || Tag.WALLS.isTagged(belowMat)) {
height = 0.5;
}
boolean upperBlockBypass = false;
if (height >= 0.5) {
Block aboveHeadBlock = aboveBlock.getRelative(BlockFace.UP);
if (!aboveHeadBlock.getType().isSolid()) {
height = 0.5;
} else {
upperBlockBypass = true; // Cancel this event. What's happening is the user is going to get stuck due to
// the height.
}
}
// Normalize teleport to the center of the block. Feet ON the ground, plz.
// Leave Yaw and Pitch alone
to.setX(Math.floor(to.getX()) + 0.5);
to.setY(Math.floor(to.getY()) + height);
to.setZ(Math.floor(to.getZ()) + 0.5);
if (aboveBlock.getType().isSolid() || (toBlock.getType().isSolid() && !lowerBlockBypass) || upperBlockBypass) {
boolean bypass = false;
if ((to.getWorld().getEnvironment() == Environment.NETHER) && (to.getBlockY() > 124) && (to.getBlockY() < 129)) {
bypass = true;
}
if (!bypass) {
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
public void throwEyeOfEnder(PlayerInteractEvent pie) {
if (!preventUsingEyeOfEnder) {
return;
}
if (pie.getAction() != Action.RIGHT_CLICK_AIR && pie.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
if (pie.getItem() != null && pie.getItem().getType() == Material.ENDER_EYE) {
pie.setCancelled(true);
pie.getPlayer().sendMessage(ChatColor.RED + "Throwing Eyes of Ender is disabled.");
}
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void lavaToCobble(BlockPhysicsEvent e) {
if (!disableLavaCobbleMountains) {
return;
}
if (e.getBlock().getType() != Material.LAVA) {
return;
}
if (isLavaSourceNear(e.getBlock(), 3)) {
return;
}
boolean foundWater = false;
for (Block block : WorldUtils.getAllBlockSides(e.getBlock(), true)) {
if (block.getType() == Material.WATER) {
foundWater = true;
break;
}
}
if (foundWater) {
Bukkit.getScheduler().runTask(SimpleAdminHacks.instance(), () -> e.getBlock().setType(Material.AIR));
}
}
private static boolean isLavaSourceNear(Block block, int ttl) {
if (ttl <= 0) {
return false;
}
if (block.getType() != Material.LAVA) {
return false;
}
if (((Levelled) block.getBlockData()).getLevel() == 0) {
// source block
return true;
}
for (Block relative : WorldUtils.getAllBlockSides(block, true)) {
if (isLavaSourceNear(relative, ttl - 1)) {
return true;
}
}
return false;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onBartering(EntitySpawnEvent event) {
if (disablePiglins && event.getEntityType() == EntityType.PIGLIN) {
event.setCancelled(true);
}
}
}
@@ -0,0 +1,67 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import net.kyori.adventure.text.Component;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.inventory.ItemStack;
import vg.civcraft.mc.civmodcore.chat.ChatUtils;
public class ItemMetaConverterHack extends BasicHack {
public ItemMetaConverterHack(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void convertOnInventoryOpen(final InventoryOpenEvent event) {
final var inventory = event.getInventory();
if (inventory.getHolder() == null) {
return; // GUI
}
for (final ItemStack item : inventory.getStorageContents()) {
processItem(item);
}
}
public static void processItem(final ItemStack item) {
if (item == null) {
return;
}
final var meta = item.getItemMeta();
if (meta == null) {
return;
}
boolean displayNameWasChanged = false;
if (meta.hasDisplayName()) {
final var currentDisplayName = meta.displayName();
assert currentDisplayName != null;
if (!ChatUtils.isBaseComponent(currentDisplayName)) {
meta.displayName(Component.text().append(currentDisplayName).build());
displayNameWasChanged = true;
}
}
boolean loreWasChanged = false;
if (meta.hasLore()) {
final var currentLore = meta.lore();
assert currentLore != null;
for (int i = 0, l = currentLore.size(); i < l; i++) {
final var loreLine = currentLore.get(i);
if (!ChatUtils.isBaseComponent(loreLine)) {
currentLore.set(i, Component.text().append(loreLine).build());
loreWasChanged = true;
}
}
if (loreWasChanged) {
meta.lore(currentLore);
}
}
if (displayNameWasChanged || loreWasChanged) {
item.setItemMeta(meta);
}
}
}
@@ -0,0 +1,129 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.DataParser;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import vg.civcraft.mc.civmodcore.entities.EntityUtils;
import vg.civcraft.mc.civmodcore.inventory.items.SpawnEggUtils;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class MobCondenser extends BasicHack
{
private Random rng;
private Map<EntityType, Double> mobSpawnModifiers;
@AutoLoad(processor = DataParser.MATERIAL)
private List<Material> materialModificationWhitelist;
public MobCondenser(SimpleAdminHacks plugin, BasicHackConfig config)
{
super(plugin, config);
this.rng = new Random();
this.mobSpawnModifiers = new HashMap<>();
}
@Override
public void onEnable()
{
super.onEnable();
final ConfigurationSection base = this.config.getBase();
final ConfigurationSection spawnModifiers = base.getConfigurationSection("mobSpawnModifiers");
if (spawnModifiers != null) {
for (final String key : spawnModifiers.getKeys(false)) {
final EntityType type = EntityUtils.getEntityType(key);
if (type == null) {
this.plugin.warning("[" + getClass().getSimpleName() + "] EntityType: \"" + key + "\" does not exist, skipping.");
continue;
}
double modifier = spawnModifiers.getDouble(key, 1.0d);
if (modifier < 0.0d) {
this.plugin.warning("[" + getClass().getSimpleName() + "] Mob Spawn Modifier: \"" + modifier + "\" for \"" + key + "\" is unsupported, defaulting to 1.0");
modifier = 1.0d;
}
this.mobSpawnModifiers.put(type, modifier);
this.plugin.info("[" + getClass().getSimpleName() + "] Registered Mob Spawn Modifier: [" + type + ": " + modifier + "]");
}
}
}
@Override
public void onDisable()
{
this.mobSpawnModifiers.clear();
super.onDisable();
}
private boolean roll(double chance) {
return rng.nextDouble() <= chance;
}
@EventHandler(priority = EventPriority.LOW) //Run before the PortalSpawnModifier
public void onCreatureSpawn(CreatureSpawnEvent e) {
if (mobSpawnModifiers.containsKey(e.getEntityType())) {
if (!roll(mobSpawnModifiers.get(e.getEntityType()))) {
if (e.getSpawnReason() != CreatureSpawnEvent.SpawnReason.SPAWNER_EGG) {
e.setCancelled(true);
}
else if (e.getEntity() instanceof Ageable) // Is from a spawn egg, and is Ageable
{
Ageable ageable = (Ageable) e.getEntity();
if (!ageable.isAdult()) { //Only spawns from right clicking other mobs with a spawn egg
e.setCancelled(true);
}
}
}
}
}
@EventHandler
public void onMobEggUse(PlayerInteractEvent e) {
if (e.getAction() == Action.RIGHT_CLICK_BLOCK && e.getItem() != null) {
try {
EntityType type = SpawnEggUtils.getEntityType(e.getItem().getType());
if (mobSpawnModifiers.containsKey(type)) {
if (!roll(mobSpawnModifiers.get(type))) {
e.setCancelled(true);
e.getItem().setAmount(e.getItem().getAmount() -1);
}
}
} catch (IllegalArgumentException ignored) {}
}
}
@EventHandler(priority = EventPriority.HIGH) //Run after PortalSpawnModifier
public void onEntityDeath(EntityDeathEvent e) {
if (mobSpawnModifiers.containsKey(e.getEntityType())) {
Iterator<ItemStack> iterator = e.getDrops().iterator();
while (iterator.hasNext()) {
ItemStack itemStack = iterator.next();
if (materialModificationWhitelist.contains(itemStack.getType())) {
itemStack.setAmount((int) Math.round(itemStack.getAmount() / mobSpawnModifiers.get(e.getEntityType())));
}
}
}
}
}
@@ -0,0 +1,657 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.PacketManager;
import java.lang.reflect.Field;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.logging.Level;
import net.minecraft.world.inventory.EnchantmentMenu;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.ThrownExpBottle;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockExpEvent;
import org.bukkit.event.enchantment.EnchantItemEvent;
import org.bukkit.event.enchantment.PrepareItemEnchantEvent;
import org.bukkit.event.entity.EntityBreedEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.ExpBottleEvent;
import org.bukkit.event.inventory.FurnaceExtractEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryCreativeEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.inventory.PrepareAnvilEvent;
import org.bukkit.event.player.PlayerExpChangeEvent;
import org.bukkit.event.player.PlayerFishEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.AnvilInventory;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Merchant;
import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.Repairable;
import org.bukkit.projectiles.ProjectileSource;
import vg.civcraft.mc.civmodcore.entities.EntityUtils;
import vg.civcraft.mc.civmodcore.inventory.InventoryUtils;
import vg.civcraft.mc.civmodcore.inventory.RecipeManager;
import vg.civcraft.mc.civmodcore.inventory.items.ItemMap;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;
import vg.civcraft.mc.civmodcore.world.WorldUtils;
public final class OldEnchanting extends BasicHack {
private static final ItemStack LAPIS_ITEM = new ItemStack(Material.LAPIS_LAZULI, 64);
private static final ItemStack EMERALD_ITEM = new ItemStack(Material.EMERALD, 1);
private static final Random RANDOM = new SecureRandom();
private final PacketManager packets;
private final ShapelessRecipe emeraldToExp;
private final ShapedRecipe expToEmerald;
private final Field enchantingTableRandomiser;
private final Map<EntityType, Double> entityExpDropModifiers;
@AutoLoad
private boolean hideEnchants;
@AutoLoad
private boolean fillLapis;
@AutoLoad
private boolean randomiseEnchants;
@AutoLoad
private boolean fixEnchantCosts;
@AutoLoad
private double experienceModifier;
@AutoLoad
private double lootModifier;
@AutoLoad
private boolean emeraldCrafting;
@AutoLoad
private boolean emeraldLeveling;
@AutoLoad
private boolean disableGrindExp;
@AutoLoad
private boolean preventOrbExp;
@AutoLoad
private boolean directBottleExp;
@AutoLoad
private int maxRepairCost;
@AutoLoad
private int expPerBottle;
@AutoLoad
private boolean allowExpRecovery;
@AutoLoad
private boolean disableEnchantedBookCreation;
@AutoLoad
private boolean disableEnchantedBookUsage;
public OldEnchanting(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
// Setup packet manager
this.packets = new PacketManager();
// Recipe that crafts Bottles o' Enchanting from Emeralds
this.emeraldToExp = new ShapelessRecipe(
new NamespacedKey(plugin, "emeraldToBottle"),
new ItemStack(Material.EXPERIENCE_BOTTLE, 9));
this.emeraldToExp.addIngredient(Material.EMERALD);
// Recipe that crafts Emeralds from Bottles o' Enchanting
this.expToEmerald = new ShapedRecipe(
new NamespacedKey(plugin, "bottleToEmerald"),
EMERALD_ITEM);
this.expToEmerald.shape("xxx", "xxx", "xxx");
this.expToEmerald.setIngredient('x', Material.EXPERIENCE_BOTTLE);
// Setup enchantment randomiser
Field randomiser = null;
try {
randomiser = FieldUtils.getDeclaredField(EnchantmentMenu.class, "p", true);
}
catch (final Throwable throwable) {
this.logger.log(Level.WARNING, "An exception was thrown while trying to reflect the enchanting " +
"table's randomiser field.", throwable);
}
this.enchantingTableRandomiser = randomiser;
// Setup entity xp modifiers
this.entityExpDropModifiers = new HashMap<>();
}
@Override
public void onEnable() {
super.onEnable();
if (this.experienceModifier < 0.0d) {
this.logger.warning("[OldEnchanting] Experience modifier [" + this.experienceModifier + "] " +
"is unsupported, defaulting to 0.2");
this.experienceModifier = 0.2d;
}
if (this.lootModifier < 0.0d) {
this.logger.warning("[OldEnchanting] Loot modifier [" + this.lootModifier + "] " +
"is unsupported, defaulting to 1.5");
this.lootModifier = 1.5d;
}
if (this.maxRepairCost < 2 && this.maxRepairCost != -1) {
this.logger.warning("[OldEnchanting] Maximum repair cost [" + this.maxRepairCost + "] " +
"is unsupported, defaulting to 33");
this.maxRepairCost = 33;
}
if (this.expPerBottle < -1) {
this.logger.warning("[OldEnchanting] Experience per bottle [" + this.expPerBottle + "] " +
"is unsupported, defaulting to 10");
this.expPerBottle = 10;
}
{ // @AutoLoad is not sophisticated enough to handle maps
final ConfigurationSection base = this.config.getBase();
final ConfigurationSection modifiers = base.getConfigurationSection("entityExpDropModifiers");
if (modifiers != null) {
for (final String key : modifiers.getKeys(false)) {
final EntityType type = EntityUtils.getEntityType(key);
if (type == null) {
this.logger.warning("[OldEnchanting] EntityType [" + key + "] does not exist, skipping.");
continue;
}
double modifier = modifiers.getDouble(key, 1.0d);
if (modifier < 0.0d) {
this.logger.warning("[OldEnchanting] Experience modifier [" + modifier + "] for " +
"[" + key + "] is unsupported, defaulting to 1.0");
modifier = 1.0d;
}
this.entityExpDropModifiers.put(type, modifier);
}
}
}
if (this.hideEnchants) {
this.packets.addAdapter(new PacketAdapter(this.plugin, PacketType.Play.Server.WINDOW_DATA) {
@Override
public void onPacketSending(final PacketEvent event) {
final InventoryType type = event.getPlayer().getOpenInventory().getType();
if (type == InventoryType.ENCHANTING) {
final PacketContainer packet = event.getPacket();
final int property = packet.getIntegers().read(1);
switch (property) {
case 3:
case 4:
case 5:
case 6:
packet.getIntegers().write(2, -1);
break;
}
}
}
});
}
if (this.emeraldCrafting) {
RecipeManager.registerRecipe(this.emeraldToExp);
RecipeManager.registerRecipe(this.expToEmerald);
}
}
@Override
public void onDisable() {
if (this.fillLapis) {
for (final Player player : Bukkit.getOnlinePlayers()) {
final InventoryView inventory = player.getOpenInventory();
if (inventory.getType() != InventoryType.ENCHANTING) {
continue;
}
inventory.setItem(1, null);
}
}
this.entityExpDropModifiers.clear();
this.packets.removeAllAdapters();
if (this.emeraldCrafting) {
RecipeManager.removeRecipe(this.emeraldToExp);
RecipeManager.removeRecipe(this.expToEmerald);
}
super.onDisable();
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onEntityDeath(final EntityDeathEvent event) {
if (this.disableGrindExp) {
event.setDroppedExp(0);
return;
}
final LivingEntity entity = event.getEntity();
final EntityType entityType = entity.getType();
int experience = event.getDroppedExp();
if (this.entityExpDropModifiers.containsKey(entityType)) {
experience = applyModifier(experience, this.entityExpDropModifiers.get(entityType));
}
else if (entityType != EntityType.PLAYER) {
experience = applyModifier(experience, this.experienceModifier);
}
if (entityType != EntityType.PLAYER) {
final Player killer = entity.getKiller();
if (killer != null) {
final ItemStack held = killer.getInventory().getItemInMainHand();
if (ItemUtils.isValidItem(held) && held.hasItemMeta()) {
final ItemMeta meta = held.getItemMeta();
if (meta.hasEnchant(Enchantment.LOOT_BONUS_MOBS)) {
final double modifier = this.lootModifier * meta.getEnchantLevel(Enchantment.LOOT_BONUS_MOBS);
experience = applyModifier(experience, modifier);
}
}
}
}
event.setDroppedExp(Math.max(0, experience));
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockExp(final BlockExpEvent event) {
if (this.disableGrindExp) {
event.setExpToDrop(0);
}
else {
event.setExpToDrop(applyModifier(event.getExpToDrop(), this.experienceModifier));
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onFurnaceExp(final FurnaceExtractEvent event) {
if (this.disableGrindExp) {
event.setExpToDrop(0);
}
else {
event.setExpToDrop(applyModifier(event.getExpToDrop(), this.experienceModifier));
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onFishingExp(final PlayerFishEvent event) {
if (this.disableGrindExp) {
event.setExpToDrop(0);
}
else {
event.setExpToDrop(applyModifier(event.getExpToDrop(), this.experienceModifier));
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onBreedExp(final EntityBreedEvent event) {
if (this.disableGrindExp) {
event.setExperience(0);
}
else {
event.setExperience(applyModifier(event.getExperience(), this.experienceModifier));
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onMerchantRecipe(final InventoryOpenEvent event) {
if (!this.disableGrindExp) {
return;
}
final Inventory inventory = event.getInventory();
if (!(inventory instanceof Merchant)) {
return;
}
for (final MerchantRecipe recipe : ((Merchant) inventory).getRecipes()) {
recipe.setExperienceReward(false);
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onExpChange(final PlayerExpChangeEvent event) {
if (this.preventOrbExp) {
event.setAmount(0);
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onExpBottle(final ExpBottleEvent event) {
if (this.expPerBottle != -1) { // If bottle xp is not random
event.setExperience(this.expPerBottle);
}
if (this.directBottleExp) {
final ThrownExpBottle bottle = event.getEntity();
final ProjectileSource thrower = bottle.getShooter();
if (thrower instanceof Player) {
final Player player = (Player) thrower;
player.giveExp(event.getExperience());
// Play the experience sound as no experience orbs will be spawned
// Credit to Team CoFH for the random pitch generator, see their code below
// https://github.com/CoFH/ThermalFoundation/blob/1.12/src/main/java/cofh/thermalfoundation/item/tome/ItemTomeExperience.java#L268
// This is a reasonable use of their "Copy portions of this code for use in other projects." clause.
player.getWorld().playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.1F,
(RANDOM.nextFloat() - RANDOM.nextFloat()) * 0.35f + 0.9f);
event.setExperience(0);
bottle.teleport(player);
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onEmeraldExp(final PlayerInteractEvent event) {
if (!this.emeraldCrafting
|| !this.emeraldLeveling
|| this.expPerBottle == 0) {
return;
}
switch (event.getAction()) {
case RIGHT_CLICK_AIR:
case RIGHT_CLICK_BLOCK:
break;
default:
return;
}
final Player player = event.getPlayer();
final PlayerInventory inventory = player.getInventory();
final EquipmentSlot slot = Objects.requireNonNull(event.getHand());
final ItemStack held = inventory.getItem(slot);
if (!ItemUtils.isValidItem(held)
|| !ItemUtils.areItemsSimilar(held, EMERALD_ITEM)) {
return;
}
if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
final Block clicked = Objects.requireNonNull(event.getClickedBlock());
if (clicked.getType().isInteractable()) {
event.setCancelled(true); // Don't give levels if trying to open a chest for example
return;
}
}
final int amount = held.getAmount();
if (amount <= 0) {
return;
}
int experience = 0;
if (this.expPerBottle != -1) { // If bottle xp is not random
experience = this.expPerBottle * 9;
}
else {
// Simulate nine thrown bottles, which drop between 3-11 experience
// https://minecraft.gamepedia.com/Bottle_o%27_Enchanting
for (int i = 0; i < 9; i++) {
experience += RANDOM.nextInt(11 - 3 + 1) + 3;
}
}
player.giveExp(experience);
inventory.setItem(slot, ItemUtils.decrementItem(held));
event.setCancelled(true); // Give the emerald leveling precedence
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerExpRecovery(final PlayerInteractEvent event) {
if (!this.allowExpRecovery
|| this.expPerBottle == -1 // If bottle xp is random
|| this.expPerBottle == 0 // If bottle xp is nothing
|| event.getAction() != Action.LEFT_CLICK_BLOCK) {
return;
}
final Block block = event.getClickedBlock();
if (!WorldUtils.isValidBlock(block)
|| block.getType() != Material.ENCHANTING_TABLE) {
return;
}
final Player player = event.getPlayer();
final PlayerInventory inventory = player.getInventory();
final ItemStack held = inventory.getItemInMainHand();
if (held.getType() != Material.GLASS_BOTTLE) {
return;
}
final int totalExp = computeCurrentXP(player);
if (totalExp < this.expPerBottle) {
return;
}
createExpBottles(player, totalExp);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPrepareBookEnchant(final PrepareItemEnchantEvent event) {
if (!this.disableEnchantedBookCreation) {
return;
}
final ItemStack item = event.getItem();
if (!ItemUtils.isValidItem(item)
|| item.getType() != Material.BOOK) {
return;
}
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPrepareBookUsage(final PrepareAnvilEvent event) {
if (!this.disableEnchantedBookUsage) {
return;
}
final AnvilInventory inventory = event.getInventory();
if (inventory.first(Material.ENCHANTED_BOOK) != -1) {
if (event.getResult() != null) {
event.setResult(null);
// This is needed because of client side shenanigans
Bukkit.getScheduler().runTaskLater(this.plugin, () -> {
for (final Player viewer : InventoryUtils.getViewingPlayers(inventory)) {
viewer.updateInventory();
}
}, 1L);
}
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPrepareItemEnchant(final PrepareItemEnchantEvent event) {
final CraftInventoryView view = (CraftInventoryView) event.getView();
final EnchantmentMenu table = (EnchantmentMenu) view.getHandle();
if (this.randomiseEnchants) {
try {
final Random tableRandom = (Random) this.enchantingTableRandomiser.get(table);
tableRandom.nextDouble();
}
catch (final IllegalArgumentException | IllegalAccessException | ClassCastException throwable) {
this.logger.log(Level.WARNING, "Could not set randomiser!", throwable);
}
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onAnvilRepair(final PrepareAnvilEvent event) {
if (this.maxRepairCost == -1) { // If no maximum
return;
}
final ItemStack result = event.getResult();
if (!ItemUtils.isValidItem(result)) {
return;
}
ItemUtils.handleItemMeta(result, (Repairable meta) -> {
final int newRepairCost = this.maxRepairCost - 2;
if (meta.getRepairCost() < newRepairCost) {
return false;
}
meta.setRepairCost(newRepairCost);
return true;
});
event.setResult(result);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onEnchantItem(final EnchantItemEvent event) {
if (this.fixEnchantCosts) {
final Player player = event.getEnchanter();
if (player.getLevel() < event.getExpLevelCost()) {
event.setCancelled(true);
return;
}
// The level cost of enchantments is always the button index + 1, assuming the server is Bukkit.
// And so if we want the enchantment to actually cost 30 levels as required by the ExpLevelCost, we need to
// make up for the difference with a manual level reduction of the remainder.
final int bukkitLevelCost = event.whichButton() + 1;
final int levelRemainder = event.getExpLevelCost() - bukkitLevelCost;
final int newLevel = player.getLevel() - levelRemainder;
player.setLevel(newLevel);
// The exp level cost needs to be reduced to the remaining cost, otherwise Bukkit's enchantment process
// still expects you to have thirty levels on you.
event.setExpLevelCost(bukkitLevelCost);
}
if (this.fillLapis) {
event.getInventory().setItem(1, LAPIS_ITEM.clone());
}
}
// TODO: This function causes weirdness when you place Lapis Lazuli in the item to enchant slot. It should be
// modified to only cancel the event if items are being added to or removed from the consumable slot.
@EventHandler(priority = EventPriority.HIGHEST)
public void onInventoryClick(final InventoryClickEvent event) {
if (!this.fillLapis
|| event instanceof InventoryCreativeEvent) {
return;
}
final Inventory inventory = event.getClickedInventory();
if (!InventoryUtils.isValidInventory(inventory) || inventory.getType() != InventoryType.ENCHANTING) {
return;
}
final ItemStack currentItem = event.getCurrentItem();
if (!ItemUtils.isValidItem(currentItem) || !currentItem.isSimilar(LAPIS_ITEM)) {
return;
}
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onOpenEnchantingTable(final InventoryOpenEvent event) {
if (!this.fillLapis) {
return;
}
final Inventory inventory = event.getInventory();
if (inventory.getType() != InventoryType.ENCHANTING) {
return;
}
event.getInventory().setItem(1, LAPIS_ITEM.clone());
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onCloseEnchantingTable(final InventoryCloseEvent event) {
if (!this.fillLapis) {
return;
}
final Inventory inventory = event.getInventory();
if (inventory.getType() != InventoryType.ENCHANTING) {
return;
}
inventory.setItem(1, null);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onBreakEnchantingTable(final BlockBreakEvent event) {
if (!this.fillLapis) {
return;
}
final Block block = event.getBlock();
if (block.getType() != Material.ENCHANTING_TABLE) {
return;
}
block.getDrops().removeIf(drop -> drop.isSimilar(LAPIS_ITEM));
}
// ------------------------------------------------------------
// Do not touch anything below unless you want a headache!
// ------------------------------------------------------------
private int applyModifier(int experience, double modifier) {
return (int) Math.max(0, Math.ceil(experience * modifier));
}
private int computeCurrentXP(Player player) {
float currentLevel = player.getLevel();
float progress = player.getExp();
float a = 1f, b = 6f, c = 0f, x = 2f, y = 7f;
if (currentLevel > 16 && currentLevel <= 31) {
a = 2.5f;
b = -40.5f;
c = 360f;
x = 5f;
y = -38f;
}
else if (currentLevel >= 32) {
a = 4.5f;
b = -162.5f;
c = 2220f;
x = 9f;
y = -158f;
}
return (int) Math
.floor(a * currentLevel * currentLevel + b * currentLevel + c + progress * (x * currentLevel + y));
}
private void createExpBottles(Player player, int totalExp) {
ItemMap inv = new ItemMap(player.getInventory());
int bottles = inv.getAmount(new ItemStack(Material.GLASS_BOTTLE));
int xpavailable = totalExp / this.expPerBottle;
int remove = Math.min(bottles, xpavailable);
if (remove == 0) {
return;
}
boolean noSpace = false;
int bottleCount = 0;
ItemMap removeMap = new ItemMap();
removeMap.addItemAmount(new ItemStack(Material.GLASS_BOTTLE), remove);
for (ItemStack is : removeMap.getItemStackRepresentation()) {
int initialAmount = is.getAmount();
player.getInventory().removeItem(is);
is.setType(Material.EXPERIENCE_BOTTLE);
HashMap<Integer, ItemStack> result = player.getInventory().addItem(is);
if (!result.isEmpty()) {
is.setType(Material.GLASS_BOTTLE);
player.getInventory().addItem(is);
noSpace = true;
break;
}
else {
bottleCount += initialAmount;
}
}
if (bottleCount > 0) {
int endXP = totalExp - bottleCount * this.expPerBottle;
player.setLevel(0);
player.setExp(0);
player.giveExp(endXP);
player.sendMessage(ChatColor.GREEN + "Created " + bottleCount + " XP bottles.");
}
if (noSpace) {
player.sendMessage(ChatColor.RED + "Not enough space in inventory for all XP bottles.");
}
}
}
@@ -0,0 +1,111 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Statistic;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Phantom;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerStatisticIncrementEvent;
import vg.civcraft.mc.civmodcore.world.WorldUtils;
public final class PhantomMenace extends BasicHack {
@AutoLoad
private int timeSinceRestCap;
@AutoLoad
private boolean nightSpawn;
@AutoLoad
private boolean stormSpawn;
@AutoLoad
private int maximumLightSpawn;
public PhantomMenace(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
this.timeSinceRestCap = Math.max(-1, this.timeSinceRestCap);
this.maximumLightSpawn = Math.min(Math.max(this.maximumLightSpawn, 0), 15);
}
@EventHandler
public void onCreatureSpawn(final CreatureSpawnEvent event) {
// Do not regulate unless the creature is a phantom
if (event.getEntityType() != EntityType.PHANTOM) {
return;
}
// Do not regulate phantom spawning unless it's natural
switch (event.getSpawnReason()) {
case NATURAL:
case DEFAULT:
break;
default:
return;
}
if (!this.nightSpawn && !this.stormSpawn) {
event.setCancelled(true);
plugin().debug("Phantom prevented from spawning: cannot spawn during the night nor during storms.");
return;
}
final Phantom phantom = (Phantom) event.getEntity();
final World world = phantom.getWorld();
if (!this.nightSpawn && this.stormSpawn) {
if (!world.hasStorm()) {
event.setCancelled(true);
plugin().debug("Phantom prevented from spawning: night spawning disabled.");
}
}
else if (this.nightSpawn && !this.stormSpawn) {
if (world.getTime() < 12300 || world.getTime() > 23850) {
event.setCancelled(true);
plugin().debug("Phantom prevented from spawning: storm spawning disabled.");
}
}
final UUID targetUUID = phantom.getSpawningEntity();
if (targetUUID != null) {
final Entity target = Bukkit.getEntity(targetUUID);
if (target != null) {
final Location targetLocation = target.getLocation();
if (WorldUtils.isBlockLoaded(targetLocation)) {
final Block targetBlock = targetLocation.getBlock();
final int lightLevel = targetBlock.getLightLevel();
if (lightLevel > this.maximumLightSpawn) {
event.setCancelled(true);
plugin().debug("Phantom prevented from spawning: light level was too high.");
}
}
}
}
// Otherwise let the phantom(s) spawn
}
@EventHandler
public void onStatisticIncrease(final PlayerStatisticIncrementEvent event) {
if (event.getStatistic() != Statistic.TIME_SINCE_REST) {
return;
}
final Player player = event.getPlayer();
if (event.getPreviousValue() < 72_000 && event.getNewValue() >= 72_000) {
player.sendMessage(ChatColor.DARK_RED + "You haven't slept in a while...");
}
final int cap = this.timeSinceRestCap;
if (cap <= -1 || event.getNewValue() < cap) {
return;
}
player.setStatistic(Statistic.TIME_SINCE_REST, Math.min(event.getNewValue(), cap));
event.setCancelled(true); // Prevent the increment
}
}
@@ -0,0 +1,73 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Syntax;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
public final class PlayerRevive extends BasicHack {
private final CommandManager commands;
public PlayerRevive(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
this.commands = new CommandManager(plugin) {
@Override
public void registerCommands() {
registerCommand(new ReviveCommand());
}
};
}
@Override
public void onEnable() {
super.onEnable();
this.commands.init();
}
@Override
public void onDisable() {
this.commands.reset();
super.onDisable();
}
@CommandPermission("simpleadmin.revive")
public static class ReviveCommand extends BaseCommand {
@CommandAlias("revive|respawn|resurrect|ress")
@Syntax("<player name>")
@Description("Revives a player")
@CommandCompletion("@players")
public void revivePlayer(final CommandSender sender, @Single final String name) {
final Player player = Bukkit.getPlayer(name);
if (player == null) {
sender.sendMessage(ChatColor.RED + "That player doesn't exist or isn't online.");
return;
}
if (!player.isDead()) {
sender.sendMessage(ChatColor.RED + "That player is not dead.");
return;
}
final Location prevBedSpawn = player.getBedSpawnLocation();
player.setBedSpawnLocation(player.getLocation(), true);
player.spigot().respawn();
player.setBedSpawnLocation(prevBedSpawn, true);
player.sendMessage(ChatColor.YELLOW + "You have been revived.");
sender.sendMessage(player.getName() + " revived.");
}
}
}
@@ -0,0 +1,126 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.BukkitCommandCompletionContext;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Syntax;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
import vg.civcraft.mc.civmodcore.commands.TabComplete;
public final class PlayerStatistics extends BasicHack {
private final CommandManager commands;
public PlayerStatistics(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
this.commands = new CommandManager(plugin()) {
@Override
public void registerCommands() {
registerCommand(new StatsCommand());
}
};
}
@Override
public void onEnable() {
super.onEnable();
this.commands.init();
}
@Override
public void onDisable() {
this.commands.reset();
super.onDisable();
}
@CommandPermission("simpleadmin.stats")
public static class StatsCommand extends BaseCommand {
public static final String ALIAS = "stats|stat|statistic|statistics";
@CommandAlias(StatsCommand.ALIAS)
@Default
public void fallback(final CommandSender sender, @Optional final String playerName) {
sender.sendMessage(ChatColor.RED + "Available statistics commands:");
sender.sendMessage(ChatColor.YELLOW + "get: " + ChatColor.WHITE + "/stats <player> <statistic>");
sender.sendMessage(ChatColor.YELLOW + "set: " + ChatColor.WHITE + "/stats <player> <statistic> <value>");
}
@CommandAlias(StatsCommand.ALIAS)
@Description("Gets the statistic for a player.")
@Syntax("<player> <statistic>")
@CommandCompletion("@players @stats")
public void getPlayerStatistic(final CommandSender sender,
final String playerName,
@Single final String statName) {
final Player player = Bukkit.getPlayer(playerName);
if (player == null) {
sender.sendMessage(ChatColor.RED + "Could not find that player.");
return;
}
final Statistic statistic = EnumUtils.getEnumIgnoreCase(Statistic.class, statName);
if (statistic == null) {
sender.sendMessage(ChatColor.RED + "Could not find that statistic.");
return;
}
sender.sendMessage(ChatColor.GOLD + player.getName() + " [" + statistic.name() + "]: " +
ChatColor.WHITE + player.getStatistic(statistic));
}
@CommandAlias(StatsCommand.ALIAS)
@Description("Sets the statistic for a player.")
@Syntax("<player> <statistic> <value>")
@CommandCompletion("@players @stats @nothing")
public void setPlayerStatistic(final CommandSender sender,
final String playerName,
final String statName,
final int value) {
final Player player = Bukkit.getPlayer(playerName);
if (player == null) {
sender.sendMessage(ChatColor.RED + "Could not find that player.");
return;
}
final Statistic statistic = EnumUtils.getEnumIgnoreCase(Statistic.class, statName);
if (statistic == null) {
sender.sendMessage(ChatColor.RED + "Could not find that statistic.");
return;
}
player.setStatistic(statistic, value);
sender.sendMessage(ChatColor.GOLD + player.getName() + " [" + statistic.name() + "] set to: " +
ChatColor.WHITE + player.getStatistic(statistic));
}
@TabComplete("stats")
public List<String> tabCompleteStatistics(final BukkitCommandCompletionContext context) {
final List<String> results = new ArrayList<>();
for (final Statistic statistic : Statistic.values()) {
final String slug = statistic.name();
if (!StringUtils.startsWith(slug, context.getInput())) {
continue;
}
results.add(slug);
}
return results;
}
}
}
@@ -0,0 +1,84 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import org.bukkit.Bukkit;
import org.bukkit.HeightMap;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerPortalEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
public class PortalModifyHack extends BasicHack {
@AutoLoad
private String targetWorld;
@AutoLoad
private String homeWorld;
public PortalModifyHack(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
}
//We want to go last incase any plugins want to cancel our attempt
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerEnterPortal(PlayerPortalEvent event) {
Player player = event.getPlayer();
if (event.getCause() != PlayerTeleportEvent.TeleportCause.END_PORTAL) {
return;
}
World world = Bukkit.getWorld(targetWorld);
if (world == null) {
return;
}
Location to;
switch (getTargetWorld(player).getEnvironment()) {
case NETHER:
to = new Location(getTargetWorld(player), event.getFrom().getX(), 125, event.getFrom().getZ());
break;
case NORMAL:
to = getTargetWorld(player).getHighestBlockAt(player.getLocation(), HeightMap.WORLD_SURFACE).getLocation().toCenterLocation().add(0,1,0);
break;
default:
return;
}
event.setTo(to);
if (to.getWorld().getName().equals(targetWorld)) {
spawnExit(to);
}
}
private World getTargetWorld(Player player) {
World target = Bukkit.getWorld(targetWorld);
World home = Bukkit.getWorld(homeWorld);
if (target == null) {
return player.getWorld();
}
if (home == null) {
return player.getWorld();
}
return player.getWorld().equals(target) ? home : target;
}
private void spawnExit(Location location) {
if (location == null) {
return;
}
//:Glad:
if (location.getBlock().getRelative(0,-2,0).getType() == Material.BEDROCK) {
location.getBlock().getRelative(0,-2,0).setType(Material.NETHERRACK);
};
if (location.getBlock().getRelative(0,-1,0).getType() == Material.BEDROCK) {
location.getBlock().getRelative(0,-1,0).setType(Material.NETHERRACK);
}
location.getBlock().setType(Material.AIR);
location.getBlock().getRelative(0,1,0).setType(Material.AIR);
location.getBlock().getRelative(0,2,0).setType(Material.END_PORTAL);
}
}
@@ -0,0 +1,145 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import java.util.Iterator;
import java.util.Random;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.PigZombie;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockDispenseArmorEvent;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.inventory.ItemStack;
public class PortalSpawnModifier extends BasicHack {
private Random rng;
@AutoLoad
private double pigManChance;
@AutoLoad
private double ghastChance;
@AutoLoad
private double witherSkeletonChance;
@AutoLoad
private double witherHeadDropChance;
public PortalSpawnModifier(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
rng = new Random();
}
@EventHandler
public void portalSpawn(CreatureSpawnEvent event) {
if (event.getSpawnReason() != SpawnReason.NETHER_PORTAL) {
return;
}
if (event.getEntityType() == EntityType.ZOMBIFIED_PIGLIN) {
if (!roll(pigManChance)) {
event.setCancelled(true);
return;
}
if (roll(ghastChance)) {
event.setCancelled(true);
spawnGhast(event.getLocation());
}
else if (roll(witherSkeletonChance)) {
event.setCancelled(true);
spawnWitherSkeleton(event.getLocation());
}
}
}
private void spawnGhast(Location loc) {
PigZombie pigZombie = (PigZombie) loc.getWorld().spawnEntity(loc, EntityType.ZOMBIFIED_PIGLIN);
pigZombie.getEquipment().setItemInOffHand(new ItemStack(Material.GHAST_TEAR));
}
private void spawnWitherSkeleton(Location loc) {
PigZombie pigZombie = (PigZombie) loc.getWorld().spawnEntity(loc, EntityType.ZOMBIFIED_PIGLIN);
pigZombie.getEquipment().setHelmet(new ItemStack(Material.WITHER_SKELETON_SKULL));
}
private boolean roll(double chance) {
return rng.nextDouble() <= chance;
}
@EventHandler
public void onEntityPickupItem(EntityPickupItemEvent e) {
if (e.getEntityType() == EntityType.ZOMBIFIED_PIGLIN) {
e.setCancelled(true); // Prevents giving piglins items to change what they drop
}
}
@EventHandler
public void dispenseArmorEvent(BlockDispenseArmorEvent e) {
if (e.getTargetEntity().getType() == EntityType.ZOMBIFIED_PIGLIN) {
e.setCancelled(true);
}
}
@EventHandler
public void zombiePiglinDeath(EntityDeathEvent e) {
if (e.getEntityType() != EntityType.ZOMBIFIED_PIGLIN) { // If the death is not a piglin, we can disregard.
return;
}
EntityType type = EntityType.ZOMBIFIED_PIGLIN; // Default type is piglin
//Iterating through the drops to remove the added wither skull & ghast tear, if present.
Iterator<ItemStack> iter = e.getDrops().iterator();
while(iter.hasNext()) {
ItemStack is = iter.next();
if (is.getType() == Material.WITHER_SKELETON_SKULL) {
iter.remove();
} else if (is.getType() == Material.GHAST_TEAR) {
iter.remove();
}
}
if (e.getEntity().getEquipment().getHelmet().getType() == Material.WITHER_SKELETON_SKULL) {
type = EntityType.WITHER_SKELETON;
} else if (e.getEntity().getEquipment().getItemInOffHand().getType() == Material.GHAST_TEAR) {
type = EntityType.GHAST;
}
if (type != EntityType.ZOMBIFIED_PIGLIN) { // Type is a mock-wither or mock-ghast
e.getDrops().clear(); // Clear piglin drops
if (type == EntityType.GHAST) {
int amount = rng.nextInt(2); // Random drop of 0 or 1 Ghast Tear
if (amount > 0) {
e.getDrops().add(new ItemStack(Material.GHAST_TEAR, amount));
}
amount = rng.nextInt(3); // Random drop of 0, 1, or 2 Gunpowder
if (amount > 0) {
e.getDrops().add(new ItemStack(Material.GUNPOWDER, amount));
}
}
else { //WITHER
int amount = rng.nextInt(3); // Random drop of 0, 1, or 2 Bones
if (amount > 0) {
e.getDrops().add(new ItemStack(Material.BONE, amount));
}
amount = rng.nextInt(3) -1; // Random drop of -1, 0, or 1 Coal. (1/3 chance for a single coal)
if (amount > 0) {
e.getDrops().add(new ItemStack(Material.COAL, amount));
}
if (roll(witherHeadDropChance)) { // Standard role for wither skull chance.
e.getDrops().add(new ItemStack(Material.WITHER_SKELETON_SKULL));
}
}
}
}
}
@@ -0,0 +1,132 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils;
import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI;
import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection;
import vg.civcraft.mc.civmodcore.players.settings.impl.BooleanSetting;
public final class SafeOreBreak extends BasicHack {
@AutoLoad
private List<List<String>> ores;
private final List<BooleanOreSetting> oreSettings = new ArrayList<>();
private final List<List<Material>> breakOres = new ArrayList<>();
public SafeOreBreak(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
}
@Override
public void onEnable() {
super.onEnable();
MenuSection mainMenu = plugin.getSettingManager().getMainMenu();
OUTER:
for (List<String> parts : ores) {
List<Material> materialParts = new ArrayList<>(parts.size());
for (String part : parts) {
Material material = MaterialUtils.getMaterial(part);
if (material == null) {
logger.warning("Invalid material '" + part + "'. Skipping.");
continue OUTER;
}
materialParts.add(material);
}
breakOres.add(materialParts);
String materialName = PlainTextComponentSerializer.plainText().serialize(Component.translatable(materialParts.get(0)));
BooleanOreSetting setting = new BooleanOreSetting(plugin, false,
"Safe " + materialName + " break",
"safeOreBreak_" + materialParts.get(0).getKey().getKey(),
"Prevents you from breaking " + materialName
+ " without a silk touch pickaxe.",
materialParts.get(0));
oreSettings.add(setting);
PlayerSettingAPI.registerSetting(setting, mainMenu);
}
}
@Override
public void onDisable() {
super.onDisable();
}
@EventHandler(ignoreCancelled = true)
public void onOreBreak(BlockBreakEvent event) {
if (event.getPlayer().getGameMode() != GameMode.SURVIVAL) {
return;
}
ItemStack item = event.getPlayer().getInventory().getItemInMainHand();
if (item.hasItemMeta() && item.getItemMeta().hasEnchant(Enchantment.SILK_TOUCH)) {
return;
}
OUTER:
for (int i = 0; i < breakOres.size(); i++) {
List<Material> list = breakOres.get(i);
for (Material material : list) {
if (material == event.getBlock().getType()) {
Boolean value = oreSettings.get(i).getValue(event.getPlayer());
if (!value) {
continue OUTER;
}
event.getPlayer().sendMessage(Component.text(
"A SimpleAdminHacks /config option is preventing you from breaking that ore without a silk touch pickaxe.")
.color(NamedTextColor.RED));
event.setCancelled(true);
return;
}
}
}
}
private static class BooleanOreSetting extends BooleanSetting {
private final Material ore;
public BooleanOreSetting(JavaPlugin owningPlugin, Boolean defaultValue, String name, String identifier,
String description, Material ore) {
super(owningPlugin, defaultValue, name, identifier, description);
this.ore = ore;
}
@Override
public ItemStack getGuiRepresentation(UUID player) {
ItemStack item;
if (getValue(player)) {
item = new ItemStack(ore);
item.addUnsafeEnchantment(Enchantment.DURABILITY, 1);
item.addItemFlags(ItemFlag.HIDE_ENCHANTS);
} else {
item = new ItemStack(ore);
}
applyInfoToItemStack(item, player);
return item;
}
}
}
@@ -0,0 +1,122 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.DataParser;
import com.programmerdan.minecraft.simpleadminhacks.framework.utilities.BetterToString;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import org.apache.commons.collections4.CollectionUtils;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.BoundingBox;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;
import vg.civcraft.mc.civmodcore.world.ChunkLoadedFilter;
public final class ShipOutOfLuck extends BasicHack {
@AutoLoad(processor = DataParser.MATERIAL)
private List<Material> boatBreakers;
private int ignoredBoatCounter = 0;
public ShipOutOfLuck(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
}
@Override
public void onEnable() {
super.onEnable();
// Just in case it's auto loaded to null.
if (this.boatBreakers == null) {
this.boatBreakers = new ArrayList<>();
}
}
@Override
public void onDisable() {
this.boatBreakers.clear();
super.onDisable();
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void preventBoatPlacements(final PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
final Block placedOn = Objects.requireNonNull(event.getClickedBlock());
final ItemStack placed = event.getItem();
if (!this.boatBreakers.contains(placedOn.getType())
|| !ItemUtils.isValidItem(placed)
|| !Tag.ITEMS_BOATS.isTagged(placed.getType())) {
return;
}
event.setCancelled(true);
plugin().debug("Prevented boat placement on [" + placedOn.getType().name() + "] by [" +
event.getPlayer().getName() + "] at [" + BetterToString.location(placedOn.getLocation()) + "]");
//return;
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void preventBoatUsage(final VehicleMoveEvent event) {
final Vehicle vehicle = event.getVehicle();
if (vehicle.getType() != EntityType.BOAT) {
return;
}
final List<Entity> passengers = vehicle.getPassengers();
if (passengers.isEmpty()) {
return;
}
if (++this.ignoredBoatCounter < 40) {
return;
}
this.ignoredBoatCounter = 0;
final World world = vehicle.getWorld();
final List<Material> illegalBlocks = getCollidingBlocks(vehicle.getBoundingBox())
.filter(ChunkLoadedFilter.blockPosition(world))
.map(loc -> world.getBlockAt(loc.getX(), loc.getY(), loc.getZ()).getType())
.filter(this.boatBreakers::contains)
.distinct()
.collect(Collectors.toCollection(ArrayList::new));
if (CollectionUtils.isEmpty(illegalBlocks)) {
return;
}
vehicle.eject();
plugin().debug("Ejected [" + passengers.stream().map(CommandSender::getName)
.collect(Collectors.joining(", ")) + "] from boat at [" + BetterToString.location(event.getTo())
+ "] because they sailed over [" + illegalBlocks.stream().map(Material::name)
.collect(Collectors.joining(", ")) + "]");
//return;
}
private static Stream<BlockPos> getCollidingBlocks(final BoundingBox bounds) {
// This transform the bounds of the boat to be a flat rectangle roughly a carpet's thickness smaller on each
// side and placed the same distance below the boat, which should be enough to reliably detect what blocks
// are carrying the boat without false alarming with blocks beside the boat.
final int minX = (int) Math.floor(bounds.getMinX() + 0.06);
final int maxX = (int) Math.floor(bounds.getMaxX() - 0.06);
final int valY = (int) Math.floor(bounds.getMinY() - 0.06);
final int minZ = (int) Math.floor(bounds.getMinZ() + 0.06);
final int maxZ = (int) Math.floor(bounds.getMaxZ() - 0.06);
return BlockPos.betweenClosedStream(minX, valY, minZ, maxX, valY, maxZ);
}
}
@@ -0,0 +1,88 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import com.programmerdan.minecraft.banstick.handler.BanStickDatabaseHandler;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
public final class StrayStats extends BasicHack {
// ------------------------------------------------------------
// Bootstrap
// ------------------------------------------------------------
private final CommandManager commands;
public StrayStats(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
this.commands = new CommandManager(plugin()) {
@Override
public void registerCommands() {
registerCommand(new StatsCommand());
}
};
}
@Override
public void onEnable() {
super.onEnable();
if(plugin.getServer().getPluginManager().getPlugin("BanStick") == null) {
plugin.severe("StrayStats failed to initialize because BanStick plugin is not found");
return;
}
commands.init();
}
@Override
public void onDisable() {
commands.reset();
super.onDisable();
}
// ------------------------------------------------------------
// Statistics Commands
// ------------------------------------------------------------
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
private class StatsCommand extends BaseCommand {
@CommandAlias("compile_player_join_statistics")
@CommandPermission("simpleadmin.stats")
public void compileStatistics(final CommandSender sender) {
sender.sendMessage(ChatColor.GOLD + "Starting to compile player join statistics!");
Bukkit.getScheduler().runTaskAsynchronously(plugin(), () -> {
try (final var file = new FileWriter(new File(plugin().getDataFolder(), "playerJoinStats.csv"))) {
try (final var connection = BanStickDatabaseHandler.getInstanceData().getConnection();
final var statement = connection.prepareStatement(
"SELECT name, first_add FROM bs_player ORDER BY first_add ASC");
final var query = statement.executeQuery();) {
while (query.next()) {
file.write(query.getString(1) + "," + DATE_FORMAT.format(query.getTimestamp(2)) + "\n");
}
}
}
catch (final IOException | SQLException exception) {
plugin().warning("Could not complete compilation!", exception);
sender.sendMessage(ChatColor.RED + "Could not complete compilation!");
return;
}
sender.sendMessage(ChatColor.GREEN + "Finished compiling player join statistics!");
});
}
}
}
@@ -0,0 +1,106 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import java.util.logging.Level;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityBreedEvent;
public class StriderBreeding extends BasicHack {
@AutoLoad
private double minSpeed;
@AutoLoad
private double maxSpeed;
@AutoLoad
private int minHealth;
@AutoLoad
private int maxHealth;
public StriderBreeding(SimpleAdminHacks plugin, BasicHackConfig config) {
super(plugin, config);
}
@EventHandler(priority = EventPriority.HIGH)
public void onStriderBreed(EntityBreedEvent event) {
if (event.getEntity().getType() != EntityType.STRIDER) {
return;
}
double dadSpeed = event.getFather().getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).getBaseValue();
double mumSpeed = event.getMother().getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).getBaseValue();
double irwinHallDist = (0.45 + Math.random() * 0.3 + Math.random() * 0.3 + Math.random() * 0.3) * 0.25;
double newStriderSpeed = (dadSpeed + mumSpeed + irwinHallDist) / 3;
if (newStriderSpeed < minSpeed) {
newStriderSpeed = minSpeed;
} else if (newStriderSpeed > maxSpeed) {
newStriderSpeed = maxSpeed;
}
event.getEntity().getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(newStriderSpeed);
double dadHealth = event.getFather().getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue();
double mumHealth = event.getFather().getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue();
double randomHealth = Math.random() * (this.maxHealth - this.minHealth) + this.minHealth;
double newStriderHealth = Math.round(((float) dadHealth + (float) mumHealth + (float) randomHealth) / 3);
if (newStriderHealth < minHealth) {
newStriderHealth = minHealth;
} else if (newStriderHealth > maxHealth) {
newStriderHealth = maxHealth;
}
event.getEntity().getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(newStriderHealth);
event.getEntity().setHealth(newStriderHealth);
plugin.getLogger().log(Level.INFO, "Strider breed to have speed: " + newStriderSpeed
+ " and health: " + newStriderHealth);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onStriderSpawn(CreatureSpawnEvent event) {
if (event.getEntity().getType() != EntityType.STRIDER) {
return;
}
if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.BREEDING) {
return;
}
rollSpeedStat(event.getEntity(), this.minSpeed, this.maxSpeed);
rollHealthStat(event.getEntity(), this.minHealth, this.maxHealth);
}
public void rollSpeedStat(LivingEntity strider, double minSpeed, double maxSpeed) {
AttributeInstance moveSpeed = strider.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED);
if (moveSpeed == null) {
return;
}
double irwinHallDist = (0.45 + Math.random() * 0.3 + Math.random() * 0.3 + Math.random() * 0.3) * 0.25;
if (irwinHallDist < minSpeed) {
irwinHallDist = minSpeed;
} else if (irwinHallDist > maxSpeed) {
irwinHallDist = maxSpeed;
}
moveSpeed.setBaseValue(irwinHallDist);
plugin.getLogger().log(Level.INFO, "Setting Strider Speed to: " + irwinHallDist);
}
public void rollHealthStat(LivingEntity strider, int minHealth, int maxHealth) {
AttributeInstance moveSpeed = strider.getAttribute(Attribute.GENERIC_MAX_HEALTH);
if (moveSpeed == null) {
return;
}
int newValue = Math.round((float) Math.random() * (maxHealth - minHealth) + minHealth);
if (newValue < minHealth) {
newValue = minHealth;
} else if (newValue > maxHealth) {
newValue = maxHealth;
}
moveSpeed.setBaseValue(newValue);
//We set the health to the new value since when we update the health, they dont auto heal
strider.setHealth(newValue);
plugin.getLogger().log(Level.INFO, "Setting Strider Health to: " + newValue);
}
}
@@ -0,0 +1,110 @@
package com.programmerdan.minecraft.simpleadminhacks.hacks.basic;
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack;
import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig;
import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad;
import net.md_5.bungee.api.ChatColor;
public final class TestConfigHack extends BasicHack {
@AutoLoad
private boolean boolValue;
@AutoLoad
private byte byteValue;
@AutoLoad
private short shortValue;
@AutoLoad
private int intValue;
@AutoLoad
private long longValue;
@AutoLoad
private long maybeLongValue;
@AutoLoad
private float floatValue;
@AutoLoad
private double doubleValue;
@AutoLoad
private char charValue;
public TestConfigHack(final SimpleAdminHacks plugin, final BasicHackConfig config) {
super(plugin, config);
}
@Override
public void onEnable() {
super.onEnable();
// boolean
if (this.boolValue) {
this.plugin.info(ChatColor.GREEN + "Boolean value passes!");
}
else {
this.plugin.info(ChatColor.RED + "Boolean value failed.");
}
// byte
if (this.byteValue == (byte) 8) {
this.plugin.info(ChatColor.GREEN + "Byte value passes!");
}
else {
this.plugin.info(ChatColor.RED + "Byte value failed: " + this.byteValue);
}
// short
if (this.shortValue == (short) 14646) {
this.plugin.info(ChatColor.GREEN + "Short value passes!");
}
else {
this.plugin.info(ChatColor.RED + "Short value failed: " + this.shortValue);
}
// int
if (this.intValue == 33822776) {
this.plugin.info(ChatColor.GREEN + "Int value passes!");
}
else {
this.plugin.info(ChatColor.RED + "Int value failed: " + this.intValue);
}
// long
if (this.longValue == 2486941267899177L) {
this.plugin.info(ChatColor.GREEN + "Long value passes!");
}
else {
this.plugin.info(ChatColor.RED + "Long value failed: " + this.longValue);
}
// long - second, ambiguous test
if (this.maybeLongValue == 6543L) {
this.plugin.info(ChatColor.GREEN + "Long (ambiguous) value passes!");
}
else {
this.plugin.info(ChatColor.RED + "Long (ambiguous) value failed: " + this.maybeLongValue);
}
// float
if (this.floatValue == 3847.1234f) {
this.plugin.info(ChatColor.GREEN + "Float value passes!");
}
else {
this.plugin.info(ChatColor.RED + "Float value failed: " + this.floatValue);
}
// double
if (this.doubleValue == 345978623.178569873d) {
this.plugin.info(ChatColor.GREEN + "Double value passes!");
}
else {
this.plugin.info(ChatColor.RED + "Double value failed: " + this.doubleValue);
}
// char
if (this.charValue == 'c') {
this.plugin.info(ChatColor.GREEN + "Char value passes!");
}
else {
this.plugin.info(ChatColor.RED + "Char value failed: " + this.charValue);
}
}
}
+543
View File
@@ -0,0 +1,543 @@
# Effectively Modern Humbug. For 1.10 ground up.
configuration_file_version: 1
debug: true
broadcast_permission: simpleadmin.broadcast
hacks:
############################################################
## Basic Hacks
############################################################
AntiFastBreak:
enabled: true
breakDenyDuration: 3000
laggLenciency: 1.2
#Prevents pearls from landing that have been in flight for more than the configured amount of time
AntiStatisChamber:
enabled: true
pearlLifetime: 5m
#Bounds armour to specific players disallowing others to wear the same armour piece after first equip
ArmourBound:
enabled: true
whitelist:
- DIAMOND_HELMET
- DIAMOND_CHESTPLATE
- DIAMOND_LEGGINGS
- DIAMOND_BOOTS
- NETHERITE_HELMET
- NETHERITE_CHESTPLATE
- NETHERITE_LEGGINGS
- NETHERITE_BOOTS
ArthropodEggHack:
enabled: true
eggChance: 0.03
lootingChance: 0.02
removeDrops: true
allowedTypes:
- SHEEP
- PIG
- COW
- BAT
- CHICKEN
- WOLF
- SQUID
- MUSHROOM_COW
- RABBIT
- HORSE
- VILLAGER
- LLAMA
- OCELOT
- FOX
- COD
- TROPICAL_FISH
- SALMON
- TURTLE
- DOLPHIN
AttrHider:
enabled: true
# Hide item data like enchantments, firework effects, book data, potion, etc..
hideItemMeta: true
# Hide potion effects on entities, except for particles
hideEffects: true
# Hide the health of entities
hideHealth: true
# Limit player ping in the tablist to the same 6 values vanilla clients can discern visually
roundPlayerListPing: true
AutoRespawn:
enabled: true
# Delay in MS
# Default: 300000 (five minutes)
respawnDelay: 300000
# The respawn delay (in MS) if someone logs in dead
# Default 0 (instant)
loginRespawnDelay: 0
# Jokey joke messages sent to the player if auto-respawned
# Deactivate by removing all the quotes below
respawnQuotes:
- <gold>Wait... how did I get here?
- <gold>Must've been a rough night o.o
- <gold>Why does my entire body ache?
- <gold>That <it>really<gold> hurt!
BedLocator:
enabled: true
BeeKeeping:
enabled: true
# Value between 0 and 1 that determines the percentage chance that a bee will replace
# the natural spawning of a passive creature should certain conditions be met.
# Default: 0.2 (20%)
spawnChance: 0.2
# Value between 0 and 1 that determines the percentage chance that a bee will lose
# its stinger and thus die when successfully attacking an entity.
# Default: 1.0 (100%)
severStingerChance: 1.0
BuffSpanker:
enabled: true
# The list of PotionEffectType's that you do not wish players to receive.
naughty:
- DOLPHINS_GRACE
- BAD_OMEN
- SLOW_DIGGING
- SLOW_FALLING
DebugWand:
enabled: true
DogFacts:
enabled: false
intervalTime: 30m
#Since we use the tellraw command to dispatch dogfacts now, all our announcements **MUST** be in JSON format
#YAML also doesn't like JSON's usage of "" so we have to add escape characters (\) to counteract that.
#Here's the site I used to make these ones: https://minecraft.tools/en/json_text.php
announcements:
- "[\"\",{\"text\":\"Buy \"},{\"text\":\"Dogecoin\",\"color\":\"dark_red\",\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://www.coindesk.com/price/dogecoin\"}},{\"text\":\"!\"}]"
- "[\"\",{\"text\":\"Very \"},{\"text\":\"Wow!\",\"italic\":true,\"hoverEvent\":{\"action\":\"show_text\",\"contents\":\"Wow!\"}}]"
EventDebugHack:
enabled: true
EventHandlerList:
enabled: true
GoldBlockElevators:
enabled: true
elevatorBlock: LODESTONE
HumbugBatchOne:
enabled: true
allowSheepDying: true
allowUsingAnvils: true
allowUsingEnchantingTables: true
infiniteCauldrons: true
disableEnderDragon: true
disableIronFarms: true
disableEnderCrystalDamage: true
disableMiningFatigue: true
canEquipBanners: true
disableLavaCobbleMountains: true
disableWanderingTrader: true
preventPearlGlitching: true
preventUsingEyeOfEnder: true
disablePiglins: true
disableGapples: true
ItemMetaConverterHack:
enabled: true
OldEnchanting:
enabled: true
# Hides what enchantment will be granted within the Enchanting Table
hideEnchants: true
# Automatically fills the consumable slot with the Enchanting Table with Lapis Lazuli that cannot be taken
fillLapis: true
# Randomises the enchantment offers each time the item is placed in an Enchanting Table
randomiseEnchants: true
# Force enchantments to cost what they appear in the Enchanting Table
fixEnchantCosts: true
# Experience modifier, all experience drops will be multiplied by this (does not apply to player exp)
# NOTE: Modifier must be 0 or greater
experienceModifier: 0.2
# Loot modifier, multiply the amount of exp dropped from each level of Looting (does not apply to player exp)
# NOTE: Modifier must be 0 or greater
lootModifier: 1.5
# Enables xp bottles to be crafted from emeralds and vice versa
emeraldCrafting: true
# Enables gaining xp from emeralds directly if emeraldCrafting is enabled
emeraldLeveling: true
# Disables exp from mobs, fishing, mining, breeding, furnace extracting, etc
disableGrindExp: true
# Prevents exp orbs from granting xp
preventOrbExp: true
# Ensures that the player who threw the xp bottle is the one to get the xp (experienceModifier does not apply)
directBottleExp: true
# Defines the maximum repair cost of an item
# NOTE: Value must be 2 or greater, or -1 to disable
maxRepairCost: 33
# Defines the set amount of xp that xp bottles will produce
# NOTE: Value must be a 0 or greater, or -1 for vanilla random
expPerBottle: 10
# Allows player's to store their levels in bottles if expPerBottle has a value higher than 0
allowExpRecovery: true
# Disallows players from creating enchanted books
disableEnchantedBookCreation: true
# Disallows players from using enchanted books
disableEnchantedBookUsage: true
# Modifies the exp drops for specific mob types, which will be used in lieu of experienceModifier
# NOTE: Players have an implicit modifier of 1.0
# NOTE: Modifiers must be 0 or greater
entityExpDropModifiers:
ENDER_DRAGON: 0.1
PhantomMenace:
enabled: true
# Players have a statistic called TIME_SINCE_REST. If a player's value is above 72k (3 days minecraft time) then
# they will start spawning phantoms. Setting the cap to below 72k will effectively disable phantoms. Setting the
# cap to -1 will disable the cap. Default: 80k (10% chance per two minutes during the night and thunder storms.)
# Read more here: https://minecraft.gamepedia.com/Phantom#Java_Edition
timeSinceRestCap: 80000
# Determines whether phantoms are allowed to spawn during the night.
nightSpawn: true
# Determines whether phantoms are allowed to spawn during thunder storms.
stormSpawn: true
# Determines what the maximum light level is that phantoms can spawn to.
maximumLightSpawn: 7
PlayerRevive:
enabled: true
PlayerStatistics:
enabled: true
#Alters end portals to send you to a target dimension instead of just the end
#This is a very specific hack made for CivMC, it is not recommended to enable
#Unless you want to fully mimic their behaviour
PortalModifyHack:
enabled: false
targetWorld: world_nether
homeWorld: world
PortalSpawnModifier:
enabled: true
pigManChance: 0.5
ghastChance: 0.07
witherSkeletonChance: 0.05
witherHeadDropChance: 0.04
SafeOreBreak:
enabled: true
ores:
- [DIAMOND_ORE,DEEPSLATE_DIAMOND_ORE]
- [COAL_ORE,DEEPSLATE_COAL_ORE]
- [REDSTONE_ORE,DEEPSLATE_REDSTONE_ORE]
- [LAPIS_ORE,DEEPSLATE_LAPIS_ORE]
ShipOutOfLuck:
enabled: true
# The list of Material's that boats cannot be placed on and will eject passengers
boatBreakers:
- ICE
- PACKED_ICE
- FROSTED_ICE
- BLUE_ICE
StrayStats:
enabled: true
TestConfigHack:
enabled: true
# Please do not change any of the values below, otherwise it will undermine the test.
boolValue: true
byteValue: 8
shortValue: 14646
intValue: 33822776
longValue: 2486941267899177
maybeLongValue: 6543
floatValue: 3847.1234
doubleValue: 345978623.178569873
charValue: 'c'
############################################################
## Simple Hacks
############################################################
BadBoyWatch:
enabled: true
trackingDepth: 16
clearOnMatch: true
watchedMaterials:
- CHEST
- FURNACE
- DROPPER
- DISPENSER
- JUKEBOX
- NOTE_BLOCK
- BEACON
- ENDER_CHEST
- SPONGE
- HOPPER
- PISTON_BASE
- PISTON_STICKY_BASE
- OBSERVER
CTAnnounce:
enabled: false
delay: 10000
message: '%Victim% was combat tagged by %Attacker%'
broadcast: [OP, CONSOLE]
DisableAI:
enabled: true
# Specify below the living entities and their spawning circumstances you want to disable the AI for.
# NOTE: See https://papermc.io/javadocs/paper/1.16/org/bukkit/entity/EntityType.html
# NOTE: See https://papermc.io/javadocs/paper/1.16/org/bukkit/event/entity/CreatureSpawnEvent.SpawnReason.html
# Note: You can also just state "ALL" for all spawn circumstances
VILLAGER: ALL
ENDERMITE: [DISPENSE_EGG, EGG, SPAWNER_EGG]
ElytraFeatures:
enabled: true
# Whether Elytra flight should be outright disabled
disableFlight: false
# Whether Elytra flight should be disabled in combat
disableFlightInCombat: false
# Whether firework boosting should be outright disabled
disableFireworkBoosting: false
# Whether firework boosting should be disabled in combat
disableFireworkBoostingInCombat: false
# Whether to restrict firework boosting to explosive fireworks
disableSafeFireworkBoosting: false
# This regulates player damage when Elytra flying above world height.
heightDamage:
# The amount of damage (in half-hearts) the flier should take.
# NOTE: Setting to 0 will disable the damage.
damage: 1
# The damage is multiplied by the number of blocks above world height multiplied by this number.
scales: 1.0
# The amount of blocks above height limit where damage is not applied.
buffer: 5
# The interval (in milliseconds) between damage ticks.
interval: 1000
Experimental:
enabled: true
combatspy: false
teleportspy: false
postteleport:
spy: false
count: 10
GameFeatures:
enabled: true
disablePotatoXP: false
# Villager trading is not like above and true = able to trade and false = not able
villagerTrading: false
# Wither spawn is also true = enabled and false = disabled
witherSpawning: false
# Prevent or allow pillager patrols from spawning
# Patrols spawning is also true = enabled and false = disabled
patrolSpawning: false
# Phantom spawn is also true = enabled and false = disabled
phantomSpawning: false
# Enderchests is also true = enabled and false = disabled
enderChestPlacement: true
enderChestUse: false
chorusFruitTeleportation: false
# WeepingAngel bans people when they die, the numbers are in milliseconds.
weepingAngel:
enabled: false
environment: 60000 # 1 Minute
playerKill: 300000 # 5 Minutes
# prevent use of buckets or dispensing water in hell biomes
blockWaterInHell: true
minecartTeleport: true
obsidianGenerators: true
personalDeathMessages: true
disableNetheriteCrafting: true
goldBlockTeleport: false
GameFixes:
enabled: true
# Does not fully work atm
blockElytraBreakBug: false
damageOnElytraBreakBug: 1.0
canStorageTeleport: false
stopHopperDupe: true
stopRailDupe: true
stopEndPortalDeletion: true
stopBedBombingInHellBiomes: true
stopAnchorBombing: true
preventTreeWraparound: true
maintainFlatBedrock: false
fixPearlGlitch: true
preventLongSigns: true
signLengthLimit: 100
preventLongSignsAbsolute: true
cancelLongSignEvent: false
hardLimitBookPageCount: true
GameTuning:
enabled: true
chunkLimits:
enabled: true
exceededMessage: '&9Limit reached for this chunk, you cannot place more of this type of block.'
tileEntities:
HOPPER: 4
PISTON_BASE: 4
PISTON_STICKY_BASE: 4
DISPENSER: 4
DROPPER: 4
REDSTONE_COMPARATOR: 4
OBSERVER: 2
exempt:
- Steve
- Alex
daytimeBed:
enabled: true
spawnSetMessage: '&7Your spawn has been set.'
# Makes nether ratio 1 to 1
oneToOneNether: false
# Return nether portal, true = enabled false = disabled
returnNetherPortal: true
allowNetherTravel: false
# Disables minecart inventories (trying to fix dupe issues)
# enabled = able to right click disabled = not able to right click
chestedMinecartInventories: true
hopperMinecartInventories: true
enderChestInventories: false
stopTrapHorses: true
killTrapHorses: true
changeSpawnerType: false
allowVillagerTrading: false
enderGrief: false
witherGrief: false
dragonGrief: true
allowEnchantedApples: false
preventFallingThroughBedrock: true
# Disable the Bad Omen effect that player receive when killing pillager patrols
badOmen: false
#a list of materials players shouldn't be allowed to place at all
noplace:
- DRAGON_EGG
rainReduction:
enabled: false
rainOccurrenceChance: .5
HorseStats:
enabled: false
wand: COMPASS
Insight:
enabled: false
into:
- org.bukkit.event.player
- org.bukkit.event.entity
- org.bukkit.event.block
- org.bukkit.event.inventory
- org.bukkit.event.enchantment
- org.bukkit.event.hanging
- org.bukkit.event.painting
- org.bukkit.event.vehicle
- org.bukkit.event.weather
- org.bukkit.event.world
Introbook:
enabled: true
follow: true
contents:
title: Welcome, ${player}
author: Bonkill
pages:
- " &oWelcome to Devoted, ${player}!"
- " &oWhat is Devoted?\n\"A new type of Minecraft server built towards providing a competitive, high stakes, immersive experience controlled by the players.\"&r\n-ShadedJon, server owner"
- " &oWhat is Devoted?&r\n&lNOT just survival&r\nAdmins are hands-off and only handle glitches and hackers\n&lNOT just chaos&r\nPlugins allow players and towns to enforce their own rules\n&lNOT just roleplay&r\nNobody pretends -- conflict is genuine and heated"
- " &oBasic Mechanics&r\n-The world is custom generated\n-Many custom biomes\n-Limited chat range\n-Respawn in random area unless you sleep\n-Mobs spawn sparsely\n-Vanilla Nether\n-Ore veins and custom ore spawning."
- " &oDevoted Mods&r\n &lCitadel&r:\n Reinforces things so it takes numerous breaks to destroy them. Locks some things too.\n&lMore Info:&r &oVisual Guide:&r imgur.com/BnlL2 &oWiki Page:&r tinyurl.com/citadelmod\n&oType \"/help citadel\" in chat to get commands"
- " &lPrison Pearl&r:\n Ender Pearls trap players in the end. Others can steal back your pearl and free you -- you always know where it is so they cannot hide it\n&lMore Info:&r &oVisual Guide:&r imgur.com/XbhkK &oWiki Page:&r tinyurl.com/prisperl"
- " &lJuke Alert&r:\n Creates 'Juke' blocks that record player activity in radius. If you steal, grief, or trespass -- people will know about it & put a bounty for you to be pearled.\n&lMore Info:&r &oWiki Page:&r tinyurl.com/snitchblock"
- " &lMythicMobs&r:\n Some custom mobs to increase the risk and thrill on specific islands and areas throughout Devoted.\n&lMore Info:&r &oBukkit Page:&r dev.bukkit.org/bukkit-plugins/mythicmobs"
- " &lItem Exchange&r:\n It's a minecraft shop mod that enables different chests (and other inventory blocks) to perform an exchange of items with a player.\n&lMore Info:&r &oWiki Page:&r tinyurl.com/itemxchng"
- " &lHumbug&r:\n Disables some features of minecraft -- see wiki for short list. Please read it to ensure you don't waste resources on a useless block.\n&lMore Info:&r &oWiki Page:&r tinyurl.com/humbugwiki"
- " &lRealistic Biomes&r:\n Biomes are large, crops grow different in different biomes. Hit ground with seed to see growth rate. Farms need sunlight. Crops grow with nobody around.\n&lMore Info:&r &oWiki Page:&r tinyurl.com/realbiome"
- " &lArthropod Egg&r:\n Allows you to capture passive mobs into spawn eggs using Bane of Arthropod enchantment.\n&lMore Info:&r &oGithub Page:&r tinyurl.com/arthrop"
- " &lBrewery&r:\n Custom brewing of booze and more.\n&lMore Info:&r &oBukkit Page:&r tinyurl.com/bukkitbrewp"
- " &oFurther Info&r\nVisit our subreddit at: &oreddit.com/r/Devoted&r"
- "We extend our appreciation to the devs, admins, and players at: &oreddit.com/r/Civcraft"
InvControl:
enabled: true
NewfriendAssist:
enabled: true
announce: '&f%Player% is brand new!'
giveIntroKitToRandomSpawners: true
broadcast: [PERM, CONSOLE]
helptips: on
helptips_end: 20m
introkit:
enabled: true
contents:
- ==: org.bukkit.inventory.ItemStack
v: 1
type: COOKIE
amount: 32
meta:
==: ItemMeta
meta-type: UNSPECIFIC
display-name: 'Manna'
lore:
- "Gift from the Admins as you"
- "begin your journey on Devotion."
- ==: org.bukkit.inventory.ItemStack
v: 1
type: RED_BED
amount: 1
meta:
==: ItemMeta
meta-type: UNSPECIFIC
lore:
- "This world is unforgiving."
- "Be sure to get a good night's"
- "rest soon."
OneTimeTeleport:
enabled: true
# Item materials that should be removed from the player upon teleporting
material_blacklist: []
# Blocks that will block OTT if in range of the target player
unsafe_materials: [LAVA, WATER]
# How long newfriends will have to use OTT before it expires
ott_timeout: 2d
# Whether or not to limit OTT to same-world teleports
limitToSameWorld: false
ReinforcedChestBreak:
enabled: true
# in seconds
delay: 180
message: "&4%player% is raiding a chest at %x% %y% %z%."
#Used to roll a dice on whether to drop an additional sapling on block break or leaf decay
SaplingHack:
enabled: true
#Should we still roll the dice for people when using a fortune enchanted tool?
allow_fortune: true
blocks:
JUNGLE_LEAVES: 0.5
DARK_OAK_LEAVES: 0.5
SanityHack:
enabled: true
trackPlace: true
trackBreak: true
belowYLevel: 7
#Currently changes a striders speed to be between 0.175 (Default) and 0.3375 (Horse max speed)
#aswell as default health to between 20 (Default) and 50 (2.5 heart bars)
#Only works on newly spawned striders, not pre-existing mobs
StriderBreeding:
enabled: true
#Default strider speed is 0.175
minSpeed: 0.175
#Max horse speed is 0.3375
maxSpeed: 0.3375
#10 Hearts
minHealth: 20
#25 Hearts
maxHealth: 50
TimingsHack:
enabled: true
ToggleLamp:
enabled: false
cooldownTime: 100
BetterRails:
enabled: true
# All in metres per second
# Minecraft will prevent you going faster than 30m/s
base: 11
materials:
COBBLESTONE: 8
COPPER_BLOCK: 29
EXPOSED_COPPER: 23
WEATHERED_COPPER: 18
OXIDIZED_COPPER: 14
skyBase: 1
skyMaterials:
COBBLESTONE: 0
COPPER_BLOCK: 1
EXPOSED_COPPER: 1
WEATHERED_COPPER: 1
OXIDIZED_COPPER: 1
CopperRail:
enabled: true
deoxidise: true
# Chance to oxidise copper one level.
# Unoxidised copper will oxidise at 75% of this level.
damage: 0.03
FasterHorses:
enabled: true
minSpeed: 0.1125
maxSpeed: 0.438827582278 # 18.5m/s
Loaded 100 of 126 files, more files were not shown because too many files have changed in this diff. Show more